From 29f25fbe033e97f74123f2380d6e384ba840d0da Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Mon, 10 Jul 2017 12:26:35 +0800 Subject: [PATCH 001/295] Add pixel softmax layer for FCN model 1. Add switch function for switching image dimensions order 2. Add CpuMatrix::backwardSoftmax function 3. Add pixel softmax layer, python wrapper and grad_test --- paddle/function/CMakeLists.txt | 1 + paddle/function/SwitchOp.cpp | 132 ++++++++++++++++++ paddle/function/SwitchOp.h | 62 ++++++++ paddle/function/SwitchOpGpu.cu | 80 +++++++++++ paddle/function/SwitchOpTest.cpp | 44 ++++++ paddle/gserver/layers/PixelSoftmaxLayer.cpp | 89 ++++++++++++ paddle/gserver/layers/PixelSoftmaxLayer.h | 44 ++++++ paddle/gserver/tests/test_LayerGrad.cpp | 19 +++ paddle/math/Matrix.cpp | 21 +++ paddle/math/Matrix.h | 5 + python/paddle/trainer/config_parser.py | 16 +++ .../paddle/trainer_config_helpers/layers.py | 38 +++++ 12 files changed, 551 insertions(+) create mode 100644 paddle/function/SwitchOp.cpp create mode 100644 paddle/function/SwitchOp.h create mode 100644 paddle/function/SwitchOpGpu.cu create mode 100644 paddle/function/SwitchOpTest.cpp create mode 100644 paddle/gserver/layers/PixelSoftmaxLayer.cpp create mode 100644 paddle/gserver/layers/PixelSoftmaxLayer.h diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 1518a8a654..138f7dcf16 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(SwitchOpTest) endif() add_simple_unittest(ConvOpTest) diff --git a/paddle/function/SwitchOp.cpp b/paddle/function/SwitchOp.cpp new file mode 100644 index 0000000000..4667c4e01d --- /dev/null +++ b/paddle/function/SwitchOp.cpp @@ -0,0 +1,132 @@ +/* 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 "SwitchOp.h" +#include "paddle/math/Vector.h" + +namespace paddle { + +template <> +void NCHW2NHWC(real* outputs, + const real* inputs, + const int num, + const int inC, + const int inH, + const int inW) { + for (int n = 0; n < num; ++n) { + for (int c = 0; c < inC; ++c) { + for (int h = 0; h < inH; ++h) { + for (int w = 0; w < inW; ++w) { + outputs[((n * inH + h) * inW + w) * inC + c] = *(inputs++); + } + } + } + } +} + +template <> +void NHWC2NCHW(real* outputs, + const real* inputs, + const int num, + const int inH, + const int inW, + const int inC) { + for (int n = 0; n < num; ++n) { + for (int h = 0; h < inH; ++h) { + for (int w = 0; w < inW; ++w) { + for (int c = 0; c < inC; ++c) { + outputs[((n * inC + c) * inH + h) * inW + w] = *(inputs++); + } + } + } + } +} + +/** + * \brief Padding zeros to input according to the specify dimension. + * The struct pad_ contains the padding size in each dimension. + * The input and output is a 4D tensor. In PadFunc, we only + * pad zeros to the 2nd to 4th dimension. + * + * Argument in this Function: + * \param pad_ A struct object contains the padding size in each dimension. + * It has six integers. The channelStart and channelEnd indicate + * how many zeros to add before and after the input in channel + * dimension. And the heightStart and heightEnd indicate padding + * in height dimension. The widthStart and widthEnd indicate the + * padding in width dimension. + * \param inputs A 4D tensor, only one input. + * \param outputs A 4D tensor, the output value after padding. + * + */ + +template +class NCHW2NHWCFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override {} + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + + size_t num = inputs[0].shape()[0]; + size_t inC = inputs[0].shape()[1]; + size_t inH = inputs[0].shape()[2]; + size_t inW = inputs[0].shape()[3]; + typename Tensor::Vector vec(outputs[0].shape().getElements(), + outputs[0].data()); + vec.zero(); + + NCHW2NHWC( + outputs[0].data(), inputs[0].data(), num, inC, inH, inW); + } +}; + +/** + * \brief The backward propagation of padding Function. Remove the elements + * in the padding positions of forward. + * + * Argument in this Function: + * \param pad_ The same meaning as it in PadFunc. + * \param inputs The gradient with respect to the output value of PadFunc. + * \param outputs The gradient with respect to the input value of PadFunc. + */ + +template +class NHWC2NCHWFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override {} + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + + size_t num = inputs[0].shape()[0]; + size_t inH = inputs[0].shape()[1]; + size_t inW = inputs[0].shape()[2]; + size_t inC = inputs[0].shape()[3]; + + NHWC2NCHW( + outputs[0].data(), inputs[0].data(), num, inH, inW, inC); + } +}; + +REGISTER_TYPED_FUNC(NCHW2NHWC, CPU, NCHW2NHWCFunc); +REGISTER_TYPED_FUNC(NHWC2NCHW, CPU, NHWC2NCHWFunc); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(NCHW2NHWC, GPU, NCHW2NHWCFunc); +REGISTER_TYPED_FUNC(NHWC2NCHW, GPU, NHWC2NCHWFunc); +#endif + +} // namespace paddle diff --git a/paddle/function/SwitchOp.h b/paddle/function/SwitchOp.h new file mode 100644 index 0000000000..5a2418a703 --- /dev/null +++ b/paddle/function/SwitchOp.h @@ -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. */ + +#pragma once + +#include "Function.h" + +namespace paddle { + +/** + * \brief This funtion switch dimension order of image input. + * The input and output is a 4D tensor. Switch order 'batch_size, + *channels, height, width' to + * order 'batch_size, height, width, channels'. + * + * \param[out] outputs save results. + * \param[in] inputs input data. + * \param[in] num batch size of input data. + * \param[in] inC channel number of input data. + * \param[in] inH height of input data. + * \param[in] inH with of input data. + */ +template +void NCHW2NHWC(real* outputs, + const real* inputs, + const int num, + const int inC, + const int inH, + const int inW); + +/** + * \brief This funtion switch dimension order of image input. + * The input and output is a 4D tensor. Switch order 'batch_size, + *height, width, channels' to + * order 'batch_size, channels, height, width'. + * + * \param[out] inGrad gradients of previous layer. + * \param[in] outGrad output gradients. + * \param[in] num batch size of input data. + * \param[in] inH height of input data. + * \param[in] inW with of input data. + * \param[in] inC channel number of input data. + */ +template +void NHWC2NCHW(real* inGrad, + const real* outGrad, + const int num, + const int inH, + const int inW, + const int inC); +} // namespace paddle diff --git a/paddle/function/SwitchOpGpu.cu b/paddle/function/SwitchOpGpu.cu new file mode 100644 index 0000000000..c2020cb2ab --- /dev/null +++ b/paddle/function/SwitchOpGpu.cu @@ -0,0 +1,80 @@ +/* Copyright (c) 2016 Paddle + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 "SwitchOp.h" + +namespace paddle { + +__global__ void KeNCHW2NHWC(real* outputs, const real* inputs, + int inC, int inH, int inW, + 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 * inH + h) * inW + w) * inC +c; + outputs[off] = inputs[idx]; + } +} + +template <> +void NCHW2NHWC(real* outputs, + const real* inputs, + const int num, + const int inC, + const int inH, + const int inW) { + size_t nth = num * inC * inH * inW; + int blockSize = 1024; + int gridSize = (nth + 1024 - 1) / 1024; + KeNCHW2NHWC<<>> + (outputs, inputs, inC, inH, inW, nth); + CHECK_SYNC("NCHW2NHWC"); +} + +__global__ void KeNHWC2NCHW(real* outputs, const real* inputs, + int inH, int inW, int inC, + int nthreads) { + const int idx = threadIdx.x + blockIdx.x * blockDim.x; + if (idx < nthreads) { + const int c = idx % inC; + const int w = (idx / inC) % inW; + const int h = (idx / inC / inW) % inH; + const int n = idx / inW / inH / inC; + + const int off = ((n * inC + c) * inH + h) * inW + w; + outputs[off] = inputs[idx]; + } +} + +template <> +void NHWC2NCHW(real* outputs, + const real* inputs, + const int num, + const int inH, + const int inW, + const int inC) { + int nth = num * inC * inH * inW; + int blockSize = 1024; + int gridSize = (nth + 1024 - 1) / 1024; + KeNHWC2NCHW<<>> + (outputs, inputs, inH, inW, inC, nth); + CHECK_SYNC("NHWC2NCHW"); +} + +} // namespace paddle diff --git a/paddle/function/SwitchOpTest.cpp b/paddle/function/SwitchOpTest.cpp new file mode 100644 index 0000000000..03b0dd66dd --- /dev/null +++ b/paddle/function/SwitchOpTest.cpp @@ -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. */ + +#include +#include "FunctionTest.h" + +namespace paddle { + +TEST(Pad, real) { + for (size_t numSamples : {1, 4, 8, 16}) { + for (size_t channels : {1, 4, 8, 16}) { + for (size_t imgSizeH : {1, 4, 8, 16}) { + for (size_t imgSizeW : {1, 4, 8, 16}) { + VLOG(3) << " numSamples=" << numSamples << " channels=" << channels + << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; + for (bool test_grad : {true, false}) { + CpuGpuFuncCompare compare(test_grad ? "NHWC2NCHW" : "NCHW2NHWC", + FuncConfig()); + TensorShape inDims{numSamples, channels, imgSizeH, imgSizeW}; + TensorShape outDims{numSamples, imgSizeH, imgSizeW, channels}; + 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/PixelSoftmaxLayer.cpp b/paddle/gserver/layers/PixelSoftmaxLayer.cpp new file mode 100644 index 0000000000..6da84a6303 --- /dev/null +++ b/paddle/gserver/layers/PixelSoftmaxLayer.cpp @@ -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. */ + +#include "PixelSoftmaxLayer.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +REGISTER_LAYER(pixel_softmax, PixelSoftmaxLayer); + +bool PixelSoftmaxLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parent class */ + Layer::init(layerMap, parameterMap); + auto& img_conf = config_.inputs(0).image_conf(); + inH_ = + img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(); + inW_ = img_conf.img_size(); + inC_ = img_conf.channels(); + createFunction(forward_, "NCHW2NHWC", FuncConfig()); + createFunction(backward_, "NHWC2NCHW", FuncConfig()); + inDims_ = TensorShape({0, inH_, inW_, inC_}); + outDims_ = TensorShape({0, inC_, inH_, inW_}); + return true; +} + +void PixelSoftmaxLayer::forward(PassType passType) { + Layer::forward(passType); + MatrixPtr input = inputLayers_[0]->getOutputValue(); + size_t batchSize = input->getHeight(); + // cout<<"useGpu:"<zeroMem(); + resetOutput(batchSize, inH_ * inW_ * inC_); + inDims_.setDim(0, batchSize); + outDims_.setDim(0, batchSize); + + // switch NCHW to NHWC + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inDims_); + outputs.addArg(*tmpInput_, outDims_); + forward_[0]->calc(inputs, outputs); + // softmax forward and save softmax result into tmpMatrix_ + tmpInput_->softmax(*tmpOutput_); + + // switch NHWC to NCHW + BufferArgs inputs_1; + BufferArgs outputs_1; + inputs_1.addArg(*tmpOutput_, outDims_); + outputs_1.addArg(*getOutputValue(), inDims_); + backward_[0]->calc(inputs_1, outputs_1); +} + +void PixelSoftmaxLayer::backward(const UpdateCallback& callback) { + (void)callback; + REGISTER_TIMER_INFO("PixelSoftmaxBackward", getName().c_str()); + + // switch NCHW to NHWC + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), inDims_); + outputs.addArg(*tmpInput_, outDims_); + forward_[0]->calc(inputs, outputs); + // softmax backward and save grad result into tmpOutput_ + tmpInput_->softmaxBackward(*tmpOutput_); + + // switch NHWC to NCHW + BufferArgs inputs_1; + BufferArgs outputs_1; + inputs_1.addArg(*tmpInput_, outDims_); + outputs_1.addArg(*getInputGrad(0), inDims_); + backward_[0]->calc(inputs_1, outputs_1); +} +} // namespace paddle diff --git a/paddle/gserver/layers/PixelSoftmaxLayer.h b/paddle/gserver/layers/PixelSoftmaxLayer.h new file mode 100644 index 0000000000..80a4ddad5a --- /dev/null +++ b/paddle/gserver/layers/PixelSoftmaxLayer.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 "Layer.h" + +namespace paddle { + +/** + * \brief This layer calculate softmax in image channel dimension. + */ +class PixelSoftmaxLayer : public Layer { +public: + explicit PixelSoftmaxLayer(const LayerConfig& config) : Layer(config) {} + + ~PixelSoftmaxLayer() {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + void forward(PassType passType) override; + void backward(const UpdateCallback& callback = nullptr) override; + +protected: + uint32_t inC_; + uint32_t inH_; + uint32_t inW_; + TensorShape inDims_; + TensorShape outDims_; + MatrixPtr tmpInput_; + MatrixPtr tmpOutput_; +}; +} // namespace paddle diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 59d1e9273d..8a9904087e 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1792,6 +1792,25 @@ TEST(Layer, RowConvLayer) { } } +TEST(Layer, PixelSoftmaxLayer) { + 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); + img->set_img_size_y(16); + + // config softmax layer + config.layerConfig.set_type("pixel_softmax"); + config.layerConfig.set_name("pixelSofrmaxLayer"); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "pixel_softmax", 100, false, useGpu, true, 2); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 4431d613f6..2c18df3732 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -3385,6 +3385,27 @@ void CpuMatrix::oneHotCrossEntropyWithSelfNormBp(Matrix& output, real* out = output.getData(); \ for (size_t i = 0; i < numSamples; ++i, grad += dim, out += dim) +void CpuMatrix::softmaxBackward(Matrix& outputV) { + CHECK(!outputV.useGpu()) << "Matrix type are not equal"; + size_t height = getHeight(); + size_t width = getWidth(); + CHECK(height == outputV.getHeight() && width == outputV.getWidth()) + << "Matrix dimensions are not equal"; + Matrix::resizeOrCreate(sftmaxDot_, + height_, + width_, + /* trans */ false, + useGpu_); + Matrix::resizeOrCreate(sftmaxSum_, + height_, + 1, + /* trans */ false, + useGpu_); + sftmaxDot_->dotMul(*this, outputV); + sftmaxSum_->colMerge(*sftmaxDot_); + softmaxDerivative(outputV, *sftmaxSum_); +} + void CpuMatrix::softmax(Matrix& output) { CHECK(!output.useGpu()); diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 7dfd593225..dcb63a2d3f 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -1456,6 +1456,10 @@ public: }; class CpuMatrix : public Matrix { +private: + MatrixPtr sftmaxSum_; + MatrixPtr sftmaxDot_; + public: CpuMatrix(size_t height, size_t width, bool trans = false); CpuMatrix(real* data, size_t height, size_t width, bool trans = false) @@ -1728,6 +1732,7 @@ public: Matrix& prevGrad2); void softmax(Matrix& output); + void softmaxBackward(Matrix& outputV); void sequenceSoftmax(Matrix& output, const IVector& index); void softmaxDerivative(Matrix& output, Matrix& sftmaxSum); diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 370529ed97..dc9c503e0b 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3171,6 +3171,22 @@ class RecurrentLayerGroup(LayerBase): name, 'recurrent_layer_group', 0, inputs=[], device=device) +@config_layer('pixel_softmax') +class PixelSoftmaxLayer(LayerBase): + def __init__(self, input, name, **xargs): + super(PixelSoftmaxLayer, self).__init__( + name, 'pixel_softmax', 0, inputs=inputs, **xargs) + + 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) + self.set_cnn_layer(name, image_conf.img_size_y, image_conf.img_size, + image_conf.channels) + + # Deprecated, use a new layer specific class instead @config_func def Layer(name, type, **xargs): diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 206de1f8e1..fdac5984b0 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' + PIXEL_SOFTMAX_LAYER = 'pixel_softmax' @staticmethod def is_layer_type(type_name): @@ -5853,3 +5854,40 @@ def prelu_layer(input, layer_type=LayerType.PRELU, parents=input, size=l.config.size) + + +@layer_support() +@wrap_name_default('pixel_softmax') +def pixel_softmax_layer(input, name=None, layer_attr=None): + """ + This layer calculate softmax in image channel dimension + + The example usage is: + + .. code-block:: python + + prelu = pixel_softmax(input=layer, name='softmax') + + :param name: Name of this layer. + :type name: basestring + :param input: The input layer. + :type input: LayerOutput + :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], + name=name, + type=LayerType.PIXEL_SOFTMAX_LAYER, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name=name, + layer_type=LayerType.PIXEL_SOFTMAX_LAYER, + parents=input, + size=l.config.size) From 0152d97e6344fbf866d75bf24f6f6034a81f5e81 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Tue, 11 Jul 2017 10:23:29 +0800 Subject: [PATCH 002/295] fix pixel softmax python wrapper bug --- python/paddle/trainer/config_parser.py | 2 +- python/paddle/trainer_config_helpers/layers.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index c24af47c4b..261e834e11 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3176,7 +3176,7 @@ class RecurrentLayerGroup(LayerBase): @config_layer('pixel_softmax') class PixelSoftmaxLayer(LayerBase): - def __init__(self, input, name, **xargs): + def __init__(self, name, inputs, **xargs): super(PixelSoftmaxLayer, self).__init__( name, 'pixel_softmax', 0, inputs=inputs, **xargs) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index d8cc52d409..2f8b0d1002 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -126,6 +126,7 @@ __all__ = [ 'row_conv_layer', 'dropout_layer', 'prelu_layer', + 'pixel_softmax_layer', ] @@ -5905,8 +5906,8 @@ def pixel_softmax_layer(input, name=None, layer_attr=None): else: assert isinstance(input, collections.Sequence) l = Layer( - inputs=[x.name for x in input], name=name, + inputs=[x.name for x in input], type=LayerType.PIXEL_SOFTMAX_LAYER, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( From 1cdf149b6fccf4fba030f0bb847965500960fa9b Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 19 Jul 2017 12:50:45 +0800 Subject: [PATCH 003/295] 1. delete PixelSoftmaxLayer and add SwitchOrderLayer 2. Make SwitchOrderLayer support for softmax activation 3. Fix bugs --- CMakeLists.txt | 2 +- paddle/function/SwitchOp.cpp | 72 ++++++----- paddle/function/SwitchOp.h | 8 +- paddle/function/SwitchOpGpu.cu | 26 ++-- paddle/gserver/layers/PixelSoftmaxLayer.cpp | 89 -------------- paddle/gserver/layers/SwitchOrderLayer.cpp | 112 ++++++++++++++++++ ...PixelSoftmaxLayer.h => SwitchOrderLayer.h} | 19 +-- paddle/gserver/tests/test_LayerGrad.cpp | 14 ++- paddle/math/Matrix.cpp | 21 ---- paddle/math/Matrix.h | 1 - proto/ModelConfig.proto | 8 ++ python/paddle/trainer/config_parser.py | 21 ++-- .../paddle/trainer_config_helpers/layers.py | 36 +++--- 13 files changed, 231 insertions(+), 198 deletions(-) delete mode 100644 paddle/gserver/layers/PixelSoftmaxLayer.cpp create mode 100644 paddle/gserver/layers/SwitchOrderLayer.cpp rename paddle/gserver/layers/{PixelSoftmaxLayer.h => SwitchOrderLayer.h} (71%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15a7c6b074..fdc62b3151 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ # limitations under the License cmake_minimum_required(VERSION 3.0) - +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ldl -lpthread") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) set(PROJ_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/paddle/function/SwitchOp.cpp b/paddle/function/SwitchOp.cpp index 4667c4e01d..01e252a8dc 100644 --- a/paddle/function/SwitchOp.cpp +++ b/paddle/function/SwitchOp.cpp @@ -23,12 +23,17 @@ void NCHW2NHWC(real* outputs, const int num, const int inC, const int inH, - const int inW) { + const int inW, + const int argType) { for (int n = 0; n < num; ++n) { for (int c = 0; c < inC; ++c) { for (int h = 0; h < inH; ++h) { for (int w = 0; w < inW; ++w) { - outputs[((n * inH + h) * inW + w) * inC + c] = *(inputs++); + if (argType == ADD_TO) { + outputs[((n * inH + h) * inW + w) * inC + c] += *(inputs++); + } else { + outputs[((n * inH + h) * inW + w) * inC + c] = *(inputs++); + } } } } @@ -41,12 +46,17 @@ void NHWC2NCHW(real* outputs, const int num, const int inH, const int inW, - const int inC) { + const int inC, + const int argType) { for (int n = 0; n < num; ++n) { for (int h = 0; h < inH; ++h) { for (int w = 0; w < inW; ++w) { for (int c = 0; c < inC; ++c) { - outputs[((n * inC + c) * inH + h) * inW + w] = *(inputs++); + if (argType == ADD_TO) { + outputs[((n * inC + c) * inH + h) * inW + w] += *(inputs++); + } else { + outputs[((n * inC + c) * inH + h) * inW + w] = *(inputs++); + } } } } @@ -54,23 +64,15 @@ void NHWC2NCHW(real* outputs, } /** - * \brief Padding zeros to input according to the specify dimension. - * The struct pad_ contains the padding size in each dimension. - * The input and output is a 4D tensor. In PadFunc, we only - * pad zeros to the 2nd to 4th dimension. + * \brief Switch dimension order of image input. + * The input and output is a 4D tensor. Switch order + * 'batch_size,channels, height, width' to + * order 'batch_size, height, width, channels'. * * Argument in this Function: - * \param pad_ A struct object contains the padding size in each dimension. - * It has six integers. The channelStart and channelEnd indicate - * how many zeros to add before and after the input in channel - * dimension. And the heightStart and heightEnd indicate padding - * in height dimension. The widthStart and widthEnd indicate the - * padding in width dimension. - * \param inputs A 4D tensor, only one input. - * \param outputs A 4D tensor, the output value after padding. - * + * \param inputs input data with order 'batch_size,channels, height, width'. + * \param outputs output data with order 'batch_size, height, width, channels'. */ - template class NCHW2NHWCFunc : public FunctionBase { public: @@ -84,25 +86,26 @@ public: size_t inC = inputs[0].shape()[1]; size_t inH = inputs[0].shape()[2]; size_t inW = inputs[0].shape()[3]; - typename Tensor::Vector vec(outputs[0].shape().getElements(), - outputs[0].data()); - vec.zero(); - - NCHW2NHWC( - outputs[0].data(), inputs[0].data(), num, inC, inH, inW); + NCHW2NHWC(outputs[0].data(), + inputs[0].data(), + num, + inC, + inH, + inW, + outputs[0].getArgType()); } }; /** - * \brief The backward propagation of padding Function. Remove the elements - * in the padding positions of forward. + * \brief Switch dimension order of image input. + * The input and output is a 4D tensor. Switch order + * 'batch_size, height, width, channels' to + * order 'batch_size, channels, height, width'. * * Argument in this Function: - * \param pad_ The same meaning as it in PadFunc. - * \param inputs The gradient with respect to the output value of PadFunc. - * \param outputs The gradient with respect to the input value of PadFunc. + * \param inputs input data with order 'batch_size, height, width, channels'. + * \param outputs output data with order 'batch_size, channels, height, width'. */ - template class NHWC2NCHWFunc : public FunctionBase { public: @@ -117,8 +120,13 @@ public: size_t inW = inputs[0].shape()[2]; size_t inC = inputs[0].shape()[3]; - NHWC2NCHW( - outputs[0].data(), inputs[0].data(), num, inH, inW, inC); + NHWC2NCHW(outputs[0].data(), + inputs[0].data(), + num, + inH, + inW, + inC, + outputs[0].getArgType()); } }; diff --git a/paddle/function/SwitchOp.h b/paddle/function/SwitchOp.h index 5a2418a703..e4c1c3ac92 100644 --- a/paddle/function/SwitchOp.h +++ b/paddle/function/SwitchOp.h @@ -30,6 +30,7 @@ namespace paddle { * \param[in] inC channel number of input data. * \param[in] inH height of input data. * \param[in] inH with of input data. + * \param[in] argType type of output argument. */ template void NCHW2NHWC(real* outputs, @@ -37,7 +38,8 @@ void NCHW2NHWC(real* outputs, const int num, const int inC, const int inH, - const int inW); + const int inW, + const int argtype); /** * \brief This funtion switch dimension order of image input. @@ -51,6 +53,7 @@ void NCHW2NHWC(real* outputs, * \param[in] inH height of input data. * \param[in] inW with of input data. * \param[in] inC channel number of input data. + * \param[in] argType type of output argument. */ template void NHWC2NCHW(real* inGrad, @@ -58,5 +61,6 @@ void NHWC2NCHW(real* inGrad, const int num, const int inH, const int inW, - const int inC); + const int inC, + const int argType); } // namespace paddle diff --git a/paddle/function/SwitchOpGpu.cu b/paddle/function/SwitchOpGpu.cu index c2020cb2ab..0b9401dea1 100644 --- a/paddle/function/SwitchOpGpu.cu +++ b/paddle/function/SwitchOpGpu.cu @@ -19,7 +19,7 @@ namespace paddle { __global__ void KeNCHW2NHWC(real* outputs, const real* inputs, int inC, int inH, int inW, - int nthreads) { + int nthreads, int argType) { const int idx = threadIdx.x + blockIdx.x * blockDim.x; if (idx < nthreads) { const int w = idx % inW; @@ -28,7 +28,11 @@ __global__ void KeNCHW2NHWC(real* outputs, const real* inputs, const int n = idx / inW / inH / inC; const int off = ((n * inH + h) * inW + w) * inC +c; - outputs[off] = inputs[idx]; + if (argType == ADD_TO) { + outputs[off] += inputs[idx]; + } else { + outputs[off] = inputs[idx]; + } } } @@ -38,18 +42,19 @@ void NCHW2NHWC(real* outputs, const int num, const int inC, const int inH, - const int inW) { + const int inW, + const int argType) { size_t nth = num * inC * inH * inW; int blockSize = 1024; int gridSize = (nth + 1024 - 1) / 1024; KeNCHW2NHWC<<>> - (outputs, inputs, inC, inH, inW, nth); + (outputs, inputs, inC, inH, inW, nth, argType); CHECK_SYNC("NCHW2NHWC"); } __global__ void KeNHWC2NCHW(real* outputs, const real* inputs, int inH, int inW, int inC, - int nthreads) { + int nthreads, int argType) { const int idx = threadIdx.x + blockIdx.x * blockDim.x; if (idx < nthreads) { const int c = idx % inC; @@ -58,7 +63,11 @@ __global__ void KeNHWC2NCHW(real* outputs, const real* inputs, const int n = idx / inW / inH / inC; const int off = ((n * inC + c) * inH + h) * inW + w; - outputs[off] = inputs[idx]; + if (argType == ADD_TO) { + outputs[off] += inputs[idx]; + } else { + outputs[off] = inputs[idx]; + } } } @@ -68,12 +77,13 @@ void NHWC2NCHW(real* outputs, const int num, const int inH, const int inW, - const int inC) { + const int inC, + const int argType) { int nth = num * inC * inH * inW; int blockSize = 1024; int gridSize = (nth + 1024 - 1) / 1024; KeNHWC2NCHW<<>> - (outputs, inputs, inH, inW, inC, nth); + (outputs, inputs, inH, inW, inC, nth, argType); CHECK_SYNC("NHWC2NCHW"); } diff --git a/paddle/gserver/layers/PixelSoftmaxLayer.cpp b/paddle/gserver/layers/PixelSoftmaxLayer.cpp deleted file mode 100644 index 6da84a6303..0000000000 --- a/paddle/gserver/layers/PixelSoftmaxLayer.cpp +++ /dev/null @@ -1,89 +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 "PixelSoftmaxLayer.h" -#include "paddle/utils/Stat.h" - -namespace paddle { - -REGISTER_LAYER(pixel_softmax, PixelSoftmaxLayer); - -bool PixelSoftmaxLayer::init(const LayerMap& layerMap, - const ParameterMap& parameterMap) { - /* Initialize the basic parent class */ - Layer::init(layerMap, parameterMap); - auto& img_conf = config_.inputs(0).image_conf(); - inH_ = - img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(); - inW_ = img_conf.img_size(); - inC_ = img_conf.channels(); - createFunction(forward_, "NCHW2NHWC", FuncConfig()); - createFunction(backward_, "NHWC2NCHW", FuncConfig()); - inDims_ = TensorShape({0, inH_, inW_, inC_}); - outDims_ = TensorShape({0, inC_, inH_, inW_}); - return true; -} - -void PixelSoftmaxLayer::forward(PassType passType) { - Layer::forward(passType); - MatrixPtr input = inputLayers_[0]->getOutputValue(); - size_t batchSize = input->getHeight(); - // cout<<"useGpu:"<zeroMem(); - resetOutput(batchSize, inH_ * inW_ * inC_); - inDims_.setDim(0, batchSize); - outDims_.setDim(0, batchSize); - - // switch NCHW to NHWC - BufferArgs inputs; - BufferArgs outputs; - inputs.addArg(*getInputValue(0), inDims_); - outputs.addArg(*tmpInput_, outDims_); - forward_[0]->calc(inputs, outputs); - // softmax forward and save softmax result into tmpMatrix_ - tmpInput_->softmax(*tmpOutput_); - - // switch NHWC to NCHW - BufferArgs inputs_1; - BufferArgs outputs_1; - inputs_1.addArg(*tmpOutput_, outDims_); - outputs_1.addArg(*getOutputValue(), inDims_); - backward_[0]->calc(inputs_1, outputs_1); -} - -void PixelSoftmaxLayer::backward(const UpdateCallback& callback) { - (void)callback; - REGISTER_TIMER_INFO("PixelSoftmaxBackward", getName().c_str()); - - // switch NCHW to NHWC - BufferArgs inputs; - BufferArgs outputs; - inputs.addArg(*getOutputGrad(), inDims_); - outputs.addArg(*tmpInput_, outDims_); - forward_[0]->calc(inputs, outputs); - // softmax backward and save grad result into tmpOutput_ - tmpInput_->softmaxBackward(*tmpOutput_); - - // switch NHWC to NCHW - BufferArgs inputs_1; - BufferArgs outputs_1; - inputs_1.addArg(*tmpInput_, outDims_); - outputs_1.addArg(*getInputGrad(0), inDims_); - backward_[0]->calc(inputs_1, outputs_1); -} -} // namespace paddle diff --git a/paddle/gserver/layers/SwitchOrderLayer.cpp b/paddle/gserver/layers/SwitchOrderLayer.cpp new file mode 100644 index 0000000000..2a8a9500fa --- /dev/null +++ b/paddle/gserver/layers/SwitchOrderLayer.cpp @@ -0,0 +1,112 @@ +/* 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 "SwitchOrderLayer.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +REGISTER_LAYER(switch_order, SwitchOrderLayer); + +bool SwitchOrderLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parent class */ + Layer::init(layerMap, parameterMap); + auto& img_conf = config_.inputs(0).image_conf(); + size_t inH = + img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(); + size_t inW = img_conf.img_size(); + size_t inC = img_conf.channels(); + inDims_ = TensorShape({0, inC, inH, inW}); + outDims_ = TensorShape(4); + + auto& reshape_conf = config_.reshape_conf(); + for (size_t i = 0; i < reshape_conf.heightaxis_size(); i++) { + LOG(INFO) << "reshape height axis: " << reshape_conf.heightaxis(i); + heightAxis_.push_back(reshape_conf.heightaxis(i)); + } + for (size_t i = 0; i < reshape_conf.widthaxis_size(); i++) { + LOG(INFO) << "reshape width axis: " << reshape_conf.widthaxis(i); + widthAxis_.push_back(reshape_conf.widthaxis(i)); + } + createFunction(nchw2nhwc_, "NCHW2NHWC", FuncConfig()); + createFunction(nhwc2nchw_, "NHWC2NCHW", FuncConfig()); + return true; +} + +void SwitchOrderLayer::setOutDims() { + outDims_.setDim(0, inDims_[0]); + outDims_.setDim(1, inDims_[2]); + outDims_.setDim(2, inDims_[3]); + outDims_.setDim(3, inDims_[1]); + reshapeHeight_ = 1; + for (size_t i = 0; i < heightAxis_.size(); i++) { + reshapeHeight_ *= outDims_[heightAxis_[i]]; + } + output_.setFrameHeight(reshapeHeight_); + reshapeWidth_ = 1; + for (size_t i = 0; i < widthAxis_.size(); i++) { + reshapeWidth_ *= outDims_[widthAxis_[i]]; + } + output_.setFrameWidth(reshapeWidth_); + LOG(INFO) << "outDims: " << outDims_[0] << "; " << outDims_[1] << ";" + << outDims_[2] << ";" << outDims_[3]; +} + +void SwitchOrderLayer::setInDims() { + MatrixPtr input = inputLayers_[0]->getOutputValue(); + size_t batchSize = input->getHeight(); + 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); + int totalCount = input->getElementCnt(); + int channels = totalCount / (inDims_[0] * inDims_[2] * inDims_[3]); + if (channels != 0) inDims_.setDim(1, channels); + LOG(INFO) << "inDims: " << inDims_[0] << "; " << inDims_[1] << ";" + << inDims_[2] << ";" << inDims_[3]; +} + +void SwitchOrderLayer::forward(PassType passType) { + Layer::forward(passType); + setInDims(); + setOutDims(); + resetOutput(outDims_[0], outDims_[1] * outDims_[2] * outDims_[3]); + if (heightAxis_.size() > 0) { + getOutputValue()->reshape(reshapeHeight_, reshapeWidth_); + } + + // switch NCHW to NHWC + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inDims_); + outputs.addArg(*getOutputValue(), outDims_); + nchw2nhwc_[0]->calc(inputs, outputs); + // forwardActivation(); +} + +void SwitchOrderLayer::backward(const UpdateCallback& callback) { + (void)callback; + // backwardActivation(); + + // switch NHWC to NCHW + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outDims_); + outputs.addArg(*getInputGrad(0), inDims_, ADD_TO); + nhwc2nchw_[0]->calc(inputs, outputs); +} +} // namespace paddle diff --git a/paddle/gserver/layers/PixelSoftmaxLayer.h b/paddle/gserver/layers/SwitchOrderLayer.h similarity index 71% rename from paddle/gserver/layers/PixelSoftmaxLayer.h rename to paddle/gserver/layers/SwitchOrderLayer.h index 80a4ddad5a..47b1f7f73e 100644 --- a/paddle/gserver/layers/PixelSoftmaxLayer.h +++ b/paddle/gserver/layers/SwitchOrderLayer.h @@ -21,24 +21,27 @@ namespace paddle { /** * \brief This layer calculate softmax in image channel dimension. */ -class PixelSoftmaxLayer : public Layer { +class SwitchOrderLayer : public Layer { public: - explicit PixelSoftmaxLayer(const LayerConfig& config) : Layer(config) {} + explicit SwitchOrderLayer(const LayerConfig& config) : Layer(config) {} - ~PixelSoftmaxLayer() {} + ~SwitchOrderLayer() {} bool init(const LayerMap& layerMap, const ParameterMap& parameterMap) override; void forward(PassType passType) override; void backward(const UpdateCallback& callback = nullptr) override; + void setInDims(); + void setOutDims(); protected: - uint32_t inC_; - uint32_t inH_; - uint32_t inW_; + std::vector> nchw2nhwc_; + std::vector> nhwc2nchw_; TensorShape inDims_; TensorShape outDims_; - MatrixPtr tmpInput_; - MatrixPtr tmpOutput_; + std::vector heightAxis_; + std::vector widthAxis_; + size_t reshapeHeight_; + size_t reshapeWidth_; }; } // namespace paddle diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 98c9cbe9f5..42c23f0226 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1802,7 +1802,7 @@ TEST(Layer, RowConvLayer) { } } -TEST(Layer, PixelSoftmaxLayer) { +TEST(Layer, SwitchOrderLayer) { TestConfig config; // config input_0 config.inputDefs.push_back({INPUT_DATA, "layer_0", 1024, 0}); @@ -1812,12 +1812,18 @@ TEST(Layer, PixelSoftmaxLayer) { img->set_img_size(16); img->set_img_size_y(16); + ReshapeConfig* reshape = config.layerConfig.mutable_reshape_conf(); + reshape->add_heightaxis(0); + reshape->add_heightaxis(1); + reshape->add_heightaxis(2); + reshape->add_widthaxis(3); + // config softmax layer - config.layerConfig.set_type("pixel_softmax"); - config.layerConfig.set_name("pixelSofrmaxLayer"); + config.layerConfig.set_type("switch_order"); + config.layerConfig.set_name("switchOrderLayer"); for (auto useGpu : {false, true}) { - testLayerGrad(config, "pixel_softmax", 100, false, useGpu, true, 2); + testLayerGrad(config, "switch_order", 100, false, useGpu, true); } } diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 2c18df3732..4431d613f6 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -3385,27 +3385,6 @@ void CpuMatrix::oneHotCrossEntropyWithSelfNormBp(Matrix& output, real* out = output.getData(); \ for (size_t i = 0; i < numSamples; ++i, grad += dim, out += dim) -void CpuMatrix::softmaxBackward(Matrix& outputV) { - CHECK(!outputV.useGpu()) << "Matrix type are not equal"; - size_t height = getHeight(); - size_t width = getWidth(); - CHECK(height == outputV.getHeight() && width == outputV.getWidth()) - << "Matrix dimensions are not equal"; - Matrix::resizeOrCreate(sftmaxDot_, - height_, - width_, - /* trans */ false, - useGpu_); - Matrix::resizeOrCreate(sftmaxSum_, - height_, - 1, - /* trans */ false, - useGpu_); - sftmaxDot_->dotMul(*this, outputV); - sftmaxSum_->colMerge(*sftmaxDot_); - softmaxDerivative(outputV, *sftmaxSum_); -} - void CpuMatrix::softmax(Matrix& output) { CHECK(!output.useGpu()); diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index dcb63a2d3f..20f97a5060 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -1732,7 +1732,6 @@ public: Matrix& prevGrad2); void softmax(Matrix& output); - void softmaxBackward(Matrix& outputV); void sequenceSoftmax(Matrix& output, const IVector& index); void softmaxDerivative(Matrix& output, Matrix& sftmaxSum); diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 37cd16c798..9fd017b23e 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -266,6 +266,11 @@ message PadConfig { repeated uint32 pad_w = 4; } +message ReshapeConfig { + repeated uint32 heightAxis = 1; + repeated uint32 widthAxis = 2; +} + message MultiBoxLossConfig { required uint32 num_classes = 1; required float overlap_threshold = 2; @@ -476,6 +481,9 @@ message LayerConfig { // 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 switch order layer + optional ReshapeConfig reshape_conf = 54; } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 261e834e11..fe06dd812e 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3174,20 +3174,13 @@ class RecurrentLayerGroup(LayerBase): name, 'recurrent_layer_group', 0, inputs=[], device=device) -@config_layer('pixel_softmax') -class PixelSoftmaxLayer(LayerBase): - def __init__(self, name, inputs, **xargs): - super(PixelSoftmaxLayer, self).__init__( - name, 'pixel_softmax', 0, inputs=inputs, **xargs) - - 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) - self.set_cnn_layer(name, image_conf.img_size_y, image_conf.img_size, - image_conf.channels) +@config_layer('switch_order') +class SwitchOrderLayer(LayerBase): + def __init__(self, name, inputs, reshape, **xargs): + super(SwitchOrderLayer, self).__init__( + name, 'switch_order', 0, inputs=inputs, **xargs) + self.conf.reshape_conf.heightAxis_ = reshape['height'] + self.conf.reshape_conf.widthAxis_ = reshape['width'] # Deprecated, use a new layer specific class instead diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 2f8b0d1002..6980a31679 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -126,7 +126,7 @@ __all__ = [ 'row_conv_layer', 'dropout_layer', 'prelu_layer', - 'pixel_softmax_layer', + 'switch_order_layer', ] @@ -218,7 +218,7 @@ class LayerType(object): SMOOTH_L1 = 'smooth_l1' PRELU = 'prelu' - PIXEL_SOFTMAX_LAYER = 'pixel_softmax' + SWITCH_ORDER_LAYER = 'switch_order' @staticmethod def is_layer_type(type_name): @@ -5881,37 +5881,37 @@ def prelu_layer(input, @layer_support() -@wrap_name_default('pixel_softmax') -def pixel_softmax_layer(input, name=None, layer_attr=None): +@wrap_name_default('switch_order') +def switch_order_layer(input, name=None, reshape=None, layer_attr=None): """ - This layer calculate softmax in image channel dimension + This layer switch dimension order of image input. + From order "batchSize, channels, height, width" + to order "batchSize, height, width, channels". The example usage is: .. code-block:: python + reshape = {'height':[ 0, 1, 2], 'width':[3]} + switch = switch_order(input=layer, name='switch', reshape=reshape) - prelu = pixel_softmax(input=layer, name='softmax') - - :param name: Name of this layer. - :type name: basestring :param input: The input layer. :type input: LayerOutput + :param name: Name of this layer. + :type name: basestring + :param reshape: reshape matrix by axises. + :type reshape: Dict :return: LayerOutput object. :rtype: LayerOutput """ - if isinstance(input, LayerOutput): - input = [input] - elif isinstance(input, Projection): - input = [input] - else: - assert isinstance(input, collections.Sequence) + assert isinstance(input, LayerOutput) l = Layer( name=name, - inputs=[x.name for x in input], - type=LayerType.PIXEL_SOFTMAX_LAYER, + inputs=input, + reshape=reshape, + type=LayerType.SWITCH_ORDER_LAYER, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( name=name, - layer_type=LayerType.PIXEL_SOFTMAX_LAYER, + layer_type=LayerType.SWITCH_ORDER_LAYER, parents=input, size=l.config.size) From fa02963659239fbbd61594b61073802cc9ab4513 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 19 Jul 2017 13:15:03 +0800 Subject: [PATCH 004/295] Delete debug log --- paddle/gserver/layers/SwitchOrderLayer.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/paddle/gserver/layers/SwitchOrderLayer.cpp b/paddle/gserver/layers/SwitchOrderLayer.cpp index 2a8a9500fa..8d337611b9 100644 --- a/paddle/gserver/layers/SwitchOrderLayer.cpp +++ b/paddle/gserver/layers/SwitchOrderLayer.cpp @@ -33,11 +33,9 @@ bool SwitchOrderLayer::init(const LayerMap& layerMap, auto& reshape_conf = config_.reshape_conf(); for (size_t i = 0; i < reshape_conf.heightaxis_size(); i++) { - LOG(INFO) << "reshape height axis: " << reshape_conf.heightaxis(i); heightAxis_.push_back(reshape_conf.heightaxis(i)); } for (size_t i = 0; i < reshape_conf.widthaxis_size(); i++) { - LOG(INFO) << "reshape width axis: " << reshape_conf.widthaxis(i); widthAxis_.push_back(reshape_conf.widthaxis(i)); } createFunction(nchw2nhwc_, "NCHW2NHWC", FuncConfig()); @@ -60,8 +58,6 @@ void SwitchOrderLayer::setOutDims() { reshapeWidth_ *= outDims_[widthAxis_[i]]; } output_.setFrameWidth(reshapeWidth_); - LOG(INFO) << "outDims: " << outDims_[0] << "; " << outDims_[1] << ";" - << outDims_[2] << ";" << outDims_[3]; } void SwitchOrderLayer::setInDims() { @@ -76,8 +72,6 @@ void SwitchOrderLayer::setInDims() { int totalCount = input->getElementCnt(); int channels = totalCount / (inDims_[0] * inDims_[2] * inDims_[3]); if (channels != 0) inDims_.setDim(1, channels); - LOG(INFO) << "inDims: " << inDims_[0] << "; " << inDims_[1] << ";" - << inDims_[2] << ";" << inDims_[3]; } void SwitchOrderLayer::forward(PassType passType) { @@ -95,12 +89,12 @@ void SwitchOrderLayer::forward(PassType passType) { inputs.addArg(*getInputValue(0), inDims_); outputs.addArg(*getOutputValue(), outDims_); nchw2nhwc_[0]->calc(inputs, outputs); - // forwardActivation(); + forwardActivation(); } void SwitchOrderLayer::backward(const UpdateCallback& callback) { (void)callback; - // backwardActivation(); + backwardActivation(); // switch NHWC to NCHW BufferArgs inputs; From e23acb4e6f7b12f1b61faf3cf8d74872b7df5b39 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 19 Jul 2017 14:09:32 +0800 Subject: [PATCH 005/295] fix cmake --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a85224843..2a6b0a20e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,6 @@ # limitations under the License cmake_minimum_required(VERSION 3.0) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ldl -lpthread") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) set(PROJ_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}) From a6c53fc2fcef380784829cfb29764e1a6458827d Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 19 Jul 2017 17:32:05 +0800 Subject: [PATCH 006/295] fix python wrapper bugs --- python/paddle/trainer/config_parser.py | 4 ++-- python/paddle/trainer_config_helpers/layers.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 6e2f218234..0a466380ae 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3187,8 +3187,8 @@ class SwitchOrderLayer(LayerBase): def __init__(self, name, inputs, reshape, **xargs): super(SwitchOrderLayer, self).__init__( name, 'switch_order', 0, inputs=inputs, **xargs) - self.conf.reshape_conf.heightAxis_ = reshape['height'] - self.conf.reshape_conf.widthAxis_ = reshape['width'] + self.config.reshape_conf.heightAxis.extend(reshape['height']) + self.config.reshape_conf.widthAxis.extend(reshape['width']) # Deprecated, use a new layer specific class instead diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1f5b9e999c..0bcfbe1e0c 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -5976,7 +5976,11 @@ def gated_unit_layer(input, @layer_support() @wrap_name_default('switch_order') -def switch_order_layer(input, name=None, reshape=None, layer_attr=None): +def switch_order_layer(input, + name=None, + reshape=None, + act=None, + layer_attr=None): """ This layer switch dimension order of image input. From order "batchSize, channels, height, width" @@ -6000,9 +6004,10 @@ def switch_order_layer(input, name=None, reshape=None, layer_attr=None): assert isinstance(input, LayerOutput) l = Layer( name=name, - inputs=input, + inputs=input.name, reshape=reshape, type=LayerType.SWITCH_ORDER_LAYER, + active_type=act.name, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( name=name, From baae8447ac936b29fb2b14981851bb502f5193cd Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 19 Jul 2017 18:53:32 +0800 Subject: [PATCH 007/295] Fix SwitchOrderLayer grad bugs by reshape output.grad --- paddle/gserver/layers/SwitchOrderLayer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/gserver/layers/SwitchOrderLayer.cpp b/paddle/gserver/layers/SwitchOrderLayer.cpp index 8d337611b9..6a91042f62 100644 --- a/paddle/gserver/layers/SwitchOrderLayer.cpp +++ b/paddle/gserver/layers/SwitchOrderLayer.cpp @@ -81,6 +81,7 @@ void SwitchOrderLayer::forward(PassType passType) { resetOutput(outDims_[0], outDims_[1] * outDims_[2] * outDims_[3]); if (heightAxis_.size() > 0) { getOutputValue()->reshape(reshapeHeight_, reshapeWidth_); + getOutputGrad()->reshape(reshapeHeight_, reshapeWidth_); } // switch NCHW to NHWC From b63e1c6d8a3e44b68263399f9720165703deccfd Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 14 Aug 2017 11:49:21 +0800 Subject: [PATCH 008/295] "op name" --- paddle/operators/name_convention.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 paddle/operators/name_convention.md diff --git a/paddle/operators/name_convention.md b/paddle/operators/name_convention.md new file mode 100644 index 0000000000..da5bcb7485 --- /dev/null +++ b/paddle/operators/name_convention.md @@ -0,0 +1,11 @@ +## Operator Name Convention + +To make the operator document itself more clear. we recommend operator names observe the listing conventions. + +### Input/Output names + +Variable name is uppercase. e.g. `X`, `Y` + +Tensor name is lowercase. e.g. `tensor` + +if only have one output, use `Out` From e9eee6f78559d6318e554b7b5ab021b271d8ddb6 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 15 Aug 2017 09:57:40 +0800 Subject: [PATCH 009/295] "polish words" --- paddle/operators/name_convention.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/paddle/operators/name_convention.md b/paddle/operators/name_convention.md index da5bcb7485..2260bf5660 100644 --- a/paddle/operators/name_convention.md +++ b/paddle/operators/name_convention.md @@ -4,8 +4,12 @@ To make the operator document itself more clear. we recommend operator names obs ### Input/Output names -Variable name is uppercase. e.g. `X`, `Y` +* Variable name is prefer uppercase. e.g. `X`, `Y`. But when the variable is tensor, its name should lowercase. e.g. `matrix`, to discriminate with otherone. -Tensor name is lowercase. e.g. `tensor` +* element wise operator, math operator or similar op, please obey common name convention. if the operator only have one output, use `Out`. -if only have one output, use `Out` +* we prefer more meaningful input/output name. + +### Best Practice +e.g. `rowwise_add`, inputs : `X`, `Y`, outputs : `Out` +e.g. `cosine` , inputs : `X`, `axis`, outputs : `Out` From 5d98b6f217f8c59ae32f7dabefb69037d80f9cb2 Mon Sep 17 00:00:00 2001 From: chengduoZH Date: Mon, 21 Aug 2017 16:32:29 +0800 Subject: [PATCH 010/295] Adapting to the BatchNorm structure to support 3D data --- paddle/gserver/layers/BatchNormBaseLayer.cpp | 6 ++- paddle/gserver/layers/BatchNormBaseLayer.h | 1 + paddle/gserver/tests/test_LayerGrad.cpp | 49 ++++++++++++++++++++ paddle/parameter/Argument.cpp | 2 + paddle/parameter/Argument.h | 8 ++-- proto/ModelConfig.proto | 13 ++++++ 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/paddle/gserver/layers/BatchNormBaseLayer.cpp b/paddle/gserver/layers/BatchNormBaseLayer.cpp index 1ceaaaa206..f7a80e23e1 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.cpp +++ b/paddle/gserver/layers/BatchNormBaseLayer.cpp @@ -62,14 +62,18 @@ void BatchNormBaseLayer::calFeatureMapSize() { const ImageConfig& conf = config_.inputs(0).image_conf(); imageH_ = inputLayers_[0]->getOutput().getFrameHeight(); imageW_ = inputLayers_[0]->getOutput().getFrameWidth(); + imageD_ = inputLayers_[0]->getOutput().getFrameDepth(); + + if (0 == imageD_) imageD_ = conf.img_size_z(); if (imageH_ == 0 && imageW_ == 0) { imageH_ = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); imageW_ = conf.img_size(); } else { getOutput().setFrameHeight(imageH_); getOutput().setFrameWidth(imageW_); + getOutput().setFrameDepth(imageD_); } - imgPixels_ = imageH_ * imageW_; + imgPixels_ = imageH_ * imageW_ * imageD_; } } // namespace paddle diff --git a/paddle/gserver/layers/BatchNormBaseLayer.h b/paddle/gserver/layers/BatchNormBaseLayer.h index 230bafc31d..e721d2d267 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.h +++ b/paddle/gserver/layers/BatchNormBaseLayer.h @@ -80,6 +80,7 @@ protected: /// Height or width of input image feature. /// Both of them are 1 if the input is fully-connected layer. + int imageD_; int imageH_; int imageW_; /// Height * Width. diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 0f312b6ca5..6418772584 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1594,6 +1594,55 @@ TEST(Layer, BatchNormalizationLayer) { #endif } +void testBatchNorm3DLayer(const string& type, bool trans, bool useGpu) { + TestConfig config; + const int CHANNELS = 10; + const int IMG_SIZE = 16; + const int IMG_SIZE_Y = 8; + const int IMG_SIZE_Z = 8; + size_t size = CHANNELS * IMG_SIZE * IMG_SIZE_Y * IMG_SIZE_Z; + config.layerConfig.set_type(type); + config.layerConfig.set_size(size); + config.layerConfig.set_active_type("sigmoid"); + config.biasSize = CHANNELS; + config.inputDefs.push_back({INPUT_DATA, + "layer_0", + /* dim= */ size, + /* paraSize= */ CHANNELS}); + + config.inputDefs.push_back({INPUT_DATA, "layer_1_running_mean", 1, CHANNELS}); + config.inputDefs.back().isStatic = true; + config.inputDefs.push_back({INPUT_DATA, "layer_2_running_var", 1, CHANNELS}); + config.inputDefs.back().isStatic = true; + + LayerInputConfig* input = config.layerConfig.add_inputs(); + config.layerConfig.add_inputs(); + config.layerConfig.add_inputs(); + + ImageConfig* img_conf = input->mutable_image_conf(); + img_conf->set_channels(CHANNELS); + img_conf->set_img_size(IMG_SIZE); + img_conf->set_img_size_y(IMG_SIZE_Y); + img_conf->set_img_size_z(IMG_SIZE_Z); + + testLayerGrad(config, + "batch_norm", + 64, + /* trans= */ trans, + useGpu, + /* useWeight */ true); +} + +TEST(Layer, testBatchNorm3DLayer) { + testBatchNorm3DLayer("batch_norm", false, false); +#ifndef PADDLE_ONLY_CPU + testBatchNorm3DLayer("batch_norm", false, true); + if (hl_get_cudnn_lib_version() >= int(4000)) { + testBatchNorm3DLayer("cudnn_batch_norm", false, true); + } +#endif +} + void testConvOperator(bool isDeconv) { TestConfig config; const int NUM_FILTERS = 16; diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 0547ac93cd..77fd0c5890 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -186,6 +186,7 @@ void Argument::resizeAndCopyFrom(const Argument& src, resizeAndCopy(strs, src.strs, useGpu, stream); frameWidth = src.frameWidth; frameHeight = src.frameHeight; + frameDepth = src.frameDepth; } int32_t Argument::resizeAndCopyFrom(const Argument& src, @@ -206,6 +207,7 @@ int32_t Argument::resizeAndCopyFrom(const Argument& src, dataId = src.dataId; frameWidth = src.frameWidth; frameHeight = src.frameHeight; + frameDepth = src.frameDepth; if (!src.sequenceStartPositions) { // non-sequence input, copy samples directly diff --git a/paddle/parameter/Argument.h b/paddle/parameter/Argument.h index d8d7a4398f..ba3ad2fd4d 100644 --- a/paddle/parameter/Argument.h +++ b/paddle/parameter/Argument.h @@ -1,11 +1,8 @@ /* 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. @@ -35,6 +32,7 @@ struct Argument { strs(nullptr), frameHeight(0), frameWidth(0), + frameDepth(0), sequenceStartPositions(nullptr), subSequenceStartPositions(nullptr), cpuSequenceDims(nullptr), @@ -64,6 +62,7 @@ struct Argument { allCount = argument.allCount; frameHeight = argument.frameHeight; frameWidth = argument.frameWidth; + frameDepth = argument.frameDepth; dataId = argument.dataId; } @@ -76,6 +75,7 @@ struct Argument { // A dataBatch includes batchSize frames, one frame maybe not only vector size_t frameHeight; size_t frameWidth; + size_t frameDepth; // If NULL, each position is treated independently. // Otherwise, its size should be #NumberOfSequences + 1. @@ -136,8 +136,10 @@ struct Argument { } size_t getFrameHeight() const { return frameHeight; } size_t getFrameWidth() const { return frameWidth; } + size_t getFrameDepth() const { return frameDepth; } void setFrameHeight(size_t h) { frameHeight = h; } void setFrameWidth(size_t w) { frameWidth = w; } + void setFrameDepth(size_t d) { frameDepth = d; } int64_t getNumSequences() const { return sequenceStartPositions ? sequenceStartPositions->getSize() - 1 diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 4f3d5bf3f6..ef2b076c33 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -82,6 +82,12 @@ message ConvConfig { // if not set, use img_size optional uint32 img_size_y = 14; + + optional uint32 filter_size_z = 15 [ default = 1 ]; + optional uint32 padding_z = 16 [ default = 1 ]; + optional uint32 stride_z = 17 [ default = 1 ]; + optional uint32 output_z = 18 [ default = 1 ]; + optional uint32 img_size_z = 19 [ default = 1 ]; } message PoolConfig { @@ -124,6 +130,12 @@ message PoolConfig { // if not set, use padding optional uint32 padding_y = 13; + + optional uint32 size_z = 14 [ default = 1 ]; + optional uint32 stride_z = 15 [ default = 1 ]; + optional uint32 output_z = 16 [ default = 1 ]; + optional uint32 img_size_z = 17 [ default = 1 ]; + optional uint32 padding_z = 18 [ default = 1 ]; } message SppConfig { @@ -256,6 +268,7 @@ message ImageConfig { // The size of input feature map. required uint32 img_size = 8; optional uint32 img_size_y = 9; + optional uint32 img_size_z = 10 [ default = 1 ]; } message PriorBoxConfig { From 5ca4118451a38a8fa1e876fd5416028010ec218b Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 22 Aug 2017 17:27:04 +0800 Subject: [PATCH 011/295] Update Dockerfile of android to support building for arm64-v8a and armeabi. --- Dockerfile.android | 18 ++++--- paddle/scripts/docker/build_android.sh | 65 +++++++++++++++++++------- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Dockerfile.android b/Dockerfile.android index c0fa58c384..aa95abb366 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -4,9 +4,15 @@ 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 variables +ARG ANDROID_ABI + +ENV ANDROID_ABI=${ANDROID_ABI:-"armeabi-v7a"} + ENV HOME=/root \ ANDROID_NDK_HOME=/opt/android-ndk-linux \ - ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain-gcc + ANDROID_ARM_STANDALONE_TOOLCHAIN=/opt/arm-toolchain-gcc \ + ANDROID_ARM64_STANDALONE_TOOLCHAIN=/opt/arm64-toolchain-gcc RUN apt-get update && \ apt-get install -y \ @@ -15,12 +21,11 @@ RUN apt-get update && \ apt-get clean -y # 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 && \ +RUN wget -qO- go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz | \ + tar -xz -C /usr/local && \ mkdir /root/gopath && \ mkdir /root/gopath/bin && \ - mkdir /root/gopath/src && \ - rm go.tgz + mkdir /root/gopath/src 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:${GOPATH}/bin @@ -42,7 +47,8 @@ RUN mkdir /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} && \ + ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-21 --install-dir=${ANDROID_ARM_STANDALONE_TOOLCHAIN} && \ + ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm64 --platform=android-21 --install-dir=${ANDROID_ARM64_STANDALONE_TOOLCHAIN} && \ rm -rf /opt/android-ndk-tmp && \ rm -rf ${ANDROID_NDK_HOME} diff --git a/paddle/scripts/docker/build_android.sh b/paddle/scripts/docker/build_android.sh index 5584e29e2a..593ae28e49 100644 --- a/paddle/scripts/docker/build_android.sh +++ b/paddle/scripts/docker/build_android.sh @@ -2,22 +2,55 @@ set -xe -mkdir -p /paddle/build_android -cd /paddle/build_android +mkdir -p /paddle/build_android/$ANDROID_ABI +cd /paddle/build_android/$ANDROID_ABI rm -rf /paddle/install 2>/dev/null || true -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 \ - .. + +THIRD_PARTY_PATH=/paddle/third_party_android/$ANDROID_ABI + +if [ $ANDROID_ABI == "armeabi-v7a" ]; then + cmake -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM_STANDALONE_TOOLCHAIN \ + -DANDROID_ABI=$ANDROID_ABI \ + -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 \ + -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_C_API=ON \ + -DWITH_SWIG_PY=OFF \ + /paddle +elif [ $ANDROID_ABI == "arm64-v7a" ]; then + cmake -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM64_STANDALONE_TOOLCHAIN \ + -DANDROID_ABI=$ANDROID_ABI \ + -DANDROID_ARM_MODE=ON \ + -DHOST_C_COMPILER=/usr/bin/gcc \ + -DHOST_CXX_COMPILER=/usr/bin/g++ \ + -DCMAKE_INSTALL_PREFIX=/paddle/install \ + -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_C_API=ON \ + -DWITH_SWIG_PY=OFF \ + /paddle +elif [ $ANDROID_ABI == "armeabi" ]; then + cmake -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM_STANDALONE_TOOLCHAIN \ + -DANDROID_ABI=$ANDROID_ABI \ + -DANDROID_ARM_MODE=ON \ + -DHOST_C_COMPILER=/usr/bin/gcc \ + -DHOST_CXX_COMPILER=/usr/bin/g++ \ + -DCMAKE_INSTALL_PREFIX=/paddle/install \ + -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_C_API=ON \ + -DWITH_SWIG_PY=OFF \ + /paddle +else + echo "Invalid ANDROID_ABI: $ANDROID_ABI" +fi + make -j `nproc` make install -j `nproc` From 8a4fad4248e942061586538e8de14a7d08052330 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 23 Aug 2017 19:43:57 +0800 Subject: [PATCH 012/295] Support to use clang for Android cross-compiling. --- cmake/cblas.cmake | 4 + cmake/external/warpctc.cmake | 1 + paddle/cuda/include/hl_cpu_gru.cuh | 166 ++++++++++++------------- paddle/function/MulOp.cpp | 37 +++--- paddle/math/MathFunctions.cpp | 4 + paddle/math/MathFunctions.h | 23 +++- paddle/math/Matrix.cpp | 18 ++- paddle/scripts/docker/build_android.sh | 24 ++-- 8 files changed, 155 insertions(+), 122 deletions(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index 854066fd1d..ab111eccc0 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -13,6 +13,10 @@ # system paths. # +if(USE_EIGEN_FOR_BLAS) + return() +endif(USE_EIGEN_FOR_BLAS) + set(CBLAS_FOUND OFF) ## Find MKLML First. diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 2d7daed9bc..3cc652bed5 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -41,6 +41,7 @@ IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "App ELSE() SET(USE_OMP ON) ENDIF() +SET(USE_OMP OFF FORCE) ExternalProject_Add( extern_warpctc diff --git a/paddle/cuda/include/hl_cpu_gru.cuh b/paddle/cuda/include/hl_cpu_gru.cuh index c0a37ced2a..732799a28b 100644 --- a/paddle/cuda/include/hl_cpu_gru.cuh +++ b/paddle/cuda/include/hl_cpu_gru.cuh @@ -20,11 +20,11 @@ limitations under the License. */ #include "paddle/math/MathFunctions.h" -#ifndef PADDLE_TYPE_DOUBLE -#define CBLAS_GEMM paddle::gemm -#else -#define CBLAS_GEMM paddle::gemm -#endif +// #ifndef PADDLE_TYPE_DOUBLE +// #define CBLAS_GEMM paddle::gemm +// #else +// #define CBLAS_GEMM paddle::gemm +// #endif template void hl_naive_gru_forward_reset_output(OpResetOutput opResetOutput, @@ -219,37 +219,37 @@ void hl_cpu_gru_forward(OpResetOutput opResetOutput, hl_activation_mode_t active_node, hl_activation_mode_t active_gate) { if (value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasNoTrans, - batchSize, - 2 * frameSize, - frameSize, - 1, - value.prevOutValue, - frameSize, - value.gateWeight, - frameSize * 2, - 1, - value.gateValue, - frameSize * 3); +// CBLAS_GEMM(CblasNoTrans, +// CblasNoTrans, +// batchSize, +// 2 * frameSize, +// frameSize, +// 1, +// value.prevOutValue, +// frameSize, +// value.gateWeight, +// frameSize * 2, +// 1, +// value.gateValue, +// frameSize * 3); } forward_reset_output(opResetOutput, value, frameSize, batchSize, active_gate); if (value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasNoTrans, - batchSize, - frameSize, - frameSize, - 1, - value.resetOutputValue, - frameSize, - value.stateWeight, - frameSize, - 1, - value.gateValue + frameSize * 2, - frameSize * 3); +// CBLAS_GEMM(CblasNoTrans, +// CblasNoTrans, +// batchSize, +// frameSize, +// frameSize, +// 1, +// value.resetOutputValue, +// frameSize, +// value.stateWeight, +// frameSize, +// 1, +// value.gateValue + frameSize * 2, +// frameSize * 3); } forward_final_output(opFinalOutput, value, frameSize, batchSize, active_node); @@ -538,34 +538,34 @@ void hl_cpu_gru_backward(OpStateGrad opStateGrad, frameSize, batchSize, active_node); if (value.prevOutValue && grad.prevOutGrad) { - CBLAS_GEMM(CblasNoTrans, - CblasTrans, - batchSize, - frameSize, - frameSize, - 1, - grad.gateGrad + frameSize * 2, - frameSize * 3, - value.stateWeight, - frameSize, - 0, - grad.resetOutputGrad, - frameSize); +// CBLAS_GEMM(CblasNoTrans, +// CblasTrans, +// batchSize, +// frameSize, +// frameSize, +// 1, +// grad.gateGrad + frameSize * 2, +// frameSize * 3, +// value.stateWeight, +// frameSize, +// 0, +// grad.resetOutputGrad, +// frameSize); if (grad.stateWeightGrad) { - CBLAS_GEMM(CblasTrans, - CblasNoTrans, - frameSize, - frameSize, - batchSize, - 1, - value.resetOutputValue, - frameSize, - grad.gateGrad + frameSize * 2, - frameSize * 3, - 1, - grad.stateWeightGrad, - frameSize); +// CBLAS_GEMM(CblasTrans, +// CblasNoTrans, +// frameSize, +// frameSize, +// batchSize, +// 1, +// value.resetOutputValue, +// frameSize, +// grad.gateGrad + frameSize * 2, +// frameSize * 3, +// 1, +// grad.stateWeightGrad, +// frameSize); } } @@ -573,34 +573,34 @@ void hl_cpu_gru_backward(OpStateGrad opStateGrad, frameSize, batchSize, active_gate); if (grad.prevOutGrad && value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasTrans, - batchSize, - frameSize, - frameSize * 2, - 1, - grad.gateGrad, - frameSize * 3, - value.gateWeight, - frameSize * 2, - 1, - grad.prevOutGrad, - frameSize); +// CBLAS_GEMM(CblasNoTrans, +// CblasTrans, +// batchSize, +// frameSize, +// frameSize * 2, +// 1, +// grad.gateGrad, +// frameSize * 3, +// value.gateWeight, +// frameSize * 2, +// 1, +// grad.prevOutGrad, +// frameSize); if (grad.gateWeightGrad) { - CBLAS_GEMM(CblasTrans, - CblasNoTrans, - frameSize, - frameSize * 2, - batchSize, - 1, - value.prevOutValue, - frameSize, - grad.gateGrad, - frameSize * 3, - 1, - grad.gateWeightGrad, - frameSize * 2); +// CBLAS_GEMM(CblasTrans, +// CblasNoTrans, +// frameSize, +// frameSize * 2, +// batchSize, +// 1, +// value.prevOutValue, +// frameSize, +// grad.gateGrad, +// frameSize * 3, +// 1, +// grad.gateWeightGrad, +// frameSize * 2); } } } diff --git a/paddle/function/MulOp.cpp b/paddle/function/MulOp.cpp index 91b4b8ed91..25e41edad5 100644 --- a/paddle/function/MulOp.cpp +++ b/paddle/function/MulOp.cpp @@ -13,18 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "MulOp.h" -/// todo(tianbing), delete it -#include -#include "paddle/math/MathFunctions.h" +#include "GemmFunctor.h" #include "paddle/math/SIMDFunctions.h" #include "paddle/utils/ThreadLocal.h" -#ifndef PADDLE_TYPE_DOUBLE -#define GEMM paddle::gemm -#else -#define GEMM paddle::gemm -#endif - namespace { inline void vecAddTo(real* a, const real* b, real scaleB, size_t len) { for (unsigned int i = 0; i < len; ++i) { @@ -114,19 +106,20 @@ void MulOp(CpuMatrix& out, real scaleT, bool aTrans, bool bTrans) { - GEMM(aTrans ? CblasTrans : CblasNoTrans, - bTrans ? CblasTrans : CblasNoTrans, - out.getHeight(), - out.getWidth(), - !aTrans ? a.getWidth() : a.getHeight(), - scaleAB, - a.getData(), - a.getStride(), - b.getData(), - b.getStride(), - scaleT, - out.getData(), - out.getStride()); + BlasGemm::compute( + aTrans, + bTrans, + out.getHeight(), + out.getWidth(), + !aTrans ? a.getWidth() : a.getHeight(), + scaleAB, + a.getData(), + a.getStride(), + b.getData(), + b.getStride(), + scaleT, + out.getData(), + out.getStride()); } /// dense matrix (+)= sparse matrix * dense matrix diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index c8ba1074a1..c2f17beeb8 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -84,6 +84,7 @@ LAPACK_ROUTINE_EACH(DYNAMIC_LOAD_LAPACK_WRAP) namespace paddle { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template <> void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, @@ -143,6 +144,7 @@ void gemm(const CBLAS_TRANSPOSE transA, C, ldc); } +#endif template <> int getrf(const CBLAS_ORDER order, @@ -182,6 +184,7 @@ int getri(const CBLAS_ORDER order, return dynload::PADDLE_DGETRI(order, N, A, lda, ipiv); } +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template <> void axpy(const int n, const float alpha, const float* x, float* y) { cblas_saxpy(n, alpha, x, 1, y, 1); @@ -201,6 +204,7 @@ template <> double dotProduct(const int n, const double* x, const double* y) { return cblas_ddot(n, x, 1, y, 1); } +#endif #if defined(PADDLE_USE_MKL) || defined(PADDLE_USE_MKLML) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 637643838f..9297ae78c2 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -40,7 +40,14 @@ extern "C" { #ifndef LAPACK_FOUND extern "C" { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS #include +#else +typedef enum CBLAS_ORDER { + CblasRowMajor = 101, + CblasColMajor = 102 +} CBLAS_ORDER; +#endif int LAPACKE_sgetrf( int matrix_layout, int m, int n, float* a, int lda, int* ipiv); int LAPACKE_dgetrf( @@ -56,6 +63,7 @@ int LAPACKE_dgetri( namespace paddle { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, @@ -70,6 +78,7 @@ void gemm(const CBLAS_TRANSPOSE transA, const T beta, T* C, const int ldc); +#endif template int getrf(const CBLAS_ORDER Order, @@ -84,10 +93,20 @@ int getri( const CBLAS_ORDER Order, const int N, T* A, const int lda, const int* ipiv); template -void axpy(const int n, const T alpha, const T* x, T* y); +void axpy(const int n, const T alpha, const T* x, T* y) { + /// y = y + alpha * x + for (int i = 0; i < n; i++) { + y[i] = y[i] + alpha * x[i]; + } +} template -T dotProduct(const int n, const T* x, const T* y); +T dotProduct(const int n, const T* x, const T* y) { + T result = static_cast(0); + for (int i = 0; i < n; i++) { + result += x[i] * y[i]; + } +} template void vExp(const int n, const T* a, T* r); diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 27f7d95b75..fbf3accc9a 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -28,6 +28,7 @@ limitations under the License. */ #include "hl_top_k.h" #include "paddle/utils/Logging.h" +#include "paddle/function/GemmFunctor.h" #include "paddle/utils/ThreadLocal.h" #include "SIMDFunctions.h" @@ -2222,24 +2223,29 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { CHECK(!isTransposed()) << "Not supported"; size_t a_col, b_col, a_row, b_row; - CBLAS_TRANSPOSE a_trans, b_trans; + // CBLAS_TRANSPOSE a_trans, b_trans; + bool a_trans, b_trans; if (!a->isTransposed()) { a_col = a->getWidth(); a_row = a->getHeight(); - a_trans = CblasNoTrans; + // a_trans = CblasNoTrans; + a_trans = false; } else { a_col = a->getHeight(); a_row = a->getWidth(); - a_trans = CblasTrans; + // a_trans = CblasTrans; + a_trans = true; } if (!b->isTransposed()) { b_col = b->getWidth(); b_row = b->getHeight(); - b_trans = CblasNoTrans; + // b_trans = CblasNoTrans; + b_trans = false; } else { b_col = b->getHeight(); b_row = b->getWidth(); - b_trans = CblasTrans; + // b_trans = CblasTrans; + b_trans = true; } CHECK_EQ(a_col, b_row); @@ -2256,7 +2262,7 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { int lda = a->getStride(); int ldb = b->getStride(); int ldc = getStride(); - gemm( + BlasGemm::compute( a_trans, b_trans, M, N, K, scaleAB, A, lda, B, ldb, scaleT, C, ldc); } diff --git a/paddle/scripts/docker/build_android.sh b/paddle/scripts/docker/build_android.sh index 593ae28e49..79f5ab12e9 100644 --- a/paddle/scripts/docker/build_android.sh +++ b/paddle/scripts/docker/build_android.sh @@ -2,9 +2,9 @@ set -xe -mkdir -p /paddle/build_android/$ANDROID_ABI -cd /paddle/build_android/$ANDROID_ABI -rm -rf /paddle/install 2>/dev/null || true +rm -rf /paddle/build_android 2>/dev/null || true +mkdir -p /paddle/build_android +cd /paddle/build_android THIRD_PARTY_PATH=/paddle/third_party_android/$ANDROID_ABI @@ -14,19 +14,25 @@ if [ $ANDROID_ABI == "armeabi-v7a" ]; then -DANDROID_ABI=$ANDROID_ABI \ -DANDROID_ARM_NEON=ON \ -DANDROID_ARM_MODE=ON \ + -DCMAKE_C_COMPILER=$ANDROID_ARM_STANDALONE_TOOLCHAIN/bin/arm-linux-androideabi-clang \ + -DCMAKE_CXX_COMPILER=$ANDROID_ARM_STANDALONE_TOOLCHAIN/bin/arm-linux-androideabi-clang++ \ -DHOST_C_COMPILER=/usr/bin/gcc \ -DHOST_CXX_COMPILER=/usr/bin/g++ \ -DCMAKE_INSTALL_PREFIX=/paddle/install \ -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ -DCMAKE_BUILD_TYPE=Release \ + -DUSE_EIGEN_FOR_BLAS=ON \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle -elif [ $ANDROID_ABI == "arm64-v7a" ]; then + -DWITH_STYLE_CHECK=OFF \ + .. +elif [ $ANDROID_ABI == "arm64-v8a" ]; then cmake -DCMAKE_SYSTEM_NAME=Android \ -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM64_STANDALONE_TOOLCHAIN \ -DANDROID_ABI=$ANDROID_ABI \ -DANDROID_ARM_MODE=ON \ + -DCMAKE_C_COMPILER=$ANDROID_ARM64_STANDALONE_TOOLCHAIN/bin/aarch64-linux-android-clang \ + -DCMAKE_CXX_COMPILER=$ANDROID_ARM64_STANDALONE_TOOLCHAIN/bin/aarch64-linux-android-clang++ \ -DHOST_C_COMPILER=/usr/bin/gcc \ -DHOST_CXX_COMPILER=/usr/bin/g++ \ -DCMAKE_INSTALL_PREFIX=/paddle/install \ @@ -34,7 +40,7 @@ elif [ $ANDROID_ABI == "arm64-v7a" ]; then -DCMAKE_BUILD_TYPE=Release \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle + .. elif [ $ANDROID_ABI == "armeabi" ]; then cmake -DCMAKE_SYSTEM_NAME=Android \ -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM_STANDALONE_TOOLCHAIN \ @@ -47,10 +53,10 @@ elif [ $ANDROID_ABI == "armeabi" ]; then -DCMAKE_BUILD_TYPE=Release \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle + .. else echo "Invalid ANDROID_ABI: $ANDROID_ABI" fi -make -j `nproc` -make install -j `nproc` +make VERBOSE=1 +make install From f241773c4f1803631bba968bca1d5621a0d3ced5 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 23 Aug 2017 19:43:57 +0800 Subject: [PATCH 013/295] Support to use clang for Android cross-compiling. --- Dockerfile.android | 4 +- cmake/cblas.cmake | 4 + cmake/external/warpctc.cmake | 1 + paddle/cuda/include/hl_cpu_gru.cuh | 166 ++++++++++++------------- paddle/function/MulOp.cpp | 37 +++--- paddle/math/MathFunctions.cpp | 4 + paddle/math/MathFunctions.h | 23 +++- paddle/math/Matrix.cpp | 18 ++- paddle/scripts/docker/build_android.sh | 51 ++++++-- 9 files changed, 181 insertions(+), 127 deletions(-) diff --git a/Dockerfile.android b/Dockerfile.android index aa95abb366..6013215d9d 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -47,8 +47,8 @@ RUN mkdir /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_ARM_STANDALONE_TOOLCHAIN} && \ - ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm64 --platform=android-21 --install-dir=${ANDROID_ARM64_STANDALONE_TOOLCHAIN} && \ + ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-23 --install-dir=${ANDROID_ARM_STANDALONE_TOOLCHAIN} && \ + ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm64 --platform=android-23 --install-dir=${ANDROID_ARM64_STANDALONE_TOOLCHAIN} && \ rm -rf /opt/android-ndk-tmp && \ rm -rf ${ANDROID_NDK_HOME} diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index 854066fd1d..ab111eccc0 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -13,6 +13,10 @@ # system paths. # +if(USE_EIGEN_FOR_BLAS) + return() +endif(USE_EIGEN_FOR_BLAS) + set(CBLAS_FOUND OFF) ## Find MKLML First. diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 2d7daed9bc..3cc652bed5 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -41,6 +41,7 @@ IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "App ELSE() SET(USE_OMP ON) ENDIF() +SET(USE_OMP OFF FORCE) ExternalProject_Add( extern_warpctc diff --git a/paddle/cuda/include/hl_cpu_gru.cuh b/paddle/cuda/include/hl_cpu_gru.cuh index c0a37ced2a..732799a28b 100644 --- a/paddle/cuda/include/hl_cpu_gru.cuh +++ b/paddle/cuda/include/hl_cpu_gru.cuh @@ -20,11 +20,11 @@ limitations under the License. */ #include "paddle/math/MathFunctions.h" -#ifndef PADDLE_TYPE_DOUBLE -#define CBLAS_GEMM paddle::gemm -#else -#define CBLAS_GEMM paddle::gemm -#endif +// #ifndef PADDLE_TYPE_DOUBLE +// #define CBLAS_GEMM paddle::gemm +// #else +// #define CBLAS_GEMM paddle::gemm +// #endif template void hl_naive_gru_forward_reset_output(OpResetOutput opResetOutput, @@ -219,37 +219,37 @@ void hl_cpu_gru_forward(OpResetOutput opResetOutput, hl_activation_mode_t active_node, hl_activation_mode_t active_gate) { if (value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasNoTrans, - batchSize, - 2 * frameSize, - frameSize, - 1, - value.prevOutValue, - frameSize, - value.gateWeight, - frameSize * 2, - 1, - value.gateValue, - frameSize * 3); +// CBLAS_GEMM(CblasNoTrans, +// CblasNoTrans, +// batchSize, +// 2 * frameSize, +// frameSize, +// 1, +// value.prevOutValue, +// frameSize, +// value.gateWeight, +// frameSize * 2, +// 1, +// value.gateValue, +// frameSize * 3); } forward_reset_output(opResetOutput, value, frameSize, batchSize, active_gate); if (value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasNoTrans, - batchSize, - frameSize, - frameSize, - 1, - value.resetOutputValue, - frameSize, - value.stateWeight, - frameSize, - 1, - value.gateValue + frameSize * 2, - frameSize * 3); +// CBLAS_GEMM(CblasNoTrans, +// CblasNoTrans, +// batchSize, +// frameSize, +// frameSize, +// 1, +// value.resetOutputValue, +// frameSize, +// value.stateWeight, +// frameSize, +// 1, +// value.gateValue + frameSize * 2, +// frameSize * 3); } forward_final_output(opFinalOutput, value, frameSize, batchSize, active_node); @@ -538,34 +538,34 @@ void hl_cpu_gru_backward(OpStateGrad opStateGrad, frameSize, batchSize, active_node); if (value.prevOutValue && grad.prevOutGrad) { - CBLAS_GEMM(CblasNoTrans, - CblasTrans, - batchSize, - frameSize, - frameSize, - 1, - grad.gateGrad + frameSize * 2, - frameSize * 3, - value.stateWeight, - frameSize, - 0, - grad.resetOutputGrad, - frameSize); +// CBLAS_GEMM(CblasNoTrans, +// CblasTrans, +// batchSize, +// frameSize, +// frameSize, +// 1, +// grad.gateGrad + frameSize * 2, +// frameSize * 3, +// value.stateWeight, +// frameSize, +// 0, +// grad.resetOutputGrad, +// frameSize); if (grad.stateWeightGrad) { - CBLAS_GEMM(CblasTrans, - CblasNoTrans, - frameSize, - frameSize, - batchSize, - 1, - value.resetOutputValue, - frameSize, - grad.gateGrad + frameSize * 2, - frameSize * 3, - 1, - grad.stateWeightGrad, - frameSize); +// CBLAS_GEMM(CblasTrans, +// CblasNoTrans, +// frameSize, +// frameSize, +// batchSize, +// 1, +// value.resetOutputValue, +// frameSize, +// grad.gateGrad + frameSize * 2, +// frameSize * 3, +// 1, +// grad.stateWeightGrad, +// frameSize); } } @@ -573,34 +573,34 @@ void hl_cpu_gru_backward(OpStateGrad opStateGrad, frameSize, batchSize, active_gate); if (grad.prevOutGrad && value.prevOutValue) { - CBLAS_GEMM(CblasNoTrans, - CblasTrans, - batchSize, - frameSize, - frameSize * 2, - 1, - grad.gateGrad, - frameSize * 3, - value.gateWeight, - frameSize * 2, - 1, - grad.prevOutGrad, - frameSize); +// CBLAS_GEMM(CblasNoTrans, +// CblasTrans, +// batchSize, +// frameSize, +// frameSize * 2, +// 1, +// grad.gateGrad, +// frameSize * 3, +// value.gateWeight, +// frameSize * 2, +// 1, +// grad.prevOutGrad, +// frameSize); if (grad.gateWeightGrad) { - CBLAS_GEMM(CblasTrans, - CblasNoTrans, - frameSize, - frameSize * 2, - batchSize, - 1, - value.prevOutValue, - frameSize, - grad.gateGrad, - frameSize * 3, - 1, - grad.gateWeightGrad, - frameSize * 2); +// CBLAS_GEMM(CblasTrans, +// CblasNoTrans, +// frameSize, +// frameSize * 2, +// batchSize, +// 1, +// value.prevOutValue, +// frameSize, +// grad.gateGrad, +// frameSize * 3, +// 1, +// grad.gateWeightGrad, +// frameSize * 2); } } } diff --git a/paddle/function/MulOp.cpp b/paddle/function/MulOp.cpp index 91b4b8ed91..25e41edad5 100644 --- a/paddle/function/MulOp.cpp +++ b/paddle/function/MulOp.cpp @@ -13,18 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "MulOp.h" -/// todo(tianbing), delete it -#include -#include "paddle/math/MathFunctions.h" +#include "GemmFunctor.h" #include "paddle/math/SIMDFunctions.h" #include "paddle/utils/ThreadLocal.h" -#ifndef PADDLE_TYPE_DOUBLE -#define GEMM paddle::gemm -#else -#define GEMM paddle::gemm -#endif - namespace { inline void vecAddTo(real* a, const real* b, real scaleB, size_t len) { for (unsigned int i = 0; i < len; ++i) { @@ -114,19 +106,20 @@ void MulOp(CpuMatrix& out, real scaleT, bool aTrans, bool bTrans) { - GEMM(aTrans ? CblasTrans : CblasNoTrans, - bTrans ? CblasTrans : CblasNoTrans, - out.getHeight(), - out.getWidth(), - !aTrans ? a.getWidth() : a.getHeight(), - scaleAB, - a.getData(), - a.getStride(), - b.getData(), - b.getStride(), - scaleT, - out.getData(), - out.getStride()); + BlasGemm::compute( + aTrans, + bTrans, + out.getHeight(), + out.getWidth(), + !aTrans ? a.getWidth() : a.getHeight(), + scaleAB, + a.getData(), + a.getStride(), + b.getData(), + b.getStride(), + scaleT, + out.getData(), + out.getStride()); } /// dense matrix (+)= sparse matrix * dense matrix diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index c8ba1074a1..c2f17beeb8 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -84,6 +84,7 @@ LAPACK_ROUTINE_EACH(DYNAMIC_LOAD_LAPACK_WRAP) namespace paddle { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template <> void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, @@ -143,6 +144,7 @@ void gemm(const CBLAS_TRANSPOSE transA, C, ldc); } +#endif template <> int getrf(const CBLAS_ORDER order, @@ -182,6 +184,7 @@ int getri(const CBLAS_ORDER order, return dynload::PADDLE_DGETRI(order, N, A, lda, ipiv); } +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template <> void axpy(const int n, const float alpha, const float* x, float* y) { cblas_saxpy(n, alpha, x, 1, y, 1); @@ -201,6 +204,7 @@ template <> double dotProduct(const int n, const double* x, const double* y) { return cblas_ddot(n, x, 1, y, 1); } +#endif #if defined(PADDLE_USE_MKL) || defined(PADDLE_USE_MKLML) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 637643838f..9297ae78c2 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -40,7 +40,14 @@ extern "C" { #ifndef LAPACK_FOUND extern "C" { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS #include +#else +typedef enum CBLAS_ORDER { + CblasRowMajor = 101, + CblasColMajor = 102 +} CBLAS_ORDER; +#endif int LAPACKE_sgetrf( int matrix_layout, int m, int n, float* a, int lda, int* ipiv); int LAPACKE_dgetrf( @@ -56,6 +63,7 @@ int LAPACKE_dgetri( namespace paddle { +#ifndef PADDLE_USE_EIGEN_FOR_BLAS template void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, @@ -70,6 +78,7 @@ void gemm(const CBLAS_TRANSPOSE transA, const T beta, T* C, const int ldc); +#endif template int getrf(const CBLAS_ORDER Order, @@ -84,10 +93,20 @@ int getri( const CBLAS_ORDER Order, const int N, T* A, const int lda, const int* ipiv); template -void axpy(const int n, const T alpha, const T* x, T* y); +void axpy(const int n, const T alpha, const T* x, T* y) { + /// y = y + alpha * x + for (int i = 0; i < n; i++) { + y[i] = y[i] + alpha * x[i]; + } +} template -T dotProduct(const int n, const T* x, const T* y); +T dotProduct(const int n, const T* x, const T* y) { + T result = static_cast(0); + for (int i = 0; i < n; i++) { + result += x[i] * y[i]; + } +} template void vExp(const int n, const T* a, T* r); diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 27f7d95b75..fbf3accc9a 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -28,6 +28,7 @@ limitations under the License. */ #include "hl_top_k.h" #include "paddle/utils/Logging.h" +#include "paddle/function/GemmFunctor.h" #include "paddle/utils/ThreadLocal.h" #include "SIMDFunctions.h" @@ -2222,24 +2223,29 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { CHECK(!isTransposed()) << "Not supported"; size_t a_col, b_col, a_row, b_row; - CBLAS_TRANSPOSE a_trans, b_trans; + // CBLAS_TRANSPOSE a_trans, b_trans; + bool a_trans, b_trans; if (!a->isTransposed()) { a_col = a->getWidth(); a_row = a->getHeight(); - a_trans = CblasNoTrans; + // a_trans = CblasNoTrans; + a_trans = false; } else { a_col = a->getHeight(); a_row = a->getWidth(); - a_trans = CblasTrans; + // a_trans = CblasTrans; + a_trans = true; } if (!b->isTransposed()) { b_col = b->getWidth(); b_row = b->getHeight(); - b_trans = CblasNoTrans; + // b_trans = CblasNoTrans; + b_trans = false; } else { b_col = b->getHeight(); b_row = b->getWidth(); - b_trans = CblasTrans; + // b_trans = CblasTrans; + b_trans = true; } CHECK_EQ(a_col, b_row); @@ -2256,7 +2262,7 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { int lda = a->getStride(); int ldb = b->getStride(); int ldc = getStride(); - gemm( + BlasGemm::compute( a_trans, b_trans, M, N, K, scaleAB, A, lda, B, ldb, scaleT, C, ldc); } diff --git a/paddle/scripts/docker/build_android.sh b/paddle/scripts/docker/build_android.sh index 593ae28e49..a61c7c40e9 100644 --- a/paddle/scripts/docker/build_android.sh +++ b/paddle/scripts/docker/build_android.sh @@ -2,11 +2,31 @@ set -xe -mkdir -p /paddle/build_android/$ANDROID_ABI -cd /paddle/build_android/$ANDROID_ABI -rm -rf /paddle/install 2>/dev/null || true +COMPILER=gcc +USE_EIGEN=ON +if [ $COMPILER == clang ]; then + SUFFIX=_clang + C_COMPILER=clang + CXX_COMPILER=clang++ +else + SUFFIX=_gcc + C_COMPILER=gcc + CXX_COMPILER=g++ +fi +if [ $USE_EIGEN == ON ]; then + SUFFIX=${SUFFIX}_eigen +else + SUFFIX=${SUFFIX}_openblas +fi -THIRD_PARTY_PATH=/paddle/third_party_android/$ANDROID_ABI +BUILD_ROOT=/paddle/build_android$SUFFIX +DEST_ROOT=/paddle/install$SUFFIX + +rm -rf $BUILD_ROOT 2>/dev/null || true +mkdir -p $BUILD_ROOT +cd $BUILD_ROOT + +THIRD_PARTY_PATH=/paddle/third_party_android$SUFFIX/$ANDROID_ABI if [ $ANDROID_ABI == "armeabi-v7a" ]; then cmake -DCMAKE_SYSTEM_NAME=Android \ @@ -14,27 +34,34 @@ if [ $ANDROID_ABI == "armeabi-v7a" ]; then -DANDROID_ABI=$ANDROID_ABI \ -DANDROID_ARM_NEON=ON \ -DANDROID_ARM_MODE=ON \ + -DCMAKE_C_COMPILER=$ANDROID_ARM_STANDALONE_TOOLCHAIN/bin/arm-linux-androideabi-${C_COMPILER} \ + -DCMAKE_CXX_COMPILER=$ANDROID_ARM_STANDALONE_TOOLCHAIN/bin/arm-linux-androideabi-${CXX_COMPILER} \ -DHOST_C_COMPILER=/usr/bin/gcc \ -DHOST_CXX_COMPILER=/usr/bin/g++ \ - -DCMAKE_INSTALL_PREFIX=/paddle/install \ + -DCMAKE_INSTALL_PREFIX=$DEST_ROOT \ -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ -DCMAKE_BUILD_TYPE=Release \ + -DUSE_EIGEN_FOR_BLAS=${USE_EIGEN} \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle -elif [ $ANDROID_ABI == "arm64-v7a" ]; then + -DWITH_STYLE_CHECK=OFF \ + .. +elif [ $ANDROID_ABI == "arm64-v8a" ]; then cmake -DCMAKE_SYSTEM_NAME=Android \ -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM64_STANDALONE_TOOLCHAIN \ -DANDROID_ABI=$ANDROID_ABI \ -DANDROID_ARM_MODE=ON \ + -DCMAKE_C_COMPILER=$ANDROID_ARM64_STANDALONE_TOOLCHAIN/bin/aarch64-linux-android-${C_COMPILER} \ + -DCMAKE_CXX_COMPILER=$ANDROID_ARM64_STANDALONE_TOOLCHAIN/bin/aarch64-linux-android-${CXX_COMPILER} \ -DHOST_C_COMPILER=/usr/bin/gcc \ -DHOST_CXX_COMPILER=/usr/bin/g++ \ - -DCMAKE_INSTALL_PREFIX=/paddle/install \ + -DCMAKE_INSTALL_PREFIX=$DEST_ROOT \ -DTHIRD_PARTY_PATH=$THIRD_PARTY_PATH \ -DCMAKE_BUILD_TYPE=Release \ + -DUSE_EIGEN_FOR_BLAS=${USE_EIGEN} \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle + .. elif [ $ANDROID_ABI == "armeabi" ]; then cmake -DCMAKE_SYSTEM_NAME=Android \ -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_ARM_STANDALONE_TOOLCHAIN \ @@ -47,10 +74,10 @@ elif [ $ANDROID_ABI == "armeabi" ]; then -DCMAKE_BUILD_TYPE=Release \ -DWITH_C_API=ON \ -DWITH_SWIG_PY=OFF \ - /paddle + .. else echo "Invalid ANDROID_ABI: $ANDROID_ABI" fi -make -j `nproc` -make install -j `nproc` +make VERBOSE=1 -j2 +make install -j2 From 46034faf97e7a135756d36391f5c4a970fed92ab Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 29 Aug 2017 16:05:10 -0700 Subject: [PATCH 014/295] Design doc: operator based parameter server. --- doc/design/ops/dist_train.md | 82 +++++++++++++++++++++++++ doc/design/ops/src/dist-graph.graffle | Bin 0 -> 4915 bytes doc/design/ops/src/dist-graph.png | Bin 0 -> 133866 bytes doc/design/ops/src/local-graph.graffle | Bin 0 -> 2515 bytes doc/design/ops/src/local-graph.png | Bin 0 -> 31493 bytes 5 files changed, 82 insertions(+) create mode 100644 doc/design/ops/dist_train.md create mode 100644 doc/design/ops/src/dist-graph.graffle create mode 100644 doc/design/ops/src/dist-graph.png create mode 100644 doc/design/ops/src/local-graph.graffle create mode 100644 doc/design/ops/src/local-graph.png diff --git a/doc/design/ops/dist_train.md b/doc/design/ops/dist_train.md new file mode 100644 index 0000000000..0380826b0d --- /dev/null +++ b/doc/design/ops/dist_train.md @@ -0,0 +1,82 @@ +# Design Doc: Operation Graph Based Parameter Server + +## Abstract + +We propose an approach to implment the parameter server. In this +approach, there is no fundimental difference between the trainer and +the parameter server: they both run sub-graphs, but sub-graphs of +different purposes. + +## Background + +The previous implementations of the parameter server does not run a +sub-graph. parameter initialization, optimizer computation, network +communication and checkpointing are implemented twice on both the +trainer and the parameter server. + +It would be great if we can write code once and use them on both the +trainer and the parameter server: reduces code duplication and +improves extensibility. Given during the current refactor, we are +representing everything as a computing graph on the +trainer. Representing everything as a computing graph on the parameter +server becomes a natural extension. + +## Design + +### Graph Converter + +The *graph converter* converts user-defined operation (OP) graph into +sub-graphs to be scheduled on different nodes. + +1. The user-defined OP graph will be cut into sub-graphs of +different purposes (e.g., trainer, parameter server) to run on +different workers. + +1. OPs will be added to the subgraphs, so the subgraphs can +communicate with each other. We will need these OPs: *send*, *recv*, +*gradient accumulator*, *string accumulator*, *loop forever*. + +Below is an example of converting the user defined graph to the +sub-graphs for the trainer and the parameter server: + + + +After converting: + + + +1. The parameter variable W and it's optimizer subgraph are placed on the parameter server. +1. Operators are added to the sub-graphs. + - *send* operator sends data and sender's address to the destination. + - *recv* operator receives data and sender's address from the + destination. It will block until data has been received. + - *gradient accumulator* operator accumulates *N* pieces of + gradients. N=1 in Async-SGD, N>1 in Sync-SGD. + - *string accumulator* accumulates *N* pieces of strings into a + list of strings. N=1 in Async-SGD, N>1 in Sync-SGD. + - *loop forever* runs itself as a target forever. + +### Benefits + +- Model parallelism become easier to implement: it's an extension to + the trainer - parameter server approach. we already have the + communication OPs, but need to extend the graph converter. + +- User-defined optimizer is easier to add - user can now express it as + a subgraph. + +- No more duplication logic inside the trainer and the parameter + server in the background section. + +### Challenges + +- It might be hard for the graph converter to cut a general graph + (without any hint for which sub-graph is the optimizer). We may need + to label which sub-graph inside the OP graph is the optimizer. + +- It's important to balance the parameter shards of on multiple + parameter server. If a single parameter is very big (some + word-embedding, fully connected, softmax layer), we need to + automatically partition the single parameter onto different + parameter servers when possible (only element-wise optimizer depends + on the parameter variable). diff --git a/doc/design/ops/src/dist-graph.graffle b/doc/design/ops/src/dist-graph.graffle new file mode 100644 index 0000000000000000000000000000000000000000..1e1cb18dfecd9ee956ce4fe721a9bec4a24282c2 GIT binary patch literal 4915 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 literal 0 HcmV?d00001 diff --git a/doc/design/ops/src/dist-graph.png b/doc/design/ops/src/dist-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..6f49dce07415025ade04bf0227f652c98540a056 GIT binary patch literal 133866 zcmcG0Wmr~Q*EJv_B`poopmet&jg%nLor+4QbV`>>r=TE+bV*2ulp-J?(gFg~E%D78 z^_=s**Y*AQethTRJdfUI?|bjH)?9OrG3Ho=-By*qj7g4(goJciQ9(un2?8{n0~PClPmXy7MbU;LnJUIq7K5FL8b#PN$=C zn?~Bs(UOLrgO7uY?gl0e4UL$ig_VegjNG5w;cwz}_ne*WML0R#+}t?acscAGtvR`c zg@rk}csO}@*x?FxCl6a^Q+IY-C;H!C^7ngWES=09@7p`yx3i@|e6OjQor|+L9UbC9 z|NQg&J)Q4c{reEs08e}iA_{L26F z*}u>Eef{lwcFuM%3y$~A6>Xg@9pPqYQ^ecd;Q4d+|NV;p{atE~_bp-6=XZ1ex%
jb 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

VfWjnL%wr+lwkisBUefSM*2 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 vd}Q4_JiOrGQ2Ht3Dw=$?NEfJtmTn>KdmR3r Date: Tue, 29 Aug 2017 16:07:26 -0700 Subject: [PATCH 015/295] change image size --- doc/design/ops/dist_train.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/ops/dist_train.md b/doc/design/ops/dist_train.md index 0380826b0d..0bc350d8c0 100644 --- a/doc/design/ops/dist_train.md +++ b/doc/design/ops/dist_train.md @@ -39,11 +39,11 @@ communicate with each other. We will need these OPs: *send*, *recv*, Below is an example of converting the user defined graph to the sub-graphs for the trainer and the parameter server: - + After converting: - + 1. The parameter variable W and it's optimizer subgraph are placed on the parameter server. 1. Operators are added to the sub-graphs. From 75856ec3370358b0c182e03095032a01c8202fa7 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 29 Aug 2017 16:17:49 -0700 Subject: [PATCH 016/295] fix typo --- doc/design/ops/dist_train.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/design/ops/dist_train.md b/doc/design/ops/dist_train.md index 0bc350d8c0..8e92c87a59 100644 --- a/doc/design/ops/dist_train.md +++ b/doc/design/ops/dist_train.md @@ -2,8 +2,8 @@ ## Abstract -We propose an approach to implment the parameter server. In this -approach, there is no fundimental difference between the trainer and +We propose an approach to implement the parameter server. In this +approach, there is no fundamental difference between the trainer and the parameter server: they both run sub-graphs, but sub-graphs of different purposes. @@ -16,7 +16,7 @@ trainer and the parameter server. It would be great if we can write code once and use them on both the trainer and the parameter server: reduces code duplication and -improves extensibility. Given during the current refactor, we are +improves extensibility. Given that after the current refactor, we are representing everything as a computing graph on the trainer. Representing everything as a computing graph on the parameter server becomes a natural extension. @@ -25,8 +25,8 @@ server becomes a natural extension. ### Graph Converter -The *graph converter* converts user-defined operation (OP) graph into -sub-graphs to be scheduled on different nodes. +The *graph converter* converts the user-defined operation (OP) graph +into sub-graphs to be scheduled on different nodes. 1. The user-defined OP graph will be cut into sub-graphs of different purposes (e.g., trainer, parameter server) to run on @@ -66,7 +66,7 @@ After converting: a subgraph. - No more duplication logic inside the trainer and the parameter - server in the background section. + server mentioned in the background section. ### Challenges From 6efbe2ff43be576c64962f94f6fcf453ef0dd8a7 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 30 Aug 2017 12:03:49 +0800 Subject: [PATCH 017/295] Merge im2col functor. --- paddle/operators/math/im2col.cc | 215 ++++++++++++++++++++ paddle/operators/math/im2col.cu | 334 ++++++++++++++++++++++++++++++++ paddle/operators/math/im2col.h | 86 ++++++++ 3 files changed, 635 insertions(+) create mode 100644 paddle/operators/math/im2col.cc create mode 100644 paddle/operators/math/im2col.cu create mode 100644 paddle/operators/math/im2col.h diff --git a/paddle/operators/math/im2col.cc b/paddle/operators/math/im2col.cc new file mode 100644 index 0000000000..dafb21b335 --- /dev/null +++ b/paddle/operators/math/im2col.cc @@ -0,0 +1,215 @@ +/* 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/operators/math/im2col.cu b/paddle/operators/math/im2col.cu new file mode 100644 index 0000000000..60bcdf8acc --- /dev/null +++ b/paddle/operators/math/im2col.cu @@ -0,0 +1,334 @@ +/* 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 "hl_device_functions.cuh" + +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; + } + } + } +} + +/* + * 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 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<<>>( + 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; + } + } +} + +/* + * 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]; + + 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<<>>( + 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, 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 >= 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<<>>( + imData, colData, inputChannels, inputHeight, inputWidth, filterHeight, + filterWidth, strideHeight, strideWidth, paddingHeight, paddingWidth, + outputHeight, outputWidth); + CHECK_SYNC("Im2ColFunctor GPU failed"); + } +}; + +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 = + * [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]; + + 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<<>>( + 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/operators/math/im2col.h b/paddle/operators/math/im2col.h new file mode 100644 index 0000000000..4568ca2fd1 --- /dev/null +++ b/paddle/operators/math/im2col.h @@ -0,0 +1,86 @@ +/* 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 "TensorShape.h" +#include "TensorType.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 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. + * \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] + * 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 convolution matrix + * [inputChannels, + * filterHeight, + * filterWidth, ======> [height, width] + * 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 matrix + * [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 From f7be9cb97aa4b90c0ccd6f954a71d3caada4dac7 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 30 Aug 2017 13:55:58 +0800 Subject: [PATCH 018/295] Refine the cpu code. --- paddle/operators/math/CMakeLists.txt | 4 +- paddle/operators/math/im2col.cc | 319 +++++++++++++++------------ paddle/operators/math/im2col.h | 20 +- 3 files changed, 186 insertions(+), 157 deletions(-) diff --git a/paddle/operators/math/CMakeLists.txt b/paddle/operators/math/CMakeLists.txt index ed51d416ed..f31281ebac 100644 --- a/paddle/operators/math/CMakeLists.txt +++ b/paddle/operators/math/CMakeLists.txt @@ -1,8 +1,8 @@ if(WITH_GPU) - nv_library(math_function SRCS math_function.cc math_function.cu DEPS cblas device_context) + nv_library(math_function SRCS math_function.cc math_function.cu im2col.cc DEPS cblas device_context) else() - cc_library(math_function SRCS math_function.cc DEPS cblas device_context) + cc_library(math_function SRCS math_function.cc im2col.cc DEPS cblas device_context) endif() nv_test(math_function_test SRCS math_function_test.cc DEPS math_function tensor) diff --git a/paddle/operators/math/im2col.cc b/paddle/operators/math/im2col.cc index dafb21b335..8124e322cb 100644 --- a/paddle/operators/math/im2col.cc +++ b/paddle/operators/math/im2col.cc @@ -12,48 +12,54 @@ WITHOUT 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 "paddle/operators/math/im2col.h" namespace paddle { /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + * im = [input_channels, input_height, input_width] + * col = + * [input_channels, filter_height, filter_width, output_height, output_width] */ template -class Im2ColFunctor { +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); + void operator()(const framework::Tensor& im, framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[1]; + int filter_width = col.dims()[2]; + int output_height = col.dims()[3]; + int output_width = col.dims()[4]; + int channels_col = input_channels * filter_height * filter_width; + + const T* im_data = im.data(); + T* col_data = col.data(); + + for (int c = 0; c < channels_col; ++c) { + int w_offset = c % filter_width; + int h_offset = (c / filter_width) % filter_height; + int c_im = c / filter_width / filter_height; + for (int h = 0; h < output_height; ++h) { + for (int w = 0; w < output_width; ++w) { + int im_row_idx = h * stride_height + h_offset; + int im_col_idx = w * stride_width + w_offset; + if ((im_row_idx - padding_height) < 0 || + (im_row_idx - padding_height) >= input_height || + (im_col_idx - padding_width) < 0 || + (im_col_idx - padding_width) >= input_width) { + col_data[(c * output_height + h) * output_width + w] = T(0); } else { - imRowIdx += c_im * inputHeight - paddingHeight; - imColIdx -= paddingWidth; - colData[(c * outputHeight + h) * outputWidth + w] = - imData[imRowIdx * inputWidth + imColIdx]; + im_row_idx += c_im * input_height - padding_height; + im_col_idx -= padding_width; + col_data[(c * output_height + h) * output_width + w] = + im_data[im_row_idx * input_width + im_col_idx]; } } } @@ -62,41 +68,46 @@ class Im2ColFunctor { }; /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + * im = [input_channels, input_height, input_width] + * col = + * [input_channels, filter_height, filter_width, output_height, output_width] */ template -class Col2ImFunctor { +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]; + void operator()(framework::Tensor& im, const framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[1]; + int filter_width = col.dims()[2]; + int output_height = col.dims()[3]; + int output_width = col.dims()[4]; + int channels_col = input_channels * filter_height * filter_width; + + T* im_data = im.data(); + const T* col_data = col.data(); + + for (int c = 0; c < channels_col; ++c) { + int w_offset = c % filter_width; + int h_offset = (c / filter_width) % filter_height; + int c_im = c / filter_width / filter_height; + for (int h = 0; h < output_height; ++h) { + for (int w = 0; w < output_width; ++w) { + int im_row_idx = h * stride_height + h_offset; + int im_col_idx = w * stride_width + w_offset; + if ((im_row_idx - padding_height) >= 0 && + (im_row_idx - padding_height) < input_height && + (im_col_idx - padding_width) >= 0 && + (im_col_idx - padding_width) < input_width) { + im_row_idx += c_im * input_height - padding_height; + im_col_idx -= padding_width; + im_data[im_row_idx * input_width + im_col_idx] += + col_data[(c * output_height + h) * output_width + w]; } } } @@ -104,52 +115,61 @@ class Col2ImFunctor { } }; -template class Im2ColFunctor; -template class Im2ColFunctor; -template class Col2ImFunctor; -template class Col2ImFunctor; +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * im = [input_channels, input_height, input_width] + * col = + * [output_height, output_width, input_channels, filter_height, filter_width] */ template -class Im2ColFunctor { +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); + void operator()(const framework::Tensor& im, framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[3]; + int filter_width = col.dims()[4]; + int output_height = col.dims()[0]; + int output_width = col.dims()[1]; + + const T* im_data = im.data(); + T* col_data = col.data(); + + for (int col_row_idx = 0; col_row_idx < output_height; ++col_row_idx) { + for (int col_col_idx = 0; col_col_idx < output_width; ++col_col_idx) { + for (int channel = 0; channel < input_channels; ++channel) { + for (int filter_row_idx = 0; filter_row_idx < filter_height; + ++filter_row_idx) { + for (int filter_col_idx = 0; filter_col_idx < filter_width; + ++filter_col_idx) { + int im_row_offset = + col_row_idx * stride_height + filter_row_idx - padding_height; + int im_col_offset = + col_col_idx * stride_width + filter_col_idx - padding_width; + int col_offset = (((col_row_idx * output_width + col_col_idx) * + input_channels + + channel) * + filter_height + + filter_row_idx) * + filter_width + + filter_col_idx; + if (im_row_offset < 0 || im_row_offset >= input_height || + im_col_offset < 0 || im_col_offset >= input_width) { + col_data[col_offset] = T(0); } else { - int imDataOffset = - (channel * inputHeight + imRowOffset) * inputWidth + - imColOffset; - colData[colDataOffset] = imData[imDataOffset]; + int im_offset = + (channel * input_height + im_row_offset) * input_width + + im_col_offset; + col_data[col_offset] = im_data[im_offset]; } } } @@ -160,44 +180,53 @@ class Im2ColFunctor { }; /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * im = [input_channels, input_height, input_width] + * col = + * [output_height, output_width, input_channels, filter_height, filter_width] */ template -class Col2ImFunctor { +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]; + void operator()(framework::Tensor& im, const framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[3]; + int filter_width = col.dims()[4]; + int output_height = col.dims()[0]; + int output_width = col.dims()[1]; + + T* im_data = im.data(); + const T* col_data = col.data(); + + for (int col_row_idx = 0; col_row_idx < output_height; ++col_row_idx) { + for (int col_col_idx = 0; col_col_idx < output_width; ++col_col_idx) { + for (int channel = 0; channel < input_channels; ++channel) { + for (int filter_row_idx = 0; filter_row_idx < filter_height; + ++filter_row_idx) { + for (int filter_col_idx = 0; filter_col_idx < filter_width; + ++filter_col_idx) { + int im_row_offset = + col_row_idx * stride_height + filter_row_idx - padding_height; + int im_col_offset = + col_col_idx * stride_width + filter_col_idx - padding_width; + int col_offset = (((col_row_idx * output_width + col_col_idx) * + input_channels + + channel) * + filter_height + + filter_row_idx) * + filter_width + + filter_col_idx; + if (im_row_offset >= 0 && im_row_offset < input_height && + im_col_offset >= 0 && im_col_offset < input_width) { + int im_offset = + (channel * input_height + im_row_offset) * input_width + + im_col_offset; + im_data[im_offset] += col_data[col_offset]; } } } @@ -207,9 +236,9 @@ class Col2ImFunctor { } }; -template class Im2ColFunctor; -template class Im2ColFunctor; -template class Col2ImFunctor; -template class Col2ImFunctor; +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; } // namespace paddle diff --git a/paddle/operators/math/im2col.h b/paddle/operators/math/im2col.h index 4568ca2fd1..f2f982b687 100644 --- a/paddle/operators/math/im2col.h +++ b/paddle/operators/math/im2col.h @@ -14,8 +14,8 @@ limitations under the License. */ #pragma once -#include "TensorShape.h" -#include "TensorType.h" +#include "paddle/framework/tensor.h" +#include "paddle/platform/device_context.h" namespace paddle { @@ -67,20 +67,20 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * \note The caller needs to ensure that imShape.inputChannels is equal to * colShape.inputChannels. */ -template +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); + void operator()(const framework::Tensor& im, framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width); }; -template +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); + void operator()(framework::Tensor& im, const framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width); }; } // namespace paddle From e967645c15a1264855bac7cbd88dd6a9ed5301ad Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 30 Aug 2017 14:31:28 +0800 Subject: [PATCH 019/295] Refine the gpu code. --- paddle/operators/math/CMakeLists.txt | 3 +- paddle/operators/math/im2col.cu | 452 ++++++++++++++------------- 2 files changed, 236 insertions(+), 219 deletions(-) diff --git a/paddle/operators/math/CMakeLists.txt b/paddle/operators/math/CMakeLists.txt index f31281ebac..7a41e294a8 100644 --- a/paddle/operators/math/CMakeLists.txt +++ b/paddle/operators/math/CMakeLists.txt @@ -1,6 +1,7 @@ if(WITH_GPU) - nv_library(math_function SRCS math_function.cc math_function.cu im2col.cc DEPS cblas device_context) + nv_library(math_function SRCS math_function.cc math_function.cu im2col.cc + im2col.cu DEPS cblas device_context) else() cc_library(math_function SRCS math_function.cc im2col.cc DEPS cblas device_context) endif() diff --git a/paddle/operators/math/im2col.cu b/paddle/operators/math/im2col.cu index 60bcdf8acc..875989af58 100644 --- a/paddle/operators/math/im2col.cu +++ b/paddle/operators/math/im2col.cu @@ -12,86 +12,89 @@ WITHOUT 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 "hl_device_functions.cuh" +#include "paddle/operators/math/im2col.h" +#include "paddle/platform/cuda_helper.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) { +__global__ void im2col(const T* data_im, int num_outs, int height, int width, + int filter_height, int filter_width, int stride_height, + int stride_width, int padding_height, int padding_width, + int output_height, int output_width, 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; + if (index < num_outs) { + int w_out = index % output_width; + index /= output_width; + int h_out = index % output_height; + int channel_in = index / output_height; + int channel_out = channel_in * filter_height * filter_width; + int h_in = h_out * stride_height; + int w_in = w_out * stride_width; - 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) { + data_col += (channel_out * output_height + h_out) * output_width + w_out; + for (int i = 0; i < filter_height; ++i) { + for (int j = 0; j < filter_width; ++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) { + if ((rIdx - (int)padding_height) >= (int)height || + (rIdx - (int)padding_height) < 0 || + (cIdx - (int)padding_width) >= (int)width || + (cIdx - (int)padding_width) < 0) { *data_col = 0; } else { - rIdx = rIdx + channel_in * height - paddingH; - cIdx = cIdx - paddingW; + rIdx = rIdx + channel_in * height - padding_height; + cIdx = cIdx - padding_width; *data_col = data_im[rIdx * width + cIdx]; } - data_col += height_col * width_col; + data_col += output_height * output_width; } } } } /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + * im = [input_channels, input_height, input_width] + * col = + * [input_channels, filter_height, filter_width, output_height, output_width] */ template -class Im2ColFunctor { +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]; + void operator()(const framework::Tensor& im, framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); - int numKernels = inputChannels * outputHeight * outputWidth; - int blocks = (numKernels + 1024 - 1) / 1024; - int blockX = 512; - int blockY = (blocks + 512 - 1) / 512; + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[1]; + int filter_width = col.dims()[2]; + int output_height = col.dims()[3]; + int output_width = col.dims()[4]; + + int num_outputs = input_channels * output_height * output_width; + int blocks = (num_outputs + 1024 - 1) / 1024; + int block_x = 512; + int block_y = (blocks + 512 - 1) / 512; dim3 threads(1024, 1); - dim3 grid(blockX, blockY); - im2col<<>>( - imData, numKernels, inputHeight, inputWidth, filterHeight, filterWidth, - strideHeight, strideWidth, paddingHeight, paddingWidth, outputHeight, - outputWidth, colData); - CHECK_SYNC("Im2ColFunctor GPU failed"); + dim3 grid(block_x, block_y); + im2col<<>>( + im.data(), num_outputs, input_height, input_width, filter_height, + filter_width, stride_height, stride_width, padding_height, + padding_width, output_height, output_width, col.data()); } }; 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 channels, size_t filter_height, + size_t filter_width, size_t stride_height, + size_t stride_width, size_t padding_height, + size_t padding_width, size_t output_height, + size_t output_width, T* data_im) { size_t index = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; if (index < n) { @@ -99,104 +102,112 @@ __global__ void col2im(size_t n, const T* data_col, size_t height, size_t width, 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)) { + if ((w - (int)padding_width) >= 0 && + (w - (int)padding_width) < (width - 2 * padding_width) && + (h - (int)padding_height) >= 0 && + (h - padding_height) < (height - 2 * padding_height)) { // 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)); + int w_col_start = (w < (int)filter_width) + ? 0 + : (w - int(filter_width)) / (int)stride_width + 1; + int w_col_end = + min((int)(w / (int)stride_width + 1), (int)(output_width)); + int h_col_start = (h < (int)filter_height) + ? 0 + : (h - (int)filter_height) / (int)stride_height + 1; + int h_col_end = min(int(h / stride_height + 1), int(output_height)); 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]; + int c_col = int(c * filter_height * filter_width) + + (h - h_col * (int)stride_height) * (int)filter_width + + (w - w_col * (int)stride_width); + val += + data_col[(c_col * output_height + h_col) * output_width + w_col]; } } - h -= paddingH; - w -= paddingW; - data_im[c * ((width - 2 * paddingW) * (height - 2 * paddingH)) + - h * (width - 2 * paddingW) + w] += val; + h -= padding_height; + w -= padding_width; + data_im[c * ((width - 2 * padding_width) * + (height - 2 * padding_height)) + + h * (width - 2 * padding_width) + w] += val; } } } /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + * im = [input_channels, input_height, input_width] + * col = + * [input_channels, filter_height, filter_width, output_height, output_width] */ template -class Col2ImFunctor { +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]; + void operator()(framework::Tensor& im, const framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[1]; + int filter_width = col.dims()[2]; + int output_height = col.dims()[3]; + int output_width = col.dims()[4]; - size_t numKernels = inputChannels * (inputHeight + 2 * paddingHeight) * - (inputWidth + 2 * paddingWidth); + size_t num_kernels = input_channels * (input_height + 2 * padding_height) * + (input_width + 2 * padding_width); - size_t blocks = (numKernels + 1024 - 1) / 1024; - size_t blockX = 512; - size_t blockY = (blocks + 512 - 1) / 512; + size_t blocks = (num_kernels + 1024 - 1) / 1024; + size_t block_x = 512; + size_t block_y = (blocks + 512 - 1) / 512; dim3 threads(1024, 1); - dim3 grid(blockX, blockY); + dim3 grid(block_x, block_y); // To avoid involving atomic operations, we will launch one kernel per // bottom dimension, and then in the kernel add up the top dimensions. - col2im<<>>( - numKernels, colData, inputHeight + 2 * paddingHeight, - inputWidth + 2 * paddingWidth, inputChannels, filterHeight, filterWidth, - strideHeight, strideWidth, paddingHeight, paddingWidth, outputHeight, - outputWidth, imData); - CHECK_SYNC("Col2ImFunctor GPU failed"); + col2im<<>>( + num_kernels, col.data(), input_height + 2 * padding_height, + input_width + 2 * padding_width, input_channels, filter_height, + filter_width, stride_height, stride_width, padding_height, + padding_width, output_height, output_width, im.data()); } }; -template class Im2ColFunctor; -template class Im2ColFunctor; -template class Col2ImFunctor; -template class Col2ImFunctor; +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; 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 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; +__global__ void im2colOCF(const T* im_data, T* col_data, int input_channels, + int input_height, int input_width, int filter_height, + int filter_width, int stride_height, int stride_width, + int padding_height, int padding_width, + int output_height, int output_width) { + int swid = blockIdx.x; + int shid = blockIdx.y; + for (int channelid = threadIdx.z; channelid < input_channels; + channelid += blockDim.z) { + for (int idy = threadIdx.y; idy < filter_height; idy += blockDim.y) { + for (int idx = threadIdx.x; idx < filter_width; idx += blockDim.x) { + int width_offset = idx + swid * stride_width - padding_width; + int height_offset = idy + shid * stride_height - padding_height; + int im_offset = width_offset + height_offset * input_width + + channelid * input_height * input_width; - int colOffset = idx + idy * filterWidth + - channelId * filterHeight * filterWidth + - (shId * outputWidth + swId) * - (inputChannels * filterHeight * filterWidth); + int col_offset = idx + idy * filter_width + + channelid * filter_height * filter_width + + (shid * output_width + swid) * + (input_channels * filter_height * filter_width); - if (heightOffset >= inputHeight || heightOffset < 0 || - widthOffset >= inputWidth || widthOffset < 0) { - colData[colOffset] = T(0); + if (height_offset >= input_height || height_offset < 0 || + width_offset >= input_width || width_offset < 0) { + col_data[col_offset] = T(0); } else { - colData[colOffset] = imData[imOffset]; + col_data[col_offset] = im_data[im_offset]; } } } @@ -204,76 +215,79 @@ __global__ void im2colOCF(const T* imData, T* colData, int inputChannels, } /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * im = [input_channels, input_height, input_width] + * col = + * [output_height, output_width, input_channels, filter_height, filter_width] */ template -class Im2ColFunctor { +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]; + void operator()(const framework::Tensor& im, framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[3]; + int filter_width = col.dims()[4]; + int output_height = col.dims()[0]; + int output_width = col.dims()[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; + int block_dim_x = 0; + int block_dim_y = 0; + if (filter_height <= 4 && filter_width <= 4) { + block_dim_x = 4; + block_dim_y = 4; + } else if (filter_height <= 8 && filter_width <= 8) { + block_dim_x = 8; + block_dim_y = 8; + } else if (filter_height <= 16 && filter_width <= 16) { + block_dim_x = 16; + block_dim_y = 16; } else { - blockDimX = 32; - blockDimY = 32; + block_dim_x = 32; + block_dim_y = 32; } - int blockDimZ = 1024 / blockDimX / blockDimY; - dim3 threads(blockDimX, blockDimY, std::min(blockDimZ, inputChannels)); - dim3 grid(outputWidth, outputHeight); - im2colOCF<<>>( - imData, colData, inputChannels, inputHeight, inputWidth, filterHeight, - filterWidth, strideHeight, strideWidth, paddingHeight, paddingWidth, - outputHeight, outputWidth); - CHECK_SYNC("Im2ColFunctor GPU failed"); + int block_dim_z = 1024 / block_dim_x / block_dim_y; + dim3 threads(block_dim_x, block_dim_y, + std::min(block_dim_z, input_channels)); + dim3 grid(output_width, output_height); + im2colOCF<<>>( + im.data(), col.data(), input_channels, input_height, input_width, + filter_height, filter_width, stride_height, stride_width, + padding_height, padding_width, output_height, output_width); } }; 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; +__global__ void col2imOCF(T* im_data, const T* col_data, int input_channels, + int input_height, int input_width, int filter_height, + int filter_width, int stride_height, int stride_width, + int padding_height, int padding_width, + int output_height, int output_width) { + int swid = blockIdx.x; + int shid = blockIdx.y; + for (int channelid = threadIdx.z; channelid < input_channels; + channelid += blockDim.z) { + for (int idy = threadIdx.y; idy < filter_height; idy += blockDim.y) { + for (int idx = threadIdx.x; idx < filter_width; idx += blockDim.x) { + int width_offset = idx + swid * stride_width - padding_width; + int height_offset = idy + shid * stride_height - padding_height; + int im_offset = width_offset + height_offset * input_width + + channelid * input_height * input_width; - int colOffset = idx + idy * filterWidth + - channelId * filterHeight * filterWidth + - (shId * outputWidth + swId) * - (inputChannels * filterHeight * filterWidth); + int col_offset = idx + idy * filter_width + + channelid * filter_height * filter_width + + (shid * output_width + swid) * + (input_channels * filter_height * filter_width); - if (heightOffset >= 0 && heightOffset < inputHeight && - widthOffset >= 0 && widthOffset < inputWidth) { - paddle::paddleAtomicAdd(imData + imOffset, colData[colOffset]); + if (height_offset >= 0 && height_offset < input_height && + width_offset >= 0 && width_offset < input_width) { + paddle::platform::CudaAtomicAdd(im_data + im_offset, + col_data[col_offset]); } } } @@ -281,54 +295,56 @@ __global__ void col2imOCF(T* imData, const T* colData, int inputChannels, } /* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * im = [input_channels, input_height, input_width] + * col = + * [output_height, output_width, input_channels, filter_height, filter_width] */ template -class Col2ImFunctor { +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]; + void operator()(framework::Tensor& im, const framework::Tensor& col, + int stride_height, int stride_width, int padding_height, + int padding_width) { + PADDLE_ENFORCE(im.dims().size() == 3); + PADDLE_ENFORCE(col.dims().size() == 5); + int input_channels = im.dims()[0]; + int input_height = im.dims()[1]; + int input_width = im.dims()[2]; + int filter_height = col.dims()[3]; + int filter_width = col.dims()[4]; + int output_height = col.dims()[0]; + int output_width = col.dims()[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; + int block_dim_x = 0; + int block_dim_y = 0; + if (filter_height <= 4 && filter_width <= 4) { + block_dim_x = 4; + block_dim_y = 4; + } else if (filter_height <= 8 && filter_width <= 8) { + block_dim_x = 8; + block_dim_y = 8; + } else if (filter_height <= 16 && filter_width <= 16) { + block_dim_x = 16; + block_dim_y = 16; } else { - blockDimX = 32; - blockDimY = 32; + block_dim_x = 32; + block_dim_y = 32; } - int blockDimZ = 1024 / blockDimX / blockDimY; - dim3 threads(blockDimX, blockDimY, std::min(blockDimZ, inputChannels)); - dim3 grid(outputWidth, outputHeight); - col2imOCF<<>>( - imData, colData, inputChannels, inputHeight, inputWidth, filterHeight, - filterWidth, strideHeight, strideWidth, paddingHeight, paddingWidth, - outputHeight, outputWidth); - CHECK_SYNC("Col2ImFunctor GPU failed"); + int block_dim_z = 1024 / block_dim_x / block_dim_y; + dim3 threads(block_dim_x, block_dim_y, + std::min(block_dim_z, input_channels)); + dim3 grid(output_width, output_height); + col2imOCF<<>>( + im.data(), col.data(), input_channels, input_height, input_width, + filter_height, filter_width, stride_height, stride_width, + padding_height, padding_width, output_height, output_width); } }; -template class Im2ColFunctor; -template class Im2ColFunctor; -template class Col2ImFunctor; -template class Col2ImFunctor; +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; } // namespace paddle From 2d707e32c83d92a857b7e5359aae9415f8464d11 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 30 Aug 2017 14:39:32 +0800 Subject: [PATCH 020/295] Refine the comments. --- paddle/operators/math/im2col.h | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/paddle/operators/math/im2col.h b/paddle/operators/math/im2col.h index f2f982b687..da51bc69a2 100644 --- a/paddle/operators/math/im2col.h +++ b/paddle/operators/math/im2col.h @@ -29,40 +29,40 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * * \param imData Image data. * \param imShape The shape of imData, - * [inputChannels, inputHeight, inputWidth]. + * [input_channels, input_height, input_width]. * \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] + * [input_channels, filter_height, filter_width, output_height, output_width] * 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. + * input_channels * filter_height * filter_width, and the width is equal + * output_height * output_width. * * Reshape: * shape of colData shape of convolution matrix - * [inputChannels, - * filterHeight, - * filterWidth, ======> [height, width] - * outputHeight, - * outputWidth] + * [input_channels, + * filter_height, + * filter_width, ======> [height, width] + * output_height, + * output_width] * * If the template argument Format is kOCF, the shape of colData is: - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * [output_height, output_width, input_channels, filter_height, filter_width] * 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. + * The shape of sequence matrix is [seq_length, step_size], where the seq_length + * is equal output_height * output_width, and the step_size is equal + * input_channels * filter_height * filter_width. * * Reshape: * shape of colData shape of sequence matrix - * [outputHeight, - * outputWidth, - * inputChannels, ======> [seqLength, stepSize] - * filterHeight, - * filterWidth] + * [output_height, + * output_width, + * input_channels, ======> [seqLength, stepSize] + * filter_height, + * filter_width] * * \note The caller needs to ensure that imShape.inputChannels is equal to * colShape.inputChannels. From 26cec83901dc443a60aef911c1ad2baf882eb474 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 30 Aug 2017 19:54:14 +0800 Subject: [PATCH 021/295] Add pad op --- paddle/operators/CMakeLists.txt | 1 + paddle/operators/pad_op.cc | 77 ++++++++++++++++++ paddle/operators/pad_op.cu | 21 +++++ paddle/operators/pad_op.h | 81 +++++++++++++++++++ paddle/pybind/CMakeLists.txt | 3 +- paddle/pybind/pybind.cc | 1 + .../paddle/v2/framework/tests/test_pad_op.py | 32 ++++++++ 7 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 paddle/operators/pad_op.cc create mode 100644 paddle/operators/pad_op.cu create mode 100644 paddle/operators/pad_op.h create mode 100644 python/paddle/v2/framework/tests/test_pad_op.py diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index f466dbc79a..1a759133e1 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -72,3 +72,4 @@ op_library(uniform_random_op SRCS uniform_random_op.cc uniform_random_op.cu) op_library(lookup_table_op SRCS lookup_table_op.cc lookup_table_op.cu) op_library(scale_op SRCS scale_op.cc scale_op.cu DEPS net_op) op_library(minus_op SRCS minus_op.cc minus_op.cu DEPS scale_op) +op_library(pad_op SRCS pad_op.cc pad_op.cu) diff --git a/paddle/operators/pad_op.cc b/paddle/operators/pad_op.cc new file mode 100644 index 0000000000..f96d61669b --- /dev/null +++ b/paddle/operators/pad_op.cc @@ -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. */ + +#include "paddle/operators/pad_op.h" + +namespace paddle { +namespace operators { + +using framework::Tensor; + +class PadOp : public framework::OperatorWithKernel { + public: + using framework::OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(const framework::InferShapeContext &ctx) const override { + auto dim0 = ctx.Input("X")->dims(); + auto dim1 = ctx.Output("Out")->dims(); + auto paddings = GetAttr>>("paddings"); + for (int i = 0; i < dim0.size(); ++i) { + dim1[i] = dim0[i] + paddings[i][0] + paddings[i][1]; + } + ctx.Output("Out")->Resize(dim1); + } +}; + +class MulOpMaker : public framework::OpProtoAndCheckerMaker { + public: + MulOpMaker(framework::OpProto *proto, framework::OpAttrChecker *op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("X", "The input of pad op"); + AddOutput("Out", "The output of pad op"); + AddComment(R"DOC( +Pad Operator. +)DOC"); + AddAttr>>( + "paddings", "The padding rules for each dimension"); + AddAttr("pad_value", "The value to be padded into tensor") + .SetDefault(0.0f); + } +}; + +class PadOpGrad : public framework::OperatorWithKernel { + public: + using framework::OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(const framework::InferShapeContext &ctx) const override { + PADDLE_ENFORCE_NOT_NULL(ctx.InputVar("X"), "Input(X) should not be null"); + PADDLE_ENFORCE_NOT_NULL(ctx.InputVar(framework::GradVarName("Out")), + "Input(Out@GRAD) should not be null"); + auto x_dims = ctx.Input("X")->dims(); + auto *x_grad = ctx.Output(framework::GradVarName("X")); + + x_grad->Resize(x_dims); + } +}; + +} // namespace operators +} // namespace paddle + +namespace ops = paddle::operators; +REGISTER_OP(pad, ops::PadOp, ops::PadOpMaker, pad_grad, ops::PadOpGrad); +REGISTER_OP_CPU_KERNEL(pad, ops::PadKernel); +REGISTER_OP_CPU_KERNEL(pad_grad, + ops::PadGradKernel); diff --git a/paddle/operators/pad_op.cu b/paddle/operators/pad_op.cu new file mode 100644 index 0000000000..555a7dba23 --- /dev/null +++ b/paddle/operators/pad_op.cu @@ -0,0 +1,21 @@ +/* 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. */ + +#define EIGEN_USE_GPU +#include "paddle/operators/pad_op.h" + +namespace ops = paddle::operators; +REGISTER_OP_GPU_KERNEL(pad, ops::PadKernel); +REGISTER_OP_GPU_KERNEL(pad_grad, + ops::PadGradKernel); diff --git a/paddle/operators/pad_op.h b/paddle/operators/pad_op.h new file mode 100644 index 0000000000..6a743bd31c --- /dev/null +++ b/paddle/operators/pad_op.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 "paddle/operators/math/math_function.h" + +#include "paddle/framework/eigen.h" +#include "paddle/framework/op_registry.h" + +namespace paddle { +namespace operators { + +using Tensor = framework::Tensor; + +template +using EigenTensor = framework::EigenTensor; + +template +class PadKernel : public framework::OpKernel { + public: + void Compute(const framework::ExecutionContext& context) const override { + auto paddings = + context.op_.GetAttr>>("paddings"); + T pad_value = context.op_.GetAttr("pad_value"); + + auto* X = context.Input("X"); + auto* Out = context.Output("Out"); + Out->mutable_data(context.GetPlace()); + auto dims = X->dims(); + + // Eigen::TensorMap> X_tensor = EigenTensor::From(*X); + // Eigen::TensorMap> + // Out_tensor = EigenTensor::From(*Out); + EigenTensor::ConstType X_tensor = + EigenTensor::From(*X); + EigenTensor::Type Out_tensor = + EigenTensor::From(*Out); + Out_tensor = X_tensor.pad(paddings, pad_value); + } +}; + +template +class PadGradKernel : public framework::OpKernel { + public: + void Compute(const framework::ExecutionContext& ctx) const override { + std::vector> paddings = + context.op_.GetAttr>>("paddings"); + for (int i = 0; i < paddings.size(); ++i) { + paddings[0].first = -paddings[0].first; + paddings[1].second = -paddings[1].second; + } + auto* dOut = ctx.Input(framework::GradVarName("Out")); + auto dims = dOut->dims(); + + auto* dX = ctx.Output(framework::GradVarName("X")); + dX->mutable_data(ctx.GetPlace()); + + EigenTensor::Type dX_tensor = + EigenTensor::From(*dX); + EigenTensor::ConstType dOut_tensor = + EigenTensor::From(*dOut); + dX_tensor = dOut_tensor.pad(paddings, 0); + } +}; + +} // namespace operators +} // namespace paddle diff --git a/paddle/pybind/CMakeLists.txt b/paddle/pybind/CMakeLists.txt index abb9c248ee..17ef1e8291 100644 --- a/paddle/pybind/CMakeLists.txt +++ b/paddle/pybind/CMakeLists.txt @@ -17,5 +17,6 @@ cc_library(paddle_pybind SHARED fill_zeros_like_op lookup_table_op scale_op - minus_op) + minus_op + pad_op) endif(WITH_PYTHON) diff --git a/paddle/pybind/pybind.cc b/paddle/pybind/pybind.cc index 8fa8be2cef..0176eb7a88 100644 --- a/paddle/pybind/pybind.cc +++ b/paddle/pybind/pybind.cc @@ -47,6 +47,7 @@ USE_OP(scale); USE_OP_ITSELF(identity); USE_OP(minus); USE_CPU_ONLY_OP(gather); +USE_OP(pad); namespace paddle { namespace framework { diff --git a/python/paddle/v2/framework/tests/test_pad_op.py b/python/paddle/v2/framework/tests/test_pad_op.py new file mode 100644 index 0000000000..89ac7e7e1d --- /dev/null +++ b/python/paddle/v2/framework/tests/test_pad_op.py @@ -0,0 +1,32 @@ +import unittest +import numpy as np +from gradient_checker import GradientChecker, create_op +from op_test_util import OpTestMeta + + +class TestPadOp(unittest.TestCase): + __metaclass__ = OpTestMeta + + def setUp(self): + self.type = "pad" + self.inputs = {'X': np.random.random((16, 16)).astype("float32"), } + self.attrs['paddings'] = ((0, 1), (2, 3)) + self.attrs['pad_value'] = 0 + self.outputs = { + 'Out': np.pad(self.inputs['X'], + self.attrs['paddings'], + mode='constant', + constant_value=0) + } + + +class PadGradOpTest(GradientChecker): + def test_pad(self): + op = Operator("pad", paddings=((0, 1), (2, 3)), pad_value=0) + inputs = {'X': np.random.random((16, 16)).astype("float32"), } + + self.check_grad(op, inputs, set(["X"]), "Out", max_relative_error=0.5) + + +if __name__ == '__main__': + unittest.main() From a4df3f5bd8917b2cb510b23dc63bc97a20108f23 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 30 Aug 2017 22:21:53 +0800 Subject: [PATCH 022/295] Finish framework of squared_l2_distance_op. --- paddle/operators/CMakeLists.txt | 2 + paddle/operators/squared_l2_distance_op.cc | 82 ++++++++++++++++++ paddle/operators/squared_l2_distance_op.cu | 25 ++++++ paddle/operators/squared_l2_distance_op.h | 84 +++++++++++++++++++ paddle/pybind/CMakeLists.txt | 3 +- paddle/pybind/pybind.cc | 1 + .../paddle/v2/framework/tests/CMakeLists.txt | 1 + .../paddle/v2/framework/tests/op_test_util.py | 10 +-- .../tests/test_squared_l2_distance_op.py | 25 ++++++ 9 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 paddle/operators/squared_l2_distance_op.cc create mode 100644 paddle/operators/squared_l2_distance_op.cu create mode 100644 paddle/operators/squared_l2_distance_op.h create mode 100644 python/paddle/v2/framework/tests/test_squared_l2_distance_op.py diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index f0fd12f1b5..1c32d1df4a 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -73,3 +73,5 @@ op_library(uniform_random_op SRCS uniform_random_op.cc uniform_random_op.cu) op_library(lookup_table_op SRCS lookup_table_op.cc lookup_table_op.cu) op_library(scale_op SRCS scale_op.cc scale_op.cu DEPS net_op) op_library(minus_op SRCS minus_op.cc minus_op.cu DEPS scale_op) + +op_library(squared_l2_distance_op SRCS squared_l2_distance_op.cc squared_l2_distance_op.cu) diff --git a/paddle/operators/squared_l2_distance_op.cc b/paddle/operators/squared_l2_distance_op.cc new file mode 100644 index 0000000000..9fc498d5a5 --- /dev/null +++ b/paddle/operators/squared_l2_distance_op.cc @@ -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 "paddle/operators/squared_l2_distance_op.h" + +namespace paddle { +namespace operators { + +class SquaredL2DistanceOp : public framework::OperatorWithKernel { + public: + using framework::OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(const framework::InferShapeContext &ctx) const override { + PADDLE_ENFORCE_NOT_NULL(ctx.InputVar("X"), + "Input of SquaredL2DistanceOp " + "must be initialized."); + PADDLE_ENFORCE_EQ(ctx.Input("X")->dims(), + ctx.Input("Y")->dims(), + "Dimensions of SquaredL2DistanceOp's two inputs " + "must be same.") + framework::DDim dims = ctx.Input("X")->dims(); + ctx.Output("sub_result")->Resize(dims); + ctx.Output("Out")->Resize(framework::make_ddim({dims[0], 1})); + } +}; + +class SquaredL2DistanceOpMaker : public framework::OpProtoAndCheckerMaker { + public: + SquaredL2DistanceOpMaker(framework::OpProto *proto, + framework::OpAttrChecker *op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("X", "Input value."); + AddInput("Y", "Target value."); + AddOutput("sub_result", + "Buffering substraction result which " + "will be reused in backward.") + .AsIntermediate(); + AddOutput("Out", "Squared l2 distance between input and target."); + AddComment(R"DOC( + SquaredL2DistanceOp will cacluate the squared L2 distances for + input and target. Number of distance value equals to the + first dimension of input. + )DOC"); + } +}; + +class SquaredL2DistanceGradOp : public framework::OperatorWithKernel { + public: + using framework::OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(const framework::InferShapeContext &ctx) const override { + ctx.Output(framework::GradVarName("X")) + ->Resize(ctx.Input("X")->dims()); + } +}; + +} // namespace operators +} // namespace paddle + +namespace ops = paddle::operators; +REGISTER_OP(squared_l2_distance, ops::SquaredL2DistanceOp, + ops::SquaredL2DistanceOpMaker, squared_l2_distance_grad, + ops::SquaredL2DistanceGradOp); +REGISTER_OP_CPU_KERNEL( + squared_l2_distance, + ops::SquaredL2DistanceKernel); +REGISTER_OP_CPU_KERNEL( + squared_l2_distance_grad, + ops::SquaredL2DistanceGradKernel); diff --git a/paddle/operators/squared_l2_distance_op.cu b/paddle/operators/squared_l2_distance_op.cu new file mode 100644 index 0000000000..3fe62f1a9c --- /dev/null +++ b/paddle/operators/squared_l2_distance_op.cu @@ -0,0 +1,25 @@ +/* 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. */ + +#define EIGEN_USE_GPU + +#include "paddle/operators/squared_l2_distance_op.h" + +namespace ops = paddle::operators; +REGISTER_OP_GPU_KERNEL( + squared_l2_distance, + ops::SquaredL2DistanceKernel); +REGISTER_OP_GPU_KERNEL( + squared_l2_distance_grad, + ops::SquaredL2DistanceGradKernel); diff --git a/paddle/operators/squared_l2_distance_op.h b/paddle/operators/squared_l2_distance_op.h new file mode 100644 index 0000000000..b350fd0117 --- /dev/null +++ b/paddle/operators/squared_l2_distance_op.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 "paddle/framework/eigen.h" +#include "paddle/framework/op_registry.h" + +namespace paddle { +namespace operators { + +using Tensor = framework::Tensor; +template +using EigenMatrix = framework::EigenMatrix; +template +using EigenVector = framework::EigenVector; + +template +class SquaredL2DistanceKernel : public framework::OpKernel { + public: + void Compute(const framework::ExecutionContext& context) const override { + auto* input0 = context.Input("X"); + auto* input1 = context.Input("Y"); + auto* output0 = context.Output("sub_result"); + auto* output1 = context.Output("Out"); + + output0->mutable_data(context.GetPlace()); + output1->mutable_data(context.GetPlace()); + + auto X = EigenMatrix::From(*input0); + auto Y = EigenMatrix::From(*input1); + auto subResult = EigenMatrix::From(*output0); + auto Z = EigenMatrix::From(*output1); + + auto place = context.GetEigenDevice(); + // buffer the substraction result + subResult.device(place) = X - Y; + const auto& inDims = X.dimensions(); + const auto& subResMat = subResult.reshape(Eigen::array( + {static_cast(inDims[0]), static_cast(X.size() / inDims[0])})); + Z.device(place) = subResMat.pow(2).sum(Eigen::array({1})); + } +}; + +template +class SquaredL2DistanceGradKernel : public framework::OpKernel { + public: + void Compute(const framework::ExecutionContext& context) const override { + auto* input0 = context.Input("sub_result"); + auto* OG = context.Input(framework::GradVarName("Out")); + auto* IG = context.Output(framework::GradVarName("X")); + + IG->mutable_data(context.GetPlace()); + + auto subResult = EigenMatrix::From(*input0); + auto outGrad = EigenMatrix::From(*OG); + auto inGrad = EigenMatrix::From(*IG); + + const auto& subResDims = subResult.dimensions(); + int firstDim = static_cast(subResDims[0]); + int cols = subResult.size() / firstDim; + const auto subResMat = + subResult.reshape(Eigen::array({firstDim, cols})); + // create a matrix view for input gradient tensor + auto inGradMat = inGrad.reshape(Eigen::array({firstDim, cols})); + inGradMat.device(context.GetEigenDevice()) = + 2 * (outGrad.broadcast(Eigen::array({1, cols}))) * subResMat; + } +}; + +} // namespace operators +} // namespace paddle diff --git a/paddle/pybind/CMakeLists.txt b/paddle/pybind/CMakeLists.txt index 37e186a408..df8c2b37cf 100644 --- a/paddle/pybind/CMakeLists.txt +++ b/paddle/pybind/CMakeLists.txt @@ -18,5 +18,6 @@ cc_library(paddle_pybind SHARED fill_zeros_like_op lookup_table_op scale_op - minus_op) + minus_op + squared_l2_distance_op) endif(WITH_PYTHON) diff --git a/paddle/pybind/pybind.cc b/paddle/pybind/pybind.cc index 3bc150ccb7..69a5f98a43 100644 --- a/paddle/pybind/pybind.cc +++ b/paddle/pybind/pybind.cc @@ -48,6 +48,7 @@ USE_OP_ITSELF(identity); USE_OP(minus); USE_CPU_ONLY_OP(gather); USE_CPU_ONLY_OP(scatter); +USE_OP(squared_l2_distance); namespace paddle { namespace framework { diff --git a/python/paddle/v2/framework/tests/CMakeLists.txt b/python/paddle/v2/framework/tests/CMakeLists.txt index 661ebd8964..06ff1f4a0c 100644 --- a/python/paddle/v2/framework/tests/CMakeLists.txt +++ b/python/paddle/v2/framework/tests/CMakeLists.txt @@ -32,3 +32,4 @@ py_test(test_gradient_checker SRCS test_gradient_checker.py) py_test(test_lookup_table SRCS test_lookup_table.py) py_test(test_scale_and_identity_op SRCS test_scale_and_identity_op.py) py_test(mnist SRCS mnist.py) +py_test(test_squared_l2_distance_op SRCS test_squared_l2_distance_op.py) diff --git a/python/paddle/v2/framework/tests/op_test_util.py b/python/paddle/v2/framework/tests/op_test_util.py index 3bc05a0fec..370f27eaf6 100644 --- a/python/paddle/v2/framework/tests/op_test_util.py +++ b/python/paddle/v2/framework/tests/op_test_util.py @@ -6,13 +6,13 @@ from paddle.v2.framework.op import Operator class OpTestMeta(type): """ Operator Test ClassMeta. - - It injects `test_all` method into user's OperatorTest class, to make Python + + It injects `test_all` method into user's OperatorTest class, to make Python unittest module run that method. - + The `test_all` read what value is stored in `self`. It use self's values to create and run a operator, and check whether that op is OK or not. - + See `test_add_two_op` for example usage. """ @@ -66,7 +66,7 @@ class OpTestMeta(type): self.assertTrue( numpy.allclose( actual, expect, atol=1e-05), - "output name: " + out_name + "has diff") + "output name: " + out_name + " has diff") obj.test_all = test_all return obj diff --git a/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py b/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py new file mode 100644 index 0000000000..eeddb5a3bf --- /dev/null +++ b/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py @@ -0,0 +1,25 @@ +import unittest +from op_test_util import OpTestMeta +from gradient_checker import GradientChecker, create_op +import numpy as np + + +class TestSquaredL2DistanceOp(unittest.TestCase): + __metaclass__ = OpTestMeta + + def setUp(self): + self.type = 'squared_l2_distance' + self.inputs = { + 'X': np.random.uniform(0.1, 1., (2, 3)).astype('float32'), + 'Y': np.random.uniform(0.1, 1., (2, 3)).astype('float32') + } + subRes = self.inputs['X'] - self.inputs['Y'] + output = subRes * subRes + self.outputs = { + 'sub_result': subRes, + 'Out': np.expand_dims(output.sum(1), 1) + } + + +if __name__ == '__main__': + unittest.main() From 3b0e43aa6ab4f30ca960537b13f600cc36d6066e Mon Sep 17 00:00:00 2001 From: chengduoZH Date: Wed, 30 Aug 2017 23:09:58 +0800 Subject: [PATCH 023/295] add config parse --- proto/ModelConfig.proto | 2 + python/paddle/trainer/config_parser.py | 90 +++++++++++++++++-- .../paddle/trainer_config_helpers/layers.py | 15 +++- .../tests/configs/test_BatchNorm3D.py | 17 ++++ .../tests/layers_test.py | 2 +- 5 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 95c236ad88..0525fb9dc3 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -515,6 +515,8 @@ message LayerConfig { // for HuberRegressionLoss optional double delta = 57 [ default = 1.0 ]; + // for 3D data + optional double depth = 58 [ default = 1 ]; } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index c11037c3c8..bc9aacaf11 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1172,6 +1172,20 @@ def get_img_size(input_layer_name, channels): return img_size, img_size_y +def get_img3d_size(input_layer_name, channels): + input = g_layer_map[input_layer_name] + img_pixels = input.size / channels + img_size = input.width + img_size_y = input.height + img_size_z = input.depth + + config_assert( + img_size * img_size_y * img_size_z == img_pixels, + "Input layer %s: Incorrect input image size %d * %d * %d for input image pixels %d" + % (input_layer_name, img_size, img_size_y, img_size_z, img_pixels)) + return img_size, img_size_y, img_size_z + + def parse_bilinear(bilinear, input_layer_name, bilinear_conf): parse_image(bilinear, input_layer_name, bilinear_conf.image_conf) bilinear_conf.out_size_x = bilinear.out_size_x @@ -1224,6 +1238,12 @@ def parse_image(image, input_layer_name, image_conf): get_img_size(input_layer_name, image_conf.channels) +def parse_image3d(image, input_layer_name, image_conf): + image_conf.channels = image.channels + image_conf.img_size, image_conf.img_size_y, image_conf.img_size_z = \ + get_img3d_size(input_layer_name, image_conf.channels) + + def parse_norm(norm, input_layer_name, norm_conf): norm_conf.norm_type = norm.norm_type config_assert( @@ -1585,6 +1605,9 @@ class LayerBase(object): self.config.height = height self.config.width = width + def set_layer_depth(self, depth): + self.config.depth = depth + def set_cnn_layer(self, input_layer_name, height, @@ -1788,11 +1811,19 @@ class DetectionOutputLayer(LayerBase): @config_layer('data') class DataLayer(LayerBase): - def __init__(self, name, size, height=None, width=None, device=None): + def __init__(self, + name, + size, + depth=None, + height=None, + width=None, + device=None): super(DataLayer, self).__init__( name, 'data', size, inputs=[], device=device) if height and width: self.set_layer_height_width(height, width) + if depth: + self.set_layer_depth(depth) ''' @@ -2077,6 +2108,7 @@ class BatchNormLayer(LayerBase): name, inputs, bias=True, + img3D=False, use_global_stats=True, moving_average_fraction=0.9, batch_norm_type=None, @@ -2121,15 +2153,33 @@ class BatchNormLayer(LayerBase): input_layer = self.get_input_layer(0) image_conf = self.config.inputs[0].image_conf - parse_image(self.inputs[0].image, input_layer.name, image_conf) - - # Only pass the width and height of input to batch_norm layer - # when either of it is non-zero. - if input_layer.width != 0 or input_layer.height != 0: - self.set_cnn_layer(name, image_conf.img_size_y, image_conf.img_size, - image_conf.channels, False) + if img3D: + parse_image3d(self.inputs[0].image, input_layer.name, image_conf) + # Only pass the width and height of input to batch_norm layer + # when either of it is non-zero. + if input_layer.width != 0 or input_layer.height != 0: + self.set_cnn_layer( + input_layer_name=name, + depth=image_conf.img_size_z, + height=image_conf.img_size_y, + width=image_conf.img_size, + channels=image_conf.channels, + is_print=True) + else: + self.set_layer_size(input_layer.size) else: - self.set_layer_size(input_layer.size) + parse_image(self.inputs[0].image, input_layer.name, image_conf) + # Only pass the width and height of input to batch_norm layer + # when either of it is non-zero. + if input_layer.width != 0 or input_layer.height != 0: + self.set_cnn_layer( + input_layer_name=name, + height=image_conf.img_size_y, + width=image_conf.img_size, + channels=image_conf.channels, + is_print=True) + else: + self.set_layer_size(input_layer.size) psize = self.calc_parameter_size(image_conf) dims = [1, psize] @@ -2139,6 +2189,28 @@ class BatchNormLayer(LayerBase): self.create_bias_parameter(bias, psize) + def set_cnn_layer(self, + input_layer_name, + depth=None, + height=None, + width=None, + channels=None, + is_print=True): + depthIsNone = False + if depth is None: + depth = 1 + depthIsNone = True + size = depth * height * width * channels + self.set_layer_size(size) + self.set_layer_height_width(height, width) + self.set_layer_depth(depth) + if is_print and depthIsNone: + print("output for %s: c = %d, h = %d, w = %d, size = %d" % + (input_layer_name, channels, height, width, size)) + elif is_print: + print("output for %s: c = %d, d = %d, h = %d, w = %d, size = %d" % + (input_layer_name, channels, depth, height, width, size)) + def calc_parameter_size(self, image_conf): return image_conf.channels diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index a525ce71d0..35c84ad597 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -166,6 +166,7 @@ class LayerType(object): EXCONVTRANS_LAYER = 'exconvt' CUDNNCONV_LAYER = 'cudnn_conv' POOL_LAYER = 'pool' + POOL3D_LAYER = 'pool3d' BATCH_NORM_LAYER = 'batch_norm' NORM_LAYER = 'norm' SUM_TO_ONE_NORM_LAYER = 'sum_to_one_norm' @@ -894,7 +895,8 @@ def mixed_layer(size=0, @layer_support() -def data_layer(name, size, height=None, width=None, layer_attr=None): +def data_layer(name, size, depth=None, height=None, width=None, + layer_attr=None): """ Define DataLayer For NeuralNetwork. @@ -921,15 +923,18 @@ def data_layer(name, size, height=None, width=None, layer_attr=None): type=LayerType.DATA, name=name, size=size, + depth=depth, height=height, width=width, **ExtraLayerAttribute.to_kwargs(layer_attr)) + if depth is None: + depth = 1 num_filters = None if height is not None and width is not None: - num_filters = size / (width * height) - assert num_filters * width * height == size, \ - "size=%s width=%s height=%s" % (size, width, height) + num_filters = size / (width * height * depth) + assert num_filters * width * height * depth == size, \ + "size=%s width=%s height=%s depth=%s" % (size, width, height, depth) return LayerOutput(name, LayerType.DATA, size=size, num_filters=num_filters) @@ -2799,6 +2804,7 @@ def img_cmrnorm_layer(input, def batch_norm_layer(input, act=None, name=None, + img3D=False, num_channels=None, bias_attr=None, param_attr=None, @@ -2885,6 +2891,7 @@ def batch_norm_layer(input, (batch_norm_type == "cudnn_batch_norm") l = Layer( name=name, + img3D=img3D, inputs=Input( input.name, image=Image(channels=num_channels), **param_attr.attr), active_type=act.name, diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py b/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py new file mode 100644 index 0000000000..af694382b6 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py @@ -0,0 +1,17 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-4) + +data = data_layer(name='data', size=180, width=30, height=6) +# +batchNorm = batch_norm_layer(data, num_channels=1) +# +outputs(batchNorm) + +# # +data3D = data_layer(name='data3D22', size=120 * 3, width=20, height=6, depth=3) +# +print(data3D) +batchNorm3D = batch_norm_layer(data3D, num_channels=1, img3D=True) +# +outputs(batchNorm3D) diff --git a/python/paddle/trainer_config_helpers/tests/layers_test.py b/python/paddle/trainer_config_helpers/tests/layers_test.py index 05902ea293..68c8e128cb 100644 --- a/python/paddle/trainer_config_helpers/tests/layers_test.py +++ b/python/paddle/trainer_config_helpers/tests/layers_test.py @@ -16,4 +16,4 @@ from paddle.trainer.config_parser import parse_config_and_serialize if __name__ == '__main__': parse_config_and_serialize( - 'trainer_config_helpers/tests/layers_test_config.py', '') + 'trainer_config_helpers/tests/configs/test_BatchNorm3D.py', '') From 30c0df6d27198867f8c9ef0c098505eeaded1522 Mon Sep 17 00:00:00 2001 From: chengduoZH Date: Thu, 31 Aug 2017 13:49:42 +0800 Subject: [PATCH 024/295] fix layers_test.py --- python/paddle/trainer_config_helpers/tests/configs/file_list.sh | 2 +- python/paddle/trainer_config_helpers/tests/layers_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 1ca5c8a07e..8462f2d710 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -9,6 +9,6 @@ test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer test_prelu_layer test_row_conv test_detection_output_layer test_multibox_loss_layer test_recursive_topology test_gated_unit_layer test_clip_layer test_row_l2_norm_layer test_kmax_seq_socre_layer test_seq_select_layers test_scale_shift_layer -test_seq_slice_layer) +test_seq_slice_layer test_BatchNorm3D) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/layers_test.py b/python/paddle/trainer_config_helpers/tests/layers_test.py index 68c8e128cb..05902ea293 100644 --- a/python/paddle/trainer_config_helpers/tests/layers_test.py +++ b/python/paddle/trainer_config_helpers/tests/layers_test.py @@ -16,4 +16,4 @@ from paddle.trainer.config_parser import parse_config_and_serialize if __name__ == '__main__': parse_config_and_serialize( - 'trainer_config_helpers/tests/configs/test_BatchNorm3D.py', '') + 'trainer_config_helpers/tests/layers_test_config.py', '') From 3ad748e225f7c5537338b1a5786924b93363c12d Mon Sep 17 00:00:00 2001 From: chengduoZH Date: Thu, 31 Aug 2017 16:23:12 +0800 Subject: [PATCH 025/295] Fix img_layers.protostr,img_trans_layers.protostr. Add test_BatchNorm3D.protostr --- .../configs/protostr/img_layers.protostr | 1 + .../protostr/img_trans_layers.protostr | 1 + .../protostr/test_BatchNorm3D.protostr | 92 +++++++++++++++++++ .../tests/configs/test_BatchNorm3D.py | 14 +-- 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_BatchNorm3D.protostr diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/img_layers.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/img_layers.protostr index 1a577b8d9b..5ddf6052df 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/img_layers.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/img_layers.protostr @@ -62,6 +62,7 @@ layers { moving_average_fraction: 0.9 height: 227 width: 227 + depth: 1 } layers { name: "__crmnorm_0__" diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/img_trans_layers.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/img_trans_layers.protostr index 2818389b16..c0252b945b 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/img_trans_layers.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/img_trans_layers.protostr @@ -62,6 +62,7 @@ layers { moving_average_fraction: 0.9 height: 256 width: 256 + depth: 1 } layers { name: "__crmnorm_0__" diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_BatchNorm3D.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_BatchNorm3D.protostr new file mode 100644 index 0000000000..832ed24a31 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_BatchNorm3D.protostr @@ -0,0 +1,92 @@ +type: "nn" +layers { + name: "data3D" + type: "data" + size: 360 + active_type: "" + height: 6 + width: 20 + depth: 3 +} +layers { + name: "__batch_norm_0__" + type: "batch_norm" + size: 360 + active_type: "relu" + inputs { + input_layer_name: "data3D" + input_parameter_name: "___batch_norm_0__.w0" + image_conf { + channels: 1 + img_size: 20 + img_size_y: 6 + img_size_z: 3 + } + } + inputs { + input_layer_name: "data3D" + input_parameter_name: "___batch_norm_0__.w1" + } + inputs { + input_layer_name: "data3D" + input_parameter_name: "___batch_norm_0__.w2" + } + bias_parameter_name: "___batch_norm_0__.wbias" + moving_average_fraction: 0.9 + height: 6 + width: 20 + depth: 3 +} +parameters { + name: "___batch_norm_0__.w0" + size: 1 + initial_mean: 1.0 + initial_std: 0.0 + initial_strategy: 0 + initial_smart: false +} +parameters { + name: "___batch_norm_0__.w1" + size: 1 + initial_mean: 0.0 + initial_std: 0.0 + dims: 1 + dims: 1 + initial_strategy: 0 + initial_smart: false + is_static: true + is_shared: true +} +parameters { + name: "___batch_norm_0__.w2" + size: 1 + initial_mean: 0.0 + initial_std: 0.0 + dims: 1 + dims: 1 + initial_strategy: 0 + initial_smart: false + is_static: true + is_shared: true +} +parameters { + name: "___batch_norm_0__.wbias" + size: 1 + initial_mean: 0.0 + initial_std: 0.0 + dims: 1 + dims: 1 + initial_strategy: 0 + initial_smart: false +} +input_layer_names: "data3D" +output_layer_names: "__batch_norm_0__" +sub_models { + name: "root" + layer_names: "data3D" + layer_names: "__batch_norm_0__" + input_layer_names: "data3D" + output_layer_names: "__batch_norm_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py b/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py index af694382b6..a991b22252 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_BatchNorm3D.py @@ -2,16 +2,10 @@ from paddle.trainer_config_helpers import * settings(batch_size=1000, learning_rate=1e-4) -data = data_layer(name='data', size=180, width=30, height=6) -# -batchNorm = batch_norm_layer(data, num_channels=1) -# -outputs(batchNorm) +#data = data_layer(name='data', size=180, width=30, height=6) +#batchNorm = batch_norm_layer(data, num_channels=1) +#outputs(batchNorm) -# # -data3D = data_layer(name='data3D22', size=120 * 3, width=20, height=6, depth=3) -# -print(data3D) +data3D = data_layer(name='data3D', size=120 * 3, width=20, height=6, depth=3) batchNorm3D = batch_norm_layer(data3D, num_channels=1, img3D=True) -# outputs(batchNorm3D) From f7e75a03cf03d8b71ab9be2800c7ed8058866c02 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 31 Aug 2017 19:57:22 +0800 Subject: [PATCH 026/295] Refine the neon depthwise convolution code(separate the Function and kernel). --- paddle/function/neon/NeonDepthwiseConv.cpp | 454 +------------------ paddle/function/neon/NeonDepthwiseConv.h | 480 +++++++++++++++++++++ 2 files changed, 481 insertions(+), 453 deletions(-) create mode 100644 paddle/function/neon/NeonDepthwiseConv.h diff --git a/paddle/function/neon/NeonDepthwiseConv.cpp b/paddle/function/neon/NeonDepthwiseConv.cpp index f09e98587d..7e5f752a0b 100644 --- a/paddle/function/neon/NeonDepthwiseConv.cpp +++ b/paddle/function/neon/NeonDepthwiseConv.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 "neon_util.h" +#include "NeonDepthwiseConv.h" #include "paddle/function/ConvOp.h" #include "paddle/function/Im2Col.h" @@ -22,458 +22,6 @@ namespace neon { #if defined(__ARM_NEON__) || defined(__ARM_NEON) -template -struct DepthwiseConvKernel {}; - -inline float32_t conv3x3(float32x4_t r0, - float32x4_t r1, - float32x4_t r2, - float32x4_t k0, - float32x4_t k1, - float32x4_t k2) { - float32x4_t tmp; - tmp = vmulq_f32(r0, k0); - tmp = vmlaq_f32(tmp, r1, k1); - tmp = vmlaq_f32(tmp, r2, k2); - return vaddvq_f32(tmp); -} - -inline float32_t conv4x4(float32x4_t r0, - float32x4_t r1, - float32x4_t r2, - float32x4_t r3, - float32x4_t k0, - float32x4_t k1, - float32x4_t k2, - float32x4_t k3) { - float32x4_t tmp; - tmp = vmulq_f32(r0, k0); - tmp = vmlaq_f32(tmp, r1, k1); - tmp = vmlaq_f32(tmp, r2, k2); - tmp = vmlaq_f32(tmp, r3, k3); - return vaddvq_f32(tmp); -} - -/** - * Each step calculates four elements of the output. - * First step: - * R0[0, 1, 2, 3...] * K[0][0] - * R0[1, 2, 3, 4...] * K[0][1] - * R0[2, 3, 4, 5...] * K[0][2] - * R1[0, 1, 2, 3...] * K[1][0] - * R1[1, 2, 3, 4...] * K[1][1] - * R1[2, 3, 4, 5...] * K[1][2] - * R2[0, 1, 2, 3...] * K[2][0] - * R2[1, 2, 3, 4...] * K[2][1] - * + R2[2, 3, 4, 5...] * K[2][2] - * ------------------------------ - * Output[0, 1, 2, 3] - */ -template <> -struct DepthwiseConvKernel<3, 1> { - static void run(const float* inputData, - const float* filterData, - int inputHeight, - int inputWidth, - int outputChannels, - int outputHeight, - int outputWidth, - int filterMultiplier, - float* outputData) { - const int steps = outputWidth >> 2; - const int remain = outputWidth & 3; - for (int c = 0; c < outputChannels; c++, filterData += 9) { - // Load the filters - float32x4_t k[3]; - k[0] = vld1q_f32(filterData); - k[1] = vld1q_f32(filterData + 3); - k[2] = vld1q_f32(filterData + 6); - k[0] = vsetq_lane_f32(0.f, k[0], 3); - k[1] = vsetq_lane_f32(0.f, k[1], 3); - k[2] = vsetq_lane_f32(0.f, k[2], 3); - - const float* r0 = - inputData + (c / filterMultiplier) * (inputHeight * inputWidth); - const float* r1 = r0 + inputWidth; - const float* r2 = r0 + inputWidth * 2; - float32x4_t input[3][3]; - for (int h = 0; h < outputHeight; h++) { - for (int s = 0; s < steps; s++) { - // Load the inputs - float32x4_t tmp; - input[0][0] = vld1q_f32(r0); - tmp = vld1q_f32(r0 + 4); - input[0][1] = vextq_f32(input[0][0], tmp, 1); - input[0][2] = vextq_f32(input[0][0], tmp, 2); - input[1][0] = vld1q_f32(r1); - tmp = vld1q_f32(r1 + 4); - input[1][1] = vextq_f32(input[1][0], tmp, 1); - input[1][2] = vextq_f32(input[1][0], tmp, 2); - input[2][0] = vld1q_f32(r2); - tmp = vld1q_f32(r2 + 4); - input[2][1] = vextq_f32(input[2][0], tmp, 1); - input[2][2] = vextq_f32(input[2][0], tmp, 2); - - float32x4_t tmp1 = vdupq_n_f32(0.f); - float32x4_t tmp2 = vdupq_n_f32(0.f); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][0], k[1], 0); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][1], k[1], 1); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][2], k[1], 2); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); - tmp1 = vaddq_f32(tmp1, tmp2); - - vst1q_f32(outputData, tmp1); - r0 += 4; - r1 += 4; - r2 += 4; - outputData += 4; - } - - for (int r = 0; r < remain; r++) { - float32x4_t i0 = vld1q_f32(r0); - float32x4_t i1 = vld1q_f32(r1); - float32x4_t i2 = vld1q_f32(r2); - *outputData = conv3x3(i0, i1, i2, k[0], k[1], k[2]); - r0++; - r1++; - r2++; - outputData++; - } - - r0 += 2; - r1 += 2; - r2 += 2; - } - } - } -}; - -/** - * Each step calculates four elements of the output. - * First step: - * R0[0, 2, 4, 6...] * K[0][0] - * R0[1, 3, 5, 7...] * K[0][1] - * R0[2, 4, 6, 8...] * K[0][2] - * R1[0, 2, 4, 6...] * K[1][0] - * R1[1, 3, 5, 7...] * K[1][1] - * R1[2, 4, 6, 8...] * K[1][2] - * R2[0, 2, 4, 6...] * K[2][0] - * R2[1, 3, 5, 7...] * K[2][1] - * R2[2, 4, 6, 8...] * K[2][2] - * ------------------------------ - * Output[0, 1, 2, 3] - */ -template <> -struct DepthwiseConvKernel<3, 2> { - static void run(const float* inputData, - const float* filterData, - int inputHeight, - int inputWidth, - int outputChannels, - int outputHeight, - int outputWidth, - int filterMultiplier, - float* outputData) { - const int steps = outputWidth >> 2; - const int remain = outputWidth & 3; - for (int c = 0; c < outputChannels; c++, filterData += 9) { - // Load the filters - float32x4_t k[3]; - k[0] = vld1q_f32(filterData); - k[1] = vld1q_f32(filterData + 3); - k[2] = vld1q_f32(filterData + 6); - k[0] = vsetq_lane_f32(0.f, k[0], 3); - k[1] = vsetq_lane_f32(0.f, k[1], 3); - k[2] = vsetq_lane_f32(0.f, k[2], 3); - - const float* start = - inputData + (c / filterMultiplier) * (inputHeight * inputWidth); - float32x4_t input[3][3]; - for (int h = 0; h < outputHeight; h++) { - const float* r0 = start + 2 * h * inputWidth; - const float* r1 = start + (2 * h + 1) * inputWidth; - const float* r2 = start + (2 * h + 2) * inputWidth; - for (int s = 0; s < steps; s++) { - // Load the inputs - float32x4_t data1; - float32x4x2_t data2; - - data2 = vld2q_f32(r0); - input[0][0] = data2.val[0]; - input[0][1] = data2.val[1]; - data1 = vld1q_f32(r0 + 8); - input[0][2] = vextq_f32(data2.val[0], data1, 1); - - data2 = vld2q_f32(r1); - input[1][0] = data2.val[0]; - input[1][1] = data2.val[1]; - data1 = vld1q_f32(r1 + 8); - input[1][2] = vextq_f32(data2.val[0], data1, 1); - - data2 = vld2q_f32(r2); - input[2][0] = data2.val[0]; - input[2][1] = data2.val[1]; - data1 = vld1q_f32(r2 + 8); - input[2][2] = vextq_f32(data2.val[0], data1, 1); - - float32x4_t tmp1 = vdupq_n_f32(0.f); - float32x4_t tmp2 = vdupq_n_f32(0.f); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][0], k[1], 0); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][1], k[1], 1); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][2], k[1], 2); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); - tmp1 = vaddq_f32(tmp1, tmp2); - - vst1q_f32(outputData, tmp1); - r0 += 8; - r1 += 8; - r2 += 8; - outputData += 4; - } - - for (int r = 0; r < remain; r++) { - float32x4_t i0 = vld1q_f32(r0); - float32x4_t i1 = vld1q_f32(r1); - float32x4_t i2 = vld1q_f32(r2); - *outputData = conv3x3(i0, i1, i2, k[0], k[1], k[2]); - r0 += 2; - r1 += 2; - r2 += 2; - outputData++; - } - } - } - } -}; - -/** - * Each step calculates four elements of the output. - */ -template <> -struct DepthwiseConvKernel<4, 1> { - static void run(const float* inputData, - const float* filterData, - int inputHeight, - int inputWidth, - int outputChannels, - int outputHeight, - int outputWidth, - int filterMultiplier, - float* outputData) { - const int steps = outputWidth >> 2; - const int remain = outputWidth & 3; - for (int c = 0; c < outputChannels; c++, filterData += 16) { - // Load the filters - float32x4_t k[4]; - k[0] = vld1q_f32(filterData); - k[1] = vld1q_f32(filterData + 4); - k[2] = vld1q_f32(filterData + 8); - k[3] = vld1q_f32(filterData + 12); - - const float* r0 = - inputData + (c / filterMultiplier) * (inputHeight * inputWidth); - const float* r1 = r0 + inputWidth; - const float* r2 = r0 + inputWidth * 2; - const float* r3 = r0 + inputWidth * 3; - float32x4_t input[4][4]; - for (int h = 0; h < outputHeight; h++) { - for (int s = 0; s < steps; s++) { - // Load the inputs - float32x4_t tmp; - input[0][0] = vld1q_f32(r0); - tmp = vld1q_f32(r0 + 4); - input[0][1] = vextq_f32(input[0][0], tmp, 1); - input[0][2] = vextq_f32(input[0][0], tmp, 2); - input[0][3] = vextq_f32(input[0][0], tmp, 3); - - input[1][0] = vld1q_f32(r1); - tmp = vld1q_f32(r1 + 4); - input[1][1] = vextq_f32(input[1][0], tmp, 1); - input[1][2] = vextq_f32(input[1][0], tmp, 2); - input[1][3] = vextq_f32(input[1][0], tmp, 3); - - input[2][0] = vld1q_f32(r2); - tmp = vld1q_f32(r2 + 4); - input[2][1] = vextq_f32(input[2][0], tmp, 1); - input[2][2] = vextq_f32(input[2][0], tmp, 2); - input[2][3] = vextq_f32(input[2][0], tmp, 3); - - input[3][0] = vld1q_f32(r3); - tmp = vld1q_f32(r3 + 4); - input[3][1] = vextq_f32(input[3][0], tmp, 1); - input[3][2] = vextq_f32(input[3][0], tmp, 2); - input[3][3] = vextq_f32(input[3][0], tmp, 3); - - float32x4_t tmp1 = vdupq_n_f32(0.f); - float32x4_t tmp2 = vdupq_n_f32(0.f); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][3], k[0], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][0], k[1], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][1], k[1], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][2], k[1], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][3], k[1], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][3], k[2], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[3][0], k[3], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[3][1], k[3], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[3][2], k[3], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[3][3], k[3], 3); - tmp1 = vaddq_f32(tmp1, tmp2); - - vst1q_f32(outputData, tmp1); - r0 += 4; - r1 += 4; - r2 += 4; - r3 += 4; - outputData += 4; - } - - for (int r = 0; r < remain; r++) { - float32x4_t i0 = vld1q_f32(r0); - float32x4_t i1 = vld1q_f32(r1); - float32x4_t i2 = vld1q_f32(r2); - float32x4_t i3 = vld1q_f32(r3); - *outputData = conv4x4(i0, i1, i2, i3, k[0], k[1], k[2], k[3]); - r0++; - r1++; - r2++; - r3++; - outputData++; - } - - r0 += 3; - r1 += 3; - r2 += 3; - r3 += 3; - } - } - } -}; - -/** - * Each step calculates four elements of the output. - */ -template <> -struct DepthwiseConvKernel<4, 2> { - static void run(const float* inputData, - const float* filterData, - int inputHeight, - int inputWidth, - int outputChannels, - int outputHeight, - int outputWidth, - int filterMultiplier, - float* outputData) { - const int steps = outputWidth >> 2; - const int remain = outputWidth & 3; - for (int c = 0; c < outputChannels; c++, filterData += 16) { - // Load the filters - float32x4_t k[4]; - k[0] = vld1q_f32(filterData); - k[1] = vld1q_f32(filterData + 4); - k[2] = vld1q_f32(filterData + 8); - k[3] = vld1q_f32(filterData + 12); - - const float* start = - inputData + (c / filterMultiplier) * (inputHeight * inputWidth); - float32x4_t input[4][4]; - for (int h = 0; h < outputHeight; h++) { - const float* r0 = start + 2 * h * inputWidth; - const float* r1 = start + (2 * h + 1) * inputWidth; - const float* r2 = start + (2 * h + 2) * inputWidth; - const float* r3 = start + (2 * h + 3) * inputWidth; - for (int s = 0; s < steps; s++) { - // Load the inputs - float32x4x2_t data1; - float32x4x2_t data2; - - data1 = vld2q_f32(r0); - data2 = vld2q_f32(r0 + 8); - input[0][0] = data1.val[0]; - input[0][1] = data1.val[1]; - input[0][2] = vextq_f32(data1.val[0], data2.val[0], 1); - input[0][3] = vextq_f32(data1.val[1], data2.val[1], 1); - - data1 = vld2q_f32(r1); - data2 = vld2q_f32(r1 + 8); - input[1][0] = data1.val[0]; - input[1][1] = data1.val[1]; - input[1][2] = vextq_f32(data1.val[0], data2.val[0], 1); - input[1][3] = vextq_f32(data1.val[1], data2.val[1], 1); - - data1 = vld2q_f32(r2); - data2 = vld2q_f32(r2 + 8); - input[2][0] = data1.val[0]; - input[2][1] = data1.val[1]; - input[2][2] = vextq_f32(data1.val[0], data2.val[0], 1); - input[2][3] = vextq_f32(data1.val[1], data2.val[1], 1); - - data1 = vld2q_f32(r3); - data2 = vld2q_f32(r3 + 8); - input[3][0] = data1.val[0]; - input[3][1] = data1.val[1]; - input[3][2] = vextq_f32(data1.val[0], data2.val[0], 1); - input[3][3] = vextq_f32(data1.val[1], data2.val[1], 1); - - float32x4_t tmp1 = vdupq_n_f32(0.f); - float32x4_t tmp2 = vdupq_n_f32(0.f); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[0][3], k[0], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][0], k[1], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][1], k[1], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[1][2], k[1], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[1][3], k[1], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[2][3], k[2], 3); - tmp1 = vmlaq_laneq_f32(tmp1, input[3][0], k[3], 0); - tmp2 = vmlaq_laneq_f32(tmp2, input[3][1], k[3], 1); - tmp1 = vmlaq_laneq_f32(tmp1, input[3][2], k[3], 2); - tmp2 = vmlaq_laneq_f32(tmp2, input[3][3], k[3], 3); - tmp1 = vaddq_f32(tmp1, tmp2); - - vst1q_f32(outputData, tmp1); - r0 += 8; - r1 += 8; - r2 += 8; - r3 += 8; - outputData += 4; - } - - for (int r = 0; r < remain; r++) { - float32x4_t i0 = vld1q_f32(r0); - float32x4_t i1 = vld1q_f32(r1); - float32x4_t i2 = vld1q_f32(r2); - float32x4_t i3 = vld1q_f32(r3); - *outputData = conv4x4(i0, i1, i2, i3, k[0], k[1], k[2], k[3]); - r0 += 2; - r1 += 2; - r2 += 2; - r3 += 2; - outputData++; - } - } - } - } -}; - template class NeonDepthwiseConvFunction : public ConvFunctionBase { public: diff --git a/paddle/function/neon/NeonDepthwiseConv.h b/paddle/function/neon/NeonDepthwiseConv.h new file mode 100644 index 0000000000..cb1abe1f32 --- /dev/null +++ b/paddle/function/neon/NeonDepthwiseConv.h @@ -0,0 +1,480 @@ +/* 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 "neon_util.h" + +namespace paddle { + +namespace neon { + +#if defined(__ARM_NEON__) || defined(__ARM_NEON) + +template +struct DepthwiseConvKernel {}; + +inline float32_t conv3x3(float32x4_t r0, + float32x4_t r1, + float32x4_t r2, + float32x4_t k0, + float32x4_t k1, + float32x4_t k2) { + float32x4_t tmp; + tmp = vmulq_f32(r0, k0); + tmp = vmlaq_f32(tmp, r1, k1); + tmp = vmlaq_f32(tmp, r2, k2); + return vaddvq_f32(tmp); +} + +inline float32_t conv4x4(float32x4_t r0, + float32x4_t r1, + float32x4_t r2, + float32x4_t r3, + float32x4_t k0, + float32x4_t k1, + float32x4_t k2, + float32x4_t k3) { + float32x4_t tmp; + tmp = vmulq_f32(r0, k0); + tmp = vmlaq_f32(tmp, r1, k1); + tmp = vmlaq_f32(tmp, r2, k2); + tmp = vmlaq_f32(tmp, r3, k3); + return vaddvq_f32(tmp); +} + +/** + * Each step calculates four elements of the output. + * First step: + * R0[0, 1, 2, 3...] * K[0][0] + * R0[1, 2, 3, 4...] * K[0][1] + * R0[2, 3, 4, 5...] * K[0][2] + * R1[0, 1, 2, 3...] * K[1][0] + * R1[1, 2, 3, 4...] * K[1][1] + * R1[2, 3, 4, 5...] * K[1][2] + * R2[0, 1, 2, 3...] * K[2][0] + * R2[1, 2, 3, 4...] * K[2][1] + * + R2[2, 3, 4, 5...] * K[2][2] + * ------------------------------ + * Output[0, 1, 2, 3] + */ +template <> +struct DepthwiseConvKernel<3, 1> { + static void run(const float* inputData, + const float* filterData, + int inputHeight, + int inputWidth, + int outputChannels, + int outputHeight, + int outputWidth, + int filterMultiplier, + float* outputData) { + const int steps = outputWidth >> 2; + const int remain = outputWidth & 3; + for (int c = 0; c < outputChannels; c++, filterData += 9) { + // Load the filters + float32x4_t k[3]; + k[0] = vld1q_f32(filterData); + k[1] = vld1q_f32(filterData + 3); + k[2] = vld1q_f32(filterData + 6); + k[0] = vsetq_lane_f32(0.f, k[0], 3); + k[1] = vsetq_lane_f32(0.f, k[1], 3); + k[2] = vsetq_lane_f32(0.f, k[2], 3); + + const float* r0 = + inputData + (c / filterMultiplier) * (inputHeight * inputWidth); + const float* r1 = r0 + inputWidth; + const float* r2 = r0 + inputWidth * 2; + float32x4_t input[3][3]; + for (int h = 0; h < outputHeight; h++) { + for (int s = 0; s < steps; s++) { + // Load the inputs + float32x4_t tmp; + input[0][0] = vld1q_f32(r0); + tmp = vld1q_f32(r0 + 4); + input[0][1] = vextq_f32(input[0][0], tmp, 1); + input[0][2] = vextq_f32(input[0][0], tmp, 2); + input[1][0] = vld1q_f32(r1); + tmp = vld1q_f32(r1 + 4); + input[1][1] = vextq_f32(input[1][0], tmp, 1); + input[1][2] = vextq_f32(input[1][0], tmp, 2); + input[2][0] = vld1q_f32(r2); + tmp = vld1q_f32(r2 + 4); + input[2][1] = vextq_f32(input[2][0], tmp, 1); + input[2][2] = vextq_f32(input[2][0], tmp, 2); + + float32x4_t tmp1 = vdupq_n_f32(0.f); + float32x4_t tmp2 = vdupq_n_f32(0.f); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][0], k[1], 0); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][1], k[1], 1); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][2], k[1], 2); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); + tmp1 = vaddq_f32(tmp1, tmp2); + + vst1q_f32(outputData, tmp1); + r0 += 4; + r1 += 4; + r2 += 4; + outputData += 4; + } + + for (int r = 0; r < remain; r++) { + float32x4_t i0 = vld1q_f32(r0); + float32x4_t i1 = vld1q_f32(r1); + float32x4_t i2 = vld1q_f32(r2); + *outputData = conv3x3(i0, i1, i2, k[0], k[1], k[2]); + r0++; + r1++; + r2++; + outputData++; + } + + r0 += 2; + r1 += 2; + r2 += 2; + } + } + } +}; + +/** + * Each step calculates four elements of the output. + * First step: + * R0[0, 2, 4, 6...] * K[0][0] + * R0[1, 3, 5, 7...] * K[0][1] + * R0[2, 4, 6, 8...] * K[0][2] + * R1[0, 2, 4, 6...] * K[1][0] + * R1[1, 3, 5, 7...] * K[1][1] + * R1[2, 4, 6, 8...] * K[1][2] + * R2[0, 2, 4, 6...] * K[2][0] + * R2[1, 3, 5, 7...] * K[2][1] + * R2[2, 4, 6, 8...] * K[2][2] + * ------------------------------ + * Output[0, 1, 2, 3] + */ +template <> +struct DepthwiseConvKernel<3, 2> { + static void run(const float* inputData, + const float* filterData, + int inputHeight, + int inputWidth, + int outputChannels, + int outputHeight, + int outputWidth, + int filterMultiplier, + float* outputData) { + const int steps = outputWidth >> 2; + const int remain = outputWidth & 3; + for (int c = 0; c < outputChannels; c++, filterData += 9) { + // Load the filters + float32x4_t k[3]; + k[0] = vld1q_f32(filterData); + k[1] = vld1q_f32(filterData + 3); + k[2] = vld1q_f32(filterData + 6); + k[0] = vsetq_lane_f32(0.f, k[0], 3); + k[1] = vsetq_lane_f32(0.f, k[1], 3); + k[2] = vsetq_lane_f32(0.f, k[2], 3); + + const float* start = + inputData + (c / filterMultiplier) * (inputHeight * inputWidth); + float32x4_t input[3][3]; + for (int h = 0; h < outputHeight; h++) { + const float* r0 = start + 2 * h * inputWidth; + const float* r1 = start + (2 * h + 1) * inputWidth; + const float* r2 = start + (2 * h + 2) * inputWidth; + for (int s = 0; s < steps; s++) { + // Load the inputs + float32x4_t data1; + float32x4x2_t data2; + + data2 = vld2q_f32(r0); + input[0][0] = data2.val[0]; + input[0][1] = data2.val[1]; + data1 = vld1q_f32(r0 + 8); + input[0][2] = vextq_f32(data2.val[0], data1, 1); + + data2 = vld2q_f32(r1); + input[1][0] = data2.val[0]; + input[1][1] = data2.val[1]; + data1 = vld1q_f32(r1 + 8); + input[1][2] = vextq_f32(data2.val[0], data1, 1); + + data2 = vld2q_f32(r2); + input[2][0] = data2.val[0]; + input[2][1] = data2.val[1]; + data1 = vld1q_f32(r2 + 8); + input[2][2] = vextq_f32(data2.val[0], data1, 1); + + float32x4_t tmp1 = vdupq_n_f32(0.f); + float32x4_t tmp2 = vdupq_n_f32(0.f); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][0], k[1], 0); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][1], k[1], 1); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][2], k[1], 2); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); + tmp1 = vaddq_f32(tmp1, tmp2); + + vst1q_f32(outputData, tmp1); + r0 += 8; + r1 += 8; + r2 += 8; + outputData += 4; + } + + for (int r = 0; r < remain; r++) { + float32x4_t i0 = vld1q_f32(r0); + float32x4_t i1 = vld1q_f32(r1); + float32x4_t i2 = vld1q_f32(r2); + *outputData = conv3x3(i0, i1, i2, k[0], k[1], k[2]); + r0 += 2; + r1 += 2; + r2 += 2; + outputData++; + } + } + } + } +}; + +/** + * Each step calculates four elements of the output. + */ +template <> +struct DepthwiseConvKernel<4, 1> { + static void run(const float* inputData, + const float* filterData, + int inputHeight, + int inputWidth, + int outputChannels, + int outputHeight, + int outputWidth, + int filterMultiplier, + float* outputData) { + const int steps = outputWidth >> 2; + const int remain = outputWidth & 3; + for (int c = 0; c < outputChannels; c++, filterData += 16) { + // Load the filters + float32x4_t k[4]; + k[0] = vld1q_f32(filterData); + k[1] = vld1q_f32(filterData + 4); + k[2] = vld1q_f32(filterData + 8); + k[3] = vld1q_f32(filterData + 12); + + const float* r0 = + inputData + (c / filterMultiplier) * (inputHeight * inputWidth); + const float* r1 = r0 + inputWidth; + const float* r2 = r0 + inputWidth * 2; + const float* r3 = r0 + inputWidth * 3; + float32x4_t input[4][4]; + for (int h = 0; h < outputHeight; h++) { + for (int s = 0; s < steps; s++) { + // Load the inputs + float32x4_t tmp; + input[0][0] = vld1q_f32(r0); + tmp = vld1q_f32(r0 + 4); + input[0][1] = vextq_f32(input[0][0], tmp, 1); + input[0][2] = vextq_f32(input[0][0], tmp, 2); + input[0][3] = vextq_f32(input[0][0], tmp, 3); + + input[1][0] = vld1q_f32(r1); + tmp = vld1q_f32(r1 + 4); + input[1][1] = vextq_f32(input[1][0], tmp, 1); + input[1][2] = vextq_f32(input[1][0], tmp, 2); + input[1][3] = vextq_f32(input[1][0], tmp, 3); + + input[2][0] = vld1q_f32(r2); + tmp = vld1q_f32(r2 + 4); + input[2][1] = vextq_f32(input[2][0], tmp, 1); + input[2][2] = vextq_f32(input[2][0], tmp, 2); + input[2][3] = vextq_f32(input[2][0], tmp, 3); + + input[3][0] = vld1q_f32(r3); + tmp = vld1q_f32(r3 + 4); + input[3][1] = vextq_f32(input[3][0], tmp, 1); + input[3][2] = vextq_f32(input[3][0], tmp, 2); + input[3][3] = vextq_f32(input[3][0], tmp, 3); + + float32x4_t tmp1 = vdupq_n_f32(0.f); + float32x4_t tmp2 = vdupq_n_f32(0.f); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][3], k[0], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][0], k[1], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][1], k[1], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][2], k[1], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][3], k[1], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][3], k[2], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[3][0], k[3], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[3][1], k[3], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[3][2], k[3], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[3][3], k[3], 3); + tmp1 = vaddq_f32(tmp1, tmp2); + + vst1q_f32(outputData, tmp1); + r0 += 4; + r1 += 4; + r2 += 4; + r3 += 4; + outputData += 4; + } + + for (int r = 0; r < remain; r++) { + float32x4_t i0 = vld1q_f32(r0); + float32x4_t i1 = vld1q_f32(r1); + float32x4_t i2 = vld1q_f32(r2); + float32x4_t i3 = vld1q_f32(r3); + *outputData = conv4x4(i0, i1, i2, i3, k[0], k[1], k[2], k[3]); + r0++; + r1++; + r2++; + r3++; + outputData++; + } + + r0 += 3; + r1 += 3; + r2 += 3; + r3 += 3; + } + } + } +}; + +/** + * Each step calculates four elements of the output. + */ +template <> +struct DepthwiseConvKernel<4, 2> { + static void run(const float* inputData, + const float* filterData, + int inputHeight, + int inputWidth, + int outputChannels, + int outputHeight, + int outputWidth, + int filterMultiplier, + float* outputData) { + const int steps = outputWidth >> 2; + const int remain = outputWidth & 3; + for (int c = 0; c < outputChannels; c++, filterData += 16) { + // Load the filters + float32x4_t k[4]; + k[0] = vld1q_f32(filterData); + k[1] = vld1q_f32(filterData + 4); + k[2] = vld1q_f32(filterData + 8); + k[3] = vld1q_f32(filterData + 12); + + const float* start = + inputData + (c / filterMultiplier) * (inputHeight * inputWidth); + float32x4_t input[4][4]; + for (int h = 0; h < outputHeight; h++) { + const float* r0 = start + 2 * h * inputWidth; + const float* r1 = start + (2 * h + 1) * inputWidth; + const float* r2 = start + (2 * h + 2) * inputWidth; + const float* r3 = start + (2 * h + 3) * inputWidth; + for (int s = 0; s < steps; s++) { + // Load the inputs + float32x4x2_t data1; + float32x4x2_t data2; + + data1 = vld2q_f32(r0); + data2 = vld2q_f32(r0 + 8); + input[0][0] = data1.val[0]; + input[0][1] = data1.val[1]; + input[0][2] = vextq_f32(data1.val[0], data2.val[0], 1); + input[0][3] = vextq_f32(data1.val[1], data2.val[1], 1); + + data1 = vld2q_f32(r1); + data2 = vld2q_f32(r1 + 8); + input[1][0] = data1.val[0]; + input[1][1] = data1.val[1]; + input[1][2] = vextq_f32(data1.val[0], data2.val[0], 1); + input[1][3] = vextq_f32(data1.val[1], data2.val[1], 1); + + data1 = vld2q_f32(r2); + data2 = vld2q_f32(r2 + 8); + input[2][0] = data1.val[0]; + input[2][1] = data1.val[1]; + input[2][2] = vextq_f32(data1.val[0], data2.val[0], 1); + input[2][3] = vextq_f32(data1.val[1], data2.val[1], 1); + + data1 = vld2q_f32(r3); + data2 = vld2q_f32(r3 + 8); + input[3][0] = data1.val[0]; + input[3][1] = data1.val[1]; + input[3][2] = vextq_f32(data1.val[0], data2.val[0], 1); + input[3][3] = vextq_f32(data1.val[1], data2.val[1], 1); + + float32x4_t tmp1 = vdupq_n_f32(0.f); + float32x4_t tmp2 = vdupq_n_f32(0.f); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][0], k[0], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][1], k[0], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[0][2], k[0], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[0][3], k[0], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][0], k[1], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][1], k[1], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[1][2], k[1], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[1][3], k[1], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][0], k[2], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][1], k[2], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[2][2], k[2], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[2][3], k[2], 3); + tmp1 = vmlaq_laneq_f32(tmp1, input[3][0], k[3], 0); + tmp2 = vmlaq_laneq_f32(tmp2, input[3][1], k[3], 1); + tmp1 = vmlaq_laneq_f32(tmp1, input[3][2], k[3], 2); + tmp2 = vmlaq_laneq_f32(tmp2, input[3][3], k[3], 3); + tmp1 = vaddq_f32(tmp1, tmp2); + + vst1q_f32(outputData, tmp1); + r0 += 8; + r1 += 8; + r2 += 8; + r3 += 8; + outputData += 4; + } + + for (int r = 0; r < remain; r++) { + float32x4_t i0 = vld1q_f32(r0); + float32x4_t i1 = vld1q_f32(r1); + float32x4_t i2 = vld1q_f32(r2); + float32x4_t i3 = vld1q_f32(r3); + *outputData = conv4x4(i0, i1, i2, i3, k[0], k[1], k[2], k[3]); + r0 += 2; + r1 += 2; + r2 += 2; + r3 += 2; + outputData++; + } + } + } + } +}; + +#endif + +} // namespace neon +} // namespace paddle From f8b885f27f19474124d46002d6572c239910eefd Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 31 Aug 2017 20:15:48 +0800 Subject: [PATCH 027/295] Using EigenTensor to reshape tensor. --- paddle/operators/squared_l2_distance_op.cc | 64 ++++++++--- paddle/operators/squared_l2_distance_op.h | 128 ++++++++++++++++++--- 2 files changed, 157 insertions(+), 35 deletions(-) diff --git a/paddle/operators/squared_l2_distance_op.cc b/paddle/operators/squared_l2_distance_op.cc index 9fc498d5a5..3049f0f8ba 100644 --- a/paddle/operators/squared_l2_distance_op.cc +++ b/paddle/operators/squared_l2_distance_op.cc @@ -22,36 +22,52 @@ class SquaredL2DistanceOp : public framework::OperatorWithKernel { using framework::OperatorWithKernel::OperatorWithKernel; protected: - void InferShape(const framework::InferShapeContext &ctx) const override { + void InferShape(const framework::InferShapeContext& ctx) const override { PADDLE_ENFORCE_NOT_NULL(ctx.InputVar("X"), "Input of SquaredL2DistanceOp " "must be initialized."); - PADDLE_ENFORCE_EQ(ctx.Input("X")->dims(), - ctx.Input("Y")->dims(), - "Dimensions of SquaredL2DistanceOp's two inputs " - "must be same.") - framework::DDim dims = ctx.Input("X")->dims(); - ctx.Output("sub_result")->Resize(dims); - ctx.Output("Out")->Resize(framework::make_ddim({dims[0], 1})); + PADDLE_ENFORCE_NOT_NULL(ctx.InputVar("Y"), + "Target of SquaredL2DistanceOp " + "must be initialized."); + + auto* X = ctx.Input("X"); + auto xDims = X->dims(); + auto* Y = ctx.Input("Y"); + auto yDims = Y->dims(); + + PADDLE_ENFORCE_EQ(framework::arity(xDims), framework::arity(yDims), + "Tensor rank of both SquaredL2DistanceOp's " + "inputs must be same."); + int rank = framework::arity(xDims); + PADDLE_ENFORCE(rank >= 2 || rank <= 6, "Tensor rank should be in [2, 6]."); + PADDLE_ENFORCE(yDims[0] == 1 || yDims[0] == xDims[0], + "First dimension of target must be equal to input " + "or to 1."); + + ctx.Output("sub_result")->Resize(xDims); + ctx.Output("Out")->Resize({xDims[0], 1}); } }; class SquaredL2DistanceOpMaker : public framework::OpProtoAndCheckerMaker { public: - SquaredL2DistanceOpMaker(framework::OpProto *proto, - framework::OpAttrChecker *op_checker) + SquaredL2DistanceOpMaker(framework::OpProto* proto, + framework::OpAttrChecker* op_checker) : OpProtoAndCheckerMaker(proto, op_checker) { - AddInput("X", "Input value."); - AddInput("Y", "Target value."); + AddInput("X", "Input of SquaredL2DistanceOp."); + AddInput("Y", "Target of SquaredL2DistanceOp."); AddOutput("sub_result", "Buffering substraction result which " "will be reused in backward.") .AsIntermediate(); AddOutput("Out", "Squared l2 distance between input and target."); AddComment(R"DOC( - SquaredL2DistanceOp will cacluate the squared L2 distances for + SquaredL2DistanceOp will cacluate the squared L2 distance for input and target. Number of distance value equals to the - first dimension of input. + first dimension of input. First dimension of target could be equal to + input or to 1. If the first dimension of target is 1, SquaredL2DistanceOp + will broadcast the first dimension to the first dimension of input. + You can decide whether calculate the gradient of target. )DOC"); } }; @@ -61,9 +77,23 @@ class SquaredL2DistanceGradOp : public framework::OperatorWithKernel { using framework::OperatorWithKernel::OperatorWithKernel; protected: - void InferShape(const framework::InferShapeContext &ctx) const override { - ctx.Output(framework::GradVarName("X")) - ->Resize(ctx.Input("X")->dims()); + void InferShape(const framework::InferShapeContext& ctx) const override { + PADDLE_ENFORCE_NOT_NULL(ctx.InputVar(framework::GradVarName("Out")), + "Gradient of Out should not be null"); + // check out grad dimensions + auto outDims = ctx.Input(framework::GradVarName("Out"))->dims(); + auto xDims = ctx.Input("X")->dims(); + auto yDims = ctx.Input("Y")->dims(); + PADDLE_ENFORCE_EQ(outDims[0], xDims[0], + "First dimension of output gradient and " + "input value must be equal."); + PADDLE_ENFORCE_EQ(outDims[1], 1, + "Second dimension of output gradient " + "must be 1."); + auto* xGrad = ctx.Output(framework::GradVarName("X")); + auto* yGrad = ctx.Output(framework::GradVarName("Y")); + if (xGrad != nullptr) xGrad->Resize(xDims); + if (yGrad != nullptr) yGrad->Resize(yDims); } }; diff --git a/paddle/operators/squared_l2_distance_op.h b/paddle/operators/squared_l2_distance_op.h index b350fd0117..e95364c706 100644 --- a/paddle/operators/squared_l2_distance_op.h +++ b/paddle/operators/squared_l2_distance_op.h @@ -20,17 +20,44 @@ namespace paddle { namespace operators { using Tensor = framework::Tensor; -template -using EigenMatrix = framework::EigenMatrix; +using EigenTensor = framework::EigenTensor; template -using EigenVector = framework::EigenVector; +using EigenMatrix = framework::EigenMatrix; template class SquaredL2DistanceKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { + auto* input0 = context.Input("X"); + const int rank = framework::arity(input0->dims()); + switch (rank) { + case 2: + Operate<2>(context); + break; + case 3: + Operate<3>(context); + break; + case 4: + Operate<4>(context); + break; + case 5: + Operate<5>(context); + break; + case 6: + Operate<6>(context); + break; + default: + // already asserted in SquaredL2DistanceOpMaker + break; + } + } + + private: + template + void Operate(const framework::ExecutionContext& context) const { auto* input0 = context.Input("X"); auto* input1 = context.Input("Y"); auto* output0 = context.Output("sub_result"); @@ -39,17 +66,28 @@ class SquaredL2DistanceKernel : public framework::OpKernel { output0->mutable_data(context.GetPlace()); output1->mutable_data(context.GetPlace()); - auto X = EigenMatrix::From(*input0); - auto Y = EigenMatrix::From(*input1); - auto subResult = EigenMatrix::From(*output0); + auto X = EigenTensor::From(*input0); + auto Y = EigenTensor::From(*input1); + auto subResult = EigenTensor::From(*output0); auto Z = EigenMatrix::From(*output1); + auto xDims = X.dimensions(); + auto yDims = Y.dimensions(); + auto place = context.GetEigenDevice(); + // buffer the substraction result - subResult.device(place) = X - Y; - const auto& inDims = X.dimensions(); + if (yDims[0] == 1 && xDims[0] != yDims[0]) { + auto yBroadcastDims = yDims; + yBroadcastDims[0] = xDims[0]; + subResult.device(place) = X - Y.broadcast(yBroadcastDims); + } else { + subResult.device(place) = X - Y; + } + + // create matrix view for substraction result const auto& subResMat = subResult.reshape(Eigen::array( - {static_cast(inDims[0]), static_cast(X.size() / inDims[0])})); + {static_cast(xDims[0]), static_cast(X.size() / xDims[0])})); Z.device(place) = subResMat.pow(2).sum(Eigen::array({1})); } }; @@ -59,24 +97,78 @@ class SquaredL2DistanceGradKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { auto* input0 = context.Input("sub_result"); - auto* OG = context.Input(framework::GradVarName("Out")); - auto* IG = context.Output(framework::GradVarName("X")); + const int rank = framework::arity(input0->dims()); + switch (rank) { + case 2: + Operate<2>(context); + break; + case 3: + Operate<3>(context); + break; + case 4: + Operate<4>(context); + break; + case 5: + Operate<5>(context); + break; + case 6: + Operate<6>(context); + break; + default: + // already asserted in SquaredL2DistanceOpMaker + break; + } + } - IG->mutable_data(context.GetPlace()); + private: + template + void Operate(const framework::ExecutionContext& context) const { + auto* input0 = context.Input("sub_result"); + auto* OG = context.Input(framework::GradVarName("Out")); + auto* XG = context.Output(framework::GradVarName("X")); + auto* YG = context.Output(framework::GradVarName("Y")); - auto subResult = EigenMatrix::From(*input0); + auto subResult = EigenTensor::From(*input0); auto outGrad = EigenMatrix::From(*OG); - auto inGrad = EigenMatrix::From(*IG); - const auto& subResDims = subResult.dimensions(); + auto subResDims = subResult.dimensions(); int firstDim = static_cast(subResDims[0]); int cols = subResult.size() / firstDim; const auto subResMat = subResult.reshape(Eigen::array({firstDim, cols})); - // create a matrix view for input gradient tensor - auto inGradMat = inGrad.reshape(Eigen::array({firstDim, cols})); - inGradMat.device(context.GetEigenDevice()) = + + // calculate gradient + auto gradMat = 2 * (outGrad.broadcast(Eigen::array({1, cols}))) * subResMat; + + // propagate back to input + auto eigenPlace = context.GetEigenDevice(); + if (XG != nullptr) { + XG->mutable_data(context.GetPlace()); + auto xGrad = EigenTensor::From(*XG); + // dimensions are same with subResult + auto xGradMat = xGrad.reshape(Eigen::array({firstDim, cols})); + xGradMat.device(eigenPlace) = gradMat; + } + if (YG != nullptr) { + YG->mutable_data(context.GetPlace()); + auto yGrad = EigenTensor::From(*YG); + auto dimsYGrad = yGrad.dimensions(); + auto yGradMat = yGrad.reshape(Eigen::array( + {static_cast(dimsYGrad[0]), + static_cast(yGrad.size() / dimsYGrad[0])})); + + PADDLE_ENFORCE(dimsYGrad[0] <= firstDim, + "First dimension of gradient must be greater or " + "equal than first dimension of target"); + + if (dimsYGrad[0] == firstDim) { + yGradMat.device(eigenPlace) = -1 * gradMat; + } else { + yGradMat.device(eigenPlace) = + -1 * (gradMat.sum(Eigen::array({0}))); + } + } } }; From 4b6b7251c10371ceceb84c55ebc587715591c436 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 31 Aug 2017 21:29:49 +0800 Subject: [PATCH 028/295] Refine NeonDepthwiseConv. --- paddle/function/neon/NeonDepthwiseConv.cpp | 35 +++++++++++----------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/paddle/function/neon/NeonDepthwiseConv.cpp b/paddle/function/neon/NeonDepthwiseConv.cpp index 7e5f752a0b..3d502f5d6d 100644 --- a/paddle/function/neon/NeonDepthwiseConv.cpp +++ b/paddle/function/neon/NeonDepthwiseConv.cpp @@ -18,8 +18,6 @@ limitations under the License. */ namespace paddle { -namespace neon { - #if defined(__ARM_NEON__) || defined(__ARM_NEON) template @@ -45,16 +43,16 @@ public: 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]; - size_t filterMultiplier = outputChannels / groups_; + int batchSize = input[0]; + int inputChannels = input[1]; + int inputHeight = input[2]; + int inputWidth = input[3]; + int filterHeight = getFilterHeight(filter); + int filterWidth = getFilterWidth(filter); + int outputChannels = output[1]; + int outputHeight = output[2]; + int outputWidth = output[3]; + int filterMultiplier = outputChannels / groups_; CHECK_EQ(inputChannels, groups_); // only support strideH() == strideW() and filterHeight == filterWidth. @@ -90,18 +88,18 @@ public: DepthWiseConv; if (filterWidth == 3 && strideW() == 1) { - DepthWiseConv = DepthwiseConvKernel<3, 1>::run; + DepthWiseConv = neon::DepthwiseConvKernel<3, 1>::run; } else if (filterWidth == 3 && strideW() == 2) { - DepthWiseConv = DepthwiseConvKernel<3, 2>::run; + DepthWiseConv = neon::DepthwiseConvKernel<3, 2>::run; } else if (filterWidth == 4 && strideW() == 1) { - DepthWiseConv = DepthwiseConvKernel<4, 1>::run; + DepthWiseConv = neon::DepthwiseConvKernel<4, 1>::run; } else if (filterWidth == 4 && strideW() == 2) { - DepthWiseConv = DepthwiseConvKernel<4, 2>::run; + DepthWiseConv = neon::DepthwiseConvKernel<4, 2>::run; } else { LOG(FATAL) << "Not supported"; } - for (size_t i = 0; i < batchSize; i++) { + for (int i = 0; i < batchSize; i++) { DepthWiseConv(inputPadding, filterData, inputHeight, @@ -117,9 +115,10 @@ public: } }; +#ifndef PADDLE_TYPE_DOUBLE REGISTER_TYPED_FUNC(NeonDepthwiseConv, CPU, NeonDepthwiseConvFunction); +#endif #endif -} // namespace neon } // namespace paddle From 40d47fae95b04a26d0fa47eb46a83871f30f229f Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 31 Aug 2017 21:36:09 +0800 Subject: [PATCH 029/295] [Refine code]Move class Padding into the NeonDepthwiseConv.h. --- paddle/function/Im2Col.h | 91 --------------------- paddle/function/neon/NeonDepthwiseConv.cpp | 15 ++-- paddle/function/neon/NeonDepthwiseConv.h | 92 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 99 deletions(-) diff --git a/paddle/function/Im2Col.h b/paddle/function/Im2Col.h index 9b91e223a6..1e0cff436f 100644 --- a/paddle/function/Im2Col.h +++ b/paddle/function/Im2Col.h @@ -94,95 +94,4 @@ public: int paddingWidth); }; -template -struct Padding { - static void run(const T* src, - T* dest, - int channels, - int inputHeight, - int inputWidth, - int paddingHeight, - int paddingWidth) { - const int destWidth = inputWidth + 2 * paddingWidth; - for (int c = 0; c < channels; c++) { - if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(T)); - dest += destWidth * paddingHeight; - } - - for (int i = 0; i < inputHeight; i++) { - // padding head - for (int j = 0; j < paddingWidth; j++) { - *dest++ = T(0); - } - - memcpy(dest, src, inputWidth * sizeof(T)); - dest += inputWidth; - src += inputWidth; - - // padding tail - for (int j = 0; j < paddingWidth; j++) { - *dest++ = T(0); - } - } - - if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(T)); - dest += destWidth * paddingHeight; - } - } - } -}; - -#if defined(__ARM_NEON__) || defined(__ARM_NEON) -template <> -struct Padding { - static void run(const float* src, - float* dest, - int channels, - int inputHeight, - int inputWidth, - int paddingHeight, - int paddingWidth) { - const int destWidth = inputWidth + 2 * paddingWidth; - for (int c = 0; c < channels; c++) { - if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(float)); - dest += destWidth * paddingHeight; - } - - for (int i = 0; i < inputHeight; i++) { - // padding head - for (int j = 0; j < paddingWidth; j++) { - *dest++ = float(0); - } - - int step = inputWidth >> 2; - int remain = inputWidth & 3; - for (int s = 0; s < step; s++) { - float32x4_t s0 = vld1q_f32(src); - vst1q_f32(dest, s0); - src += 4; - dest += 4; - } - for (int r = 0; r < remain; r++) { - *dest++ = *src++; - } - - // padding tail - for (int j = 0; j < paddingWidth; j++) { - *dest++ = float(0); - } - } - - if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(float)); - dest += destWidth * paddingHeight; - } - } - } -}; - -#endif - } // namespace paddle diff --git a/paddle/function/neon/NeonDepthwiseConv.cpp b/paddle/function/neon/NeonDepthwiseConv.cpp index 3d502f5d6d..bd9a56a8a5 100644 --- a/paddle/function/neon/NeonDepthwiseConv.cpp +++ b/paddle/function/neon/NeonDepthwiseConv.cpp @@ -14,7 +14,6 @@ limitations under the License. */ #include "NeonDepthwiseConv.h" #include "paddle/function/ConvOp.h" -#include "paddle/function/Im2Col.h" namespace paddle { @@ -70,13 +69,13 @@ public: (inputWidth + 2 * paddingW()); resizeBuffer(newSize); inputPadding = reinterpret_cast(memory_->getBuf()); - Padding::run(inputData, - inputPadding, - batchSize * inputChannels, - inputHeight, - inputWidth, - paddingH(), - paddingW()); + neon::Padding::run(inputData, + inputPadding, + batchSize * inputChannels, + inputHeight, + inputWidth, + paddingH(), + paddingW()); // height and width of padding data inputHeight += 2 * paddingH(); diff --git a/paddle/function/neon/NeonDepthwiseConv.h b/paddle/function/neon/NeonDepthwiseConv.h index cb1abe1f32..3ceaa65ddb 100644 --- a/paddle/function/neon/NeonDepthwiseConv.h +++ b/paddle/function/neon/NeonDepthwiseConv.h @@ -14,6 +14,7 @@ limitations under the License. */ #pragma once +#include #include "neon_util.h" namespace paddle { @@ -474,6 +475,97 @@ struct DepthwiseConvKernel<4, 2> { } }; +template +struct Padding { + static void run(const T* src, + T* dest, + int channels, + int inputHeight, + int inputWidth, + int paddingHeight, + int paddingWidth) { + const int destWidth = inputWidth + 2 * paddingWidth; + for (int c = 0; c < channels; c++) { + if (paddingHeight > 0) { + memset(dest, 0, destWidth * paddingHeight * sizeof(T)); + dest += destWidth * paddingHeight; + } + + for (int i = 0; i < inputHeight; i++) { + // padding head + for (int j = 0; j < paddingWidth; j++) { + *dest++ = T(0); + } + + memcpy(dest, src, inputWidth * sizeof(T)); + dest += inputWidth; + src += inputWidth; + + // padding tail + for (int j = 0; j < paddingWidth; j++) { + *dest++ = T(0); + } + } + + if (paddingHeight > 0) { + memset(dest, 0, destWidth * paddingHeight * sizeof(T)); + dest += destWidth * paddingHeight; + } + } + } +}; + +#if defined(__ARM_NEON__) || defined(__ARM_NEON) +template <> +struct Padding { + static void run(const float* src, + float* dest, + int channels, + int inputHeight, + int inputWidth, + int paddingHeight, + int paddingWidth) { + const int destWidth = inputWidth + 2 * paddingWidth; + for (int c = 0; c < channels; c++) { + if (paddingHeight > 0) { + memset(dest, 0, destWidth * paddingHeight * sizeof(float)); + dest += destWidth * paddingHeight; + } + + for (int i = 0; i < inputHeight; i++) { + // padding head + for (int j = 0; j < paddingWidth; j++) { + *dest++ = float(0); + } + + int step = inputWidth >> 2; + int remain = inputWidth & 3; + for (int s = 0; s < step; s++) { + float32x4_t s0 = vld1q_f32(src); + vst1q_f32(dest, s0); + src += 4; + dest += 4; + } + for (int r = 0; r < remain; r++) { + *dest++ = *src++; + } + + // padding tail + for (int j = 0; j < paddingWidth; j++) { + *dest++ = float(0); + } + } + + if (paddingHeight > 0) { + memset(dest, 0, destWidth * paddingHeight * sizeof(float)); + dest += destWidth * paddingHeight; + } + } + } +}; + +#endif + #endif } // namespace neon From 840104c99a59f3f970c71eea27382c09e0de6a28 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 31 Aug 2017 21:59:35 +0800 Subject: [PATCH 030/295] Add NeonDepthwiseConvTransposeFunction. --- paddle/function/neon/NeonDepthwiseConv.cpp | 19 ++- paddle/function/neon/NeonDepthwiseConv.h | 62 ++++----- .../neon/NeonDepthwiseConvTranspose.cpp | 124 ++++++++++++++++++ 3 files changed, 164 insertions(+), 41 deletions(-) create mode 100644 paddle/function/neon/NeonDepthwiseConvTranspose.cpp diff --git a/paddle/function/neon/NeonDepthwiseConv.cpp b/paddle/function/neon/NeonDepthwiseConv.cpp index bd9a56a8a5..18126152ea 100644 --- a/paddle/function/neon/NeonDepthwiseConv.cpp +++ b/paddle/function/neon/NeonDepthwiseConv.cpp @@ -64,9 +64,10 @@ public: // padding the input float* inputPadding = inputData; + int padInputHeight = inputHeight + 2 * paddingH(); + int padInputWidth = inputWidth + 2 * paddingW(); if (paddingH() > 0 || paddingW() > 0) { - int newSize = batchSize * inputChannels * (inputHeight + 2 * paddingH()) * - (inputWidth + 2 * paddingW()); + int newSize = batchSize * inputChannels * padInputHeight * padInputWidth; resizeBuffer(newSize); inputPadding = reinterpret_cast(memory_->getBuf()); neon::Padding::run(inputData, @@ -74,12 +75,8 @@ public: batchSize * inputChannels, inputHeight, inputWidth, - paddingH(), - paddingW()); - - // height and width of padding data - inputHeight += 2 * paddingH(); - inputWidth += 2 * paddingW(); + padInputHeight, + padInputWidth); } std::function { template struct Padding { - static void run(const T* src, - T* dest, + static void run(const T* input, + T* inputPadding, int channels, int inputHeight, int inputWidth, - int paddingHeight, - int paddingWidth) { - const int destWidth = inputWidth + 2 * paddingWidth; + int padInputHeight, + int padInputWidth) { + const int paddingHeight = (padInputHeight - inputHeight) / 2; + const int paddingWidth = (padInputWidth - inputWidth) / 2; for (int c = 0; c < channels; c++) { if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(T)); - dest += destWidth * paddingHeight; + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(T)); + inputPadding += padInputWidth * paddingHeight; } for (int i = 0; i < inputHeight; i++) { // padding head for (int j = 0; j < paddingWidth; j++) { - *dest++ = T(0); + *inputPadding++ = T(0); } - memcpy(dest, src, inputWidth * sizeof(T)); - dest += inputWidth; - src += inputWidth; + memcpy(inputPadding, input, inputWidth * sizeof(T)); + inputPadding += inputWidth; + input += inputWidth; // padding tail for (int j = 0; j < paddingWidth; j++) { - *dest++ = T(0); + *inputPadding++ = T(0); } } if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(T)); - dest += destWidth * paddingHeight; + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(T)); + inputPadding += padInputWidth * paddingHeight; } } } @@ -518,47 +519,48 @@ struct Padding { #if defined(__ARM_NEON__) || defined(__ARM_NEON) template <> struct Padding { - static void run(const float* src, - float* dest, + static void run(const float* input, + float* inputPadding, int channels, int inputHeight, int inputWidth, - int paddingHeight, - int paddingWidth) { - const int destWidth = inputWidth + 2 * paddingWidth; + int padInputHeight, + int padInputWidth) { + const int paddingHeight = (padInputHeight - inputHeight) / 2; + const int paddingWidth = (padInputWidth - inputWidth) / 2; for (int c = 0; c < channels; c++) { if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(float)); - dest += destWidth * paddingHeight; + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(float)); + inputPadding += padInputWidth * paddingHeight; } for (int i = 0; i < inputHeight; i++) { // padding head for (int j = 0; j < paddingWidth; j++) { - *dest++ = float(0); + *inputPadding++ = float(0); } int step = inputWidth >> 2; int remain = inputWidth & 3; for (int s = 0; s < step; s++) { - float32x4_t s0 = vld1q_f32(src); - vst1q_f32(dest, s0); - src += 4; - dest += 4; + float32x4_t s0 = vld1q_f32(input); + vst1q_f32(inputPadding, s0); + input += 4; + inputPadding += 4; } for (int r = 0; r < remain; r++) { - *dest++ = *src++; + *inputPadding++ = *input++; } // padding tail for (int j = 0; j < paddingWidth; j++) { - *dest++ = float(0); + *inputPadding++ = float(0); } } if (paddingHeight > 0) { - memset(dest, 0, destWidth * paddingHeight * sizeof(float)); - dest += destWidth * paddingHeight; + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(float)); + inputPadding += padInputWidth * paddingHeight; } } } diff --git a/paddle/function/neon/NeonDepthwiseConvTranspose.cpp b/paddle/function/neon/NeonDepthwiseConvTranspose.cpp new file mode 100644 index 0000000000..03d571ecfe --- /dev/null +++ b/paddle/function/neon/NeonDepthwiseConvTranspose.cpp @@ -0,0 +1,124 @@ +/* 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 "NeonDepthwiseConv.h" +#include "paddle/function/ConvOp.h" + +namespace paddle { + +#if defined(__ARM_NEON__) || defined(__ARM_NEON) + +template +class NeonDepthwiseConvTransposeFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + 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(); + + int batchSize = input[0]; + int inputChannels = input[1]; + int inputHeight = input[2]; + int inputWidth = input[3]; + int filterHeight = getFilterHeight(filter); + int filterWidth = getFilterWidth(filter); + int outputChannels = output[1]; + int outputHeight = output[2]; + int outputWidth = output[3]; + int filterMultiplier = outputChannels / groups_; + CHECK_EQ(inputChannels, groups_); + + // only support strideH() == strideW() and filterHeight == filterWidth. + CHECK_EQ(strideH(), strideW()); + CHECK_EQ(paddingH(), paddingW()); + CHECK_EQ(filterHeight, filterWidth); + + float* inputData = inputs[0].data(); + float* filterData = inputs[1].data(); + float* outputData = outputs[0].data(); + + // padding the input, input -> inputPadding + float* inputPadding = inputData; + int padInputHeight = + (inputHeight - 1) * strideH() + 2 * filterHeight - 1 - 2 * paddingH(); + int padInputWidth = + (inputWidth - 1) * strideW() + 2 * filterWidth - 1 - 2 * paddingW(); + + if (padInputHeight > inputHeight || padInputWidth > inputWidth) { + int newSize = batchSize * inputChannels * padInputHeight * padInputWidth; + resizeBuffer(newSize); + inputPadding = reinterpret_cast(memory_->getBuf()); + neon::Padding::run(inputData, + inputPadding, + batchSize * inputChannels, + inputHeight, + inputWidth, + padInputHeight, + padInputWidth); + } + + std::function + DepthWiseConv; + + if (filterWidth == 3) { + DepthWiseConv = neon::DepthwiseConvKernel<3, 1>::run; + } else if (filterWidth == 4) { + DepthWiseConv = neon::DepthwiseConvKernel<4, 1>::run; + } else { + LOG(FATAL) << "Not supported"; + } + + for (int i = 0; i < batchSize; i++) { + DepthWiseConv(inputPadding, + filterData, + padInputHeight, + padInputWidth, + outputChannels, + outputHeight, + outputWidth, + filterMultiplier, + outputData); + inputPadding += inputChannels * padInputHeight * padInputWidth; + outputData += outputChannels * outputHeight * outputWidth; + } + } +}; + +#ifndef PADDLE_TYPE_DOUBLE + +REGISTER_TYPED_FUNC(NeonDepthwiseConvTranspose, + CPU, + NeonDepthwiseConvTransposeFunction); + +#endif + +#endif + +} // namespace paddle From 6bef079660f689a1b9c061e31c8273de353f98da Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 31 Aug 2017 22:31:34 +0800 Subject: [PATCH 031/295] Follow coding style and move reshaping operation to paddle tensor. --- paddle/operators/squared_l2_distance_op.cc | 47 ++--- paddle/operators/squared_l2_distance_op.h | 170 ++++++------------ .../tests/test_squared_l2_distance_op.py | 10 ++ 3 files changed, 92 insertions(+), 135 deletions(-) diff --git a/paddle/operators/squared_l2_distance_op.cc b/paddle/operators/squared_l2_distance_op.cc index 3049f0f8ba..b19c274dcc 100644 --- a/paddle/operators/squared_l2_distance_op.cc +++ b/paddle/operators/squared_l2_distance_op.cc @@ -30,22 +30,27 @@ class SquaredL2DistanceOp : public framework::OperatorWithKernel { "Target of SquaredL2DistanceOp " "must be initialized."); - auto* X = ctx.Input("X"); - auto xDims = X->dims(); - auto* Y = ctx.Input("Y"); - auto yDims = Y->dims(); + auto* x = ctx.Input("X"); + auto x_dims = x->dims(); + auto* y = ctx.Input("Y"); + auto y_dims = y->dims(); - PADDLE_ENFORCE_EQ(framework::arity(xDims), framework::arity(yDims), + PADDLE_ENFORCE_EQ(framework::arity(x_dims), framework::arity(y_dims), "Tensor rank of both SquaredL2DistanceOp's " "inputs must be same."); - int rank = framework::arity(xDims); - PADDLE_ENFORCE(rank >= 2 || rank <= 6, "Tensor rank should be in [2, 6]."); - PADDLE_ENFORCE(yDims[0] == 1 || yDims[0] == xDims[0], + + int rank = framework::arity(x_dims); + PADDLE_ENFORCE(rank >= 2, "Tensor rank should be at least equal to 2."); + PADDLE_ENFORCE_EQ(framework::product(x_dims) / x_dims[0], + framework::product(y_dims) / y_dims[0], + "Product of dimensions expcet the first dimension of " + "input and target must be equal."); + PADDLE_ENFORCE(y_dims[0] == 1 || y_dims[0] == x_dims[0], "First dimension of target must be equal to input " "or to 1."); - ctx.Output("sub_result")->Resize(xDims); - ctx.Output("Out")->Resize({xDims[0], 1}); + ctx.Output("sub_result")->Resize(x_dims); + ctx.Output("Out")->Resize({x_dims[0], 1}); } }; @@ -66,8 +71,8 @@ class SquaredL2DistanceOpMaker : public framework::OpProtoAndCheckerMaker { input and target. Number of distance value equals to the first dimension of input. First dimension of target could be equal to input or to 1. If the first dimension of target is 1, SquaredL2DistanceOp - will broadcast the first dimension to the first dimension of input. - You can decide whether calculate the gradient of target. + will broadcast target's first dimension to input's first dimension. + You can decide whether calculate the gradient of input and target. )DOC"); } }; @@ -81,19 +86,19 @@ class SquaredL2DistanceGradOp : public framework::OperatorWithKernel { PADDLE_ENFORCE_NOT_NULL(ctx.InputVar(framework::GradVarName("Out")), "Gradient of Out should not be null"); // check out grad dimensions - auto outDims = ctx.Input(framework::GradVarName("Out"))->dims(); - auto xDims = ctx.Input("X")->dims(); - auto yDims = ctx.Input("Y")->dims(); - PADDLE_ENFORCE_EQ(outDims[0], xDims[0], + auto out_dims = ctx.Input(framework::GradVarName("Out"))->dims(); + auto x_dims = ctx.Input("X")->dims(); + auto y_dims = ctx.Input("Y")->dims(); + PADDLE_ENFORCE_EQ(out_dims[0], x_dims[0], "First dimension of output gradient and " "input value must be equal."); - PADDLE_ENFORCE_EQ(outDims[1], 1, + PADDLE_ENFORCE_EQ(out_dims[1], 1, "Second dimension of output gradient " "must be 1."); - auto* xGrad = ctx.Output(framework::GradVarName("X")); - auto* yGrad = ctx.Output(framework::GradVarName("Y")); - if (xGrad != nullptr) xGrad->Resize(xDims); - if (yGrad != nullptr) yGrad->Resize(yDims); + auto* x_grad = ctx.Output(framework::GradVarName("X")); + auto* y_grad = ctx.Output(framework::GradVarName("Y")); + if (x_grad != nullptr) x_grad->Resize(x_dims); + if (y_grad != nullptr) y_grad->Resize(y_dims); } }; diff --git a/paddle/operators/squared_l2_distance_op.h b/paddle/operators/squared_l2_distance_op.h index e95364c706..ec8c34ddf8 100644 --- a/paddle/operators/squared_l2_distance_op.h +++ b/paddle/operators/squared_l2_distance_op.h @@ -20,9 +20,6 @@ namespace paddle { namespace operators { using Tensor = framework::Tensor; -template -using EigenTensor = framework::EigenTensor; template using EigenMatrix = framework::EigenMatrix; @@ -31,64 +28,39 @@ template class SquaredL2DistanceKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { - auto* input0 = context.Input("X"); - const int rank = framework::arity(input0->dims()); - switch (rank) { - case 2: - Operate<2>(context); - break; - case 3: - Operate<3>(context); - break; - case 4: - Operate<4>(context); - break; - case 5: - Operate<5>(context); - break; - case 6: - Operate<6>(context); - break; - default: - // already asserted in SquaredL2DistanceOpMaker - break; - } - } - - private: - template - void Operate(const framework::ExecutionContext& context) const { - auto* input0 = context.Input("X"); - auto* input1 = context.Input("Y"); - auto* output0 = context.Output("sub_result"); - auto* output1 = context.Output("Out"); - - output0->mutable_data(context.GetPlace()); - output1->mutable_data(context.GetPlace()); - - auto X = EigenTensor::From(*input0); - auto Y = EigenTensor::From(*input1); - auto subResult = EigenTensor::From(*output0); - auto Z = EigenMatrix::From(*output1); - - auto xDims = X.dimensions(); - auto yDims = Y.dimensions(); + auto* in0 = context.Input("X"); + auto* in1 = context.Input("Y"); + auto* out0 = context.Output("sub_result"); + auto* out1 = context.Output("Out"); + + auto in0_dims = in0->dims(); + auto in1_dims = in1->dims(); + + int cols = framework::product(in0_dims) / in0_dims[0]; + // reduce dimensions except the first + auto x = + EigenMatrix::From(*in0, framework::make_ddim({in0_dims[0], cols})); + auto y = + EigenMatrix::From(*in1, framework::make_ddim({in1_dims[0], cols})); + + out0->mutable_data(context.GetPlace()); + out1->mutable_data(context.GetPlace()); + auto sub_result = EigenMatrix::From(*out0); + auto z = EigenMatrix::From(*out1); auto place = context.GetEigenDevice(); - + auto x_dims = x.dimensions(); + auto y_dims = y.dimensions(); // buffer the substraction result - if (yDims[0] == 1 && xDims[0] != yDims[0]) { - auto yBroadcastDims = yDims; - yBroadcastDims[0] = xDims[0]; - subResult.device(place) = X - Y.broadcast(yBroadcastDims); + if (y_dims[0] == 1 && x_dims[0] > y_dims[0]) { + auto y_broadcast_dims = y_dims; + y_broadcast_dims[0] = x_dims[0]; + sub_result.device(place) = x - y.broadcast(y_broadcast_dims); } else { - subResult.device(place) = X - Y; + sub_result.device(place) = x - y; } - // create matrix view for substraction result - const auto& subResMat = subResult.reshape(Eigen::array( - {static_cast(xDims[0]), static_cast(X.size() / xDims[0])})); - Z.device(place) = subResMat.pow(2).sum(Eigen::array({1})); + z.device(place) = sub_result.pow(2).sum(Eigen::array({1})); } }; @@ -96,77 +68,47 @@ template class SquaredL2DistanceGradKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { - auto* input0 = context.Input("sub_result"); - const int rank = framework::arity(input0->dims()); - switch (rank) { - case 2: - Operate<2>(context); - break; - case 3: - Operate<3>(context); - break; - case 4: - Operate<4>(context); - break; - case 5: - Operate<5>(context); - break; - case 6: - Operate<6>(context); - break; - default: - // already asserted in SquaredL2DistanceOpMaker - break; - } - } + auto* in0 = context.Input("sub_result"); + auto* in1 = context.Input(framework::GradVarName("Out")); + auto* x_g = context.Output(framework::GradVarName("X")); + auto* y_g = context.Output(framework::GradVarName("Y")); - private: - template - void Operate(const framework::ExecutionContext& context) const { - auto* input0 = context.Input("sub_result"); - auto* OG = context.Input(framework::GradVarName("Out")); - auto* XG = context.Output(framework::GradVarName("X")); - auto* YG = context.Output(framework::GradVarName("Y")); + auto sub_result = EigenMatrix::From(*in0); + auto out_grad = EigenMatrix::From(*in1); - auto subResult = EigenTensor::From(*input0); - auto outGrad = EigenMatrix::From(*OG); - - auto subResDims = subResult.dimensions(); - int firstDim = static_cast(subResDims[0]); - int cols = subResult.size() / firstDim; - const auto subResMat = - subResult.reshape(Eigen::array({firstDim, cols})); + auto x_dims = x_g->dims(); + auto y_dims = y_g->dims(); + int cols = framework::product(x_dims) / x_dims[0]; // calculate gradient - auto gradMat = - 2 * (outGrad.broadcast(Eigen::array({1, cols}))) * subResMat; + auto grad_mat = + 2 * (out_grad.broadcast(Eigen::array({1, cols}))) * sub_result; // propagate back to input - auto eigenPlace = context.GetEigenDevice(); - if (XG != nullptr) { - XG->mutable_data(context.GetPlace()); - auto xGrad = EigenTensor::From(*XG); + auto eigen_place = context.GetEigenDevice(); + if (x_g != nullptr) { + x_g->mutable_data(context.GetPlace()); + // eigen matrix + auto x_grad = + EigenMatrix::From(*x_g, framework::make_ddim({x_dims[0], cols})); // dimensions are same with subResult - auto xGradMat = xGrad.reshape(Eigen::array({firstDim, cols})); - xGradMat.device(eigenPlace) = gradMat; + x_grad.device(eigen_place) = grad_mat; } - if (YG != nullptr) { - YG->mutable_data(context.GetPlace()); - auto yGrad = EigenTensor::From(*YG); - auto dimsYGrad = yGrad.dimensions(); - auto yGradMat = yGrad.reshape(Eigen::array( - {static_cast(dimsYGrad[0]), - static_cast(yGrad.size() / dimsYGrad[0])})); - - PADDLE_ENFORCE(dimsYGrad[0] <= firstDim, + + if (y_g != nullptr) { + y_g->mutable_data(context.GetPlace()); + auto y_grad = + EigenMatrix::From(*y_g, framework::make_ddim({y_dims[0], cols})); + + PADDLE_ENFORCE(sub_result.dimensions()[0] >= y_dims[0], "First dimension of gradient must be greater or " "equal than first dimension of target"); - if (dimsYGrad[0] == firstDim) { - yGradMat.device(eigenPlace) = -1 * gradMat; + if (sub_result.dimensions()[0] == y_dims[0]) { + y_grad.device(eigen_place) = -1 * grad_mat; } else { - yGradMat.device(eigenPlace) = - -1 * (gradMat.sum(Eigen::array({0}))); + y_grad.device(eigen_place) = + -1 * (grad_mat.sum(Eigen::array({0}))); } } } diff --git a/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py b/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py index eeddb5a3bf..51c95b286a 100644 --- a/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py +++ b/python/paddle/v2/framework/tests/test_squared_l2_distance_op.py @@ -21,5 +21,15 @@ class TestSquaredL2DistanceOp(unittest.TestCase): } +class TestSquaredL2DistanceGradOp(GradientChecker): + def test_squared_l2_distance(self): + op = create_op("squared_l2_distance") + inputs = { + 'X': np.random.uniform(0.1, 1., (2, 3)).astype('float32'), + 'Y': np.random.uniform(0.1, 1., (2, 3)).astype('float32') + } + self.check_grad(op, inputs, set(["X", "Y"]), "Out") + + if __name__ == '__main__': unittest.main() From 90bf4f60aea012a3eeb819fe4655069d66dbe6e6 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 31 Aug 2017 23:59:58 +0800 Subject: [PATCH 032/295] Add stride support 2 for NeonDepthwiseConvTranspose. --- paddle/function/neon/NeonDepthwiseConv.h | 57 +++++++++++++++++++ .../neon/NeonDepthwiseConvTranspose.cpp | 26 ++++++--- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/paddle/function/neon/NeonDepthwiseConv.h b/paddle/function/neon/NeonDepthwiseConv.h index 30f0158c61..aefeea78ba 100644 --- a/paddle/function/neon/NeonDepthwiseConv.h +++ b/paddle/function/neon/NeonDepthwiseConv.h @@ -566,6 +566,63 @@ struct Padding { } }; +// for stride is 2 +struct StridePadding { + static void run(const float* input, + float* inputPadding, + int channels, + int inputHeight, + int inputWidth, + int padInputHeight, + int padInputWidth) { + const int paddingHeight = (padInputHeight - (inputHeight * 2 - 1)) / 2; + const int paddingWidth = (padInputWidth - (inputWidth * 2 - 1)) / 2; + for (int c = 0; c < channels; c++) { + if (paddingHeight > 0) { + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(float)); + inputPadding += padInputWidth * paddingHeight; + } + + for (int i = 0; i < inputHeight; i++) { + // padding head + for (int j = 0; j < paddingWidth; j++) { + *inputPadding++ = float(0); + } + + int step = inputWidth >> 2; + int remain = inputWidth & 3; + float32x4_t s1 = vdupq_n_f32(0.f); + for (int s = 0; s < step; s++) { + float32x4_t s0 = vld1q_f32(input); + float32x4x2_t v = {s0, s1}; + vst2q_f32(inputPadding, v); + input += 4; + inputPadding += 8; + } + for (int r = 0; r < remain; r++) { + *inputPadding++ = *input++; + *inputPadding++ = float(0); + } + inputPadding--; + + // padding tail + for (int j = 0; j < paddingWidth; j++) { + *inputPadding++ = float(0); + } + if (i != inputHeight - 1) { + memset(inputPadding, 0, padInputWidth * sizeof(float)); + inputPadding += padInputWidth; + } + } + + if (paddingHeight > 0) { + memset(inputPadding, 0, padInputWidth * paddingHeight * sizeof(float)); + inputPadding += padInputWidth * paddingHeight; + } + } + } +}; + #endif #endif diff --git a/paddle/function/neon/NeonDepthwiseConvTranspose.cpp b/paddle/function/neon/NeonDepthwiseConvTranspose.cpp index 03d571ecfe..49ca4bc8a0 100644 --- a/paddle/function/neon/NeonDepthwiseConvTranspose.cpp +++ b/paddle/function/neon/NeonDepthwiseConvTranspose.cpp @@ -74,13 +74,25 @@ public: int newSize = batchSize * inputChannels * padInputHeight * padInputWidth; resizeBuffer(newSize); inputPadding = reinterpret_cast(memory_->getBuf()); - neon::Padding::run(inputData, - inputPadding, - batchSize * inputChannels, - inputHeight, - inputWidth, - padInputHeight, - padInputWidth); + if (strideH() == 1) { + neon::Padding::run(inputData, + inputPadding, + batchSize * inputChannels, + inputHeight, + inputWidth, + padInputHeight, + padInputWidth); + } else if (strideH() == 2) { + neon::StridePadding::run(inputData, + inputPadding, + batchSize * inputChannels, + inputHeight, + inputWidth, + padInputHeight, + padInputWidth); + } else { + LOG(FATAL) << "Not supported"; + } } std::function Date: Thu, 31 Aug 2017 15:47:22 -0700 Subject: [PATCH 033/295] update figures --- doc/design/ops/src/dist-graph.graffle | Bin 4915 -> 6059 bytes doc/design/ops/src/dist-graph.png | Bin 133866 -> 228040 bytes doc/design/ops/src/local-graph.graffle | Bin 2515 -> 2527 bytes doc/design/ops/src/local-graph.png | Bin 31493 -> 28561 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/design/ops/src/dist-graph.graffle b/doc/design/ops/src/dist-graph.graffle index 1e1cb18dfecd9ee956ce4fe721a9bec4a24282c2..6353f736042ed6056d4f59b0cb4072ae2f7008aa 100644 GIT binary patch literal 6059 zcmV;c7gXpUiwFP!000030PS5{bDPMvexCdaPM*%GZOeV7o9s+>Id&$=>?F>_PCPT^ zs$E#ZHajwC5Q^hT<-eZ>+2YoKBtREu`N6s^=%!iUw=UhRe)_Mk*F)or6O3Ko`}r~M zp~r^f_5FeCUH<&|TZ!+c)ZD zyXt`6ARIha%}mdp>5lq#;P%63kA8ak*|~kT-w)j{&KvvI30`{x=j+qG=v(!p>xIsx z6Ffst_wL``AEMfJ`*x^K`DuFQ*+F&?*wLGxo{qynUGg*4!=8WbxtD=I8TF3TyO)7| zaWQnn)4e;9lr$D*kEq@gK`o1=B%bcwpLyDYN$Bqn{yQ0mh3;Zr=qHN zS8jiH(A_}XiA56Q``>1L`u1%X3xRu#NG|a7zuYl)v81GDssE;+%(SSa-*=2}>YeCW zeD=Mxb`152^-@Pu`Tcw5>Z2A9hW5DF`0T& z7d`z^oznlD{F?fC`ukyE-z5D?diL%p-5qsJ&5i zz_EwVsQGj+eseFXK1Ap^w8KJ&2*P3)$z5U@1V5qX6UvQ$AoW-1U33yZ$RGd88M>Zv zYDay@9&qT6M??GexNi>&tt#LfqtN)-h~Iq8ANq?sbWUzZg~M!xv%GAqjE8=Ia_x9w z9{;o9qlNLiQ{r(%Q5wF;YGoD;v;Mq{SR|`=ceiF}kH*d*tpOp6K`D4(V+DHxL3jag2^Hn$&V2{(%tc4v#=3~f zOeufL0QCO2SB^cX?of(f;_vS7sFfvI{pqs@C&waMI5rY{@2fAfW1G4Ysheq0bH~6; zSlFa4Hy#p^?y914|hI_CWyF1i-En zux2yBPOT^4^@qW$NdT`+@hThORkYy{Yrr^1j0>p*@fq-*Z!3q zjGYh}GrE1}Oq_pySF(M7=m$#ppJ#zHAe^6F29D!JZ_bAkN4+YkvymMP&cd(UMCw1r zBz`ev^g;-WUnGiND2+v^6^k$yi*OW+sH3A76UQQ^P$JQJ-N3WMNnj7p{_00xgzn{4 z=y(@)KlB6i=uvzXV@iy({sl7j2Lt1SGnn+9;Ox==_7w=ipUM9f$*?sPQAF2(A`4Ii zDC!PHzfDxf6@Dh(`Oxow{!e2Zf%kY9Ftg>oM~vF=9tFqc-s4>c2lA;++{gEJ7&?KM zlhjTRZ)T8)AxK=~tO>@Q!yIN!0Of%4<|xMvP)<6Z8!aUQK=}`Ya+pzFuggw5M>)9c zumqw$SA`Lcv_x1PCadNH{;~)zg&2cW7^K45PlXXSO@$ph-k`(uF&F&7A(8_k0g>Gy z@@N#g*Ggvt<9Kp@8EJnf_`EZj*jmFZDK-H@0HN;YcRo1%FEE$WG%z$91cnV)0&!;c zIA<0@SYjq6;yVUGg!VW^gh;_9HZ8LDh!xdYlJuk{X3rps+Ne{rbP%G>xuebq zFX?CJ=FBct=cEYQ_@oqJkd$i3J+uk;$X1s5YcfuWjO4V3Osvcrn=(p7PZsTAM3fm* zidw8Em~xd&jyWSH zr}I!$hDU|jQ;wh%CRYqc2oe#iT2_w=so2pHRB|qhXUNiA8PVwnRzr3L!BWDjqP)UQ zD5brU_DZO4KJu%ezajxvlTrmO6%|dRcX@JJSiuRYQ}Em;;E?u*xRyh38yvEno6`mh z0f#(j4k?BQ3v);@HJVP2QXZ=+i6<|FrPM8o2M|Td@kiL)F(TV$QD!p4<9vjW-@oe# zr-H1i@~8C>B_$Az&#G_mzzzBl1avKJ96Gd`lTU$a`&g8MRB2hN^t~O}*N%#MbR-H^ zxlOCg8e3)BnP*?0TWy}51*`nPJ^QY$GDl`lB56r#nkF(aCvjGv_YkG`4k5QT*krRZ zlfCU2D4z-~nG6MMzZPq+TkQjezHLKqHa7IUD?>j|0fUHa_lV5O;%*-`L0RI_%a0 zYn!5Q4pBHDbe#xoBM4u7&G}|O7)DJ%C7^QC%5Sv{OJyXn$p|wgp%yneGq-EOC>7wL z0@4~I?f0%HhGbNqTgnJX${*b^I1?*}irf}^20FhAoj(a|SGfTOuG9TwXx|U*zH{y5 zQR+&a&OdXW0UC0YHy|~ddgc+Oe6&77@??| zq(D6LC0nfA8|D)A7!@3qSAgR>0*UaTol9|gjVaub3u;me>0PuzYuO7l0 zfRhf4d_BMy;swC<7UH@(XkB_=7HXm%Nj*-(q9R9};hu6=MPeD2&un+Jrwh~X1z5;u zS7x&}MfG8OmbKIz1jE>B@K6I-)`91{K=iv@^bDqQ8(rXTOr^FSAj6qSV3Ta1se~N_ z!M?7z${(24RynH%;ARJ=wKhA{)HH)>-Nq=CtS$=O2Gc4a3I!{$2r|O7N zu`=biHr3jICa3fni^rH6~dNAg5Xij7h%P2nM6B=L~R3MzWn2|iNrFPnFyRZ zfOGqBCai^rnyL^fEi>NF06#cH!U7Zk*L>hQ?F3m04A> zK9%o6hCF6>P0C#6F@I9+y;k9<=3eI)Ol5BMY_Ss(bjKhT?xEBl&KJth$t&<= zq0@W{CGr!eDWn1Brs%A)Wj z?a|m7WD$^JVYFlL+$=?Wq>s1mo7y7?)PVeLd!jjptzn!2Az` zc?pli6m*jM68WumL@foh)8NFb`qjTBo~-X|BD5-?2FjVVx(tEf~Cb7VUlu4 zfCJaGoAq6eYl_QpN|)6XK&0`R^UcgdY8AAWZ={6U<{-B+dMhEgJPh`{d1T$3%zl1p2Uf$~|>l6g|F_G_{Bx>Z78=-W2*q^Y47xGO_HM{=&Y zrvP^TVcU7JhR}Sg>^yIiOCSTIngQkRfQEA^4+lhG21EnVe@fDdpw{<4nMe8$fie5Y=pG3J!GJ3+fv$tr4=T7<{1_ zw`GHPaG)P{42~fN-}q;8gtZwn0q{%?P)H{;0o9=brlN-hwv^K(2)B5&H`7MP;1WR$ z4i0`}2fynOT!*V|3c)3Y5F8M?PK34*fiJ!=eVd*olLRmd7~QmDUM+*vL_Gye!c0l1 zr9vP|wrf?Xq~Nat(i$V}_pYamgaS(Hk44pEWxOM)@>W$$NW{vK!?wk|0R&b7fs?>? zl~-WkIw;g^T3WGfX`ujAp{!SFEi=BzX24KA?r!^&2K$&v-mH3D*#<_5glX~~RdIpR zb0#feF&yn;Cl)Inr@WL*Q52Ix@i-_Rw8N$#)fDoyRj9Pe=F~iVw{=Rl1);O`}wB&d{I-=45oD(O{-i<6w1h!m{tl=C|JoQOZ|b3 zY?XX!klJwn!2CmdCRr@}T+YUVjoijeErU6kbt1jtS})z~=`JHZj%b2ZNdW2qb%|i1 z#af|mwx3f5r~}lsdMZ{H`R>Z>e)UM;4Get+guW2^E?L8_v!x3r$vidBsJVg2kuVwif5=wqid?2mDW0gJYwU* zOEsu9TPRSbw5p~qkfwwgs2K=_J}QBx>7g&4>AA!CQ(h0TCG3 z-u7)TVekZGd*;(>U_NEn=F_6lPRUGw`Ru5y9^UxEnjINkX_?bfu@BAYLWLf%pYz$z z(@wH5u{X<*d}zSJ2)tQ9V|Qpg8inq)(%HZ`o}6Ds+TR^K3CG>RvlKuuAqN23#tAtQ z?F>k8yrzUjb8L)TD+3LKd4(UkdZKac1YfF)D0VWvPz4Agi#rkRVj3qf)u=`D)tIxD zL1g)-Z`|=%2a|1dnD)oFUf(!=dDt;F(z&LBA|{WYu-sfyEc(_CE?qCB zHt9^*RDG}3W z)lkn)dKXnCUAepN|N22Beh}$YBAg6r^o^bB+I?cFhckCGmOIzT6{Rlck_zUM8mp^x zijw2t@;oh^Xi4^}3xjm87#VAx+=O^=C+@%*zw!H@omnndYl>6SpWLx~p0M1#_#ZEh z^YX9W+n3Hv4`=cNtz48X$KMD-cT}LX{23H5ebiKnKGcf8@?3pT&>r;^#6z6!G)c zzVw`M5&5wK#POBmggITFDf(M~;9e+O8uj`xb*Jx7N)Q&iNbXW>5b}iaC(76RM>KMU zw&D||;FruDxt?)qN5qmp;!AAWUfJGY=*)dl@BGl2e+Z5OS2dBjDar0U1!7RrO2y_r zc|=do)qVXuoidI;)HMw~-w*#Dy!v(c$09527<$?McI3mL*cHC0ydE?zY;|-26wM6uYEy`sT?rv|kJ85U_{`U`)p%at}s9SVy z*Zq9z4$|&VvN4_JlI{-q&xw6L*AUzd{|DzXLtwL^*d>hhq76=YwBFwoRB$ykT=woh zGSi|KBJofOfqRTdF7Wg}RRd>QJR_9b&5?+$Ze}8+#q8+oIz&+!tghf50)~KI;x@U&gr~kb4?9!9z`IhT^evbw8zGONr zw{1C{&+l()hdb)~|9pA(>HqdG_iBx+gHqqN{GfDoQ$9P{E4|;@-PQYj+uYsVukDwv z&Q7Ye67;dVdvNi-^u8Me{g1o5&(F_-!K8$i+hc~EO)q#ogU)uKfzS@x@8LAV z-V^G;w{5Exe0lfBr$_Vki{1*XC-cmBHN6w3ZN7ZkW#7U<%L&Ym>3u<;cE@kWP2k*C z%Lt&$+hNa>Muz7Z?9Csa{J?`t{sI~bZqKngo;&CZm+ye$`X~VhuU8^-|G#ZGJ7?beF$0j=c z{JDdPDhi59WTeWPBuj*RDD7Ywi2}UGpev?Gr1aN^(oZUDR1jmb+J}-TQcVa&MhEb( zUx}+{XWX+5KYQ|O*XWy(+Nm}t`%#1OHIw+|55=m%t8GS-+g8WPno(A(P`cVTTF}Kr-ZKsR3za|ZhHu8A@PVOY-v}}a z5rmZ;r0x){MDRx{f230BPXvD%?N}$Smu`P-+LlwQ8%&2}gMG{I+s13PW!Ras0`N=y zp!B&EzIjPEJ+y4I_S(-hGXQ64+0YpG-PWLIIzbx$li{P8@f#Vj&skhoWbeGXXl-R%A+J>JOV=yFqB)af^wc6{WOKU2{0h+0oy#ycRRu^spzrSLVX~uF-a%<7z|# z`5vsug(VbC>0C2kf?^$#M!I z?|$AtfSCnN`~CslKR6b=wz`Jrn*k~T^q6{PO_hS$CiUL+a8|KJY87g=R5goxpbir5RKp$OGN5m5|9BovB3YxW}J zP()Thg!OBAju8wz!@mEk#l8rvPB$=}2cre93%z?6UWHG@(tYazm97lW=$V1(m3-5K zproX?+n(wBzuuyfw0kH3FoVbv@MRJ?w{bG;DQj5_@yDjoo)^Y4Ql>+yz>TOmZsf{^ zui|4jaltXemBi4=5(rfkg(?UGg{lOctRxXdLP%7JDA8Z30f}16f+UF=xDpkMs*1ry zYZ4YDiU z+c$UPLN&v(3r~P%a9f4YnZ~TLKoQ(>S=M9?i?aBkB*Q&dL~Zh?%3IE?N)9AAA#pSq2VrqI|p%jM%t;w;Bm zjrNGxit;ACqNQbGvqM2qPn+gI)G}&GO*q>`{ z0t2ZEvM7l}R4F(m%$>qN+9@m$#1}Y)Bev@)>kh_SAqtR?2}hHizwL7hy-u?8T+j2S z@5%Q*x&9|PoczJNq8bPV=9^MlZ|7JzQ8h59w~VzVt#`vSWrYs!w^+~qmV`A zO}~-I+!b(ISWy*OQdI=5OJxhHia?piQZ$9ClAMlo5{@U|_2dm-#{Hy(3S3aJ6G{zp zfg{R2ORh7T#T^woq_j(#<}L~yM%r*PR+Mxba~Ub3(~LCnp5uIxA$$YpWI3EK5Fae< zCOFQw5a;Qzcn+MWXZ+%Gew1QCi~ottfDjSD7-fy95a~)vxYJnC1X05(rHCRG5^iRx zjAb+`$|91&{993f{mSzgg`TE(((oV^AV-81X0RM2E>{Mz9!~>W|{h1cIuE*!C`vlS4(S#)2v^XUukT zXqXC!Qc;t`9|Fi&+CZvQ@~LvW?6_!+EO(WR^XuQpuT|4&b820dTD2vpRb4%`zByU6 zEHiUv-NqzTTu%}zBZ0(bF(kSr%&PGul#}F4B-yTWid=R%MqHdCF_rTDg0eevYCgJtw=Uio@{cotq*O>7c)K=RPiq03j06A~5oMK74blcmwqv(l!7ulDWXQ3T#?H6(EJG-%pGvY7I~<0B zk`|Wr6#q6X;7uhTZrJNI?3!m-z}F=_&A_8Uy|Rs#$)1~%Yei{)ny71%szH-qbq6K8 z;5Cd!<*GGhp&CYOE99xLaC%(gj8!d?KpfZ|#cCLl;<{->6{2TRK|hcYOER9P5yG%1w(uIw6w}`gqms?utCahLMjnIl`a3I_>{cQFz(}1^W@qI;b=sUbYCikC zt{o;a#I_L_!?$HU_|ZN-wHtMe%x`7;?&R3j#Y(wyQa$LFPcLuKA%f%mhI5O;26y%A z8vCNzYw6vS%1O<>xN7wJc7s;*?%mge(RZOt*Z>5}_DQAu_1V5@^$wxo?d!?@-ceUS z?N=)Yr;je2Nq>PeT@M>H=yktIq;AW3)LBELa#`)38uc=gY89gSP5Sj{*r3- zPt7CkRX?pB==Jk`=(%^-(aYWb*8{uXsDL_^#<$b%9X;)a4O&NC(9ibo8b-AX7kUUi z*S_7NM*Y;kyDfJ?O$Yux8;2Lei#F>owhn>^2U6|k=*$q=!Dqa|5aUU|3rj(^OgMWSDi#6@<5IUa?z?&t2O8iY$Gt+C9ux6WjetMcT$F!0nN61 zR!>HJR*yq$QHWjk1FHv?=#{F2W`|ibPo5U$?Wr)uO9grqPk+)_CJ^L--sS{)>Z$`h zk&rDXEnWeKlh(qdHI48RpTVz zrV%fa#v!mNMbZ?avNFZt9w4g8Y-6{wW&p?v;+3Hmt_<~34(l^D>$m9c+ezn!+`hJZ zx368dvS-{pmJd$99t^80om9@DhXbR2fsDE~fJV}==+oo=u-a1la$F2**q|St_81!N zH_6LWtFw30)epbJb?n{s8oJI3K%E+OR@Z4*N-9&_sXnaRRAxP6+&((Ann$;xy2Fd6 zr`<_8smdM*mM?E`vV>GvJqpwu4(q|ie&z6dPd~ZQM+K^mK)s_Tk*@aald+Z zdQv&8|2QWNjE-1!sAKlu)$X5Fy=?18&w9H9ug8^P?J1*D{;nS#=(X_68=a}8*Bh0v zI@F!7!|PGkZSV?FtF91rWq3x<4A>qL(|a<#wG@hqa+|Q1BhPBb!`2DDCr7J&qXjxl z&M=BwBZ3Ph&yDY{$He#8ZHR=EIvZJUi3FVWkrtW;+vDQ_kzAFeVde)hdiGcFus zlf+eL25A!6#P33M6T-nASZ&ikb6bz*B$3Yb&oSv+%eR^lCyd1#2i3Iv@s-grC%%88 zPh!8okdJyGxCdkihL;h?8S$^C*{`|PmJ!opk}?i+O5@e^qBBoY>69(#09Js0f`VhY z=dddnoGFJd04_O`C+z&C9ILv5k8z{~-XPxFtz|yv9W8aP|X9UV>ww}+HR0h#xae+WiwD` zrqcifkm$C)Ac0MS&WH6#ft zM;tMn4F$Jdvx2N6+B2Ld!%sv9!xlY=&OrNWxK8(qGghArcg6&M@A&ZZ8vSiISH1lT)!8YjkTs>g?ybmz*WI9KiiC l)A=GHi6ov8g_27qkps*)tLU|F13JIF`+tdxp}N)#0RR@&q}l)g diff --git a/doc/design/ops/src/dist-graph.png b/doc/design/ops/src/dist-graph.png index 6f49dce07415025ade04bf0227f652c98540a056..d3c4175af5723ddf9e72044558434ce9768c41ec 100644 GIT binary patch literal 228040 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

@tElO{qc@^ujXBafOWX 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

$aB#r3 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