From a8342d073de789f69fda1a2c34d846c73e909623 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 18 Oct 2016 12:50:31 +0800 Subject: [PATCH 0001/1503] Add benchmark config and document --- benchmark/README.md | 168 ++ benchmark/caffe/image/alexnet.prototxt | 347 +++ benchmark/caffe/image/googlenet.prototxt | 2334 +++++++++++++++++ benchmark/caffe/image/run.sh | 30 + benchmark/caffe/image/run_multi.sh | 24 + .../caffe/image/smallnet_mnist_cifar.prototxt | 198 ++ benchmark/caffe/image/solver.prototxt | 10 + benchmark/figs/alexnet-4gpu.png | Bin 0 -> 81990 bytes benchmark/figs/googlenet-4gpu.png | Bin 0 -> 82569 bytes benchmark/figs/rnn_lstm_4gpus.png | Bin 0 -> 73243 bytes benchmark/figs/rnn_lstm_cls.png | Bin 0 -> 117634 bytes benchmark/paddle/image/alexnet.py | 57 + benchmark/paddle/image/googlenet.py | 147 ++ benchmark/paddle/image/provider.py | 24 + benchmark/paddle/image/run.sh | 54 + benchmark/paddle/image/run_multi.sh | 42 + .../paddle/image/smallnet_mnist_cifar.py | 47 + benchmark/paddle/rnn/imdb.py | 42 + benchmark/paddle/rnn/provider.py | 64 + benchmark/paddle/rnn/rnn.py | 42 + benchmark/paddle/rnn/run.sh | 38 + benchmark/paddle/rnn/run_multi.sh | 34 + benchmark/tensorflow/image/alexnet.py | 260 ++ .../tensorflow/image/alexnet_multi_gpu.py | 335 +++ benchmark/tensorflow/image/googlenet.py | 282 ++ .../tensorflow/image/googlenet_multi_gpu.py | 381 +++ benchmark/tensorflow/image/run.sh | 28 + benchmark/tensorflow/image/run_multi.sh | 22 + .../tensorflow/image/smallnet_mnist_cifar.py | 273 ++ benchmark/tensorflow/rnn/README.md | 5 + benchmark/tensorflow/rnn/reader.py | 90 + benchmark/tensorflow/rnn/rnn.py | 201 ++ benchmark/tensorflow/rnn/rnn_multi_gpu.py | 306 +++ benchmark/tensorflow/rnn/run.sh | 29 + benchmark/tensorflow/rnn/run_multi.sh | 28 + 35 files changed, 5942 insertions(+) create mode 100644 benchmark/README.md create mode 100644 benchmark/caffe/image/alexnet.prototxt create mode 100644 benchmark/caffe/image/googlenet.prototxt create mode 100755 benchmark/caffe/image/run.sh create mode 100755 benchmark/caffe/image/run_multi.sh create mode 100644 benchmark/caffe/image/smallnet_mnist_cifar.prototxt create mode 100644 benchmark/caffe/image/solver.prototxt create mode 100644 benchmark/figs/alexnet-4gpu.png create mode 100644 benchmark/figs/googlenet-4gpu.png create mode 100644 benchmark/figs/rnn_lstm_4gpus.png create mode 100644 benchmark/figs/rnn_lstm_cls.png create mode 100644 benchmark/paddle/image/alexnet.py create mode 100644 benchmark/paddle/image/googlenet.py create mode 100644 benchmark/paddle/image/provider.py create mode 100755 benchmark/paddle/image/run.sh create mode 100755 benchmark/paddle/image/run_multi.sh create mode 100644 benchmark/paddle/image/smallnet_mnist_cifar.py create mode 100755 benchmark/paddle/rnn/imdb.py create mode 100644 benchmark/paddle/rnn/provider.py create mode 100755 benchmark/paddle/rnn/rnn.py create mode 100755 benchmark/paddle/rnn/run.sh create mode 100755 benchmark/paddle/rnn/run_multi.sh create mode 100644 benchmark/tensorflow/image/alexnet.py create mode 100644 benchmark/tensorflow/image/alexnet_multi_gpu.py create mode 100644 benchmark/tensorflow/image/googlenet.py create mode 100644 benchmark/tensorflow/image/googlenet_multi_gpu.py create mode 100755 benchmark/tensorflow/image/run.sh create mode 100755 benchmark/tensorflow/image/run_multi.sh create mode 100644 benchmark/tensorflow/image/smallnet_mnist_cifar.py create mode 100644 benchmark/tensorflow/rnn/README.md create mode 100755 benchmark/tensorflow/rnn/reader.py create mode 100755 benchmark/tensorflow/rnn/rnn.py create mode 100755 benchmark/tensorflow/rnn/rnn_multi_gpu.py create mode 100755 benchmark/tensorflow/rnn/run.sh create mode 100755 benchmark/tensorflow/rnn/run_multi.sh diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000000..8d2cf5737d --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,168 @@ +# Benchmark + +Machine: + +- CPU: 12-core Intel(R) Xeon(R) CPU E5-2620 v2 @2.10GHz +- GPU: Tesla K40m +- cuDNN: v5.1 +- system: Docker 1.12.1, all platform are tested in docker environment. + +Platform: + +- PaddlePaddle: +- Tensorflow: gcr.io/tensorflow/tensorflow:0.11.0rc0-gpu +- Caffe: + +Several convolutional neural networks and recurrent neural network are used to test. + +## Image + +### Benchmark Model + +AlexNet, GooleNet and a small network which refer the config of cifar10 in Caffe are used. + +- [AlexNet](https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet): but the group size is one. + +- [GoogleNet](https://github.com/BVLC/caffe/tree/master/models/bvlc_googlenet): but remove loss1 and loss2 when testing benchmark. + +- [SmallNet](https://github.com/BVLC/caffe/blob/master/examples/cifar10/cifar10\_quick\_train\_test.prototxt) + + +### Singe-GPU + +- AlexNet: input - 3 * 227 * 227, Time: ms/batch + +| BatchSize | 64 | 128 | 256 | 512 | +|--------------|-----| -----| ------| -----| +| PaddlePaddle | 195 | 334 | 602 | 1629 | +| TensorFlow | 223 | 364 | 645 | 1235 | +| Caffe | 324 | 627 | 1232 | 2513 | + +##### Notation + +All platforms use cuDnn-v5.1. You might see that caffe is slower, because the workspace limit size is 8 * 1024 * 1024 in Caffe's cuDnn-conv interface. This size is larger in PaddlePaddle and TensorFlow. Caffe will be faster if increasing the workspace limit size. + +- GoogletNet: input - 3 * 224 * 224, Time: ms/batch + + +| BatchSize | 64 | 128 | 256 | +|--------------|-------| -------| --------| +| PaddlePaddle | 613 | 1149 | 2348 | +| TensorFlow | 644 | 1176 | 2219 | +| Caffe | 694 | 1364 | out of memory | + +- SmallNet: input - 3 * 32 * 32, Time ms/batch + +| BatchSize | 64 | 128 | 256 | 512 | +|--------------|--------| -------- | --------|---------| +| PaddlePaddle | 10.463 | 18.184 | 33.113 | 63.039 | +| TensorFlow | 9 | 15 | 28 | 59 | +| Caffe | 9.373 | 16.6606 | 31.4797 | 59.719 | + +##### Notation + +All the tests in caffe use `caffe time` to execute, which is not including the parameter updating process. But the time in PaddlePaddle and TensorFlow contains it. + +In Tensorflow, they implement algorithm searching method instead of using the algorithm searching interface in cuDNN. + +### Multi-GPU: 4 GPUs + +- AlexNet, ms / batch + +| totoal-BatchSize | 128 * 4 | 256 * 4 | +|------------------|----------| -----------| +| PaddlePaddle | 347 | 622 | +| TensorFlow | 377 | 675 | +| Caffe | 1229 | 2435 | + +For example, if `totoal-BatchSize = 128 * 4`, the speed is calculated by + +``` + time_at_1gpu_batch_128 * 4 / time_at_4gpu_total_batch_512 += (334 * 4)/347 += 3.85 +``` + + + + +- GooleNet, ms / batch + +| totoal-BatchSize | 128 * 4 | 256 * 4 | +|-------------------|--------------| ----------- | +| PaddlePaddle | 1178 | 2367 | +| TensorFlow | 1210 | 2292 | +| Caffe | 2007 | out of memory | + + + + +## RNN +We use lstm network for text classfication to test benchmark. + +### Dataset +- [IMDB](http://www.iro.umontreal.ca/~lisa/deep/data/imdb.pkl) +- Sequence legth=100, in fact, PaddlePaddle support training with variable-length sequence. But TensorFlow need to pad, in order to compare, we also pad sequence length to 100 in PaddlePaddle. +- Dictionary size=30000 +- Peephole connection is used in `lstmemory` by default in PaddlePaddle. It is also configured in TensorFlow. + +### Single GPU + +#### LSTM in Text Classification + +Testing network for different hidden size, batch size with `2 lstm layer + fc` network. + +- Batch size = 64, ms / batch + +| hidden_size | 256 | 512 | 1280 | +|--------------|-------| -------| --------| +| PaddlePaddle | 83 | 184 | 641 | +| TensorFlow | 175 | 280 | 818 | + +- Batch size = 128, ms / batch + +| hidden_size | 256 | 512 | 1280 | +|--------------|------- | -------| --------| +| PaddlePaddle | 110 | 261 | 1007 | +| TensorFlow | 181 | 361 | 1237 | + + +- Batch size = 256, ms / batch + +| hidden_size | 256 | 512 | 1280 | +|--------------|-------| -------| --------| +| PaddlePaddle | 170 | 414 | 1655 | +| TensorFlow | 238 | 536 | 1905 | + + + +#### Seq2Seq + +The benchmark of sequence-to-sequence network will be add later. + + +### Multi GPU: 4 GPUs + +#### LSTM in Text Classification + +- hidden_size = 256, ms / batch + +| batch_size | 256 | 512 | +|--------------| -------| --------| +| PaddlePaddle | 90 | 118 | +| TensorFlow | 226 | 118 | + + +- hidden_size = 512, ms / batch + +| batch_size | 256 | 512 | +|--------------| -------| --------| +| PaddlePaddle | 189 | 268 | +| TensorFlow | 297 | 383 | + + + + +#### Seq2Seq + +The benchmark of sequence-to-sequence network will be add later. diff --git a/benchmark/caffe/image/alexnet.prototxt b/benchmark/caffe/image/alexnet.prototxt new file mode 100644 index 0000000000..aca184ddaf --- /dev/null +++ b/benchmark/caffe/image/alexnet.prototxt @@ -0,0 +1,347 @@ +name: "alexnet" +input: "data" +input_dim: 64 +input_dim: 3 +input_dim: 227 +input_dim: 227 +input: "label" +input_dim: 64 +input_dim: 1 +input_dim: 1 +input_dim: 1 +force_backward: true +layer { + name: "conv1" + type: "Convolution" + bottom: "data" + top: "conv1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 96 + kernel_size: 11 + stride: 4 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu1" + type: "ReLU" + bottom: "conv1" + top: "conv1" +} +layer { + name: "norm1" + type: "LRN" + bottom: "conv1" + top: "norm1" + lrn_param { + local_size: 5 + alpha: 0.0001 + beta: 0.75 + } +} +layer { + name: "pool1" + type: "Pooling" + bottom: "norm1" + top: "pool1" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "conv2" + type: "Convolution" + bottom: "pool1" + top: "conv2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 2 + kernel_size: 5 + group: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0.1 + } + } +} +layer { + name: "relu2" + type: "ReLU" + bottom: "conv2" + top: "conv2" +} +layer { + name: "norm2" + type: "LRN" + bottom: "conv2" + top: "norm2" + lrn_param { + local_size: 5 + alpha: 0.0001 + beta: 0.75 + } +} +layer { + name: "pool2" + type: "Pooling" + bottom: "norm2" + top: "pool2" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "conv3" + type: "Convolution" + bottom: "pool2" + top: "conv3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 384 + pad: 1 + kernel_size: 3 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu3" + type: "ReLU" + bottom: "conv3" + top: "conv3" +} +layer { + name: "conv4" + type: "Convolution" + bottom: "conv3" + top: "conv4" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 384 + pad: 1 + kernel_size: 3 + group: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0.1 + } + } +} +layer { + name: "relu4" + type: "ReLU" + bottom: "conv4" + top: "conv4" +} +layer { + name: "conv5" + type: "Convolution" + bottom: "conv4" + top: "conv5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + group: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0.1 + } + } +} +layer { + name: "relu5" + type: "ReLU" + bottom: "conv5" + top: "conv5" +} +layer { + name: "pool5" + type: "Pooling" + bottom: "conv5" + top: "pool5" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "fc6" + type: "InnerProduct" + bottom: "pool5" + top: "fc6" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + inner_product_param { + num_output: 4096 + weight_filler { + type: "gaussian" + std: 0.005 + } + bias_filler { + type: "constant" + value: 0.1 + } + } +} +layer { + name: "relu6" + type: "ReLU" + bottom: "fc6" + top: "fc6" +} +layer { + name: "drop6" + type: "Dropout" + bottom: "fc6" + top: "fc6" + dropout_param { + dropout_ratio: 0.5 + } +} +layer { + name: "fc7" + type: "InnerProduct" + bottom: "fc6" + top: "fc7" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + inner_product_param { + num_output: 4096 + weight_filler { + type: "gaussian" + std: 0.005 + } + bias_filler { + type: "constant" + value: 0.1 + } + } +} +layer { + name: "relu7" + type: "ReLU" + bottom: "fc7" + top: "fc7" +} +layer { + name: "drop7" + type: "Dropout" + bottom: "fc7" + top: "fc7" + dropout_param { + dropout_ratio: 0.5 + } +} +layer { + name: "fc8" + type: "InnerProduct" + bottom: "fc7" + top: "fc8" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + inner_product_param { + num_output: 1000 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "loss" + type: "SoftmaxWithLoss" + bottom: "fc8" + bottom: "label" + top: "loss" +} diff --git a/benchmark/caffe/image/googlenet.prototxt b/benchmark/caffe/image/googlenet.prototxt new file mode 100644 index 0000000000..c5f3b4fe3e --- /dev/null +++ b/benchmark/caffe/image/googlenet.prototxt @@ -0,0 +1,2334 @@ +name: "googlenet" +input: "data" +input_dim: 128 +input_dim: 3 +input_dim: 224 +input_dim: 224 +input: "label" +input_dim: 128 +input_dim: 1 +input_dim: 1 +input_dim: 1 +layer { + name: "conv1/7x7_s2" + type: "Convolution" + bottom: "data" + top: "conv1/7x7_s2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 3 + kernel_size: 7 + stride: 2 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "conv1/relu_7x7" + type: "ReLU" + bottom: "conv1/7x7_s2" + top: "conv1/7x7_s2" +} +layer { + name: "pool1/3x3_s2" + type: "Pooling" + bottom: "conv1/7x7_s2" + top: "pool1/3x3_s2" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +#layer { +# name: "pool1/norm1" +# type: "LRN" +# bottom: "pool1/3x3_s2" +# top: "pool1/norm1" +# lrn_param { +# local_size: 5 +# alpha: 0.0001 +# beta: 0.75 +# } +#} +layer { + name: "conv2/3x3_reduce" + type: "Convolution" +# bottom: "pool1/norm1" + bottom: "pool1/3x3_s2" + top: "conv2/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "conv2/relu_3x3_reduce" + type: "ReLU" + bottom: "conv2/3x3_reduce" + top: "conv2/3x3_reduce" +} +layer { + name: "conv2/3x3" + type: "Convolution" + bottom: "conv2/3x3_reduce" + top: "conv2/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 192 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "conv2/relu_3x3" + type: "ReLU" + bottom: "conv2/3x3" + top: "conv2/3x3" +} +#layer { +# name: "conv2/norm2" +# type: "LRN" +# bottom: "conv2/3x3" +# top: "conv2/norm2" +# lrn_param { +# local_size: 5 +# alpha: 0.0001 +# beta: 0.75 +# } +#} +layer { + name: "pool2/3x3_s2" + type: "Pooling" +# bottom: "conv2/norm2" + bottom: "conv2/3x3" + top: "pool2/3x3_s2" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "inception_3a/1x1" + type: "Convolution" + bottom: "pool2/3x3_s2" + top: "inception_3a/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_1x1" + type: "ReLU" + bottom: "inception_3a/1x1" + top: "inception_3a/1x1" +} +layer { + name: "inception_3a/3x3_reduce" + type: "Convolution" + bottom: "pool2/3x3_s2" + top: "inception_3a/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 96 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_3a/3x3_reduce" + top: "inception_3a/3x3_reduce" +} +layer { + name: "inception_3a/3x3" + type: "Convolution" + bottom: "inception_3a/3x3_reduce" + top: "inception_3a/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_3x3" + type: "ReLU" + bottom: "inception_3a/3x3" + top: "inception_3a/3x3" +} +layer { + name: "inception_3a/5x5_reduce" + type: "Convolution" + bottom: "pool2/3x3_s2" + top: "inception_3a/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 16 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_3a/5x5_reduce" + top: "inception_3a/5x5_reduce" +} +layer { + name: "inception_3a/5x5" + type: "Convolution" + bottom: "inception_3a/5x5_reduce" + top: "inception_3a/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_5x5" + type: "ReLU" + bottom: "inception_3a/5x5" + top: "inception_3a/5x5" +} +layer { + name: "inception_3a/pool" + type: "Pooling" + bottom: "pool2/3x3_s2" + top: "inception_3a/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_3a/pool_proj" + type: "Convolution" + bottom: "inception_3a/pool" + top: "inception_3a/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3a/relu_pool_proj" + type: "ReLU" + bottom: "inception_3a/pool_proj" + top: "inception_3a/pool_proj" +} +layer { + name: "inception_3a/output" + type: "Concat" + bottom: "inception_3a/1x1" + bottom: "inception_3a/3x3" + bottom: "inception_3a/5x5" + bottom: "inception_3a/pool_proj" + top: "inception_3a/output" +} +layer { + name: "inception_3b/1x1" + type: "Convolution" + bottom: "inception_3a/output" + top: "inception_3b/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_1x1" + type: "ReLU" + bottom: "inception_3b/1x1" + top: "inception_3b/1x1" +} +layer { + name: "inception_3b/3x3_reduce" + type: "Convolution" + bottom: "inception_3a/output" + top: "inception_3b/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_3b/3x3_reduce" + top: "inception_3b/3x3_reduce" +} +layer { + name: "inception_3b/3x3" + type: "Convolution" + bottom: "inception_3b/3x3_reduce" + top: "inception_3b/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 192 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_3x3" + type: "ReLU" + bottom: "inception_3b/3x3" + top: "inception_3b/3x3" +} +layer { + name: "inception_3b/5x5_reduce" + type: "Convolution" + bottom: "inception_3a/output" + top: "inception_3b/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_3b/5x5_reduce" + top: "inception_3b/5x5_reduce" +} +layer { + name: "inception_3b/5x5" + type: "Convolution" + bottom: "inception_3b/5x5_reduce" + top: "inception_3b/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 96 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_5x5" + type: "ReLU" + bottom: "inception_3b/5x5" + top: "inception_3b/5x5" +} +layer { + name: "inception_3b/pool" + type: "Pooling" + bottom: "inception_3a/output" + top: "inception_3b/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_3b/pool_proj" + type: "Convolution" + bottom: "inception_3b/pool" + top: "inception_3b/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_3b/relu_pool_proj" + type: "ReLU" + bottom: "inception_3b/pool_proj" + top: "inception_3b/pool_proj" +} +layer { + name: "inception_3b/output" + type: "Concat" + bottom: "inception_3b/1x1" + bottom: "inception_3b/3x3" + bottom: "inception_3b/5x5" + bottom: "inception_3b/pool_proj" + top: "inception_3b/output" +} +layer { + name: "pool3/3x3_s2" + type: "Pooling" + bottom: "inception_3b/output" + top: "pool3/3x3_s2" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "inception_4a/1x1" + type: "Convolution" + bottom: "pool3/3x3_s2" + top: "inception_4a/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 192 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_1x1" + type: "ReLU" + bottom: "inception_4a/1x1" + top: "inception_4a/1x1" +} +layer { + name: "inception_4a/3x3_reduce" + type: "Convolution" + bottom: "pool3/3x3_s2" + top: "inception_4a/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 96 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_4a/3x3_reduce" + top: "inception_4a/3x3_reduce" +} +layer { + name: "inception_4a/3x3" + type: "Convolution" + bottom: "inception_4a/3x3_reduce" + top: "inception_4a/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 208 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_3x3" + type: "ReLU" + bottom: "inception_4a/3x3" + top: "inception_4a/3x3" +} +layer { + name: "inception_4a/5x5_reduce" + type: "Convolution" + bottom: "pool3/3x3_s2" + top: "inception_4a/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 16 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_4a/5x5_reduce" + top: "inception_4a/5x5_reduce" +} +layer { + name: "inception_4a/5x5" + type: "Convolution" + bottom: "inception_4a/5x5_reduce" + top: "inception_4a/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 48 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_5x5" + type: "ReLU" + bottom: "inception_4a/5x5" + top: "inception_4a/5x5" +} +layer { + name: "inception_4a/pool" + type: "Pooling" + bottom: "pool3/3x3_s2" + top: "inception_4a/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_4a/pool_proj" + type: "Convolution" + bottom: "inception_4a/pool" + top: "inception_4a/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4a/relu_pool_proj" + type: "ReLU" + bottom: "inception_4a/pool_proj" + top: "inception_4a/pool_proj" +} +layer { + name: "inception_4a/output" + type: "Concat" + bottom: "inception_4a/1x1" + bottom: "inception_4a/3x3" + bottom: "inception_4a/5x5" + bottom: "inception_4a/pool_proj" + top: "inception_4a/output" +} +#layer { +# name: "loss1/ave_pool" +# type: "Pooling" +# bottom: "inception_4a/output" +# top: "loss1/ave_pool" +# pooling_param { +# pool: AVE +# kernel_size: 5 +# stride: 3 +# } +#} +#layer { +# name: "loss1/conv" +# type: "Convolution" +# bottom: "loss1/ave_pool" +# top: "loss1/conv" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# convolution_param { +# num_output: 128 +# kernel_size: 1 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0.2 +# } +# } +#} +#layer { +# name: "loss1/relu_conv" +# type: "ReLU" +# bottom: "loss1/conv" +# top: "loss1/conv" +#} +#layer { +# name: "loss1/fc" +# type: "InnerProduct" +# bottom: "loss1/conv" +# top: "loss1/fc" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# inner_product_param { +# num_output: 1024 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0.2 +# } +# } +#} +#layer { +# name: "loss1/relu_fc" +# type: "ReLU" +# bottom: "loss1/fc" +# top: "loss1/fc" +#} +#layer { +# name: "loss1/drop_fc" +# type: "Dropout" +# bottom: "loss1/fc" +# top: "loss1/fc" +# dropout_param { +# dropout_ratio: 0.7 +# } +#} +#layer { +# name: "loss1/classifier" +# type: "InnerProduct" +# bottom: "loss1/fc" +# top: "loss1/classifier" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# inner_product_param { +# num_output: 1000 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0 +# } +# } +#} +#layer { +# name: "loss1/loss" +# type: "SoftmaxWithLoss" +# bottom: "loss1/classifier" +# bottom: "label" +# top: "loss1/loss1" +# loss_weight: 0.3 +#} +layer { + name: "inception_4b/1x1" + type: "Convolution" + bottom: "inception_4a/output" + top: "inception_4b/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 160 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_1x1" + type: "ReLU" + bottom: "inception_4b/1x1" + top: "inception_4b/1x1" +} +layer { + name: "inception_4b/3x3_reduce" + type: "Convolution" + bottom: "inception_4a/output" + top: "inception_4b/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 112 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_4b/3x3_reduce" + top: "inception_4b/3x3_reduce" +} +layer { + name: "inception_4b/3x3" + type: "Convolution" + bottom: "inception_4b/3x3_reduce" + top: "inception_4b/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 224 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_3x3" + type: "ReLU" + bottom: "inception_4b/3x3" + top: "inception_4b/3x3" +} +layer { + name: "inception_4b/5x5_reduce" + type: "Convolution" + bottom: "inception_4a/output" + top: "inception_4b/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 24 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_4b/5x5_reduce" + top: "inception_4b/5x5_reduce" +} +layer { + name: "inception_4b/5x5" + type: "Convolution" + bottom: "inception_4b/5x5_reduce" + top: "inception_4b/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_5x5" + type: "ReLU" + bottom: "inception_4b/5x5" + top: "inception_4b/5x5" +} +layer { + name: "inception_4b/pool" + type: "Pooling" + bottom: "inception_4a/output" + top: "inception_4b/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_4b/pool_proj" + type: "Convolution" + bottom: "inception_4b/pool" + top: "inception_4b/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4b/relu_pool_proj" + type: "ReLU" + bottom: "inception_4b/pool_proj" + top: "inception_4b/pool_proj" +} +layer { + name: "inception_4b/output" + type: "Concat" + bottom: "inception_4b/1x1" + bottom: "inception_4b/3x3" + bottom: "inception_4b/5x5" + bottom: "inception_4b/pool_proj" + top: "inception_4b/output" +} +layer { + name: "inception_4c/1x1" + type: "Convolution" + bottom: "inception_4b/output" + top: "inception_4c/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_1x1" + type: "ReLU" + bottom: "inception_4c/1x1" + top: "inception_4c/1x1" +} +layer { + name: "inception_4c/3x3_reduce" + type: "Convolution" + bottom: "inception_4b/output" + top: "inception_4c/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_4c/3x3_reduce" + top: "inception_4c/3x3_reduce" +} +layer { + name: "inception_4c/3x3" + type: "Convolution" + bottom: "inception_4c/3x3_reduce" + top: "inception_4c/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_3x3" + type: "ReLU" + bottom: "inception_4c/3x3" + top: "inception_4c/3x3" +} +layer { + name: "inception_4c/5x5_reduce" + type: "Convolution" + bottom: "inception_4b/output" + top: "inception_4c/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 24 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_4c/5x5_reduce" + top: "inception_4c/5x5_reduce" +} +layer { + name: "inception_4c/5x5" + type: "Convolution" + bottom: "inception_4c/5x5_reduce" + top: "inception_4c/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_5x5" + type: "ReLU" + bottom: "inception_4c/5x5" + top: "inception_4c/5x5" +} +layer { + name: "inception_4c/pool" + type: "Pooling" + bottom: "inception_4b/output" + top: "inception_4c/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_4c/pool_proj" + type: "Convolution" + bottom: "inception_4c/pool" + top: "inception_4c/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4c/relu_pool_proj" + type: "ReLU" + bottom: "inception_4c/pool_proj" + top: "inception_4c/pool_proj" +} +layer { + name: "inception_4c/output" + type: "Concat" + bottom: "inception_4c/1x1" + bottom: "inception_4c/3x3" + bottom: "inception_4c/5x5" + bottom: "inception_4c/pool_proj" + top: "inception_4c/output" +} +layer { + name: "inception_4d/1x1" + type: "Convolution" + bottom: "inception_4c/output" + top: "inception_4d/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 112 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_1x1" + type: "ReLU" + bottom: "inception_4d/1x1" + top: "inception_4d/1x1" +} +layer { + name: "inception_4d/3x3_reduce" + type: "Convolution" + bottom: "inception_4c/output" + top: "inception_4d/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 144 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_4d/3x3_reduce" + top: "inception_4d/3x3_reduce" +} +layer { + name: "inception_4d/3x3" + type: "Convolution" + bottom: "inception_4d/3x3_reduce" + top: "inception_4d/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 288 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_3x3" + type: "ReLU" + bottom: "inception_4d/3x3" + top: "inception_4d/3x3" +} +layer { + name: "inception_4d/5x5_reduce" + type: "Convolution" + bottom: "inception_4c/output" + top: "inception_4d/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_4d/5x5_reduce" + top: "inception_4d/5x5_reduce" +} +layer { + name: "inception_4d/5x5" + type: "Convolution" + bottom: "inception_4d/5x5_reduce" + top: "inception_4d/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_5x5" + type: "ReLU" + bottom: "inception_4d/5x5" + top: "inception_4d/5x5" +} +layer { + name: "inception_4d/pool" + type: "Pooling" + bottom: "inception_4c/output" + top: "inception_4d/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_4d/pool_proj" + type: "Convolution" + bottom: "inception_4d/pool" + top: "inception_4d/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4d/relu_pool_proj" + type: "ReLU" + bottom: "inception_4d/pool_proj" + top: "inception_4d/pool_proj" +} +layer { + name: "inception_4d/output" + type: "Concat" + bottom: "inception_4d/1x1" + bottom: "inception_4d/3x3" + bottom: "inception_4d/5x5" + bottom: "inception_4d/pool_proj" + top: "inception_4d/output" +} +#layer { +# name: "loss2/ave_pool" +# type: "Pooling" +# bottom: "inception_4d/output" +# top: "loss2/ave_pool" +# pooling_param { +# pool: AVE +# kernel_size: 5 +# stride: 3 +# } +#} +#layer { +# name: "loss2/conv" +# type: "Convolution" +# bottom: "loss2/ave_pool" +# top: "loss2/conv" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# convolution_param { +# num_output: 128 +# kernel_size: 1 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0.2 +# } +# } +#} +#layer { +# name: "loss2/relu_conv" +# type: "ReLU" +# bottom: "loss2/conv" +# top: "loss2/conv" +#} +#layer { +# name: "loss2/fc" +# type: "InnerProduct" +# bottom: "loss2/conv" +# top: "loss2/fc" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# inner_product_param { +# num_output: 1024 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0.2 +# } +# } +#} +#layer { +# name: "loss2/relu_fc" +# type: "ReLU" +# bottom: "loss2/fc" +# top: "loss2/fc" +#} +#layer { +# name: "loss2/drop_fc" +# type: "Dropout" +# bottom: "loss2/fc" +# top: "loss2/fc" +# dropout_param { +# dropout_ratio: 0.7 +# } +#} +#layer { +# name: "loss2/classifier" +# type: "InnerProduct" +# bottom: "loss2/fc" +# top: "loss2/classifier" +# param { +# lr_mult: 1 +# decay_mult: 1 +# } +# param { +# lr_mult: 2 +# decay_mult: 0 +# } +# inner_product_param { +# num_output: 1000 +# weight_filler { +# type: "xavier" +# } +# bias_filler { +# type: "constant" +# value: 0 +# } +# } +#} +#layer { +# name: "loss2/loss" +# type: "SoftmaxWithLoss" +# bottom: "loss2/classifier" +# bottom: "label" +# top: "loss2/loss1" +# loss_weight: 0.3 +#} +layer { + name: "inception_4e/1x1" + type: "Convolution" + bottom: "inception_4d/output" + top: "inception_4e/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_1x1" + type: "ReLU" + bottom: "inception_4e/1x1" + top: "inception_4e/1x1" +} +layer { + name: "inception_4e/3x3_reduce" + type: "Convolution" + bottom: "inception_4d/output" + top: "inception_4e/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 160 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_4e/3x3_reduce" + top: "inception_4e/3x3_reduce" +} +layer { + name: "inception_4e/3x3" + type: "Convolution" + bottom: "inception_4e/3x3_reduce" + top: "inception_4e/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 320 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_3x3" + type: "ReLU" + bottom: "inception_4e/3x3" + top: "inception_4e/3x3" +} +layer { + name: "inception_4e/5x5_reduce" + type: "Convolution" + bottom: "inception_4d/output" + top: "inception_4e/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_4e/5x5_reduce" + top: "inception_4e/5x5_reduce" +} +layer { + name: "inception_4e/5x5" + type: "Convolution" + bottom: "inception_4e/5x5_reduce" + top: "inception_4e/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_5x5" + type: "ReLU" + bottom: "inception_4e/5x5" + top: "inception_4e/5x5" +} +layer { + name: "inception_4e/pool" + type: "Pooling" + bottom: "inception_4d/output" + top: "inception_4e/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_4e/pool_proj" + type: "Convolution" + bottom: "inception_4e/pool" + top: "inception_4e/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_4e/relu_pool_proj" + type: "ReLU" + bottom: "inception_4e/pool_proj" + top: "inception_4e/pool_proj" +} +layer { + name: "inception_4e/output" + type: "Concat" + bottom: "inception_4e/1x1" + bottom: "inception_4e/3x3" + bottom: "inception_4e/5x5" + bottom: "inception_4e/pool_proj" + top: "inception_4e/output" +} +layer { + name: "pool4/3x3_s2" + type: "Pooling" + bottom: "inception_4e/output" + top: "pool4/3x3_s2" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "inception_5a/1x1" + type: "Convolution" + bottom: "pool4/3x3_s2" + top: "inception_5a/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_1x1" + type: "ReLU" + bottom: "inception_5a/1x1" + top: "inception_5a/1x1" +} +layer { + name: "inception_5a/3x3_reduce" + type: "Convolution" + bottom: "pool4/3x3_s2" + top: "inception_5a/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 160 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_5a/3x3_reduce" + top: "inception_5a/3x3_reduce" +} +layer { + name: "inception_5a/3x3" + type: "Convolution" + bottom: "inception_5a/3x3_reduce" + top: "inception_5a/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 320 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_3x3" + type: "ReLU" + bottom: "inception_5a/3x3" + top: "inception_5a/3x3" +} +layer { + name: "inception_5a/5x5_reduce" + type: "Convolution" + bottom: "pool4/3x3_s2" + top: "inception_5a/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_5a/5x5_reduce" + top: "inception_5a/5x5_reduce" +} +layer { + name: "inception_5a/5x5" + type: "Convolution" + bottom: "inception_5a/5x5_reduce" + top: "inception_5a/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_5x5" + type: "ReLU" + bottom: "inception_5a/5x5" + top: "inception_5a/5x5" +} +layer { + name: "inception_5a/pool" + type: "Pooling" + bottom: "pool4/3x3_s2" + top: "inception_5a/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_5a/pool_proj" + type: "Convolution" + bottom: "inception_5a/pool" + top: "inception_5a/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5a/relu_pool_proj" + type: "ReLU" + bottom: "inception_5a/pool_proj" + top: "inception_5a/pool_proj" +} +layer { + name: "inception_5a/output" + type: "Concat" + bottom: "inception_5a/1x1" + bottom: "inception_5a/3x3" + bottom: "inception_5a/5x5" + bottom: "inception_5a/pool_proj" + top: "inception_5a/output" +} +layer { + name: "inception_5b/1x1" + type: "Convolution" + bottom: "inception_5a/output" + top: "inception_5b/1x1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 384 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_1x1" + type: "ReLU" + bottom: "inception_5b/1x1" + top: "inception_5b/1x1" +} +layer { + name: "inception_5b/3x3_reduce" + type: "Convolution" + bottom: "inception_5a/output" + top: "inception_5b/3x3_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 192 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_3x3_reduce" + type: "ReLU" + bottom: "inception_5b/3x3_reduce" + top: "inception_5b/3x3_reduce" +} +layer { + name: "inception_5b/3x3" + type: "Convolution" + bottom: "inception_5b/3x3_reduce" + top: "inception_5b/3x3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 384 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_3x3" + type: "ReLU" + bottom: "inception_5b/3x3" + top: "inception_5b/3x3" +} +layer { + name: "inception_5b/5x5_reduce" + type: "Convolution" + bottom: "inception_5a/output" + top: "inception_5b/5x5_reduce" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 48 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_5x5_reduce" + type: "ReLU" + bottom: "inception_5b/5x5_reduce" + top: "inception_5b/5x5_reduce" +} +layer { + name: "inception_5b/5x5" + type: "Convolution" + bottom: "inception_5b/5x5_reduce" + top: "inception_5b/5x5" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 2 + kernel_size: 5 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_5x5" + type: "ReLU" + bottom: "inception_5b/5x5" + top: "inception_5b/5x5" +} +layer { + name: "inception_5b/pool" + type: "Pooling" + bottom: "inception_5a/output" + top: "inception_5b/pool" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 1 + pad: 1 + } +} +layer { + name: "inception_5b/pool_proj" + type: "Convolution" + bottom: "inception_5b/pool" + top: "inception_5b/pool_proj" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0.2 + } + } +} +layer { + name: "inception_5b/relu_pool_proj" + type: "ReLU" + bottom: "inception_5b/pool_proj" + top: "inception_5b/pool_proj" +} +layer { + name: "inception_5b/output" + type: "Concat" + bottom: "inception_5b/1x1" + bottom: "inception_5b/3x3" + bottom: "inception_5b/5x5" + bottom: "inception_5b/pool_proj" + top: "inception_5b/output" +} +layer { + name: "pool5/7x7_s1" + type: "Pooling" + bottom: "inception_5b/output" + top: "pool5/7x7_s1" + pooling_param { + pool: AVE + kernel_size: 7 + stride: 1 + } +} +layer { + name: "pool5/drop_7x7_s1" + type: "Dropout" + bottom: "pool5/7x7_s1" + top: "pool5/7x7_s1" + dropout_param { + dropout_ratio: 0.4 + } +} +layer { + name: "loss3/classifier" + type: "InnerProduct" + bottom: "pool5/7x7_s1" + top: "loss3/classifier" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + inner_product_param { + num_output: 1000 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "loss3/loss3" + type: "SoftmaxWithLoss" + bottom: "loss3/classifier" + bottom: "label" + top: "loss3/loss3" + loss_weight: 1 +} diff --git a/benchmark/caffe/image/run.sh b/benchmark/caffe/image/run.sh new file mode 100755 index 0000000000..aa9ac20ca5 --- /dev/null +++ b/benchmark/caffe/image/run.sh @@ -0,0 +1,30 @@ +set -e + +function test() { + cfg=$1 + batch=$2 + prefix=$3 + sed -i "/input: \"data\"/{n;s/^input_dim.*/input_dim: $batch/g}" $cfg + sed -i "/input: \"label\"/{n;s/^input_dim.*/input_dim: $batch/g}" $cfg + caffe time --model=$cfg --iterations=50 --gpu 0 > logs/$prefix-1gpu-batch${batch}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +# alexnet +test alexnet.prototxt 64 alexnet +test alexnet.prototxt 128 alexnet +test alexnet.prototxt 256 alexnet +test alexnet.prototxt 512 alexnet + +# googlenet +test googlenet.prototxt 64 googlenet +test googlenet.prototxt 128 googlenet + +# small net +test smallnet_mnist_cifar.prototxt 64 smallnet +test smallnet_mnist_cifar.prototxt 128 smallnet +test smallnet_mnist_cifar.prototxt 256 smallnet +test smallnet_mnist_cifar.prototxt 512 smallnet diff --git a/benchmark/caffe/image/run_multi.sh b/benchmark/caffe/image/run_multi.sh new file mode 100755 index 0000000000..f72b062c11 --- /dev/null +++ b/benchmark/caffe/image/run_multi.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +function test() { + cfg=$1 + batch=$2 + prefix=$3 + batch_per_gpu=`expr ${batch} / 4` + sed -i "/input: \"data\"/{n;s/^input_dim.*/input_dim: ${batch_per_gpu}/g}" $cfg + sed -i "/input: \"label\"/{n;s/^input_dim.*/input_dim: ${batch_per_gpu}/g}" $cfg + sed -i "1c\net : \"${cfg}\"" solver.prototxt + caffe train --solver=solver.prototxt -gpu all > logs/${prefix}-4gpu-batch${batch}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +# alexnet +test alexnet.prototxt 512 alexnet +test alexnet.prototxt 1024 alexnet + +# googlnet +test googlenet.prototxt 512 googlenet diff --git a/benchmark/caffe/image/smallnet_mnist_cifar.prototxt b/benchmark/caffe/image/smallnet_mnist_cifar.prototxt new file mode 100644 index 0000000000..3cb0e32bbf --- /dev/null +++ b/benchmark/caffe/image/smallnet_mnist_cifar.prototxt @@ -0,0 +1,198 @@ +name: "mnist/cifar" +input: "data" +input_dim: 128 +input_dim: 3 +input_dim: 32 +input_dim: 32 +input: "label" +input_dim: 128 +input_dim: 1 +input_dim: 1 +input_dim: 1 +layer { + name: "conv1" + type: "Convolution" + bottom: "data" + top: "conv1" + param { + lr_mult: 1 + } + param { + lr_mult: 2 + } + convolution_param { + num_output: 32 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.0001 + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "pool1" + type: "Pooling" + bottom: "conv1" + top: "pool1" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "relu1" + type: "ReLU" + bottom: "pool1" + top: "pool1" +} +layer { + name: "conv2" + type: "Convolution" + bottom: "pool1" + top: "conv2" + param { + lr_mult: 1 + } + param { + lr_mult: 2 + } + convolution_param { + num_output: 32 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "relu2" + type: "ReLU" + bottom: "conv2" + top: "conv2" +} +layer { + name: "pool2" + type: "Pooling" + bottom: "conv2" + top: "pool2" + pooling_param { + pool: AVE + kernel_size: 3 + stride: 2 + } +} +layer { + name: "conv3" + type: "Convolution" + bottom: "pool2" + top: "conv3" + param { + lr_mult: 1 + } + param { + lr_mult: 2 + } + convolution_param { + num_output: 64 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "relu3" + type: "ReLU" + bottom: "conv3" + top: "conv3" +} +layer { + name: "pool3" + type: "Pooling" + bottom: "conv3" + top: "pool3" + pooling_param { + pool: AVE + kernel_size: 3 + stride: 2 + } +} +layer { + name: "ip1" + type: "InnerProduct" + bottom: "pool3" + top: "ip1" + param { + lr_mult: 1 + } + param { + lr_mult: 2 + } + inner_product_param { + num_output: 64 + weight_filler { + type: "gaussian" + std: 0.1 + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "ip2" + type: "InnerProduct" + bottom: "ip1" + top: "ip2" + param { + lr_mult: 1 + } + param { + lr_mult: 2 + } + inner_product_param { + num_output: 10 + weight_filler { + type: "gaussian" + std: 0.1 + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "accuracy" + type: "Accuracy" + bottom: "ip2" + bottom: "label" + top: "accuracy" + include { + phase: TEST + } +} +layer { + name: "loss" + type: "SoftmaxWithLoss" + bottom: "ip2" + bottom: "label" + top: "loss" +} diff --git a/benchmark/caffe/image/solver.prototxt b/benchmark/caffe/image/solver.prototxt new file mode 100644 index 0000000000..61c10284e6 --- /dev/null +++ b/benchmark/caffe/image/solver.prototxt @@ -0,0 +1,10 @@ +net: "alexnet.prototxt" +base_lr: 0.01 +lr_policy: "fixed" +display: 20 +max_iter: 200 +momentum: 0.9 +weight_decay: 0.0005 +snapshot: 10000 +snapshot_prefix: "models/caffe_alexnet_train" +solver_mode: GPU diff --git a/benchmark/figs/alexnet-4gpu.png b/benchmark/figs/alexnet-4gpu.png new file mode 100644 index 0000000000000000000000000000000000000000..864c11313d7eacc90910a902c6a3a39c2bef77cc GIT binary patch literal 81990 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 z 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 literal 0 HcmV?d00001 diff --git a/benchmark/figs/googlenet-4gpu.png b/benchmark/figs/googlenet-4gpu.png new file mode 100644 index 0000000000000000000000000000000000000000..098ed35bf7763d34993357501f16b6d859c8733f GIT binary patch literal 82569 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 z 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

o7zPL=7Pd29)iywlrsds!CtZzi8TY3&fc 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 qDd`bKaTVD8@j4GX6|dj^Y(yF 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 X{(5eQ-H(F>{-`Ny-ubF%8u-5ev@q;@ literal 0 HcmV?d00001 diff --git a/benchmark/figs/rnn_lstm_4gpus.png b/benchmark/figs/rnn_lstm_4gpus.png new file mode 100644 index 0000000000000000000000000000000000000000..973ce2fa5f65e9681c972d4f5bd5776b5c4aa264 GIT binary patch literal 73243 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%0AGXeR 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 H_5J=I8vUTY literal 0 HcmV?d00001 diff --git a/benchmark/figs/rnn_lstm_cls.png b/benchmark/figs/rnn_lstm_cls.png new file mode 100644 index 0000000000000000000000000000000000000000..26d05cac11aa7ae8cdfbcd8c4401f6547a9404f6 GIT binary patch literal 117634 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

  • mxd5FP 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 train.txt + done + fi + + if [ ! -d "train.list" ]; then + echo "train.txt" > train.list + fi +} + +function train() { + cfg=$1 + thread=$2 + bz=$3 + args="batch_size=$3" + prefix=$4 + paddle train --job=time \ + --config=$cfg \ + --use_gpu=True \ + --trainer_count=$thread \ + --log_period=10 \ + --test_period=100 \ + --config_args=$args \ + --cudnn_dir=/home/dangqingqing/tools/cudnn-5.1/lib64 \ + > logs/$prefix-${thread}gpu-$bz.log 2>&1 +} + +gen_file +if [ ! -d "logs" ]; then + mkdir logs +fi + +#========single-gpu=========# +# alexnet +train alexnet.py 1 64 alexnet +train alexnet.py 1 128 alexnet +train alexnet.py 1 256 alexnet +train alexnet.py 1 512 alexnet + +# googlenet +train googlenet.py 1 64 googlenet +train googlenet.py 1 128 googlenet +train googlenet.py 1 256 googlenet + +# smallnet +train smallnet_mnist_cifar.py 1 64 smallnet +train smallnet_mnist_cifar.py 1 128 smallnet +train smallnet_mnist_cifar.py 1 256 smallnet +train smallnet_mnist_cifar.py 1 512 smallnet diff --git a/benchmark/paddle/image/run_multi.sh b/benchmark/paddle/image/run_multi.sh new file mode 100755 index 0000000000..c506668fe0 --- /dev/null +++ b/benchmark/paddle/image/run_multi.sh @@ -0,0 +1,42 @@ +set -e + +function gen_file() { + if [ ! -d "train.txt" ]; then + for ((i=1;i<=1024;i++)) + do + echo "train/n09246464/n09246464_38735.jpeg 972" >> train.txt + done + fi + + if [ ! -d "train.list" ]; then + echo "train.txt" > train.list + fi +} + +function train() { + cfg=$1 + thread=$2 + bz=$3 + args="batch_size=$3" + prefix=$4 + paddle train --job=time \ + --config=$cfg \ + --use_gpu=True \ + --trainer_count=$thread \ + --log_period=10 \ + --test_period=100 \ + --config_args=$args \ + > logs/$prefix-${thread}gpu-$bz.log 2>&1 +} + +gen_file +if [ ! -d "logs" ]; then + mkdir logs +fi + +#========multi-gpus=========# +train alexnet.py 4 512 alexnet +train alexnet.py 4 1024 alexnet + +train googlenet.py 4 512 googlenet +train googlenet.py 4 1024 googlenet diff --git a/benchmark/paddle/image/smallnet_mnist_cifar.py b/benchmark/paddle/image/smallnet_mnist_cifar.py new file mode 100644 index 0000000000..78dba880d2 --- /dev/null +++ b/benchmark/paddle/image/smallnet_mnist_cifar.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +from paddle.trainer_config_helpers import * + +height=32 +width=32 +num_class = 10 + +batch_size = get_config_arg('batch_size', int, 128) + +args={'height':height, 'width':width, 'color':True, 'num_class':num_class} +define_py_data_sources2("train.list", + None, + module="provider", + obj="process", + args=args) + +settings( + batch_size = batch_size, + learning_rate = 0.01 / batch_size, + learning_method = MomentumOptimizer(0.9), + regularization = L2Regularization(0.0005 * batch_size) +) + + +# conv1 +net = data_layer('data', size=height * width * 3) +net = img_conv_layer(input=net, filter_size=5, num_channels=3, + num_filters=32, stride=1, padding=2) +net = img_pool_layer(input=net, pool_size=3, stride=2, padding=1) + +# conv2 +net = img_conv_layer(input=net, filter_size=5, num_filters=32, + stride=1, padding=2) +net = img_pool_layer(input=net, pool_size=3, stride=2, padding=1, pool_type=AvgPooling()) + +# conv3 +net = img_conv_layer(input=net, filter_size=3, num_filters=64, + stride=1, padding=1) +net = img_pool_layer(input=net, pool_size=3, stride=2, padding=1, pool_type=AvgPooling()) + +net = fc_layer(input=net, size=64, act=ReluActivation()) +net = fc_layer(input=net, size=10, act=SoftmaxActivation()) + +lab = data_layer('label', num_class) +loss = classification_cost(input=net, label=lab) +outputs(loss) diff --git a/benchmark/paddle/rnn/imdb.py b/benchmark/paddle/rnn/imdb.py new file mode 100755 index 0000000000..93e1686854 --- /dev/null +++ b/benchmark/paddle/rnn/imdb.py @@ -0,0 +1,42 @@ +from __future__ import print_function +import six.moves.cPickle as pickle +import gzip +import os +import numpy + +def get_dataset_file(dataset, default_dataset, origin): + data_dir, data_file = os.path.split(dataset) + if (not os.path.isfile(dataset)) and data_file == default_dataset: + from six.moves import urllib + print('Downloading data from %s' % origin) + urllib.request.urlretrieve(origin, dataset) + + return dataset + +def create_data(path="imdb.pkl"): + + if (not os.path.isfile('imdb.train.pkl')): + path = get_dataset_file( + path, "imdb.pkl", + "http://www.iro.umontreal.ca/~lisa/deep/data/imdb.pkl") + + if path.endswith(".gz"): + f = gzip.open(path, 'rb') + else: + f = open(path, 'rb') + + train_set = pickle.load(f) + test_set = pickle.load(f) + f.close() + + pickle.dump(train_set, open('imdb.train.pkl', 'wb')) + pickle.dump(test_set, open('imdb.test.pkl', 'wb')) + + if (not os.path.isfile('train.list')): + file('train.list', 'w').write('imdb.train.pkl\n') + +def main(): + create_data('imdb.pkl') + +if __name__ == "__main__": + main() diff --git a/benchmark/paddle/rnn/provider.py b/benchmark/paddle/rnn/provider.py new file mode 100644 index 0000000000..90d3fee676 --- /dev/null +++ b/benchmark/paddle/rnn/provider.py @@ -0,0 +1,64 @@ +import io,os +import random +import numpy as np +import six.moves.cPickle as pickle +from paddle.trainer.PyDataProvider2 import * + +def remove_unk(x, n_words): + return [[1 if w >= n_words else w for w in sen] for sen in x] + +# ============================================================== +# tensorflow uses fixed length, but PaddlePaddle can process +# variable-length. Padding is used in benchmark in order to +# compare with other platform. +# ============================================================== +def pad_sequences(sequences, maxlen=None, dtype='int32', padding='post', + truncating='post', value=0.): + lengths = [len(s) for s in sequences] + + nb_samples = len(sequences) + if maxlen is None: + maxlen = np.max(lengths) + + x = (np.ones((nb_samples, maxlen)) * value).astype(dtype) + for idx, s in enumerate(sequences): + if len(s) == 0: + continue # empty list was found + if truncating == 'pre': + trunc = s[-maxlen:] + elif truncating == 'post': + trunc = s[:maxlen] + else: + raise ValueError("Truncating type '%s' not understood" % padding) + + if padding == 'post': + x[idx, :len(trunc)] = trunc + elif padding == 'pre': + x[idx, -len(trunc):] = trunc + else: + raise ValueError("Padding type '%s' not understood" % padding) + return x + + +def initHook(settings, vocab_size, pad_seq, maxlen, **kwargs): + settings.vocab_size = vocab_size + settings.pad_seq = pad_seq + settings.maxlen = maxlen + settings.input_types = [ + integer_value_sequence(vocab_size), + integer_value(2)] + +@provider(init_hook=initHook, min_pool_size=-1, cache=CacheType.CACHE_PASS_IN_MEM) +def process(settings, file): + f = open(file, 'rb') + train_set = pickle.load(f) + f.close() + x, y = train_set + + # remove unk, namely remove the words out of dictionary + x = remove_unk(x, settings.vocab_size) + if settings.pad_seq: + x = pad_sequences(x, maxlen=settings.maxlen, value=0.) + + for i in range(len(y)): + yield map(int,x[i]), int(y[i]) diff --git a/benchmark/paddle/rnn/rnn.py b/benchmark/paddle/rnn/rnn.py new file mode 100755 index 0000000000..fc8221b112 --- /dev/null +++ b/benchmark/paddle/rnn/rnn.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +from paddle.trainer_config_helpers import * +import imdb + +num_class = 2 +vocab_size = 30000 +fixedlen = 100 +batch_size = get_config_arg('batch_size', int, 128) +lstm_num = get_config_arg('lstm_num', int, 1) +hidden_size = get_config_arg('hidden_size', int, 128) +# whether to pad sequence into fixed length +pad_seq = get_config_arg('pad_seq', bool, True) +imdb.create_data('imdb.pkl') + +args={'vocab_size':vocab_size, 'pad_seq':pad_seq, 'maxlen':fixedlen} +define_py_data_sources2("train.list", + None, + module="provider", + obj="process", + args=args) + +settings( + batch_size=batch_size, + learning_rate=2e-3, + learning_method=AdamOptimizer(), + regularization=L2Regularization(8e-4), + gradient_clipping_threshold=25 +) + +net = data_layer('data', size=vocab_size) +net = embedding_layer(input=net, size=128) + +for i in xrange(lstm_num): + net = simple_lstm(input=net, size=hidden_size) + +net = last_seq(input=net) +net = fc_layer(input=net, size=2, act=SoftmaxActivation()) + +lab = data_layer('label', num_class) +loss = classification_cost(input=net, label=lab) +outputs(loss) diff --git a/benchmark/paddle/rnn/run.sh b/benchmark/paddle/rnn/run.sh new file mode 100755 index 0000000000..92c6e0b4b4 --- /dev/null +++ b/benchmark/paddle/rnn/run.sh @@ -0,0 +1,38 @@ +set -e + +function train() { + cfg=$1 + thread=$2 + args="lstm_num=${3},seq_pad=${4},hidden_size=${5},batch_size=${6}" + paddle train --job=time \ + --config=$cfg \ + --use_gpu=1 \ + --trainer_count=$thread \ + --log_period=10 \ + --test_period=100 \ + --num_passes=1 \ + --feed_data=1 \ + --config_args=$args \ + >logs/rnn-pad${4}-${thread}gpu-lstm${3}-batch${6}-hid${5}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +## padding, single gpu +#-----config--gpu--lstm_num--padding--hidden_size--batch_size +## lstm_num=2, batch_size=64 +train rnn.py 1 2 1 256 64 +train rnn.py 1 2 1 512 64 +train rnn.py 1 2 1 1280 64 + +## lstm_num=2, batch_size=128 +train rnn.py 1 2 1 256 128 +train rnn.py 1 2 1 512 128 +train rnn.py 1 2 1 1280 128 + +## lstm_num=4, batch_size=256 +train rnn.py 1 2 1 256 256 +train rnn.py 1 2 1 512 256 +train rnn.py 1 2 1 1280 256 diff --git a/benchmark/paddle/rnn/run_multi.sh b/benchmark/paddle/rnn/run_multi.sh new file mode 100755 index 0000000000..50ee469bcd --- /dev/null +++ b/benchmark/paddle/rnn/run_multi.sh @@ -0,0 +1,34 @@ +set -e + +function train() { + cfg=$1 + thread=$2 + args="lstm_num=${3},seq_pad=${4},hidden_size=${5},batch_size=${6}" + paddle train --job=time \ + --config=$cfg \ + --use_gpu=1 \ + --trainer_count=$thread \ + --log_period=10 \ + --test_period=100 \ + --num_passes=1 \ + --feed_data=1 \ + --config_args=$args \ + >logs/rnn-pad${4}-${thread}gpu-lstm${3}-hid${5}-batch${6}.log 2>&1 +} + + +if [ ! -d "logs" ]; then + mkdir logs +fi + +#-----config--gpu--lstm_num--padding--hidden_size--batch_size +#==================multi gpus=====================# +# hidden_size=256, lstm_num=2, different batch size +train rnn.py 4 2 1 256 128 +train rnn.py 4 2 1 256 256 +train rnn.py 4 2 1 256 512 + +# hidden_size=512, lstm_num=4, different batch size +train rnn.py 4 2 1 512 128 +train rnn.py 4 2 1 512 256 +train rnn.py 4 2 1 512 512 diff --git a/benchmark/tensorflow/image/alexnet.py b/benchmark/tensorflow/image/alexnet.py new file mode 100644 index 0000000000..57b7ef6c32 --- /dev/null +++ b/benchmark/tensorflow/image/alexnet.py @@ -0,0 +1,260 @@ +from six.moves import xrange # pylint: disable=redefined-builtin +from datetime import datetime +import math +import time + +import tensorflow.python.platform +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 128, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('forward_only', False, + """Only run the forward pass.""") +tf.app.flags.DEFINE_boolean('forward_backward_only', False, + """Only run the forward-forward pass.""") +tf.app.flags.DEFINE_string('data_format', 'NCHW', + """The data format for Convnet operations. + Can be either NHWC or NCHW. + """) +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +def _conv(name, inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd=0.0005): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w',[kH, kW, nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None and wd > 0: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + if FLAGS.data_format == 'NCHW': + strides = [1, 1, dH, dW] + else: + strides = [1, dH, dW, 1] + conv = tf.nn.conv2d(inpOp, kernel, strides, padding=padType, + data_format=FLAGS.data_format) + + biases = tf.get_variable(name=name + '_b', shape=[nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32) + + bias = tf.reshape( + tf.nn.bias_add(conv, biases, data_format=FLAGS.data_format), + conv.get_shape()) + + conv1 = tf.nn.relu(bias, name=scope) + return conv1 + +def _affine(name, inpOp, nIn, nOut, wd=0.0005, act=True, drop=None): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w', [nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None and wd > 0: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + biases = tf.get_variable(name + '_b', [nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32,trainable=True) + + affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \ + tf.matmul(inpOp, kernel) + biases + + output = tf.nn.dropout(affine1, drop) if drop else affine1 + + return output + +def _mpool(name, inpOp, kH, kW, dH, dW): + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.max_pool(inpOp, + ksize=ksize, + strides=strides, + padding='VALID', + data_format=FLAGS.data_format, + name=name) + +def _norm(name, l_input, lsize=4): + return tf.nn.lrn(l_input, lsize, bias=1.0, + alpha=0.001 / 9.0, + beta=0.75, name=name) + + + +def loss(logits, labels): + labels = tf.cast(labels, tf.int64) + cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( + logits, labels, name='cross_entropy_per_example') + cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') + tf.add_to_collection('losses', cross_entropy_mean) + + # The total loss is defined as the cross entropy loss plus all of the weight + # decay terms (L2 loss). + return tf.add_n(tf.get_collection('losses'), name='total_loss') + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + +def inference(images): + conv1 = _conv ('conv1', images, 3, 96, 11, 11, 4, 4, 'VALID') + pool1 = _mpool('pool1', conv1, 3, 3, 2, 2) + norm1 = _norm ('norm1', pool1, lsize=5) + conv2 = _conv ('conv2', norm1, 96, 256, 5, 5, 1, 1, 'SAME') + pool2 = _mpool('pool2', conv2, 3, 3, 2, 2) + norm2 = _norm ('norm2', pool2, lsize=5) + conv3 = _conv ('conv3', norm2, 256, 384, 3, 3, 1, 1, 'SAME') + conv4 = _conv ('conv4', conv3, 384, 384, 3, 3, 1, 1, 'SAME') + conv5 = _conv ('conv5', conv4, 384, 256, 3, 3, 1, 1, 'SAME') + pool5 = _mpool('pool5', conv5, 3, 3, 2, 2) + resh1 = tf.reshape(pool5, [-1, 256 * 6 * 6]) + affn1 = _affine('fc6', resh1, 256 * 6 * 6, 4096, 0.5) + affn2 = _affine('fc7', affn1, 4096, 4096, 0.5) + affn3 = _affine('fc8', affn2, 4096, 1000, wd=None, act=False) # last fc + + return affn3 + + +def time_tensorflow_run(session, target, info_string): + num_steps_burn_in = 10 + total_duration = 0.0 + total_duration_squared = 0.0 + if not isinstance(target, list): + target = [target] + target_op = tf.group(*target) + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _ = session.run(target_op) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + print ('%s: step %d, duration = %.3f' % + (datetime.now(), i - num_steps_burn_in, duration)) + total_duration += duration + total_duration_squared += duration * duration + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), info_string, FLAGS.num_batches, mn, sd)) + +def _add_loss_summaries(total_loss): + """ + Generates moving average for all losses and associated summaries for + visualizing the performance of the network. + + Args: + total_loss: Total loss from loss(). + Returns: + loss_averages_op: op for generating moving averages of losses. + """ + # Compute the moving average of all individual losses and the total loss. + loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') + losses = tf.get_collection('losses') + loss_averages_op = loss_averages.apply(losses + [total_loss]) + + # Attach a scalar summary to all individual losses and the total loss; do the + # same for the averaged version of the losses. + for l in losses + [total_loss]: + # Name each loss as '(raw)' and name the moving average version of the loss + # as the original loss name. + tf.scalar_summary(l.op.name +' (raw)', l) + tf.scalar_summary(l.op.name, loss_averages.average(l)) + + return loss_averages_op + + + +def run_benchmark(): + with tf.Graph().as_default(): + with tf.device('/gpu:0'): + # Generate some dummy images. + image_size = 224 + # Note that our padding definition is slightly different the cuda-convnet. + # In order to force the model to start with the same activations sizes, + # we add 3 to the image_size and employ VALID padding above. + if FLAGS.data_format == 'NCHW': + image_shape = [FLAGS.batch_size, 3, image_size + 3, image_size + 3] + else: + image_shape = [FLAGS.batch_size, image_size + 3, image_size + 3, 3] + images = tf.get_variable('image', image_shape, + initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32), + dtype=tf.float32, + trainable=False) + + labels = tf.get_variable('label', [FLAGS.batch_size], + initializer=tf.constant_initializer(1), + dtype=tf.int32, + trainable=False) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(images) + + objective = loss(last_layer, labels) + # Compute the gradient with respect to all the parameters. + + # Compute gradients. + # opt = tf.train.GradientDescentOptimizer(0.001) + opt = tf.train.MomentumOptimizer(0.001, 0.9) + grads = opt.compute_gradients(objective) + global_step = tf.get_variable('global_step', [], + initializer=tf.constant_initializer(0.0, dtype=tf.float32), + trainable=False, dtype=tf.float32) + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Track the moving averages of all trainable variables. + variable_averages = tf.train.ExponentialMovingAverage( + 0.9, global_step) + variables_averages_op = variable_averages.apply(tf.trainable_variables()) + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + + run_forward = True + run_forward_backward = True + if FLAGS.forward_only and FLAGS.forward_backward_only: + raise ValueError("Cannot specify --forward_only and " + "--forward_backward_only at the same time.") + if FLAGS.forward_only: + run_forward_backward = False + elif FLAGS.forward_backward_only: + run_forward = False + + if run_forward: + time_tensorflow_run(sess, last_layer, "Forward") + + if run_forward_backward: + with tf.control_dependencies([apply_gradient_op, variables_averages_op]): + train_op = tf.no_op(name='train') + time_tensorflow_run(sess, [train_op, objective], "Forward-backward") + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/image/alexnet_multi_gpu.py b/benchmark/tensorflow/image/alexnet_multi_gpu.py new file mode 100644 index 0000000000..949ad77f3b --- /dev/null +++ b/benchmark/tensorflow/image/alexnet_multi_gpu.py @@ -0,0 +1,335 @@ +from six.moves import xrange # pylint: disable=redefined-builtin +from datetime import datetime +import math +import re +import time + +import tensorflow.python.platform +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 64, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_string('data_format', 'NCHW', + """The data format for Convnet operations. + Can be either NHWC or NCHW. + """) + +tf.app.flags.DEFINE_string('train_dir', '/train_model', + """Directory where to write event logs """ + """and checkpoint.""") +tf.app.flags.DEFINE_integer('num_gpus', 4, + """How many GPUs to use.""") +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN=50000 +NUM_EPOCHS_PER_DECAY=50 +INITIAL_LEARNING_RATE = 0.1 +LEARNING_RATE_DECAY_FACTOR = 0.1 +TOWER_NAME = 'tower' + + +def _conv(name, inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd=0.005): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w',[kH, kW, nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + if FLAGS.data_format == 'NCHW': + strides = [1, 1, dH, dW] + else: + strides = [1, dH, dW, 1] + conv = tf.nn.conv2d(inpOp, kernel, strides, padding=padType, + data_format=FLAGS.data_format) + + biases = tf.get_variable(name=name + '_b', shape=[nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32) + + bias = tf.reshape( + tf.nn.bias_add(conv, biases, data_format=FLAGS.data_format), + conv.get_shape()) + + conv1 = tf.nn.relu(bias, name=scope) + return conv1 + +def _affine(name, inpOp, nIn, nOut, wd=0.005, act=True): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w', [nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + biases = tf.get_variable(name + '_b', [nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32,trainable=True) + + affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \ + tf.matmul(inpOp, kernel) + biases + + return affine1 + +def _mpool(name, inpOp, kH, kW, dH, dW): + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.max_pool(inpOp, + ksize=ksize, + strides=strides, + padding='VALID', + data_format=FLAGS.data_format, + name=name) + +def _norm(name, l_input, lsize=4): + return tf.nn.lrn(l_input, lsize, bias=1.0, + alpha=0.001 / 9.0, + beta=0.75, name=name) + +def loss(logits, labels): + labels = tf.cast(labels, tf.int64) + cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( + logits, labels, name='cross_entropy_per_example') + cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') + tf.add_to_collection('losses', cross_entropy_mean) + + # The total loss is defined as the cross entropy loss plus all of the weight + # decay terms (L2 loss). + return tf.add_n(tf.get_collection('losses'), name='total_loss') + + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + +def inference(images): + conv1 = _conv ('conv1', images, 3, 96, 11, 11, 4, 4, 'VALID') + pool1 = _mpool('pool1', conv1, 3, 3, 2, 2) + norm1 = _norm ('norm1', pool1, lsize=5) + conv2 = _conv ('conv2', norm1, 96, 256, 5, 5, 1, 1, 'SAME') + pool2 = _mpool('pool2', conv2, 3, 3, 2, 2) + norm2 = _norm ('norm2', pool2, lsize=5) + conv3 = _conv ('conv3', norm2, 256, 384, 3, 3, 1, 1, 'SAME') + conv4 = _conv ('conv4', conv3, 384, 384, 3, 3, 1, 1, 'SAME') + conv5 = _conv ('conv5', conv4, 384, 256, 3, 3, 1, 1, 'SAME') + pool5 = _mpool('pool5', conv5, 3, 3, 2, 2) + resh1 = tf.reshape(pool5, [-1, 256 * 6 * 6]) + affn1 = _affine('fc6', resh1, 256 * 6 * 6, 4096) + affn2 = _affine('fc7', affn1, 4096, 4096) + affn3 = _affine('fc8', affn2, 4096, 1000, wd=None, act=False) # last fc + + return affn3 + +def tower_loss(scope): + """Calculate the total loss on a single tower running the model. + Args: + scope: unique prefix string identifying the tower, e.g. 'tower_0' + Returns: + Tensor of shape [] containing the total loss for a batch of data + """ + image_size = 224 + if FLAGS.data_format == 'NCHW': + image_shape = [FLAGS.batch_size, 3, image_size + 3, image_size + 3] + else: + image_shape = [FLAGS.batch_size, image_size + 3, image_size + 3, 3] + images = tf.get_variable('image', image_shape, + initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32), + dtype=tf.float32, + trainable=False) + + labels = tf.get_variable('label', [FLAGS.batch_size], + initializer=tf.constant_initializer(1), + dtype=tf.int32, + trainable=False) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(images) + + # Build the portion of the Graph calculating the losses. Note that we will + # assemble the total_loss using a custom function below. + _ = loss(last_layer, labels) + + # Assemble all of the losses for the current tower only. + losses = tf.get_collection('losses', scope) + + # Calculate the total loss for the current tower. + total_loss = tf.add_n(losses, name='total_loss') + + # Compute the moving average of all individual losses and the total loss. + loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') + loss_averages_op = loss_averages.apply(losses + [total_loss]) + + # Attach a scalar summary to all individual losses and the total loss; do the + # same for the averaged version of the losses. + for l in losses + [total_loss]: + # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training + # session. This helps the clarity of presentation on tensorboard. + loss_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', l.op.name) + # Name each loss as '(raw)' and name the moving average version of the loss + # as the original loss name. + tf.scalar_summary(loss_name +' (raw)', l) + tf.scalar_summary(loss_name, loss_averages.average(l)) + + with tf.control_dependencies([loss_averages_op]): + total_loss = tf.identity(total_loss) + return total_loss + + +def average_gradients(tower_grads): + """Calculate the average gradient for each shared variable across all towers. + Note that this function provides a synchronization point across all towers. + Args: + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over individual gradients. The inner list is over the gradient + calculation for each tower. + Returns: + List of pairs of (gradient, variable) where the gradient has been averaged + across all towers. + """ + average_grads = [] + for grad_and_vars in zip(*tower_grads): + # Note that each grad_and_vars looks like the following: + # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) + grads = [] + for g, _ in grad_and_vars: + # Add 0 dimension to the gradients to represent the tower. + expanded_g = tf.expand_dims(g, 0) + + # Append on a 'tower' dimension which we will average over below. + grads.append(expanded_g) + + # Average over the 'tower' dimension. + grad = tf.concat(0, grads) + grad = tf.reduce_mean(grad, 0) + + # Keep in mind that the Variables are redundant because they are shared + # across towers. So .. we will just return the first tower's pointer to + # the Variable. + v = grad_and_vars[0][1] + grad_and_var = (grad, v) + average_grads.append(grad_and_var) + return average_grads + +def time_tensorflow_run(session, target): + num_steps_burn_in = 50 + total_duration = 0.0 + total_duration_squared = 0.0 + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _, loss_value = session.run(target) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + num_examples_per_step = FLAGS.batch_size * FLAGS.num_gpus + examples_per_sec = num_examples_per_step / duration + sec_per_batch = duration + + format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f ' + 'sec/batch batch_size = %d)') + print (format_str % + (datetime.now(), i - num_steps_burn_in, + loss_value, duration, sec_per_batch, num_examples_per_step)) + + total_duration += duration + total_duration_squared += duration * duration + + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: FwdBwd across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), FLAGS.num_batches, mn, sd)) + +def run_benchmark(): + with tf.Graph().as_default(), tf.device('/cpu:0'): + # Create a variable to count the number of train() calls. This equals the + # number of batches processed * FLAGS.num_gpus. + global_step = tf.get_variable( + 'global_step', [], + initializer=tf.constant_initializer(0), trainable=False) + + # Calculate the learning rate schedule. + num_batches_per_epoch = (NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / + FLAGS.batch_size) + decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY) + + # Decay the learning rate exponentially based on the number of steps. + lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE, + global_step, + decay_steps, + LEARNING_RATE_DECAY_FACTOR, + staircase=True) + + # Create an optimizer that performs gradient descent. + # opt = tf.train.GradientDescentOptimizer(lr) + opt = tf.train.MomentumOptimizer(lr, 0.9) + + # Calculate the gradients for each model tower. + tower_grads = [] + for i in xrange(FLAGS.num_gpus): + with tf.device('/gpu:%d' % i): + with tf.name_scope('%s_%d' % (TOWER_NAME, i)) as scope: + # Calculate the loss for one tower of the model. This function + # constructs the entire model but shares the variables across + # all towers. + loss = tower_loss(scope) + + # Reuse variables for the next tower. + tf.get_variable_scope().reuse_variables() + + # Retain the summaries from the final tower. + summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope) + + # Calculate the gradients for the batch of data on this tower. + grads = opt.compute_gradients(loss) + + # Keep track of the gradients across all towers. + tower_grads.append(grads) + + # We must calculate the mean of each gradient. Note that this is the + # synchronization point across all towers. + grads = average_gradients(tower_grads) + + # Apply the gradients to adjust the shared variables. + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Group all updates to into a single train op. + train_op = tf.group(apply_gradient_op) + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. allow_soft_placement must be set to + # True to build towers on GPU, as some of the ops do not have GPU + # implementations. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + time_tensorflow_run(sess, [train_op, loss]) + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/image/googlenet.py b/benchmark/tensorflow/image/googlenet.py new file mode 100644 index 0000000000..097a8997b7 --- /dev/null +++ b/benchmark/tensorflow/image/googlenet.py @@ -0,0 +1,282 @@ +from six.moves import xrange +from datetime import datetime +import math +import time + +import tensorflow.python.platform +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 128, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('forward_only', False, + """Only run the forward pass.""") +tf.app.flags.DEFINE_boolean('forward_backward_only', False, + """Only run the forward-forward pass.""") +tf.app.flags.DEFINE_string('data_format', 'NCHW', + """The data format for Convnet operations. + Can be either NHWC or NCHW. + """) +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +parameters = [] + +conv_counter = 1 +pool_counter = 1 +affine_counter = 1 + +def _conv(inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd = 0.0005): + global conv_counter + global parameters + name = 'conv' + str(conv_counter) + conv_counter += 1 + with tf.name_scope(name) as scope: + kernel = tf.Variable(tf.truncated_normal([kH, kW, nIn, nOut], + dtype=tf.float32, + stddev=1e-1), name='weights') + + if wd is not None and wd > 0: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + if FLAGS.data_format == 'NCHW': + strides = [1, 1, dH, dW] + else: + strides = [1, dH, dW, 1] + conv = tf.nn.conv2d(inpOp, kernel, strides, padding=padType, + data_format=FLAGS.data_format) + biases = tf.Variable(tf.constant(0.0, shape=[nOut], dtype=tf.float32), + trainable=True, name='biases') + bias = tf.reshape(tf.nn.bias_add(conv, biases, + data_format=FLAGS.data_format), + conv.get_shape()) + conv1 = tf.nn.relu(bias, name=scope) + parameters += [kernel, biases] + return conv1 + +def _affine(inpOp, nIn, nOut, act=True, wd = 0.0005): + global affine_counter + global parameters + name = 'affine' + str(affine_counter) + affine_counter += 1 + with tf.name_scope(name) as scope: + kernel = tf.Variable(tf.truncated_normal([nIn, nOut], + dtype=tf.float32, + stddev=1e-1), name='weights') + + if wd is not None and wd > 0: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + biases = tf.Variable(tf.constant(0.0, shape=[nOut], dtype=tf.float32), + trainable=True, name='biases') + affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else tf.matmul(inpOp, kernel) + biases + parameters += [kernel, biases] + return affine1 + +def _mpool(inpOp, kH, kW, dH, dW, padding): + global pool_counter + global parameters + name = 'pool' + str(pool_counter) + pool_counter += 1 + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.max_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + +def _apool(inpOp, kH, kW, dH, dW, padding): + global pool_counter + global parameters + name = 'pool' + str(pool_counter) + pool_counter += 1 + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.avg_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + +def _inception(inp, inSize, o1s, o2s1, o2s2, o3s1, o3s2, o4s1, o4s2): + conv1 = _conv(inp, inSize, o1s, 1, 1, 1, 1, 'VALID') + + conv3_ = _conv(inp, inSize, o2s1, 1, 1, 1, 1, 'VALID') + conv3 = _conv(conv3_, o2s1, o2s2, 3, 3, 1, 1, 'SAME') + + conv5_ = _conv(inp, inSize, o3s1, 1, 1, 1, 1, 'VALID') + conv5 = _conv(conv5_, o3s1, o3s2, 5, 5, 1, 1, 'SAME') + + pool_ = _mpool(inp, o4s1, o4s1, 1, 1, 'SAME') + pool = _conv(pool_, inSize, o4s2, 1, 1, 1, 1, 'VALID') + + if FLAGS.data_format == 'NCHW': + channel_dim = 1 + else: + channel_dim = 3 + incept = tf.concat(channel_dim, [conv1, conv3, conv5, pool]) + return incept + + +def loss(logits, labels): + batch_size = tf.size(labels) + labels = tf.expand_dims(labels, 1) + indices = tf.expand_dims(tf.range(0, batch_size, 1), 1) + concated = tf.concat(1, [indices, labels]) + onehot_labels = tf.sparse_to_dense( + concated, tf.pack([batch_size, 1000]), 1.0, 0.0) + cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits, + onehot_labels, + name='xentropy') + loss = tf.reduce_mean(cross_entropy, name='xentropy_mean') + return loss + +def inference(images): + # stage 1 + conv1 = _conv (images, 3, 64, 7, 7, 2, 2, 'SAME') + pool1 = _mpool(conv1, 3, 3, 2, 2, 'SAME') + # stage 2 + conv2 = _conv (pool1, 64, 64, 1, 1, 1, 1, 'VALID') + conv3 = _conv (conv2, 64, 192, 3, 3, 1, 1, 'SAME') + pool3 = _mpool(conv3, 3, 3, 2, 2, 'SAME') + + # stage 3 + incept3a = _inception(pool3, 192, 64, 96, 128, 16, 32, 3, 32) + incept3b = _inception(incept3a, 256, 128, 128, 192, 32, 96, 3, 64) + pool4 = _mpool(incept3b, 3, 3, 2, 2, 'SAME') + + # stage 4 + incept4a = _inception(pool4, 480, 192, 96, 208, 16, 48, 3, 64) + incept4b = _inception(incept4a, 512, 160, 112, 224, 24, 64, 3, 64) + incept4c = _inception(incept4b, 512, 128, 128, 256, 24, 64, 3, 64) + incept4d = _inception(incept4c, 512, 112, 144, 288, 32, 64, 3, 64) + incept4e = _inception(incept4d, 528, 256, 160, 320, 32, 128, 3, 128) + pool5 = _mpool(incept4e, 3, 3, 2, 2, 'SAME') + + # stage 5 + incept5a = _inception(pool5, 832, 256, 160, 320, 32, 128, 3, 128) + incept5b = _inception(incept5a, 832, 384, 192, 384, 48, 128, 3, 128) + pool6 = _apool(incept5b, 7, 7, 1, 1, 'VALID') + + # output 1 + resh1 = tf.reshape(pool6, [-1, 1024]) + drop = tf.nn.dropout(resh1, 0.4) + affn1 = _affine(resh1, 1024, 1000, act=False) + + return affn1 + + +def time_tensorflow_run(session, target, info_string): + num_steps_burn_in = 10 + total_duration = 0.0 + total_duration_squared = 0.0 + if not isinstance(target, list): + target = [target] + target_op = tf.group(*target) + for i in range(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _ = session.run(target_op) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + print ('%s: step %d, duration = %.3f' % + (datetime.now(), i - num_steps_burn_in, duration)) + total_duration += duration + total_duration_squared += duration * duration + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), info_string, FLAGS.num_batches, mn, sd)) + +def run_benchmark(): + global parameters + with tf.Graph().as_default(): + # Generate some dummy images. + image_size = 224 + if FLAGS.data_format == 'NCHW': + image_shape = [FLAGS.batch_size, 3, image_size, image_size] + else: + image_shape = [FLAGS.batch_size, image_size, image_size, 3] + + images = tf.get_variable('image', image_shape, + initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32), + dtype=tf.float32, + trainable=False) + + labels = tf.get_variable('label', [FLAGS.batch_size], + initializer=tf.constant_initializer(1), + dtype=tf.int32, + trainable=False) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(images) + + objective = loss(last_layer, labels) + + # Compute gradients. + # opt = tf.train.GradientDescentOptimizer(0.001) + opt = tf.train.MomentumOptimizer(0.001, 0.9) + grads = opt.compute_gradients(objective) + global_step = tf.get_variable('global_step', [], + initializer=tf.constant_initializer(0.0, dtype=tf.float32), + trainable=False, dtype=tf.float32) + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Track the moving averages of all trainable variables. + variable_averages = tf.train.ExponentialMovingAverage( + 0.9, global_step) + variables_averages_op = variable_averages.apply(tf.trainable_variables()) + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + + run_forward = True + run_forward_backward = True + if FLAGS.forward_only and FLAGS.forward_backward_only: + raise ValueError("Cannot specify --forward_only and " + "--forward_backward_only at the same time.") + if FLAGS.forward_only: + run_forward_backward = False + elif FLAGS.forward_backward_only: + run_forward = False + + if run_forward: + # Run the forward benchmark. + time_tensorflow_run(sess, last_layer, "Forward") + + if run_forward_backward: + with tf.control_dependencies([apply_gradient_op, variables_averages_op]): + train_op = tf.no_op(name='train') + time_tensorflow_run(sess, [train_op, objective], "Forward-backward") + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/image/googlenet_multi_gpu.py b/benchmark/tensorflow/image/googlenet_multi_gpu.py new file mode 100644 index 0000000000..e22a6b6253 --- /dev/null +++ b/benchmark/tensorflow/image/googlenet_multi_gpu.py @@ -0,0 +1,381 @@ +from six.moves import xrange # pylint: disable=redefined-builtin +from datetime import datetime +import math +import re +import time + +import tensorflow.python.platform +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 64, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_string('data_format', 'NCHW', + """The data format for Convnet operations. + Can be either NHWC or NCHW. + """) + +tf.app.flags.DEFINE_string('train_dir', '/train_model', + """Directory where to write event logs """ + """and checkpoint.""") +tf.app.flags.DEFINE_integer('num_gpus', 4, + """How many GPUs to use.""") +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN=50000 +NUM_EPOCHS_PER_DECAY=50 +INITIAL_LEARNING_RATE = 0.1 +LEARNING_RATE_DECAY_FACTOR = 0.1 +TOWER_NAME = 'tower' + + +def _conv(name, inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd=0.005): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w',[kH, kW, nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + if FLAGS.data_format == 'NCHW': + strides = [1, 1, dH, dW] + else: + strides = [1, dH, dW, 1] + conv = tf.nn.conv2d(inpOp, kernel, strides, padding=padType, + data_format=FLAGS.data_format) + + biases = tf.get_variable(name=name + '_b', shape=[nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32) + + bias = tf.reshape( + tf.nn.bias_add(conv, biases, data_format=FLAGS.data_format), + conv.get_shape()) + + conv1 = tf.nn.relu(bias, name=scope) + return conv1 + +def _affine(name, inpOp, nIn, nOut, wd=0.005, act=True): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w', [nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + biases = tf.get_variable(name + '_b', [nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32,trainable=True) + + affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \ + tf.matmul(inpOp, kernel) + biases + + return affine1 + +def _mpool(name, inpOp, kH, kW, dH, dW, padding): + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.max_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + +def _apool(name, inpOp, kH, kW, dH, dW, padding): + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.avg_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + +def loss(logits, labels): + labels = tf.cast(labels, tf.int64) + cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( + logits, labels, name='cross_entropy_per_example') + cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') + tf.add_to_collection('losses', cross_entropy_mean) + + # The total loss is defined as the cross entropy loss plus all of the weight + # decay terms (L2 loss). + return tf.add_n(tf.get_collection('losses'), name='total_loss') + + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + + +def _inception(name, inp, inSize, o1s, o2s1, o2s2, o3s1, o3s2, o4s1, o4s2): + conv1 = _conv(name + '_1' , inp, inSize, o1s, 1, 1, 1, 1, 'VALID') + + conv3_ = _conv(name + '_3r', inp, inSize, o2s1, 1, 1, 1, 1, 'VALID') + conv3 = _conv(name + '_3', conv3_, o2s1, o2s2, 3, 3, 1, 1, 'SAME') + + conv5_ = _conv(name + '_5r', inp, inSize, o3s1, 1, 1, 1, 1, 'VALID') + conv5 = _conv(name + '5', conv5_, o3s1, o3s2, 5, 5, 1, 1, 'SAME') + + pool_ = _mpool(name + 'pool', inp, o4s1, o4s1, 1, 1, 'SAME') + pool = _conv(name + 'proj', pool_, inSize, o4s2, 1, 1, 1, 1, 'VALID') + + if FLAGS.data_format == 'NCHW': + channel_dim = 1 + else: + channel_dim = 3 + incept = tf.concat(channel_dim, [conv1, conv3, conv5, pool]) + return incept + + +def inference(images): + # stage 1 + conv1 = _conv ('conv1', images, 3, 64, 7, 7, 2, 2, 'SAME') + pool1 = _mpool('pool1', conv1, 3, 3, 2, 2, 'SAME') + + # stage 2 + conv2 = _conv ('conv2', pool1, 64, 64, 1, 1, 1, 1, 'VALID') + conv3 = _conv ('conv3', conv2, 64, 192, 3, 3, 1, 1, 'SAME') + pool3 = _mpool('pool3', conv3, 3, 3, 2, 2, 'SAME') + + # stage 3 + incept3a = _inception('ince3a', pool3, 192, 64, 96, 128, 16, 32, 3, 32) + incept3b = _inception('ince3b', incept3a, 256, 128, 128, 192, 32, 96, 3, 64) + pool4 = _mpool('pool4', incept3b, 3, 3, 2, 2, 'SAME') + + # stage 4 + incept4a = _inception('ince4a', pool4, 480, 192, 96, 208, 16, 48, 3, 64) + incept4b = _inception('ince4b', incept4a, 512, 160, 112, 224, 24, 64, 3, 64) + incept4c = _inception('ince4c', incept4b, 512, 128, 128, 256, 24, 64, 3, 64) + incept4d = _inception('ince4d', incept4c, 512, 112, 144, 288, 32, 64, 3, 64) + incept4e = _inception('ince4e', incept4d, 528, 256, 160, 320, 32, 128, 3, 128) + pool5 = _mpool('pool5', incept4e, 3, 3, 2, 2, 'SAME') + + # stage 5 + incept5a = _inception('ince5a', pool5, 832, 256, 160, 320, 32, 128, 3, 128) + incept5b = _inception('ince5b', incept5a, 832, 384, 192, 384, 48, 128, 3, 128) + pool6 = _apool('pool6', incept5b, 7, 7, 1, 1, 'VALID') + + # output 1 + resh1 = tf.reshape(pool6, [-1, 1024]) + drop = tf.nn.dropout(resh1, 0.4) + affn1 = _affine('fc_out', resh1, 1024, 1000, act=False) + + return affn1 + +def tower_loss(scope): + """Calculate the total loss on a single tower running the model. + Args: + scope: unique prefix string identifying the tower, e.g. 'tower_0' + Returns: + Tensor of shape [] containing the total loss for a batch of data + """ + image_size = 224 + if FLAGS.data_format == 'NCHW': + image_shape = [FLAGS.batch_size, 3, image_size, image_size] + else: + image_shape = [FLAGS.batch_size, image_size, image_size, 3] + images = tf.get_variable('image', image_shape, + initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32), + dtype=tf.float32, + trainable=False) + + labels = tf.get_variable('label', [FLAGS.batch_size], + initializer=tf.constant_initializer(1), + dtype=tf.int32, + trainable=False) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(images) + + # Build the portion of the Graph calculating the losses. Note that we will + # assemble the total_loss using a custom function below. + _ = loss(last_layer, labels) + + # Assemble all of the losses for the current tower only. + losses = tf.get_collection('losses', scope) + + # Calculate the total loss for the current tower. + total_loss = tf.add_n(losses, name='total_loss') + + # Compute the moving average of all individual losses and the total loss. + loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') + loss_averages_op = loss_averages.apply(losses + [total_loss]) + + # Attach a scalar summary to all individual losses and the total loss; do the + # same for the averaged version of the losses. + for l in losses + [total_loss]: + # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training + # session. This helps the clarity of presentation on tensorboard. + loss_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', l.op.name) + # Name each loss as '(raw)' and name the moving average version of the loss + # as the original loss name. + tf.scalar_summary(loss_name +' (raw)', l) + tf.scalar_summary(loss_name, loss_averages.average(l)) + + with tf.control_dependencies([loss_averages_op]): + total_loss = tf.identity(total_loss) + return total_loss + + +def average_gradients(tower_grads): + """Calculate the average gradient for each shared variable across all towers. + Note that this function provides a synchronization point across all towers. + Args: + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over individual gradients. The inner list is over the gradient + calculation for each tower. + Returns: + List of pairs of (gradient, variable) where the gradient has been averaged + across all towers. + """ + average_grads = [] + for grad_and_vars in zip(*tower_grads): + # Note that each grad_and_vars looks like the following: + # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) + grads = [] + for g, _ in grad_and_vars: + # Add 0 dimension to the gradients to represent the tower. + expanded_g = tf.expand_dims(g, 0) + + # Append on a 'tower' dimension which we will average over below. + grads.append(expanded_g) + + # Average over the 'tower' dimension. + grad = tf.concat(0, grads) + grad = tf.reduce_mean(grad, 0) + + # Keep in mind that the Variables are redundant because they are shared + # across towers. So .. we will just return the first tower's pointer to + # the Variable. + v = grad_and_vars[0][1] + grad_and_var = (grad, v) + average_grads.append(grad_and_var) + return average_grads + +def time_tensorflow_run(session, target): + num_steps_burn_in = 50 + total_duration = 0.0 + total_duration_squared = 0.0 + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _, loss_value = session.run(target) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + num_examples_per_step = FLAGS.batch_size * FLAGS.num_gpus + examples_per_sec = num_examples_per_step / duration + sec_per_batch = duration + + format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f ' + 'sec/batch batch_size = %d)') + print (format_str % + (datetime.now(), i - num_steps_burn_in, + loss_value, duration, sec_per_batch, num_examples_per_step)) + + total_duration += duration + total_duration_squared += duration * duration + + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: FwdBwd across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), FLAGS.num_batches, mn, sd)) + +def run_benchmark(): + with tf.Graph().as_default(), tf.device('/cpu:0'): + # Create a variable to count the number of train() calls. This equals the + # number of batches processed * FLAGS.num_gpus. + global_step = tf.get_variable( + 'global_step', [], + initializer=tf.constant_initializer(0), trainable=False) + + # Calculate the learning rate schedule. + num_batches_per_epoch = (NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / + FLAGS.batch_size) + decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY) + + # Decay the learning rate exponentially based on the number of steps. + lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE, + global_step, + decay_steps, + LEARNING_RATE_DECAY_FACTOR, + staircase=True) + + # Create an optimizer that performs gradient descent. + opt = tf.train.MomentumOptimizer(lr, 0.9) + + # Calculate the gradients for each model tower. + tower_grads = [] + for i in xrange(FLAGS.num_gpus): + with tf.device('/gpu:%d' % i): + with tf.name_scope('%s_%d' % (TOWER_NAME, i)) as scope: + # Calculate the loss for one tower of the model. This function + # constructs the entire model but shares the variables across + # all towers. + loss = tower_loss(scope) + + # Reuse variables for the next tower. + tf.get_variable_scope().reuse_variables() + + # Retain the summaries from the final tower. + summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope) + + # Calculate the gradients for the batch of data on this tower. + grads = opt.compute_gradients(loss) + + # Keep track of the gradients across all towers. + tower_grads.append(grads) + + # We must calculate the mean of each gradient. Note that this is the + # synchronization point across all towers. + grads = average_gradients(tower_grads) + + # Apply the gradients to adjust the shared variables. + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Group all updates to into a single train op. + train_op = tf.group(apply_gradient_op) + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. allow_soft_placement must be set to + # True to build towers on GPU, as some of the ops do not have GPU + # implementations. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + time_tensorflow_run(sess, [train_op, loss]) + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/image/run.sh b/benchmark/tensorflow/image/run.sh new file mode 100755 index 0000000000..eade36beb9 --- /dev/null +++ b/benchmark/tensorflow/image/run.sh @@ -0,0 +1,28 @@ +set -e + +function test() { + cfg=$1 + batch_size=$2 + prefix=$3 + python $cfg --batch_size=$batch_size > logs/${prefix}-1gpu-${batch_size}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +# alexnet +test alexnet.py 64 alexnet +test alexnet.py 128 alexnet +test alexnet.py 256 alexnet +test alexnet.py 512 alexnet + +# googlenet +test googlenet.py 64 googlenet +test googlenet.py 128 googlenet + +# smallnet +test smallnet_mnist_cifar.py 64 smallnet +test smallnet_mnist_cifar.py 128 smallnet +test smallnet_mnist_cifar.py 256 smallnet +test smallnet_mnist_cifar.py 512 smallnet diff --git a/benchmark/tensorflow/image/run_multi.sh b/benchmark/tensorflow/image/run_multi.sh new file mode 100755 index 0000000000..69faa43317 --- /dev/null +++ b/benchmark/tensorflow/image/run_multi.sh @@ -0,0 +1,22 @@ +set -e + +function test() { + cfg=$1 + num_gpu=$2 + batch_size=$3 + batch_per_gpu=`expr ${batch_size} / ${num_gpu}` + prefix=$4 + python $cfg --num_gpus=$num_gpu --batch_size=${batch_per_gpu} > logs/${prefix}-4gpu-${batch_size}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +# alexnet +test alexnet_multi_gpu.py 4 512 alexnet +test alexnet_multi_gpu.py 4 1024 alexnet + +# googlenet +test googlenet_multi_gpu.py 4 512 alexnet +test googlenet_multi_gpu.py 4 1024 alexnet diff --git a/benchmark/tensorflow/image/smallnet_mnist_cifar.py b/benchmark/tensorflow/image/smallnet_mnist_cifar.py new file mode 100644 index 0000000000..b539d1bed0 --- /dev/null +++ b/benchmark/tensorflow/image/smallnet_mnist_cifar.py @@ -0,0 +1,273 @@ +from six.moves import xrange # pylint: disable=redefined-builtin +from datetime import datetime +import math +import time + +import tensorflow.python.platform +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 128, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('forward_only', False, + """Only run the forward pass.""") +tf.app.flags.DEFINE_boolean('forward_backward_only', False, + """Only run the forward-forward pass.""") +tf.app.flags.DEFINE_string('data_format', 'NCHW', + """The data format for Convnet operations. + Can be either NHWC or NCHW. + """) +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +parameters = [] + +conv_counter = 1 +pool_counter = 1 +affine_counter = 1 + +def _conv(inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd=0.005, act=True): + global conv_counter + global parameters + name = 'conv' + str(conv_counter) + conv_counter += 1 + with tf.name_scope(name) as scope: + kernel = tf.Variable(tf.truncated_normal([kH, kW, nIn, nOut], + dtype=tf.float32, + stddev=1e-1), name='weights') + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + if FLAGS.data_format == 'NCHW': + strides = [1, 1, dH, dW] + else: + strides = [1, dH, dW, 1] + conv = tf.nn.conv2d(inpOp, kernel, strides, padding=padType, + data_format=FLAGS.data_format) + biases = tf.Variable(tf.constant(0.0, shape=[nOut], dtype=tf.float32), + trainable=True, name='biases') + bias = tf.reshape(tf.nn.bias_add(conv, biases, + data_format=FLAGS.data_format), + conv.get_shape()) + + conv1 = tf.nn.relu(bias, name=scope) if act else bias + + parameters += [kernel, biases] + + return conv1 + +def _affine(inpOp, nIn, nOut, wd=None, act=True): + global affine_counter + global parameters + name = 'affine' + str(affine_counter) + affine_counter += 1 + with tf.name_scope(name) as scope: + kernel = tf.Variable(tf.truncated_normal([nIn, nOut], + dtype=tf.float32, + stddev=1e-1), name='weights') + + if wd is not None: + weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + + biases = tf.Variable(tf.constant(0.0, shape=[nOut], dtype=tf.float32), + trainable=True, name='biases') + + affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else tf.matmul(inpOp, kernel) + biases + + parameters += [kernel, biases] + + return affine1 + +def _mpool(inpOp, kH, kW, dH, dW, padding): + global pool_counter + global parameters + name = 'pool' + str(pool_counter) + pool_counter += 1 + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.max_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + + +def _apool(inpOp, kH, kW, dH, dW, padding): + global pool_counter + global parameters + name = 'pool' + str(pool_counter) + pool_counter += 1 + if FLAGS.data_format == 'NCHW': + ksize = [1, 1, kH, kW] + strides = [1, 1, dH, dW] + else: + ksize = [1, kH, kW, 1] + strides = [1, dH, dW, 1] + return tf.nn.avg_pool(inpOp, + ksize=ksize, + strides=strides, + padding=padding, + data_format=FLAGS.data_format, + name=name) + +def _norm(name, l_input, lsize=4): + return tf.nn.lrn(l_input, lsize, bias=1.0, + alpha=0.001 / 9.0, + beta=0.75, name=name) + +def loss(logits, labels): + batch_size = tf.size(labels) + labels = tf.expand_dims(labels, 1) + indices = tf.expand_dims(tf.range(0, batch_size, 1), 1) + concated = tf.concat(1, [indices, labels]) + onehot_labels = tf.sparse_to_dense( + concated, tf.pack([batch_size, 10]), 1.0, 0.0) + cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits, + onehot_labels, + name='xentropy') + loss = tf.reduce_mean(cross_entropy, name='xentropy_mean') + return loss + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + +def inference(images): + conv1 = _conv (images, 3, 32, 5, 5, 1, 1, 'SAME') + pool1 = _mpool(conv1, 3, 3, 2, 2, 'SAME') + conv2 = _conv (pool1, 32, 32, 5, 5, 1, 1, 'SAME') + pool2 = _apool(conv2, 3, 3, 2, 2, 'SAME') + conv3 = _conv (pool2, 32, 64, 5, 5, 1, 1, 'SAME') + pool3 = _apool(conv3, 3, 3, 2, 2, 'SAME') + resh1 = tf.reshape(pool3, [-1, 64 * 4 * 4]) + affn1 = _affine(resh1, 64 * 4 * 4, 64) + affn2 = _affine(affn1, 64, 10, act=False) + + print ('conv1:', get_incoming_shape(conv1)) + print ('pool1:', get_incoming_shape(pool1)) + print ('conv2:', get_incoming_shape(conv2)) + print ('pool2:', get_incoming_shape(pool2)) + print ('conv3:', get_incoming_shape(conv3)) + print ('pool3:', get_incoming_shape(pool3)) + + return affn2 + + +def time_tensorflow_run(session, target, info_string): + num_steps_burn_in = 10 + total_duration = 0.0 + total_duration_squared = 0.0 + if not isinstance(target, list): + target = [target] + target_op = tf.group(*target) + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _ = session.run(target_op) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + print ('%s: step %d, duration = %.3f' % + (datetime.now(), i - num_steps_burn_in, duration)) + total_duration += duration + total_duration_squared += duration * duration + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), info_string, FLAGS.num_batches, mn, sd)) + +def run_benchmark(): + global parameters + with tf.Graph().as_default(): + # Generate some dummy images. + image_size = 32 + # Note that our padding definition is slightly different the cuda-convnet. + # In order to force the model to start with the same activations sizes, + # we add 3 to the image_size and employ VALID padding above. + if FLAGS.data_format == 'NCHW': + image_shape = [FLAGS.batch_size, 3, image_size, image_size] + else: + image_shape = [FLAGS.batch_size, image_size, image_size, 3] + + images = tf.get_variable('image', image_shape, + initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32), + dtype=tf.float32, + trainable=False) + + labels = tf.get_variable('label', [FLAGS.batch_size], + initializer=tf.constant_initializer(1), + dtype=tf.int32, + trainable=False) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(images) + + objective = loss(last_layer, labels) + + # Compute gradients. + # opt = tf.train.GradientDescentOptimizer(0.001) + opt = tf.train.MomentumOptimizer(0.001, 0.9) + grads = opt.compute_gradients(objective) + global_step = tf.get_variable('global_step', [], + initializer=tf.constant_initializer(0.0, dtype=tf.float32), + trainable=False, dtype=tf.float32) + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Track the moving averages of all trainable variables. + variable_averages = tf.train.ExponentialMovingAverage( + 0.9, global_step) + variables_averages_op = variable_averages.apply(tf.trainable_variables()) + + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + + run_forward = True + run_forward_backward = True + if FLAGS.forward_only and FLAGS.forward_backward_only: + raise ValueError("Cannot specify --forward_only and " + "--forward_backward_only at the same time.") + if FLAGS.forward_only: + run_forward_backward = False + elif FLAGS.forward_backward_only: + run_forward = False + + if run_forward: + # Run the forward benchmark. + time_tensorflow_run(sess, last_layer, "Forward") + + if run_forward_backward: + with tf.control_dependencies([apply_gradient_op, variables_averages_op]): + train_op = tf.no_op(name='train') + time_tensorflow_run(sess, [train_op, objective], "Forward-backward") + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/rnn/README.md b/benchmark/tensorflow/rnn/README.md new file mode 100644 index 0000000000..b5314d5446 --- /dev/null +++ b/benchmark/tensorflow/rnn/README.md @@ -0,0 +1,5 @@ +You also should install tflearn: + +```bash +pip install tflearn +``` diff --git a/benchmark/tensorflow/rnn/reader.py b/benchmark/tensorflow/rnn/reader.py new file mode 100755 index 0000000000..0d8308046e --- /dev/null +++ b/benchmark/tensorflow/rnn/reader.py @@ -0,0 +1,90 @@ +import os.path +import io +import numpy as np +import tensorflow as tf + +# tflearn +import tflearn +from tflearn.data_utils import to_categorical, pad_sequences +from tflearn.datasets import imdb + + +FLAGS = tf.app.flags.FLAGS + +class DataSet(object): + def __init__(self, data, labels): + assert data.shape[0] == labels.shape[0], ( + 'data.shape: %s labels.shape: %s' % (data.shape, + labels.shape)) + self._num_examples = data.shape[0] + + self._data = data + self._labels = labels + self._epochs_completed = 0 + self._index_in_epoch = 0 + + @property + def data(self): + return self._data + + @property + def labels(self): + return self._labels + + @property + def num_examples(self): + return self._num_examples + + @property + def epochs_completed(self): + return self._epochs_completed + + def next_batch(self, batch_size): + assert batch_size <= self._num_examples + + start = self._index_in_epoch + self._index_in_epoch += batch_size + if self._index_in_epoch > self._num_examples: + # Finished epoch + self._epochs_completed += 1 + # Shuffle the data + perm = np.arange(self._num_examples) + np.random.shuffle(perm) + self._data = self._data[perm] + self._labels = self._labels[perm] + # Start next epoch + start = 0 + self._index_in_epoch = batch_size + + end = self._index_in_epoch + + return self._data[start:end], self._labels[start:end] + + +def create_datasets(file_path, vocab_size=30000, val_fraction=0.0): + + # IMDB Dataset loading + train, test, _ = imdb.load_data(path=file_path, n_words=vocab_size, + valid_portion=val_fraction, sort_by_len=False) + trainX, trainY = train + testX, testY = test + + # Data preprocessing + # Sequence padding + trainX = pad_sequences(trainX, maxlen=FLAGS.max_len, value=0.) + testX = pad_sequences(testX, maxlen=FLAGS.max_len, value=0.) + # Converting labels to binary vectors + trainY = to_categorical(trainY, nb_classes=2) + testY = to_categorical(testY, nb_classes=2) + + train_dataset = DataSet(trainX, trainY) + + return train_dataset + + +def main(): + create_datasets('imdb.pkl') + + +if __name__ == "__main__": + main() diff --git a/benchmark/tensorflow/rnn/rnn.py b/benchmark/tensorflow/rnn/rnn.py new file mode 100755 index 0000000000..5377187f39 --- /dev/null +++ b/benchmark/tensorflow/rnn/rnn.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +from six.moves import xrange # pylint: disable=redefined-builtin +import math +import time +import numpy as np +from datetime import datetime + +import reader +import tensorflow as tf +from tensorflow.python.ops import rnn + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 128, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('num_layers', 1, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('max_len', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('forward_only', False, + """Only run the forward pass.""") +tf.app.flags.DEFINE_boolean('forward_backward_only', False, + """Only run the forward-forward pass.""") +tf.app.flags.DEFINE_integer('hidden_size', 128, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('emb_size', 128, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") + +VOCAB_SIZE=30000 +NUM_CLASS=2 + +def get_feed_dict(x_data, y_data=None): + feed_dict = {} + + if y_data is not None: + feed_dict[y_input] = y_data + + for i in xrange(x_data.shape[0]): + feed_dict[x_input[i]] = x_data[i, :, :] + + return feed_dict + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + + +# Note input * W is done in LSTMCell, +# which is different from PaddlePaddle +def single_lstm(name, incoming, n_units, use_peepholes=True, + return_seq=False, return_state=False): + with tf.name_scope(name) as scope: + cell = tf.nn.rnn_cell.LSTMCell(n_units, use_peepholes=use_peepholes) + output, _cell_state = rnn.rnn(cell, incoming, dtype=tf.float32) + out = output if return_seq else output[-1] + return (out, _cell_state) if return_state else out + +def lstm(name, incoming, n_units, use_peepholes=True, + return_seq=False, return_state=False, num_layers=1): + with tf.name_scope(name) as scope: + lstm_cell = tf.nn.rnn_cell.LSTMCell(n_units, use_peepholes=use_peepholes) + cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * num_layers) + initial_state = cell.zero_state(FLAGS.batch_size, dtype=tf.float32) + if not isinstance(incoming, list): + # if the input is embeding, the Tensor shape : [None, time_step, emb_size] + incoming = [tf.squeeze(input_, [1]) + for input_ in tf.split(1, FLAGS.max_len, incoming)] + outputs, state = tf.nn.rnn(cell, incoming, initial_state=initial_state, + dtype=tf.float32) + out = outputs if return_seq else outputs[-1] + return (out, _cell_state) if return_state else out + + +def embedding(name, incoming, vocab_size, emb_size): + with tf.name_scope(name) as scope: + #with tf.device("/cpu:0"): + embedding = tf.get_variable( + name+'_emb', [vocab_size, emb_size], dtype=tf.float32) + out = tf.nn.embedding_lookup(embedding, incoming) + return out + +def fc(name, inpOp, nIn, nOut, act=True): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w', [nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + biases = tf.get_variable(name + '_b', [nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32,trainable=True) + + net = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \ + tf.matmul(inpOp, kernel) + biases + + return net + +def inference(seq): + net = embedding('emb', seq, VOCAB_SIZE, FLAGS.emb_size) + print "emb:", get_incoming_shape(net) + net = lstm('lstm', net, FLAGS.hidden_size, num_layers=FLAGS.num_layers) + print "lstm:", get_incoming_shape(net) + net = fc('fc1', net, FLAGS.hidden_size, 2) + return net + +def loss(logits, labels): + # one label index for one sample + labels = tf.cast(labels, tf.float32) + cross_entropy = tf.nn.softmax_cross_entropy_with_logits( + logits, labels, name='cross_entropy_per_example') + cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') + tf.add_to_collection('losses', cross_entropy_mean) + return tf.add_n(tf.get_collection('losses'), name='total_loss') + + +def time_tensorflow_run(session, target, x_input, y_input, info_string): + num_steps_burn_in = 50 + total_duration = 0.0 + total_duration_squared = 0.0 + if not isinstance(target, list): + target = [target] + target_op = tf.group(*target) + train_dataset = reader.create_datasets("imdb.pkl", VOCAB_SIZE) + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + data, label = train_dataset.next_batch(FLAGS.batch_size) + _ = session.run(target_op, feed_dict={x_input:data, y_input:label}) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + print ('%s: step %d, duration = %.3f' % + (datetime.now(), i - num_steps_burn_in, duration)) + total_duration += duration + total_duration_squared += duration * duration + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), info_string, FLAGS.num_batches, mn, sd)) + + +def run_benchmark(): + with tf.Graph().as_default(): + global_step=0 + with tf.device('/cpu:0'): + global_step = tf.Variable(0, trainable=False) + with tf.device('/gpu:0'): + #x_input = tf.placeholder(tf.int32, [None, FLAGS.max_len], name="x_input") + #y_input = tf.placeholder(tf.int32, [None, NUM_CLASS], name="y_input") + x_input = tf.placeholder(tf.int32, [FLAGS.batch_size, FLAGS.max_len], name="x_input") + y_input = tf.placeholder(tf.int32, [FLAGS.batch_size, NUM_CLASS], name="y_input") + # Generate some dummy sequnce. + + + last_layer = inference(x_input) + + objective = loss(last_layer, y_input) + opt = tf.train.AdamOptimizer(0.001) + grads = opt.compute_gradients(objective) + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + init = tf.initialize_all_variables() + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + + run_forward = True + run_forward_backward = True + if FLAGS.forward_only and FLAGS.forward_backward_only: + raise ValueError("Cannot specify --forward_only and " + "--forward_backward_only at the same time.") + if FLAGS.forward_only: + run_forward_backward = False + elif FLAGS.forward_backward_only: + run_forward = False + + if run_forward: + time_tensorflow_run(sess, last_layer, x_input, y_input, "Forward") + + if run_forward_backward: + with tf.control_dependencies([apply_gradient_op]): + train_op = tf.no_op(name='train') + time_tensorflow_run(sess, [train_op, objective], x_input, y_input, "Forward-backward") + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() + diff --git a/benchmark/tensorflow/rnn/rnn_multi_gpu.py b/benchmark/tensorflow/rnn/rnn_multi_gpu.py new file mode 100755 index 0000000000..97ba5d4c29 --- /dev/null +++ b/benchmark/tensorflow/rnn/rnn_multi_gpu.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +from six.moves import xrange # pylint: disable=redefined-builtin +import re +import math +import time +import numpy as np +from datetime import datetime + +import reader +import tensorflow as tf +from tensorflow.python.ops import rnn + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_integer('batch_size', 64, + """Batch size.""") +tf.app.flags.DEFINE_integer('num_batches', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('num_layers', 1, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('max_len', 100, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('hidden_size', 128, + """Number of batches to run.""") +tf.app.flags.DEFINE_integer('emb_size', 64, + """Number of batches to run.""") +tf.app.flags.DEFINE_boolean('log_device_placement', False, + """Whether to log device placement.""") +tf.app.flags.DEFINE_integer('num_gpus', 4, + """How many GPUs to use.""") + +VOCAB_SIZE=30000 +NUM_CLASS=2 + + +NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN=50000 +NUM_EPOCHS_PER_DECAY=50 +INITIAL_LEARNING_RATE = 0.1 +LEARNING_RATE_DECAY_FACTOR = 0.1 +TOWER_NAME = 'tower' + +train_dataset = reader.create_datasets("imdb.pkl", VOCAB_SIZE) + +def get_incoming_shape(incoming): + """ Returns the incoming data shape """ + if isinstance(incoming, tf.Tensor): + return incoming.get_shape().as_list() + elif type(incoming) in [np.array, list, tuple]: + return np.shape(incoming) + else: + raise Exception("Invalid incoming layer.") + + +# Note input * W is done in LSTMCell, +# which is different from PaddlePaddle +def single_lstm(name, incoming, n_units, use_peepholes=True, + return_seq=False, return_state=False): + with tf.name_scope(name) as scope: + cell = tf.nn.rnn_cell.LSTMCell(n_units, use_peepholes=use_peepholes) + output, _cell_state = rnn.rnn(cell, incoming, dtype=tf.float32) + out = output if return_seq else output[-1] + return (out, _cell_state) if return_state else out + + +def lstm(name, incoming, n_units, use_peepholes=True, + return_seq=False, return_state=False, num_layers=1): + with tf.name_scope(name) as scope: + lstm_cell = tf.nn.rnn_cell.LSTMCell(n_units, use_peepholes=use_peepholes) + cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * num_layers) + initial_state = cell.zero_state(FLAGS.batch_size, dtype=tf.float32) + if not isinstance(incoming, list): + # if the input is embeding, the Tensor shape : [None, time_step, emb_size] + incoming = [tf.squeeze(input_, [1]) + for input_ in tf.split(1, FLAGS.max_len, incoming)] + outputs, state = tf.nn.rnn(cell, incoming, initial_state=initial_state, + dtype=tf.float32) + out = outputs if return_seq else outputs[-1] + return (out, _cell_state) if return_state else out + + +def embedding(name, incoming, vocab_size, emb_size): + with tf.name_scope(name) as scope: + #with tf.device("/cpu:0"): + embedding = tf.get_variable( + name+'_emb', [vocab_size, emb_size], dtype=tf.float32) + out = tf.nn.embedding_lookup(embedding, incoming) + return out + + +def fc(name, inpOp, nIn, nOut, act=True): + with tf.name_scope(name) as scope: + kernel = tf.get_variable(name + '_w', [nIn, nOut], + initializer=tf.truncated_normal_initializer(stddev=0.01, dtype=tf.float32), + dtype=tf.float32) + + biases = tf.get_variable(name + '_b', [nOut], + initializer=tf.constant_initializer(value=0.0, dtype=tf.float32), + dtype=tf.float32,trainable=True) + + net = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \ + tf.matmul(inpOp, kernel) + biases + + return net + + +def inference(seq): + net = embedding('emb', seq, VOCAB_SIZE, FLAGS.emb_size) + print "emb:", get_incoming_shape(net) + net = lstm('lstm', net, FLAGS.hidden_size, num_layers=FLAGS.num_layers) + print "lstm:", get_incoming_shape(net) + net = fc('fc1', net, FLAGS.hidden_size, 2) + return net + + +def loss(logits, labels): + # one label index for one sample + #labels = tf.cast(labels, tf.int64) + # cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( + # logits, labels, name='cross_entropy_per_example') + labels = tf.cast(labels, tf.float32) + cross_entropy = tf.nn.softmax_cross_entropy_with_logits( + logits, labels, name='cross_entropy_per_example') + cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') + tf.add_to_collection('losses', cross_entropy_mean) + return tf.add_n(tf.get_collection('losses'), name='total_loss') + + +def tower_loss(scope): + """Calculate the total loss on a single tower running the model. + Args: + scope: unique prefix string identifying the tower, e.g. 'tower_0' + Returns: + Tensor of shape [] containing the total loss for a batch of data + """ + data, label = train_dataset.next_batch(FLAGS.batch_size) + + # Build a Graph that computes the logits predictions from the + # inference model. + last_layer = inference(data) + + # Build the portion of the Graph calculating the losses. Note that we will + # assemble the total_loss using a custom function below. + #_ = loss(last_layer, label) + _ = loss(last_layer, label) + + # Assemble all of the losses for the current tower only. + losses = tf.get_collection('losses', scope) + + # Calculate the total loss for the current tower. + total_loss = tf.add_n(losses, name='total_loss') + + # Compute the moving average of all individual losses and the total loss. + loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') + loss_averages_op = loss_averages.apply(losses + [total_loss]) + + # Attach a scalar summary to all individual losses and the total loss; do the + # same for the averaged version of the losses. + for l in losses + [total_loss]: + # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training + # session. This helps the clarity of presentation on tensorboard. + loss_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', l.op.name) + # Name each loss as '(raw)' and name the moving average version of the loss + # as the original loss name. + tf.scalar_summary(loss_name +' (raw)', l) + #tf.scalar_summary(loss_name, loss_averages.average(l)) + + with tf.control_dependencies([loss_averages_op]): + total_loss = tf.identity(total_loss) + return total_loss + + +def average_gradients(tower_grads): + """Calculate the average gradient for each shared variable across all towers. + Note that this function provides a synchronization point across all towers. + Args: + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over individual gradients. The inner list is over the gradient + calculation for each tower. + Returns: + List of pairs of (gradient, variable) where the gradient has been averaged + across all towers. + """ + average_grads = [] + for grad_and_vars in zip(*tower_grads): + # Note that each grad_and_vars looks like the following: + # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) + grads = [] + for g, _ in grad_and_vars: + # Add 0 dimension to the gradients to represent the tower. + expanded_g = tf.expand_dims(g, 0) + + # Append on a 'tower' dimension which we will average over below. + grads.append(expanded_g) + + # Average over the 'tower' dimension. + grad = tf.concat(0, grads) + grad = tf.reduce_mean(grad, 0) + + # Keep in mind that the Variables are redundant because they are shared + # across towers. So .. we will just return the first tower's pointer to + # the Variable. + v = grad_and_vars[0][1] + grad_and_var = (grad, v) + average_grads.append(grad_and_var) + return average_grads + +def time_tensorflow_run(session, target): + num_steps_burn_in = 80 + total_duration = 0.0 + total_duration_squared = 0.0 + for i in xrange(FLAGS.num_batches + num_steps_burn_in): + start_time = time.time() + _ = session.run(target, feed_dict={x_input:data, y_input:label}) + _, loss_value = session.run(target) + duration = time.time() - start_time + if i > num_steps_burn_in: + if not i % 10: + num_examples_per_step = FLAGS.batch_size * FLAGS.num_gpus + examples_per_sec = num_examples_per_step / duration + # sec_per_batch = duration / FLAGS.num_gpus + sec_per_batch = duration + + format_str = ('%s: step %d, loss= %.2f (%.1f examples/sec; %.3f ' + 'sec/batch batch_size= %d)') + print (format_str % + (datetime.now(), i - num_steps_burn_in, + loss_value, duration, sec_per_batch, num_examples_per_step)) + + total_duration += duration + total_duration_squared += duration * duration + + mn = total_duration / FLAGS.num_batches + vr = total_duration_squared / FLAGS.num_batches - mn * mn + sd = math.sqrt(vr) + print ('%s: FwdBwd across %d steps, %.3f +/- %.3f sec / batch' % + (datetime.now(), FLAGS.num_batches, mn, sd)) + +def run_benchmark(): + with tf.Graph().as_default(), tf.device('/cpu:0'): + # Create a variable to count the number of train() calls. This equals the + # number of batches processed * FLAGS.num_gpus. + global_step = tf.get_variable( + 'global_step', [], + initializer=tf.constant_initializer(0), trainable=False) + + # Calculate the learning rate schedule. + num_batches_per_epoch = (NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / + FLAGS.batch_size) + decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY) + + # Create an optimizer that performs gradient descent. + opt = tf.train.AdamOptimizer(0.001) + + #train_dataset = reader.create_datasets("imdb.pkl", VOCAB_SIZE) + + # Calculate the gradients for each model tower. + tower_grads = [] + for i in xrange(FLAGS.num_gpus): + with tf.device('/gpu:%d' % i): + with tf.name_scope('%s_%d' % (TOWER_NAME, i)) as scope: + # Calculate the loss for one tower of the model. This function + # constructs the entire model but shares the variables across + # all towers. + loss = tower_loss(scope) + + # Reuse variables for the next tower. + tf.get_variable_scope().reuse_variables() + + # Retain the summaries from the final tower. + # summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope) + + # Calculate the gradients for the batch of data on this tower. + grads = opt.compute_gradients(loss) + + # Keep track of the gradients across all towers. + tower_grads.append(grads) + + # We must calculate the mean of each gradient. Note that this is the + # synchronization point across all towers. + grads = average_gradients(tower_grads) + + # Apply the gradients to adjust the shared variables. + apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) + + # Group all updates to into a single train op. + train_op = tf.group(apply_gradient_op) + + # Build an initialization operation. + init = tf.initialize_all_variables() + + # Start running operations on the Graph. allow_soft_placement must be set to + # True to build towers on GPU, as some of the ops do not have GPU + # implementations. + sess = tf.Session(config=tf.ConfigProto( + allow_soft_placement=True, + log_device_placement=FLAGS.log_device_placement)) + sess.run(init) + time_tensorflow_run(sess, [train_op, loss]) + + +def main(_): + run_benchmark() + + +if __name__ == '__main__': + tf.app.run() diff --git a/benchmark/tensorflow/rnn/run.sh b/benchmark/tensorflow/rnn/run.sh new file mode 100755 index 0000000000..bb4c69cb95 --- /dev/null +++ b/benchmark/tensorflow/rnn/run.sh @@ -0,0 +1,29 @@ +set -e + +function test() { + lstm_num=$1 + batch_size=$2 + hid_size=$3 + prefix=$4 + python rnn.py --num_layers=${lstm_num} --batch_size=$batch_size \ + --hidden_size=${hid_size} \ + --forward_backward_only=1 \ + > logs/1gpu-${lstm_num}lstm-batch${batch_size}-hid${hid_size}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +#--lstm_num--batch_size--hidden_size--# +test 2 64 256 +test 2 64 512 +test 2 64 1280 + +test 2 128 256 +test 2 128 512 +test 2 128 1280 + +test 2 256 256 +test 2 256 512 +test 2 256 1280 diff --git a/benchmark/tensorflow/rnn/run_multi.sh b/benchmark/tensorflow/rnn/run_multi.sh new file mode 100755 index 0000000000..f7f52e01e3 --- /dev/null +++ b/benchmark/tensorflow/rnn/run_multi.sh @@ -0,0 +1,28 @@ +set -e + +function test() { + num_gpu=$1 + lstm_num=$2 + hid_size=$3 + batch_per_gpu=`expr ${batch_size} / ${num_gpu}` + batch_size=$4 + python rnn_multi_gpu.py --num_layers=${lstm_num} --batch_size=$batch_per_gpu \ + --num_gpus=${num_gpu} \ + --hidden_size=${hid_size} \ + --forward_backward_only=1 \ + > logs/${num_gpu}gpu-${lstm_num}lstm-hid${hid_size}-batch${batch_size}.log 2>&1 +} + +if [ ! -d "logs" ]; then + mkdir logs +fi + +#--num_gpus--lstm_num--hiddne_size--batch_size--# +test 4 2 256 128 +test 4 2 256 256 +test 4 2 256 512 + +test 4 2 512 128 +test 4 2 512 256 +test 4 2 512 512 + -- GitLab From dc6859f1e068f2c356f38e7d44cf2a005a49020c Mon Sep 17 00:00:00 2001 From: zhouti Date: Mon, 7 Nov 2016 15:33:00 +0800 Subject: [PATCH 0002/1503] Added paddle on kubernetes tutorial. --- doc/kubernetes_on_paddle.md | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 doc/kubernetes_on_paddle.md diff --git a/doc/kubernetes_on_paddle.md b/doc/kubernetes_on_paddle.md new file mode 100644 index 0000000000..2f3109e87b --- /dev/null +++ b/doc/kubernetes_on_paddle.md @@ -0,0 +1,200 @@ +# Paddle On Kubernetes + +>In this article, we will introduce how to run Paddle training job on single CPU machine using Kubernetes. In next article, we will introduce how to run Paddle training job on distributed cluster. + +## Build Docker Image + +In distributed Kubernetes cluster, we will use Ceph or other shared storage system for storing training related data so that all processes in Paddle training can retrieve data from Ceph. In this example, we will only demo training job on single machine. In order to simplify the requirement of the environment, we will directly put training data into Paddle's Docker Image, so we need to create a Paddle Docker image that already includes the training data. + +Paddle's [Quick Start Tutorial](http://www.paddlepaddle.org/doc/demo/quick_start/index_en.html) introduces how to download and train data by using script from Paddle's source code. +And `paddledev/paddle:cpu-demo-latest` image has the Paddle source code and demo. (Caution: Default Paddle image `paddledev/paddle:cpu-latest` doesn't include the source code, Paddle's different versions of image can be referred here: [Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html)), so we run this container and download the training data, and then commit the whole container to be a new Docker image. + +### Run Docker Container + +``` +$ docker run --name quick_start_data -it paddledev/paddle:cpu-demo-latest +``` + +### Download Training Data + +Getting into `/root/paddle/demo/quick_start/data` Directory,using `get_data.sh` to download training data. + +``` +$ root@fbd1f2bb71f4:~/paddle/demo/quick_start/data# ./get_data.sh + +Downloading Amazon Electronics reviews data... +--2016-10-31 01:33:43-- http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +Resolving snap.stanford.edu (snap.stanford.edu)... 171.64.75.80 +Connecting to snap.stanford.edu (snap.stanford.edu)|171.64.75.80|:80... connected. +HTTP request sent, awaiting response... 200 OK +Length: 495854086 (473M) [application/x-gzip] +Saving to: 'reviews_Electronics_5.json.gz' + + 10% [=======> ] 874,279 64.7KB/s eta 2h 13m + +``` + +### Modify Startup Script + +After downloading the data,modify `/root/paddle/demo/quick_start/train.sh` file contents are as follows (one more cd cmd): +``` +set -e +cd /root/paddle/demo/quick_start +cfg=trainer_config.lr.py +#cfg=trainer_config.emb.py +#cfg=trainer_config.cnn.py +#cfg=trainer_config.lstm.py +#cfg=trainer_config.bidi-lstm.py +#cfg=trainer_config.db-lstm.py +paddle train \ + --config=$cfg \ + --save_dir=./output \ + --trainer_count=4 \ + --log_period=20 \ + --num_passes=15 \ + --use_gpu=false \ + --show_parameter_stats_period=100 \ + --test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' +``` + +### Commit Docker Image + +``` +$ docker commit quick_start_data mypaddle/paddle:quickstart +``` + +## Use Kubernetes For Training + +>We will use Kubernetes job for training process, following steps shows how to do the training with Kubernetes. + +### Create Yaml Files + +The output result in container will be demolished when job finished (container stopped running), so we need to mount the volume out to the local disk when creating the container to store the training result. Using our previously created image, we can create a [Kubernetes Job](http://kubernetes.io/docs/user-guide/jobs/#what-is-a-job), the yaml contents are as follows: + +``` +apiVersion: batch/v1 +kind: Job +metadata: + name: quickstart +spec: + parallelism: 1 + completions: 1 + template: + metadata: + name: quickstart + spec: + volumes: + - name: output + hostPath: + path: /home/work/paddle_output + containers: + - name: pi + image: mypaddle/paddle:quickstart + command: ["bin/bash", "-c", "/root/paddle/demo/quick_start/train.sh"] + volumeMounts: + - name: output + mountPath: /root/paddle/demo/quick_start/output + restartPolicy: Never +``` + +### Start Paddle Job + +Using the above yaml file to start the Kubernetes job. + +``` +$ kubectl create -f paddle.yaml +``` + +Get the detailed status of the job: + +``` +$ kubectl get job +NAME DESIRED SUCCESSFUL AGE +quickstart 1 0 58s + +$ kubectl describe job quickstart +Name: quickstart +Namespace: default +Image(s): registry.baidu.com/public/paddle:cpu-demo-latest +Selector: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84 +Parallelism: 1 +Completions: 1 +Start Time: Mon, 31 Oct 2016 11:20:16 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Pods Statuses: 0 Running / 1 Succeeded / 0 Failed +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +Events: + FirstSeen LastSeen Count From SubobjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: quickstart-fa0wx +``` + +### Get Training Result + +We can use kubectl command to take a look at the status of related pod. + +``` +$ kubectl describe pod quickstart-fa0wx +Name: quickstart-fa0wx +Namespace: default +Node: paddle-demo-let02/10.206.202.44 +Start Time: Mon, 31 Oct 2016 11:20:17 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Status: Succeeded +IP: 10.0.0.9 +Controllers: Job/quickstart +Containers: + quickstart: + Container ID: docker://b8561f5c79193550d64fa47418a9e67ebdd71546186e840f88de5026b8097465 + Image: registry.baidu.com/public/paddle:cpu-demo-latest + Image ID: docker://18e457ce3d362ff5f3febf8e7f85ffec852f70f3b629add10aed84f930a68750 + Port: + Command: + bin/bash + -c + /root/paddle/demo/quick_start/train.sh + QoS Tier: + cpu: BestEffort + memory: BestEffort + State: Terminated + Reason: Completed + Exit Code: 0 + Started: Mon, 31 Oct 2016 11:20:20 +0800 + Finished: Mon, 31 Oct 2016 11:21:46 +0800 + Ready: False + Restart Count: 0 + Environment Variables: +Conditions: + Type Status + Ready False +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +``` + +We can also ssh to Kubernetes node to take a look at the training result. + +``` +[root@paddle-demo-let02 paddle_output]# ll +total 60 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00000 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00001 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00002 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00003 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00004 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00005 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00006 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00007 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00008 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00009 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00010 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00011 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 +``` -- GitLab From 891867ecd636eea06ba7550811e83888e672dccc Mon Sep 17 00:00:00 2001 From: zhangjinchao01 Date: Wed, 9 Nov 2016 13:44:54 +0800 Subject: [PATCH 0003/1503] update srl demo --- .../data/extract_dict_feature.py | 60 +++--- .../data/extract_pairs.py | 16 +- demo/semantic_role_labeling/data/get_data.sh | 6 +- demo/semantic_role_labeling/dataprovider.py | 38 ++-- demo/semantic_role_labeling/db_lstm.py | 180 +++++++++++++----- demo/semantic_role_labeling/predict.py | 63 ++++-- demo/semantic_role_labeling/predict.sh | 11 +- demo/semantic_role_labeling/train.sh | 18 +- .../semantic_role_labeling.md | 85 +++++---- 9 files changed, 314 insertions(+), 163 deletions(-) diff --git a/demo/semantic_role_labeling/data/extract_dict_feature.py b/demo/semantic_role_labeling/data/extract_dict_feature.py index 2982e54c66..daca5f01cf 100644 --- a/demo/semantic_role_labeling/data/extract_dict_feature.py +++ b/demo/semantic_role_labeling/data/extract_dict_feature.py @@ -17,24 +17,15 @@ import os from optparse import OptionParser -def extract_dict_features(pair_file, feature_file, src_dict_file, - tgt_dict_file): - src_dict = set() - tgt_dict = set() - - with open(pair_file) as fin, open(feature_file, 'w') as feature_out, open( - src_dict_file, 'w') as src_dict_out, open(tgt_dict_file, - 'w') as tgt_dict_out: +def extract_dict_features(pair_file, feature_file): + + with open(pair_file) as fin, open(feature_file, 'w') as feature_out: for line in fin: - sentence, labels = line.strip().split('\t') + sentence, predicate, labels = line.strip().split('\t') sentence_list = sentence.split() labels_list = labels.split() - src_dict.update(sentence_list) - tgt_dict.update(labels_list) - verb_index = labels_list.index('B-V') - verb_feature = sentence_list[verb_index] mark = [0] * len(labels_list) if verb_index > 0: @@ -42,47 +33,50 @@ def extract_dict_features(pair_file, feature_file, src_dict_file, ctx_n1 = sentence_list[verb_index - 1] else: ctx_n1 = 'bos' - ctx_n1_feature = ctx_n1 + + if verb_index > 1: + mark[verb_index - 2] = 1 + ctx_n2 = sentence_list[verb_index - 2] + else: + ctx_n2 = 'bos' mark[verb_index] = 1 - ctx_0_feature = sentence_list[verb_index] + ctx_0 = sentence_list[verb_index] if verb_index < len(labels_list) - 2: mark[verb_index + 1] = 1 ctx_p1 = sentence_list[verb_index + 1] else: ctx_p1 = 'eos' - ctx_p1_feature = ctx_p1 + + if verb_index < len(labels_list) - 3: + mark[verb_index + 2] = 1 + ctx_p2 = sentence_list[verb_index + 2] + else: + ctx_p2 = 'eos' + feature_str = sentence + '\t' \ - + verb_feature + '\t' \ - + ctx_n1_feature + '\t' \ - + ctx_0_feature + '\t' \ - + ctx_p1_feature + '\t' \ + + predicate + '\t' \ + + ctx_n2 + '\t' \ + + ctx_n1 + '\t' \ + + ctx_0 + '\t' \ + + ctx_p1 + '\t' \ + + ctx_p2 + '\t' \ + ' '.join([str(i) for i in mark]) + '\t' \ + labels feature_out.write(feature_str + '\n') - src_dict_out.write('\n') - src_dict_out.write('\n'.join(list(src_dict))) - - tgt_dict_out.write('\n'.join(list(tgt_dict))) if __name__ == '__main__': - usage = '-p pair_file -f feature_file -s source dictionary -t target dictionary ' + usage = '-p pair_file -f feature_file' parser = OptionParser(usage) parser.add_option('-p', dest='pair_file', help='the pair file') - parser.add_option( - '-f', dest='feature_file', help='the file to store feature') - parser.add_option( - '-s', dest='src_dict', help='the file to store source dictionary') - parser.add_option( - '-t', dest='tgt_dict', help='the file to store target dictionary') + parser.add_option('-f', dest='feature_file', help='the feature file') (options, args) = parser.parse_args() - extract_dict_features(options.pair_file, options.feature_file, - options.src_dict, options.tgt_dict) + extract_dict_features(options.pair_file, options.feature_file) diff --git a/demo/semantic_role_labeling/data/extract_pairs.py b/demo/semantic_role_labeling/data/extract_pairs.py index 4d1bef8f95..86ab00ce41 100644 --- a/demo/semantic_role_labeling/data/extract_pairs.py +++ b/demo/semantic_role_labeling/data/extract_pairs.py @@ -51,7 +51,7 @@ def read_sentences(words_file): for line in fin: line = line.strip() if line == '': - sentences.append(s.lower()) + sentences.append(s) s = '' else: s += line + ' ' @@ -64,6 +64,11 @@ def transform_labels(sentences, labels): if len(labels[i]) == 1: continue else: + verb_list = [] + for x in labels[i][0]: + if x !='-': + verb_list.append(x) + for j in xrange(1, len(labels[i])): label_list = labels[i][j] current_tag = 'O' @@ -88,8 +93,7 @@ def transform_labels(sentences, labels): is_in_bracket = True else: print 'error:', ll - - sen_lab_pair.append((sentences[i], label_seq)) + sen_lab_pair.append((sentences[i], verb_list[j-1], label_seq)) return sen_lab_pair @@ -97,9 +101,9 @@ def write_file(sen_lab_pair, output_file): with open(output_file, 'w') as fout: for x in sen_lab_pair: sentence = x[0] - label_seq = ' '.join(x[1]) - assert len(sentence.split()) == len(x[1]) - fout.write(sentence + '\t' + label_seq + '\n') + label_seq = ' '.join(x[2]) + assert len(sentence.split()) == len(x[2]) + fout.write(sentence + '\t' + x[1]+'\t' +label_seq + '\n') if __name__ == '__main__': diff --git a/demo/semantic_role_labeling/data/get_data.sh b/demo/semantic_role_labeling/data/get_data.sh index 268c0995e2..55e33f4685 100644 --- a/demo/semantic_role_labeling/data/get_data.sh +++ b/demo/semantic_role_labeling/data/get_data.sh @@ -14,6 +14,10 @@ # limitations under the License. set -e wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz +wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/verbDict.txt --no-check-certificate +wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/targetDict.txt --no-check-certificate +wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/wordDict.txt --no-check-certificate +wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/emb --no-check-certificate tar -xzvf conll05st-tests.tar.gz rm conll05st-tests.tar.gz cp ./conll05st-release/test.wsj/words/test.wsj.words.gz . @@ -22,4 +26,4 @@ gunzip test.wsj.words.gz gunzip test.wsj.props.gz python extract_pairs.py -w test.wsj.words -p test.wsj.props -o test.wsj.seq_pair -python extract_dict_feature.py -p test.wsj.seq_pair -f feature -s src.dict -t tgt.dict +python extract_dict_feature.py -p test.wsj.seq_pair -f feature diff --git a/demo/semantic_role_labeling/dataprovider.py b/demo/semantic_role_labeling/dataprovider.py index 2ef25c42c1..c1c6eeca51 100644 --- a/demo/semantic_role_labeling/dataprovider.py +++ b/demo/semantic_role_labeling/dataprovider.py @@ -17,11 +17,15 @@ from paddle.trainer.PyDataProvider2 import * UNK_IDX = 0 -def hook(settings, word_dict, label_dict, **kwargs): +def hook(settings, word_dict, label_dict, predicate_dict, **kwargs): settings.word_dict = word_dict settings.label_dict = label_dict + settings.predicate_dict = predicate_dict + #all inputs are integral and sequential type settings.slots = [ + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(predicate_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), @@ -31,27 +35,33 @@ def hook(settings, word_dict, label_dict, **kwargs): integer_value_sequence(len(label_dict))] -@provider(init_hook=hook) -def process(obj, file_name): +def get_batch_size(yeild_data): + return len(yeild_data[0]) + + +@provider(init_hook=hook, should_shuffle=True, calc_batch_size=get_batch_size, + can_over_batch_size=False, cache=CacheType.CACHE_PASS_IN_MEM) +def process(settings, file_name): with open(file_name, 'r') as fdata: for line in fdata: - sentence, predicate, ctx_n1, ctx_0, ctx_p1, mark, label = \ + sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = \ line.strip().split('\t') - + words = sentence.split() sen_len = len(words) - word_slot = [obj.word_dict.get(w, UNK_IDX) for w in words] + word_slot = [settings.word_dict.get(w, UNK_IDX) for w in words] - predicate_slot = [obj.word_dict.get(predicate, UNK_IDX)] * sen_len - ctx_n1_slot = [obj.word_dict.get(ctx_n1, UNK_IDX)] * sen_len - ctx_0_slot = [obj.word_dict.get(ctx_0, UNK_IDX)] * sen_len - ctx_p1_slot = [obj.word_dict.get(ctx_p1, UNK_IDX)] * sen_len + predicate_slot = [settings.predicate_dict.get(predicate)] * sen_len + ctx_n2_slot = [settings.word_dict.get(ctx_n2, UNK_IDX)] * sen_len + ctx_n1_slot = [settings.word_dict.get(ctx_n1, UNK_IDX)] * sen_len + ctx_0_slot = [settings.word_dict.get(ctx_0, UNK_IDX)] * sen_len + ctx_p1_slot = [settings.word_dict.get(ctx_p1, UNK_IDX)] * sen_len + ctx_p2_slot = [settings.word_dict.get(ctx_p2, UNK_IDX)] * sen_len marks = mark.split() mark_slot = [int(w) for w in marks] label_list = label.split() - label_slot = [obj.label_dict.get(w) for w in label_list] - - yield word_slot, predicate_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, mark_slot, label_slot + label_slot = [settings.label_dict.get(w) for w in label_list] + yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot, label_slot diff --git a/demo/semantic_role_labeling/db_lstm.py b/demo/semantic_role_labeling/db_lstm.py index 364460afbe..68feb9c4a7 100644 --- a/demo/semantic_role_labeling/db_lstm.py +++ b/demo/semantic_role_labeling/db_lstm.py @@ -19,8 +19,9 @@ import sys from paddle.trainer_config_helpers import * #file paths -word_dict_file = './data/src.dict' -label_dict_file = './data/tgt.dict' +word_dict_file = './data/wordDict.txt' +label_dict_file = './data/targetDict.txt' +predicate_file= './data/verbDict.txt' train_list_file = './data/train.list' test_list_file = './data/test.list' @@ -31,8 +32,10 @@ if not is_predict: #load dictionaries word_dict = dict() label_dict = dict() + predicate_dict = dict() with open(word_dict_file, 'r') as f_word, \ - open(label_dict_file, 'r') as f_label: + open(label_dict_file, 'r') as f_label, \ + open(predicate_file, 'r') as f_pre: for i, line in enumerate(f_word): w = line.strip() word_dict[w] = i @@ -41,6 +44,11 @@ if not is_predict: w = line.strip() label_dict[w] = i + for i, line in enumerate(f_pre): + w = line.strip() + predicate_dict[w] = i + + if is_test: train_list_file = None @@ -51,91 +59,169 @@ if not is_predict: module='dataprovider', obj='process', args={'word_dict': word_dict, - 'label_dict': label_dict}) + 'label_dict': label_dict, + 'predicate_dict': predicate_dict }) word_dict_len = len(word_dict) label_dict_len = len(label_dict) + pred_len = len(predicate_dict) else: word_dict_len = get_config_arg('dict_len', int) label_dict_len = get_config_arg('label_len', int) + pred_len = get_config_arg('pred_len', int) +############################## Hyper-parameters ################################## mark_dict_len = 2 word_dim = 32 mark_dim = 5 -hidden_dim = 128 +hidden_dim = 512 depth = 8 -emb_lr = 1e-2 -fc_lr = 1e-2 -lstm_lr = 2e-2 + + + +########################### Optimizer ####################################### + settings( batch_size=150, - learning_method=AdamOptimizer(), - learning_rate=1e-3, + learning_method=MomentumOptimizer(momentum=0), + learning_rate=2e-2, regularization=L2Regularization(8e-4), - gradient_clipping_threshold=25) + is_async=False, + model_average=ModelAverage(average_window=0.5, + max_average_window=10000), + +) -#6 features + + + +####################################### network ############################## +#8 features and 1 target word = data_layer(name='word_data', size=word_dict_len) -predicate = data_layer(name='verb_data', size=word_dict_len) +predicate = data_layer(name='verb_data', size=pred_len) + +ctx_n2 = data_layer(name='ctx_n2_data', size=word_dict_len) ctx_n1 = data_layer(name='ctx_n1_data', size=word_dict_len) ctx_0 = data_layer(name='ctx_0_data', size=word_dict_len) ctx_p1 = data_layer(name='ctx_p1_data', size=word_dict_len) +ctx_p2 = data_layer(name='ctx_p2_data', size=word_dict_len) mark = data_layer(name='mark_data', size=mark_dict_len) + if not is_predict: target = data_layer(name='target', size=label_dict_len) -ptt = ParameterAttribute(name='src_emb', learning_rate=emb_lr) -layer_attr = ExtraLayerAttribute(drop_rate=0.5) -fc_para_attr = ParameterAttribute(learning_rate=fc_lr) -lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=lstm_lr) -para_attr = [fc_para_attr, lstm_para_attr] -word_embedding = embedding_layer(size=word_dim, input=word, param_attr=ptt) -predicate_embedding = embedding_layer( - size=word_dim, input=predicate, param_attr=ptt) -ctx_n1_embedding = embedding_layer(size=word_dim, input=ctx_n1, param_attr=ptt) -ctx_0_embedding = embedding_layer(size=word_dim, input=ctx_0, param_attr=ptt) -ctx_p1_embedding = embedding_layer(size=word_dim, input=ctx_p1, param_attr=ptt) -mark_embedding = embedding_layer(size=mark_dim, input=mark) +default_std=1/math.sqrt(hidden_dim)/3.0 + +emb_para = ParameterAttribute(name='emb', initial_std=0., learning_rate=0.) +std_0 = ParameterAttribute(initial_std=0.) +std_default = ParameterAttribute(initial_std=default_std) + +word_embedding = embedding_layer(size=word_dim, input=word, param_attr=emb_para) +predicate_embedding = embedding_layer(size=word_dim, input=predicate, param_attr=ParameterAttribute(name='vemb',initial_std=default_std)) +ctx_n2_embedding = embedding_layer(size=word_dim, input=ctx_n2, param_attr=emb_para) +ctx_n2_embedding = embedding_layer(size=word_dim, input=ctx_n2, param_attr=emb_para) +ctx_n1_embedding = embedding_layer(size=word_dim, input=ctx_n1, param_attr=emb_para) +ctx_0_embedding = embedding_layer(size=word_dim, input=ctx_0, param_attr=emb_para) +ctx_p1_embedding = embedding_layer(size=word_dim, input=ctx_p1, param_attr=emb_para) +ctx_p2_embedding = embedding_layer(size=word_dim, input=ctx_p2, param_attr=emb_para) +mark_embedding = embedding_layer(name='word_ctx-in_embedding', size=mark_dim, input=mark, param_attr=std_0) + hidden_0 = mixed_layer( + name='hidden0', size=hidden_dim, + bias_attr=std_default, input=[ - full_matrix_projection(input=word_embedding), - full_matrix_projection(input=predicate_embedding), - full_matrix_projection(input=ctx_n1_embedding), - full_matrix_projection(input=ctx_0_embedding), - full_matrix_projection(input=ctx_p1_embedding), - full_matrix_projection(input=mark_embedding), + full_matrix_projection(input=word_embedding, param_attr=std_default), + full_matrix_projection(input=predicate_embedding, param_attr=std_default), + full_matrix_projection(input=ctx_n2_embedding, param_attr=std_default), + full_matrix_projection(input=ctx_n1_embedding, param_attr=std_default), + full_matrix_projection(input=ctx_0_embedding, param_attr=std_default), + full_matrix_projection(input=ctx_p1_embedding, param_attr=std_default), + full_matrix_projection(input=ctx_p2_embedding, param_attr=std_default), + full_matrix_projection(input=mark_embedding, param_attr=std_default) ]) -lstm_0 = lstmemory(input=hidden_0, layer_attr=layer_attr) + +mix_hidden_lr = 1e-3 +lstm_para_attr = ParameterAttribute(initial_std=0.0, learning_rate=1.0) +hidden_para_attr = ParameterAttribute(initial_std=default_std, learning_rate=mix_hidden_lr) + +lstm_0 = lstmemory(name='lstm0', + input=hidden_0, + act=ReluActivation(), + gate_act=SigmoidActivation(), + state_act=SigmoidActivation(), + bias_attr=std_0, + param_attr=lstm_para_attr) #stack L-LSTM and R-LSTM with direct edges input_tmp = [hidden_0, lstm_0] + for i in range(1, depth): - fc = fc_layer(input=input_tmp, size=hidden_dim, param_attr=para_attr) + mix_hidden = mixed_layer(name='hidden'+str(i), + size=hidden_dim, + bias_attr=std_default, + input=[full_matrix_projection(input=input_tmp[0], param_attr=hidden_para_attr), + full_matrix_projection(input=input_tmp[1], param_attr=lstm_para_attr) + ] + ) + + lstm = lstmemory(name='lstm'+str(i), + input=mix_hidden, + act=ReluActivation(), + gate_act=SigmoidActivation(), + state_act=SigmoidActivation(), + reverse=((i % 2)==1), + bias_attr=std_0, + param_attr=lstm_para_attr) + + input_tmp = [mix_hidden, lstm] + +feature_out = mixed_layer(name='output', + size=label_dict_len, + bias_attr=std_default, + input=[full_matrix_projection(input=input_tmp[0], param_attr=hidden_para_attr), + full_matrix_projection(input=input_tmp[1], param_attr=lstm_para_attr) + ], + ) - lstm = lstmemory( - input=fc, - act=ReluActivation(), - reverse=(i % 2) == 1, - layer_attr=layer_attr) - input_tmp = [fc, lstm] -prob = fc_layer( - input=input_tmp, - size=label_dict_len, - act=SoftmaxActivation(), - param_attr=para_attr) if not is_predict: - cls = classification_cost(input=prob, label=target) - outputs(cls) + crf_l = crf_layer( name = 'crf', + size = label_dict_len, + input = feature_out, + label = target, + param_attr=ParameterAttribute(name='crfw',initial_std=default_std, learning_rate=mix_hidden_lr) + + ) + + + crf_dec_l = crf_decoding_layer(name = 'crf_dec_l', + size = label_dict_len, + input = feature_out, + label = target, + param_attr=ParameterAttribute(name='crfw') + ) + + + eval = sum_evaluator(input=crf_dec_l) + + outputs(crf_l) + else: - outputs(prob) + crf_dec_l = crf_decoding_layer(name = 'crf_dec_l', + size = label_dict_len, + input = feature_out, + param_attr=ParameterAttribute(name='crfw') + ) + + outputs(crf_dec_l) + diff --git a/demo/semantic_role_labeling/predict.py b/demo/semantic_role_labeling/predict.py index 9a27112828..80183300f2 100644 --- a/demo/semantic_role_labeling/predict.py +++ b/demo/semantic_role_labeling/predict.py @@ -26,7 +26,7 @@ UNK_IDX = 0 class Prediction(): - def __init__(self, train_conf, dict_file, model_dir, label_file): + def __init__(self, train_conf, dict_file, model_dir, label_file, predicate_dict_file): """ train_conf: trainer configure. dict_file: word dictionary file name. @@ -35,16 +35,19 @@ class Prediction(): self.dict = {} self.labels = {} + self.predicate_dict={} self.labels_reverse = {} - self.load_dict_label(dict_file, label_file) + self.load_dict_label(dict_file, label_file, predicate_dict_file) len_dict = len(self.dict) len_label = len(self.labels) + len_pred = len(self.predicate_dict) conf = parse_config( train_conf, - 'dict_len=' + str(len_dict) + + 'dict_len=' + str(len_dict) + ',label_len=' + str(len_label) + + ',pred_len=' + str(len_pred) + ',is_predict=True') self.network = swig_paddle.GradientMachine.createFromConfigProto( conf.model_config) @@ -52,15 +55,17 @@ class Prediction(): slots = [ integer_value_sequence(len_dict), + integer_value_sequence(len_pred), integer_value_sequence(len_dict), integer_value_sequence(len_dict), integer_value_sequence(len_dict), integer_value_sequence(len_dict), + integer_value_sequence(len_dict), integer_value_sequence(2) - ] + ] self.converter = DataProviderConverter(slots) - def load_dict_label(self, dict_file, label_file): + def load_dict_label(self, dict_file, label_file, predicate_dict_file): """ Load dictionary from self.dict_file. """ @@ -71,39 +76,42 @@ class Prediction(): self.labels[line.strip()] = line_count self.labels_reverse[line_count] = line.strip() + for line_count, line in enumerate(open(predicate_dict_file, 'r')): + self.predicate_dict[line.strip()] = line_count def get_data(self, data_file): """ Get input data of paddle format. """ with open(data_file, 'r') as fdata: for line in fdata: - sentence, predicate, ctx_n1, ctx_0, ctx_p1, mark, label = line.strip( + sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = line.strip( ).split('\t') words = sentence.split() sen_len = len(words) - + word_slot = [self.dict.get(w, UNK_IDX) for w in words] - predicate_slot = [self.dict.get(predicate, UNK_IDX)] * sen_len + predicate_slot = [self.predicate_dict.get(predicate, UNK_IDX)] * sen_len + ctx_n2_slot = [self.dict.get(ctx_n2, UNK_IDX)] * sen_len ctx_n1_slot = [self.dict.get(ctx_n1, UNK_IDX)] * sen_len ctx_0_slot = [self.dict.get(ctx_0, UNK_IDX)] * sen_len ctx_p1_slot = [self.dict.get(ctx_p1, UNK_IDX)] * sen_len + ctx_p2_slot = [self.dict.get(ctx_p2, UNK_IDX)] * sen_len marks = mark.split() mark_slot = [int(w) for w in marks] + + yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot - yield word_slot, predicate_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, mark_slot - - def predict(self, data_file): + def predict(self, data_file, output_file): """ data_file: file name of input data. """ input = self.converter(self.get_data(data_file)) output = self.network.forwardTest(input) - prob = output[0]["value"] - lab = list(np.argsort(-prob)[:, 0]) + lab = output[0]["id"].tolist() - with open(data_file, 'r') as fin, open('predict.res', 'w') as fout: + with open(data_file, 'r') as fin, open(output_file, 'w') as fout: index = 0 for line in fin: sen = line.split('\t')[0] @@ -115,8 +123,8 @@ class Prediction(): def option_parser(): - usage = ("python predict.py -c config -w model_dir " - "-d word dictionary -l label_file -i input_file") + usage = ("python predict.py -c config -w model_dir " + "-d word dictionary -l label_file -i input_file -p pred_dict_file") parser = OptionParser(usage="usage: %s [options]" % usage) parser.add_option( "-c", @@ -137,6 +145,13 @@ def option_parser(): dest="label_file", default=None, help="label file") + parser.add_option( + "-p", + "--predict_dict_file", + action="store", + dest="predict_dict_file", + default=None, + help="predict_dict_file") parser.add_option( "-i", "--data", @@ -150,6 +165,14 @@ def option_parser(): dest="model_path", default=None, help="model path") + + parser.add_option( + "-o", + "--output_file", + action="store", + dest="output_file", + default=None, + help="output file") return parser.parse_args() @@ -160,10 +183,12 @@ def main(): dict_file = options.dict_file model_path = options.model_path label_file = options.label_file + predict_dict_file = options.predict_dict_file + output_file = options.output_file swig_paddle.initPaddle("--use_gpu=0") - predict = Prediction(train_conf, dict_file, model_path, label_file) - predict.predict(data_file) + predict = Prediction(train_conf, dict_file, model_path, label_file, predict_dict_file) + predict.predict(data_file,output_file) if __name__ == '__main__': diff --git a/demo/semantic_role_labeling/predict.sh b/demo/semantic_role_labeling/predict.sh index a545b9a5d5..d0acdb0bd0 100644 --- a/demo/semantic_role_labeling/predict.sh +++ b/demo/semantic_role_labeling/predict.sh @@ -26,15 +26,18 @@ LOG=`get_best_pass $log` LOG=(${LOG}) best_model_path="output/pass-${LOG[1]}" - config_file=db_lstm.py -dict_file=./data/src.dict -label_file=./data/tgt.dict +dict_file=./data/wordDict.txt +label_file=./data/targetDict.txt +predicate_dict_file=./data/verbDict.txt input_file=./data/feature +output_file=predict.res python predict.py \ -c $config_file \ -w $best_model_path \ -l $label_file \ + -p $predicate_dict_file \ -d $dict_file \ - -i $input_file + -i $input_file \ + -o $output_file diff --git a/demo/semantic_role_labeling/train.sh b/demo/semantic_role_labeling/train.sh index 94c7b6f31d..b566931db0 100644 --- a/demo/semantic_role_labeling/train.sh +++ b/demo/semantic_role_labeling/train.sh @@ -16,12 +16,18 @@ set -e paddle train \ --config=./db_lstm.py \ + --use_gpu=0 \ + --log_period=5000 \ + --trainer_count=1 \ + --show_parameter_stats_period=5000 \ + --saving_period=1 \ --save_dir=./output \ - --trainer_count=4 \ - --log_period=10 \ - --num_passes=500 \ - --use_gpu=false \ - --show_parameter_stats_period=10 \ - --test_all_data_in_one_period=1 \ + --local=1 \ + --num_passes=10000 \ + --test_period=0 \ + --average_test_period=10000000 \ + --init_model_path=./data \ + --load_missing_parameter_strategy=rand \ + --dot_period=100 \ 2>&1 | tee 'train.log' diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index 05fbc8278d..c6b9813f6a 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -30,8 +30,6 @@ Several new files appear in the `data `directory as follows. conll05st-release:the test data set of CoNll-2005 shared task test.wsj.words:the Wall Street Journal data sentences test.wsj.props: the propositional arguments -src.dict:the dictionary of words in sentences -tgt.dict:the labels dictionary feature: the extracted features from data set ``` @@ -67,6 +65,8 @@ def hook(settings, word_dict, label_dict, **kwargs): settings.label_dict = label_dict #all inputs are integral and sequential type settings.slots = [ + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(predicate_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), @@ -77,34 +77,39 @@ def hook(settings, word_dict, label_dict, **kwargs): ``` The corresponding data iterator is as following: ``` -@provider(use_seq=True, init_hook=hook) -def process(obj, file_name): +@provider(init_hook=hook, should_shuffle=True, calc_batch_size=get_batch_size, + can_over_batch_size=False, cache=CacheType.CACHE_PASS_IN_MEM) +def process(settings, file_name): with open(file_name, 'r') as fdata: for line in fdata: - sentence, predicate, ctx_n1, ctx_0, ctx_p1, mark, label = line.strip().split('\t') + sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = \ + line.strip().split('\t') + words = sentence.split() sen_len = len(words) - word_slot = [obj.word_dict.get(w, UNK_IDX) for w in words] + word_slot = [settings.word_dict.get(w, UNK_IDX) for w in words] - predicate_slot = [obj.word_dict.get(predicate, UNK_IDX)] * sen_len - ctx_n1_slot = [obj.word_dict.get(ctx_n1, UNK_IDX) ] * sen_len - ctx_0_slot = [obj.word_dict.get(ctx_0, UNK_IDX) ] * sen_len - ctx_p1_slot = [obj.word_dict.get(ctx_p1, UNK_IDX) ] * sen_len + predicate_slot = [settings.predicate_dict.get(predicate)] * sen_len + ctx_n2_slot = [settings.word_dict.get(ctx_n2, UNK_IDX)] * sen_len + ctx_n1_slot = [settings.word_dict.get(ctx_n1, UNK_IDX)] * sen_len + ctx_0_slot = [settings.word_dict.get(ctx_0, UNK_IDX)] * sen_len + ctx_p1_slot = [settings.word_dict.get(ctx_p1, UNK_IDX)] * sen_len + ctx_p2_slot = [settings.word_dict.get(ctx_p2, UNK_IDX)] * sen_len marks = mark.split() mark_slot = [int(w) for w in marks] label_list = label.split() - label_slot = [obj.label_dict.get(w) for w in label_list] - - yield word_slot, predicate_slot, ctx_n1_slot, ctx_0_slot, ctx_p1_slot, mark_slot, label_slot + label_slot = [settings.label_dict.get(w) for w in label_list] + yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot, label_slot ``` -The `process`function yield 7 lists which are six features and labels. +The `process`function yield 9 lists which are 8 features and label. ### Neural Network Config `db_lstm.py` is the neural network config file to load the dictionaries and define the data provider module and network architecture during the training procedure. -Seven `data_layer` load instances from data provider. Six features are transformed into embedddings respectively, and mixed by `mixed_layer` . Deep bidirectional LSTM layers extract features for the softmax layer. The objective function is cross entropy of labels. +Nine `data_layer` load instances from data provider. Eight features are transformed into embedddings respectively, and mixed by `mixed_layer` . Deep bidirectional LSTM layers extract features for the softmax layer. The objective function is cross entropy of labels. ### Run Training The script for training is `train.sh`, user just need to execute: @@ -115,24 +120,36 @@ The content in `train.sh`: ``` paddle train \ --config=./db_lstm.py \ + --use_gpu=0 \ + --log_period=5000 \ + --trainer_count=1 \ + --show_parameter_stats_period=5000 \ + --saving_period=1 \ --save_dir=./output \ - --trainer_count=4 \ - --log_period=10 \ - --num_passes=500 \ - --use_gpu=false \ - --show_parameter_stats_period=10 \ - --test_all_data_in_one_period=1 \ + --local=1 \ + --num_passes=10000 \ + --test_period=0 \ + --average_test_period=10000000 \ + --init_model_path=./data \ + --load_missing_parameter_strategy=rand \ + --dot_period=100 \ 2>&1 | tee 'train.log' ``` - \--config=./db_lstm.py : network config file. -- \--save_di=./output: output path to save models. -- \--trainer_count=4 : set thread number (or GPU count). -- \--log_period=10 : print log every 20 batches. -- \--num_passes=500: set pass number, one pass in PaddlePaddle means training all samples in dataset one time. -- \--use_gpu=false: use CPU to train, set true, if you install GPU version of PaddlePaddle and want to use GPU to train. -- \--show_parameter_stats_period=10: show parameter statistic every 100 batches. -- \--test_all_data_in_one_period=1: test all data in every testing. +- \--use_gpu=false: use CPU to train, set true, if you install GPU version of PaddlePaddle and want to use GPU to train, until now crf_layer do not support GPU +- \--log_period=500: print log every 20 batches. +- \--trainer_count=1: set thread number (or GPU count). +- \--show_parameter_stats_period=5000: show parameter statistic every 100 batches. +- \--saving_period=1: save model per pass +- \--save_dir=./output: output path to save models. +- \--local=1: traing in local mode +- \--num_passes=10000: set pass number, one pass in PaddlePaddle means training all samples in dataset one time. +- \--test_period=0: run testing each pass +- \--average_test_period=10000000: do test on average parameter every average_test_period batches +- \--init_model_path=./data: parameter initialization path +- \--load_missing_parameter_strategy=rand: random initialization unexisted parameters +- \--dot_period=100: print a dot per 100 batches After training, the models will be saved in directory `output`. @@ -166,11 +183,13 @@ The script for prediction is `predict.sh`, user just need to execute: In `predict.sh`, user should offer the network config file, model path, label file, word dictionary file, feature file ``` python predict.py - -c $config_file - -w $model_path - -l $label_file - -d $dict_file - -i $input_file + -c $config_file \ + -w $best_model_path \ + -l $label_file \ + -p $predicate_dict_file \ + -d $dict_file \ + -i $input_file \ + -o $output_file ``` `predict.py` is the main executable python script, which includes functions: load model, load data, data prediction. The network model will output the probability distribution of labels. In the demo, we take the label with maximum probability as result. User can also implement the beam search or viterbi decoding upon the probability distribution matrix. -- GitLab From c6a0298e2a582d22144a229664c0b99d13b3330d Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 17:24:21 +0800 Subject: [PATCH 0004/1503] create PR to polish test_period meaning --- paddle/trainer/Trainer.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 7fc48dd1fb..32c4bad239 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -46,6 +46,18 @@ P_DEFINE_int32(test_period, 0, " If not 0, test log_period batches." " If 0, test on all test data"); +P_DEFINE_int32(test_batches_while_training, 0, + "Run test every so many train batches." + " 0 for testing after each pass." + " If not 0, test log_period batches." + " If 0, test on all test data"); + +P_DEFINE_int32(test_batches_while_end, 0, + "Run test every so many train batches." + " 0 for testing after each pass." + " If not 0, test log_period batches." + " If 0, test on all test data"); + P_DEFINE_bool(local, true, "Train in local mode or not"); P_DEFINE_bool( -- GitLab From 0a0c55d2289d3d474014590758ec3143decf2ba2 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 19:27:29 +0800 Subject: [PATCH 0005/1503] more friendly test options --- paddle/trainer/Tester.cpp | 18 ++++++++---- paddle/trainer/Tester.h | 2 +- paddle/trainer/TesterConfig.h | 11 +++++-- paddle/trainer/Trainer.cpp | 54 ++++++++++++++++++++--------------- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/paddle/trainer/Tester.cpp b/paddle/trainer/Tester.cpp index d3b88019fa..f57e09d40a 100644 --- a/paddle/trainer/Tester.cpp +++ b/paddle/trainer/Tester.cpp @@ -90,13 +90,20 @@ void Tester::testOneDataBatch( testContext_.numSamples += dataBatch.getSize(); } -void Tester::testOnePeriod() { +void Tester::testOnePeriod(bool finishPass) { DataBatch dataBatch; int64_t batchSize = config_->getOptConfig().batch_size(); + bool testAllData = - intconfig_->testPeriod == 0 || intconfig_->testAllDataInOnePeriod; - int batches = - testAllData ? std::numeric_limits::max() : intconfig_->testPeriod; + (!finishPass && !intconfig_->testBatchesWhileTraining) || + (finishPass && !intconfig_->testBatchesWhileEnd); + int batches; + if (testAllData) { + batches = std::numeric_limits::max(); + } else { + batches = finishPass ? + intconfig_->testBatchesWhileEnd : intconfig_->testBatchesWhileTraining; + } std::vector outArgs; @@ -108,7 +115,8 @@ void Tester::testOnePeriod() { if (intconfig_->prevBatchState) { gradientMachine_->resetState(); } - if (testAllData) { + if ((!finishPass && !intconfig_->testBatchesWhileTraining) || + (finishPass && !intconfig_->testBatchesWhileEnd)) { break; } else { num = testDataProvider_->getNextBatch(batchSize, &dataBatch); diff --git a/paddle/trainer/Tester.h b/paddle/trainer/Tester.h index 671ffc5220..21e11422aa 100644 --- a/paddle/trainer/Tester.h +++ b/paddle/trainer/Tester.h @@ -67,7 +67,7 @@ public: * It is convenience to test small set of data when test data set is large and * is training at same time. */ - void testOnePeriod(); + void testOnePeriod(bool finishPass = true); void startTestPeriod(); void finishTestPeriod(); void testOneDataBatch(const DataBatch& dataBatch, diff --git a/paddle/trainer/TesterConfig.h b/paddle/trainer/TesterConfig.h index d5e644ce61..b7b550dec7 100644 --- a/paddle/trainer/TesterConfig.h +++ b/paddle/trainer/TesterConfig.h @@ -38,12 +38,17 @@ struct TesterConfig { /** * indicate test period */ - int testPeriod; + int testPeriodWhileTraining; /** - * indicate whether testing data in one period + * indicate how many batches are used for testing under training */ - bool testAllDataInOnePeriod; + bool testBatchesWhileTraining; + + /** + * indicate how many batches are used for testing at pass end + */ + bool testBatchesWhileEnd; /** * indicate whether to save previous batch state diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 32c4bad239..477813b474 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -40,31 +40,28 @@ limitations under the License. */ #include "TrainerConfigHelper.h" P_DEFINE_string(config, "", "Trainer config file"); -P_DEFINE_int32(test_period, 0, - "Run test every so many train batches." - " 0 for testing after each pass." - " If not 0, test log_period batches." - " If 0, test on all test data"); -P_DEFINE_int32(test_batches_while_training, 0, +P_DEFINE_int32(test_period, 0, + "This option was deprecated, use test_period_while_training " + " instead. "); +P_DEFINE_int32(test_period_while_training, 0, "Run test every so many train batches." - " 0 for testing after each pass." " If not 0, test log_period batches." + " If 0, test nothing."); +P_DEFINE_int32(test_batches_while_training, 1000, + "test test_batches_while_training batches if test_period != 0." " If 0, test on all test data"); - P_DEFINE_int32(test_batches_while_end, 0, - "Run test every so many train batches." - " 0 for testing after each pass." - " If not 0, test log_period batches." - " If 0, test on all test data"); + "test test_batches_while_end batches at pass end." + " Always run test at pass end." + " If not 0, test test_batches_while_end batches." + " If 0, test on all test data."); +P_DEFINE_bool(test_all_data_in_one_period, false, + "This option was deprecated, use test_batches_while_training " + "and test_batches_while_end instead"); P_DEFINE_bool(local, true, "Train in local mode or not"); -P_DEFINE_bool( - test_all_data_in_one_period, false, - "true will test all data in one test peroid." - "Otherwise test (batch_size * log_peroid) data in one test period."); - P_DEFINE_int32(average_test_period, 0, "Do test on average parameter every so" " many batches. MUST be devided by FLAGS_log_period." @@ -469,9 +466,9 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { FOR_TIMING(globalStat.reset()); } - if (testDataProvider_ && FLAGS_test_period > 0 && - trainPassContext_.batchId % FLAGS_test_period == 0) { - tester_->testOnePeriod(); + if (testDataProvider_ && FLAGS_test_period_while_training > 0 && + trainPassContext_.batchId % FLAGS_test_period_while_training == 0) { + tester_->testOnePeriod(false); } if (FLAGS_saving_period_by_batches > 0 && @@ -480,7 +477,7 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { 0 == FLAGS_trainer_id) { trainerInternal_.getParameterUpdater()->catchUpWith(); if (testDataProvider_) { - tester_->testOnePeriod(); + tester_->testOnePeriod(false); } paramUtil_->saveParametersOnePass( trainPassContext_.passId, trainPassContext_.passInnerId); @@ -636,8 +633,19 @@ void Trainer::test() { std::unique_ptr Trainer::createTesterConfig() { TesterConfig* conf = new TesterConfig; - conf->testPeriod = FLAGS_test_period; - conf->testAllDataInOnePeriod = FLAGS_test_all_data_in_one_period; + if (FLAGS_test_period) { + LOG(WARNING) + << "--test_period was deprecated, use --test_period_while_training" + << "--test_batches_while_training --test_batches_while_end instead."; + } + if (FLAGS_test_all_data_in_one_period) { + LOG(WARNING) + << "--test_all_data_in_one_period was deprecated, use" + << " --test_batches_while_training and --test_batches_while_end instead"; + } + conf->testPeriodWhileTraining = FLAGS_test_period_while_training; + conf->testBatchesWhileTraining = FLAGS_test_batches_while_training; + conf->testBatchesWhileEnd = FLAGS_test_batches_while_end; conf->prevBatchState = FLAGS_prev_batch_state; conf->logPeriod = FLAGS_log_period; conf->loadsaveParametersInPserver = FLAGS_loadsave_parameters_in_pserver; -- GitLab From 0feecbd13c9cf700e80fe4dbce84c9b724cf248e Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 19:50:01 +0800 Subject: [PATCH 0006/1503] modify on docs --- doc/ui/cmd_argument/argument_outline.md | 11 ++++++++--- doc/ui/cmd_argument/detail_introduction.md | 14 +++++++++----- doc/ui/cmd_argument/use_case.md | 7 ++++--- paddle/trainer/Trainer.cpp | 3 ++- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/doc/ui/cmd_argument/argument_outline.md b/doc/ui/cmd_argument/argument_outline.md index d6cc2c6ed7..bafa5dfef2 100644 --- a/doc/ui/cmd_argument/argument_outline.md +++ b/doc/ui/cmd_argument/argument_outline.md @@ -68,7 +68,7 @@ It looks like there are a lot of arguments. However, most of them are for develo -test_period +test_period_while_training √√ @@ -143,8 +143,13 @@ It looks like there are a lot of arguments. However, most of them are for develo -testing during trainingtest_all_data_in_one_period -√√ +testing during trainingtest_batches_while_training +√√√< + + + +testing during trainingtest_batches_while_end +√√√< diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index 07608e5edf..0628289dbd 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -109,9 +109,9 @@ - Load parameter from this pass to test. - type: int32 (default: -1). -* `--test_period` - - Run testing every test_period train batches. If not set, run testing each pass. - - type: int32 (default: 1000). +* `--test_period_while_training` + - Run test every so many train batches. If not 0, test log_period batches. If 0, test nothing. + - type: int32 (default: 0). * `--test_wait` - Whether to wait for parameter per pass if not exist. If set test_data_path in submitting environment of cluster, it will launch one process to perfom testing, so we need to set test_wait=1. Note that in the cluster submitting environment, this argument has been set True by default. @@ -121,8 +121,12 @@ - File that saves the model list when testing. It was set automatically when using cluster submitting environment after setting model_path. - type: string (default: "", null). -* `--test_all_data_in_one_period` - - This argument is usually used in testing period during traning. If true, all data will be tested in one test period. Otherwise (batch_size * log_peroid) data will be tested. +* `--test_batches_while_training` + - Test test_batches_while_training batches if test_batches_while_training != 0. If 0, test on all test data. + - type: bool (default: 1000). + +* `--test_batches_while_end` + - Test test_batches_while_end batches if test_batches_while_end != 0. If 0, test on all test data. - type: bool (default: 0). * `--predict_output_dir` diff --git a/doc/ui/cmd_argument/use_case.md b/doc/ui/cmd_argument/use_case.md index a6bfba29af..b243560106 100644 --- a/doc/ui/cmd_argument/use_case.md +++ b/doc/ui/cmd_argument/use_case.md @@ -10,9 +10,10 @@ paddle train \ --config=network_config \ --save_dir=output \ --trainer_count=COUNT \ #(default:1) - --test_period=M \ #(default:1000) - --test_all_data_in_one_period=true \ #(default:false) - --num_passes=N \ #(defalut:100) + --test_period_while_training=M \ #(default:0) + --test_batches_while_training=BATCHES \#(default:1000) + --test_batches_while_end=BATCHES \ #(default:0) + --num_passes=N \ #(defalut:100) --log_period=K \ #(default:100) --dot_period=1000 \ #(default:1) #[--show_parameter_stats_period=100] \ #(default:0) diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 477813b474..107fa240cf 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -49,7 +49,8 @@ P_DEFINE_int32(test_period_while_training, 0, " If not 0, test log_period batches." " If 0, test nothing."); P_DEFINE_int32(test_batches_while_training, 1000, - "test test_batches_while_training batches if test_period != 0." + "test test_batches_while_training batches if " + "test_batches_while_training != 0." " If 0, test on all test data"); P_DEFINE_int32(test_batches_while_end, 0, "test test_batches_while_end batches at pass end." -- GitLab From c509908b748c98d3a46d70ac044edd0cbb297002 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 21:12:31 +0800 Subject: [PATCH 0007/1503] follow comments --- doc/ui/cmd_argument/detail_introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index 0628289dbd..1ff4bdbaa3 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -110,7 +110,7 @@ - type: int32 (default: -1). * `--test_period_while_training` - - Run test every so many train batches. If not 0, test log_period batches. If 0, test nothing. + - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches. If 0, test nothing. - type: int32 (default: 0). * `--test_wait` -- GitLab From dcf06bff04b6486817f83d160288c515763ea0b4 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 21:34:43 +0800 Subject: [PATCH 0008/1503] follow comments --- doc/ui/cmd_argument/detail_introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index 1ff4bdbaa3..40c6f5d2d8 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -110,7 +110,7 @@ - type: int32 (default: -1). * `--test_period_while_training` - - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches. If 0, test nothing. + - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches, if 0 test nothing. - type: int32 (default: 0). * `--test_wait` @@ -122,11 +122,11 @@ - type: string (default: "", null). * `--test_batches_while_training` - - Test test_batches_while_training batches if test_batches_while_training != 0. If 0, test on all test data. + - Test test_batches_while_training batches if test_batches_while_training != 0 while doing training. If 0, test on all test data. - type: bool (default: 1000). * `--test_batches_while_end` - - Test test_batches_while_end batches if test_batches_while_end != 0. If 0, test on all test data. + - Test test_batches_while_end batches if test_batches_while_end != 0 at pass end. If 0, test on all test data. - type: bool (default: 0). * `--predict_output_dir` -- GitLab From 453e6ba598906b58cc74d854bb0170bf800675fa Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 21:35:36 +0800 Subject: [PATCH 0009/1503] follow comments --- doc/ui/cmd_argument/detail_introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index 40c6f5d2d8..af97e04d54 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -110,7 +110,7 @@ - type: int32 (default: -1). * `--test_period_while_training` - - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches, if 0 test nothing. + - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches, if 0, test nothing. - type: int32 (default: 0). * `--test_wait` -- GitLab From b62c80f1561efe07156ef99c1e57fb3eeb24267d Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 9 Nov 2016 21:42:28 +0800 Subject: [PATCH 0010/1503] qfg --- doc/ui/cmd_argument/detail_introduction.md | 2 +- paddle/trainer/Trainer.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index af97e04d54..1f7e406a53 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -110,7 +110,7 @@ - type: int32 (default: -1). * `--test_period_while_training` - - Run test every test_period_while_training batches while doing training. If not 0, test log_period batches, if 0, test nothing. + - Run test every test_period_while_training batches while doing training. If not 0, test test_batches_while_training batches, if 0, test nothing. - type: int32 (default: 0). * `--test_wait` diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 107fa240cf..507a080cc4 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -45,8 +45,8 @@ P_DEFINE_int32(test_period, 0, "This option was deprecated, use test_period_while_training " " instead. "); P_DEFINE_int32(test_period_while_training, 0, - "Run test every so many train batches." - " If not 0, test log_period batches." + "Run test every test_period_while_training batches." + " If not 0, test test_batches_while_training batches." " If 0, test nothing."); P_DEFINE_int32(test_batches_while_training, 1000, "test test_batches_while_training batches if " -- GitLab From 0752b3b710a5f29fba7f7d5e595aa9da3a76fdda Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 14 Nov 2016 14:08:29 +0800 Subject: [PATCH 0011/1503] add layer check for recurrent_group --- .../paddle/trainer_config_helpers/layers.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 796121a641..952b1f0971 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -2754,7 +2754,12 @@ class SubsequenceInput(object): @wrap_name_default("recurrent_group") -def recurrent_group(step, input, reverse=False, name=None, targetInlink=None): +def recurrent_group(step, + input, + reverse=False, + name=None, + targetInlink=None, + is_train=True): """ Recurrent layer group is an extremely flexible recurrent unit in PaddlePaddle. As long as the user defines the calculation done within a @@ -2819,6 +2824,12 @@ def recurrent_group(step, input, reverse=False, name=None, targetInlink=None): :type targetInlink: LayerOutput|SubsequenceInput + :param is_train: recurrent_group is used for training (True) or generating (False). + If is training, one of the input type must be LayerOutput; else, + none of input type should be LayerOutput. + + : type is_train: bool + :return: LayerOutput object. :rtype: LayerOutput """ @@ -2866,6 +2877,7 @@ def recurrent_group(step, input, reverse=False, name=None, targetInlink=None): seq_reversed=reverse, target_inlinkname=targetInlinkName) in_args = [] + has_LayerOutput = True for each_input in input: assert is_single_input(each_input) if isinstance(each_input, LayerOutput): @@ -2873,6 +2885,7 @@ def recurrent_group(step, input, reverse=False, name=None, targetInlink=None): elif isinstance(each_input, SubsequenceInput): in_args.append(each_input.input) else: + has_LayerOutput = False mem_name = "__%s_memory__" % each_input.input.name mem = memory( name=mem_name, @@ -2886,6 +2899,8 @@ def recurrent_group(step, input, reverse=False, name=None, targetInlink=None): mix += identity_projection(mem) in_args.append(mem) + assert (is_train == has_LayerOutput) + layer_outs = step(*in_args) if isinstance(layer_outs, LayerOutput): @@ -3177,7 +3192,11 @@ def beam_search(step, return predict tmp = recurrent_group( - step=__real_step__, input=real_input, reverse=False, name=name) + step=__real_step__, + input=real_input, + reverse=False, + name=name, + is_train=False) return tmp -- GitLab From 9ec91b6ecaea86a00336fa338bed6349dabef5da Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 14 Nov 2016 17:47:50 +0800 Subject: [PATCH 0012/1503] Revise build docs --- doc/build/build_from_source.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/build/build_from_source.md b/doc/build/build_from_source.md index b8f26f431e..efdda29828 100644 --- a/doc/build/build_from_source.md +++ b/doc/build/build_from_source.md @@ -95,7 +95,7 @@ As a simple example, consider the following: ```bash # necessary sudo apt-get update - sudo apt-get install -y g++ make cmake build-essential libatlas-base-dev python python-pip libpython-dev m4 libprotobuf-dev protobuf-compiler python-protobuf python-numpy git + sudo apt-get install -y g++ make cmake swig build-essential libatlas-base-dev python python-pip libpython-dev m4 libprotobuf-dev protobuf-compiler python-protobuf python-numpy git # optional sudo apt-get install libgoogle-glog-dev sudo apt-get install libgflags-dev @@ -149,15 +149,15 @@ If still not found, you can manually set it based on CMake error information fro As a simple example, consider the following: -- **Only CPU** +- **Only CPU with swig** ```bash - cmake .. -DWITH_GPU=OFF + cmake .. -DWITH_GPU=OFF -DWITH_SWIG_PY=ON ``` -- **GPU** +- **GPU with swig** ```bash - cmake .. -DWITH_GPU=ON + cmake .. -DWITH_GPU=ON -DWITH_SWIG_PY=ON ``` - **GPU with doc and swig** @@ -170,15 +170,13 @@ Finally, you can build PaddlePaddle: ```bash # you can add build option here, such as: -cmake .. -DWITH_GPU=ON -DCMAKE_INSTALL_PREFIX= +cmake .. -DWITH_GPU=ON -DCMAKE_INSTALL_PREFIX= -DWITH_SWIG_PY=ON # please use sudo make install, if you want to install PaddlePaddle into the system make -j `nproc` && make install # set PaddlePaddle installation path in ~/.bashrc export PATH=/bin:$PATH ``` -**Note:** - If you set `WITH_SWIG_PY=ON`, related python dependencies also need to be installed. Otherwise, PaddlePaddle will automatically install python dependencies at first time when user run paddle commands, such as `paddle version`, `paddle train`. -- GitLab From 20f853ba8c71a64c676517d9ec5e18b256ef04e6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 14 Nov 2016 17:50:03 +0800 Subject: [PATCH 0013/1503] Travis ci does not build when only docs changed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ffe3bc193b..d39d93f705 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ addons: before_install: - | if [ ${JOB} == "BUILD_AND_TEST" ]; then - if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)' + if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(\.rst$)' then echo "Only markdown docs were updated, stopping build process." exit -- GitLab From d7d14ce35051477a37b339eb2047580b248e62da Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 14 Nov 2016 18:04:41 +0800 Subject: [PATCH 0014/1503] Change png/jpg files do not trigger Travis ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d39d93f705..effcf90769 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ addons: before_install: - | if [ ${JOB} == "BUILD_AND_TEST" ]; then - if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(\.rst$)' + if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(\.rst$)|(\.jpg$)|(\.png$)' then echo "Only markdown docs were updated, stopping build process." exit -- GitLab From 2be3a74779935777924d4bb061f98de4ea8272a1 Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Mon, 7 Nov 2016 15:06:56 -0800 Subject: [PATCH 0015/1503] Modified API to use FLAGS_use_gpu as useGpu default value --- paddle/api/Matrix.cpp | 11 +++++++++++ paddle/api/Paddle.swig | 9 ++++++++- paddle/api/PaddleAPI.h | 31 +++++++++++++++++++++++-------- paddle/api/Util.cpp | 2 ++ paddle/api/Vector.cpp | 24 +++++++++++++++++++++++- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/paddle/api/Matrix.cpp b/paddle/api/Matrix.cpp index 6a79f83495..f1ff957c6b 100644 --- a/paddle/api/Matrix.cpp +++ b/paddle/api/Matrix.cpp @@ -52,6 +52,17 @@ Matrix* Matrix::createDense(const std::vector& data, size_t height, return m; } +Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, + bool copy, bool useGpu) { + if (useGpu) { + /// Gpu mode only supports copy=True + CHECK(copy); + return Matrix::createGpuDenseFromNumpy(data, dim1, dim2); + } else { + return Matrix::createCpuDenseFromNumpy(data, dim1, dim2, copy); + } +} + Matrix* Matrix::createCpuDenseFromNumpy(float* data, int dim1, int dim2, bool copy) { auto m = new Matrix(); diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index a09f24ce1c..eaee182b52 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -133,14 +133,21 @@ namespace std { %newobject Matrix::createZero; %newobject Matrix::createSparse; %newobject Matrix::createDense; +%newobject Matrix::createDenseFromNumpy; +%newobject Matrix::createCpuDenseFromNumpy; +%newobject Matrix::createGpuDenseFromNumpy; %newobject Vector::createZero; %newobject Vector::create; +%newobject Vector::createVectorFromNumpy; %newobject Vector::createCpuVectorFromNumpy; %newobject Vector::createGpuVectorFromNumpy; %newobject IVector::createZero; %newobject IVector::create; +%newobject IVector::createVectorFromNumpy; +%newobject IVector::createCpuVectorFromNumpy; +%newobject IVector::createGpuVectorFromNumpy; %newobject Trainer::createByCommandLine; -%newobject Trainer::getNetworkOutput; +%newobject Trainer::getForwardOutput; %newobject Trainer::getLayerOutput; %newobject Arguments::getSlotValue; %newobject Arguments::getSlotIds; diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index cf790f2f8e..0825260fa1 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -42,6 +42,9 @@ using namespace paddle::enumeration_wrapper; // NOLINT */ void initPaddle(int argc, char** argv); +/// Return FLAGS_use_gpu +bool isUseGpu(); + /// Return true if this py_paddle is compiled in GPU Version bool isGpuVersion(); @@ -101,7 +104,8 @@ public: /** * Create A Matrix with height,width, which is filled by zero. */ - static Matrix* createZero(size_t height, size_t width, bool useGpu = false); + static Matrix* createZero(size_t height, size_t width, + bool useGpu = isUseGpu()); /** * Create Sparse Matrix. @@ -114,7 +118,7 @@ public: */ static Matrix* createSparse(size_t height, size_t width, size_t nnz, bool isNonVal = true, bool trans = false, - bool useGpu = false); + bool useGpu = isUseGpu()); /** * Create Dense Matrix. @@ -123,7 +127,11 @@ public: * @note the value will be copy into a new matrix. */ static Matrix* createDense(const std::vector& data, size_t height, - size_t width, bool useGpu = false); + size_t width, bool useGpu = isUseGpu()); + + static Matrix* createDenseFromNumpy(float* data, int dim1, int dim2, + bool copy = true, + bool useGpu = isUseGpu()); /** * Create Cpu Dense Matrix from numpy matrix, dtype=float32 @@ -221,15 +229,18 @@ public: ~Vector(); /// Create Vector filled with zero. - static Vector* createZero(size_t sz, bool useGpu = false); + static Vector* createZero(size_t sz, bool useGpu = isUseGpu()); /** * Create Vector from list of float. * * It will create a new vector, and copy data into it. */ - static Vector* create(const std::vector& data, bool useGpu = false); + static Vector* create(const std::vector& data, + bool useGpu = isUseGpu()); + static Vector* createVectorFromNumpy(float* data, int dim, bool copy = true, + bool useGpu = isUseGpu()); /** * Create Cpu Vector from numpy array, which dtype=float32 * @@ -279,13 +290,17 @@ class IVector { public: /// Create IVector filled with zero - static IVector* createZero(size_t sz, bool useGpu = false); + static IVector* createZero(size_t sz, bool useGpu = isUseGpu()); /** * Create IVector from list of int. * It will create a new vector, and copy data into it. */ - static IVector* create(const std::vector& data, bool useGpu = false); + static IVector* create(const std::vector& data, + bool useGpu = isUseGpu()); + + static IVector* createVectorFromNumpy(int* data, int dim, bool copy = true, + bool useGpu = isUseGpu()); /** * Create Cpu IVector from numpy array, which dtype=int32 @@ -297,7 +312,7 @@ public: /** * Create Gpu IVector from numpy array, which dtype=int32 */ - static IVector* createGpuVectorFromNumy(int* data, int dim); + static IVector* createGpuVectorFromNumpy(int* data, int dim); /// Cast to numpy array inplace. void toNumpyArrayInplace(int** view_data, int* dim1) throw(UnsupportError); diff --git a/paddle/api/Util.cpp b/paddle/api/Util.cpp index 8a6741078f..f953b322ce 100644 --- a/paddle/api/Util.cpp +++ b/paddle/api/Util.cpp @@ -41,6 +41,8 @@ IntWithFloatArray::IntWithFloatArray(const float* v, const int* i, size_t l, bool f) : valBuf(v), idxBuf(i), length(l), needFree(f) {} +bool isUseGpu() {return FLAGS_use_gpu;} + bool isGpuVersion() { #ifdef PADDLE_ONLY_CPU return false; diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 1affc1a5fe..5abafad9d1 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -39,6 +39,17 @@ IVector* IVector::create(const std::vector& data, bool useGpu) { return v; } +IVector* IVector::createVectorFromNumpy(int* data, int dim, bool copy, + bool useGpu) { + if (useGpu) { + /// if use gpu only copy=true is supported + CHECK(copy); + return IVector::createGpuVectorFromNumpy(data, dim); + } else { + return IVector::createCpuVectorFromNumpy(data, dim, copy); + } +} + IVector* IVector::createCpuVectorFromNumpy(int* data, int dim, bool copy) { auto v = new IVector(); if (copy) { @@ -50,7 +61,7 @@ IVector* IVector::createCpuVectorFromNumpy(int* data, int dim, bool copy) { return v; } -IVector* IVector::createGpuVectorFromNumy(int* data, int dim) { +IVector* IVector::createGpuVectorFromNumpy(int* data, int dim) { auto v = new IVector(); v->m->vec = paddle::IVector::create(dim, true); v->m->vec->copyFrom(data, dim); @@ -188,6 +199,17 @@ Vector* Vector::createByPaddleVectorPtr(void* ptr) { } } +Vector* Vector::createVectorFromNumpy(float* data, int dim, bool copy, + bool useGpu) { + if (useGpu) { + /// if use gpu only copy=True is supported + CHECK(copy); + return Vector::createGpuVectorFromNumpy(data, dim); + } else { + return Vector::createCpuVectorFromNumpy(data, dim, copy); + } +} + Vector* Vector::createCpuVectorFromNumpy(float* data, int dim, bool copy) { CHECK_GT(dim, 0); auto retVec = new Vector(); -- GitLab From f22573bdafa4554482fa51459e2763b12bea3190 Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Tue, 8 Nov 2016 10:21:55 -0800 Subject: [PATCH 0016/1503] changed to isUsingGpu() in PaddleAPI.h and throw exceptions instead of CHECK --- paddle/api/Matrix.cpp | 8 ++++++-- paddle/api/PaddleAPI.h | 25 ++++++++++++++----------- paddle/api/Util.cpp | 2 +- paddle/api/Vector.cpp | 16 ++++++++++++---- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/paddle/api/Matrix.cpp b/paddle/api/Matrix.cpp index f1ff957c6b..6201ce926f 100644 --- a/paddle/api/Matrix.cpp +++ b/paddle/api/Matrix.cpp @@ -53,10 +53,14 @@ Matrix* Matrix::createDense(const std::vector& data, size_t height, } Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, - bool copy, bool useGpu) { + bool copy, bool useGpu) + throw (UnsupportError) { if (useGpu) { /// Gpu mode only supports copy=True - CHECK(copy); + if (!copy) { + UnsupportError e; + throw e; + } return Matrix::createGpuDenseFromNumpy(data, dim1, dim2); } else { return Matrix::createCpuDenseFromNumpy(data, dim1, dim2, copy); diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 0825260fa1..386de6d597 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -43,7 +43,7 @@ using namespace paddle::enumeration_wrapper; // NOLINT void initPaddle(int argc, char** argv); /// Return FLAGS_use_gpu -bool isUseGpu(); +bool isUsingGpu(); /// Return true if this py_paddle is compiled in GPU Version bool isGpuVersion(); @@ -105,7 +105,7 @@ public: * Create A Matrix with height,width, which is filled by zero. */ static Matrix* createZero(size_t height, size_t width, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()); /** * Create Sparse Matrix. @@ -118,7 +118,7 @@ public: */ static Matrix* createSparse(size_t height, size_t width, size_t nnz, bool isNonVal = true, bool trans = false, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()); /** * Create Dense Matrix. @@ -127,11 +127,12 @@ public: * @note the value will be copy into a new matrix. */ static Matrix* createDense(const std::vector& data, size_t height, - size_t width, bool useGpu = isUseGpu()); + size_t width, bool useGpu = isUsingGpu()); static Matrix* createDenseFromNumpy(float* data, int dim1, int dim2, bool copy = true, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError) ; /** * Create Cpu Dense Matrix from numpy matrix, dtype=float32 @@ -229,7 +230,7 @@ public: ~Vector(); /// Create Vector filled with zero. - static Vector* createZero(size_t sz, bool useGpu = isUseGpu()); + static Vector* createZero(size_t sz, bool useGpu = isUsingGpu()); /** * Create Vector from list of float. @@ -237,10 +238,11 @@ public: * It will create a new vector, and copy data into it. */ static Vector* create(const std::vector& data, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()); static Vector* createVectorFromNumpy(float* data, int dim, bool copy = true, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError) ; /** * Create Cpu Vector from numpy array, which dtype=float32 * @@ -290,17 +292,18 @@ class IVector { public: /// Create IVector filled with zero - static IVector* createZero(size_t sz, bool useGpu = isUseGpu()); + static IVector* createZero(size_t sz, bool useGpu = isUsingGpu()); /** * Create IVector from list of int. * It will create a new vector, and copy data into it. */ static IVector* create(const std::vector& data, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()); static IVector* createVectorFromNumpy(int* data, int dim, bool copy = true, - bool useGpu = isUseGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError) ; /** * Create Cpu IVector from numpy array, which dtype=int32 diff --git a/paddle/api/Util.cpp b/paddle/api/Util.cpp index f953b322ce..f72c06aad3 100644 --- a/paddle/api/Util.cpp +++ b/paddle/api/Util.cpp @@ -41,7 +41,7 @@ IntWithFloatArray::IntWithFloatArray(const float* v, const int* i, size_t l, bool f) : valBuf(v), idxBuf(i), length(l), needFree(f) {} -bool isUseGpu() {return FLAGS_use_gpu;} +bool isUsingGpu() {return FLAGS_use_gpu;} bool isGpuVersion() { #ifdef PADDLE_ONLY_CPU diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 5abafad9d1..787cf1c973 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -40,10 +40,14 @@ IVector* IVector::create(const std::vector& data, bool useGpu) { } IVector* IVector::createVectorFromNumpy(int* data, int dim, bool copy, - bool useGpu) { + bool useGpu) + throw (UnsupportError) { if (useGpu) { /// if use gpu only copy=true is supported - CHECK(copy); + if (!copy) { + UnsupportError e; + throw e; + } return IVector::createGpuVectorFromNumpy(data, dim); } else { return IVector::createCpuVectorFromNumpy(data, dim, copy); @@ -200,10 +204,14 @@ Vector* Vector::createByPaddleVectorPtr(void* ptr) { } Vector* Vector::createVectorFromNumpy(float* data, int dim, bool copy, - bool useGpu) { + bool useGpu) + throw (UnsupportError) { if (useGpu) { /// if use gpu only copy=True is supported - CHECK(copy); + if (!copy) { + UnsupportError e; + throw e; + } return Vector::createGpuVectorFromNumpy(data, dim); } else { return Vector::createCpuVectorFromNumpy(data, dim, copy); -- GitLab From 70fecee080ac91781c6c68bdc9cbe9fdaa0cbe48 Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Tue, 8 Nov 2016 14:02:21 -0800 Subject: [PATCH 0017/1503] add unittest for Matrix and Vector in API --- paddle/api/PaddleAPI.h | 3 +++ paddle/api/Vector.cpp | 15 +++++++++++ paddle/api/test/testMatrix.py | 14 +++++++--- paddle/api/test/testVector.py | 49 ++++++++++++++++++++++++++++++----- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 386de6d597..5df7320136 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -272,6 +272,9 @@ public: /// Return is GPU vector or not. bool isGpu() const; + /// Return a list of float, the memory is alloced and copied. + FloatArray getData() const; + /// __len__ in python size_t getSize() const; diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 787cf1c973..3da7a5c476 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -267,6 +267,21 @@ void Vector::copyFromNumpyArray(float* data, int dim) { m->vec->copyFrom(data, dim); } +FloatArray Vector::getData() const { + if (this->isGpu()) { + float* src = m->vec->getData(); + size_t len = m->vec->getSize(); + float* dest = new float[len]; + hl_memcpy_device2host(dest, src, len * sizeof(float)); + FloatArray ret_val(dest, len); + ret_val.needFree = true; + return ret_val; + } else { + FloatArray ret_val(m->vec->getData(), m->vec->getSize()); + return ret_val; + } +} + bool Vector::isGpu() const { return std::dynamic_pointer_cast(m->vec) != nullptr; } diff --git a/paddle/api/test/testMatrix.py b/paddle/api/test/testMatrix.py index 11035a9281..6d0d42f340 100644 --- a/paddle/api/test/testMatrix.py +++ b/paddle/api/test/testMatrix.py @@ -42,7 +42,7 @@ class TestMatrix(unittest.TestCase): self.assertEqual(m.getSparseRowCols(2), []) def test_sparse_value(self): - m = swig_paddle.Matrix.createSparse(3, 3, 6, False) + m = swig_paddle.Matrix.createSparse(3, 3, 6, False, False, False) self.assertIsNotNone(m) m.sparseCopyFrom([0, 2, 3, 3], [0, 1, 2], [7.3, 4.2, 3.2]) @@ -66,7 +66,7 @@ class TestMatrix(unittest.TestCase): self.assertIsNotNone(m) self.assertTrue(abs(m.get(1, 1) - 0.5) < 1e-5) - def test_numpy(self): + def test_numpyCpu(self): numpy_mat = np.matrix([[1, 2], [3, 4], [5, 6]], dtype="float32") m = swig_paddle.Matrix.createCpuDenseFromNumpy(numpy_mat) self.assertEqual( @@ -100,8 +100,16 @@ class TestMatrix(unittest.TestCase): for a, e in zip(gpu_m.getData(), [1.0, 3.23, 3.0, 4.0, 5.0, 6.0]): self.assertAlmostEqual(a, e) + + def test_numpy(self): + numpy_mat = np.matrix([[1, 2], [3, 4], [5, 6]], dtype="float32") + m = swig_paddle.Matrix.createDenseFromNumpy(numpy_mat) + self.assertEqual((int(m.getHeight()), int(m.getWidth())), numpy_mat.shape) + self.assertEqual(m.isGpu(), swig_paddle.isUsingGpu()) + for a, e in zip(m.getData(), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]): + self.assertAlmostEqual(a, e) if __name__ == "__main__": - swig_paddle.initPaddle("--use_gpu=0") + swig_paddle.initPaddle("--use_gpu=1" if swig_paddle.isGpuVersion() else "--use_gpu=0") unittest.main() diff --git a/paddle/api/test/testVector.py b/paddle/api/test/testVector.py index 5226df79ee..4903951414 100644 --- a/paddle/api/test/testVector.py +++ b/paddle/api/test/testVector.py @@ -20,20 +20,28 @@ import unittest class TestIVector(unittest.TestCase): def test_createZero(self): - m = swig_paddle.IVector.createZero(10) + m = swig_paddle.IVector.createZero(10, False) self.assertIsNotNone(m) for i in xrange(10): self.assertEqual(m[i], 0) m[i] = i self.assertEqual(m[i], i) + + m = swig_paddle.IVector.createZero(10) + self.assertEqual(m.isGpu(), swig_paddle.isUsingGpu()) + self.assertEqual(m.getData(), [0]*10) def test_create(self): - m = swig_paddle.IVector.create(range(10)) + m = swig_paddle.IVector.create(range(10), False) self.assertIsNotNone(m) for i in xrange(10): self.assertEqual(m[i], i) + + m = swig_paddle.IVector.create(range(10)) + self.assertEqual(m.isGpu(), swig_paddle.isUsingGpu()) + self.assertEqual(m.getData(), range(10)) - def test_numpy(self): + def test_cpu_numpy(self): vec = np.array([1, 3, 4, 65, 78, 1, 4], dtype="int32") iv = swig_paddle.IVector.createCpuVectorFromNumpy(vec) self.assertEqual(vec.shape[0], int(iv.__len__())) @@ -61,25 +69,43 @@ class TestIVector(unittest.TestCase): expect_vec = range(0, 10) expect_vec[4] = 7 self.assertEqual(vec.getData(), expect_vec) + + def test_numpy(self): + vec = np.array([1, 3, 4, 65, 78, 1, 4], dtype="int32") + iv = swig_paddle.IVector.createVectorFromNumpy(vec) + self.assertEqual(iv.isGpu(), swig_paddle.isUsingGpu()) + self.assertEqual(iv.getData(), list(vec)) class TestVector(unittest.TestCase): def testCreateZero(self): - v = swig_paddle.Vector.createZero(10) + v = swig_paddle.Vector.createZero(10, False) self.assertIsNotNone(v) for i in xrange(len(v)): self.assertTrue(util.doubleEqual(v[i], 0)) v[i] = i self.assertTrue(util.doubleEqual(v[i], i)) + + v = swig_paddle.Vector.createZero(10) + self.assertEqual(v.isGpu(), swig_paddle.isUsingGpu()) + self.assertEqual(v.getData(), [0]*10) def testCreate(self): - v = swig_paddle.Vector.create([x / 100.0 for x in xrange(100)]) + v = swig_paddle.Vector.create([x / 100.0 for x in xrange(100)], False) self.assertIsNotNone(v) for i in xrange(len(v)): self.assertTrue(util.doubleEqual(v[i], i / 100.0)) self.assertEqual(100, len(v)) + + v = swig_paddle.Vector.create([x / 100.0 for x in xrange(100)]) + self.assertEqual(v.isGpu(), swig_paddle.isUsingGpu()) + self.assertEqual(100, len(v)) + vdata = v.getData() + for i in xrange(len(v)): + self.assertTrue(util.doubleEqual(vdata[i], i / 100.0)) + - def testNumpy(self): + def testCpuNumpy(self): numpy_arr = np.array([1.2, 2.3, 3.4, 4.5], dtype="float32") vec = swig_paddle.Vector.createCpuVectorFromNumpy(numpy_arr) assert isinstance(vec, swig_paddle.Vector) @@ -102,9 +128,18 @@ class TestVector(unittest.TestCase): for i in xrange(1, len(numpy_3)): util.doubleEqual(numpy_3[i], vec[i]) + + def testNumpy(self): + numpy_arr = np.array([1.2, 2.3, 3.4, 4.5], dtype="float32") + vec = swig_paddle.Vector.createVectorFromNumpy(numpy_arr) + self.assertEqual(vec.isGpu(), swig_paddle.isUsingGpu()) + vecData = vec.getData() + for n, v in zip(numpy_arr, vecData): + self.assertTrue(util.doubleEqual(n, v)) + def testCopyFromNumpy(self): - vec = swig_paddle.Vector.createZero(1) + vec = swig_paddle.Vector.createZero(1, False) arr = np.array([1.3, 3.2, 2.4], dtype="float32") vec.copyFromNumpyArray(arr) for i in xrange(len(vec)): -- GitLab From 91e6dcb68f19b339c526a90672122dad54789fe5 Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Tue, 8 Nov 2016 15:32:21 -0800 Subject: [PATCH 0018/1503] fixed a bug in Paddle::Vector::createCpuVectorFromNumpy --- paddle/api/Vector.cpp | 2 +- paddle/api/test/testVector.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 3da7a5c476..b8b1b2d2f1 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -223,7 +223,7 @@ Vector* Vector::createCpuVectorFromNumpy(float* data, int dim, bool copy) { auto retVec = new Vector(); if (copy) { retVec->m->vec = paddle::Vector::create((size_t)dim, false); - return retVec; + retVec->m->vec->copyFrom(data, dim); } else { retVec->m->vec = paddle::Vector::create(data, (size_t)dim, false); } diff --git a/paddle/api/test/testVector.py b/paddle/api/test/testVector.py index 4903951414..5ca4d90dee 100644 --- a/paddle/api/test/testVector.py +++ b/paddle/api/test/testVector.py @@ -150,3 +150,4 @@ if __name__ == '__main__': swig_paddle.initPaddle("--use_gpu=1" if swig_paddle.isGpuVersion() else "--use_gpu=0") unittest.main() + -- GitLab From b207535198d218e49508752cdc8ae9d4221d78d4 Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Fri, 11 Nov 2016 11:21:20 -0800 Subject: [PATCH 0019/1503] Add setUseGpu in PaddleAPI.h and handle UnsupportedError in swig with meaningful message displayed --- paddle/api/Matrix.cpp | 6 ++---- paddle/api/Paddle.swig | 14 ++++++++++++++ paddle/api/PaddleAPI.h | 19 ++++++++++++------- paddle/api/Util.cpp | 2 ++ paddle/api/Vector.cpp | 12 ++++-------- paddle/api/test/testMatrix.py | 8 ++++++-- paddle/api/test/testVector.py | 10 ++++++---- 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/paddle/api/Matrix.cpp b/paddle/api/Matrix.cpp index 6201ce926f..0c8d2935a0 100644 --- a/paddle/api/Matrix.cpp +++ b/paddle/api/Matrix.cpp @@ -53,13 +53,11 @@ Matrix* Matrix::createDense(const std::vector& data, size_t height, } Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, - bool copy, bool useGpu) - throw (UnsupportError) { + bool copy, bool useGpu) { if (useGpu) { /// Gpu mode only supports copy=True if (!copy) { - UnsupportError e; - throw e; + throw UnsupportError("Gpu mode only supports copy=True"); } return Matrix::createGpuDenseFromNumpy(data, dim1, dim2); } else { diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index eaee182b52..e723a669f3 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -4,6 +4,20 @@ #define SWIG_FILE_WITH_INIT #include "api/PaddleAPI.h" %} + +%include "exception.i" +%exception{ + try{ + $action + } + catch(UnsupportError &ex ){ + SWIG_exception(SWIG_RuntimeError, ex.what()); + } + catch( ... ){ + SWIG_fail; + } +} + %include "std_vector.i" %include "std_pair.i" #ifdef SWIGPYTHON diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 5df7320136..807519e739 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -18,6 +18,7 @@ limitations under the License. */ #include #include #include +#include #include #include "paddle/utils/GlobalConstants.h" #include "paddle/utils/TypeDefs.h" @@ -45,6 +46,9 @@ void initPaddle(int argc, char** argv); /// Return FLAGS_use_gpu bool isUsingGpu(); +/// Set the Flags_use_gpu to the given parameter +void setUseGpu(bool useGpu); + /// Return true if this py_paddle is compiled in GPU Version bool isGpuVersion(); @@ -55,7 +59,11 @@ class IOError {}; class RangeError {}; /// Not support Error, such as access GPU memory directly, etc. -class UnsupportError {}; +class UnsupportError : public std::runtime_error { +public: + UnsupportError() : std::runtime_error(" ") {}; + UnsupportError(const std::string& message) : std::runtime_error(message) {}; +}; /// This type will map to python's list of float. struct FloatArray { @@ -131,8 +139,7 @@ public: static Matrix* createDenseFromNumpy(float* data, int dim1, int dim2, bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError) ; + bool useGpu = isUsingGpu()); /** * Create Cpu Dense Matrix from numpy matrix, dtype=float32 @@ -241,8 +248,7 @@ public: bool useGpu = isUsingGpu()); static Vector* createVectorFromNumpy(float* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError) ; + bool useGpu = isUsingGpu()); /** * Create Cpu Vector from numpy array, which dtype=float32 * @@ -305,8 +311,7 @@ public: bool useGpu = isUsingGpu()); static IVector* createVectorFromNumpy(int* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError) ; + bool useGpu = isUsingGpu()); /** * Create Cpu IVector from numpy array, which dtype=int32 diff --git a/paddle/api/Util.cpp b/paddle/api/Util.cpp index f72c06aad3..a8932351a6 100644 --- a/paddle/api/Util.cpp +++ b/paddle/api/Util.cpp @@ -43,6 +43,8 @@ IntWithFloatArray::IntWithFloatArray(const float* v, const int* i, size_t l, bool isUsingGpu() {return FLAGS_use_gpu;} +void setUseGpu(bool useGpu) {FLAGS_use_gpu = useGpu;} + bool isGpuVersion() { #ifdef PADDLE_ONLY_CPU return false; diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index b8b1b2d2f1..547be27ed5 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -40,13 +40,11 @@ IVector* IVector::create(const std::vector& data, bool useGpu) { } IVector* IVector::createVectorFromNumpy(int* data, int dim, bool copy, - bool useGpu) - throw (UnsupportError) { + bool useGpu) { if (useGpu) { /// if use gpu only copy=true is supported if (!copy) { - UnsupportError e; - throw e; + throw UnsupportError("Gpu mode only supports copy=True"); } return IVector::createGpuVectorFromNumpy(data, dim); } else { @@ -204,13 +202,11 @@ Vector* Vector::createByPaddleVectorPtr(void* ptr) { } Vector* Vector::createVectorFromNumpy(float* data, int dim, bool copy, - bool useGpu) - throw (UnsupportError) { + bool useGpu) { if (useGpu) { /// if use gpu only copy=True is supported if (!copy) { - UnsupportError e; - throw e; + throw UnsupportError("Gpu mode only supports copy=True"); } return Vector::createGpuVectorFromNumpy(data, dim); } else { diff --git a/paddle/api/test/testMatrix.py b/paddle/api/test/testMatrix.py index 6d0d42f340..87cedd607c 100644 --- a/paddle/api/test/testMatrix.py +++ b/paddle/api/test/testMatrix.py @@ -111,5 +111,9 @@ class TestMatrix(unittest.TestCase): if __name__ == "__main__": - swig_paddle.initPaddle("--use_gpu=1" if swig_paddle.isGpuVersion() else "--use_gpu=0") - unittest.main() + swig_paddle.initPaddle("--use_gpu=0") + suite = unittest.TestLoader().loadTestsFromTestCase(TestMatrix) + unittest.TextTestRunner().run(suite) + if swig_paddle.isGpuVersion(): + swig_paddle.setUseGpu(True) + unittest.main() diff --git a/paddle/api/test/testVector.py b/paddle/api/test/testVector.py index 5ca4d90dee..48aaa1d73d 100644 --- a/paddle/api/test/testVector.py +++ b/paddle/api/test/testVector.py @@ -147,7 +147,9 @@ class TestVector(unittest.TestCase): if __name__ == '__main__': - swig_paddle.initPaddle("--use_gpu=1" - if swig_paddle.isGpuVersion() else "--use_gpu=0") - unittest.main() - + swig_paddle.initPaddle("--use_gpu=0") + suite = unittest.TestLoader().loadTestsFromTestCase(TestVector) + unittest.TextTestRunner().run(suite) + if swig_paddle.isGpuVersion(): + swig_paddle.setUseGpu(True) + unittest.main() \ No newline at end of file -- GitLab From 4c86285a996a9be52c63eeef9d917cf18b78302b Mon Sep 17 00:00:00 2001 From: wangyang59 Date: Fri, 11 Nov 2016 13:44:16 -0800 Subject: [PATCH 0020/1503] modifed Paddle.swig to specially handle UnsupportError only --- paddle/api/Matrix.cpp | 3 ++- paddle/api/Paddle.swig | 15 ++++----------- paddle/api/PaddleAPI.h | 9 ++++++--- paddle/api/Vector.cpp | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/paddle/api/Matrix.cpp b/paddle/api/Matrix.cpp index 0c8d2935a0..e5493a381a 100644 --- a/paddle/api/Matrix.cpp +++ b/paddle/api/Matrix.cpp @@ -53,7 +53,8 @@ Matrix* Matrix::createDense(const std::vector& data, size_t height, } Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, - bool copy, bool useGpu) { + bool copy, bool useGpu) + throw (UnsupportError) { if (useGpu) { /// Gpu mode only supports copy=True if (!copy) { diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index e723a669f3..6a0fbc537d 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -6,17 +6,10 @@ %} %include "exception.i" -%exception{ - try{ - $action - } - catch(UnsupportError &ex ){ - SWIG_exception(SWIG_RuntimeError, ex.what()); - } - catch( ... ){ - SWIG_fail; - } -} +%typemap(throws) UnsupportError %{ + SWIG_exception(SWIG_RuntimeError, $1.what()); + SWIG_fail; +%} %include "std_vector.i" %include "std_pair.i" diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 807519e739..5688ece44d 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -139,7 +139,8 @@ public: static Matrix* createDenseFromNumpy(float* data, int dim1, int dim2, bool copy = true, - bool useGpu = isUsingGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError); /** * Create Cpu Dense Matrix from numpy matrix, dtype=float32 @@ -248,7 +249,8 @@ public: bool useGpu = isUsingGpu()); static Vector* createVectorFromNumpy(float* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError); /** * Create Cpu Vector from numpy array, which dtype=float32 * @@ -311,7 +313,8 @@ public: bool useGpu = isUsingGpu()); static IVector* createVectorFromNumpy(int* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()); + bool useGpu = isUsingGpu()) + throw (UnsupportError); /** * Create Cpu IVector from numpy array, which dtype=int32 diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 547be27ed5..d44cdefc35 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -40,7 +40,7 @@ IVector* IVector::create(const std::vector& data, bool useGpu) { } IVector* IVector::createVectorFromNumpy(int* data, int dim, bool copy, - bool useGpu) { + bool useGpu) throw (UnsupportError){ if (useGpu) { /// if use gpu only copy=true is supported if (!copy) { @@ -202,7 +202,7 @@ Vector* Vector::createByPaddleVectorPtr(void* ptr) { } Vector* Vector::createVectorFromNumpy(float* data, int dim, bool copy, - bool useGpu) { + bool useGpu) throw (UnsupportError){ if (useGpu) { /// if use gpu only copy=True is supported if (!copy) { -- GitLab From b8f73d12754f6c3c702b5107026069bdf3a28f18 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Tue, 15 Nov 2016 11:08:51 +0800 Subject: [PATCH 0021/1503] delete redundant code --- demo/semantic_role_labeling/db_lstm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/semantic_role_labeling/db_lstm.py b/demo/semantic_role_labeling/db_lstm.py index 68feb9c4a7..32dcd07483 100644 --- a/demo/semantic_role_labeling/db_lstm.py +++ b/demo/semantic_role_labeling/db_lstm.py @@ -122,7 +122,7 @@ std_default = ParameterAttribute(initial_std=default_std) word_embedding = embedding_layer(size=word_dim, input=word, param_attr=emb_para) predicate_embedding = embedding_layer(size=word_dim, input=predicate, param_attr=ParameterAttribute(name='vemb',initial_std=default_std)) -ctx_n2_embedding = embedding_layer(size=word_dim, input=ctx_n2, param_attr=emb_para) + ctx_n2_embedding = embedding_layer(size=word_dim, input=ctx_n2, param_attr=emb_para) ctx_n1_embedding = embedding_layer(size=word_dim, input=ctx_n1, param_attr=emb_para) ctx_0_embedding = embedding_layer(size=word_dim, input=ctx_0, param_attr=emb_para) -- GitLab From 7df77bcfa9382da4d7cb099110134cf2b6b6a1e3 Mon Sep 17 00:00:00 2001 From: utopiar Date: Tue, 15 Nov 2016 11:10:04 +0800 Subject: [PATCH 0022/1503] fix bug for issue 345 --- demo/sentiment/data/get_imdb.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/sentiment/data/get_imdb.sh b/demo/sentiment/data/get_imdb.sh index 41523927af..28fa86232d 100755 --- a/demo/sentiment/data/get_imdb.sh +++ b/demo/sentiment/data/get_imdb.sh @@ -38,11 +38,11 @@ unzip master.zip mkdir -p imdb/train mkdir -p imdb/test -cp -r aclImdb/train/pos/ imdb/train/ -cp -r aclImdb/train/neg/ imdb/train/ +cp -r aclImdb/train/pos/ imdb/train/pos +cp -r aclImdb/train/neg/ imdb/train/neg -cp -r aclImdb/test/pos/ imdb/test/ -cp -r aclImdb/test/neg/ imdb/test/ +cp -r aclImdb/test/pos/ imdb/test/pos +cp -r aclImdb/test/neg/ imdb/test/neg #remove compressed package rm aclImdb_v1.tar.gz -- GitLab From 8c59437612280738f59df859d9f512b318153d05 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 15 Nov 2016 12:35:36 +0800 Subject: [PATCH 0023/1503] change is_train to is_generating --- python/paddle/trainer_config_helpers/layers.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index f0757b9ce2..7cd290023a 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -494,8 +494,7 @@ def scaling_projection(input, param_attr=None): :return: A ScalingProjection object :rtype: ScalingProjection """ - proj = ScalingProjection(input_layer_name=input.name, - **param_attr.attr) + proj = ScalingProjection(input_layer_name=input.name, **param_attr.attr) proj.origin = input return proj @@ -2788,7 +2787,7 @@ def recurrent_group(step, reverse=False, name=None, targetInlink=None, - is_train=True): + is_generating=False): """ Recurrent layer group is an extremely flexible recurrent unit in PaddlePaddle. As long as the user defines the calculation done within a @@ -2853,11 +2852,11 @@ def recurrent_group(step, :type targetInlink: LayerOutput|SubsequenceInput - :param is_train: recurrent_group is used for training (True) or generating (False). - If is training, one of the input type must be LayerOutput; else, - none of input type should be LayerOutput. + :param is_generating: If is generating, none of input type should be LayerOutput; + else, for training or testing, one of the input type must + be LayerOutput. - : type is_train: bool + : type is_generating: bool :return: LayerOutput object. :rtype: LayerOutput @@ -2928,7 +2927,7 @@ def recurrent_group(step, mix += identity_projection(mem) in_args.append(mem) - assert (is_train == has_LayerOutput) + assert (is_generating != has_LayerOutput) layer_outs = step(*in_args) @@ -3225,7 +3224,7 @@ def beam_search(step, input=real_input, reverse=False, name=name, - is_train=False) + is_generating=True) return tmp -- GitLab From f663d8815ff7455545dac2a07090b9754f69021a Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Tue, 15 Nov 2016 15:54:47 +0800 Subject: [PATCH 0024/1503] delete the default settings --- demo/semantic_role_labeling/train.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/demo/semantic_role_labeling/train.sh b/demo/semantic_role_labeling/train.sh index b566931db0..90b32d5ce4 100644 --- a/demo/semantic_role_labeling/train.sh +++ b/demo/semantic_role_labeling/train.sh @@ -20,14 +20,10 @@ paddle train \ --log_period=5000 \ --trainer_count=1 \ --show_parameter_stats_period=5000 \ - --saving_period=1 \ --save_dir=./output \ - --local=1 \ --num_passes=10000 \ - --test_period=0 \ --average_test_period=10000000 \ --init_model_path=./data \ --load_missing_parameter_strategy=rand \ - --dot_period=100 \ 2>&1 | tee 'train.log' -- GitLab From 5c8339f5d9f58eab0d6cb39f76599851b8e1f056 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Tue, 15 Nov 2016 15:59:34 +0800 Subject: [PATCH 0025/1503] delete default setting in docs --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index c6b9813f6a..69378d0d4e 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -124,15 +124,11 @@ paddle train \ --log_period=5000 \ --trainer_count=1 \ --show_parameter_stats_period=5000 \ - --saving_period=1 \ --save_dir=./output \ - --local=1 \ --num_passes=10000 \ - --test_period=0 \ --average_test_period=10000000 \ --init_model_path=./data \ --load_missing_parameter_strategy=rand \ - --dot_period=100 \ 2>&1 | tee 'train.log' ``` @@ -141,15 +137,11 @@ paddle train \ - \--log_period=500: print log every 20 batches. - \--trainer_count=1: set thread number (or GPU count). - \--show_parameter_stats_period=5000: show parameter statistic every 100 batches. -- \--saving_period=1: save model per pass - \--save_dir=./output: output path to save models. -- \--local=1: traing in local mode - \--num_passes=10000: set pass number, one pass in PaddlePaddle means training all samples in dataset one time. -- \--test_period=0: run testing each pass - \--average_test_period=10000000: do test on average parameter every average_test_period batches - \--init_model_path=./data: parameter initialization path - \--load_missing_parameter_strategy=rand: random initialization unexisted parameters -- \--dot_period=100: print a dot per 100 batches After training, the models will be saved in directory `output`. -- GitLab From cfa304bad47ec86b6d44c8fa1807deb5664a2ffa Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Tue, 15 Nov 2016 16:13:05 +0800 Subject: [PATCH 0026/1503] to loop --- demo/semantic_role_labeling/db_lstm.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/demo/semantic_role_labeling/db_lstm.py b/demo/semantic_role_labeling/db_lstm.py index 32dcd07483..943076d914 100644 --- a/demo/semantic_role_labeling/db_lstm.py +++ b/demo/semantic_role_labeling/db_lstm.py @@ -130,21 +130,14 @@ ctx_p1_embedding = embedding_layer(size=word_dim, input=ctx_p1, param_attr=emb_p ctx_p2_embedding = embedding_layer(size=word_dim, input=ctx_p2, param_attr=emb_para) mark_embedding = embedding_layer(name='word_ctx-in_embedding', size=mark_dim, input=mark, param_attr=std_0) +all_emb=[word_embedding, predicate_embedding, ctx_n2_embedding, ctx_n1_embedding, ctx_0_embedding, + ctx_p1_embedding, ctx_p2_embedding, mark_embedding] hidden_0 = mixed_layer( name='hidden0', size=hidden_dim, bias_attr=std_default, - input=[ - full_matrix_projection(input=word_embedding, param_attr=std_default), - full_matrix_projection(input=predicate_embedding, param_attr=std_default), - full_matrix_projection(input=ctx_n2_embedding, param_attr=std_default), - full_matrix_projection(input=ctx_n1_embedding, param_attr=std_default), - full_matrix_projection(input=ctx_0_embedding, param_attr=std_default), - full_matrix_projection(input=ctx_p1_embedding, param_attr=std_default), - full_matrix_projection(input=ctx_p2_embedding, param_attr=std_default), - full_matrix_projection(input=mark_embedding, param_attr=std_default) - ]) + input=[ full_matrix_projection(input=emb, param_attr=std_default ) for emb in all_emb ]) mix_hidden_lr = 1e-3 -- GitLab From 29727289fca4f5d925a094cd6317e37e9644a65f Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 15 Nov 2016 16:31:17 +0800 Subject: [PATCH 0027/1503] modify first level directory of english doc --- doc/algorithm/index.rst | 7 ++++++ doc/algorithm/rnn/rnn.rst | 4 ++-- doc/build/contribute_to_paddle.md | 2 +- doc/build/index.rst | 11 +++------ doc/demo/quick_start/index_en.md | 2 +- doc/dev/index.rst | 9 ++++++++ doc/dev/layer.md | 4 ++++ doc/dev/new_layer/index.rst | 7 ------ doc/dev/new_layer/new_layer.rst | 1 + doc/index.md | 23 ------------------- doc/index.rst | 10 ++++++++ doc/layer.md | 4 ---- doc/ui/api/trainer_config_helpers/layers.rst | 2 +- doc/user_guide.rst | 13 +++++++++++ .../paddle/trainer_config_helpers/layers.py | 1 + 15 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 doc/algorithm/index.rst create mode 100644 doc/dev/index.rst create mode 100644 doc/dev/layer.md delete mode 100644 doc/dev/new_layer/index.rst delete mode 100644 doc/index.md create mode 100644 doc/index.rst delete mode 100644 doc/layer.md create mode 100644 doc/user_guide.rst diff --git a/doc/algorithm/index.rst b/doc/algorithm/index.rst new file mode 100644 index 0000000000..6073add3c0 --- /dev/null +++ b/doc/algorithm/index.rst @@ -0,0 +1,7 @@ +Algorithm Tutorial +================== + +.. toctree:: + :maxdepth: 1 + + rnn/rnn.rst diff --git a/doc/algorithm/rnn/rnn.rst b/doc/algorithm/rnn/rnn.rst index 343f55a20e..399c5da5ff 100644 --- a/doc/algorithm/rnn/rnn.rst +++ b/doc/algorithm/rnn/rnn.rst @@ -1,5 +1,5 @@ -Recurrent Neural Network Configuration -====================================== +RNN Configuration +================= This tutorial will guide you how to configure recurrent neural network in PaddlePaddle. PaddlePaddle supports highly flexible and efficient recurrent neural network configuration. In this tutorial, you will learn how to: diff --git a/doc/build/contribute_to_paddle.md b/doc/build/contribute_to_paddle.md index a9ab69c5f4..1d03eb7362 100644 --- a/doc/build/contribute_to_paddle.md +++ b/doc/build/contribute_to_paddle.md @@ -1,4 +1,4 @@ -# Contribute to PaddlePaddle +# Contribute Code We sincerely appreciate your contributions. You can use fork and pull request workflow to merge your code. diff --git a/doc/build/index.rst b/doc/build/index.rst index 511cdea145..b4fe459604 100644 --- a/doc/build/index.rst +++ b/doc/build/index.rst @@ -1,5 +1,5 @@ -Build And Install PaddlePaddle -================================ +Install and Build +================= Install PaddlePaddle ---------------------- @@ -18,11 +18,7 @@ Build from Source .. warning:: - Please use :code:`deb` package or :code:`docker` image to install paddle. The building guide is used for hacking or contributing to PaddlePaddle. - - -If you want to hack and contribute PaddlePaddle source code, following guides can help you\: - + Please use :code:`deb` package or :code:`docker` image to install paddle. The building guide is used for hacking or contributing PaddlePaddle source code. .. toctree:: :maxdepth: 1 @@ -30,4 +26,3 @@ If you want to hack and contribute PaddlePaddle source code, following guides ca build_from_source.md contribute_to_paddle.md - diff --git a/doc/demo/quick_start/index_en.md b/doc/demo/quick_start/index_en.md index e7d7451229..80d816a768 100644 --- a/doc/demo/quick_start/index_en.md +++ b/doc/demo/quick_start/index_en.md @@ -1,4 +1,4 @@ -# Quick Start Tutorial +# Quick Start This tutorial will teach the basics of deep learning (DL), including how to implement many different models in PaddlePaddle. You will learn how to: - Prepare data into the standardized format that PaddlePaddle accepts. diff --git a/doc/dev/index.rst b/doc/dev/index.rst new file mode 100644 index 0000000000..0468dd492b --- /dev/null +++ b/doc/dev/index.rst @@ -0,0 +1,9 @@ +Development Guide +================= + +.. toctree:: + :maxdepth: 1 + + layer.md + new_layer/new_layer.rst + ../source/index.md diff --git a/doc/dev/layer.md b/doc/dev/layer.md new file mode 100644 index 0000000000..930fb0de1a --- /dev/null +++ b/doc/dev/layer.md @@ -0,0 +1,4 @@ +# Layer Documents + +* [Layer Source Code Document](../source/gserver/layers/index.rst) +* [Layer Python API Document](../ui/api/trainer_config_helpers/index.rst) diff --git a/doc/dev/new_layer/index.rst b/doc/dev/new_layer/index.rst deleted file mode 100644 index 37dac3a14d..0000000000 --- a/doc/dev/new_layer/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Writing New Layers -================== - -.. toctree:: - :maxdepth: 3 - - new_layer.rst diff --git a/doc/dev/new_layer/new_layer.rst b/doc/dev/new_layer/new_layer.rst index bd4a4c46c8..2fa0073048 100644 --- a/doc/dev/new_layer/new_layer.rst +++ b/doc/dev/new_layer/new_layer.rst @@ -1,3 +1,4 @@ +================== Writing New Layers ================== diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index cbd08ba52a..0000000000 --- a/doc/index.md +++ /dev/null @@ -1,23 +0,0 @@ -PaddlePaddle Documentation -========================== - -User Guide ----------- -* [Introduction](introduction/index.md) -* [Quick Start](demo/quick_start/index_en.md) -* [Build and Installation](build/index.rst) -* [Contribute Code](build/contribute_to_paddle.md) -* [User Interface](ui/index.md) -* [Model Config Interface](ui/api/trainer_config_helpers/index.rst) -* [Example and Demo](demo/index.md) -* [Cluster Train](cluster/index.md) - -Development Guide ------------------ -* [Layer Documents](layer.md) -* [Writing New Layers](dev/new_layer/index.rst) -* [Source Code Documents](source/index.md) - -Algorithm Tutorial ------------------- -* [RNN Configuration](algorithm/rnn/rnn.rst) diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000000..668ad75a90 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,10 @@ +PaddlePaddle Documentation +========================== + +.. toctree:: + :maxdepth: 1 + + introduction/index.md + user_guide.rst + dev/index.rst + algorithm/index.rst diff --git a/doc/layer.md b/doc/layer.md deleted file mode 100644 index 45f2e2bad5..0000000000 --- a/doc/layer.md +++ /dev/null @@ -1,4 +0,0 @@ -# Layer Documents - -* [Layer Source Code Document](source/gserver/layers/index.rst) -* [Layer Python API Document](ui/api/trainer_config_helpers/layers_index.rst) diff --git a/doc/ui/api/trainer_config_helpers/layers.rst b/doc/ui/api/trainer_config_helpers/layers.rst index 4a02af3969..b487b739a7 100644 --- a/doc/ui/api/trainer_config_helpers/layers.rst +++ b/doc/ui/api/trainer_config_helpers/layers.rst @@ -192,7 +192,7 @@ embedding_layer :noindex: scaling_projection ------------------ +------------------ .. automodule:: paddle.trainer_config_helpers.layers :members: scaling_projection :noindex: diff --git a/doc/user_guide.rst b/doc/user_guide.rst new file mode 100644 index 0000000000..d4deb3ca5a --- /dev/null +++ b/doc/user_guide.rst @@ -0,0 +1,13 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 1 + + demo/quick_start/index_en.md + build/index.rst + build/contribute_to_paddle.md + ui/index.md + ui/api/trainer_config_helpers/index.rst + demo/index.md + cluster/index.md diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 7cd290023a..d984e84320 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -3807,6 +3807,7 @@ def linear_comb_layer(weights, vectors, size=None, name=None, layer_attr=None): .. math:: z(i) = \sum_{j=0}^{M-1} x(j) y(i+Nj) + where :math:`0 \le i \le N-1` Or in the matrix notation: -- GitLab From 76a41f31471a4bba66110f0ca2bc8921d0d70004 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 15 Nov 2016 16:55:20 +0800 Subject: [PATCH 0028/1503] Keep libcudart.so link in binary --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d7e7e49e9a..b1fdae3b5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,6 @@ else() endif(WITH_AVX) if(WITH_DSO) - set(CUDA_LIBRARIES "") add_definitions(-DPADDLE_USE_DSO) endif(WITH_DSO) -- GitLab From 2e9ea1cece1274ccffc69d6be703fbff4a71eb9a Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 15 Nov 2016 16:56:19 +0800 Subject: [PATCH 0029/1503] Add Gpu profiler interface --- paddle/cuda/include/hl_cuda.h | 10 ++++++++++ paddle/cuda/include/stub/hl_cuda_stub.h | 4 ++++ paddle/cuda/src/hl_cuda_device.cc | 13 +++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/paddle/cuda/include/hl_cuda.h b/paddle/cuda/include/hl_cuda.h index 3196db67f6..d763658c93 100644 --- a/paddle/cuda/include/hl_cuda.h +++ b/paddle/cuda/include/hl_cuda.h @@ -335,4 +335,14 @@ extern bool hl_cuda_event_is_ready(hl_event_t event); */ extern void hl_device_synchronize(); +/** + * @brief gpu profiler start + */ +extern void hl_profiler_start(); + +/** + * @brief gpu profiler stop + */ +extern void hl_profiler_end(); + #endif // HL_CUDA_H_ diff --git a/paddle/cuda/include/stub/hl_cuda_stub.h b/paddle/cuda/include/stub/hl_cuda_stub.h index 675ac03b0e..fa7904421d 100644 --- a/paddle/cuda/include/stub/hl_cuda_stub.h +++ b/paddle/cuda/include/stub/hl_cuda_stub.h @@ -93,4 +93,8 @@ inline bool hl_cuda_event_is_ready(hl_event_t event) { return true; } inline void hl_device_synchronize() {} +inline void hl_profiler_start() {} + +inline void hl_profiler_end() {} + #endif // HL_CUDA_STUB_H_ diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 3ea2c91bd5..0f45462adc 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -17,6 +17,7 @@ limitations under the License. */ #include #include #include +#include #include #include "hl_cuda.h" #include "hl_cuda.ph" @@ -133,8 +134,9 @@ void* cudart_dso_handle = nullptr; __macro(cudaGetLastError) \ __macro(cudaFuncSetCacheConfig) \ __macro(cudaRuntimeGetVersion) \ - __macro(cudaGetErrorString) - + __macro(cudaGetErrorString) \ + __macro(cudaProfilerStart) \ + __macro(cudaProfilerStop) CUDA_ROUTINE_EACH(DYNAMIC_LOAD_CUDART_WRAP) #undef CUDA_ROUNTINE_EACH @@ -756,3 +758,10 @@ bool hl_cuda_event_is_ready(hl_event_t event) { } return true; } + +void hl_profiler_start() { + CHECK_CUDA(dynload::cudaProfilerStart()); +} +void hl_profiler_end() { + CHECK_CUDA(dynload::cudaProfilerStop()); +} -- GitLab From e8c0fb9e143b2cdf619cc921d7afbd5339a6ca14 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 15 Nov 2016 17:22:22 +0800 Subject: [PATCH 0030/1503] Add GPU Profiler unit test --- paddle/math/tests/CMakeLists.txt | 1 + paddle/math/tests/test_GpuProfiler.cpp | 123 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 paddle/math/tests/test_GpuProfiler.cpp diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index 247be983ba..33d4478b4d 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -14,3 +14,4 @@ add_simple_unittest(test_perturbation) add_simple_unittest(test_CpuGpuVector) add_simple_unittest(test_Allocator) add_simple_unittest(test_FPException) +add_simple_unittest(test_GpuProfiler) \ No newline at end of file diff --git a/paddle/math/tests/test_GpuProfiler.cpp b/paddle/math/tests/test_GpuProfiler.cpp new file mode 100644 index 0000000000..7645447789 --- /dev/null +++ b/paddle/math/tests/test_GpuProfiler.cpp @@ -0,0 +1,123 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#ifndef PADDLE_ONLY_CPU + +#include "paddle/utils/Util.h" +#include "paddle/math/Matrix.h" +#include "paddle/math/SparseMatrix.h" +#include +#include "paddle/gserver/tests/TestUtil.h" +#include "paddle/utils/Stat.h" +#include "hl_cuda.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +void MatrixCheckErr(const Matrix& matrix1, const Matrix& matrix2) { + CHECK(matrix1.getHeight() == matrix2.getHeight()); + CHECK(matrix1.getWidth() == matrix2.getWidth()); +#ifndef PADDLE_TYPE_DOUBLE + real err = 1e-3; +#else + real err = 1e-10; +#endif + + int height = matrix1.getHeight(); + int width = matrix1.getWidth(); + const real* data1 = matrix1.getData(); + const real* data2 = matrix2.getData(); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + real a = data1[i * width + j]; + real b = data2[i * width + j]; + if (fabs(a - b) > err) { + if ((fabsf(a - b) / fabsf(a)) > (err / 10.0f)) { + count++; + } + } + } + } + EXPECT_EQ(count, 0) << "There are " << count << " different element."; +} + +void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, + int channels) { + int inWidth = imgSizeH * imgSizeW * channels; + int outWidth = 2 * imgSizeH * 2 * imgSizeW * channels; + real ratioH = 0.5; + real ratioW = 0.5; + + // forward + MatrixPtr input = CpuMatrix::create(numSamples, inWidth, false, false); + MatrixPtr inputGpu = GpuMatrix::create(numSamples, inWidth, false, true); + + MatrixPtr target = CpuMatrix::create(numSamples, outWidth, false, false); + MatrixPtr targetGpu = GpuMatrix::create(numSamples, outWidth, false, true); + MatrixPtr targetCheck = CpuMatrix::create(numSamples, outWidth, false, false); + + input->randomizeUniform(); + inputGpu->copyFrom(*input); + + target->bilinearForward(*input, imgSizeH, imgSizeW, + 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + targetGpu->bilinearForward(*inputGpu, imgSizeH, imgSizeW, + 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + + // check + targetCheck->copyFrom(*targetGpu); + MatrixCheckErr(*target, *targetCheck); + + // backward + MatrixPtr inputGrad = CpuMatrix::create(numSamples, inWidth, false, false); + MatrixPtr inputGpuGrad = GpuMatrix::create(numSamples, inWidth, false, true); + + MatrixPtr targetGrad = CpuMatrix::create(numSamples, outWidth, false, false); + MatrixPtr targetGpuGrad = GpuMatrix::create(numSamples, outWidth, false, + true); + MatrixPtr targetCheckGrad = + CpuMatrix::create(numSamples, inWidth, false, false); + + inputGrad->randomizeUniform(); + targetGrad->randomizeUniform(); + inputGpuGrad->copyFrom(*inputGrad); + targetGpuGrad->copyFrom(*targetGrad); + + inputGrad->bilinearBackward(*targetGrad, 2 * imgSizeH, 2 * imgSizeW, + imgSizeH, imgSizeW, channels, ratioH, ratioW); + inputGpuGrad->bilinearBackward(*targetGpuGrad, 2 * imgSizeH, 2 * imgSizeW, + imgSizeH, imgSizeW, channels, ratioH, ratioW); + + // check + targetCheckGrad->copyFrom(*inputGpuGrad); + MatrixCheckErr(*inputGrad, *targetCheckGrad); +} + +TEST(Profiler, BilinearFwdBwd) { + hl_profiler_start(); + auto numSamples = 10; + auto channels = 16; + auto imgSize = 64; + testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); + hl_profiler_end(); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + initMain(argc, argv); + return RUN_ALL_TESTS(); +} + +#endif /* PADDLE_ONLY_CPU */ -- GitLab From 23bce472ea35b128d629e43a6f5d99356e22c547 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 15 Nov 2016 17:55:54 +0800 Subject: [PATCH 0031/1503] Add gpu profiling docs --- doc/index.md | 1 + doc/optimization/gpu_profiling.rst | 77 +++++++++++++++++++++++++++++ doc/optimization/index.rst | 7 +++ doc/optimization/nvprof.png | Bin 0 -> 487751 bytes doc/optimization/nvvp1.png | Bin 0 -> 426047 bytes 5 files changed, 85 insertions(+) create mode 100644 doc/optimization/gpu_profiling.rst create mode 100644 doc/optimization/index.rst create mode 100644 doc/optimization/nvprof.png create mode 100644 doc/optimization/nvvp1.png diff --git a/doc/index.md b/doc/index.md index a4dffb0405..9c5ebba9be 100644 --- a/doc/index.md +++ b/doc/index.md @@ -17,6 +17,7 @@ Development Guide * [Layer Documents](layer.md) * [Writing New Layers](dev/new_layer/index.rst) * [Source Code Documents](source/index.md) +* [GPU Profiling Documents](optimization/index.rst) Algorithm Tutorial ------------------ diff --git a/doc/optimization/gpu_profiling.rst b/doc/optimization/gpu_profiling.rst new file mode 100644 index 0000000000..583c2d6cae --- /dev/null +++ b/doc/optimization/gpu_profiling.rst @@ -0,0 +1,77 @@ +GPU Profiling +============= + +This tutorial will guide you step-by-step through how to conduct profiling and performance tuning using :code:`nvprof` and :code:`nvvp`. + +- What is profiling? +- Why we need profiling? +- How to do profiling? +- Profile tools +- Hands-on Tutorial + +What's profiling? +================= +In software engineering, profiling is a form of dynamic program analysis that measures the space (memory) or time +complexity of a program, the usage of particular instructions, or the frequency and duration of function calls. +Most commonly, profiling information serves to aid program optimization. + +Briefly, profiler is used to measure application performance. Program analysis tools are extremely important for +understanding program behavior. Simple profiling can tell you that how long does an operation take? For advanced +profiling, it can interpret why does an operation take a long time? + +Why we need profiling? +====================== +Since training deep neural network typically take a very long time to get over, performance is gradually becoming +the most important thing in deep learning field. The first step to improve performance is to understand what parts +are slow. No point in improving performance of a region which doesn’t take much time! + + +How to do profiling? +==================== +To achieve maximum performance, there are five steps you can take to reach your goals. + +- Profile the code +- Find the slow parts +- Work out why they’re slow +- Make them fast +- Profile the code again + +Usually, processor has two key performance limits include float point throughput and +memory throughput. For GPU, it also need more parallelism to fulfill its potential. +This is why they can be so fast. + +Profiler Tools +============== +For general GPU profiling, a bunch of tools are provided from both NVIDIA and third party. + +:code:`nvprof` is Nvidia profiler and :code:`nvvp` is (GUI based) Nvidia visual profiler. +In this tutorial, we will focus on nvprof and nvvp. + +:code:`test_GpuProfiler` from :code:`paddle/math/tests` directory will be used to evaluate +above profilers. + +.. code-block:: c++ + + TEST(Profiler, BilinearFwdBwd) { + hl_profiler_start(); + auto numSamples = 10; + auto channels = 16; + auto imgSize = 64; + testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); + hl_profiler_end(); + } + +:code:`hl_profiler_start` and :code:`hl_profiler_end` can be used to profile only regions of interest +in PaddlePaddle. They are wrapper functions of :code:`cudaProfilerStart` and :code:`cudaProfilerStop` +respectively to avoid program crashes when CPU version of PaddlePaddle invokes them. + +Hands-on Approach +================= + +.. image:: nvprof.png + :align: center + :scale: 30% + +.. image:: nvvp1.png + :align: center + :scale: 30% \ No newline at end of file diff --git a/doc/optimization/index.rst b/doc/optimization/index.rst new file mode 100644 index 0000000000..c9e87e0778 --- /dev/null +++ b/doc/optimization/index.rst @@ -0,0 +1,7 @@ +Performance Tuning +================== + +.. toctree:: + :maxdepth: 3 + + gpu_profiling.rst diff --git a/doc/optimization/nvprof.png b/doc/optimization/nvprof.png new file mode 100644 index 0000000000000000000000000000000000000000..5931a9b7dc43e6438c9c2105020f59eb3367f0d9 GIT binary patch literal 487751 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 pzq<3cmM^og8YTH*iGmRmF%P>qB{r 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�x#7UqwvD2k3f@L)%F>A|y}8KmEHD@Fa1q+_(V^({V$@ zYEz)g2d)HXh)iH6`=lxJ2OTk;?biCM*U7Sj1#Zh*S@)5BNSolg;>aI`11TPFIIspr zpA5?~=|mE|kQ(qNk(B}WwnAkqBmZ|2a?h()XoMtp{cMF-L8oirExuyRJT?~RT8GOt z0>XBOCx^9l?k#tWgOY0Z57WLxRq6J7ZZpYIlGb5<-;h1}3-Sl0K${SIV42yr+**7f zLICp&7#+Y^pbyY4)#yz7c8peeTvB3$&w`bl6R-+)&OSVsW|6^v`zR(s}sG$av~IPDXU zN;i`jq+|}2sTLv&S+et8Yssw;1O0&yNV{S6uL=mR)gje)a8W__K|>+FtiE3FpZU^A zr7-8Z1x!v?pUGK9T;J##$s`@`^@zny@U?991FfEpyF*Rukups+Y<5yurO!j1+QDA5 z^y~u9X&7>C=iqF>cr}e1HqEEdg_|StEhwKblsJ1YE;DQ7wvsSWe@_|5->zmQ<2vLX*=RBb zy0R{+IO9zOhjwS(flA#>ps$!!XM4JSmC*#JfdS3zhsew`<`2R@BKrRineO)Cpb#3Y z1!iXOh*Q&W>vbAM$T;OMc9h-P(JyVEXZ{5$UY_4nOm6MBPX)nwU}8YjeWq_s0~?Cd zs)C?ym2|PI=EYR$(`uOMH(1e5Zo56sqL#C3Q|fq1G22TIypupqMC2+D`WmDDXXEZ& z*TEc9)<)xJHf!(V6?cq#cXZT_HNtFnMu%sTVG_96TKj_@si3ZSF?Gf8cWTt-=_@W$cSw9>bXmllyffJrrTuZ)&{~JE>{9y-ZD}FccwW&0hcpGEARQ#LRRh~%PTxJL9@1)TpE@q(n zaDmQoMz5#11)~Zq4O`k;+B@P^myU*;8J_ov+ltI&%W+(L?syAy>Dur7lwlcI*p1}Z z5$=CGe1)?#&ag+d>GStwiCqJ$2}_w(b}O9%=R`pgC4GaIdcaCKm3Xg-polOa^N5%DJX^;xVU=- z?wxp+1Kz2@KFl!fo%BXQ(U)gxp!UldfdJngE$Gd=I@s{Y^G0yrHvAbwlha?&C)AqI zUr-^u>oOADxn_7y`V*!Y5~~wmL=IEL1>4To`flPvs|Wo*#|u*OC$c5j`j;?vF@2<~ zZGPcmh!59n<+2?BN`nlNX07v{PfA>v18OL5@jxp;d=dBs`c6zI^384(OmmNI`RnUm zuBQXLrjRBlhUNiY`omo8XrHEu75-IixGc)6pJ7=NDH@H&ZyB|V#dKLCZw=Yxx7V}T7gqsLa;$$2Q1B!Slny|`vf@hgW^4SRknnOn9}ng4kKT0o zQ+E$V03YJkkNk3+{0+C3Vf46NWN#!NOY65lcrhkZnZ1xiIx3Vaz~qB;;bc=~?L6(=#s_@oLbAX`5-oG&&sXj#}&a zQWo~>qimY<{wh@R+W>plIzo(s%-u`qpDQ0g&*fT+i#iw;LcLp1=lz`)u5^!PF&_e!@hi^_g9p5fR_($~>Dg*9 zxld2~?5?7F6hqpqG31E;gWP91x96HyI^#c^sI2Ck{UCaiK1?3l7|a~G7mCP*DHG>5 zZoqEtwZX_S(yi}#-_%}axh#AimVZL?=~q%4hua70@vCg2JCMVrXJS#eKIS%Q zi)7r)*}eq|8udF@z0kc9orjX3W}?Bg0EZ_4J#7S~1yhs4HUk(H%*-wU(#` z4V&r>fDWy5q=Ci{I`{)UA!eOOC~zWyH-(p(B)4A3MG!ZvFgLTTASe^fL08TB2-$zw&bBV~SPsmhfpX@I?91fg&#Oge%0+wV^2q zvZy>kCHfti1j;sgz}+LCtrobJ{5E<%>6c9h=@~Xra*hbxlKTo-FxUbF!DQBPh>P`O z*5}6iwL7N2>XQOGB^nb{vdOl1({58TC{?p%=LBCcF8%i=GM{sXt=%CzfG04L=bG%> z91_Uwecl|)eUn4!`LN%-Q6i-kFMDIfZ&u7qKV(^K2!1WF0Jbs^W}P)sTBqD2E0gu1 z?`_<8j74di|JcMs#<6FZd*X7Ms#dc>1@u9!3pHZ%J3*^VD6xdZKM|WeHXg9Lkl@k0 znImSrB=q(IQDG0831%>UA(~$yB`E0kIBrru;yEu*6;m#4ZGH4~I0G^+%C{COjUEK1 zClSA$;eCa|a|9s9|1?;fXYtxE)PzStR-^D(ztQL_iOLVaWy^6BplIviRiOIcTi~1r z>ttxcZhsi>FkzvsjaD$#hx^qlqsUT$^6PRcjo0Z8T$QYp{-fE_O@qAGzF9;EtSC$t zVS?VWbs?~+?brFj)FW=cZpHh08uFW1)r*l`0}5|{q%qN`yXC{KKU*(`xn3nu#64rS ziCS0~S&e!-NH$)jZEZNySV3CeUdkPL=}cZbH7mF*_735k=m^fb^T)H4ODV&_tg1x# z-9r#BASl)8pIPQcQS|)`veRUeT2hXEQ^tP({qsk(?A|sO+LzNWT}!xRkEk%$Pq3C4 z$T096={cuSU^4Zn#L$-^o2U2Bt@LOOzoF8v;I-t!-&-twgqQ#FE`Q85OZz*gBWIQH zDf%WOJB}fR79TGJNcw6nk9J!M&3!7CIX^wFOQmkC-;845kN(be<)sdkO~7%zjlF}N zCrkOJ_-B7IhW<2TRicPUKQm9?*`ol{T%~8j_xg(UTyyx~_$MyXWeMnVz(_S;E2S-@ zX{uf9mq>45&Ykt{`dWkNc!lu!bM7b~V~~!<7J4+nbKcQD&a^XJi>1j)iitx%_463} z6VgTHIM>P|%HAg%%k!wX zx5z)AXqO>`+nM_075Wll8W{Il6h?XOKTGwxU>$+*?y1pj%zVX4AEUF&m0dkS0f*0w@ z+o(_uu+prn3Bds;u<1{N(_a}TQsHvSL)O*&nWi0Ihh$Ts6ogZGJ{H=n;2YIQ5uAwp zO67m|qyO`an|;*u@itp)t}*ZoD`z>*_;`2jX!zCX|~SKAn3SfndVc>Q}FDX&A&P7(LP$QjTI@%5=e8 z`e@vI7&DUK_42J`w;o}x;|fPEk$p{7tddBQ^^V9`_OOF{zUl3 z+f`Zr+f)&m?#|SsJd(l7?Nhqr8Te`o@d!2EcxNu^Pm0OJbXY{i$DsHROF$~ zxQQ~Hs?+>$d;x_GbL~_!+aw#Mji{R_*7Ft_XyvQT^fwazABLh7zouA^T$?>3k-ano zKTK>il4C2*thrn8=BK|PTV@4GQ?Aqq;WzI{`bIQt6X%{#ZML?zi}-#DH9OT8e+IL< ztnv5Focz+3AtJ^ZD51EQ=~|*wFWe&#J=n`uhE5iLQD^MtzQFi3A}x^VaG8jQZ%|hhH^&;`y5c++K+dao4BI%?<2ZE9&03}x>Vq{Y_kaMRx$2{_K z_y6SrV?Ag8-@@(v*1q{4xIJ9}x3};wZf~>MNApz%x$uj(+!Q<(zveb!&*=aLKc}xK z|0!ik??5%g|HZR@na59ZlOy)6wiffQXmLJxci1fW2EqR$j=}t{@ZIXwQU5Oc2Kmwd zLfv~tHQBdapCDCw?+~gey%#9~lqMoY=_M#2Akswwgf6`VP(VdMr8kk@L+=981*9aQ zNHqZr!33^3uY1i}^UOT+u9-D!-Vg5w7JLM9mjAJjy?=Yr*yvP8Scz`;hZP9WF|_0B zU?JbTWUKW2UYJo)TVJWQWm~tu$lXzP0cxXw+Q^FpUd6;~BJ~FV<5oIa@DZX)3>XuSEoN)D)RwIR@v%9q97g zmz{`hOGma~;Vq`YUBQYGK!N&)1P8%-q+dzlQ<1R0>4-OlY+gN0ep{9^B>{*b=$wSZ zH^Mmg8=o;z=Gb;Fkb(uo2Brt>zIgGjeLStHUEEkd6Rgiv#*pg5+Xv$h`u|OyAajdEaOFAr}hq1#gCC z9S-U&wWhY|*t(eTzUYCJOY17O)>9*zqFZrYqlhTz#8cyQ+_BZ z$$8QmcY5KZiY+W>FWrPMuR^f9!`sgg|cz-Fbi%p(Y^pSG}g-YK{V=-qiOCs zc*V2c{}{ZNW{KcB@7Sj^N~;Ar4$T$K1U>fV;^|}>5zCEx&B*=cR2++9Pr)lg~piz3Q28G94y|uyq12>ddJA6f5 z#%@jRKKHdOSLXW3ICx7W`xeiwN5!8c#s|om`^W`Q@0=U4BaYaOq^p|;VXcGRx0E0( z8v6WeH33aVwRFZj4b_U-{Et*Nha5EC90_;5FO7cGzy31*3x|rlP@g};_lWCz>m6p~ zP*o>L*k}Hk}$-AB1m8jqu&P(ep&p*mV<53-~0NuQ>5+7r6E7 z4cYz2=(Op`8L7GY;Ygn;)u59Wa}qWy1649?(1Y1OT2Hp@_EcUw|70@Cpk6WT{oSmq zw06gI{zZOy{eq8sWBYyI6gSVcpwaQKHpRw3-K*Q0_Bm6Zg2V`1 zHKpC%R1-$4&qUaNd?MXZj5SmKlQ)tIXM&F44C01C0!j`MydxavSvTI=gN64Nw#{{u zGG`ixy1JaSb+&a$BrU!(j(6mGinm6|pj49oB4ZxHKh@QP-J=r!N~i2^G^q~S;_wf9 zQ`}n(R6YH#&3Z*6LAVmeTROV5z>Az4+lRdZ_x!c#n=sSK zvWFn6`dQy&sbV^N=w=qmyxZ8n)Ij6ZHs`O$|17*2MBD|e;#oIy<1!v5(-C%1j@nMd zX@*F<1_a|kxf*>pV;+O{Y@fJ^J+L!E$1)fuI7Lo4%bLNbF#^MXxxbLGrt8iWCoR7n zEOX_O>Ww)$BqtJmM<$K?VooX8aYZS>wb+yJo+AfEHG@#}y4})>BzV8unoIA{6@@=b z`^}=d@*(fSsP|#qB0{QU;a{Sm4x0tU6$-+cn3^EB^9H9=B30`#lv&l}-i%Fy;<+y} zISeG+U(xTYN>o0T93y#nZxa@zA0Y|s`%cYf5I3Nzy$D`xL0+=b29J{P>Z4}3{KxDd zvg35oOqR6r7;$HYSxG8qypVPx76*>;u(d16zM365y3)1!hvc5oMpRB$QQo&<)zgg6 z8P-& zN}hF|csM}N#G4>nhv-cP1z7OO?&#}H)pkus2D4n&w|q)--o(C~l;uv(qvDHIFcPlR zjlm!P!gd-8=eUb98BmK`I^NV!CB(Kzt@N0EKVw|B z{@vF>y5@}xuT{K1O)#g1JMYq|hVN9)kzsRF2sQ&f4fHuZaW9HEh>!7c<2h4ZfQjCs} zUJ!Xdu7IX%WLXK8Ts!4iF(>rwe?h?nI;i!MY0u&|zO%gS-KU>pUR6J}G9NX(lhbSg zKNCdk=t~R*bhh_`HHjvdA2b~{&C*oBR87fC5X2(Ob*UTabi$rHQmVH(0tEWuojWe< zAs*lTJsArs>JYwQS4IlYzXf%#x*lTl3}=aUw+(nrRgkfhg1oLQTHkz5SyYbG?PADf zm3JW&QSMrXX!fNvsn1w3#B0@8zS#>CI&+5d(!!0D>+z35-7*-0rfzPz!1DAX^3jmx z8{-4W^=d3H(hpt(i}3i^2rRq|y{Y3rXUM8SlN=0Rlfpc&eZf4C6yQw#M6} z%-+HA^8NVaM+N1*^({YRXT=8q*zJauEpldm?nP8m38AXJ4aT&ISI+%%~Bm*4x zoar>gEn8==zX?^w@iIQ!E$Kq=_!Npv#VNu)8Dwu@uyWbirIJe1kc#fuAsus3LBx-^;N$7Lb4X1xkPR@oPUtN-W@N(-=F zY0ebr4kFxwW^8QUBL?3-`c$uy9Et46%^N|BrH7|mxO~2$fRnTLyMZ^Ot4dUjhG+jK3`Uhh+56F!UiV8^5rby5?tr8A+Nf7)d%_ z|9goT^O7wH+{%R+(~^&gzaj~@Qi%qf82@u?Q=pNp1gFUF>moCCL~sqh@rZ-x1IwzT zA&!@AgKpenDNu*Y-I$x4`Nz_f`J{ALWq|JyTfLlHu$Z0Q16}e!YAt$(ur*SGI(EWr zMq6Q(Xn?sIe-^4ZSSZ!jIeSAt!?vqJB-A1*!Pqt3-9IWxv0!89J*Mhm_P=X>9S&(}nY!ao z_4V<`zP|jfDL3akJKOuKD#k<$fznb)F$S+VOon4D8c5XZxmvvaBftY^7dg{7{jhU< zwTEk0Z733o4;kd?iC11_WckGja-VM+%SNp`RRJrIW9t>pfyaiL3Cz}xxBMiHQox~vawWX6aSDQc?@2SbDV?Sdb}C6d_E8u zHQ3XiEC17xLE9@O)Esa`KNk$~gD5IvyNiZE{I*10zm(jSObDCuV4eY{k2dnJmBi7u z0#`eozlzICk{H`US!`4iF&w3_!GUkV?DMpI9-o^UPjak!1<94GBC#SNA?YoRAo+bo zNd!vx)bKMz&XRq(z1N0b(#yHQ9!~WNw;W~FHHMxL9q~R97&wZu5|~XbyHc*u_fJAh zX&Q<{zn2Vt>|g8W{&;&H2W#oA%*eQ9?|2HiDfk(hKb6{$cO+ItN2?V2;p#B!OqfIOOcW>=(Q&Bc1TW2N2{ryUh}d3hs4^kj(Z4Jj#nXh~+2_4DJ9i7P0^)-B=?5zeN0J^fnKCaKEFZ?hRzF-xU__JtzMpvVzc9=<#7( zp|Zsc7IL*M6(`?F9m_uEg`zs$M5xZIC>KTWnUW#o0^UQKT3Ex8NH$Dv>!uIcBb5$N zQZy&->b~qQiOS`p!`nUJFVrYAOc=?F80+OQ?vg;cg9rjB|zDbvHau#v&TEDw{!uo((V?2PhiJvKl2V-tVEeU zB@FVc^>xfE-`Z+#%Za8mc7JmG13;>Y1L(R0NttAgum6gYnIe&++0Gif}hMW&eu^rywIZIt6Lq|x)j;S)mrDSK$C#&5rLg# zE4IUQD@t}9q~X1zND*Qvz7Fg6plLQz@Std=rA6W0C$mNe-4?x0<8RCcW>j3FDfXlf z`+$cJcTJaal{c7S$v#oP&~vjV-{f>rC0k4?RX*;+&CIM$c>WUm`EGwcJjte~L3|L6 zeY<8{Qu_fYxi_owRcs7ZcS?x7cfG6U=07A+LSZ!cAf(L^J@(<|WWm(CO`m%j;k0A0 z3!k0V6gRJz@f*2Ln@8qyT#U~b;3+^eJz&Yd$Z6{|Tr#*O|o zqRJ#Kd#@AWgDm2|n&3%1-1|fnT+(z8N9KOe53MPUe{&=~xw{^md&ftjA(6&lZidN& zjD6+m$9vU&Yk<6OC!p(s-rpfoDNBrP>mpk_1 zNDtGC0@^#+$VwamM6c&_h9|y#a`HqXxm8}6^TO34PYQZ4R$OF4iW5K7LQbj15ee|uf!MGM4Wdte_MLUJooM0 zM2q68c+3wQC#s>1q?bJm<#QHC)_SciSlP=MIQF11^Zw+@r?{tTW{!|YnC;J4q zEd5MK|Ed#|p^C#ikY2O&zmesiAS+JKcD_uN^EcCPyBHH%MNN+`fp}*F}8^7HYA%kRF`s;4`{`SO&C8C&jrHDeg#~8zJEr0 zj-;_IX3WnDrTkb}s}59v*w!S@M0dx&>|WWl_!D%%#T&}qTFdP3jIA)O@PGhJ5$;LB zDg#0^L(pdXg2l^~3~t4x-8}p(idkC-XuYjO+{C}`yJE{5`fK5qXx)?{?ji*JK0JKa zn9jprM5?AVa!g;3;oo|hFn7vCLj8ANl z{Dhf462hkTN6Ig1hL(881V=G7`BZ4EIy5+5INtb0?5<7&TnrC^7HKDZB67F4E*(PF z<%EUwt)@MYNM2hu*%m?B!HHYDeD#X^i%CK>kjSLzULvW z4JC4SSaL%}GvlqyZ)ZQCh%AMM_vZC9Fb=$2Xo_NV#Mhxll3i|=EiO7#N2rL*D-PFO zs~k26l8mMrErA1?Kcn~Gp7`MZ(;3eE|9XZ~{l7WGl~9#Nj_|b9mqgC9ln;t^G%p1c zG!`C*^BOAUDf+aFE%b_n6*C!aq4~WJY1O;#R7K7^^WlAvP6B9SjNyQhjNcdj&u%F@ zC%E;yr>!3-#mY6z)}Nj_WX+vO4i&L#2BV`m>)>66)yCi?rv9-|meQuAMLe@YC@q(@ zBON=*mPc*4Zx7)qED{S!5XCefck;TgX3`1$dvDhP48xLKU!9-H4f6Zz0KN%{?X zUkw3c%|sO3Pg_fZ#6;~kBqPub$&idE|yBm45qH+peq3Eh&lSuF{tFNmD$ zUph6>WPIXlJ!Kph`d~SATZTug>qLIUo{)#i$G4zy9CPmYu^oV6ZX~-&xQ+nry$by% zq7YgHeus-7UY@i`6Rl2E2mDs5(W?DDj1G9S{=X$uhD|aqEp5#yy4+bSh66_|e2uIV zpyxL9*gI&mL3o-fW*vWS6F%QOo#mTS?=aB$Wi*pw?3e9!Qm;74LsLz zY{oKB|1yxQ!^dMKhd0=j=drTzVA&(ndul9e?k7(YnT4KOt{O-hmHD_8)kEBRXO8Q63px4rTb?gMZOK4_5RBbAjex`p$l#tWwj@p1m6I=(di9V^<5F7&55Im~%R zR|IAQSA|bSO_M@>{n)h-pQ;U`Jm>;7cvZVD-YPxYmO}!qlT-m|OOh3lt5HqvXB=6E zsGBwGOXOqpIbXfr(mmC|HhehoXOju{m)}}v1wgSHecH+k+CtC}TY`3Htx|fpd@QFO zagV%#GClJn9U3h0MdL|^`*+6nVvJdD(TWJND4po~8G7vidEA5SG?uYPni#r0%}^JW zui$;`qVmLITjZvT?oN-K_nvH0RCC_&@8mFY1m{xoo7UN58}~sKe^G(3E&c|zi6e9l z9a{#qFv6$u^?hK`R!LTip{(@yg#SGdw)pwVsjR5d?&tkpkLLmRzl;d<@?7#=HU@(; zn21j?+4yS_rb>IYo3|l9=nC(*4jU?Xc#KcC`rdJSKJsMY!Utl8m&Ntte{Vt<`j8Q_ z_8I%bQ}XPsD@W);BN2;ut3WcQ?zrP#5(Y{M)L+s=5XOS^2>`K#4KqOsc)(3ZgpGsk z<<37Ntu2xh0CemYKD8G_RiiEE><#oQgnVB%#o3}WjXwW*RPht45fao}SDaDkHj^wW*hne4Lx=td3%B+g0H)319KAqc)QqWKn zv6UW(Ngp!5cBtMLIOvAvR=OogibHgwfrIlf_`0STrr~_#Jv{NOmgeU6`^Dk$fZeq> z6zfmD4S)76uWk#O$ZH}4DhUz4v`ObZzCXQPx{U2~=<_Svt7MjQbTHPSd>JK6dd^4U z#*c6gg5+53MXCYJE?$&&dl~-qa9Cf@eA6qVqZ>>E*OM)XL;V9hw}jfq$me#yGrquk z0qCifIix`KW6XJfhjY1a)>^KU4BB*q?aLwA_KMcJgKM503E2+a?WOfoyz78A=(6?9 zVUlJ=Qlr~rpfw}}JIH=#YWDWUx8mYas&7$AX@ADRWcHCidz!3T3ZIuO*7~{$#sr9^ z33yK5IjQ<2(qc~7>2kP_U&WdkVS%_4n-o!39;@Hqp>;UMc4#p?`$^0&?IsC@`Nx|i zH`#89QvKu^+8!)w*hsT^xlxb5a!;zGFkBUhEJo~TF2YA3>_i=m;ZZ7p2THW(D-`be zIwKgKdh>H=T1#Ts#8nNaqZ?B%pNayPyIF7G|6laA;sVlhNFp$BtKzEWH0^?A2PURT z@Wl^ejdyy|8dk*y+%r&`Z3X9a9bNZ^j0NMe!M6_fe)C@a0Q1Abkq2j{)#~1Q8JGQ=;3cuylRYZ>d{8Kf55P2z2yn#$9a9{C z&8@R3Jd6Z9kvC^aNX?Niw_;-3)I`ZHnw>8&ow&F7K1`zNZO&K0(JwwUN*BzV1km~J ziWRS&-gsb4(`PK2X*~G|qQi@U4Z%ID`@Q5N=89t=+(muuzPE)h-f^vl{B*~C>`%yh7bbW z192#zuZnpUUp*Z5uC1p*m%mXj^;&PrP;Z(6C%OC-$|M{k`Qr=N2fiZVb7R#6K4{U4 z+rHjzVx)PY5=fS7@UT!Zp0|T9R@dvJ_A%Zo)UBwcXy{1(t1|7R%8%6#M;fjPt5EuR zzJ@|A#g#4)jtT=#Zvv%hOLi=tt>*Ja$eQK$W4_hI_z6IrT-Enit@3{`g~?41EP*(* zg?<>Y1z%~Ore)4= zNx~P;r?-#X!3V7x3M%t@j~IfE>}`@jLp=>PgGDSun5zNgWzKfV#?zExW|A)bv`BnK zxFYxe5>J?JcXJS}PJjXgmX+xUx`_ z!NvC>xS?*~|B$=reMi03`tssM1X6UH<^!yoD?`!Qs}5hb3uq_x4*)zwu;y5>AG>kr z?P~#q)!xIDuuZb2Fgl>e1q{&0>rzy6Rj)MuP2cX$`3%)|hRO{zFd5S(S&(l1QP)1J z#+rCLWgJuNyim>x^Ke0j9mwN~h7@xgDTStg?Q(*CkCQp^EweJ~J)#XFP` zbum|^Pttv=oO)&Mnu)mGC{*hc2+W%T+W0k$-+e4N+NO8KDG^sW z$0V91^);0$t;T3S-&Kc`gw7=`0KyGZyR6n$Cd$sR4nztpTBA)~J>d->wMM!KWHmls zh~RuV^Mht=5jau#rE3df3$xIdhJzZ-1!U7auv8ykmSx{ZC7)bRa4Oqs6A4=Cqm!y~ z+T&QqFOejXvrM42Le+xAg*qSIE4^-$!T0I$@S{2m!t|a_0=ge{^W7>4k?9oAee7@y zw=|4o++JH6HcM9Rpi0)1531pu({u_8=C_m;CdH+e1!s=68MbS?J#40_;~JKLrvoi= z1QY|n3x7TIzH@Cq?2){~$~0fVs-}GZ5Sn&N3RW#IgusPkZtjxJMXC{%@NWTR6U?mG z(X(~wZ*E{5WsWR^S-?c|#_a;+%y%2)l1^#*Zr`6t zo1jN?Q5xz(J?a=g#h^ecuuKpGyfKNfzhC@A0tZ$MiDh05Uh?a2X1caGS6jNcJzFo* zxVhh1Fy)Gc?$eq*%H)iCeq=q-yUvT@JUsX~%Cj_}BEPntLvmfl?!M;;Ki3t`0Ce`9 z2Y;=b3{?)71wa*dmxv)@G%9s3PKMJL)oivB6f^dCs;<~~|FDGoyQ}ygOMw52G66yV zEdH03V)xm`a`>ipx@+0$zqC;A$F z-GgBr^7p)5x)oNM@rk)`9H)FTI9l#anU?!wKP>&5oGw*1MO1VTIZPKj0R-T~O|9ch znk8k*!{OO08`|Y@BAZD|?PG+h!LX5|d~DD_6f-fX4C>dHOprj<1y#w_7;lU6?tF##Xb1nZO{XDu<=XBqqv)IgziUB?tY5##xmt$D%yz|XCiX^g5??OKob}L6`nY?S zi3YB(Icd))sB^`f8e(GPT70&FB589#wNr?#)LA$s)begK z$9^hfUTle}oPYoQxU4yZcQq#D&THMq)x7{WB&b8UlYu;e8 z?Yc0`btf~pZoDel;6DmGf8M}9B-fyED33Iy4KlowNQBv=mqxF?h@_DlWG%lvqe(ID z6-AS|;(_`ydG*dW5ikwJ{J7N>W+B?#)UaBO`31dQE3FB{y6C#D|+qD?H?{0qDmP z@@XAXD|~336W_{E>y9LZdbH=#ibByRXfL_LRz5e`2hA6|km4Sec_R5`MwdDU37#R| z!O}olHZm;rUmfM@L@dCPY+r~3Gq;+9)hd7nRPA}p#vv9%WsSkTtJ*K(tx+OOOn5$h zzx)yK3me6}SM2Z)e08Kwg8lxYLR%?v0>O+oR*f~mV|{C3kg&|A+2y3%^>1%BLav%y zr5^VQ9!-U1c#lMRz!Q$JHmLK&E^(M|x{9xM{uNf^YLxs? z>+z;vViXAGU9-Gep4wDky|d$55UGvBvEu*I zvD4$)T_yPW*#Q?eIx5vm`7C0KcHG^LNk*1V8Szhax)Y=WB``vj-5 zThmSOr96IW%y-lOf;8v)3RGlWYF13Hv-T(F_xd-&^fyp#!f%|}VU*I-A z9X;Vk_3LlM?vGc262RC$&O(e|3m3Rn{5r082-arwpt9G9!|H1)2eh ze_uZ2R0wWM4Ofu-4x3-{$*f?HbWA<5fg>Ug{KLy_>t-IPqn>&CGE#I z^4zp(doVp#?>T*2w2@=hl;od>&Y0ha1FA=c}rm>hL0|Wc(H~)nBkx4Xh<}CxWtfFLrRji z867B@S{HFy!I+khZcW+2Za1mCuX+7x+4o5F8Yv5Hl=P2VkGCntB-+)-E63s2D#ByV ziViObn$y_YKeZm#i^Z6Y1Wi?*iHHaZKh4-}n;_vd;YP*A$CL~V-m_h<1Q{r!Io_k1 zXaQ7G!SVNMV7EoR3E@ewcUHnX29|nER*^0>Z-d9IHOYQ~>d93@v?=g89%!~$*@W|# zWm@-k?A~*WWDfT0a$AYQ#Sno;t0#aH-sZDshwOk4C+->kwGTmZ-v^bN|8_YzIhZ3` za`f=q1JFe3Xkq|!Xdz_$Q*u!IyO;cIR?!zhNXGx*;#rBxZTLwnWXKySd)-iEh)mS^ z`RN;<_(Vt8W>r;nCli#6d=|e(P}y@P!92*rLXb5LLRl%&j4v_eC z=lw>LOhJiPQG;K?i;bAsvTDpR?@;*ao|r3iRw=#pTY44=y&^yHmi3QM*Jj3RsImZK zrzNJKXkY@!W(#yZMbOnR_O}#0PpQ)=iB{0q6_@H!8Pc;dk{S;z?nOwz)G)j8njs!Y zpd(!@afIXj2jJ$^sxV#m8EBC1Bo7gA5p;uh7BzUNZ-U<5o z*!v-UK?%#7gxLj}i)?ZhgU$~*Wa?Aj+;w&bg3V&q$$*+CLm~*PI&e9S^y8Xp&25=$ zG;)Emi&@s*aI9nzkvXxI(>x;y_!Yf<<1$N|70+lk2dFsdQEY;k^W;BjgVkO%#fCJk zAoVA9dHy2bwC%I0xre<`j6AWH9V(@vGCm0m>?4(IH;HCD}^Mw5sBv7-w{#iORk< z1yQOI&x;*Eq*GMN$R5frRSXb_{dy(3$8yJ& zp`5DVMm+uZYszhJYZ|C+xadB$(VkMD`=EnmpHgkRv;TD`7IJ`qtoFHO zp`@qCDDU>WSm7;u#YTqH$(eYEj*(+X}-^g{j1z{Q6$^3=CLo6 z#_((uHB|~e1o<7yNEE-~(PmEeSmLy7@@9)_Tar|G8yBeUwCRyG5_b6l9}m1SmRlcQ zeuT+a!%Ua*&G5-AKNR;`=8VPS)^6~SN|lke?hJo-mOl8}kiMakX-|4_2kVW@XvM^h zyw8jNv*+4YI9M$@E$%)7Mz z`7KMzM?RBTIFWAS2I+WE`g~FSMnA0|J2ZiS8cFdH?Z7O>^tChH;Wd?BJM-Q#WVsvN zc*qr4T>adUX%b!myAz64M1>rdV>PU?oQZ#KDj)ed8vC3rwG>U8xaE%C&AMwY|0rEu zqD@p$AjE3;wj*Qe6=~#TBg}qI8O#T*vrp|`rj?RkyOgIe`Plb}b=@&_FZxH_2^<~Lvtr_-hZHUg}~ zD7<3nG%|12H+$85Zab^QU`_b04#P3ott{?)aumitJ3CbQNyhQI1V$hQ2TnNq%>jKq zm|MtIzlgGXfgg-|-d(JiZ4(du>Eb`6*k~?ZExub zi;VV^nETiYI4SMJi?O<5uq%I%{Q_XHf>W<}5T7)ywg;EO^dxHY?}1x4QkssQt&DS-R7RtRn2L4YXQ^kd&EVusx+_I76PjG3u>u?abJ z$!d9I9i>FhmkY)3+mcRio8B+}PehUSeMJ0Db(Bx(-i*?DNOp$QS81XAYDnL9y6x0=+IFt@R)nkeJ}>W*|f0Of8U<6}>+=xSn zKvPc5up?S@L0>NhZ`b7rT!`={J%Rp&63Sa=Z?_)4#t?=I85f8q7*-Tp zq(m2nXezysHd=A~k>kRa{ic!iayv~s*H1Z9nC>V264Tl}3_jLN`m9|>*r8pRJ4<>Z zMpAIVJ$5UDQ7b*Ng~&dGVEk3YNQ7=QENEpA=??Wb&TXO=~ zW``>_aK=Xr1UHS_EpTPuEy6`Lx=x0&KAl!@&Q~n>xmy~VkY4NUe_1pz78M}yPI>F0 zv8Px7+5HldBaWAOWa)!X5x^uH8}|>%SiskRNWSuyUuCqFy1U3Du3rsn-^AA=t7W+l zv;~_mQ|VQ{4maEgR)fbi6N9=a^Xt~bkPz}i|IO-_?ZoQ>F}WHYUWDgJ3R*xKdHVNt0@#aInYlTo3nm%98@Vu&pGb~tnjohWIv___ zPuN5H5|yxmHz8T2qZYm^uLms^?$GBi=LH@g+#-ic4p53WGSvI&np5c8$Q)clMm^Ca(_1XUxV5vrm zakufVn^80{*;y%IvJk?Gz3rNB=B+U4%y}f;>tgH?$fn4y??_@iTJnWzJDnY;0>{G5 z@q2_jFxe%Taj~7<{>u=Y^^$^pnMlXb;-8m>KXt7HT^a+gbCHuSLH|;7;(&s}Xaj`Z z(iuF@bM>3`WrxK$RbTK~$l(AcLiB;=Uo*3WAv3jt;$Viy!3ONJP?<%WM3TMUk!peP)E^ES_8>JS;z zza>NZ_>|_a8EQJA%L3S{4h-8A@jwPvhP#nx-nF+0dLBU>y|sx7f)az>y86ihg9Sux8v^KM2^touhgVF9H8vVIsx`U9hl} z=u1@FwA<(fxdtM+ovh7km|I50OcN6x7IJ>m){Oj?TSQBgFDE|42cy)w>^2E|*~%Zt zL*nx_Xcc@j9!ZrN_ancShJRmtPr3u7qeLp8b(_Ca5OVre13uo!&}_nce0-Z{bqsY&fPXuxseDkDh%od3qlkBz35t$ zN6)K{-w9{lya15py>xAN?c}^JV%k^wV|W7Ty>=N`eRMBt>~b&F_!Z58c_i?w2q>+E zpb`>YTl?Q@W>2$sFjJgWsxe)R2|cm8+b&@nYg3eyGBfs_adKbsy!=5oKDibT#N_qy zSwWlO(}}7RQrG%vD+P9L>S$Zv%}V-NNCtnyP?_dJbj5HEY_MqZV+~P$2|yqy>_I(6 z>B*{o&HgN_&tr0h`xbW$LPlqA4LlcrLpQ*-CI?o(3B*E^0Ki>T2H0amAgUh|#o!%Q z7ULz2%MblDdWI#SUcvr*5#qbWsk>6?-7M^`qKacZjlahF7e{HKUhC zNS{wJyh*-Ql5i~eXbej>d>fL~rHqm$yVv$TxYH!xwl-IV#r~pVjJ2l5R?)!7^$oYk z_VTcJUK^ii6nU4zM#Ik@KAw|Pf{(J$Q$c#rGOS0*FuPOrxNj;1y@GIk0sT>uVwLzr z&ifB|^Y@@3-ip9HjZQV8jksg}b>Rkw#hMI07iG7ooEbA^F8?_8`mlnc@qERR`isUW9IN@!X4I|KTRY$8X9kk`kCvH#XPvlIC)R<>^DGRRf*Yz zEQ3e1)s~vxD!-a(E-RD_UAQ;qpJgBMfu$GC$}YH>8OpbmbO1ESrA2w!b!lPpM>>29 z$a%hWme+*jT4)A2lr(TTJu*5W%M$EEa|s?#)4pL$13sdtR8Z5xbgy5kJ*2OP#PfD= z>#G;~O)0padUwWfnF6}Jl;+gw^Rc>OC$=pCkCZ`)0^AtT(H|sj6ocK6SNTD1(Eg1D zChE^QwZ3ToBuX|`0V2bz4=yebx5!QkQ!e3{o+I7}6yr*`C7wbczKoI5ETFwM(G(=hsP0K*$KvKg|f!#29pMZm_6Sqxhfw9IIaDev)Z7%FOV+B4zAUkxpF{ zosrOWyT<_hTq_V*#IWFX(OUP`*ZB?YVjbRHO>?EIPSD-&GZd8xXYuZj^6*Osb82!2 zfd(M)9Zm_A!6?b?7a9A@Wl>5W^+F`S{8+FFV(pK%%~ZT&A9HfV{x_ZQ9H2eV5t$!j z4E9Z&Y=#0O3J+}Cn&IOK%ddAI-JM{XG7_>&X5K8c<-z@?fq}7xfOHvG$Udh{rIweO$zsywUq#2|+QU(?W`RMaqv;7)5?Ldmp(~r!#T3P3r`3;!y98fY1 zE4QJTHR+6s7hbP29jk{#u4}tA@Rtd-`j00{*hN2tyw6dv{$FbQ2>NH~zibs3rNJnh z!}L-jFC594pe^JKeKL+Eo%fYGBQ%D-oO=7#MABaFVw&y|;~R2EajMO_v~_+w=%ZPw zY@Z+7I<)ZCqHCD%CX`O9RP$r!W}-&S^G!b27Vp7|s=zkTXVo5#D-siei0lCViw|S^ za6X%X?BU+49#6Z4)k~4F3{}zTh-yOqZ0_>Z$VsrT=>d}*mhZetVR8C8qi^n+gautLJ^*r#7 zo4&N2FAcXR*(`23{>M@I2~$v>=tndwwS#tCoUO5Vzs<(&h$&8&><3Z(F{W@D z?NeIzsaCUx@$iq<&foCR|Bbu%3~KUi_jW;=(yMfW6jABD6N)quQL5C4Ql*154GBeh z2LT0X(xmr}^bUg3dr2tL6KXIa|Mz~LcV^F;J!|iG?RVCmJ+nXL3&SKK;l8iyI?vyE z9M-#aA2Xf<*bv7PzmH(vAr%9yr6y=#(irAFf2C5O!bU**v?R$>TVA+ zeQJo)=H@JGJTs}RoIUEbFjv1~81_saELu&>BjTGqGE<*Be@M^iVbC(Y^~qhcB^n{5 ztm-N8pfjE54}4K_u!~xW1pUO?2l1(CyDkkEQEl^rxE@ui@?uS;c^homP(} z?S4EtI&sh%4$NlpC0O4&IqY(DdgWrrFI=GA|17F+*L1%?Iq0D8K~azp*-~!Z^I`{o zKY0fSdA`({)RA%vht#qIGsx@T6~$dG+|6ko>`$=TMva7Wocrv{?5i?6dcs<3dX`ZM zT=LHgN_mya3A7ny$d&{USAAW?_~E`ykB|#UBc{Foip!yqCWNj|G?HYN#t(qE;hpdX-PGJbc_plCXN!kQRRXz>Y z{Mrxt>#)9QqErf9=e0ePY<5?1TO1UhmoHub6@#hYtuK;ofTEcZ*}46eiS%oGdt$Zq zF%ouELavdIqS`+fFp-RMPZ+ocUWQ=RFd!q03%Xa74tRM77no9e=B_~ zDC4kM`|wKiEzg|>%~rfuT%P^OSF^AHLA9G~*uA%0LS5+j_~VX;)m8_xvTWA9{X2_C zoXrI-;;7|lISJl()yYoz<21i%;@9tXayWtO58=0Pf=I>csLaZr(`2 zRospHZmc@laCeN75R!fV!sy>u31BW~eEy$4AOZLx75ST-PFBnP9C1uy`Ayg$sabQa zgoV{A|BupmAHLTt*&6M4P5GPMtf=y{{Cj2{C=}b1ESAVVi4uQHAKHE~N5TU`gfF7&<6jW48Pe_7 zAA!yR1p1Z^KWqUgO25OMbUS%F2t{~i3a8@eg(6N@N)5>}cEVSJ>qy*&BFq2YwvF3}fDd({w;k8i?8P zrDw^ACJF8DkBj;}_*@tS-OL`Cy_OI+{K~mN;5hZ}gc&e%)I0W`u>Q%ph4ukR`kmrU zF1FkKhAh)|$%B?4o{wiQnxtlo~_ZNDY|IwdL>X|W$dY%B8&@j`DMRtQ|0kN6IO-@5sNG_A~wGwX`#@jD* z;(XA8`Cj3JMIw(og1o#;0-=>TpE|{%PcgCpkOwcih}=}E7L6%tZPkliRZA+=F*V7K z;x`s|ATKLzc@);#Obw$99X;LFTUO&MqrRglB)K;r@xe07kYwC|&OK|U3a%i(8hpb) zkF&-q<7Z&BC}0evniU+1v)o+vZSJI@6ZDSs`26C_6V3s>dz4Sy?*TBX@h^lywm?g8>Wo$VwL#$4ix5ub?gD z_$g1IpZkQY8yRcQV345ni|kNtp>a=wt&|VXce-zM<}O4R*%6TbO3BNx86{tlXdhR% zYSD)1S6)&XGH)a9_113ilt+a{4vltieVwF&&BCdrJz@cvV}}buaOn5>4~SOc)2YYl zrz3o8s;^|DO!bQvW2qJhXzvVH|4cm0Ho`-J!IUmHJkzt6MIqxx_u}WvYI;B+B@^A) zAvMe_YHDnAvb*xu?#soXjiUgY{ogk^8#kFN2D~RLyvqWk-3W=HW$ExTE7$~>xl;zG zKGzY57^`I0pS>S)Zu#e4lGahPYlLxt`6p-rd$0d)5!66a#1s6#A@=ze^UMtB89h=x zg0W*-Fl0k(AaqG8;CAxJYjX-A7*1{x%F7`XOrm-76BdQ3$FnjZGWuP>30FW5e0%Dp z^pBsH>8Q4!LQOFtj8x(CA5FYig4Vmc&mbAnaIB#5KLok*T_?-%aKj|7YpfyE<~Lpn zXV!u?hweB8=Ms2bs}!kwo#bv`VU_L zqmQ!c`AH<43nqknFfH=VBl7Q=vy6Xg(6^TZKRT9+mKgjT;)kzQS%}H8AoZL2iyo}9 z%SdIwNa6s|`z`>l@aIl$ePEJfS~FHgCa?>>14OQNDNUOthixMSQZy$_j8nwtxs6j_ zLM%(ZC^sbM9S^JGev<10dG4Ocm~{%MKg!4$pv5(tr>d<7TxI$d^9seB>h31>3zF~{ zZXGXGBp5TfvSlEu`whZ!slkr(H@Pr|0|Ug^51VZfUrvp&hvaGRKBY~ve;$k}%L@JO z_OqS6llWV%94G)k_I~Ja8nuR0$7CUC*w>u#A`Q&WD%qFv+f61JRgAh<=euI%!gW;;yL0f#v##KxEGb zG~YP=;Me(bw)QZ=$Yp?yTS$HAGU1@7SLW!ufYpNJCv_>ed5P&}==8FHk;ox51w!C3 z1er^?2c`h}w^8#sEU4m7c@$K#&)QAC=8v-G=xIAHEy4`Ox7v?g=@9Eg9d1|^C^hD) zbiWN0>+aFPs}g#NUbkAZVr4tk&G(BP(_up0qG!qDxw-N2bD)&l*nHU2^N22f<5<1AnjLp)dSPFc8Cr zMmE2E7veJg70=~a3&&`5gKPcLRC19NxzYX^ znbw~ZcHFp*WoU%Z8;8C+FS7k<+DeCqekK^~7P+f!=F(g3O;%Vs%X%+p6=iO6C+C~k z6t=Lda!$Pc)_qaEB#LCkY@#Jj1}-m8oQpN+fqIXK(m+#G3dIqhyT8M0LT*R*XA-b4 zm?5;s26I%GgBx!TjJ~x2&TMJvtSjWTH?6_baXS>g-asD7B7g7tC=-YJmhejXV8cBe zCD?D|V&ORW!|$MP@Nuwnv+3yJUHzXsk;{zbacPx$uw0A%J``0MZ zP`o@^r5u=EJ3<)N9#OTL#C(5D(v;!&dRcq8br}3vy8Qbei=Cw8v2y46ySg%dQm77F z6rrLXApm4~|EuVga^gmDEK0-T$E*lm5>s{7NA-5@^kq=Wnr{E0-WPw@2v@h>H*ymO zR$domM{0<~MIR*5dH_}jy}H#YP(N)rH#n%s{wdTkdUZxo&$VT>Bh(*oMD|WioIGz$ znCJ!E+@FqgkcP)YzaDL+o|lTAuWCPO!HSf+2!F+D1p4r5VJ-9f2s}TqF*ekI?C@h( zUG<5f9`8J&ZNTWFom52S{e3@6l7<`?ANG6kcbqD>CNvoO@LVmBhSr-LoZ5%)0YC{o zF|JyAo$eqr%fQ!BxbT-6BYk;&om7qHl@&KZt(>yr{8*b=EekpY|#B}VmYJaqu0JCKDxOb_x&28IImAvU4 zqN8?T9}S}3#ZVx+NHxn+D}e1@s!4P6slfGNrqhaE*Vuxb(P=u>ktLW3_Xev7b-MXh zGzW})S(d|Z>lSDd`(Gv*uvebDorb0W`%6-JMe(mbF&Lg0`R@ORqLrA3^8Y@$5mf(< z?(j!}E;BH1HWR6FhGz32%k1Q(+^6l#Kc}| z{=kft;D2A?m6CUpE4T5Z33J*!#}DTC(luX>$JCZZ*z;ysMaYxGAABz}Bs!PXxG9EG zYRer+JkaRZvCycia0u?y=}AK-0QrrL_kjMc4Oiq#ic(Q zR*9&TMBJIRpQqbwFlo%OuWb;jlX_+wKPIVAJ>K(-fE{)Q8dX^O1(ijJGC?>c``Aq8 zY6ED8ZRX;iG{BdwG=Fwh! zD4)eGp6}I0nM8KcGitxD?$>}2w<{8VH{gSLeG}#d;!B4~$(ToHU`a#D?{8UN?6-!a zUb^)RgW?128GW2zsWji@;RP_DArN^d4aTMSZ^p8(D221uvSI8))|A(Xuj`Yb@vCnc#*XFkj5mlH&#(3ZHk#_N&wwg|P7u>x4}B7$1-0zMQ$$vD{_ z>F$m4`qnb^ zJ?42P{18iYU zC1upv5MBfzcK~%DU*)%R4=D|#(>`5^R|Jzju#kGiPR!xk!`0wZqcS-UlZA?PpbUnx zd6vyZ{gL`Ft@a*Xx`bSQ*2rH?zFO$bPiB`{UODZ-ejKi!?AtCGgpR2RI_dhC{WSgqqWZwDxEnU`zyiJU<(4W|$ zU088bdq5bXA8S4Z<*ZoSySlTZ?L(UD#V3-xX3j*OJoMa4TQ&*w7(Wa2=C}gJ^iUps z;BXJ=-AEjR>o5MT{ZU6n-uPQ5TR5 zi?I1o8kEuJ^OmiD&v$G5I^mE0R<1Bz;dOI0ySLr$CTUoiTFTy%wY z0+*=HO)}0G)%Sd+qng1*p~6fiJ?H7qoyV09dVysT{N2$Q2eO2(ak#O~AlN}~C;t@m z#p|*w1?3gyWWk`R%Tu8knp(l1wl8#Rz%P`nOJO4r4ya=R&J|4-Ux>n8*7h#tBq~wu zJe%wDE>=$o=oL>Kr_Li;T9n@P2Q^fO0&))9KM=Qphw z?qk`phuuW?bXF6*fcuZ8nH{YQYqhy|vMf;G2{FP}F|v;M(^tSpaKLZB}XT0F7k~TuEB;ecr&Ie(Izj_o|*=+$P;!l%@7{W!aMdG@%%c#j9lkXp% z9eAqbV|q4E7SI3ujf_!ZLQ4P@mCi>Ut@ItTrVM?%tPn|^sS$F@xYYT~tJ}c0B6yjI zG24XSfdWG!IppZbup|6Ws>9?juUeu_Xluc~&1Lr z_Rp{X*mFo+xI&8dVJltw*2$GfHvXKqH%&oT-mv?b%p&yUAMEwMeK11@ z5eKxtlfZ6}<6ypRE!Dqdd2Qdn(u2RBUOgpIKH){Ss%K7pN za%!B$8MtR;IwZQt6bUzkc|WtYd#nvXSf2_qDT$Hja3>pC;YV$Fn=$lVoX^^wT=l=% z1SO88{#-b{eR+ayWu1KLcU3iszjIUe64>c*7B_T!(iSNxv|e5g!+BREjT?VBj8EKr zB0g(HQ?3+*gfE+px|#KNvY|{;0<@&kgwIFrK0JmE6H@RcYfFsV+4qQ-2qv}5NnfK; zMwiiOP|^}sI_*VQTw74#P~Yzr-QsheHjw%b(H;; z87o7((BZ{v9fQ!W2AWVP3qa)dLRI%M)48bdwA9qkTu|8@>%}@9yYuN3rBm=HX~EEs z7Nbx^L^0(D-?;=4G>i3!yG7-gNgI?vT}~?U&VL04E#XmX#Ix^ie4ezU?q^+Mq|J{V^kw#SkY~Xzp1tckLsZ-8-LJ`! zWN29ZSJ!`yloS4eko!&&V?2ibYPF*obc1~QEKnYwJ}}E59pWL8?l@tEiYHfg=iYe`9j6n3EHoiwj9A_!go>_`aEppAO( zf5hN$)kam6pE>yV?{8siyO%{0Qtot&pZ&_omecq``#JObYCtK*X0imiJ=$rGdu09iv{ZD5FOgy6Qor3Nlu-J- z`iKTW!?lJV2a`)fB4uS`zx@CfJ&Bi6u{>j(9bWw@ED7p&h-1_g{lh^Y+@9YQ2`L#> z!X`kZK$(b6LwP8nTI47%USM*0wJ0RRdG+kX;naV3XFYyh+J#%C0yAHUpUs3sOQOuw zryG@T2|mcq-2Qf!xz&Ch6S5nZR7QKgt3)4AJpm5EbEC~-;I5U-5cH4gg~o#)>RW57 zwoa6Wl@&412iGN%<6fN|JjFmt@w58q5{($=4xPVWIwOy~AX(J{hl6UnfqSCE_ZD*s zh~hhrs?IL!YS7X{M+QK8j%>3HaI$7vF*9U~F}zDDbmxprJ3Brlr#Ycj<)G8>pP?xM zYz*_4GpMFg1eHN=4%>7!PSjaxaJC>`piQRi*^sypb^%%Q^zZlTU>GTW!nBK3=^+Bl zexM>6*}LQ)Y5&5DZ*J}V;RCzCJ7=%S8U1!uW>3JiIYn5t6{sDpOR9?z20qe)pTp0{ zQjya}j`rko>+c+PgT$>@>l5a+todPgVKWr@Zdig@kB-WAzc)J*s~v6=e#oW=>(Av% zyEbw!ouQeJK@1gzGrsJBBJWzLqNxHIb&9?sHV&UpkeZEOKRbD@5`YoJ%IxPdX-7*d zf3R*sMTBpv8{#kblzyhf_kY(%SJ6Hc89#`Na})sO{|!1CSRd?KJUun-&#+-^bCyA| zG-lSaaMn)r z$Cj6BAk9E^C>JRT^ijA2vXLA8-aj6hjuzSmWWujHK;LV zTg!god_%~T6&KR}bvv_*yL<9hmu}}R3;?wAIBZ2nYR|kA#9Csv{vptAPM$nwBbdo_iwMz-qMn^jtuK?1adwF zmu{F?N3_Hor-m8olUrcPdLaeF7gK>R#>9jgG(rVTL=QITaty}d^#+3=PMl0+zlB5E z=A90oO?`D~g~reUzT`m`Dci+#`4>c0CSj%21f1n3U`ov2(MjN$LlB=~pkeZEe-6|94K@;ILRUscGv^;IYEiO(XgT&Peu9 zZ&Md07PyW|r&1TK&l(S^soLx4lJ%eLSPnYG4TS;>>HP;jAKwsB(G6E$)U>hWKlDrrUw zH0Cz|ZNhmn0n82)=-I3^f46XMsTk;fvK2A=yI)7=<;IjC3R|1eWC`ns z7Csymv8=0* ztGxjvyPUgw)%X_+;T3J{vyrwXl zHv6GjyhKsQWJmrNt(6ot7O*Ae{Urjq2|gT9Y~qCKa;R31o$^f$l5v%Z1-Zn{0GUm7 z%p0r`^aWZV4EInp_Waujb)_@Mvu<^CetcY13d_WXmbv+pW8!!BYgOKCo>}cH1A3yPT)o2b6Isz9Wxhv ziQXfLJJC-c-Rsyc{F z&_UDzvriBJ*3<-{mZlOw7|l0Tpf5Ro_$dC_fD z#w9e@KFGM$^_u>sU_^4eiLm}A_#XoQopp78Q=-$dn9J%A5J{s!!kSns3YJ=`Q3P-! z`1jJ^IU3fEaM-Kbcs~usGmYU)Fdsgk%7^}T1xvHHoK#6g##=ar7{et13!p47A^M`? zsHppf4YP|j=!e(shQQbZKpQx}wxA!YpWEA7o#;IprPPHN`AAfpUr&hNJ4pkuW%h1TGJ|$wg}y0Nys-Cg*I@h|8ZbuUsNe7Yh>;3x34gXW@-A8F;-37>Xl|1`^buTv;6QyHQh~`R1gpY zZCDSjRMIbA+4HBD9(h@nAHY%3RR68U{MOgI8(f&~oz%H`qHt7o4=c_9eGCaHFR$!K zhO94Soe}ziZOvbHrFDRENB6qoF61gjiD46fj5<+8`T#RqA zh{73=XzlA;Q$Sz!!I#PrqOnROo2?zjz`?1K^X9Ag@S*G4+0Luq<)8XKUN_Zn4od2! z`4TY?g6kat{$B6WW%H(JoP|NOFDEH;_8~oSgs!Os(d`ARk(RR_6J8~UPL=BEKfne{ z;4%v1h=jBJ$suUNe5l5_PwAz%82(;lElAL%d`r6UlIruPK%G$*+Q8~@Hlu&ti&Pmb zC+6nqtnw&-OSDi*+%d}0E`sGiMQ)0~E*{Y^v0v^DiR7B#h-#6PhnbuTPKQe2p7pXz zt645QVUwR>w(%7(qwCa?JplcC)09!7KF|DrnuqHgz-S;LOI@4rsCSTvrMnrWL-r&) z)M;KN4)@LN#|wvlM;P;^i|~=FTy8wt=-%ye(+Bmk2@F*N%p$tJTYAI{HUsXrzS#`l zNpdp^9&a}fCfa$eGs1_}+jxo~*Mb zS=b?=K)#$eKtHE0H>!gW^}Y{DveWMD?-)e&Y27|So;XI-3i;& zz0XZ33k=jfUELQWw#hKU2MA81ell(kg;O!|ie z&v)uoRR0M_x7Z&q31pYiQfC&%8Ucg~!5}3FLhq*pgc=a)HYLi0+fx3@3Mio9GvNI$ z$5GUYOl)U~AWuV2OF6TyYXTTTKXaPGVuw@s)-(* zH6}G>@uS=ZnqcWJPT1%{HSW2dE{q%Vc0yT8H$VP$fNYC|dXMe_4JFy|l*l#*c{L#Q zi0u8ldw5p4@BjB*TQ!d7@4!ykoOnr=x`ME-VJIce}($Kfbc>unm#Jt&^=cb6|H0VMQ~PY#$`*@5Gn8@krtKGwcWN)DuQu zC1}y)W?hLaYaOe)G7P+djjt03OM!uXL!SoJ6malLwUaRXS$cZ+#LEXRiUTg%=ep_( zry<=Q7jchOnSoL=ax%J86yTrtV#|$})Bz65Le9z3LrY8d1eWBljiA2)_?zOJwJwSC^;Gh|F6F6Fb%Fstm;c%=a0PvkUe4Ww1 zj!M$>V)W8@=N`vz5{l6DSp6)1HF7O;`b@t1cct~aTC8^fbm@rsXwy7?ZTyY@m;2+$ z?ByVMq?>a0wS1Q?grX{^Z4c9Gdy~jH_HxBKC~`Xi^@RJS_Fq$$FL#X64dph%k~cFT zb@Q>)fEbCawll&dm>Bu|ofAUFLHyz8K?*T$XgeVo%wBtDHr4jugK^YB1f$lO@MCZ) zDPh5WngXc9l&_md-{rjIND2G9P+hKB)-^tlMtRZGml$)+ph{PaIz{-eNbI8pnfOAfb#Nu$Gyj#bA`m?EAi|G0mqL~5 zFkjFHgO@4EZ7)B6Je^3nH9YXNCgg|Kwu#U{AU8Q?r%M;^dc$mmhV&(d=g6V@ z>G!!yXZ)O@T?^JeR#sQ$P#}2YZ9$}sRor@;<%ik*a742RdY~7@<-HuCP9$X<+|1u%F`98Cp@a!GDo;mtL zG;t}iJD%uy%%z*CU4}x~;OiL0Hyqwlm0UYYfiWR!@~`CyQdCekpW%+Vcc3zu{L+B1 zUI?8~hpkMSi%jIEoEn@Mc$k zVgl?wDiJ4OkrQ6FVzKO8C;Ej}^K>zHY-toe@9t;k_Wk2`j@_k(YFcnC+^#xWg+F%l zCbADJIHueAlPQjeFd&xY2K|D~-rKMI%~uY?7ph)B%9AT#8Ua4^K$YvDRFY+C13iQ! zM>rJyh16MRYFD&bUxC?*kdWH}8QtqOQZRP3U+(>k(&hb_DK!z5QN{ z=*c)GA}ky9;VhK7!s^ew&reDTJ-2)DnUYj}ZhXG11jrc^gS&t%-bVZ;cyk_S^8IJo zzT|$eu9xTb)~k$)vOCJb1T=5o!w3SkBmW_IQ>j98+X;3b(}-p^0EDu<%YVs`+e zph?8(Sx;lIZL8`%30W}#CPXePAgbuvm`?zPxxd%FePcqt~2xZXJ^Cq;bknS2iH!))w*qyNnA zjJy3q;73($Y(GeDEHO)tgJ>pbx-j6X2LkHwDu$|H05163^f zX;PjM6B4IoyD4h-yHsFu0W|uUH)u$I4$1Fho8zG;Efka~v=F=GiV&+RLc7(5BvPFT zvL}Xmo?vaT^GOJhH8iQlcWio}mKJHV`)PLe=6>e82A02$|90z^Rc2&Jx(8t9ye_=p?iPe}E@i@P3cpRrk zETKabaVEBGypiUqs+1EX2)TUF@JodgDhi;vPS_0Y=a!@=>g#=cI>zCgDE^H_UYM^o zq~|)Nv^s#ItC@q%pg%DZ#*F#7nPk^IeL-==e+K{V(x2z}Dy5oR%1 zQhAd}I`c;Vh5BfQa5N8HGNWpKI;~}>+M9{nL>niIGW!(ZC4n9WtCq(u&2bDjd#{oi zVM5)8ABssqN4)2UKM$Bg*Ni!_VBaMRFfi#5o49ys-9WwZ zkxLKs^cbOfB@z8s7L*9F_w)jhN_d`6Fm4s5=?3ho<@3g=h6M}6F#s%MnLklV5i@>q ztV>|F1~6SSh2xJiHa(be#?w+BEN^R~d8ag6(qd)&%G#~8#_tdlZO0|~MGQa>P`Q1X zCn0#*04@ldv%iAU3zsIRpMmb^2k)}gwt-FvQlQ!5ujFMoiaGJ5%XN&^wE8b1#qFrFS=HwIbra!Ye z;+S7A`dwD*5tq(c?b01KiLo5dCtHqWWUt#tpdrR2P=uZwcTr7G6cOMtPAueP(9>Tcjbo-**J#2rR>pt z8Rp;Dm$Kg@#HC&xy&BeYdj}5m4&dI+Y|0Lv{IBTsAm3cxH%Cgl=zmaePZ@e%lWZRv zkmWMC?|mD*t4za`VLjgmkA(4IN|Ed!BFqaOH{iMpG?Kjb`UXr^eW`iIq^5Ms;FbQf z3L5XZE;oRxZiPC};mU<1tmuO>_-7j%o?9y8IF!L95^2%G>}9U+;^9&! zx9z#y{dK(mP%w^VCTHVawa86wCy@(;OUcNq$YSk&MxpbI=nINnYy4AJ9Zlomm1DI* z9RgB56k*9vPwB?4rGv`|G*>))^Tu%?*QsV^BYg=0u30F?I2b0hrx5f#(i!GX80_?p zaCY0)i}iOZiVou~eKb1ieVaE)?T^b*Vicq0k0j9=#R+WMcO+ zFd1ntr(%&bpu?WJ3y{>}H%=Co2e}@gIupy+F{#?W8WZWe=M}V)Kge5_x0v`J6WGO7 zv6FL@C0RNT6U7pZLk+M`p+4x0@IsVhw34}(f8o??6L=$)($Ws<2oCu5S6-1p^d(2}gPp$XH*KYWYAMHGud7H=4S#6sLeP8<6fq zuO-tt9~Cv-S{VUR7dXrHd2VuPC1O6Em&XJVT!;078cGHYhyzkB(5B&4B%Npu#O{kT z;k$N&|a0d+vE#Y?Hu!TU}1;lgvaUlCf6?2K7| zH{}8FCcG#27ADacEfS437;5Sy?m}G*H=a7i^Pd%(Ci5>18neWwlCd7-$W3->VA*g& zvp9k3fc9RX%Dg#S(E;`lx{~OzOXPrAx!vbXN5+Nk&1D)AAtwJDy?XP%A$L>=p^|9X zU<8m=(?bY%g$ubm=B%-cMY-FFF;RBcZ(U%^**(>U_Y83!H=ipyL6}Elh~~a(h5^J0 z>AFrWP|vXYSo@$b?R8aqk14mHpzV_j9@j%MLR9l*I_3uot{+6x%hm=|&n2ZoC-(Vr z`22f>vLd!74Vgkybh&d}vVoz+e+Z)SO6Y`aWEcH`hgZ4NY2h3(kC164do;u7N0FWg zf)q93kd`LFkpq{$_mY^zehWxs!56o6@x+Y@~LkeD$>h9^9)CPM*2(kK;5bUjY<*F`j0l!HWIvrl+yAawmA3%9Y^ z?5`YjySg6e0NdpT`?J+y?!Y9}yb#y1kCXctwLnT7f%e4lXJLbrQu5061JFe;)O`;c zs3(XN&y9(g?(-6qPc_RRowYq>3)sFv_KR~9dRo~gD8v*Ol363*%X*g~XsuY0NRigN zxs3aU=B|}fcUE&-d=|_O!xku@ zhlYDQ$ESfe2M@)oSpFp26c`BbO=MW?pNhI45B;JZ=V?UA%x6D6zP!SQVK$qpJ{PvQ z(wKdH>hW%Gt2sw0Vo^xw#XNVCXLnp!3_mRIeFdliG;k2)*q6f{30a13Dr(hC^I?Tc zvC8!V=|;}6F57Y|n^~Pc7GN5%vjH#?MJX^-h^nfIzFX_df3zZLsln&HqI*@i?f+2j zPq=ntfF_gDIE@RY9IFmB#*9(@NQ}R*mC1dJczMJf{VK$cpz5?t0>C7{{0c}cq!FK9 zX#8uwocljthpTllFM!YmoK?x1IZTPGhGHmsH&Ddx6ex$FSQmoFwO&#_s@*M?P$kPU zbJ+kz!Nf4O2okhG$Vo(Y8J_|9xNU?t{Zn^B2kqz2#J@UnIHp20aIl*+vki|>sV)|p zy8{TK?+^;+7xJb&a(6{H5lhDxwc;lGgo5PZ4; z|3i>`08r^MAJoy1xP~s8fGn%TzW3ILarOK3-`|C(OWsnm1Bnv`+Q#iJbv2=>5v))h9 zXnY;V=U}iZ*ko;&I!4Z6m+&MGIV%GEV}l}cL~7?7PC|3(%=%#uP*b(7HJSV-Uy{6Q zb!6fgx_!?W{Fpxb(xio)M)pp|0ZUFN<-Y9uj9_1qdY6mGtqD4BGUd~iK2dV&vg^^4 zCk3GgTyUm0`6>@_4hc)sgG<$4Y@L>o4b5sKEvYPP!(BnOziJh|{gC%NOwy8LLAf={jSm0Ffw_U)eL5@>V65jk_D&No&Ev6AjKEXZ07xj5F zG{vVOCGV7AWHK(GZ_q{neHA6rVT7bo*EcZk;2x3sOuX^xQCV%VR> zkz7=$%WyCBE`lCoWR2z~T^f~}O{eOU%hVda_9kjwWNRMlKn?ZcAHXIvb9-}R6%6_k z=?-Y`XI1LCZMjh9Sn(_cKGv6tn*4a0pjQedkZdPrWAmG#CK`YFtR@ImO&Pny(>&3HS*8~=( za7-598?eXJegjbjl$br9vU_CT*ci^O8^bxaLrWn>Ow`b{66}>k1-;N@l=b|VK$8s* z4CQ%HxTJIO{c@)&E+~GQarZRb^r4WWchelnv)g~v$XcpP-Mic&OZRu51}V9i{u)L) zwi(zEwT z%J5CIAxJcYl<$Yitr^29PMtyb*Xg&5bt%nn!+eEWR$zOqCpm)ofQsoE?hU4~2>vRd z+Wbyot!c{C6n}G!sYa6B@>vTRnTxnpuV8h{$3cYw_aK4 zT-c1xa=K@H*FkB6FhQQ%$9n#V87rrWqMz?G^TP`m&}$;3TkQjmG~2kF>1a(pPDN7f zcn0jPD=E8@#p4u#q0Gc^wyjQH6lu{-#`j4gi;m8xk~_@ye%AH8)j}~oap?ItYxkeBJQ5SmG}RXiX7g+Yhe)jQ+McO@ za~H=s)&**cVvA@lES>cmdfq4f|g`2-KOxQ+M7jFN>A`SZD`hvv9QA&y^o0w zbJlgb*#jt2LV@DI;^`VzQ*aab2vodixi@DcdU|y9MyCK$^HOF;l}XEDB{f(fHS-M2 z40XVKK~~@0>r_Omv|K4%Ce`v6&3I|JqoVAbjhsU0yg8#sSa)*-5A{Va@1txoGX=ss z`Jj&zAEPIu5YEEy7QX44uzTFrePtCv?D&8sv@}HmCWy&LAt4w8WRPBWCm*@{`6Ww= z(zVay4Jq$?a+{yR_Nmpj*w}(puY@%&mv0J0^BQ8nbeayAE%$X?E5n|4zmXt||FoML zobR(7l6W7L`>ze{+8uONDVn0MZD{dg)w$4RF^;xva#x2xNy;^xz>)RZ#A}&p>)iNE zOeF;qi8Y71V>0seHiJHx>jjBy`&nr?-csAGQd{PY>s*t6-lMY-D!8-sAiw?J+4vvD zZ_Souik!>$Qa+0c*(U_pvGDT)$3e<1pjtOSi^8w0HyB&^yliT85h{8Tn+~XK93rGwPq-TnhFx9VC<$91ULq;`6*6^-s&tOJ13W+CN zX)n4|RT$9%(b6Z(2N^IvwDNgddI=0~H_srm>k~(l2Uy?47y+f22Uy6RbubxONw4NxU7&4=_L~H&uSusW>2MEcDhfA$_^& zu0S~REA;~*%0EKM06|0QGG`-zYCO+$FD)773k^tMwankxlBnU6NHPiG4!y&4yiN-w zxWMtb3`(*BA8~5snxt9YGAsOr6hu}JhQ@d)n}APSRrxmWt!5=wtT5n*M4CmzSTG+T z@#h}oPYynmcgT@{UzVv*L)nS2dRkEm$^;PnC9TH_Ko!vjA1`h`YzXv|pdIoz!~HsH zQ@GhwzX*)_F3$)C+6nS~d?mLvts8Q>4xI5~*Hkcyl_OOch8Hs4E8GbI*^iT+1#jKq zb*HBGtLYPYc6(%EejqWii=k60O9$su8to>e>*V$-PQ$&9F6^Erb}}w_T$doJ<$rMZ zo2m zGjfi34EAEVtxLG0Gv!Fhw1wk|kn};+JERt;NQt@KLOR^tHxg{T&~Ok}*F<|qpc`D{nn3*;;M#?d4E zMNseB1W)Jp34)LXT+~&G50uu<{ZH}xdVLi5dT6?^Qc2DI5BEsOy`AcR1*F#3bJNRd zaR`|AM;>M44z9|qOua-`yd5kbmwo>>v>%h|Lvkxag%vbf?&#dW^y%5!wG`uzK195?BV&~=2 zhMitfJaF~_Arqw638irz+hV(v)~`;6Fb2qZ{ZMKzp?^1r+TCg3>yj9n6Pu|H1Dpg} zp^+s_1&>^PpmQ(DZOK5n&lqX2nX5h#hod!>!PLuJ%2&-$Rs8wLDj&z!SMM5L%DA{U zTF!3@hL!ur>-o`93P-avY72b#cf7Nxd*j~^|D^xjBDPRomSw=*dYJ*r`$x3ksZf2m(T)1v z;O0puZI$yIrnt?kaz*OSa2Uf#^*d7PBb?=^fhniIxWTpG~Y)#Tz{oiXYn~3_=kyQOFFPHJttB!nw zC=CNW@oR+IIBDPy^x9@qhoO8ae)7||_gkSg`943@$Z5)?4SP^K4SJW@qD()s@3N9J z0Ei=D35J-Wc00U<=-F6FCWy&b?#0~N{?+M2<=u7G6M#+$c8$2V(-|4G&A$%Z%AttU z&=1A3ZaCl8i^V)WsRQFKSMMJx!Yf5sF`VufC>Mme1>aeVPnhlNN}sA+nkycmv){e- z;;cYaeTR7D`o|)m`R+nONqfooc6>2^%J3ihPrUQ*Uw-gL^+k!Dvb@+!o9B@!%X&f@ zN7h#|3S(9eL2Yva>&Q`?(as(MldhzdG)tHXxlgKIB!#Z!ckvp#@4MM{H;qBr2l}|Q zVW|=5hR=Gjf3bXej;YeQo4B#^J2;!z!OG8HywX%FBKSSbI$!5G$Y&1|0xO<-Vzo*Z!FczG|aO^w#mD zqDY-S9jvF7q#31n$8e7xCVs&^m`kMV!TJ>WVwryZS{P|Ok`Zm8U8O1D_RyN%Rl~)c z7Rx`h`bAwAYKyDGU+v6MEsSXLVP7`?>|jhRO}F*3&F1ksH&e&-q8|;XeR~vnhlF)> z6OediM(nWRodfIHFtge1Gd45=_DgM50pk{_MzGKjrvcULPNWW4>|c_d7sGH;f;1*? z6drF=Tvua*FGl8o+wx`p(NS{J(ZBAhWYw*1HKCx^Kmt(mfOzJ?aKy8Sa3rxdsP6FH zjka{5WT!=WiJaSVs;|tn$Ms#N8r41Xqdi@0qJyI^o?q(BT|0e$^G2U4%lI&NW+w+a z`Gc;>;CTrG7mM@F-gd~mDUPXmSmPc)_Qv;Dp_kwCHa zUe$TI+|FmYRBYpRm1yg5N|p<~J0xON^#jl!qNzWGz6OJ|oAd(&Gp3V&?ITngGqnb1 z)9Fva{Q}RzvmI|Q>dTY6m}9BB8@vMXc^2^eLyPXbg{+?&@AK^rpu}$KD%Ieos*V!lcZM~^^Z4d$Vu|tMlyf>8a)>sFe!`u#{Q4}!@nf9UrrQ@9=rI5 z!xZwEC$%W|a#DI3?#MmO9;{_~WB?2~O8@`x0{vb3KkQ#NPLegRVGe-|1<<)6KpulA z3G$0JzwZa5^Z(rAh*o?k-#PP`O3!h|j7G{)hPd(ioFKYQ3uOvD-v8>|*#7Qj&#j8i zx6-B2<{V-*)I7U|mi_QCcr1S#Y{-Osu!M!+1&(gFC$shKtaI_kcbZBX)LVIlMII0B ziA>#^W+^Fzdzq$bvfw{7y7O`1LmL1lK~oIcq@g0UKKi*|@@y^MDfv@bHtE|pdSMhXV-bHz!U66G1-wO$ z08ZZjjNkD1bpIJ~{+%%{uE-v~q+axXP%Idr)hc2pq9yxoe++Fk{)C+`m(DIVxnnz1 zV>Trs>$I0+VVC{e{C9mmIoc#B8(QPN>rwv`h-7!rKat+^&*Id_l*bMab;?f{d2$LG{YXFI=MtqV{90DkhV2A2_V^p85R+k-F)O(A^Ua7fSFGrX`}*XkA}*Fr!0D*M*+HEtAfc#k=g-2ZOf-xH z$`QJ(2k3IwAg^{o{B0klPSncMwSI}@KJeb;$Xpl54dWIOzuJ!Lli9s;G{Ppt5_Fq3 zH$7JdSbJOZ>B3&TINfQ_5jl01a<1$TJJ~`D7rZ&!!=OIATCJ|RVrXc8?C4U<@=~YR> z>oZSBCK2N2EGea9Yo;+7JK3(&Dh8gy+%49+>)r9+b<~JLq>)X0^0wo`B z)+IbvqIOPj(|u*q=j)QX()^nAVjFuCVgR_m-{Qw_#@Krsp_?)TKlyN3%27;#rBT6! zg{gs#K@eke3^Ps@b1(u-m-Mlq(c92Nd>YP|QT&$)t zK1N`XAYbbRf`xJ^cdh&IObWRs*}v-|QxdPv72{|uz_s8ulskb%Cp6xRxtZ8%X zq%7@r^;T1uIL}W@pbq$&fd z7xvyx$w-|Ht3K8Th1jyakh)P_;w*)f;K-XDO6CmSX~FmxZyxT^C8-P66CUN-8|~E- zWFqS2P1G54-0N?4&8YhyiLfT_Svd<=EiY9KMv!0LB(Qa3Gm=sGN|bec_Ra$3!_HOZ zmP{@~#YWMmIR&YJ@J1sqosRYlhA>7|0uI}h*YK3zm2OLe=_@m#h&f*p1aH)LU+;yO zNec4GrrrTT$o;D~>VZ2NVn}ESmg{VUk7emCsdLrORnOM9D);24+>b78Wu`6)^CIS= zHI>>i2S;!ZY{(_o!I(|j`N+jfZq>X@$q?DCmO_%~4Ov=0{o}+1yVS3Wm2dc+q0@i( zkH)G(b2apl@x6id1Z{7Xx%R5&IbYg&XHko|+%?i;jts>dQn$yXV%b-@XvJ=*7{S+< z8=;qfvL?fYRXTps6;|P)!#4HJ_V{daXukw=U~=^aN{Y{f-d-cd zEsakG)TDGL%bMAhUH^`ocn2ZBDpQxLgnoufW2-YrmleRgQeOfXxbAnz8;PXM(M_3^ zmfta-nkPG8DXORHJQDWx)mR-Z(GEm8^U|*%D>|VUuyf+%0B@$3zSdo6~EjWiH z^qG@=3gO6r+8wDzNW<6;^OHcVkcU5@H$#^Whlsb*50&I9UgEa2bOj3-GrKfldu4o7 zx4oS-vT?Pma1ZVb%BMdZxe2$chdlU7vk-@D;`N-lDlEgZS$EBlr{v?6;TBNzEs~{Hu-CO(`gP9%3{bb~V&Y z@tL)AWuXD)Vu%x|T@SZJnr~HKp1dhqqBM`aORf1^Kyw)1^p`|b22)2I209>W2>i2c z>lG=5^?t6UKcO#WX1)D=vRX#u?;EC0Mxp~^ivYL_3T^_?MS;Jqj>2gmDwu#1l;sD$ z_sHJ7?sz2d!KSGfC<7{x>*DkHQC`}VOytD-?mTQlr3KHRLQ;GcG?>=?mVFw~6*gOr?^awI))&wkC79Ynw^5WQ zxc!(4L$7L^G3S6xAT{@WdMOM$A%!cN-pK_`a20Dyia~9%gNat zrZ=0=Cb6H-$Ify2ZmN-02F9h5N2PuVk7~GNgP1O%iHf0deStN)ULm{T9+^IS!#@4F z&eDV5kq_R#BtL&Ao^C+>Gh;0TX9d*OK*_3k$?Dd%-=@57*5lZ&QN+#!@vhtxw503w z)XysSpZd)S&f}*G+D^!ar`0Q*B)_hlPeV?PmWB7~Zi<-heM;P&$=j1@;}P7!{n9)G z?7y(*O>58*3v3C%s8k*Wv2WXGRF$yN7zl|lG{n)SbV;91_>!TKl`pv+%A8TG7aLfy{QJFb$FHb!6(Br z1^0AY=|N02bv|%v^xEcy+yPGQ+$JlUEOXcYJW;hXGx}^}_#8k;S3RROeE)m0z3GRU|`t>JQ#BLr%lo!uAbszPMeIsT2J$^e6mzNisiBX_w^qaK@m=4)imXc+kJRZ|m;564d?VLwKCl(ipd-__zjuG<|3AV{N~Mc!%JH z_s6#GI>vukS&C3YmLx=eGD)7f9r>4}zUUFTD%CKh86Ab0A4d+$R^)5tqsmg~bP7s_ z#uJf|=<9~XBBq=}uLn!Ox?&ldqqP0}_u}71k6vdRnIG_-k?^|!ZVDKWn< zbHbiHMOt%TCq*!Z@hfw(4*uM3+fHZ%d%eYf4foZ^i{BHS%JccT@@4&kPNIxCi{<=t9wM`P_^_ zTQ4h9W#yTkdOsw!5MkJ8#%~Yy_zs%#;Sa`_AWyK0s|ls>-YfujNb_xc+`8hq+{Yx0 z(%W%#k*P|#7wK;}TCfTFwHczc8U#*pU|sUPzup{)xfHL~P zyQX4jiL!0&phYs$OkqE3AW>!|z&DY9#fW`DkP{n|NnC;Po)86RDw@5*3(!)?SNYc7 ztYo*i>6&P2QFkukQBnJCF`q&2;4hbVmGBo0Q@d)R4Mn>NM-t@kg_Hz?N2h05zQ0*N zrQ-MFaax`J`r8q0hz-}rFJm2c$257P4ch9A-Rr%JA9WaJu?5TJ5P4_OZG6GIcU8!L zm?Fwi2ZLZ9g7D|xpm-~#R-=n`Ie@UD~|B@_uiC?%N zl>qgE9oEZ!d}i>1Qb+Vl`eRbcB(?BYoK1ZIv?&8H5M}Q)QRDS4 zQMv@iP`fR0HShO(r zv$)aS>GH}^OPvnqg7<{phP?Q!4)zBXRNENhE_bvfa9)pj>d3zsT)KcyR@)&~_yqpZ zrgeR-JIw_T78TJlh8?_58rl)p9;z}du5fXKQDd_LjcsNSa#UxH}3WNFGd-lsq4_EkZHfOUVUd>whtGN`b>c7G_mG z_P^n81yZqSg@w2hCW1M@QeIN%>FtPLxQ}TsOs$^n%4cwW=r)8l>}St|T|z-blc(I8=WRrk9j^>lrpX;7@GLiF9# z7{6y^a?zFP_h6t&E`K~TmU%ZJLyS@#J8s`f!>~c%Bj2+EqdUv_w*RMTeR<4FH+3^ zl{hXXgX_YcX?R&m-jO>t=Aoxny!h%^1#3M>fx7~j z8P%J6Mna-G}vnl5@wqgeb-c%K(QrM z0|L;+KW(1U!kyk=Bc|)VetLf4^?5g#c<7ZBA!*nI9Xz=7FxV2PhY<{4q3o56BUYL1 z2QdnPC&Er37Ll|raL`paDB`MAQ<|VwvICFOyhafD3Tyj~zIh)}IOD6)9GOl@ zkCWA(+iNno7ub*?5S!PZiS$==Y}t?6lyW<8t(@vVR&>KeohD;RW9hySCj59_oF48F zQ!t&508a14*rw0n$REj$)3A`>y6)UVabbG(Zm15OE1I`uFwoKaY3TV5HSp-e8)Alc zLnT4Nb{n9Z4c?7LMY*4NawPp&AK6}?>FRBM6la%HGq3{<@M5$bs@v9f*N#@%dpfDY zP0r9I+{AmRG>`!(qFpeTL;P$zz$oD2S&E*m@!J>0Ysaf|eq>NIlit)pQQzyYU<7Oo zN``G3yD}aH->68orw^5~REpn3oAunU{Y%mtlomDl%PK#`!%uStYdR7RN3`yMMkF%g z{uHHnLP71e+=sU-PVf6TC*PK1k$v1iBThq_*-8GH7mNr&&dsM>$^!*z?`l)N)_NN+ zHXAz`y0lh%b@}lhv6&MEr>VOf+|7ZGQh;Rq)ujTydI%BxVHcFdUd)QU<)K&Z`9xAr zP$KLZyDaIM9!qvV(Wh3`asqwhQajYkEPJS!63?|{6Y7gR%Cxh#?+H}=&T(4tBqxNC z4Kyb((<&>G${b?ZOMZAoKJt2d$lD5;FbJdaVDGeSaU81+EqNNhV(u;Z?WT)7#d@3S z$M4ZKnb8kz`jp2I^wqwAosVq!P^ezL@N>ygiav+{k627^}b04X^TB}16k-d_@*iBmK;Y;z3#?MYu5r%D1)actyLb(6gvwr$>pyIC4G`>0kD2^>AKUo%P&lwF7LE>= zPw{G&XM9VQ@t5QpC`oc` zzp`di$9|*sgAX~GvD%vt?`p+&$R6tNr9o3ocHzKtaT|Pu|9WT$DldtDkSLESg0E$v zk#e>RT(!c=)2tkKExhA0bIv*?vV2IweEzSy1FhcR{~u)ddXSbQO%pVEurJVXEVIq( z(X;+GOnZO(cebO_4?W@G{@h1k32*Mn1wF*3ZqRniDq0t1n(oN-YJonq&agFFE!^7o z_nLr?NJo;ZK`~pf7!)Z~G$E-$Y0gvG|TL-e(xSzEFWp2^5M<{YO!Ie>Jac*Z`s zom6t|l`n991Ns3c3=7C7*oT#h|8!K;Y}pDY67>Xl;lK3uB0hXgj=m9m1Mnc0BdrEy zxCz1-t5kmb)mH^pFM5=+Ywb5CKUfI6rIra%{+QRAx=5O5@*IrFLEVO^?K-k)WA2SR z8CM<-@HBrxrX`ZkHTP}`oox=?%Q*hfL~>Qp=gU9Jzg95*`l>FWC1Z4W{!2{G>C1fs z^4q@y;wCP?o{FJiSmQxBOP@Z{7P!DX9T9#WJvufU%GnBPqV%JUqW8sOzL07BG8hmK zWdysthqCgT62)`B#!a@KKL1N%en7pse{|YIUvP#tSqP$D+oI^ds{L1R-R(E8@(_`7 zvEc~qlD=&fmK9!yXL>UJt_` z)Vi2m;sarn?^S{mP*MB+2QNObbX7zYhlsV~t#)Oi{*rJsNh?GMk3Meru8g^VcJ!8J zeCbm~Du;r>ba2jHCnId{K}K?q%!mf{=#(3#dQ=`?psTdlYj0=U-&()-PCZ=2V2XrN zWqq5s_GSzDAuOZh^hF639GDg%9zQ8*(JMig0Ad+@)TPcZ8;ELEEAox^@9#wc&6C16 zx=qy{q=|oYKBJ@{Bj@>w=^TYcIx@pwoX|sV;ipg%3v`}P=I$5KC^~Ctip6Zv&+0Xm z7r}QeZ?qkoX$vPeVZ>0?LkrXyiC}Vdt2$HV$UY0nB$v8G2W8T?V*|PfYr0!H_2#rQ z0L3Et&`;yVFqpvy?9~+Q$oP99>9s`$Zadc71XmL7%a}U8z|*2fWxZzLZZts8$xY$v zighHJNEo;VoqN5FW>ZL1gt6K=aBHYEn!WAhUSS=Y*V?cLv@nAdlZVF2sYD9mydw=T ztO1m>;=Ww?y75JgR=azg>30RivE{ORRx^e?k(}>0V&WWoM<68lUQEX&@?qzt2IOV2 zy}a(1gG-a#N?Nv4k>`4db=LPrlOMiLx4f~otBuOK8a!J_hP8uJiE?`YIRG#Anep#z80BxQ0QX`aoNK}R*^p`0oz6EtG7k?|& z5`!SQJSB0He7HCp`{x#CQ(i5Q+6Ts&2LI(a^S>H#{(t0a{C}7KV~cvjME?FtdsSvo zI9cF=>X4LQIW^R&)Qib<-?(!UzYxKPrR_S#*MyWYJ@jE>U8=@RPSK(;7!0vPjF zEy0jCzd*>tGz-cRgv!01wC|+jdS8MO$&H~VvlAS1j;(+f)Eu*&*k({(V!d=+FI2$I zg}NqWIXQ{2BMCi79&1`!$l$wu)q*DS9In7w)D^2s=niR9)DRVc+$6mAe$MaTYl%}` zkT>1k%#zq3a)gQ^R)@E*EAUf>f^=62*H9)h3s}+WfXq2RX$Q#;>or@U2dOMCBLcTk!%Rd@eD zjzF{sr^n5l=(3uBo(s@k4dG>wAUcRJMzkN}Ih1ucy27Q5tLj(fox#<8+k7nW8p%rY zXkx1UQ=Xz<+92U>^veE^OmF6CtHv>@jOnTf`NwHohEg{z9%UKbA))_{>Mz+jK;OWU z@4Qs4=ex%CGJiy8nXcwR*PVE}wz}K=kOP2ok;V?FykbHwZv$vCpv)le!8>2^ym_&# zBVmT#@ruV_sHpSVAwVbEYNWqohVYT=s+_Qu+Aca~qkwK8S(LOde)*|V4eMx+V+^Lw% z;2JsYeDB*^0_NX8gCX;MB|N}X$G?I^Bv^cvL|0QL`y^ZE*x#eqb`v2} z%b%t90nvxXADU!aw=W%6Eu%IZ_5_0Cg0GGysTR{O)GZaEZ3K&}%02+hjZ0n}U zZEL|4zx@-D{q$x_ls_+G)KG^+_QLb>*|j(d$WRZpG3MTo61>x-zb%&`P5jWZeAe*uMyjPU5$Oj=zI)`-edq&-)5A`4L zd2zvQG`XNlTdMfXOM^pxYJ$D9M+B1B(p%rumc7&QcPN_aG3OLz#TiAoE4|To!iU%V zxorz;e)wM0!sr6U&-|7jz>Gneo#7;|k;_0i`+C?`1@wgTnITT0Ta6UE$9e)I8=$g- zc;}5~YS&j~0>O!YlKO9!%3t)AWG9&PtDgOCkck4f0t8H3jgV2ByhN!!2_&4pte{_K zPkpd{u!D>}&97E&cD`$JJiuRE5>!~zQ~{NOFkqA;34-3hTZe*Ryxix@)0y-_s=v>e z0?WyyPj6-_;eEBn6SbPUI`>7<>EBnyf4XH`+3$cke*x)y=gQD^y?DBmO+IJ!M;mqO zl7kG&wJowdIA<-+PE8K1RB@qV_?+l59eJvqAw|ly&B2T5xPwl`1ji=F){}8lL=37V zL$VaR5SQ6US7$qQb9CwFg12YK3A2JPMGOB{f_J%(>qbQ}^(L+ zI@qan-ZR^g%Q!;(s2xB<8S?Db{7FHn?&ea%D2Vx~XlVYcv{aACSyF`jvRG-gtP zhQ7xOx&`%FR^c>bHQ66z>4rGgOK1OzP?^E)){IejtOH1BuaCRTl&vy|O8rV_|Lfkn z;=ZWPZUl|#PE)~Ol4k~`%LAMQL#$PFz0^P8#1%$HH1TTs8wX<27+@<@Tlj6y$>3ljO-uPRDLC+X!@?`2MkaqY;Qr6ig_3Fbne2GCn zR-Vwi@Mm1SKD@a&% zv~?s3wboUg3D-LbwfetN4lHc7&{HNqr3ZdE+1H#jTxCG+>sNhwR!kA!P&&ukx@LwS z|1sgWT$I)=Zgta$-Mg3gFCT-)4sRpr(9nMW?hY7;mM1&XJ*-AH#l5!U%B@7{r_kZ7 zrH@;E8nxntor^iLXgeSLS>4#W`Gw$%1>uab!QGceKm2~$R29ztQD0>Px;;G}T925+ zB=6~=1#b`9|9JQ$-R6r=Bq9I5VRgF-yvmn`-m-yCnVDdsmk_{V8L^zW+bb?~np!?9 z(p!p*IQt>-ogdKz#Aw?1nnKD$!*{FFd@Bl=6;@2W85m9Bp54~DhOD^AZZmDWv_V(1 zT{k~X$K^g*l)b~+i9(U+F>Xu!IEzLklbttUy5hPeFEk!chby{jAUw-AsUB&fy9cZK zIWECt^E0czT*07J4a z{@DBXcOa`t2J@uIr~l0HA#@{OY|X*@d^7fVU7! zP+$by0&js#ne0=m@?@K^=#;qjUO+EZe*9DX1CV_1;u}^!YOUK{!Q?wV65E=rkn1I= zkg>zCZAEdOwP!1lC(sU-s~cokRU&JE%ECl(Xn-V1a>`C*b*BOqbc>);k^ zR>7<~C9d0}ZfEqA>|)Z+r5{p$&l1?A+$I5l`~UHB^dio{If?uPB`(V(_Lyu7>_B_N zy_brHIEwbxE_W24E$>1f)0zbhy|y$I?Lyq-KlQg+xO)8!*fPh}?k(*=+AD#(x#~uM zaoNuhn`qsU_CC-p2f|xGjN~HMBCAg!SI7`5kbG%5fkWb($~^$SQfJifer2QP#fAoN zOGeV7V;hzyYUx&FI`Zu-cjlFu=R#ne#VN=``@lhmJ0%Y?9iyS}rwa?@FLA^2U$&EXvvv67fwA+Nr z7~_V=HJh;Ig;#RNz$DjRbsAe!JbqJm{!N9JN=xJ)@_d!2e7w35F}wV0t;nh*q<6S2 zCU0CyY_ummhHf5rjqILC9Vcn*OKI1Qu$|h>VtGgY$DV`#uFe8ZbdyN8ws$d;LcpUF zZJBEF)ld9PJ@iYDPdfnqrq$O4z+j9D_+s;*#cOFR7s|EN`SOubAYmSi;Uqi9252Zm zKMkS6Qg?fZ!YNvf(N-DCh2@Ql_UuGhZ1%r>x32!iAd`3V&Yn&oZoj{a@C@4&2kaOP zp;GuWon&98H50+^<^8mV%;%S~oe#>X+`cI_9~;bv%|#SkDnq~Uvku&aSis0amme(u z1E&8KoF@}<-E!TfJ&6ICjtls4Y^h`=RkU03NSE}&T=%uu*e3sdpsYFuX8Qr--?wt0_?SXBAz_1I+VlDBAomp~>DV_Qblwr8|QPkcem zPi0e=TExtYfOnfLlZ!nEnK@203rhnzcy{c`u6|??9X!F2CMdLBqDW`o7$<9O9R3=V zo7wS2PvVmf`RogdVy{D)40b*NY{pR1Yz$N1s8nHzagc{#oPW1(|kzaZU7j)tR&}4&oFf9jyQPA$Q9v8p);N@~knH zwx#*5K+L4TKdgTeb{7&6s{p}NogfXAD~7zCLr}d>-~(SMBvi5%hWa+o-g(PvK9~u)XoNf;tIAzPXiNPe6q@`fq3I!ereeo2Db0WYYTfW?8MUOcs~ z$RaLHpo!WRe@ToCwqZGaC3&;s%4g|i>6X+9_k1;GE`qUb^;CY5pC%e9nb+-Xy3+SW z<0j{MjSBhjihnXLMIah^NM*F*?ly@bq^ID=pSzB2G_yLUvgk_c=lkQzyFd#oaHnK^ z67D&tZ-JM`_G&Mm^sQSrzVsDHoDebPq+Jhibd9nN=waPq71;l%1)BDh__-bL2&9mb z288gvV_fPzl>;fIp-TIx?Z+$9`p0I2^VQFC%_eB9TYlB_wO+6BDF>b`S|W-IFz1f} zIaTXKt|oZ7O>V->*!@0}HdnyBtin)7#=Dr#y^DB{fM!kAvVmyq#GnU34C6Zt<80gX zRxDB;O%=))i5!114aS=>(xYh3COU{+Ii(Cpfvc53P6q79eu2{*yw#@V^N2vTwZA7 z{)G64p+OSHl>uRFp=uC5F|(kFy6fT#%keo?A9>kb+ZK zQdchh2`DExp`soG6kiozZL`LHu0JkMjSp16C-H`-=2msuc%Q>LE*z`4Fy4um+L^iZ zW!Pk7$=n__vMKOzHYA)o!#&0C&eOSKdXq@fB|#)*16RJ^vPXK-aKMC`hG4zaUc%wA zC_j*m^XJ?uROMtt>2aIhxNLc{>&FWGr$W{A@m82i6#K*i=a0j%M!b^8?@Kk?IN_sn z%U=V}={cyvM4rw>8j*Zc&xfRf7Ou=kphBN_)9rnYdzP1cyH*(t8KCl~dF1M^Eu|uo z>Y#{i7Kk;nggMlBLMfPQya>f@h~Bk`)mrftNAk%j`!E)30el7ss#z06oDC^qgz$!8 zCC2$g+c5gEQr6|hmu*a(6_&KYf?0x{Av0YA#)R661KQ2V3(w>~DZ8MfMes-o6`+V= z>f=YHICA6laN2Z^2>rbaxvVGDx?y^oBR=R0&}mCd>eUBK$xgyBT5QL0RElc>MU~NL z>$zCkwZgQf0It`O%*mfi@TUL+&T+(n6Mqva6SXC5Dr1I^ zEBCZ&z0LjfVmeKz`LO?4(UXu1P$eyUFgXU7j02NHq!!z^6S?3~>fxvvQwjiPm-062 z7_B_M%C?l4D!`uVx|gWda~lP}4+ru#QE)nl33asK7owm$@tTR6F~hKHN1E=DlK``* zXsY*a)Y+>;L+v)|?-r~$@X;cse9#6MB}howT4Ik2Jp4Z#V#YkG?X-B?pQ1926tpYs zu9;55C3gNf0#!r5z$u|z@HB|`t{XnH(j&i3Hfecen9`as+3-eEASs^9jtrDXuvc6D z9^A!W^u+2jy!Bv^?j)n9xV!S3*R=}ca)i|IN$jj>h<9zFsyFAajor_yA45lQCYX@8 zKDDNeb%M>3q6|}Lz`Si;1K+JThCP&-R#E%6Am58N?; zv|GMd0`0gahD@ufL)`#P00QEaO7|_<=s$UJ&2YV zf#|Z;QKPwQ?lo-ahty1<@@$jJQpb3*QnrgSuibnCnC*7_vscpXNluGGMv4V7?H5aZ zrj?r>>>uu3u)oph>*X$g`9{JCcX$6&9gZ27dsU<^h807NRrR9ai5(71Ju9Wr6DBRW zthu)Dg(er~6zr{sBT1(OUWm}*AM&qdkK+E4@E;M$@G)k|!~1_pX0y0|r_QnD9$wED zseP{#66R(g;@l$VKjY^FS9P!j7xh#U+0qlq_BE}2{!a4gEk0+HUMPg^Kw~L| ze3wh&H;|FV-^R=zPC#X`1q6eoAd|;*Pl^;kg7^0gq?ldry{tPkX=8xEQ9N813p*J{ zt920DMMYDt!`FMAvFd^qOxd0xp1d}?BrRp^sSKrQv-9O5q+Q=hhqeZRUp&GdYl?H^ zyN-wPkAV}2GOgHyc;PqqLj1iLqR{W(iDyMyYuk@XK!)D75V}M%-g^9>4J!VNi0ywG zX8gbNJ^X*a{KtG5wEmYQZ3~<|0wf^XN|GFJDAc8yjt)pIzxLjNF2j()>x+Oeb15t9s&!>eId&759E@IUxVVrZtlwx>;@o}u^333Mqs5P-M8 zGy|%bx*s~@A-8MwMRWS5c>6bWf}-B-^_BTebDTcm?Y{Xb%kb2fJM>@-#@MIpkAidl zlJk6Bv_fu#ClSB;a^H-VCD?)lDzeg2^qF64zR=_aZrjfTXw*N>xs)S` z%-mW!36Vn2^p~tlmo7YJ_ zE1xvhTNNtyI=!4zqPQKK04RKXGaRHbZN-z_EY~qJ>Qu0W$zR6!KpI9W1t3fpuZ7 zDGPj#fSIoi&Qb|48TsfU)crNJf#aS2-5#^&2ZCB>T0i`Yz2>2`#Lqxy>shENa$*>M z-A=b2pNtw_07{SH-O^I!-CdbNxlbi?wK*?G>z)e_UZMbGK2-QHu6pyQN0LwW_Ou8RRJYvzdV8X`D0y~2Xg zve<=j2R0zOA%b-q+sc%a3>e$f=AKR=$Qko^NyHGe3y+DCt8tGDv;Q5B{8NCtk1ZJn zWC|=ISlF0axd&@pVaeJlhg*>)dd*zTS>Joh8p3o({xJE934a|*?UlbUTEYSdco8K@ zP3_;AKw=bS|!QfPFk9O8g~>SHj2a!jqg!zYYj`0w$A=WL;TVLz^v*99E)J3eR;j z{)^|Osp(!ju(%RVO#ukkis*_ubtd(Xj0#XjURvJ zI`WHo6N{IM$cB!8Vwgim&P*)X%b(p3{G%$S!hKP()6&+wkOj-uD4=5-oUd zEF6_E%+Z9zhcv)ktEyZ8L#tQV`MVh40mqT&-T3Y@=YrxsBE3AU_Ls71JE7S7>-Q%0 zrpVrk%oKTNEpn0UNZFreXeY1*K?2)4HlT?;NN9kVV1UU9Eh&p}oxSeO9$Wpwf?PxI z^9uiCX_l$gSf^QusH>`hTlkWZ?e>+Fj|7>dc8%~6L_~j5Tk>tuv+r+bBy;!g5?@fM z=+jMVXu8dX1`yZbZ8|>^^og61-6qk8M}BmX8i{7_Cpg!NFBTNfpeeij%y`(CB~f{) zozrF2yYU9XzMI+Lg-F)PJ{A4b>|Q^Wsd>-B!K(;7Csq}f=*ap4zC@&xiEUR|y4UEH zz2|U;3i7ESG{sONZc>t7ZkF4}Yk^3L&mEZnw`Gi<9EGtt*W1`h)qUg^F%V%xPKdl& zdrb2Wda&t@y8WdKAsm~RP=)zQ?B=a(UwT_~v|}KeLo;M9lv<;K^Io_&B-95U1T(1H zVrE8Hwxlr@mmYs|n%y=?TtnAS^o5ix7JH*rBlTz~!0yyrCCw)>eUHkuv1*npLA@z- z?!U)dr2Lu33gXV_#+6BPP2hKbb#wlb7z4kMNgj{~JlQa&YY=L&v_9&(p8w6T_2eL^ zOof?B=^}HLdQUK~aL)_g0Yl$t?Cb5*<$cSng*%~O3*IspDU=m5*fo*ZrB|t(Z6p5$N(0jpZ-i#!A{;Yd z^y(_%J9Rb39mgCI%&6+|r`|^p-@0pU zmsU&@9u&qDbOMwiIgfH*GDnZ@ig8Rev3$%n{ny%~;y-nPI|wSshVU`OC@>uyOYJGH z4B{E{B|FI^RCsWErW?{$>;z7*CfUox5S&q95`6l?*Z@<%Dzu?G@6JO;o)1Cp>wB5j z4=obE^R3)#QSn#(~~a3;pM7(4>k2zy^smxGKnzi zSBCA5q@E2F@9`Z#ToL*}GQ=xpU3xPzv0ym#?b8Km7laOhZfl zl1%>d$is(v-YTD}waRnm!=X~3N9XKhl|;=JyQw_4u`tOXgZ9s*fi(ODaP*GZ5sj-n z>>23xsvWDfQ|zf=?2)^pbD`EHOxCA1Uc%7lj0KH>cEKE;{0OeuXo2O6N4lc|;t(K` zK1hxaJF#~hg=K(QiBbT$({z+K@X~T++*|?hV!JC%T6w5M-XH|fI-|vlAzz>=vF1CQeK}>XAj40f4LC1wW5IzfmT+Vg=FWkLXSCjqQt_y;Kfb=FUC?FlAcY-2KL<9t+ zMx}%F7A1rxy$MJ!ktQHuq*n;Z<9BiDq)1B@g-KV15&V0_Vza!*wrv@xo;bIj(ove_?{ z7aOJ#t7g>dJNpL1U?6;iHna>%C6VM+Ggj*vNb%mr?{@Mu3;Sig>%tO|@aIe3WF5&!}*b-$ktgk#+e!%>a8)i`n zWV8D2re=5j@$$aWe0Yajp|YXW<+2;F19rLDc!R#+}}Ox^x^+A@@>lbrM)K zdqzHmpG2+n2Dve;j*1ri#Tep46OTb`eHar_i;`<0Ya6T5+lRB)^D-yK7uTL-p+a;o z={mESj5AFgEJpOgWoE%%C~n8~-=WO59_F4sL5KH#a~*77%w3P9{=)l;fi#-KM+d9} zB3QIM%?3e@9}>q5*q!7L|F&*Q4<|q$!Y4Aa41U+eJNFyEy8l$ z&Ih_d_5kK1Y!RN|6cneT?k19b_gBG#*axkxpDM1(>2|>SDP1WdLvohiFv?RROFlTy z>-^u}O-!}~;MC8$2VsH9RsT9SS-q&tX{ahptl!9dvUw@!#g{J?vJ@r$L*bOf0D{+G zIvyy`NO-8Y8Kx}_*`DnmhUclgO-@(w8UG^~yfG)Mjc}972r+XTYJQ2HY+* zar|oNkGpd~MSl^qq0~0OL?;SlTa5L(e)vAMEWA4{UBC8n&FXrRhmhrS?vr%CL8r6S zBkyHkFFwM=0(*#WOZCz8!fP0J&-`i8tglm1KJ4T3y9a;rl$A0&-ETroILGHn*75>; zVZ9li@8tV_5sDgXYe~JCY7!gceUI|7OZ})SRgD@_m5_}`qJ4*eMsh;I0cy{YY8N8_I|Kdx>HsFmE|;D{u3$I73C;y*Gn1xKT?O-fV#k}n^` z$>wSJH&JO)4tWD8x`@F8YQp?uLhRC!Zi{WSM<$^E;14y4GqkwrjTMZ#a|Ir(%I?MEwO|0qS*L5#3DgBLs z7{PQe>UvaQ##17=p3ilaJ-Gp0GB=u{1drwgu^GbZW0n z+|o=5$E3as(6Ij6=~xwBwi{9~kuM&7*zf*F@D0U%V>TN7K5D+(G#N?f17u#rX(D;T zi>+=c9{>?m?ccr{HL?ak!N53%FiZX3S72=M#u!R~JX-*H-F?+nHh9&-q}d3Q?0!N1 z4fino-AWOg-ziA*Uh+H4sA6}$5jmg_NVLQ5eg|ri0)%Lha67w}hJq*9w`tTq)%qJ( zS#H7Y^LZI`z{R$pA>RZ?e*7%#!OTgGLE>VCYfrv8T3^1@VC9yzMYbwM+7-DJhA?0$ zUWJwWhe9`h#OyiVH?1nkD8N01MeV0X+Hu?vo)H5_03SFQx+Py_H zQs!I8Q;;SgiUGdHu*WmhTBkJGyniSZbV&ZTwWHCwGtNCy3H=7lrAVeVgi*5Ky5rwY z;+pi|jzi4#4r@lx7@g2&r0R0pGRJq=eZmjC3YsuGGHBvPeB$BgyNH^`ZXA-Zz-9W~ zKp?OIe(Im5@%{Hx8IpDzVRi$GC5yws-Z?HzC)yYF_ z8_I&Vp+onb>Ioy5iGrJW&cxs@!_V!lc6RS$e@5wIdq)b!f#ibjhnhY?Etmst1bAWg z;nIzOqdB~u$K%G@9S^N$3w_}?(c|36JcYCNt44%eTAk`q%Zp$ zsZZM&uc&b7(0|&wJjRlh;yi2egsDQCmjuO&6D&)iAhdPAQO`|xPUP0Fd2fcK`Ykq| z;gqM@!tQ@l*ac&GX*CN@nU*aazRSX|lC*GXRTV?!0D>Krwxt;6gxa6hr62xOV}HqF zB26KdqBSJ)iX;XchiZWgLt-8I;`_C^zQ6OnxzG_!pX|QHa-FXW3!~Y}D^g(Au+Anl zp&@CSf#@y#d{VH7?@9HNa#|=IwWHZeGT&2bJ&P}vkK~PpjmCCndc)xVyP7Pdw)*O? zf>109*RvIpJd*TP<8z_Tkw}3%dQ!KGLH(%BM{?`!1>hoa&z@ZxZ7HJiQz=4q|+53K(tzoLXOT#G2E;BfJeuJlvJ@czrfE6*1M z-*d%*k}H9yRaD`Z=B~JAW5Nk%`ypbK6XilPF7M>C+WSteFCw?z&rt#o0eHAXcS73` zn6X(+Q!nC~V!FQzyQy5x_yErsxsKD`nYouwS)SX)w8Rm<3~6?lEB1N@N+@d$d;WSw z&&_w|zC~4@&Gm<-!j~zIHJz=qeuMdX#C}CEBjMt|JExNeml9k$Iu8YJT84MM9dtVE z)!-^iD|+-Zk_af#);I{fL`#nnsm|QZt({1iS=AlkBgn9gcjji}h`)gSLt>nRqsa4$ zH+M!sZ%>ZTQvacN0ZQx9!kvu7DLJ@G+R-m8NoF^-89KVJJj*D$1OPH)CEpmT@mY^6 zR?Plq{3UsudpbECJjp>)CD>rn8ScoGZYGC|Ng7>hs$f#|d+R|#QSH|ipwnm1$4W_B zLc)OQ?E~0Tg6{ib`hN9Z^@lb&6T2eqaVmkC(|`VEi3-O)t;jSK-u@u_?i#^hbDfzW zi!m6mTW9|rs{Z0tPVF?q^FQU(1Ax6C~q08V#m)G zZ-a;5yZ@mOCm|Y_`&csX5|$&sSf~w?WGx`fz?WPfo(kQTfF)UCv>vQL76sE5OZB3j z>OJ4k7ov~m-tiatOyJnJ2DOf0Ih3=!D@V{`8({&jS1Kz6V1;UwV?yGS*ZWgf8Se-- zPk};U08%^aC!qnT^Hzh8>D6)Ig{)13R1z33Yfi{Uf0Nj`GZj?GTcNOiZ?4}+Sh7Fy zZsv!m{5(Io=m4*u;i5qB6;(>uvm#_y?vYIsmA>Tgjv*VD+6sAIQ@r$MN%h}CO1#7W z@tjOUd4N*aS%X4=8EuNDrPf-}jx4pAu4iVLp)@9a+>vM}q^kOb{qGd06hsa4a6`ga z37_i;#sgE(o)3O2M`W`!CQ(!#;PyQlW1y<-aDwjek*33 z5hQu1Ggf!9u@}tENF15Hs{{RAeriWT5b)uBb8X1cvR9| zdU2!dsp!W72GssG0K~;Np*s|c$ zcd($V{{u@_f3j=MsW9XrQ~a{gH8Dr|Ys#}PeUo$DR@-Y4j4*h-Bc8vxRo&T9{HVkO zblSm0M6u7^7)597VJvPnB>_0WtGyzuQVsC{TJYtDPK1^moG`KS*JQUV~bJzq^k?b@eSbT3cI z8RxpoTzcUXstD1h{X+4Rfd|iYi98PoB0;}cpl(KUGl%^@s6+O`=uWGS%R#oD0I%iPY|o?{Qeuy?aU_H?KLnZQTwE zvm*uK4dS$gO)7q&n&xtSG4$d;fnJ<4mt9e#V$Dq*?uy8^1IneZMibY95lko7oG{2f zrd0A}FX~1-?ELc&qKBuUbH;dpu+4M%VXx2!MHK!e*I*r@Sb(LdozoexmxKTMfqhl_ ztw5Ml$Je~W@Hf_x&_J|rx~2$594-hAv?&;av}fA5bedFroo)TAIr!AX8_!dy#g}0ah`C*YelbWtXt++Lmx+*oXstx0`te6e105;{DBUP zvFI;~>kA`&uhtSb>|_%-Upd$FpTw@*4Q_Q`dnPiH+p7C!Co7HP%&$WUACU@v0ZA87 zodO#D)m~;cPuBFO?ddgwVz*M`uG>Dezod=@9;TZ9j-j|F407-wCh#HbYHcBK99Y+? zsBW?|>~-=_?OR$H8Lxa2NvVoX3!<4W0TZ^+>ue;tIg(O2Nw^gk|5`~Lo6p6&cYL29VwOTT~dkbuCeoCZv5YyCQ(OzJkOeF9J!cUvDV+udUBHbPG*?0S>ctG+cNNz6QsRCYs@JB zIAb9RzZ&8AquZ>09oV9@tGr69(#+*zV`lGeY~6k#MkjBoL%~mRDOR|ZY8SB_)4PsK zAO)j-c+Yz0CT=8~r9{OZalZ^?I1`xy&I~Z?I`ZNgXujKIbMiIdFpY0sF4pfiVV2GJ zWyp+`=94LHty!{psH0f~x(|#dXoe;eAnau`W%%v7V6!s)sUNwV-K>NVE3coGaGNADk5B! zup#;3gjTjL?S;@mf3m4pQ-_=dY1?~ic-&i~gL_!Rx^oZQ;tP~unF&5e z{kHQ-YX`>PrPt=5YcsP!Z@zS0e^AD9%W!wOEo?i}bB^j4c;U*nm$U_1WUFIYibpJi zsa#SHRE?aB56xb(dD7J(l2?>zYHkwRLq({>pyEtxP*Eq~{kb_oqV#{f0`Z94voFL~ zC-$IMN9VlLg4zuiDaYOrG7no{Cu+fs$Vgm2P8aV+5{`=DWaSeK(oqrtBuREp5zRfw zYKMi%%XMU#!m(_$OX?YfRk`8c`p>n;xHhac{;1DUpaGz}!OTvzWKGh)w#DyTnZM96 z8vZNjrWKa;Sks00faF7ns6UX7DiM~yJ$aat#=3M;Brxoixru4g1P5GDt*rvVz*$K?q z`~OAD%eP&0w-i70mI-Q{ZIQNqR1lJyBhvOc&7X=!h~{PXT?|t7m)DM#{*#T1Mwb$c z79srHwsxeajZ#%Ld^w`o<(MAPOfdm`4exYXXc;)YM|d99_z_{! z88-p_W5wnEz<+6kw|FyzRQFl{ihK`eA>{5u8l;QoVmEnBLE0G+eg$dSekD+fRn4yP zJI+#Hkh~aQ`kY^qXcOJjPZ#fkA&m9}Qw#iyTw51x?c#W*?UKSMb%Il{#{=zWB1V_n zd=83^?7;&}DeEjfOrG+)0+)WQ4cc#*Qb~;Y-gYcR`#sK>x;;Q)NbUTemyzWjpg~SL ziHLzqPQzqzA(I0FoZp{a4GVXo>htCuA7$-WX4Ri8{-8Gbu)l{2Sq2v-{{USlOSmf# z?1p%njmuYG9MyZ*mnFK$wFj>(KPZxqiCn;BKr|kY{>zgFdJIWo|9|FcXVQk(pMNL_q6LyL-|vfW))d-am^*((e zF-oe|eo}YPJr%)OAsIV1k#Q&%p?OX3&FUW?_k|P9m9w~=zc$EauDHoClaSE?+MWby zTvabzqjR1Y?Up7@y5?KTeJ5?xVzR@7A!mTTaf z9_zu#I&0PExt2Dm>saX-HJ2oL>l#*Ad4sP~)bKPUZ%t*EbX^uJiuTqRK2!MJ!|Ti9 zNgt|Iw(VX@yq7OVH~K}D%z17(DopcMU6ddcSZ;Tr3MhNbf6T_M*M>I`kUBSZE1ElW4Cg@126svkYq zV2Z5-B<3-${c3a}~*dpUyJa|Z6uwJ3LduWaG1NRmu69-odzevMQ8YxnDw_3x4B zAm7+d$o>UhVExnr6~W@gWmPXwah^5GvD@qP2A1(x?vf>ssVE&sp;~~ow`xT%sSP4f}ETYyK6BLs=8ktb4tf6odqCWiVzy?!nvvfGg;G#*pMy)mH zhWF13AECATsJx-LJABnAr$~#7d>BmxZe%m0foM2dI{A?hy{#tIt>onU;!OT3U$bez z7`L$ajVR6MN-(xy*i24t|b`uU!D%hliy3Vk;+xx4~gGBI1@5o#LA zP6g~iR7aOuNlH~7=OYQn^+HxC2btm1U0TsD{|@i6jQ;LA`3~BOhU@(5b0QphAo%Zy z#;RT|U-Opf)iRqircyz&A!5!h9Mt5B&TE=A=LpY+sJz$P}=O8j^mshf_U+~ zH9T5Nek8ECBrb9|?&v|iuP1oGr;14nlU=axvr-XInFH7Z$t><`mm4!9lnyz3MD%%^ zrV=)7t{#h+R=xyB!%GFsa!krOyx8r;NS1p+6teHC~jf(NrU&o=^;muTX9re}h**fzPZ{ zql*ukE+3%IP^U+@gQS)Lt?%luvp6z6B4xJjeiIJsz1%9#(W|<{F%JJiz7|14IKY5j zE4id?@955clp2q{&HFWDsxfYq@-*gnNQL(^1!<+Mhjj7U8@mG_;C2Qxo$jixls06p zwak$FnLW}=p?@F@5w0qOv5DYDRCD0rP(z3PH8LbWU14nJj}ONCeq1ly*9-!K|FAv7 z3i)r;^3!7?VigI}Lf={K9G(wZ#>kJ{%(KZfpySilak(2(!YHpzQ}cf{Y5rG2*z<^u z^xnx!fWx`}JU*>Q2zt2Ocdm_id3u8{DbC~P;cL{5P{)zT>Yp1pImm6x3t8ju!JqHX z8_Y;oboYGDxDn0$wrNzTNTQlA(`ZPMW&y+z;eudtM_sEVpl}hxECkd*$cx&b%1hrE zpNKzweAL|dN8i$gMl%h;ZT%ZONLdLiy_*yPqz+Qn{A@&9{nqX|?SlG}p3Go?Kk0@L zOAR=DOrBI5^|!%N@n2jgWo@Z@XL_R2XYfGhz2{yR=4eT~ASq_WruKW6^CIO38M30* zdRFk(`wF$dinyc$6Tt%QpW5xbR_e#!3oTgtCB#TBVh&(^<=&|K%6?>`OAeHFaI z6Nk{uX|tMW9FlrqZunET!{NH0<=d2KwJRtcG8M#eH=?m^6ndc3b& zpTP12^k|&Zdn|d4`lLpWIWS<32saQHNuE$u36r%;Xz?KFOkGpnqD*vS8{LvQ7U^z8 zp2a<(oG`4PjC2&Z$O{g$EW|`4C|y&r*;t+2?olBj#rn|vVSV@U27K!js3!cy^%Pf*yy_4i8je?IklC7@xwXOPmZMwDNN&z#?C!9bXe-0;^OhxKP~WUuuho%D#e zGc73SR&M=}H$Yu|`!~T?1*a(>-Eq-#lCK%zMs4luq_JG_z%|k~!A#J7@rM!M?ZASx z|Ipm8qNdrp#&LGAU*$`UXuZ6L=d&joE>uTIbqlJ2+48hJ>^M_8|+ zK8?I?CQ1hm0_tJX1I^E%K2A1y!+~^d!^H|0?S_7CmVYQrI6v|(Flo<&-4O9)F2YV2 zu5fUj<=M>fGCfB}u0?z6V|u^cI_aeAt#J~4OLxY#^J8# zss$l&b2Dmu_bMNET*ADQqQO9D3G+j*($ioLb5bHn_-IA%Wfsp|^e{K6$2+3R*)>s7&yxRLPBLlY5K8r@AWy_cghUZxQ6 zFD@4r8(S6&#$NI7m|T0V5Nnp>`s>40O}cZ_)2xd;w$t#LZg911LR#zj*f;hhZ}^0e zyCrwFw$mS)P}uX&g{M%QG7=ZFu??t6@070W!KLSg!a$6+cFRUVQ+4?k0l#@#r-qD1 zN4|9F!p}JBiMGE=WWPtaO}@5n>kK{_nnP68brz*F)Zq;0rhL6}B;P*&J3-7bY~-<-%EXh5ccGbkZa4YjeqvJ~f|FHP2XL?eRZ#vvGdd%9=^Q==~ z_i=uNa11u0@6rKw;9hsmchjb^BWgF^R+rYDSzK^fRTc*^<0Nt#>u2c0JMbR znA5`5Kr7BHk%Lz%>@Z9eyUJ`J2C^+0o-Tug4!bUep8QZ7L}8 z0U>$Qgx3~VzrvUrR&@zz^H8TYmNor-C-}2zWW>A|aMO6)SN?Zvq$By?a-;n{f|Q{e z1X}6{F*bz44_@H-T!~N)&C&->42n&1^+@g|>>{bZfG%>!1 z2PcpgpMGC6Z!}W$|A}#)(f`O|Wa%tdj9TlJ5r4vz)WZx@AOvDo?QEOysB~;M$g7H) zC^b7+C##}x$r_e{U1H}8fcyHu%Wwz#4nQrLv!Hks3k1nPopB64h|? z4!L{~!v6kpL+YM~`c4nydXpH?-*v+iY0RQm`D68#0jmjtSS zrlYxr#=_-^R*Ck%Pe*x;JqI%yp9@&gbVwzp&$UCA=ucmD5=6AqKX@#C*A~LfCjbCb z;x8SC-pem;TLlZ2*H~NK7*XYyWZTF|#W7j8fB%PK^X1-BP9oo}m4%CFd9;1*(d(|K zd65(koIHM>U{7hVS*2b)ZE6Y;`IsT>DZ>CadSlFbU%n8*y`J73m_oGP)sEWy?7PugiyHtw`V^Cw zYi(rFKQvA2biKq|b6&7E#9LXmNZ$efntSKomE+~AOj_S8|L6C8$Xe}x8!W!i>|EnGC*s!~F6x6) znTC~L&BrSGG81n8nLA{WF38Q9Thenj3Q>%9z}GO2jJa@&N~j^fNSLSoTh@I;C-&b& z7V3%kfu-D$4b2fLui{5cmv}efU1$@Yuag!{K$gEF^M@S&oJ?D7qc)nZhU#+R8^c~f z#;%bm9TYH?yrgtEfvSLY>90L+!_K5V(xyk2qg+8ToixM9M0u#nkhMyPl~>g_DAN5) zW=<-`a{JyJZ;eUc;hqg#)c}GK+H|^3NUZldm2+Hz+O91f(uX{Hw5s*FJ@pin-J1H- zt=!wEtkZYV>$D6nIwG5>z7wx$S+Us1|GBR1{KcCatakSrg-z)-*ie2M4>!2p1l-oK zeKh|FEYM;nrwL&1>h`Q;s|2`Eto!B?m(x>VAEQZOj8}_ z6RA_hioq`JP&Woft{Z;zOnKHb5u7k3bUH75LO{NoFhdrYQ#xQOckq~BV$;ZLz5GHg zgVIV=q7Uk<8L997XqRTE2tiSne(INu**RU5H->tnn#hxfu}9h1eWqo107 z?z_D8k?Oi~m0Od4U5z{jW`xndhoXSq9?a(11=0!EcPn#l&Dq)tx)gFmS5%Y1${-U! z>AiaQO+X0M5xJ-6JDd*cI(h0aLsl7i30+z}eLv?*=}!JC#CkuUz6pNthVNiRGzkl{ zA^HHTyAfNa1d=$qp5C})`ee)oyOAj5ocXQ7-r%MN7lYH@*JxA0`fz$225pbH8u1J( zI^4>sJik=mTJFyu*<~uP`cb4a8#Z-?aVw?gw!wQpLm6>k?(z%YV8mX__%4OV8=e7! z<3S{u^hE`rhOKGz%-a|3uB6}e5JIRX^Ru)A})J&Qn#-AS`9DbeM+8WJoz|xM7bLC z)hN^KtvFZ>K8N6eiQ{$cr!?hocsnu+gl4JG&gi-g_4p=7z9p`Y!%5Gfmo3^qT5{|2 z{|=na$EO#b;7GC~P|EJ7pqS7d&DttFFU+`6P48JRO5Ln|kJCk6{jRQ}@ZVtHh$YF%viB-qG*O5s~6UeT5rsh188yk@wYWF+D5(Md>tUSy@ykUwsqTn;?eR=VsX z84ZMJGQL?fH+P}h(=v9Ihe|&Q2Rh|!curJkaIG!D>d%C`^j0xq zEPC#7)@c08n@TtQfTef#sAqvpQ|UzO(vR2Yu!*i{`+Imf$Rj?gr16TMG`lLvD0ikP zb#K%lki7O9TB=HDNf+=yJWs#d(N-*7FJRc+-H^d~lMURzL*?hN+z6I&FH5|?@h?A7 zV3nxV)-R?lVhbU?Xuk~&85S+Nkz|7|*rNMu_VrD9mwtc|cVF+d!%U{-i0j>UfR)H^ z4trwCL9+evw57pRW@dQVw8PlTPiMu_b~MpVNNO-^ib0G5ZfEwm5;y^r5CE{`Vin|j zw{;mz7w`Qi709~=Ngk)Re)FssTb2JS!``_6ocqQkMVel?bQxj&H1Gnj`Jf9;;~gam zhsk1it+qF%j}k0Azcl15eMtCVpToPj{Ei|Jl0Q8nJR@Z$9vZ-5sc$42Nx^SaZ(==g zPLAEW7HHBj+r6Jho~9o_@rN8x0mMX4E2>4!8$?rB5Dpaof+V_W%C3}_T^0A_22r}A zkg6d-#fV<*Z(fPf@o&fjlIKNTdQuM+)NMe32XEm3Ei`ZCB#R{PJ3{g@b*wC9h3UD+ zNxUbum9STvca%6zVo!5JMdGTwMJmUiFai9PFBK^% zwG;+4nlsx^!-t4Gq(=l%-0s+r8Og4_wJnEitn0C}U<|!KEOK=Fd7MMLR5zbn+bhEC z(Ag^>$C|8Eixu+t)ZWvPx$gNEBC_>7&z#b%Ifa2CXehARJYc3bycI4B)4-YTr)YB$ zK$b*Ea<{4fwin$SQ?4w&s-T$h3~Jv5Od2aoTEL+7B>2kldTeJ~s?#j__O8WmZGnO{ z7Zv5r+zTF#8pajHxj_xfMwg)vkGABxAP8ve9#-^R_qRZs9$uowsydTQ*X zR6o|4Y~~zepoA13o832W2e_pFYqMhy{vr()2oO}|+kR*|;X&3!niK}4?{nwoj!xq+ z3!ZJmK{@sK5UDz2pQUzQUFCzA>i5NwL@)GqJ}_QL8yj>~{zRy$gJum|yB`haS_PD? z*!yvC7pneXrfku1v814kq(CNmT15vZy16gtlO&l#GPRiQC&m41F*3jOgj2LYv6$4p z80ku-@wnun#zqM8JA@jFI90)^3;^jd`B}2?`s6e8#$|wucjg!bk(pe@Hqp<1=OMM! zl(v;@CD{$ne7fErJtEL;sc%KKEK5?sNq|#0m#)Pp`??Tb5jC+k^%Y#=dB(~&4|R)- z0Z@|V)J{pu>3bv^v@rXGAXf%sMWC(T6^Pm*YWo>C9CWLG@s#) zL+nlMN$m0(L6+A=aU8BDDrSicnM+?2x--opsaDVhxo-rfartCUcLe_nS(0TPH;Jp< zlXYb%SeVW5jAdCt>Eo}mR0Pmd>aC`5<_oF;Dyr#8IY5AmEW0NlF|+N_oDgF3d+Y6c z@2A`h9Fb3j0}h`>3Wd<}OZNV%0aV}37wM!hT+`-Rx>C3`x_jzQmAywj!!@mn+?WXk z{RMiyfNYJ&N0D0R)>*KS`EGG^dUDULc@i_eCpIiV;73_({nYy*>V!qJ+-;iZ^11HW z2Q$GAo;&-tN^j@Sc#U5IEF5HWu*M^{sJ%yyHjWV$$ZZpv$@?X<#~y#Pr4}bw$}!o_|YlV2?zpNPDQzoE~s;%dqiRe9B+2ag6vvnK5osHGuUnkW|TfcLGvk=1(Vcjua z;g_IQnI`j2e4#gR5$%ChLU(#$IX|ScRiJIRT849lTFZIgF zVcBi74IWgs@LtsD-(&L`b@-@1Tg3PD=(L7z-cr}+-0W9yO0rB8&B zjgi+d=}j#-}L3v)}G^-4sW>oCsF-?*+6xI^ zA4!?hZXudzonZ|sNd`ZZ)RYic34vzq>89covE{QLb@hiH3;PU8`V1oEhOZ*uV1UcV zb=J-%-l!{Fi@nKSik16rTE!PGcdB`%cmoZpljQrr9yDeHnVJg{h)=gY-Lvsl5)Ws- z1^3+i^(-U%fpg|{YyPhVe*=ZYOO6y zyu*w$HbS88@pz~V`{f5oq@l7T^X&f=VdyRtE)YNmT$FTUT2|JX5CdImOAM;659Sv6 z)al%ooh4735>mnc(GaA-5US;MB;VS60HEZpTXKj-GanDU-w7K@F&o{yK_#ppCipaP z^tUC~Q|*+({)ooURYN9RhAsG#@*3#P-HC{{^_FfxBN=3Mhxw6r3`=pmwjINX>K9}g z8T1>~aMo+eoStNbVwTag46mv{EuNYUb>0z9_!>~1M2{5~4@kH=xG0X{I<%{}3^re|#Z^}=}``Q~~CW|4`} z<$s+00^r7F?>EBviIRkY&4Z{OspUPuOym;B&mt}3B(2o6Dwh47`eMaH$PK9hJ^6rW z28gDl%NR)Vd2Rxn*NDB74`*BFeXVP$G$_&XABvFQyi^(AkxCn_Iias&$3sGxwZ08j zc^dHKhG<7w3nMCxBHr(+TaGLS8{53tS>heb=TTW&W#p!cS(xp~p~5oJkz_C4bU)in zWjsGZYt6^CR5rJgzqb@K<(?v?Uanafv8HfM(1x1*wvA|(9YIhIYyygKdBXKDtyM(i z$7kKikMq@3IFqdSqb(hN%DDwN(LTHs-OZHPjAYtd`kfJ^rMzP%=LBtw-Ci!qUtk3D zjOU0y&E6Mm;lO&qwj+Vi>{=D}F-5BYLUTw^7hftmP{C}i0;W(6LSBp9?+(-wJ zST71SMbT=_kR%mH?eP1HWwXt}2F&`s{2h>d=AV(1Z~q2dlB2NR%4RnY`1gotP6;Vz zgC1;vv9h?40RetM^=g0vr;tQ_Po2A<4|0x%KO}rRjMZo4uzTk00>gg&iI{jLUiDNv z2mj(93K}>&%navPi_5aL3Vhq z{bT^wV0ux0Kzb=ODP%Wxkkk8>{$?(-crh_4Xoor_IxPs=FPdpKr z!}qVZAd^TD{Nn}j(J*Uvt3Z1L_9QbaZk@u>U>k0s;jaM3tZwLzDh&2bHeA6Ptg6T8)3ea~pu7$|~8QMNs5Hv1=t|}>599iEYqJOx^ zXK*M-f&GWVTtyPgSofIO>ys}p{2Pd_hZ4HJ35xE1dj6-YZmCOay1Cf=&$b?24X>07 z3$v_#z!lT+hyH&n_4qd6iGW!sk*MW`YP^W=OHXo?ZJ(0KIkZd6S(@0kVW-c@(Ntd^ z6X0ek0icHX^h5!=i>&7KE4w`L1CRvuk~XwzH?nbY#b%7pd|QvzC!{5TLEk2RLXH0% z^!C@kALm?y>&);mAjx7+<|8Dd*ArB<`FOO-`ohYaWoF)7&B&AzAIMzr5MsSTK|#r} z4Q`$v@r~<|z^@OOa=_%f)KjZo-wPO}$x>=a7*I49w#nuyx+Py3d4fzh1~Yk)MNvG~ zamUNWwkL1fqi48E%xk~DPEmLos)}y973bwq(bk??2E@32ZPZEvU{irRG}Z67X?9VG zBopFI0s`HdO4Rp+t7eGC@6WVmwl*r(hkC~*8F4)7OgN1O7O+#6397!D(_~GOc=JWG z5+Ye)FAOIB()mW((f+)sjP#9Ufk)}zKhVB<`MlkK2pQhs_BA6Y%dC5&T|Jr=I z7ch+wvsWJupn95lPq*P%ji+iz_Rj*8F2_*BE^3S;B*++rElbZ=wFZ zFrxKXK~)?{uXKd3b1gTxs$$*$qhO{W$!$yK3iP zc6O~AyZszj?sv(Ld0DAhK7gbg-KnzuOP3_B@-L|2&Hs5)iuwOCR9x&YxlNFB!w9WlWgy~ zvlU4lP^B4uv6+8@bvrGZAzj^E6~O4@K>cA?e`tOwzFDxl%_MAS^NIV@o4;wMqW80vXFGdF8A%{B-W%cNc?!S3!TMRZRMngIMwNyOv>oTq8&eVTPkO3PaJ|xr* zwh8dZY@GEYXgv$S0}%R;u84gVmJuj!Yy9JWjGUy)ncUxXd1bgc+XfO#wSl-I`kKrK zlf(@T+D;~Xl#)8p(zWi&L@#L+CbEjVM2|YNAiD3jKG&q^MJMl73rPOA^cyI5Z8eW` z(P6uYgf;3PK^KAdyKuJ(PYFske_TzJcMUN|kzRiN>)RvOwsLx((_v`v@TQ^{|H+p9 zg3tv|QWJbf&h@+SY)0XM1E;=1D~p0w3Lx=8T`zg@OLr@yR0Wyu@PVd%Q$VcV+7jfU zwjQzBW}ha zLscy;uM1}ZX)3Q{IOqzr7v6V7@cRX@SeHiZ7^TQ#vP;S@cSB#=g<|-2xa)6xXAC=~ zY?=lQYAgK!);DKEogoF8?znM%v}ykK+fnW@+U&fikqciK0uM@P@G3*53F|auF+HNW zJL=i=3-5}|8Lwp)mz(!l^(9$vo2*&TlB%deFsb{mpdB>@x5{;_2@z($nT z{H#2;E41CGp{KUQyqR4YOuNcU@lnSK@%W;o+XgR*nSV_dgxZ}(H+fqOOxclgxk5bL zpSAP0mB|Zxb!wF!rB1)ODs=OMhM=}O;Rc?FiH{~J09QrKx#Fvo#z|>nGbiiXTI$z? z5l>Esl32*fBG+`%Z|3Yuid44D;nVQnhQFi z<3jzR=rVwbI*#Ba3k>)IAdS_yayQ>_zPW~72`{ru58doi`U7b?+n@eOp5i=Tok*`+_K+-VrbRG=7^~=lAN^7SApGU(jy%68X_@(P|04fb(UKp0@8GS3!;#ZyoWTN6 z0k{p-A2Y?F;@|1at|-#YXK}22NmfiU{K@t9@R02Y7x=gK1MCH%WdodUyFU2Y&bVco zF(V!qIj#DT zrcsr=2h06rY$5s8mB~N&t}WzEV2AWNnG)Y+s@$bL6=dhz>`h!I{XRW7TWrIPw0PXs zes_@z_FzgvaLRU@AW8p&y7!ER>ks?AM-aXDIubQ%5M5@JXb}+vQAZFpLqr=gW|Zii zAdw&mLZXb`qjwUb8-28BF+;+L;s5OadCpnue#%+rK5N}?&I>E=EGG8ezkOZb@8_cx z1{v$l=d4y-9HDHzv+VsOXUqL@y^Dd{;c1xn+86~%bqea03Px%Z{CDF&w9)yM)o^QG zblY&3>oSHr(Q|J9^j_9<>cGG6a9$GbHhPO@AsOc!aOq~HJ7rlJkueM|P$!5x_ANA5 z0)Ow1LvX%=9xUjAnGYXSUeR!$xl)Jy9n>&iD(XDjTea>SNlmUHW=JT#nt-keR;v`% zT}7*89zEM>EOA6-730NE5Us=!_ex@-If1e<8mY22Wf5FzU-FvcHD{*|p^`)2=FKb* z_xJmd+>2NMQku-m6pwAFEna+NXYffW-8(K{dQtARqkI#64g74ta&XMd@y59t640pk zfa!>7#;a zh~hZMymiE88}K52fO{k!H<+RNJTk=2_t)SCW1CyM4`#gMe-lG?FYEPxd)?4ue*oOQ z{=Z+2BY5x{V=3 z=YbS3n*0y&7J_K(<8tI8fD@i%NbQvw+Y~o*CPpUrdJBm1l~7+*ld4m;TX(j?6UDfd}Pylu&wcz#piyx%a%dh>MuPdV~7{2Gm>$z8{R0XIDtKrQ0aF|}lH^-TjOtTq zK~`TRya>5gANkEzzCq@MuzHOyqAkP7CBvw~v+_MM^IU=+H5d95yfV}A+Y|GIIJ;i3 zVA^xZPmsnI#QX-13qv8~V{l%3Q;t_aMB`^>Ez?q7Ujv6@MFdQQr2E77)?w1+_123w zfik|f*~@(Ke{SsK-pE#L<_4#1DIKjRk9ygJ(1vb{^MLQ6daMh0f01A~3PN3O(Iv|{ zdHnbCCmwpsf3U)e+gtNnzI6Ta9eJ#<3GU`PJRD0Invj<_M^LlOwJpI$mlKei`QZ60{bLUJDMy-S0>gAU zr~38hGY#WL+Y(Qu5%Bb1zUe`S)WdvA6~C`+??({|)-ExXJAk1=5TSOT~8J2^2r6ewN$@U@|zFP{xm zeG4-^zk+b-Z9sD^^s0oKEb>}@PYt-P`FcXZ zy$*s~Jw`KuIo*z3A~etAHY1tj^*8#2Ej9Xxs@zqkzT(Eam`LlV~|noV6P3H6Som#lug*z0hnC zVzyK}K7Ct@C$)XHrc~)*|)KI}(-eok%(t`Ime|moDDM#5Nv+uQ37sJ;nK0`g*j` zn;9c>nIAZuc_-I*opph|P2LL`0#nae1dO+x3IQOrquRKFR~cM-{DvU+H|q1&%n#VT zTZf)#ewK(EUhp5FI==hq-(}!uayzXVAEZX*&azOg;bzmM?BkYXQOZL1=$_JN)S`f9 z(LkUN^%2Q=@d4`16n}Ypyi!(EhJTr9T9nRT((`CxuAjB4W z>dI7n?b$56Vl?V4J~Cu+8lz#+4!s;}8S4}iE|+T(imv%sx%7MoyXquXrpw*VCJIV*2bZ-jcM}T21G{_Xze_ z|Gil>Q)M7uZ(X!;Q@iz>!ZT~=(f18yLk;bf)K}K85v>miGMIf69GVqrYdekOy!7F*L#v zZ_FaDJ)NTNI{V~2lw_I?2;WV+W@I=`1h$NRV{NM7tRQ)qz1e65o{WRd=7Nmn0~$ zZD`%J3;4xKZGACr$@No`KQH`(j;MF8e>qdY*5>=(!46@2gLI>Gu_%~Tuf&)myn&S> zZ|0Z2sz>KjCi(Wq$>k_pLPrvq8YxF0Kk02I!dBl7-g*@HoHsauMMU}3$ZK+x!fTku zx!@+{vrz*t3{cvG)f@DzZ~|iQ{I{QWJ&P8WrJs8S^ZE`}nJT-v1tmMMbGffL2hkr7jnI$qgrL_$}XzArW!4UfI_n-(LB3;wvN;ndG)|@2>6sWeEey^sjQANE@Tx7O_1&)xzSUq^HQ z%m=j)Utown6UNz1=0E0n3R8^ey{^tZVtD;3#Bi9KIRrDxYXNv>^t5=@)jcP6PvpIh zxXT3*(LI&H(xNd(H)#g#PcZX`i6}YZ-l{Jc!PLD1A}QxMQf4f#fL&{C;G&D0wrb)J zx~a2or&KBj%4CBgh^MP2a`$I7{nie+9%(#PV$h#hUfuS&mi@$FyK&g`cG(}clXzkt zVSUZC4@^&_TK%4N0%j1cubG=)@Z7n@mD&{hvZ4E-#Dij&Cr>Pz($n-w^{HKDTFpJ{s2h>?wWr+i6m;HwAff5}*L8K#Y5c2WZ_-XqB>h z3wT)Bo76s(RO>}1KrMMd%woej5P$vEuZixm*ch&|?JQ+v?4#A$aQ*1BtyYoUiJUJ) z0UtOU^4jVl6GEyJi^?mq@d&EwJh~+o;^Uec8zl0j6tyb5zj3(XG7sFV8X(+6OTzPW zlUO~%OfkRzl2-MB+YNRL79ptok?S=rA1&u1g8kV3H~f`JT_D3tx~0+wm)$)!t}=rp z7j^jngkm5EKBCr^Me#}|Rf7NOKcGjwcgWEBP!A{<$%>eaJ0*1VhOwUOHoSiOg6>6P z$m5u04$+rpb#bi=dEb{FWjwpee!pRgsT@U*nk}fK>O;q4sUnCKIpn+7`Ep|hjF%i8zWTPt<2 zsQPbiZn?N~o8;p3xW!}pYV!eA9@#T!G1m{VE5>*QAff9)h8I46axdoWY5U5CK*ud- zlgs<0->SfJtsjHE{O)*d8U=AgQvXom_K|(+_9XlAj{Kdf0q*n-`N)HJ{FD@%aXjQA zVJ3j)(?O2JPQR>Iyr}dMHc(nW@aodA{#*j`IFm2v7bV$uI?`9~NP;!6Ra#A5Fz9Q) zK@9u=IJ3`bie4m2d<;oyaO0kV{c);U7m5L`Rj2hxT1%4W6JYy|_(`3KHm$!Z{cv%s z84?ukubJ!DXb8NCnC0R0&n+?loESIlv(VYTtiRQ3hqNSyi!XI!oDRnLqvKA9#N_*o z5X`!C!_c#Nm!?!%@vt6Q3~RNyq0mO^aOnSi6Qc}EUPaH#D9OcU!`Ohl?D9Y-uYNg8 zIb@PK-)2I~hWm3ivr6gWm*UqL5i%J6?2g<)!N7d)g`S*cU2Bo{9 zpvRiT1c>axTF^#BWRt20xtU!R#25H2&Psl+uxauEWCY^(k=|V2WL_5d7u$i?<3?%` zr}`O&H2M~unHVZhjwlS0xj>hA4(_Zim!2>}d%B$b0}}f352#qgw_|wObY#ng?`*&f zC@TKfGuSMh)aB-d*Y-PTIy^Cg#4E;|Q!55Px-Z`AJF|wQ!X-3Rl;u0Xh~H%e6qm|+ z=T=_on683kEMq~~7WSLJO9+L&+I7UB)k4O(2<<}FI0p2YO6ezzu4Cg2B8{GtWV*QW zy^ZNaglM*oW+E9+wTmKZ?>3JI2#EFX(4YQLs>(2;#eaNI^yR&q8$Z3%yWIMDvg5-Z zxp_3u=0iH*k_8>iSMSVjijh-+3M~W(aUzKcIRSW<}hxwgTdtkqI3>XXC#q}d9L?-W$m2TvN~`F7 z6*9Yyn%Ss2bTpa?5UC>x`6-RLhf!_?rnU7==8&;KpRz5dwQ%s&WlwI8zZ%@%G$Nd59P=}V0zxs zS>crJC|6+J(gBw-qR&uExsxcZ|LB{~PG&fxV%HZVm=?rsx&!W;0Fed)@NJ^&Z#5WP z?h_b_P6%JRg}ha=o~82eHByfWsY>66_&ows0X%z+vAA}|He%K~6PqnUxSO;5NAU%g zhJHedQiFMjd7*W45hi+RY3`p|26Z!KM$}po<$>oH2+`Ve)AIzy{G+n4KHvDpwdN2v zyVxu-Pb2f6ew~&=f!_kuxe|pY(Jo-hy-@Lpr|m*EI~jGsGhBW&GxLQZFLMMP=NWbL z@2grbb#IJk{c%FV2(c#~1#4qb0YI4yi0tZNl1aiZgrYGA!bhYxMv~+o+X{Ww8iPo& zXExgXsBxkxUUw}h6EBD3Om-)b8_deodyLzA(>#59>&GUc?ad=XhQVkPCXa+BjP$jO z6FDk~-UdV?_!<+nm&F2)Z~1$KjFqpFh}8pO!})??TEp)^i2NtijnBB^#fd9%5?H-) zwNB+S?@@S-3CBbNR_lK_+5TF&A9&-p`sETK^gya8uVyD-c&eftW1}v5QPd56JKl25oJv2wcBqo+ zgnmK^4qjiZ+C8zD8xl(l8m{gC!3k4lN#mwZ4LxOw61+BfDN2mR73?45Jj1gWVC^#j z*61^L5w3m%%inYo)}5PK8wVy{09*KUv4CW?1?_Pu+nc# zA5)AV7ZG#r<>?3}B*_F8&l4ZZxi-=dihGn~S@OJ!q03o(rtg>Xa=IgYS$+~o@l^ty zsdgTs)F-0;NKed`T0N|$+&cSQJDI&HS}OzIbE8#*j=zc`GYj?rB2F|tm)2fix$1p+ zi%^i{gg3#&{876#(iQkrl*NcS)z4k=PT_W%kYFK3Y0nn3e^t6tNq}(xrMS-r6M<+ z*SD@clwWGt?(3LOCQ+^$!xA7=geN5_NGa(^ok2|aO~n498l{U0xY&>Cs5)3FRP(vUeH4AFKVBP9zU76aV+^?EmPF z6Z)U={}4a;fZ{?;LAfC;9`LmSTCen>VqXOV-(coEAvZN&iFXPynpspnMo(QckU;xJ zjR5QQWwA$K`a_d)^GgE5C)z=@9fALv2n4dU!0}3dFyS3fz|^U+Ezn+Q^0IPjSHq`0BQYw6^xLSgy^&kV zFpKd(4F_+%mW<)cVZCe`oO6c3b^aK0b!Kn%*25SmW1BVs*8kT%0(qAoZ z{KxDJ17TIm2d1A@$ulGaonDClXU$>9n3(?#*{^AvZ2xbN{l700rFTxeJx^Pb-)|v` zgj4TX&-{(uH(sr96!7nXF`%v+R3a97%*q_*j981{VmF7foLxklzT00rgHdcC&+)pe z!7-5QcEl)KV(dB}n$i=+mrd<{u7i0ik{MEP{VXNIP{Kf8gwXK$vpvPkKOk=mmajJy z*rmk&Dc}tI*kciC=N)(1S5`9V$kX{-T zO3|kJfN4O6o!=ndBPmazBmvS^IZAG#PU~A3MZ#xkPwpjQDSG5!k-UMP==08evOaw9 zK75^t9RV|#J|r0x{}#o_ykcS zj&x5T6npcfn_4Ht^im$Rl?sQLp<|3(lAk<7$0_Em^yT8e{FwfoXP^bRvTi$Ht?!uJ z%2>vU0m?j63}(88KKqJI2N z*Keha4!nP8exFR78}tr{^7k{%?XIseb)Q`d!4QBv?P%CTrc%11!ZbT$xXb3wmVI6` z)$3OU+7B_~OrUXy3dsyVmU7rG^0Q?>`|oAxq3A}J@X;^2yit)@-&YUwwH6|#pLvf% zgf4;qE`EpzK-Y7zn{la|l${#z<9J0wmdD3q?U#c!pwtC*rgbLt5~c7xYmL2z^`4q& z!qtT?8Ry5$qP=&wRt@?5%qR3ACWS)o7@vPYhCM**NqU+jjzP2n5FF06>GC$Huk@)x zmuo6N${sEzkiDK1{jVeDJy?UJcu|T{K#KpkjvssZ$0H_{EGT6LhrMCOt1sItZze91 znM~X+_FrMYybW|*2b~fJY`dCk>d^{(hGtF;2K`G5%naX>*4|Zv(x@qb1>(yB5b(Tt z+v$hYCl(VpSGu!R6vuoSUf)qs;HHgnvN;O9m`-jI+6efJQn?(pf-#Xeo)GNTX5(>y zJl?nHQPS?08l6Y4%PIr*kh%>5o|T{ zBd%hjr6I-hgC5^Q8tM`FK}T@OeW~qK7Q|Z^!RKo*4$THL`s|f65mGxQzxaLWs)5#f z5Qi3)of1GMhYd09GvIDRyFv@%+8*N;OcX6ApMGjrhu49NjxG$6xBL=4Y&xGX3Urx8Qme6SChYt>^BS@-=A8`fZ24$6=P$!xxH~eqj#^5jqGz5!vY2_1$%JfTYSXsPFU8HCvW!jb2;nERhnl5KQ0O3EIV5Y|a zG)dV1?el~F|G(M`h{AZq&UPri9YA!)Eo(O5QK==%VQ~i6KjemJevlh#cmT2Ms#TQ@yvLD9>6=P{I|FzjR9%*rGI6yhLT|zGS0_IM#yvRo0 z{s;_Lallye9}u?z@GrxBO~u43x?5^a#Ss?YCq|liEMM^-sUsijnwA2I0K+S-1+gov z0DQRC(U%RuWOg|^mCMzra3*hZULsU;@J1j+QmsF1PZ$seOt-;tVRGH-v@}W#jyMeu zT~>C@)f624l00SmDqQ<|{8eDXwKX z#Ex?DTk6Zb2lFG?)}g(ym}Th>0_J7CJp9XUq;}9Dy?J@JHLI#?prpbo_|+8+W>t&< zVf!GMFp1-gQi%x+gG>M3tCL?ecyh(A?&p%_22WVgGs{O;DPk!6?E5GTZV)H(gEwUJB(L(095TY^E(HFGlyTC;S!fgVvmOGBH#DBrYF_{K5jU@m|ET zM@fokqoO_?cXJwrOLFVI;)qMOiQIck37Xe%hs;E#-XKRAeDTU$AYARu>e~^cAjKOx zL5AhAo6c%i8``gv9sujqWH%;elGYR;LJ6cpaAMVvubmcXYgHYpuHQJzOEDa~9coRk zXVQ6!iH6pd^rc3HKEyIbDREoh%XiDGsFYcMLs8CQlNHCP!2XO+fbedX4bb#9rlMr_ zyfKJl%P@UJgrNl;|IKS^QtTdAN_R4o8m{6@l@2;6fOoYrCPAXO1+jv_*BieEz6u;r z=`!AK2yidCl>r_bgWSpY=E;+D02xZ!^b;2J+wTKuVX08t884B^b>8{;Dj`(FS#{$#f=9QyC&x+gr@t zxD+A<5X%U6!zA|x)Q?xbeP|tWCG=31bsn)x56=+#{a=~W3~{e>{C)+lh3+3n8UO{J zb)eJ_mS{{B$?ps+ghu;=xzKK=G)Pfm>KYS;*RrOzCr;?dVdYfz1#^9vYK#rz zs)9A8sV%RQS*^I$)=>d41daKN?lz5AFA2YO06T}@>Y{#;Dm4UgO)pQ1)>R$;H9+;} z8>?xlVEq@=LP4F|$CC+d9CzI@ynuKLgehMpJ?V`Xg7ie>`=wDIVL-qedjTLtc)!=u7^74MX7RATI%ziPW}F!E z>Z<$*&tgRk1wYGpSsFd#6ai$>sU^o;=T+X&Ft0LepWsKUZpkJ5!l=v$0~)g!b?MfF6AH z8=~dVqB+Mdr0>U9g+?sJuC8>XR`6AGG77<) z@)Paxvw+!REOl8J-dod$;kT zSQ_nq4&~r`)tnkI+dXwPQv2yC#3u1umxg7?GUW9B%@<3-C$>bo_hY4=vn%{h;eP~f z7biRtX-HBx;j~Q8Ip7(59H~B@Nsa?@^nW{|0~eTBe=1(&m`{oUpH@)Wezsz>op+2y zq&l-fY2^sc07$mPb#VFYW7@Cnu3#rU2ML|acbK1A3sv>9^fLnNc}H*H(gt8}FurzS zB+rzr9FpeqMXp9CWnW!s&BC`jf#UQQh3yz~zJ_OpQUq(jY=llaojJ|dn7W+gczt~t zEAj}wws;j+`qid-L|$e=6~tsjq&!#tM)1BjMe@Ua^^&7QNZk?j@!iDQ8nIt>)#|g< z`OKG>LMBA%k_Xh}cp;ofK|DIP97uoE;zS&!xKvqos~6t>BMqno>>)o8z>1f`iaS?7SjcSJJSYAS%i zcEn*q3_x-O5Ed7PL1Jo;oql#QYxjUcD6AK9nSd>doXL2A@mPEZ=Rw^!Ylr%V7GpzB z`4c~X-j;Y{#drRv^DeM$M^VE032OF|5|KY(*wkj?Z8GDcuydML*>lsJywD(9a@Qu5 zlj5e(wh*H<;~CV)7keXM(kZaa>Q&{B=TDuEBQEh|cS8Ptx?Q&QHkA+2aM;&@&v)>e zWhKTQ&;_4tJ$GZfM)^nu-UMO{2Z07SPKq)6iTJ%PLB5GY2o$N4+8w%8_hGFn-fUFp zBs~SU@SMDgHesIZZ6r#An7$Uq*M(A^)>K?O2x4OMoVgzAf-~@rJ(!dDX$wjPnzb_63us%boq<)Au2B~RY>>o zrpbY(fKq7&GFG`~BJ4Qo6g`7h*Orbbbd>Vgm~E1Q<0anf{~4{PwS6#D3(YxAnNBvcX zw}!`B(-Wacig4E0iy~}KHOID~3XyFceU)SnrqcXS>UC<-Cy?1oTRa*}b^1K%V!A(g zV~zT0##QfK0n-yhqUh=*d<_a*gYY^u*Ww+4^`puCD#fJIu>B&r59#Z&Y9I^yP9#I| zZc9vZnXPy3(|)Nvp7rJ!9rCPfF~)!Id^f2mm|x6(_)nUnX7l$rA9&_|J;{SV&#A(W zNgHV*<`oJ#SY>IrofOnG1?%(7sXig)h+o$aqgobo4eDUE@|@hqVz0>3h>?=)&)ZX{=iB zudHDb+Cj6ZYsA`q?==$*-B!*T&Fm2oCwBeiiCdqO-du@D9mrP$=HqB;V5dOd6V%cX zffBU0E9Vc_|W)glPfIMSG4jjL!+ z494*p%C1#K3m-o=y4=b1*I=2e)_b76fooe?Kipp_BXLjtwz*Ydq-pYj_!c_f7_<|U z-fk`|QNEfXa95$4Q4JLEp~OkEe;s$&tAUBN0uq z99KV8On($leObYnjDfB-csEB;98LE$v--}I<~0=hQgi^R3m zL^SK<9YRjrD?V@4YfS7<*J&$9@9X0qYH(-G!R^yCk4*c0U$y|JW|*J@5e!S0z2{ZC zVBfD?+*kKzUdY_YBF*wuW(>s@eHs(U`_RqpgZgv5CC`}#7U0@BureHt~G4A=nJCXtut~P7NM?qb4FnDUzWruCy9pagWJ_m`aEO z%on~a3&gVi&^JvTHQEBTI*C_06Z$%n^tmYHzvGNw;HBm&q3~3!BwTF4IJMWT^JJ5z z+1OC?O!+x^w!THaVl-b1f28)APJ-HN!Lg#t@dB>jPyw$M*1E6gA%a*lYfnhviJjH4 z%Y$$xnUC_7n)RCx1#f?|AUXU!oUHVFgt)1Sc$2JJM+O6F1aGN z`0O4P9v7xP&U#m-O5t}@wR#Lw-ED|3+0DsjsSa$&HH^4zs4q!F`VuWwrGbw92gE}1 z#5X!Z#PNbf0bdQf@jQ3@?$Mhw+uXfZ`I7bhk*aXQ2q8ETskh$2vJ7~{a$+A$*K;yy z?w3Xnmuerqw**N74u& z@)$QK%ZGo;$CA(#?Yux7i?e{Gor8EH$LVR|IPh4eRi70{Ba&i7u1qb&!gFS;bi3x&jV(9D5{2X=f$eWPX<*;s~}Xjg{U*OLmefN+xkMBDQO`%$J(B)W4)QXmoiUT-Z= z+|GD}?jg%U#r}0$+^9?u81MHmA z9Yt6^m@!-0OGbo)2JDX=3gS_;Frx1&KuA&?`O@R(jk9Z(G5^65)%##n;ro52znN)N zJcri*fZXLGpOHcc)3Z^PNPkmR5CXO8j2bRqR5 zAxtJ?O>*0+SI| zLgG3L%Tz>d?rsBrf9-X^i<|R-3T(Vyz+$zaSBi-r=BakBpI=%vO1;!wGa|+l*BxtV z)pb_3CT@_7Rbwx^AryI2TR@xr?uFLhsQ!Wmxq(!E4{JgGDW%f#a$jMOO3J(zoumF9 z`U-JbGJ&A1K@mY!j0O?7AaKE5%_R_u>2DPklfL;A4*_6`lYCSE>cLZ&E%S0M&^tfi z_%G{5)lRy9@d>t~{p+#@hq`!_w(ED%S2Yz^<+w=NRpTg8ZdLpNg57NSik-WtmWO;fN>#E3mE$GN34M$jumS?f0UwdZB*XeVE6`uD-rpDUi(jX5l~i>x%c6zSP&3LvWI;Ry1I#}5|BXX{OY=g^;2 z!8wv?JApp77aL$q!Ij=aA?j>C@HjXOHU=PHd~K z0V3Gm&ah0WtvA?Xk(uwJxW^@FJz*CmdN~E>?VAar3)E-s2tIl6TQ*ggIcRo*_V^sD*6;*Y=cRA7WJrUXmW)6T;4PZa~2%u{g z@HwQ2XNkY_bU1jhyigi6L{%Y0wq0ipTfdO%$>mC}|H{|R8!;VVC)Mm++ag`=7R?p{FFV6@k=~C_DXe%nG8vJvLCb50v zXjj>NI?VkkIsmZoTzs5!MNx75_%CJlB~52)q4G(bb(0WUVwnyxr1YZ$@f`Q?v}LAY zGwOD6i-B~t{@p*0m(t9|M>4Y+C8l4;15|@2B_psBYDLuvyeH8?LcC_+nJ$QU2O2{EGO;?`d z_;A0PAinAz3rxPD2_3wF@^D;hS!3=r>CD6xeaaHO0IwY#rEsuYOnIY?Iuk+ zf-vBAnCuBnjk-mI2P8WmUC12xI0ewqQFCf_%^P;NAw)KrG^{saN%EH?g10k!A?gLV z{^|;1C2rYM=5cnvYxMRV$Q@dST#CdXc7{%}A+;e)hNj&51?(;%_9aOVv$W(j)6UUa zn@bns?tZjdpCxkic-b)7&>nP?s+fPq36%g0hKs|20kxaxi2|2-7e`X0QU6#Fg524s;01iAz|1}JkSN`=G&5%!F z3AbstmIE5h$f-!Dev&Fsp-ZclueuS1dCrimyhia{Hu)*z<|x zLab0OTWihqUptUpmNKT2B*Z>Y84}MCvNE5L=MNA1GG2F&=?cCv`p+RRBBrOioxAqM zg@ySmJqr00n^cj^amjF-*Q=6|M*oh=QRCQ-*mi0p<&TahOR{HyzRdRs%X_T){`@?d z2YfG>9Zg!gsljsNwV%ncdTb7C7L{+2>Q3BdNU82e;Y|k zPw^~vr5bwpWi&~EhQ357h5CB?{zWoM0?9@Ue1a>zlv%X18$pYHs5{;lZ4-52J$o&$ zE4#?)$f&sR73qmP|Edmn?Q*A2((C5zVfI75^>^hmj;in70F`3(uU=Jm{!jrruRVcG z|AD{IA}*A9=hW`1%<{6nk(G7zEPj5y8C2UWjo{vx%|msaoE%?{=OQ!pE5{!>wz(#~ zLd*FWe8BdG(O{kdQ;C9kt~KizrE_Z3^%bMQbp{0ujm;d>7O1-mU+htWt&7m$+|c9B zy_M}eaDM?U3PcLTZzn;R_j1~0@Jn^vQ@kFz%P!k*uIDP*2*(CZ&0pUD*?}4XrrzI5 z+!!$bO(oRY5>>+1-kztjcyR^C%eMHqE{T1NScZ4Bbw^8fp@TO!T-+AfMlVO9Op|Me zy@$yFqV7g$fQj-W;Ww{X37l^FC(-~lYn*_s|J@3l6qtIToU$DrdrV*>#n!PGoGIBy zG?#+o3rQQ(J)BsXzdw7emtyP<3bR2mY|7cXBnPwN0cA}O$a*{M z-s!(Q`v1u~EdeBXnNz&0zFh2GxWhT&2y+}v>!QDFvyl#X#u$x6aHZDE(CV=iv^yro z>TgLBN_ZEL023mz4)*V5=cpL@db!vSamR>1_|=yc4_~k*_i$UpRZ_tTAWwA7_Wn0$ zm-oojSHf6Id`o+zGGjx!aW(%tG__%`m+8v$(GJN~)KlvP7}u?_d4FI%be&Ki+h^j^ zo3GGnkT23wo5hJ+4=?SV?=KB!_(CpO3Ze&5B8@F?0+5jROx8p$V8r1;>{=7U-(p$Z zi!argA&ds~F5I^_oD&i2T!^EH9^%J!SSlSdUXCiFrb1E8I54vjQ?H9?8QP6TpxpNC zI^ESBdR0~@vEE~Af66lzWxk`#)y7_KU=D1CKTWNy$URyyYLdBSmL>o^yKX4ElRvW- z)fQp}i3DQWm8ky#xyqn%Q_wn*6_#aC4vUw$r#=m*Zivsr&*US=I={4Rt~9K_gG?sO zFllMPBB~HAXaL;wWGuDfaqY}oV-3h?|D%k>uQi>{=~J9nh2A(9itv9qDvZU)#&o#C zQrhKaA*@8-p3s!HWxY8H3wz1_A{q8WbU34$qjqCXS@KUj{-=zTA=#)EhF`? zv1+1Q_Q%N!jn_^#9O#!fm+G7zQ&YGr;b5S46(XHIk*N$d24x``sJs-slP`7KH*y=Ph)iBeK<20pkIgFWVej9{^r9gUCDxyXYs>@rn&Hn1k<>6!_}Z9V%Y)+fTy zR}A0448NC*Ts)ZA?F`x8_s1_>a?14|h#3#IeN*fyAlzyZ*DwlC74@HdlfcSk$zUIR zAWq;10v97VFl(3G+mb%{+G?Rq)Cl}YSg!6R52W0C9z_pSzk=~mCoc#!o-LJ!amx}* zz6>qesvb@QnS3D&n(y9+h;pM4_O8~wS#LuhF^x0Lz32%Ij*U%fhZ1u&JyDF+*&0DY z0+V%aMhU^o)u5=^Z+*^}{n&4_N&kSnG*a2TKe)F|3r%4}rB%qJR}|sj{gQi`wEHXw zC4GmnpZ_0~!k*NGzAhIoa?*?bn^mPQmR(hwEA>{~qB zK)V{Du7E?+ByjDg+^>zd2Cb3G^3~a%JV{@ifkC%?tGmT_&jTk&4$uNQ#5OR+V1LCD zyt`XWPGf!Y>AhN>AD#y8E;ALpABn#}8S(k99u%n`Y9Y4BI=WM;M#d%xanf z<(Hn3A0(E?iXus>#~aDH`WAcq*CN2be|WEcIrp|Dw&D-F3u^A?;S<*`%TC6C?^6DE z)h=XJo(l3b!8PFFb7eiI;a(1k-(ONr$wx1_kzRE_Iy~ke%ETS_Y_67$)+Xa;qObT~ zi`_}NxgQ=a`$a*E_IBAGhPLlq>~b0u3IhpJcL{BmiZdBJ&Q)~@LJr8TsP6Z)8&85;C8I^)-zDF7lGX{3h>$f? z*4<^UDcbs(^x)g?iv8=_7+std$hxHZ4es_}9^=u;{~iWXud{K2nTJLNl*yqi)0VDP56 z;BGWGlD^|2?^0p+2oWmrEBSj)Y59{0g|V#zV4y4|M*4*`6~Ku%iX)%$)hfMmL^>fmA96XXTv~eZ&v}r8XL)nP|*={ zOJdZ@k0bNJSK-s3?6b}IUk8K9>b0Q~ZeV+oH}UNnl<#sJ=;ZSupzy8e)iCq!p0UAz zeCy^25ld)qT(#t{pWj-+vNXS;v`Ev7Op*rPRxWy3fbeG}dSmLb{YE)x)W+n+E_O~nwLjJHk9ysT{R5p~_UyO?SghiFnb;|`zK zud~{WXN7}EHN2exQU2VIXx#nxRs*W{fzY+sux&O?Y&U(q&R>1yOd3lvC*%z_^mVpmNXxFE&T@%s|S78&dBdWub}=f>fSpT z?lzO zJY^uagiR@p*D;sL)^o877WMEVTZ#k$i`AAfS|Rxt5HDc*)(WTC=>NIgz+huf>1+vfv04LZ#vktXDMIs{zKnaZwx zPu1_=Om~fq(WY?dj%wV(rWoZ9a=e!Jx7%nPrS1vZ3NIVX^(vX%jR*0zrF)(m^*zM% zZi8039~T>A;%0}tr=}&p>ITSX1ap$*LD50Vhsm8n;Sc{1sJ+T=C14P$SZ_c)Ld6PpUyi&_}(5?(4Y5-H(u32l%Y?aBcmZW%NQ z3H~v^eZEY8OeKH3mZu?w&AF`3x4iz;GMm=cX7VYcjH#u)l`_4D@iQeV5AO+0~Wx4U~bZLc& zz0v-&jFKDchHm8YpmEF{6)da9*le^#J_paduvH+nvPCelWMY4M>5E;!4B9Ebk0B%P_!I?DL=J(PNc7@ zuAa#AB&dqXiYmN|P5WvY^QrsnHJGu_Ws3+;pD~~eN%!fIKypVQSeGCtbw1_2tTe@6 z(v^C@{;a)Yqd4j2K6Q6n4kld9rm zXJTViHsXy}6s{Pxe0)@M^X*1uu2&qUj-mOW5EsZP!q?dulJ1R)Tj?&L(0Xornq98Q z+xDB=cq~Ot7J``CEYNHcsxUme-gxki`)cvz*ixK6wA~J~*%#=vePa7H%m8OqVR>=+ zs4-6`Ze5D@!?-fl_S1eGEvmO0XLG;-O7;2;r*PloMQd-=|KPKglai=!NgmHW_8{p- znQ&+1O-dV?1%NZn{~yv|pZrTy{z}wm+RKCe6#<+xV6;2NaeuXo@dD}s&8!7^Wr5_u z{xxwOROI5P5941{)kcvkf%4@3$|$7Y5Z}!ILqG+S+1W~V5Qrqk&JG3vvw#NeuO}d? zFRN0wM9ds_%-dLWEN9jm^g9T5yEI1Cv{02N)aX8Yj8K}3TlMPJs;%9`%eU!AthS4* zAirB?(J|BG0*-WK_Nbpc;I*4gLXJOf;CXfXGKh@r<$;41a`lD=kad-9f3e&VY7Rh# z8PghihE&^2yW??*lCV*|c2NS=&rD8YH{X=xF4fzSQmZUiKt~s+yM;a1Ez)Vv$BC*~ zbER~YS!G)@JIRWU*08@xxA$lJakrc_BTk(Q8!%D~v>}HLJ;9C1mkWbSm&=*9O)>EY zCnv*@&-nTl2)`PqXCU)J#1y!dHA!6wroRlBD(Z!1Zl9dE^es3?-_Aw|X7NF-S3clg zCkGSs#Ad}1;W4E*3+lJf?I)hdKG0ZzQ0aCt^9b|%W%?1mH|FtlLg)4tI=3-RpVzuQ zWu6)l{sJ1hHe<(eI2CN41;*kXhUBp6C=rbiMa;^sAXuz9gr!G!-+i#$bF3tw?h8p1 zBFl}KqQ>N@d5*>INF}@~Omh1|WbbAr{@ME2Lq919b~?kxCHZih1tbkGzYzy+Z0^Wk zzgKk$!BpVY>O40=I$aC!7NKAnFN&1_KB77!^BDAa2tn%dS1@se5KM5fmklW6w(~Q3 zv^3&ON0i?zFCntaLcG%X|FA^??|L<7?q!07=xw|^7iX51C!3haTBbxSyeF)vUrVTU`6j6GI!m4EAc)L6_p3#YK}*ypaAV#N zn#zDq_3IG005%0DVhg>u^A7uNXO{cmMxseU+NP>8^}e^j3_s74rmkK6A5KTfr#I|) zORN^ga{EJZzeak&8^o>AB%Ykdi*H!8$-Aeh9)31SJYlDiZ*rmnkyc=vQIMET((Cs* z+m=Ic<_YBVV*~6N$DZP2o#LJgw*3C5ii2@(UcdXxkAD-NsA&y2$YAg7q>%v#B7#ro zhUCmgO@|7~$4!EZKZb}ytTIoxVmnj(21}lP?6tYBZTK}C-+LcTHhM<(DZNN4*OlYL zms*L%_xUoTv0wXFzG^^^dTN@fUvDQ@Ah(z!fZ@efi-Zzv!dQ_6gaEs&+iGAtE6XUr zSfWzs3Uyhx8cQOUM0MK6g73n7=6Y{ogA7@pV5Q*p*krwVzv>OUGQRtT`IGdd=`yJh zTz9<81&4pAJK0Z#^gfTsL zAelk-(v(Tw%(m*e-sQaGU{*^?OoOZ$ZNAzF7ovpkIvsLW2W(lz%?KGQc)nTuHeu$s zTOOL~v7x+wlmDh{TLR$?+94DqHIjw5>2uf8FF5_nAP&v%wv)}RS)i|yR%CZOE6mqf z{i}HWI+k;xPRNAgQk=Se9rkRep|%RUKk~Q0fQb+4(w5Gg+n)I(bFd~Z=G0LjLb`-# zakr14R~35?Ck%|-QuQk9OV7c=Roio3(v`Z!oI`$R%T~RReJCqi<{ijxQl87GN81i$ ziD-e`qva5Y85?jJ_q|6jTs7!?{eZPVdx+iJ?U5@<(nI=|xB-hl`To5R_IC}Sj@T(2 z?PTL4cmrOld;M`uZEKCc{unw@KwO}d>LqwY z`rJh}HQ(iO(Q*3dyASNAFV5bKZ?X0WqNhd*PBSof{AgeIJgfXtfBLihUGOK^mkEaE zTo(_z9pg>*{)y5rn!k+`moEE&#;g;F63V@k7PyrdOj)%70AYD<3D2s=o3mhwYt6PA zL8A~RjZ}b#Yh+-k_KOVnYMS3R=aubLcI5BE#xzvdFVq22qZK}K!U_ya{q8}hO?09+ z)7+>(#Ckz&C(XI+x5XCw+$gtJ3wMHAceawc_J>mvf8)YD)E6t|^Mk%= zOAH%wmgFhEm!IB(GSH})7;h5qf$q3(t%12!wRetQ@ZchoBu}2!{17gLj!Tzu-&;t= zouS&t4B)Gl=HGx_fa%+P9Hp(8NJGPZ$Lk4!bG^q=E6(e~lKVm8!=H(LHY1*4H&_S6 z>>VN#x5)8f>U~JgYmc*{8AD}H=(fIt{#8Q3eTx;GC7IHUUzF!hF!#|`v+l_=(kv8& z3Hs#*gl(EcsDxpKUXN$%lz*r)Vn&a&DYu=JLyV#Xm-_ucBs+8eKB@@F4Fj0NJ*X}3 z65+qKY1Oast)W4}LPSCKJ$D9wg2+e{>8Lol@L8elSzs zwLi^XC*l`$kYSWTg>s2-N%0F`m=5$Y^|D9I0?ZasXu{5$Wt*!QREq&+wH>{mvOWiU zd8!`Yk0+&3qBXvn{k#dKM}U(3#WiG@)j;~tUN4l9@z$I8J(ALO(Re2&@^Ix z@+cawTW@A{rxB-&1r7Zr%ZPNRCH!fVD(znVvJGvol8{Upl85pLmbegqyz;{KLwUAn z<)Dgj^j1?)A<~%39%i)A<5IJQ2rt;2GRRYqXgQo zhmvk&52kCTT*|RA^I`UsGch5~MKYIhi8AT(h@dX#-p!sXc;ejvE81@$fH&!pe@75= z{P(NOwAcM|*@UOMdCr1kYKcd78fhBdeXP(vO$=@H))0qbU%Z^HeqFspwt}_Sw|p-n zke`k-*EU~jbXrGUH=-q`Y$%Phi#qo*_C-ONM7plmU-dpIyT1fK7hW&m=&78lQ9U7GcgF* ziM+L8^Qc{x=p)kH{o}*Zs2B8RyW^bi(n$$~3W*OO2;i&)>hQK0HV#6;sk8NQ5DAEIv*376&V%sYYH5hvwVM;YWEzq26!by|hRi6+(GuU%m zKgW?^83*|o^V+=(#+&6(OS~L&{zw|>sBH$-fkmwDYBblCy!MCkT=T~GOV?g+4XS?u{KHm(CK(SFwQtQer^1xG8qM^{Xn0(NIQCyt=H(+>|y zI*XJ9-q^I52zZSrIxC55B&e6w-&KqHJA?wAWWe#FSa=}MKRqk9%?I+e+0mD59 zyK&f5z67UesrZP$dHMs_nV{r<2pS<{87$BQBng-jF*>-Q>HPgH>qV>=re(f$bb!Z^ zqE?A`7gs{Fh^`#nqW`%Gyv)d1Mr;u3B-FLCYF4eM`;Ho>+r?;^Vo3cp@rk|0@d8HU zF;*KBtBXnR+lzGJn}!5Ew?Az(X4k}pW~Q|!a&2=tquoBN)8J71CsG*7v9e(hwOqHi zyqf5iNtV`tm(%+c>$gI7-86nPWO6GvBU@~E<8mnK0aoo+{cFUX_HfmUo;F;A7N@^& zlMgX_VxZ|((c2w$<)6jP-rqx)Q*YRyr$^>Xtzws*_OiC*$TSn=!}GX2J*TIPN_Ov2 z>Be$P5z}WlB`l*U0E++;H88EBq2YpnX@S4sOPBAByw3$5_r7|jsH8)kY5YmVaLZS9 z4cQ+59HVh!+5mmXC*icDYH%qrUgE{7KqTAmE34pk1g`1jo9~H4&wGOg`iN`?5M=Q! zRXHate_OwP+ptr@4fJ%(C`giLHmk@?sAcSfN9&EZtuXdyqmgkp8iiIErz858z6bLM z?b3s{-{?h#3HYV*68xSxI90P8yV7XdKamS}`S((~r-%qc7$XuWCYGo=ee;xuNs(eY zow!z7gjJ-zpz6T)UZ4BcEttSuq=yutXAJ_z!J2p~QMpu-{K)eW^px2(cxoYx^2_I*I-*AAI9(vEnJ zt<}CR^a38o03Gybchwe@nJ+0Y{K2>3JzyR%jpMC(+sW#clNu&xIvj$oxwwR2sT&MX z)5Iu=o60f)~R0c4K#z2=Yd?$)o?IRo|bk+K?LtxxAU&-9ea6 zd5_&~IhQYzK~!5>qqZ1yQ9$f1cI5NGrTg#thljskoTm7R=`o=?sAKIuy!cPSy#E=j z_y5D6k^M9OKL?(u4gXgRpg4KcQ4p#44HN7(mea+9v>qvd3u>$~@Ns#VS?9Xv@qj0D zw=jDkrbg$ri{Hf=o=Lc0nKTzkOAXVW<4sn;FxY89(c9wl1C3?*>6EgaR2!G6OX)0{ z5&qw_!#!{;F!ecrssd?^9rhL6$V>8&FZcezS(b}*PM4m9@;Yc@Rk=Hq*qO=gX!B27 z)OE8ZO=3G@+AzLfl7gJU@dPLxeXq5XV2;~ z{BdR5ZT+xVp4q!=^03r`zWXkh_k^4g3-PY9*e}yd^s*2}BjJV~nuipOvA2EP(G6Wt zc~)s)P03V(jQBXMv-B94#mNT< zVs3BWq`(}pIUX3xONlQSoev)$?5vS2mbA7uw)2!r56ISJma37)cd({63{xE^%Q#80+t1T~s2k|-IZJVKPWe}saC;tt)-r~h00heZ z0G!p%wxX_UsW<>SH>$7q6=S3KO#esR%q^}iJK7nn7sTi31Oy^bLqq;Z{NBNc_!8^V z!Je_VfV)Z)oyI-zW4!EjLxdP!2ptZOQ57!;@K}O~jrvF+^zYrv_xhaWufUitb__z~ zAoG*!`&{~{enzNB(=tP^CX4n&UdV{ce^XxH<`vfxdag6^Bk$A4I zFQuaQEO(9j;nDdZPFD=qQ%~}8T3Rd4c^hJ-{G8VW%H9Xd!UtMWcgVHFw*JR&xnKmI zdfqLM98`M&(RZi1fe8QDS%4VzT#FfO8y{nWF?qB07?uhoC4>UF_^}tnfn!`-=&Aq_sj-g@*yX(Oy3aZzx-(0%MR=aCKx4I zxV;fzym0!8+sde2e$H0fir4C0YT@eJZ8~Czpsk7~Nvt{3TTs4Sb2T(zSouD^-4TcV zV(Ptr2x>#Np}E-ENh<>Ae)6>PmJ{<2J8A4=6~5c*}ki9u_Zs- zeU_bvQLQ7ZzD+)2-rd~&xifp_hODjQ^$GNcNqXg6tz+zOcr~6HNESQ++}n?| z4Bj5~<<^OS0mG*%M?~OcBl;>Gl50gfz4B{A$BaSSQ>@JYwhm8*Q*ynyN&nv6%+aGo zE60rfWFWlkwdicW!pNr&#NE3q73TAdFqrauMPaD3CSLrBDz`k*Ja0&u0v;0 ztS3%8)10W8{5!=}tzTKLf?TEe6p|Nh%%3pe^6Z>^1r#A~wqv!TKth!Xs#-#C0#|-@ ze_GH9c*Pq)Mn-n^c5MI!w;6>moiRi#Y|*J|;4Exld<(}X`GYcG>z||jwpFeMSsd@K zP`-EjL~CEKNShTG^e~R~H4A1WYj{dH0sQ8MkLbO^NXT`q&mg1dqYS#>N2#*UVH*RQ zJfdh)0w%>E;_XSXEp=uXRWUMn+i7vYg zURD=_=`O2My3{lR+`Kgu zNd*N;x8PVH92Ozb`R%KN%j>7*jLH+D+Fh!4kFALQjN3VVft6`l{F^~0cxL&l`aU+$ zK&jQeAdIO%z9Wr>9Z5x5#god=Nu2eY`R~!TMrtn=FykJEqD@fehCY^}fCdbOlI*QA z@lC1Nt=XN1n8>u+UO8dk7wJ3AnK}7tfRM@7Kj^4RPp`64Ft zha3a`wYB>`7mnk;dpaZn?yGig;w>nqe!P#1%ZoZ`U6MN`^Oo8GPJw;{Iy4eGn zs#=@y3?LsG5oHQgoME>jQv40C>7|O8+qwg2dbF|6(Xl$|`2GtAmez&cFJBPH-K)6< z4I=_ne_+l;p_cQY*1L@$qVJ`* z@z7t^xLy{lht#hvni0=R(&h!BPD^?oZC2Slmxui>K!^+pie=d^r{2&eyIyM>bS^PxKys&S+AX5 z2&D@{7u?s^-V>1$PE2eESO?*`v-1W>8R=||QfynoCEEkt4- zYFcRAB~udq8YIz@jBQ6%9v_t-fm=TnqxXjxM@p=*Gs`$_YrYG<_P})4H{y>chFVBa zJVbb38N&tQi__#QFa@5z7&s!`*~M~nb*)V>UtcHnNwb<>GUulJu^sq>Zz#mhnkLVq zb&pTr*b9_HD;lEry)^9?`x(RDUo*!TN~NB>H#+2kZ*vEI4nrIX<+$o-i$vg`VNN)C z*!^#+5^#VVi5)4&=0BTz93wp4bx`*CoO$r;mS>Q6T{5lu^FQ(OjeiWa?P#ZdTLj&XYL)@C zLHfs{2lK7c1N)tTvS8uhi? zI*NGjh2Dg~H90116x0TZ58McQh8B)*xi%PF%4U3N&JflX#Ut2L@m_CCiOp<1vfF%| z3>nD$;uPuGUEOf1l;`FKqk?_)hjJ^`{C>-Fd>?mha;icti?HpkP9kR#nb#YpdM_Gd zOsq-~QTFMjjrM}o;ZpXhT?xsEsyAm;sEZZxbVLS>bIEuBv~p&R9ojxJ2!WZ`f(xAj zMaiGg|Cu7HW&?>v*_&lk0(z3&TARzx>YMrE#-&!&EZ3@*R5FYaIt7WyN-S_kp4+(c zY7(gWO9z58o5Toi6M%U@@8rbijD2qGhrJnp4-r*@w7@SSCV@8cFI(otK6glB;|aPl znrX@>t^N9*2|>uS5}yk8eC~VvJQJL8p_OE|UQ5yoU=?rdBb?d=r zKx{vY8G7YzzU2Ygz+yMnIGAyr0mQ|xiIjenKjY;j-T2y-=nv-bdb|LIe_Yg`pyaEG z2vTFv34uCEq*rz3mPGyVN_p_jX>;$+(-Fj z>hmMYREEM_R!)4KUJX5r;u(V8k{RwV!g!}xdg-u z#wlezN`7Y9LDJuxxn|AGKcend_^K;??9KgefadCo5fA-qGRJpA5f;RXnpUV~qNI}s z2|L{Y#)m)LXex|oVI0@G*V*cKrqG3#1(>rafnIyVU*TL*2ohIb;kTFbvCRQaoS3VO z$DL?nlry;1wX6ouj@>9ZxZ3otTb5VIeB_iZoEqodDb=J|-)T>QrpOF11L`O_=3oC^ zB-{I)Rv_+Y;0I9D_Wc`1tip~)D#E9&V5ShMLvP5S=_DTjz}wt5*U*?Q#<*SGdPdl- zqOr?}6jX0*eZ|Ku`|G?ZBX{z!;!U8HzZFMkS-?c?%Gyh5SECz`{G4~{d%6cd=t7gP39d74*#02W z-=X?QV7l}7fi{)hKrpBKvV8ohdnwJccUeAsPPtFFw+U_^f$6a`+?voe`}W>UdocCG z13~EW%L%FX1IchjstvLo!!d*Csb}DTk^g!^tS$>#lzE4jR0HGJ_V#SEgWQKh7tB{V z5yM5?-yy9a3aBn}CbtedWR&#bS8;RV%&(of@pcAj1QfE7UC9{*SH?ncPhrkjsmcfF zV#D^v_SnZg1?2o)ZEi(M4o-Wuvg2i zL`Du2Ck1UXqC;2YQW0LPZYR?BGxqcas#bWHQ+ZI#nB~E~)tU=r#_K@ilpzv}VY1-CHyfBs*-K`H?AW-TwW24KC@(?ZVl5(%x z3=7=Pv71lopcDLic27$#iO=PeFVB)9DSGY#w`6obL&;)QLn^W3o+RS=EJwTAFHd;1 z8JM#jfYa<#GNasIk3fz4L;8VLFo7-3UjfLw#S^w7C)rQd-H0TR$K1Oe6qbIf-m^@= z!%O86YEj+rlFma};yohn_kveEB@|)p?1UFdq?EZN>H^Wj)G$}CCHVU=Zt<;ZEC{#r zY}>WF^<^Q_wl7<%O4$#rn<)3`A<*b*#do?S+qbx23S=Afg7CVExc#B>hoP zXi;r2{g02IcmN-o;rr0M8nb@9#>d_dSSj57mtZE*f>n9gO#Ye|}y4#B5`&rgSj@;Jh39$jX*Y>) zBXzTyXEhXd9PG83CH{?}{@Z%`|Klg&KMVh}|4F+B=wCK9a)y8jtd`;=F!lY~G~?l3 zi3P7;Y08(IEO`dDVXYS_1SPpIe3@vTM9b2wTTX)8-hKYSJacDLuF?jpJ3&&#l747<&CaTb-DeTZH!;< zGXYcPJ2<`otYwS=P7`KVxU-El75Y*P1RB?lBVF1+T^5_H)8{=~J@-ERxq^$&%Y_?1 z_( zv}r(C{sSr_j$^%-A5B_$om(|aY}3>G&+ zhx%f*48KV=q`xbX?xweWytkex;V+1&e9md4O^2fR}<01#L1ZMzBS>?tlpM_(W4REPKQp~@-Yjs zD2l|{Hb$2A*)(jLYJP1ppXKzM0VAodiFxjuA9T7rv2%0#^>d)+Vy>2JMwCR~Fofu3 zS43xpJ(kfB2ji%_x+hc^DB$;}U#{ah8goUL|KCSw$GS+UM}ic6Mc*jS|vyfvJnLP_X9B&RRt=MQfVIXT%J_V#v`6hEdJ zba%fOtx3(+-zJ=p`>R14tLmuX_y@1}N(_)zIMpYxM7rdkaH{@R>8bC=qz9y0?q?*x z9@Jt@?LT8Aqx~-J4W7J{l-NbUo2+Jby&Kq#ZvT8h)&Vcm*lE@9$oR_@dH$jDAA)l= zI~&{Ri;+s8s;zo?0gV{a?de>UxC+V3vK_x7|Gd+#Q)A_==C61L4!`-XPK}ALMB8qA z7ZsL*H)cP%r?&fzc#3EE3?jb1cv|zAt9qw#?k@-S=pn!@QW;WL{T?CovtW4@Z%|9F zJ-xMhvh|hmv)lFB=(p5dvfT%%e#yT8dkElHwSX3}t3H2|$^87f*in(6EDKV=XhEED z{l1qIl-$P#)7bGqUcCBUcoMwI4sKe5or{I(M}rpQ020Rs3>P5OKm}F0xd>e=3{l+g z_9b8IWq5Lu{_es+Quk@C13!8n3D1aNd>L#rHgx^O!?OMJb&++%EXnbUbx!vSRF01x z35vbHfdYvP>rRqvDEDxE_ikfl_ubnc*~TWuLb<;_gY<4@x0-djahkSA!OH5*-sKI+ zbNorDU`aO`9r<6Qvr9eu0}t2wBRO92)~}1i4sG(ZvfiVi6Rbe|NxzpaU4DV6_%DH% ze*8&`03qVTgVanPV{OqW-Hw|%Y(*sT;osC}Dy6M$Ry#6(VxxL(VtK|X*}O6~8SUIg zq!Np7!(?zSD@R)l5EA)K7WA>-h;XhrQbK>vgP&CK*~}wG7Lrt=2iwtL2CV7j2=-$8 zj8c0?;+}1~r>8r#ia_0WNiNNFn4@B;y?#SKJTi>m zK=r&aL?B2>eE6z1kl?x=OCE^U8ZM@T1){)&&;=8Wd~<{CpMtmD?a*vC16Yy85wzpk zx#;fa5-LBi>dl139ildDGv?kPn6lOS_Xn1Xhv3il_Poi~()7;j75tDqYl@6aU-j2` zX7m+uEaQgR4!i*+GryV2H+%iSck|IbNGew}?nih}QElv*^(G-U8o3qk0Prz_W8iDX z-|J%~w~6^jw?dWZn;O#A>Aw?6Yw}zc5?)mvt8AY!xzE9j7F$0^A_oy_GIc3-k1(z~ zut7{(ze^5eM!7*JG#O*OI)#J-cg0xna15Am0tF#FJpFvA|D!SaZm!aXNcy9Jv|i`H zfj@dK`${%vC&f4|Pq^e6y{-$%oLcw5tq<*QCWNW>Wu9YZks|+#5lur@9&LJknyt-G2@omVw)&*Vt-I9%mSc^Kj5xeQ~%E z@ql4~Xq?b~*UY7mWV=yuVpP_1MAUg<1!(zK|I1nUPe0*vk*f%qz{qv#P6~9P{k>c6cGUI-V++8^JNjHEs`tsYoesUps>Svc!8Ym?i&RnO{@SN zTTCa}uZ7E3*fa?8jPa;zDGQw^mC)rFcPbZdW7;z9O9RXOD!otAlD^8T`6t)lE_|Xx z{XX8H9s6dBwQPwNFX*#YbT;vt^s9qDDNl?z+vvL2)zNe)*?;iuAj7k?eR039T_k=@ z#fkYbzZZjC#%T=kOMSImd_WPIct;u~MmXhu!;$~*oY@Cj?e6q(Y_nN!5&I{dl@xvh zU-D;pyE-yQCNL;gum(?t@veORJNz-ViS3lY7i&iyZHjaKJM)~qPQn>Ckv1QLK1|lK2^0k6|m9GhuoA`qRfla|^je%L@-l}S0h)r)6ze{Y}y6>Jp zkM$D$x)ao&Sh*LZ#IO4Mz_cRjZ^otFb>T^mrHAmSRMHYK*gwcxcACd&mb9C1lEM zPD~*(Jw{aq1*iLmz~dmsxp)#MSf|mGN>|^-_N-$9rC1s0N%#$z)NBn(L3#^oC-s8F+PB+=iOV^<>9i(9mF6IQ_1tzEv|79L(D7nXDQ<^w{KxCefhdSoze{Dt24HP&@nudsHdm3g?a0O-~#bo zwtGW9E?J@HW~ZjUrDnxvqA|)b z)Uf4JXyvd22ZkA*@=EQYr{RkkNQ?HX`gbIq6EAF4+MVO6GXbu7jJATb#-l!$e+US% z-A}F?@Xx~{E7l))ROr|IS(sof2lo|TS-BE0@_7HecOhiuD>mB8jDLiULyw_ohd~Tb zjlpbe-(v;K%)_Ou;bRgh-#vL*K0POuXm%GDu@3sVRBzME_3JV@*0yX~x{=C4pyU|) zlG`gF;3O%aPk6~ib&(pSn+qjI!GXqgyaUgos=*Gmm92v(WDg99VK>=nzlWdv`4B%d z_G4{1*>0)?Vwi291A}kz;OA@GClejt0$BzZxc_P3#zHtVog&raE z(}DhY-qy&C!uR;<8pIfv%;ekyHu~YOKR+p=tN^~^d8N|8cqLv3TaWsOAjRfGTO)?d z!rS6-2E@lAqfG(0#~cMjlCq73*;R>rNlE5cs362pll?IV*>9?xZpt>Z_4Y&V+2iDo z?kPSUrqN@ETLHuPIQ1h9f2hEd?<#d~!C}w2uo#o$=6i01$ z1n1mvk`%Zmfb~T8n+HpBA!E(LISg zD-92xC-SFqWh(FGn0y14Uik$d6fkZPWn(wNl~2U?1ZqN(_u-L&;7jCgZ_w?xi&7e3Z%D4YsH1&PXgix{UXHWyMI8yyR4*isiZr>Tr z@VhR>JiZ}Y{LS_;Sj*F^anR|4j1bmJ`XG}$`jI$3H~$xZjt!0< zGwvd|LjY{#t{6>B^UHv?9!c%}HG>=l1hEf;j~alFQv9?Rb$$E4O$yp+uXVY0T_#o1 zlB_|m?RwcEq~}#1`=sAVmMrbfeojc2p0mYw+!^{Ot&iJfqov@HIbc*2?|JEkg2#gIKbkQMK* z$aBZ@5E}`Qheain>TGRV^GqtVr>q9{sebX?qW=*z34$$Zm#DE`wxWH?N^j;bW$~<^ zi)iV$Jf>o_42_sAGCI3wZe9pud z)fbw_(-x+13i$OBS)k7hP_om=`I`)wKvtGCK+Y;?Z;Tyzi4 z9O5uyD9hok6qGTDUat7$c|}N{>puj#?XgoAZNLD>Gp>-C5!~vi;+g3yvd)OvNDjQy zHiX2pi1B+5xl{(8I8f53^t{~teZ`bl`K-h2RV*&yMmi_0oKHLt9?{Qk=`!D#sOY#a#GD-2_`9ap8S8oSj{G9`dE zBni+tjiuO%WpL!5Sq~3G+)X)$E!Z8 zqqj_oqQVy%uxpo(Fd2gYoL>A3ka6C*76bUFuC0wt??bL!2!r1he7lkk3i_jG^3_<; zE>NTG92ngK(i0%2BaV^vh`S5w!%J=C^zK5R4L;6K&hBn24)n{?#P06P{SgCRGsb`W z0AhzM1@MgD#wWJ5Y!S1Rf20SRBu3hhOqwCW+2mJc#hMxq0b}Yk7$u5-gmwmQDjVcv zKCUB15g`jy-7eobbX{k2uilUC=8|bh`r*x2@z9=y7NCdGx1GCqme{>=VF6m-?} zc4uRD6XNZSMSS2ICAt(WJgrKgbZuqK%Fv1SN#Y z7(fky8;y{`7(myC3%-o(bSbFjyv zW12HTsHsNF&iv!A@t9PyGH6(AD>>As9{;8xb->KT+IF z$xdWn0zlXH+esAst||kmxaGlP@hW;Rogs~ffSHEXM~_V`HbzsU(EO~Jvd<#}FkHV{ zD23`;k?Bv#u5LVPryD)>w^7@Yb^eiA$CRy4@_teK7h&IRyb2O@3-6Bg3`2g9+&9cH zS@aTWyFkg@&ypKYRkR#f>+U>M;(F2Bh$$XAV|xi|KdIRGl&_jsUv+@UFW%M2xwM7y z$?AbQec3786u06IJ&~Y9z!?G%ZY(g|eaSs;)rBcI0s6K3BA>-?*V)}3e(tNqbt=%_ zNAx8bb-8l=@zcC_O9x(?i`M69;hUD&*&H_>C1)2Ul;X4Q(s2Qn0^-xzxUF+oF^k4A zGuchFn*$k+0Vaa!OSlm{1T*wlSJ?ejrpa-76(9DqU`1ab4*E>Q#Qf>Kdo-ldMuxDA z|7x~yu`=;5{_Tu3@!w}LVAj??a%d!tzHP|L*o&Oz`s0QmfvWuV=fBqSGJ+rH=A z&kkn?3K;iD=Dye-u*bJ-JsmiC>aTJ&*u^*arw#zb}q^6g|PKN?C5Dib@ORxIeDPFGDlWxr; zAKk+$jX;cmK5)fS)$LmM^JnBHkQKoG5}p<}^MO*gnt>p!)as?1<@b8Cm!W!DgHgv? zx5%*1+_cxj1?4hu5YHfW&O5gO(}Dg6je#yNV?`3!!g!40RmNH3_rB{^V0i)IbupEL z8&GUSt`uu_bGlPPL0Cb?P^rc^!6Q(kNyUvQ2}yLoH{E%44xln^0lY zUj0rn>#BbFQ7{K*qGK27UH_9?{f22Xdnx+^>;zoVu@1c3V8j&)K6FhaMRlNzaImr| zN#B4xH%I^$$!dPsuC6BA=cfcu2}Yndtr&%|0^q#`BONO0zQUfI>q@PCTq8-w@T5^B z#?PDJRar;e_14qZLjnEyBjs%%6Vd=FX>Gh=_gZ2%RZ}Q zF|Pjmekvr@7X!c|jhIKTQ}zfI+Rb%yZjEf_q!_RuD;(GR|5@hW&$0o!l5I}s7&*!AY5h5ag9wi^)!4)rig|4GjS*7B>o$h>-V;B1F z6fNtNCeQ`&7T)tKH*>vG$>3TZ2GjKdm-NU_ouDMuuqq&l_wt!$-jq}h6I&AdJ4-K- zlmVxid$Tr&!1#YSwiNBSKC9YAEfei+z@Y`2#3tzU zRCWya)gWj_u?~_D$qb|jguna9ffEqlyR=8vUnsTJ`H%6%=58_7{2Va~%I;)W;?#3q803gcWFOC7z z_L6$E8$6dQq_#O+*Owg(QrW1RWo%pVkA%)QN_OmYz^98ru$S0kjEeb!+$_gYMp$H- z+i`#{lRTVI93<*JukltkhW4p0>6eJeHP6fBVx0`VJG{xlnuLI5{jUZh0CAd}rOLmCim@XkK^GN>3y4cVzYcH1sy7$M2~ zo1cKUXeTAtdW8Gf*&)FhBU>78-tp5gRHJ_@og-rmo(Q5>wGOGI=A$&G5-ev)k$KNo zJ9#`ZDP(Yu3ttvRrU;)YW`~6V?Res$3#jeL--EvS{ceR>+SU4x(*v`!T_U-CiTKX7 z6FXoDzPSrM+sQfn->7@CHn&uU{6ogG-}vFN#Eww0zD_F~X0V$dFMu{A>tDYvp>`J~q5Xwmdh(Hh;@hJ? zLu^=}EOw!=+E)()6qJA?*MYkYjHe?T)AO#wqW_In-jcRORf)it|4DF0w`$3r- z=Wyuu+tsy`ep}*WTz3)D02~#NotcHIpegMNpBatuXbnDxOY?u&{_aJ?H2H*r-j$v? zPNec*LdC!HpZ{Mz|NQzdqJ1ljf*^u_2veWBx&@%98bjz>aB>6kBiD-LhDBDA1?qa% z5Q-#yl3WYSrZ5A{Sx;3EQYnF@e<^otSCM1t{8Z^{l#IOHf!&CK=NoXKFO+{f`_d9% zVdT>zVqhQ~WO$Gt3+Y#9t9hvFwOCUu6C>~(qY7){cX~-|A_<@U0}OA6Yv6sJ0QN>W z>Cn?`Y0o0usxZzV@c!;~A(`-Zp*}y92R%guLy1$>1XOZ=4I|7P*PR9Q#fx4x82i$u zI_c87mK3Lq0EA1H-6td)aY*;We@O7c*9h}HKnKtM6CHh3N-HSratp8t8d~Ow$v%|z5O?2>2;r+ zSJ(8e*R>?58KZ(h<_O%K5f-pyHHq_e~>zq$lolEP9Jv5{@Mtq>RnFr zWb)5nwqL~opiiaUizh_|ujbqN>c}kDcaHVFeOMgd_zn#6K9Rl0pF}UFf-ntHfiYpt z`w_Iz8nh5FDDsE!x~rTmd3hZhIP;W{$@ER3F^~p;^hN z#8+T=n{&|kqE*>Z9yd>x+Ldpp&mPDmK2rw`&jLoV4B&Tw_tGNxZ1o-Kf9`O2a|sB& z=rlO+V{wCJz zu9A7ZNiYq?8z2!qlN>M~oWx`_QB0z#^X+w^_V%{mxCappJFB+EM zjhx>%1Q=CU+_gA(jGDF^DpBr*%^cY{!RGs=(M6r$qk?a#8omouqGFo}R{ZTVEqvlP zqVi0!O73b@xnd^Gd#0RNflqzKcb#cE!-g;kCr7sDgj??K=E@1$P|9HwL~BR$Oypm) zIzzFM4>}&(LbuHXZ>Rm>AXDfgKD=5523^@W2I6hp`O+j&C==}PAUS*cV0aWHLzdaI zftjlw>j?&Ax4P{<2=&_UtLykooVxrZ_uki18a;lxYx=@t-z?h0&rMxiJNx@W-M1$k zPlGc1z&vQYGG>x_@wcKMWx9X){mV~QfkxuKvZI3b2CRR^8WO-HwY}iNb+$&(;=CDt z5P4vN(P_I(!{EQvAIlT;OEUWO+0Gu;kJ+7@d>3|ej;Tl14l7`X54zW6;mJxzXo9O} zkz#G4(um(}_3%ulHfQR9%=Jz?Pmi!RP3LmK(7%`9Q(e3i2I!g$VTPGUzrd+~HWUeW zoge)uGE5=e>#Jj08szO|6w8-ftV_Ri{9CWCyU$o)lCn&7oSPPM7=Aky<3?cj65_2uPBf1L1Stl-dl zpSA~%LS619m+$#!K=*Mg-JDn7y<=&Z;VW@ z8xQ2aj+S&Vy=E#Lz|fQ04Qr|URj|2zFs_2~Ws zi6`;-p_%`Xa1kF=DW?0;=WCRCmj&m1m-1xhNa)OSIFS81(wAEh?(0jH1I)}CHvb_R zy3{r4?D{pMiJgqbSTyn8HzmLQ!OFo)v~8)Sh!o3 zwlBTues%6m-M*v?&aNcACNF964y(B__c;y;RT_jW`f2^Vx(Ncri|eo#-7?tKit~W1 zc&9r1Qx0o?(iqwjE#qUSat!9tWt)v-?_F#Jda}3X+H!F=sK16`++m)F#V1&(X?!KE zCEWCvml{YK!FdhA^v${QKfFg3UtHUhV->o<{p}AO+%KP@#jM_lGs-u#KBV{*Dmy@% z7Pi*Cf-~QjtH2AR^4c(PCTQQ!-FCVn8>7-CE3S{EW97NUiDCBZW5ZNjJoA000h#ao zS8hh-t7Wv9{iq)lfBZ~u<3XDZ*K5;{3E|3=2+b=pi0v-CzGawB_gQP|;VzOR!_9hB zFv)(@*?Cm(>`wJv((`R#p(g#izJG)z)5R~kpeIahaY{r1MGe*{n%GiBw3E!3kc;yj zy}tKM%wZKJN`-?dJ2{Z!2u8drdNwo8-yk5?aRwd9wn>kA5oPf?G0p(=5i7G+ z74m(ZJA{uY_!AabMAVv*@(+TrTP<7CxN8=Dk2OO%Gu%p*4XrG=*5(BrlC$kcdq*OG z(ZCQQJnt611gmS%itTPdiL6t%ko8?!b3ZlB;4>Dv>vil$q}6bu3jhHr+rDWHyOHs zw%uP^`0MAMdNZVU4s4dqHbiolrf?|HJu!fK8DZa#X1 z$l;M${vTi@_? zX(hEJRGfoa8^(*Nq1aoeC)#|23J*+)TV|xm#63-H;|?AB4HdG{dnrg3E=!6=IQkRl z1W+7<2%x_d=TI^qqvrB$=YlHQGq?WP>hyKz9I`SU;Uu>Ehm@9m*?qtw*{jKvaDB*| z{=6ogN>bo%M;<|w>+hFdQv9%XFoAG)xmWlgtXf;})ksYx>QOkQ(7m3jnnfVxSVPdo z|b~9zl-Gm zyf6@`kdyn9D5)3(jd#dh{P-|%chVTUto%IgQNxVXZOYb3m$oP-p$!4HR9CzS7J)j< zEQ3I4(CRgbUXoj`TP@*2I+52l^elwZ5^&*EwkkJIAQ50tH$7ez>A&4q6Wh&)z}`21Yu7bX=HkN@ zAd^bg7|Hvxul`6Toxc?cm1V0C*uuH$BQDImDXjhzjuNy{j7$(d;X#ZIF4IN&Zsfl!b30x^2R{C zlp$TM?`?)_yd({+pRHR(S$s7Z`6YxGK<7>WxNa}~SbmCy%Q1+eq3zE~hN-QqVIz1z%(-1ErDA*B9~zl)PKvcZKpSlozf%Vo)DT zff~ps`sc%^-q`j21Deww95||}waRSfQU3D?-MAw+V}H8oO3|D@wDF9=UHFl0Hnq?Y zxH070`hSNt`?1LGUzzb5LaR{lJPXlCN+fMqt{roQzS(k8LmQg8P))j(#CIUKu-n)EhvW>H7ltTDcVW-0gER29FB3O#QHtD3y`ZUsfA3TFxoe?Aa&*?C-*2-lgD zr?bR7p7(H>0hlJPUJlTgPuFErlgMbpuVYePoO}Z55wBln10J{`?p5FQXN^BQRZGS< zZPr&6(NztBHy8zco<2|AkB{hQzFsM{#DZ)giokibkcfl)gGrSku}Ls+a(5oi{&Qy{ zn%MYV16(Vt5W>$yT;oECS5l5eX<=NV{;#RsJ8UDJtIH6ZPAkc%U*Tv0RpLn1WP zf7SCg|3U=~9He0gY$htH0|y_@b_sjKusZr~u=I2Jo931$PFX#})GF6>wnW>=beX#7 zUmbtvcMeqx&@sCEIW*@JJ!|cqe92IsqEj`z4GOdm@I0;X8QH=Koev9^Eqbp*i(EKO z-#Yjzg@0KXsP2EYk&vGnA_o(izoN$fIqJC}#`-#%<}Gc>G)YPn{}{cN`sj0bW$|Ze zer9Q--!;QEi}R+JMZ|zFuow7XG_Q&`^{(PmZ9m^*p5i^PJ{hn0$1oXzILtd9J%bEY``3ecqqp|v&(p3wTu5BsoY<8a@iY+`tc$bXtU zFQ4EeFjE9xfY$|%)Zo}*hhsnBQ@`83vP}FXv^w^U9Vso?&_>M_S6EFVxbe)K?9Wgf zfm;_p{1x`*@=G_zUbo;>W#x&gDnZOWOTvU{)nV^lhrl8Kw-3@5ZGeH8WUn)roQ42j z?h$~9?_KBuWZ5mTkX4aQGGzOO>Zv_mwE!5rrU^&DO#K30I=92-<4zM|@4Buj*gEM` z&UBs<-47Y40j&3SJQ!fh#$?YS9N7-aBC8H+_*-65(9J9_$E@3*Te+_3yi=Up6)U~W zK)eEI#=iKpYN+3!rQv6$<)v|W>F6~kp+51js2$=={lWd!~(s z;OCvSiSbp9(+_X#-j{i5wE0}@FX;4zcS7Bsts3YCOtpB$K>auKo=We_@JYu@D(H485Fqa$rMXoL0&a6L+kXlxvjISdE+R-H!OBJ)xd)@uydNrG^4%084{UUn~*1D^)Rs~wD1D@(4_0ycMH7yeqi*B~=EoRjI@;sxofFb` zZzubv8QB(q$ZDCXw&!eB2DI_()lCCvt2hK*Zd2G1g z%xDdeKC()n=cY?KdA9%TK#HwC6WNck;vfMpJ*{JxBW2ZEjbN;I7>o|Zu$;5seZ#=9 zc-K;V9!(RROwl=gEh!~(#Q5V1Yw+pCY~*z(d0Z;HD(LT3(3vBIcl<>_Q#^mPDC=5(qz+-%M;GGEwHyvXO} zg0x;Q^6NsTl920_7=Xg#AK#SP7|i0@mYnbPM_ zdEdi)7%Eq3pY@#LnUdZx$p;e058aallaOh^l2=6p@*)wVo~1x5rd$qKdYQ$4Q$+x@ z<~_8Z*YWBsal3UIfy~#SZJN&XxS3bhjIB|IG};&(GyOwiDDb39nu6!sJ_8TF9IK05 ziw7J*ARu9wlqY1P0kz8;dNC^r4p}zs8M{YDRSCU!&r58hUF?MA^bPK9GoN1suWIhYc5*W39GoxxzD z>w({mjLq9t$qk{Yhvd9*DiM?F6aMQ>0RX9vN*A}Dq`_dhv{O~6&%Bt$w~$jKy0xST z2fkROHSz!xXZ@`w#An_R*>w#-st(`Bb`6f!$G<={H@Ni$<~?mqca&-o>+ssg2xv52 zq!VOds`-5L?ILPgN@Np zPNs}IBn(}!K!W~n2=?1QBrnIoq`fzn6M-C96eF$ktfW`*`_n6m`1vFihn&bJGK^7p z2sPdm!;a&}e?jYDcX#F}gRa#Nax6(whTIp_U0>d`mf3gj7dx>6uC-lga7;G~PAU}k z;AI(bD1w^zkF!;1dp5gVkK%vn@SXF32c=`AaE`$?pfh7N7@-CDmcA*=iFbCX3soz& zZ-NB*^FOlj6N|_}UQs1Lg_|MH5m^E_(eoLgVgnr6l`5&wAN>coVv+F;$&q|FXRs1; zk|r+$+dOdeq)?N?c|X8im7!_I$1PxYOM~kdJuMY11nm?Rou9|_?J%n9aH(yC=l;qrd6fXKW4Ox>USA5GxqPWp*bR z=iOs@Ho(9F?SVp!epWwbZm_!H`25A(0|C`XpFsCGhrK#+CsO_S3Gd1d9snOoOH}u* zoty8p3U{gk%PzBTBU~JLhSaE>&kJ3DRnYPk-~jn=KlO?w`P2rVDV$W4=gm;HQFhaD?7${jkeTPBldyQ;9%7Gk*UFI?%&xhl?8WsfD$Xl=o8TD3Rf=#a$ZXl6%&# zz50iwg*_QHJB>=#?wpBg33lf2V4eVFOEcQP?I@J1mR?tUmwnJlK`?R;k-IEC?55qd zTj)_Cd_?E{p_1UDrXLA$WvF&Bm-+xHRpf!*2@hC%XOKjB&ePU4A zH2{oEo>*Vla9+T_(J(yxtcU)Ip(NF&A*=CIU?}*%^+WZ~?0-=ie_6rf;n%^gfc}zA z6{c|)KL@9H)lS~L?rk=-jPuHSk@BK??wE8nVoX2P_~Tu$a>!i%P1s%B1M8n^GFY6| z6%&MNDaT5m%YytniY?0&+so#tZPUXN?h`Jy+4cX1yXfWB6B6_PX^U$zRbg5h*~yr9 z?0N$vG=v|TcAC_L>s*tVZ>linjmr4C=A=3We=8eW&__Y3*}=gdIONHS9_unO0+Z>_Xn0TXhO25sa zZ!g!~64IErDRhNPU^FN=X!pWAK}{Gf5puX^)`YEE{ruBvTos&2QCTjWM*rvGtNTdO zP<64%-^H7+!N#37K*IUPjOW#D{C2;smTjS&4idb#;hxj zd)(oNt10Jx+N_Vv|LrH#JY8ShMz;e5kctxM^sZ9Q6Jw#KzdnvZR!^h{wU8e@t0{FJ zVFZ+GTIFeu7Fke$&Uf(OGgHZXDHdQG16uGs?m=Ms?wKtM=o~PYF!t zFHLm4)y9rkJ@z=q*~laL%6LUTR#J8D+^p$`#Dh7BTIl2i%faDDc^hY|j@i`dJFO`~ zshSSaH(1NK^~XK7c^e>hL?aw%9}q}-Z5gnMX9My0+gSB@i^WEj;JNeRbXL3Z_MZt? zo9{e%vWb03%6C#6VhuVzeETM=uSP<$YxwF_H)#H^l!$w_=a1ZNr{U8WTVDKL3zTap z=xH(4Pei>-Vh9f~sQcKyJ$TnXTE|lbYFgF=MBi62cL}YSl&~JJx469t-}T1iSRxw+ zl+X@bqptY$dDaf5DAr2+RBmMbwLJ^qBCRb7th$e9r9IkmLf`Dr_l zB+2(&atE9dD<+!x;owq~yidG72Qygz`EKljQn?Rfp z!=@%5=O+$_e@IlBNtfNsm)<$-zOo3I$I%X~*X2MWTq3VS%~<>ip!sq=c1KTer^*?3*;<^4j+ z;DY8CcGSe9!nkMYJ^y!_8r#k~-IXj%AFs0%0aKfM^1CoG|kBR5?`1|0A#5S4mZ(lwgI_zuqfODI~v{VKf8a)I; z*kV-m`Jzj-pRNAKI~rUYIMzu+WO7T>_PD%C8y$wx&jPgy0MUEsiV_cWlSEm<2XfB$ z)vQTcvu!l?e#CDs61tl0-@S`~WHg%Un32u=`**JNvlHg2!R;;wOon5Y>upx=Pj#fS zj&bqb8>L@zc0=5oWfU|3m!yaHW!tAkmTXX2E>64AtKl1053lALxGEzCixa)d^ z=h@CLVy2}ZQe-8O@T3~qTVCV?5W~Vj){7sR{`Nd8I`fQbkG5;ZtV!V1%Aprj$g&8y zi#I3K(YO&`h2G1j#wSWPxZM_$r}L}}xpHv&@n^-`Y-d^F2v|p>u`EcXUI;T$2Cr3Z zfIecx_uYv)JhNvS<|*Hlkz`|~xKCg*`0(9;$9!9X_3jIX8HU9&pfF+)+mBLfp3f6Y z>+e>cFG}}5GV9ygLN9pR-|XvK(+hu38hRdu$)Fi;-TQnm($aeOAPNgRXBWABi#yHJHa~h&c=V=~sb%#{$+61v znaxV=p1(3I6FW0nn0VBlebIfU6KF<~r0&+{W~Z@)4+dDjI=?!>h0urrFpJci^Nb}f zgbm5&n#)_vDvE)X42g)w6@W24j!Nc)(k~wT1(B+Wl|KB3q+Kqi zm`;F=Oe8h(IkoRw5IGRZ3934M));%Lw?-cRCEuy` z%3?wUufD@sVJ;}Cq^mo)UvqG5yj3hq|FO<;>aNnUX|rgmwK8|JmxZG6MAMUy7ud4{ zxl$v7Ma|IaJFWwHnk>GbLmGj0_0oY%4WfB^_flp9{5rfa3B;hwv~Tdq&+BIgh`#c1 z)>`E_Cz?~Uzi&gwMbdYPL zT$3g8WrSh+5)O0@=D!D%s+nLXHSw{)Hf4sBm$h0hsyJu&`QB|I`l2>LGUT;u ztvd*Lk;1h9SSmgMqx#VQd^EZMjOgB}4X!No%_z(sLrA8EcG31-2q!ePL6nJw9cY>~ z_LYN=$P=#Wbxg}(&3m9^NXC(%N@Q-&w5T(PZ0-K#L@xI6!*t@Zm_5SNrP+!cG+3qpVrA5fo z#%vw$JXbUOPb0Rwavx;HP&Q)y7pDM2jt^kSos)2BOwvr$D!#U|yZ3`$&f-ZjccbrJ zvu9f5c0i(4WXmq30J?X@#eoS(tPGKRwazH?bER0~>yB^VxO|mI(W}epv1Ls&ne(u^ zKa7EDUR4AGfTLt|5P_G&~+t)i352WfYy z|3)RC`{vkPKhg_-7ogW>yT|B#w={Mr7ALxgmN<$P-)yR_71>()rRL44s_&E#axBY> zY<)wW`-Z_R1ZS8bUJAzs159MpN$4fyLU7ALo_0brl=)o447O0XdS=D> zL%m_L|BdlPN$43QTm{r~X}kBV6&R}kqBysl*D1)UB^9n-zhy(XmBan|(+|0{d7fZh z!c%C`sWramY)xZYEg(=TcK`3Mrnpj&4e?oO0#k zXl+WGTaU~-;7@~9h4wxeZL5_FRSbtJ{HcnHsD4#z;8(IUuk@jRAemO`b}8l2r?334 z!o!Q)1y|GE#ksoGn5f;&w-t!7S{(aTH+cfV}wmJt#zZO z%;o|!hJMzMOesexiGSZk$Wh!Q@g#hQi|n~;Vb^0iO<`{)Y2C~%2aykMMg6!TDRjM+ zqFIL%>Ewjr`B~aSNJat(iGN)zAP|umcNXJ<{RS<2mVL4O?76o@$^cw|k$(L|z_b>@fiiTBg{aMLFr>Eai>W7S(uKji4cOnjKgR+zU z>%tmgSoE3UXMS{^@?|d3fFS#OovK?DI<(z?_V(TTvs9s~l7V`LwXvzhJ#TR*HW=3L z?-sxy$&5g}tm5utfxtv@hb1-mu$M8qZhmg!#*HuQ1lWUKgo-MIl#uRQmrDAx3OV_8 zuBUzwYeuW_aK&`2O76ECJztOh5-ue-E1Oi^JSAC*)Wft-@{f0;j>wm2wjC21834`k||;R$WF;yR{R;enM14^7x>sE^`??sXDU zzBT>P67Zbpep^Wq_u6MZ8+-BZ-jNJF)&@wID)^KuQN(~nr+`K=(}}uDkc0EF zDJyRlIc3jzT;Nx$lppR9crYe8H2*rn&9MgRXVuO})iRT}rn=X9C)Gk2`cy_{m@$nO zU;>UyQx$O5M8A4rmyIH}3b=u)S()Ltj6N~cfL$izLddb;1EC5)jUBw1ztG5C`s;gu z#^Y%xYduTw>QEy8in@6czUDSi7Be6QNN>;xv`sUiN@da}=pT~RYQc=fGVxwhT$Jl* zkB4vIU?A<>8T$``*4RP^S+a7IWZPFat$Tu}2wH$3gkyP%6COCC!C&EE`sEMP21}ZA zU*AsMWHNF2^z&OavQ4m!wBA>TLaGOzfS@_af8QRxzk?fddvm~yHj&IPf5;gh5J(&P9NRU$BFXM@-o{LFzW+>b_nGAaEP>KS-2I0i_p`nItiChxsP?_U< zrgu{jYaj9`e=1KTQlX>6JP?kP%rNn`U(L{>r?Z&$sL9b!hmlUr4r{It7}1KzR<$=f z`6*)N+CQ5ROuI}we0(#93I;n7tSwEk+v(wn-|zE_za|wX3)7Xp*-eRi2~<41EW}S5 z8Ve1@2hdG_5qg+sLMfuRK}k}6s&FN2%ZgRhoF;X}Mue z;|2lJ(@>gQDNpW^Hb6dSD$XmK%Py`x#9C_}@T82C*zi8#vM}Ngm)6?28UDV9kdFeQ zvY`loEaTehpHH#N0ch2kj|sFb$3t7s^9(DG14As89~X>VzdkB@odo2J4+*eYIL>Wgf0um z`7)OLhcktKNmsf?J-7}us6AlbAWQ4uG4FlzY>Vvh=slbzgaJ=LzPp-)+-%~4smU*w zwofhiDPe7FoX=tUW(N+D44v(S`}b`3-T@C;5-|8$vCIxPf&op#+xGPxY6bb zQk}vzc$E>0dj?$94_lM&t;WW2u}1w#&)gz+3Kt8#%mXqkGI)qLEY0k>ftItZ} z_S@#%Y^+GD-a@78n#JqiuL+uywsgQyh38W9;_TEGUxI{0+w$OZ>@gAs`z-iqJ@#g? zjR~n-#nGXafdr0BTkEgeHI~zOAaMkY!Xn|$kXR6d|AEQ=8er3NeCfII_{tS3phXza zNep=@uw@V7G`|0VBA5aYn_f&`(ZMWhan7Bo8tL*tNcL0Iwq7Y2JqJF#y`0}K0V7O8 zBxnonQ_Ulob@x#y2#XtnS%Vqcd^Di){@q`WKRdpbhTc4H#|56CgfgxV(|(InzNb!q zqoy!K;1#6ad(en)2f?dmfTQ>E^rq!|``)uSusFz<`XPs#(|upTrzKWG@8jrqiEV`y z!0vDqyWTh%;fZ*a54=8>Z6Nz9-oA9u@ok;9II~{8{GO=jle*Ngda-Jq=^ol7y)*E8 z#r*moErbUQG5HK?+JWEjX}0baG4hl3KRzrO$$QP4$aOwqWfLcH?h$``ICenH!p*&fVwpZHq*pU=quMl1fmMDTF<0$Hxbu4U|1S#Hp2=n;5u<=%+0NK%L*C zp^~f=kK>5rt*xz{tr>@hW_f{&dHTHZsvSHHGHR(tf_Y7JlK&oU3ILwh|JDz~KXd6v7@^_J{>#rsPz}o z0KKX>sW(&OiE?vlYUzO7tEIkSCQPciF~Y)sFbFFlxD5gh>#}~S2n3x!C6)Qq-^st} zUv{^LR50!{#?j;*hv6o3g^^qFdeL)`O4*eIykb)B(nTN zVpcoTtvc=!B$8xkqSw*fLXLqI>XnMBpFZ5iR$_Qfdk7fhS-hGFZWHDsHDIMGfF&4r z<~*9JEQ$8Wqyc&@dq0J*i-teBLC@_q@!ou^tpA7=rf`|BA%I6O1HdF;k~aKoN^+xN zYM|R9O4-xJ-%U`e?fHkVrtiJRbh&*EL&BiW!9f<6W%7t?&;Z@msZJ_vUZ1~_tgv2b zdt1aPGrI?+xS)o`MlG|(>updvA2{MEJ{IRUq;y4F*E+{ec#i0Z^}HPvEe*sA?kx1G zzV83*m}0I7tPU+vZA!63FgAXmg-M)wcgb{cc_?csQN?n^)b9tW2la&%DmvV=3MPT= z*TR~I{yhF3@x=O-ae>+fL}-vGL(y!5(^2PGVA<8+;-Y)m8xXS|kX4&t2F3&3gD2wUL$%N=pPqz0ma@M6SOMbar zAo&MrRX+_phes*HiZn(n0W^3KJM=H3U6bR@gyKIW7QZ~-BuY}Yb#se1!+HjN8;T#IJr&+LtmQL74#yw>~Ni<*^0CT=O4Y-4?aa`GXZ5kGGRaCX<( zca~o17xl|<$7;A_n+Q6J-!`)kCv)VNne2vw2G-d$1YjWi7mfq{!pmswy5>{^{TdJL zkFg^9oRxaLN`a3B{(PoePExAd=?xjrW5cp1egEOj05VS0s%Td22t_^4 z#csSVj_f%eUT&)m_bCljEseA=?)?4CK6E94s`tW7aT*!CN4N!Z!v~h_#~S-1{6}mGkY5>S@YzPm8jLvcciI@Y>t|nTv^%2~K=n^rWe*lK9H?wN{VONA ziQr$}@K-dSV349UOMK7(pKOq$+Rt%vxqctm1M-iNRKCnwCNnAK6`CQ@L<3yFa19mF z2CEyNGy-l8^#@cHH`Rrp$gRY??3WHkal1ugJvKOpH z4Ff4zt#U2Cq!gJ$I@%T<4%m5p_;W*8J%8vZ4h{<80qkFA{gaXHMvJh!Ja2toys&yP zbb6M86*45;NYEW)ObBD8y!YePSxh%OOdrj_rP@`E5L4VJd5@#M2gZI*yWeoHPUy>*k2xP7rC4Y3x8;3iz zABV*^)t#Rgz{sXFq=_c@Tzh2hD_*e&*nyVJw~==R&o-1h_C<^&-{G% zb$kcmJ~aNcY*vFCT45B}$$!Q%cf2rahj5ANHPa#=@(jyzT0+Qm2|1>3~D@>U?=N3!EF{573 zJ>0iTs>@srfsjd{NC8xXR~_sYX~zQbaue|sh>fM?gv3arwYVEkJbB%>kCLjjk^^?g zxVZVJ>J_Ko=7~aR$fD|#F09~PhUf$TPx~JC9mk~?)K0%(hOtVS>4(q51MeUNDo#Oz zAv~A)2vqXlDj_v$&^0k7F0ugSafJ}Ausn*8G#CZeB|(0D(GNqP{Pnl_ zY0KH@RkF84Wx4!fG^j)8>BMA#!fQKNAXXQ{UW9!xsMIYrE#Kk!>?-Jn2a?0$4=PnR zaiG;&&e5Kewv9H#9#RNuP#UVac{SYt?eB$f{O#bi>c8Q*Z6Ff*@PlgW^O!%nVqH_+ z7oUzhO2Nukd_)#}7>;Ea3sb3`xl{WqhI!?(TKK)C754FT1xfE~oqOZVK;W4Wk9pWW z-v^GKAgB<@u-#Pww7+#asR4x}Ab%heyK(YIHtRQREt>c87tVStr9vw`fYS9Vas7&2 z_6tz7#ImcKm()u}{nrJ@f5LnCI@nDj{$vBdCU*u-z&`|7^4;A!T?zu>OE({zvGi}1 z;>Y9tL9*ep(!tun65sIarzZF!On&s+cScDy2UL+>`}R|B;tlSi8C?+l& z-;g9R9+DlvQyrX?!qlSCcW33hY`5T?MjJwx5kuOY?BgVVazW!y4SZ6%+W&GEpZ;WU zx%>p!(k7F@+#$lfs49y$3xBOt1HFzmqGqQC^b`v#xHu`@xO4?dF-zT^bAJU0)>pH9 z{4;MR%+y7Hcq@BW!)nwGyYs>Y?Y^XGIIpg>kapGC=3ukns0Se<8h7#KlAc5@Obdbi z^RU?Mp@0WWPlKwRKQWzslxhm(*uNV@S9ZWq(to$#W^rvGR|6X}y1vNp$b3;+Z5?Ia zGkPTqv+cqg@zuBcEh-?DoXJGI>*`Em&T7_%&sxta@~OctKxQr-xuoAM)8}eB{VR7pb;TqwhCpK1p@%AP@+v~$b5)&7h?L5zj3;F7PhkyJ1w<7 zL8z1?x$!~qFV}gR_&qiZg!<|>>^|NR7ma3!RRf{)VUQB;iV=TzJ@nga9IbxbTug#8 zxjhN>_310uf8RH@(d@-X64Z%G*x_&mZw{y&dMy*ZoXaz~^+KCkORh4t*s5r7pd?i4 zbL5cf{FuUp6&e(sf2~^qMvn^^gi}E!k9uwnM`2Q7xufo?eI-jG^8u-2fzOPY(wn!F z7`D_ROj2oD1H-%{vHfU)|ag_xtPnFYJBK23M)Z`7mb< z!z6iPTacxSAxNWF8GRZmIq98HbGo;;+=``vJL8{H&kp)K`hxd0mG6G&=dax`k8|C_ zE$d-T(}$yeAly*y=OuYmbxL9`g1wz(9)o`}>G*({Fs3Sa6lYAd@gY#})DxYU^O@uE z8hzf;7xsL9*T3hvOl}3|!@;m>8aSJKP=TU`GSLJu%d{aOQEOCqFmH9fcYw3xVYMZB z&VeCg{9AoeqwE)r2_!JKrDvUp(;q+o=KA#mz-0{;xKn9cHcU&Y@K{<>Uw>NNs2!+X@qyV<|9sK*|xY$Blee#Oa_nJ&3Qr;pTP_KVP(~_(d zhZ|WDLZGEv5DKCiw!4j-U1fjo&X)ieGH)wuox1mQs}JNvJnWm9Wu|ii9y2@xj)|E) z2qv`~0HhpFOS}b&R8vM>(wj%Ww>ac;y<-+=rWUI6-KnE)*QzPRhRBQe5gtUu-URnA z)C^@T)qM&sDU!QR{ABC+hthi6Sp&NVaRqz`%sAV0;WlJ~W zFxSMtu025|w*Anpf)4NJpe}>@^Bd2?_thShuIvOzosN>5KkcM{y0}8pYfToe#kJF0 zeA3i&@&azJemnTpS+6n7Yz8!VI;AhWwOD%;tjf{6MGRE9_`B|mEm{wObPouk!1YL@4_a6xv)cjwvMi-Zv-fOk6dwY z-v|VT9QP!Rb&=mF{JQyYlgZuOSF;c%m<3XbZUSA_@=L+>XdjiN&$HOI{k^xy+^PTl zd2=oAkldpYS_tRWL_VFo5smxO`ulDfHw~d*Z#ZoOYW0}8gaCZ3fW6-x%p}G01Y9=$ z0%25yHR{P<5~s5~NBEQiH~XBE5rvf`AAJB1L)+y^9D+?+HbvB_I$?;MxD@Js z23GJhb#ZMa;xQg-OlTo;lXPY@ZUdA+*-|f_n3^I_PpV}a*TS#b1H$hmS%t_d>woR{&ntD zkdU%LETDK0_o#jL9o$b~*0ME&Vty-AxMLnsJ;JsAm*<5I zjtf?Tw&`7`8(JR7qddXUFnkt<-Y)dLX?L8$OQL^mps}8b%~wPh>57-pqAI1#Hus{> ze)p2az%vNo3zMZF#P#2#^Ap~+dgvAz8tKQ!$vkjp-R}w;Q|mfkXss$Rrv;w|0)7ck z$+TD}DZJxa%t;aRxV(>?t=**Pkwo4&pUXqa3g%*3+SI{z4)0S`Y942=2vLmSGSz8< zP%xqN(zyL}vo7-#@3(U#@&=J^X)m2=rQupp&GHQ_bCIqoPtuxFG`j+1LJvo0XqZ%W zeR+m%QtB0=70u=lR#v}m`9SAdrQwS2Hrj%4{9(N6a#}!UYTY>#>QmoSwHB;UA47@# zEZ%-NPwW>EYbE?zKVkT>VW;0W;boM5Q*dK zrjSTGjewAAH9s1G<&5uN>MR`_h^}oOPUDqq3A{v0uR^Kz+>NcB2*ks39noXxkgZ?lM*Z+p0f+m^dw{l~oi`LLg%(Fx;JeZy6jE$NY+ig2(r74+sJ~?)B?x=i zW@&QE$%q`ST00G0($b&Pu*P9GJX1ApQMSj$O82h{Ha!?r;TlC=H>J-urQWDGK0|?D!a2N_sfv-aq9abTv|U`2nQWXi)}$)pFRkl zKasZT^MPr~vl_EH#P`P__&YiZOEQKecOl#)O?+%I5xMBN=oK{Ty_j0?G5pfvT+6*B zN<=gU-$IuDAt;65^r5l`u>#o}#Jn)U0)8_Q?H|Hj`6dfuU*F+hzfYYX=1Y+@Y)cbx zc?Ya|sP-Bks!Ca#z2IMbTU9oh zGj-U@annbmT7Ftdi>}VzoOynAEvk#5!wk(h-U?NBjc1-saIHP?I*Rv+78k-gr21Ej zlG0zNBI%foH@4)f8o2hMpqb`R%AW=QJkEP)9C=*cUmMj99+b8o;+ad-cv?=7yf-DQ zq%jxiSo%B;=$Lv5LwZn2qJP+@NIDnUehRX2oG3Ol4}5!kxLqb^LpG&R^IkfAg~XE_ z-~DAe@<%c1pY@-$^mLOG=*$Q(ya=`^p;H9VHO`h7d$({jFZw2J>oFsmJ&#W7i%ymb z-tc-0-Jn)It(F`BW7ZZVF=h!a1k_{P<=D|QtU=$ECc;U0NDJ}E;Eh8;<@^pc+p7m$ zmvq2X*_|j%@EKTZ_!=YdsdhJzJH)=inKSGz-Cgb+v29(}m}GjT9!6``$9UAx;UWdb zaPT(WeH?UP{|23@^__OFWMF4~Zl5V|yF)CYl|L+iO+W=JKHOi+arqreg}CL8K~ajA zP{*9qdnR4*IoTLJ{Nj5z`zb4jIir#~2rNhewYmJp6EisgU4Zs?vjMZRXSX4P%|JBG z5Zc~;TuNH<(=}Ty_(v&kw|TP%d`}aVwc*NfC^ue76xAtL?j$qaDR`7cz9Yy$cAm$*D~x5Z}?~7sr&zCCn`373Youf_!ER_*{(hL1 z(JjHeq+n&4(ugGhqMgwC#fP{tvK&P0&x}hmle}OZYXQ5h<-dz6Rt;uy#IYEaS@}*(;Zjk`2*Qo z`Nh*lIGjfj9M0{+#wUZJGUFDRZ9uIT<%ESo_?z<8e+n#Oq-i%b0$ zjyQ$BEaJ~SO~>cd;$*P^J=Hrj%sAB7UpV1?wrIrL$jl}C@#S%0cK4;|jxdN2LhB+I z$U~vV7$#{zd@6K5{6sT!v;rlZPvq+orjef=7J&yIH1@pzI%4`GUYwok5p=QnW~qyD z11KghPCKQT_bED;-@{q$OY2SBZzgT;OOZFII@36|in7Q99oSBoI|de4#MgwP`H6$K zA7Ka7V&CUk_AJZ#*4#Pwp4xX=ni5^?Kzi^#oANoJ9u<(i$lQ}AUHJq*+t5ne(-gEP zpn`rg+20Y&b) zwR!#{HgLa{I=NWgI`h`sx$l|+7bTt6ekFt)SrCc#^V9~BzwdX@yj@rOAe#{}c`yCl z#k^E#-Q)w+s>ua|M}%q|3}Xm*ipI{uICdxbCFJ^R&Gr+z{P_k3JqKc}0Gy zSts-EXAG7&+FevSM>=vR(#5~t)@TK+s~R(FOD5)xII}n;1UdOkAz}&cX_k7h7ja9h z|9Ya5%FkgIfQw%fF-J#a{@Ff&YNU=mv6Ygr7mp>fzJ9A0GX}=86Tkok z`$LG3hN%eg%G^Fz^x107Q8=15);r+OmJ9u)WW=q)=Yg(jxJYSSq3*YHY67B83<}nH zSDgK$hTwWf`#oB!UK{-~2DswvK)HolcKk%yY$X2BYVKrLb-%F@Z#qz|kfBy$&{DPE z)clt8u2B9@ZK)P)?JB(e7?YZXS$eK|*eH3Sw7NKrVe@if`80CR_JOv?GPfa~3ag@G z>`(o*eRA)jwsuGz6@5wbqZ;w>_FtXb)g)^7h9y&`{I4EY`ra2y-EDAtu`&>Wj6%&P z^|#m+>_i7clI(zkD@AIxNdUyae={5FXcehyoex0#$Z^>KOjC0;|)k+Sl<17qL!BrW=Xv*(agtV z%_pPa%{}}`I?VkQOIUO)kNA7kn{xW)?193jkJ(FQXzwW6F#&RLr#0wTepM@Ev9UE$Rp|4SYI z|D1QF5BmS>|55+U{iktmF_IG~K)E!TpZIMQ(Ryc$mcirHYO}5cm&%VAZ%|yt8U)(`GO{vyUninKwe5pGzd7d|_!$GPp*7!QV8p(cG3c)|$7t zsv;wC=jPbglC2ezJ^4=g9UXja9E!E`DXzpmcK`|c0o0C7-0MEpxuda&zP%c2-;v#M z;kQ_gkX@+HZ%CbNR1-X8k^!MeNDyel!Wayy!yb#?&1ihxFK(HToP$*jh}%N2W*kF% z%oc#|D}@UfdpRaysarh4^KxgN@3OF zb?tZ{x}3Qrgm@)lthJY$V?8_`Pp#*@|3aH|nJ{M)37-A<{-Jr3|CTp9IK;aAA9XSTGwLYB+O9 zGA{XPH=N+?Qy^m0^|pFu`9{YXNY22TA%@Y?NV;*8Y%p?LQ{v)-rYd2)ZyGK?P$<>f zF)gm2YSURE68;bsYwVih9)OCMFIXGQ!&Oer{3$Th{+E&GLel-i8Jsd3DW)n`X4rgNL=4-I|zk$_d-H--)ASVFn8ztTmXf<$o|mjRzUb) ztF+$Q*qk+Xl6N6Hdd?ZQ@*ZH|-98zMO})16_$9#U)_E()Z71>X;?k)$!4SLTr)x}5 z#G5mO4Yd23O4{FzcX=fXku(Uq;eUfZY5Up;W#ElhPCvm(Ko9;Nez>nI?2<*6+`HZ@ zHyQ1af2T8W`0ZY%@)bZqOq~I6%;yFmi)+}pffOnfz0b2Rp?U6$uMdRlBL(G4TyGr6 z5slU>oVi%In4xOFIXLqyFTUnh=sj+4$ll12PcF@4`q)xzYD1UhVK!SpTH{(hWw7625PEbgqiy1WGR6 zd{mJ2cRzB;KfABvz9gr1^e8BqI0NP(ECG@%W(|`t41l9p1w?D!XRFWxtz16)&QhDX z&4hxzFD=`L(d_4^4T#Y719t}jDrH*fj1%5 zy{L^tmd`|v3O3%zO?PnTEh z4Hd^CZpsTmRQbbHT+GaUdJfPc@ZV=c-MG4%kdAV^>|2>Dm!x~54>iG1UwYaE4&e zxc{UItdk7lZHpX%Qfq{^EK>KGCE|RXu6>RlbIxFX2mE%gCvS>l$byf_H{JLW3Dnlp zwST0o&h%znZaV!s9))SRMVZ^!l++mL^pmS#5}*|jliluS)ILK^XGf<#|0`DQ(?d#* z&{Yf3x3`3imJdXdcSM@yf_h1k7sZ;_Nj%N1-$`_pVqG!IXvFMy9k!{?jynhAGPnTH zxecRgE!+F@H<9}&b`%hmSj7Ffr4Em-692yMV8UO@EX5=h!Ma=>rR8^PkDHm-1SH{RF6 zpGeE(0hx6Vb9zBD`kE1laZjQV$?H4mDGHbu47h9Pe_RqP8=PB^HiC;RH6co_#=E*4 zF+q@~p+&ewRO9dkqt>|@Fzz}R=%gD$GtG9A0Z_)?LLogv17xhTf^cDqV!dvK#rnN+LfmhUL<`& zF}5~|=Zk<4A@U;txBtz7lWq(I!daTxJpwwM^(>Cn`7#Qh{i)TvD7+-|UgPi7eeT=x ztK_GesE=kDN|0eonCG<(L9d8CNGP<0^mPNNAPRK z$)XvQBF{fMbx>S)zrpyulZB>O!dXLp!*CdufVy%Z4>I4^f`;{B_ZC`D%bp6aM25hu9;hA)A`Al3-b88{Z& zaMtH&zp=En2=_oeZ786h(%$AqGB%X!-<&3{0zYOtw{0Xn5X0A?BWQ$EOA7ahPJ~0yK3dPi9V$hakmdKtd_=rQ$J-fhQb~9Uw z%(>NEa{Z7WF6%@H!|Yr1YKkYg{AhfWZ2r=3WSPoZxA(?x-RpvU%;{h@h1n`_@rl_| z&}s4yr$r+TU5-LO0P!hsYDGxTNN&v#rJfIv|0+i&V7U%X?Pk5?hDSfb%f3{6>=$0$ z{pJOl#@!@#dGc7YnC@5|k_B^;9C=dqrROgv2DT3ji7-R)2t`bRBdc-x$DG+&wn*`7 zVY08arFxhE*H0kXkhbFS4BLbM$D+z+*+Iu!egE&8X`PwU_JLlk=m>A&9d-j_kXvCX z27QRWhzBw#iQdwC z)IE*=7B|;w8%d9ed_Dq{gsZZEu#sigW63f~GJJCyIeuB~?_rdq^m>FQLpK(d1=>;_ z`t9WaRw4KiEfF3B>y2I1RRZ);#Lv^1FC23mbw(m`>PCbY?{#y4V9fo*4fHiY4V8h9 zz2sdJ_ou zQF>_;g&P@(^4E4K;PaK02YXysK;#Q+d?-%_t&O#p!9|H$;7+4L8JtIh#H-vJk)pA> z1;wYCTrA0@8+Sj?kAdwPK677ET-01Dlhkn&EiZ%l%~ws82I)gbpZc(+(DJ>NbY>w_ z#0)G-_c{WZq%r|bQS`|-k;q6$e;}y=pu*G?Jkbl{|b|=$MCAKfNuvfF7UaZ;>TiX z4JnzSN>lm_%l~m?J4(C+e&hdR=5Dx`%#i!F`=jjXq$)$fITclv+9?Pta;Td|gE@@q z4c06F*n1CBrw;47Za(Gc$6 z**bTIXTNr7SCJM~k^*oOoB%o~!$;yh)HgtR@oHb|A5g|F&;lofY2%8LPqe^rsAJkd zyJLQ3Z-rhYOTb8#(Sxs=R$_41OnC=K$wq3QY7!7z7EcO5iu$OLK@EYegc)fv|RM0j}O( ziCjRg%X3<;jzq>kV88kG3w$hsSArMds{*CCsd6t0+z9aJ*60y;(K5If-=%`?KQI)P zn%s)(QE`(aPo}Xl=m8#%zzzqn5{`3U3}3#H7MInBrp9xg`sebc-RM6xG5&nSSGS@d zvs}0^Xh}#Q-XS>?YTxT_tU-Wv z6o2V4W)UF=YQ)9zj>s>AX7aU&ZlAQ2e?W|#vf!5r(oWa^5cL;)W!7z1SJXxQm-nF? zR?D0q5N9H;z;zHUK1VXuN1Y3_@vl1fDjvOe7ToSF}8EX*35vC+~BI8=DTMXJNr#U{S~j-W@bX7q-XOr(*di{ z%iuD#@R*EOn@8FYz`8^F;hI_aaZG9)p7sn=m|xA+X<89$pI<9XE@r*dcF#adilXk> zwO@nbitHL+z~!AVqBAK3e;8MzajSComfgJ5(3C@ULHnI!11x>=ItTFJUU!Jn8Nr|* zzfa_C{kM=M?>gQH+rNd6-eAlNmZjMR{ZjR?SFgyyaIJhbG3?mB_coI~D$`gnD-Wl3 z{R8y}k|CiNPl0KP-zQmOZ{J&nE(oSB6zfMi>$|KQh|ooG?K~ChBCzjSLz;*8*_E>V z%7!u5)}IHyTrMjWK;)~Ejf-5LM73=eT0qeCA>hO#^7zjz)bGFos{f!l@ zMK5DuX^K*JQ{U$F6|ZgR{$n6*IOu?kL8o54Ml$|{@SsIU*Vot`Ru*h)iWD(jCBk>@lvAwVu;?@%-tOk{>PqLU?Uzj} zVneccE6-ih&va)0So_iu^cwB(KDZTTsc9SXHC(7gU?(z-V#bfvY(`c}#AVk{A%px`dh#RIb4yx>FW?eQ zzRQ^+o=eW3R+pT()jGC3{nwALG)(~cvG((Ai$uL5cp;s0DEkoUApwh*$MO%bz>lqc zMx?nKIi}wC!`RJr-G8sjQ<{_B%auhKGUgJ|)4lfI*;m6V)1}vDXqN&CdN;HDZ z+~;bFJ;>>?zL`A5!leMflIFT({{?1q6yl+W{UEbbFK+-}9u=Uzlr2Keg|5G1~eVerT6m#^zgD-Ml?}Ptl zrBibsS_4Bd*{A?gIDTd>x<)Z{%6zuZeofVxHRotE@pit+2l;JJ)&1n4^pklS4Ikn? zQV=2T{piLT2fY4yfttTqEZ4h6TkH1GQ#e+pyuD6ju@wvgGU8hLEyB2AHwf1a zuE!cPofwM*jO?z_GcwX_Qa~NNQFWpDwLk4rKjO?V>~X2Gjc+eX%pTczQ2KA!x@XZ3 zJl3uJ2uWt9x=^CeooO4s2MOnsNgKfC@y7pt|ziycn+BiYUH+irlP9a&7Db9UXw zht1io5WC9vt~8pyOw8JVdgVS1 zWguCNQlhnUh=4r4JOM2P&paMnz1gLfT8(@#p{kb_mJ_AeXHc0)Ucm*jt^>BX`iplO zV(;eRp8c0Mh%)&f?&O@}U&M}?W*FT3)NaI`N*!oiTOj%4k{<5a<0MOne*BJfcQ#-9 zn}l)lT#{74E4P$4ROgo8{GF*qma4CQR@4+m0t>Gx_2e@lZ1^t_w)~+8rnX!Pb#IDh z6|SdRp?p}^*Fn#k`YgDn18}(Q`{9Q-(9z&n1ye7X#_>nYOM}m89k`f+ZI(AqBYVsT zAA#7&oWPhQX^DGyrP}f*$_B)HO`WZYv+L>izKJH+ji!UtUs3e%`_LwRR$pU9+{dpa zk$jQDyU#kSG1B;U$Nr?Slwy$K*0~P3AE5md=nC{%vp&UMywAPssNj>c>Rq$e#Cwf? zXL$?J34j!}oP0Lzu9-V~`S762FbsBQjz{A`X&|tCn5cObw@ZlY{RhNOLYRZEkmRTA{^*77=l#{?ic-8U-!g7a zWdI$2KqC#6yw?PHdr$wnxyc_P3? zfL8;`^xEis)9TXXswj_u#4nWu=ft}AHS1Svy)(LjB@^9ZUt`9N6 zk21bpyq|YNQnOF}A0@pd&tz#M3rQS}-!*9opY9OCE8aU2h?qxb?X%5W(YMRm4W;S~ z?w$yny$Y4y{;aYnnMg3*sMfWe>h>}N6U-@1tu1FyBzXfFzq%i1komH(n`G^0C6*`; z94^%MXfbIR6AkSMt1yn;#hHrKev?NqIsTvB9o*Uv9+O84Iwqy--eN!Q-YX-TdaZ)+ zX9<%7&*P%+;TyMq`?U7DHFe4CIlQCXL*0zh7G-MV8s%{$JG1UDSx3?T1ctN)of14w zi*R{eAiKJ3YE)VIZ1j48P4K#meOAg6m6A*AWHi%)&m|L1b|5Vo$!f;%Ta*6#M$@!n z()gd2rnbm$qEdpk6x8k_wD|R`R~q|J>RF@^f|Wpl>mSNf?l4bN1t&zH?Kxqvln+ja%_bmqOhSL-# z>kNw>mcHi9rinT^*^DGHUoF}QifonHUx4H)vbOx|9C%0GZ2(Z!y00!EsBxiX#QUU& z8I45aeV#dkdKKG4#NJJJ-LLNRl#Yr1=^G&LbCc8Ou$K=IcN@D23jxl!{bVy0UABfn z#WD7yl58Qv(B#d`ThS4`TcVKWOL=(9sR?1Ddg*teB%!IY*T<#Mu`H#zC@^PsJ+ig3 zDU0Q+fo+1sJFW+yX}SslU7{>PU5oWn2%d!9NvSHt>f*M({Cx{-%Z-yE6&<`U6IIeV z0^Y1O2HLx<$NIit#_&!i6tQ-@Yb7kU#CK|a)T#A(hB1MC=xV%tW-r=&M>J75GO^Ta z)UsFT^>I6z47WBc8?QxObJER-5Ws8XtP12^dOZ$nid+n~*cz0)x*=6oVk!C<`KGo? zL3T;Y0n#wrnKzgGn)E>V(?!u`#2cdW8>(=7RD-K${@EpL*iTW+hwQxdC%(f_INM#6 z$$&u>X*XWY>;Y9{dztv+d?xhGJTr}_}Ckg;P=~CDWUH9YB6b?3ksI^#f?;hGNnM5YwU;s!V5vd6O zy4Hu}NpnLOhlh^cv&uQ|H@c8}eq%>!8DRf^TWf|5z-Ya5FS!V}*Nu@grHI?jO0cIL z@bDQHSt%t=*mYsD*C8H{NvfLrxLr)zUfqUl6ca*ysxv%f!_mT~IpHK}$)4^5+9!c2 zd_UHS?2_W=IcSoyvk; zm9j?Bwc=DxI&8^+?3|srZuk5G!#=z66p}8YP}RQv*JGuhe-0amRh7CsZ45;zeHE5~ z7C8`kzkB)>Fn^AsHFpEyV~dj3@O2_>&$iLxfXw`B`75fTN;vKQ1B!^Nr_~fdgrvp$ z-BTyEWKI zuT1Y-twNNp^DXDQOhO9SBt`Zz4Pmdq?I2&bM1fg~ii=kW20vW1_75#@aN_+%j|?9l zQ8oQG%=6*buFr&q!XoR1NVc9T`t&g5h}s%SLO_Kuh}plps90qE{Qw3epo=KWjf&jJ zRHMhPu`iH3@wIzz@ZAp@@&}68o7DN$O|^6KUKADt@mFbpzOYxl&QTdMUJtmbMmi_T zksc8Ia40M^e(!|;t}ThPT0i8&(%LBRG1F_s&hG{wXMVQWbGfoMkpJM5Jc{azmwx*@ z?S!nIflSvsD$B)nnX66HmwilLX~pmZC_TzUc_1c1tG>H2=xDI6&4J+(`X)_&eCNO) z>laeQpkYY7b~$OaDpNOwK*@Aez*DF7QZ2j`G6q@Vj^2Uk@6pwv3KBs3d=jV(G#y-b z9h^wI{Q^8t%i{>w8f0Ga7r(SJ<)2|Ly?mtvFf$) zdDRnN%qJ?o@|7|h3g&yO?<7||(w=PO7^f$L7M#fc+k>e90GrqTr+>B6X7hbYZ3JWd zjbRKZYHp)oz_%qgMcz;&j7m_9ImSv#6W#Mj`7c4F444}c?ohmgWUd$f{T z0)Mu{;tv?U@I;Z1)yoZZ*n2k*8N~40M(U2ZwDQ*QMjm<^;y99s*WgEUr%pe>34S}C zYBPhxPV0=ta}-*VIacuQ0OL1H9rHc9n~SJxl7B)0GaNa9OrVDk0}4wp!u(PdFOlN6 zx@asCKTPi>3S()2w0`30;G^>N5D}D>JRj8iS8fqOdm(+1rJ+TX(#HUK4&UIhe|IsQ z3p2ArXTerph!Nd*8|}!~ZZ8=`JS(o$rA4E;b=TF?_`PnD?ibp_=+#@hGC|vVX41{s zG*xRT|A6w;nKQG`**lWZ>mtmTW&jC-Hs0=5L zHZA*GA;OnaaYGFFmceQG(W0cO*P*frAER50>ov4l&K{ZjEnmTGb3;pubPe;aT&%=E z2P6^bz?{J1pDrpE#1;op=d!(%PkJ6~^lF64U|Vl_>?$aeOwjE@i};viLtw^(`|Pg7 z^*ztGPa>MkWnxFZZ#O(1umSN>&seaIbR6o9#;H=ii361eIQU^tb1gy^yR8qM`&owo`p$d1-%Gk7;R%{ zNsfwWWe&Lul4Rvh6GgYh0 z6JHsBf9cp$<|--a&G>8DT;SLKs?Gsyh!uQVKeWLwH*Rwl%?0379^uUr{AFg!692Bc zf%)?DZ0(Gt-;V=DXM(DRK$B8E#a?%s7R8CyV*6R27CVy7CzSP<{4e~X!}-xGrCv8L zIZI2l`!TnhZmjvcpsdt0?ETy_9sH-$kmf`^w2eetikKL5B9&|ZOW@P=aNbD%b9Ver z|6y!*y;9l61?vDXDtq(1{7dt<(W+m8AMc#eMwTTi*;pn&ucunOnK9Kc;f(OR_<+TG z4gsB}{+`fD*gL}24M#bNi>bHzf?iw|+f6k`&AQD=k*7jyC||1tCiql1A( z*zMB@`Ckq@|6lPFo4@fx_sU_EMa@)gY#E<~(G4*-Xt-Ws$(y^~c41WA`w;BlEXO!8 zP`UeZz@$g%#XE=Q!)wfEeUWF#fc$2BdzXAKQs^QB&4h5A)s*bTUulx8Wo{bRf$>*0 z=(@<{PrRy&6?ZXV_}Fu!G>SI|!b{*ZC0xa>@evjQZK!xV?hof#plQiUFE7{pxI*%f zDMa_A9obhZNO!Kgp}d=X?65Qb4M}B4p)=)Vq>X1ny2P%$K}YloLYU_&Gt`z z3++a8hh!ZR)W=HQCMQ7U4BT^=NR)W9{@WB7vAKZ)`+;wxF#purP1|9)bGMH!eK#vj z*cnZ3a#>R5k-mL=4fNU`P=20JY@AxQADJ>?r8c}{nf+>$$xtx*+uRzU7aICh>4Tz2 zJu$Vn>&(0SyQ5>3^E`pa)(zI+l-$rJHN2chBG!ICjL{UiNYmto19n^PdO{jX1&=Pw zv94As3bq5U+)Z{_@JhZsZ$IDzBbJpZj;Rt)z)8=P`&Ph%%nKypxdVh@asHE)8vkOr z8|RhRYJTwdqE~2jmexKFzCaNlAsh(Q*rJ!u1R`-`+p~Uwvws(DXZh89teMI>0q<>R zCfARYy-q6C-PG%Wk-f-a6peJJC5HDfN#J^8=5D_eHPxmLy`Mr$*i`KsK4TX0$3}MFacx15S`1@8+7io{F`6cUnZ;nV$o;T9yC|jtXxn3 z621a0fHGcckZcJLKREnc1D|Y9`yPrD_3DzIhqstCitkPEW*kM_q+vhRj3IjNMxQ>L z%d*2~K@zD>P{2I!8>^PbvEZa6?9|uy82Jxqg0TN7$?7vFiEcxOsD~VR2JI`N_Eu^} zOW~03k0;#K!_|lLZ(WI??~yZ2CkD8gm-A&T0b9k!i{U=hysIP5EMC^{FvNs6DSYP$ z5*)B>`kXHQRXXFHn_soR8uKwjc@VrfR8JA#&&8O-fi;LD+;9-(7aISPYSqL;U+j2m zT{Uq1^3T(>+^%O@!-q+0bbnAWcCfu;oF>N6#IWV9!*9E{Z~g%pXWO8^**@w}cpd6Y zo>KWSwvIOz~vn{mlaQo6_{>28=x zSmI>|#@E2Vt2s&crT8f6d)}5xJ*M#D8kW06&_Wvx_Zi;9YX=L#p9vZSey<>BVc!60!-3BX){eqVaUzDmk=cc!kB(Z?`oUOGmFkqu<@Ep#c zyh1X?*QS(x)7oGW$Go6&b8Qci6awAgK)unH=JNYZ9+8rF9_@JXp|LvXw00V33Bu&y z&keJsh3>gLc45YZ(?*{qWN$7NZUn97XHtZ1++$MSKMA>-a!(gJ7vaFr%Dm#5`qug( zPU(BEIdTc96gQZ$=%T*w_UmIXr{_}4`Vr~#a(JY2_Th3+;-4h z&r-E}S>fteN!wBrhCz&rTN za{x;FWN|LWe?8b|MTL{dCF+;WUNk{QEmo+hmIDepw}6%d1NBQr1SP=)3#NZk#8QJ6 zUL|CHwYR4uPxY(T9l}R9<`@kh$_uU1#gJJDci-~%_!kBWDv8d!Fve34A1*+!u+-pP zFh4=}VQbE|O7KXNN!-mUldeSf$E{=HXQHjVZRfGnkT;jSrW|I>UQY=btG9(}-f^Z0 zgjLr)-Olcj5Ry+O4_ZY3ox0>g4%prAf?;*=O3%B5hxFTde5dL%#%_kB#HH8QpWa}) z#pkIp(*w@e@5ZvS=7Z z5(|2ADbw6MooR#p{vlQ$&P@3asQU>!&RIvP+^7-6g{^5cM<}d-K;f|?IS{xiaE;fO)nzh;prUaUE zy;=x}DBKavw8=CsXaB*tv~JixVS<6~0oK58ukDO$eIotKxvofK+Y>&d{{w=@m-O)C zI8|!R@L>rEb^w~tfhmvfWcK31twlPk>6d(z5`S(~l2JCgn{b~ZsI`VvFmnXFUHn;} z6E6;kQK2L0%u3+43hO1F^Vi{$92Jhp5G+5PDiy`mlDsD)mS5 zm4{hm5}*(XMIaupX>DKG2T!$Q_zlTHC^*zm`>g*Ip5*-Xx{nJ@x$+_I$;3Y(2v2G4t#z^?<;v*j{59-_}Y+Q2NNhZjXCuWovRx)mWCtEwR>8z(0J z+fF`~dBwPq3KXLrxfrVE9;GfbKCl`EV~!>+C-$ncVJ_ z%gxijWAOc&3%gC)E15mM$ooc&&D%6u8|&}E(unT;`Af$V80mTR_j0?R!JnXHMN7xF z(L=wUG~YRZ)|Y*INY6@Z!qH1hhNs_C|*F<%?81XCWGxesVBrw5NhvL*G1^!;EnF0 zda>ks1(^*v;y~w{CwUgdo>uDL!+oF9C&*hs>*sn2Jg%2Bv?V>k_eUr2cu5MI)RnRv zrWl24+F$EceK>*CvHlH<(m>%%fJlO(`0Xj=wH5gJiKIu;{izu~9bQoA7Sc^OlS0#C zYlLQi44Pvp58PQLZ_-};mlE!fHgi7*R@Dh$q6vUi8ID6 zmG9*d#Sf!ntmbXGq3VHc;_K8o|Bk~?z^O%z%6%7M19^(sh2@?ocfTO9N*DDiN0_zS zivf0XDmrDsj0kQ1u(WrPC>Rk=>9bXUGCB`U=^TVDm4$gu3G`*&#AL$0 z#%;yw?XuwBlH&uxwqtJLhG7#K9G9Oy+h1}{h*!a0BhxVjY$8KPd*ysu1{>;p1ken&G86I+xf)X7T;JT&tq87w>>)%j_>Z%x&rS#SqZak z3VmMUrB=4`C6V1+$!1Jg&?$M7zUcMa4e_cg;KhfF!+~{4P2$ORXBI|wuh?&WGS(C08K)6ePG}xk?)82@baSdPOC>o{ZfrMOA|tIvvV-7> zhi-rep;4hI(50HU#S=~*(I`_%s|U7h;)hbVXnXI9G`guQjwatk@Z@49Q-B5%?`fb} zB$Tn!S|UmDx-7QS=k>;Q9^~;C7buxFmX^JsVU9ej;Rm?!1n`0n18+eALO~+W?`yxI z!!lm6yjwq#Yn?^Uu;rrLfn?m49;S`FlX&iPTGR1elZWuV2g2Jakf#PmTfX)VJ*{f7 z3UnCe0n1ak-n_up994c%VvV|yn}o0=s$b672}ENc0`R6z3aq2Rj5JJR9Bqwc48l7jqe^897yLf*V^ezI>G{01tx^WgUGsFo|3 zcNYQ#;(1N7!snnyBHc3zs(F^pW-@~2_C`Z9qrPoB5N=M2<57_%<7irgX&Ktq(Fd9iXKX><5lN@2-ROpkO625|K^L^6Id24&t{!_C z=r6<1xZje$Wh0?KUW;tkmD*!G$dY!T;gFR2F!8##+AR#vMza=NsY9CU3F8HBzFI_n$~ zIpG+{e=g!B$q{~u_UqvP9IcvG+=(fq8||Jo8Y_+;r5PDZY5L)QH}jFHpyu)%Necrg zW$6*t8lNUw**D$XH=mSsvp+MWtVuoY<>aX+#jLRMQKfh>~F2bVVi zMPIL~w8Ggp$o_YQ6o1nxn2hm1pjrPR<<#BpY31>kv)!DH(4=nG+2cNC@tyu>if@%d z8R{_BTx%lno0jh5&0in63D#O&yp=_Hz+x8X;*jFIMJzHRbNlCFK|EMk@QpT5#}dAWUg(Dv>`M-wyf1m!ow~*k zV09g>4S^HDLht*Yv~Sh>_Krdft7z7!&<%>mSwT!41@jFoJq#V;=Z4r|C}nj>_@xL! z0>1|gzvE#i<>=?C3csqckz(Nzs{&7^>|OUA_;yr>$ZCrbVYSE!NRu9qVM^yi%o#v# zAD4Wxf4H8#3E`otsimS9qb zTr8;aV=o0LRv@k#aqA-Kq?^eRzKJl;8`ec<9N3#AE}*~j8V%+thzoC!saW1(p~ed| zJ#tZAxAHT4qbdzvx<5TWB(X{Ks8MBO zzzhieZ$gR6coqj;IBs2Py0@+*HvaMDJ&y zH`+qmKzykNV3EA9sj6cCLsk~@H!y4TOhjtbriu}gJl&8GZ{;j6JH=O@_cJ&A#uK>Y zne4Y7821YG`z?G-8jYh;P)&V>RXg!^eejVo{*X<_=L=>YnkY9L`i+DEb zQOw>}b9T*{2{f_x3XlF9b?+6`WEZyUhTeOxL5lR=YXGStUFl7VG^tX8hOS6QP(VRI znn(@3_l_tC2uM#t6A(xMF-XX_-tYg{UVD{&u=W^xAFKn;G8h>d$$aO0=6zq+hTjgU zpyzJ^;&G&_a71W7JM{MI^6(b50+<9!r@xG&K$%La=Zr zrEk+7e&&kXo~BG+7Y$fvA$|%TKV`+QvI1EH0hkWDy;wuCZg9>?sCy##FYKPqq|80n z-ILQkL+eb@PrktrnNR=T*N9(SU+Q8P(C6mM8Z7AYp?(^ZfFRpcWO&h|fCPKNBo1#v zIpv;GF|BhE7j3iO80B#q-2ee#pO+r)7-pC^JsP=C*7=z=^Y{lTC_zV{*pS6c4alBm z0fd%9(||`jw~sF_tiA>}8a<>`d(G$kfzD}(C*~uJ|E4_F-#leg`I}UIuOu^``Fo(1 zdJ6wSW$K_K1)qn-baEEt&Bph$O7(+7bF>B(J2^praH@xWaWT{g`)YPOj{) zr!!_e<$a4R@h75vciDnKHD`h|T(GH3M5C$d(aYBll&db8-uj3kU6tsIBD z6#%sTh;b$hy+Pa^Dc+KOy(YvM4n{*-3v=;+n-%W(<@DvR-xb|}7{tGTGVTB6PfMht zKre||&iEE(O}S7C0z_uzn|oA3f|mpkH(P19G2n#9$MA5IYukl6Dfg&2CSxuyMrB=B z5ztPi(NHPD{?n?LZ8tRT(f;?k7nWdGk5FvMT589eq_LEMw7ulHmI$^7V>_&+v=Hkj zO+E1t$zHdCpZjEAq&fGUam~uKqDFeAIi1-}X7pxjHY+Zct)Bb#sj#~a zL>0zbLnQ4Rn;xXt1~20nG~(^(cJ%upLSOYN)qrI&F;H4#J1?q zj!~Jfh*9*@J2<*vv#uRNxFfyvTrA&2JMvfq zKPs3yE+Z8Eb8IUvH^txojZKVPJiC)RwS!AM{_L^{31>LGoOkh6d$4ibYriE(Z_F=6 zYG!{wKK{rq_s<(hN6@%-p)eaV{2{$xXS!qKY*F@=>S46- zWeLy+p<@CX-E^%~cJ57`7{9^4qq4BVH}@iq?g9o1V+QBQkem-C4yv}=5T&wj;Zqm; zi-?5=&!#M)239tiPgVre@vUyz#s0SkB70T0&DmXGF^gJkP?zxuBdixfQj6=^Zyy%R z-Od*-?Awn&-3tVXkVMo5PI*lXZ))7+Uz{@F7yrVC;nbV@$glDiZTQ6waf)|*2I=zS zO)u}!$wz!6r|cw(BT=Rugt6$yB(11E zzGY5n;b-+o>P&-}P%IeDHVWbFeHe{Q4&wiId;u+v);-)ci7tB39>9>$QxXA{_y96~ z7)O29sKKB5w+F@c>koQsgqM7=*BLRJ_S7{kFzgB2yNAr8#52uzvjel(81q2=p=|7@ zt|Gvg>pyD7|6Qg}q4g`5?b4pos$%+|bCD?%Xn_sUGC4)fpZ^r1sr@TNlX?HI5Y0Lr znHwjNy?T*&p6yc1;44EBKs#p;JWqJ8LV&<4KGZZar;~hzv9nS1w9IN-FxqBoX1`%=m>@jzJO7Ss_TFHNw$k1Nq)c~ z97m#MSDA2!d2$Yrkr6W#Kjpk`0qn`KKu&#r|1uRDPaD?98`eDM7LpiQeGerRJ~>1V zh|kDrx;kY!o35YwF#t=&8#Ei76`;}78Qe>kcSdY;b z-}<_dijDr_RQm>23hG;iR-RO?1V;rU@b9zFU0Z{WeKG3ee@PZQUX+y#7i4@k3=wn) zYqFzy$Y;C}xZ{zOPC9tbwa30Xu?y$M4xnESZ_z^@o0I^Kmfl4DMB7_c(1(n7g2M9Z zH3kjpKIDm>gKViM<_ojjv;e!fF7oF2Mau8mEfRf<fGY z%Z`^vKRR(6ywZaFn1T#==7`Q8mlPiD&P=45xuhVxBUsBp@cglq=Xw;PCc9YP6>Rli zzY(h)fPSapVtGk2tf#t;pV|e@zAsWzl5Ws(>(C091okP;)XuSOvs8~nn zaFL3w^K!9k*#2})_y-gKJl^n%*lOernbCY0wzbIMFO}uT5hC^IVnfQ6eDw}~&l3U* z4sQGZ^mLmP;~70$#2k+?eVmr zawux^OmKn9aK~A#w}xDnky8oUk)KvI$^D6sggK1pobE3Aa-iW9Ke`~*Mkjq5UCfpr zwCtu**HxOgX`6uLRP}IgNT+OWRN+gUYJQoo#XOHALzifq96@LenI)Fg zJePA=B80TQt%N)8FuY0vy-oXc$&B1gyb*k@>l9Md)!G`Nb z>q49EPX$teCW3^e;$ois@KM{VCGJpjDS8q`tzm>7J`-~byh=?VBht96E4Z0_{QzSa z7bA&fyM$sDANqfU`Wr1QTQ8jmJy%gGTvb|Mu@Zb$sy-w!uOhr_Gy56bMmK26Wq(uA zD_)MZGE(pd@+2>Fj22+Np>@Od&;9Q695pqPuaD-08h<>9zxt~GZ0G+ghMQh!qgGg1 z{{qJKR|k4QcfMEqukv_+u+0(<67WQ$Af&@cF#FiD2xV|>odZ;!*(rmcE|0zWzqSvy zKQ_s3au%$xQC=8%R7u7!7(s>K5a-CTEX8h-AW3%WyN5(iGd|bYr=Dpu&v$~k=Une% z;g_(5i=dQZ8nkp0&f5!Rsyv=S-o7a5@x@2*{or-Eu^@4|D8hI-u{CuFK!ux^;-a$< z_Dih3f&;Bh*Rii2#Lnj^Z|w#N@1DzM;I==N=f)#h+CE@|MqR}E)x47wYU|z6w_c5S z6X!ENcOqO9pbnOk3(55VNe*X&XefbTH1b*oKdZ%v4Zz&7!jL`h=eLIr2Z${Ns9Uxsv7|csdU^2`wU~^1cnwYP zrRR{>Sh&TgcWVUBO;*|)6Eh@`AJQoG7XUv3TODj=4R*ubQsU#glLAsd&@nhhYep1N>Pcsh#WB@4I67=u34Y-$YIC+70MjYQHr%!|uJF8`n zn?RIxF82U%r|T8%I<&>EA&*{g#gOzmn5Pv67S$UvK)oi1tJ9z(`68XPhBFnir1|1R zyJF%$a9#lDOp5{gc05t-{bD#~;NltQ z+=#W0M0HA)zgo!mZ(?A&HEBB`CVTvGKL!_9{qQ;>YgW*UxpPd69Qy0yRNr zV&cbO)|hEt(7XbeVGC-Dy39vOdW_h#Y|K^(b&mAss70EUgn|uE~V%H7U=HTFxQG1ks zdse@x#G>eaSLWvJ+~LgnHUbq6?&Wz`wt+{E@R$Ac3jq8=dUR=16ed@}g7a)6@*~*f?6-6;ml=5A+^Lq||FWp?-T3uIQ(%I&gMd7o&pVu` zN1e~*_9$m;q0j9Wt9sb7S%T2n%-!bfI3juzM#2ktSlXq?x3HA3A|e#qSb+FQjQHZ> zJqO!YE$s)i-z^24^o%{u5?e@`ncJjX24Qsi(E@D0E)=BG)T)NJQv1c5(RPh(%CdgS zl%DO~R+9n@fj%vLcEx5o23B(7C+4dVM9}aSlMW&|9Fpt+Nw7AXAC-A0S#J4^ot$_o zrJM_jfNeZo0DiOj_ZJ*zALE@z1DF`ztB8dhCAt~{2YlKOn5<)_Qfw%NLHWl6ws{wm z;6#RLl0-Jcq1kEi8Y$K5MIaw^JP=fgGs8Moz--ZnY`mOHfUoiOs}T00({lSvd#dYiJkz>NXu|_7bs5l!+SOmKps z@E$w^Ocuin6~Ehs5l)J^eGntsUvT|!A%LGk(DzovBb2X-E=a)VXt_yMp%W};>00*> zh^U_eCp(X4fEhKlV<?F#ZmYO`gQ$O=a*Y$ivQ<#JOokGp)JkVoFN3*tIyJ%E&K& zom?0&yIGm6H;;S^rU0BOp z-Zu#nn&Y0VmKY(5i9u^Hq^jESijyVb^q-WjLkrgKcIO+mzS37b)Ddvh7YG>E`0&_9 zJs>!^<9w)egv;0QKD-MHVs)1fWjeO zuAHk-CX`Y(YKubgrgyp4-}Bx_WM!LaT5tz#b)0}ZC8J?T!OKgEc_{$XjI%-CRHQMl z7Uz;QtM9w_P6wwtawC{#M9=))AZkD1ojCn#u7g>mTuv92BP$!->fj$HhDRS4*J^uuMfSp3W9Ni+(@P{PA?pZzh(X!?=RwA{x zF-66n`1yUcjrCkShX6IKrt-u8DYYobKlA_hM3xS?jLq1DydD7_KdmJxt~3g@8Rnz? zk_(S7GSn_NS@Qt1PTP4J=tHi1Ff;AFcsbg2+t04;?_JTUFgf6+VA;YgJQ{otNOoE1 ztnBI#Yh)ROyU|_W?hnN}Ut0k6X#LWTB$#N8;qBbsDW1M{-LUw#yS-(>pV`mu^K=O{ zrfeY>xOikx-rSFBe%xZ7rv?dw_ZRa6WA5Zo4x^ts)!k-4=~lL=XW{;k*1dd$IqlCsoXT-{rXOb2 zdwK`V%eFF6{JLM`#-6{N*2p6?oZ;Sndz0H1O9nGs!YgBAcJ+T}w$y)xJ=CZl%l(oQ zpB_4RJVG7z@W_=0T6H;&{f#=MGDd2eIAwDAzs1$2)Yoq(puz;G>khS?x5gT!o`wm6 zY0=;;K&LZ1nunJ_+m!tctPG?)b&~51_*0(E&Jw1=VMFXms0vEGOhv>r!j!Oys4S+c zad-w^e&IwRh<5D&cqqbbbIL!Qm#vztPWvb-R-5LXCb7^%{dhPlmcqfgVq zjTY|OGHSL}=#uY-t6EFR%`l%a7k!8ec|`+dmNy9!b8_s_m$=8lV{Bt%{Yr;Bf$om+ zY$Ld(guC^IXFizJEp7B8OvmP4dbl)BY(RvTrlKnP_gr2(78v_*|eK1Nk}4g zz~u-PyXvuBlHMyRoTy=pnojHE_q5wh`<$Sh%ZByLQ(3Yl!zQY3;Oya)m8!S@O@|S< z&(9y?54pJ5UH0~nODbgXEM*dTWtP@@i zLAF7Bv;K0&o)uO713J@ma&U~laQ!9uq`_B>Yz$6yVA%#wD{D=4H1S>bNI_*HndOuP z9@oXkb9+AOi1pwaNbYElWnw}-?@q^q9hDqQjw5o*9bXADRiDq&v}pP8w4dTRaaRj5 zQaFP-TyzNLj>*z)8%A*_$3$`6m;IbGsGnD|-&*vj@#A`2$RPY4;C!&h1!8lNS1?Jq zzi7}$r)rbrI)o9;@OxOZ%5=bQ3*AOR8UBI-qAV;I`iV7md)pB0^7_-rM^m1Pjz{*3 zMG>QumBGC`rz~lhC&)G+J)X>2HF9(JO#C`SeriKO>~qGx?rUn&9XBny`4&7FW{jyC z{uqV6)|ny1*(S&71d4NQ2e(=$ z#!r(rxjWGL2{_}MqKehlEN+NLYoTv$+Q6)Y)V_rA&W&iX^i5^I687|37|#f2@yV9s%!UUJ^JeYo{&5r7iy2+{2APK+#;gIM3Y7BC+ongT11JRQpd(ZR`FprS1VNAxL*Xy7ewFZNysTlK>X%svi=)Sp*Cb$wzZ3ht?o-`gB z?=+=~=BjRpWamt5cRhfAF@7M#xwP7*lp-uEgbMl`8Rjm!CuiFq#k0E$o5|Bt1ohUuYA zX3T7IgYfrWno8%xNKWDW@LMe@A9LuriK9~~RD!fa(u;w9e4j9~YHSvayS3=Oq(M*( zWJb&*(zp?0%u^D6YYEMt>tu_!$-brf(9H*}JQ>^Kz{}5~vdCf3g`FiFmttB_zGagU z;d%6W>6ESj;B$2jr2qwYR@3F1cW)WO6sRBM`QT_#y+G2-ky8oCd% z?EwrxL)g>puarTweh#blb{n@ck&oR>BHwYzwFwooP6OA6tIr8B(C+7s)*Z{8E%rslt<*= zgs;CM<*+i)A&yWy4;NL~_y$~!{ExDl=7X2pC)tP_`D=1)(!z+cso>?KFB?MTJ*tLY zvalB&!gppvmW7W?GHFon2Hedlr!k5DRv8=p7^2^AdK|xqyeDwp0lUV3{nK*JONCp3 z=f?2HEX8v6b3j9vhh4b8A8#2qQA;8^+fvOX)Y#yT zW&{9D+eYgg-=Tsg)LCfPh3bXyQBn+&G}^~;r3}_~H-f`(bXbKgpbwA`i+l#0LPk0Y zPSZ}TIayx>1PKBYEzvazBnz%eAjdA|06LVLARPz2Glo~szbr?Wj7q2HoUU{_)B;Feq(sLfV%Pejlt;7DfFWUw z)W(V{?9Spq+sB3M?fBkgz8?)b50eGbxnHRz_;vx4!WLXu4~7C;jW|u*qHA?b_P}U0 z`J;HA-tcSPpK~zfy+5_^$DgnzJv|`BaFg%6Ag0R$IL!^|1R|P&De}rw0f)PW0$=U& z42TYTiB4!>$`vs{n!c4b1DTAI4uBhq-C=vpvwn%0j`&1Mc527-h0GxM>v~QUoE{xH zy2S$}8_U`*q4Jw|^q)_O+?dQ>pTx(?v7Ikzf0@WJ64uyPSpNq0j+thYU&pVw2z0%h zHrD7~Q^jL^Qy}%hu9W@%O{tWOP><*E@)GZ4_y(T= z7*v(p14P*ISTNSr`kJ*b^pTv?mCRJc)r(>w;l0Fuqo=Qr0g^VU0px)#m1_l)Vma#~o8kg@14Oi}nL7(adq zrjL<3a92e+Qhj)dm?4df`CL%ZhHV zXP7&Vd>arbS(fV|r<(n_eNI?n6w$Hd28z!;HV&{(~*AJahqK3MkY8T;r{yyfYbN$D2v_tHcg28TmnOo2};n zCC?0Po)K=I?8398HQR7xD&X?P?Zr~8nvO(#xu*z`%IxRb%pa9s$kyku!U*$Y2K4MW zUUp7{ARPV26;QI~O9oe#H_IDx1FbEZ#zrn#k+bYKGJNN*Zx#RdA#d}q@BRB*AbjTN zSh)7KuC5Hg%)PYR&Qz2-ISH<*Q5Ae0M<%#@C_J*2#1`B9a%2yi_6ZuXKL%lLn{<%W z8VAB@pTb5To=3qM8>tC+{RydJYtoN5_kk;$Md?;kt67Kn-81ROw=+=`Aev^ow`xl4 z^udGN^oI4(lK%_b?GQD;PDh3LRjk~DF+HirFmg?{ZJ2)jzV}x+$@ZLLggD$u8h|HQ zkdlJNo^53T!)6ErlnC+rC4lgcm_M2M%O%=B%^ga?>{64eK0%buC(Dw@g;@5`m zVj;{)_SiIQEM76a@w^m?lHEgbP8D?Yws8> z6~3myYEj?${;AP)zW8BJLNQ_?&4|`ykQG=(aI}2bO7!N4Wk*M>)~cq#>hdu|oJ_L- zPiEYi7MlCwr~kRa(LilpLl&XhezmrBOH-)%;XY@+QEa1t)n5{G%D zibJ^kNdvUV&IMW$6cg4}*w-I`q( z%;ZeCN%8k&ksw57nNy*!5VTQ4ESGf0M*Kl9W6Arap@0*|S$_OD>X9Oe5$@e8$sL7c z3{#KD=L&J|QukiZ)T<{$vF*|mK}0?_F`NsE&z=>jn|$yLWv&p>ouC$(5c0?%EZZj%q;4lgg%|=&lLpteq^`OH zJWu<#e%7DoThN!Nj@KWx%7Zwq2D*RF`Nw3;` zP>GQpqdWPa{}i=lra77T`=<;?u5?(c%TDYT`e+!9y`#H`Dlu<$&Ko`G}UJKBie|NC13Q>d36EF>BP)QL{-mkiVtlyi#=Gi$r>ra1LSIQ(igosf&_KSLQHC zvh4u##fHxqDnL(eeItsVL9Bn{v%Bn-8&BGtp_!;EA8_sq9 zLsiD2J+>h)H)XyVUg@3i9^*mqoD+3zAukL81OPM zZEQVCJ(dMJYpu+7X(Z6@W9eqcVAsizJd8+ET{jMjA$i6!aolvRZ(6YiRP|C-c-Kpn zMSaOiZqI0kPJVte9$X%I=arG90@=nEjkLt;%LQ!TM~MJXPlx)k`u0Npt88Ld z$#=NEx~iLq{YM6?$#>&tqHFJBh_D@(cd@TgcKzTVFi3N6YvHdsyJ0@I+*I!-y8;-))qXWUNk-ro}$*$s?2W6?~RU|GP9T*9%( z+ZpN)=E7$gh$@d{qu5xpSJ&!XY{RX0^PTo{xI#{#?97XCf3r}_FE#X0u|GzTw@Z-q zQb#u3Y%;Q`^Ay6b==c%dj~`~rX;gYWo?hJ<2KsH@6SE}Pn2F(jPK@D?i#{b;mR^W^^J{ln?F~Wd3AmLp{5Ga4 z6Za6q1Q^P(YY$3nzY7w;rV3x^OzLS9mL(XkexS|?$?<){2XTz`b4{_qvvi|3dt+D+ zAoMUuwOzTRUw$xEHWP3iN_tj7*-*)$%5@gxr5V5fPQULw}T3Fkx@un z`?IG#bI_IqqrMFuHBVQ*?0z{)V|%Uti#Y9Z8nZ=(xF|_N-QB*3Ofz$$0wMMIzke#A zjw7**IDZ6Wb^K8#jn?!?KGWvz_>Yq^r`v|H62`$CJ?zV?1KhA@s0>Hc%TdjsF(5qJ zvm7K;ta6_%+(39bon%U#o`n6Ec&st@2;-=Wjede|xw~9UN3~HgH}!}wzB!-x-Tk|B z@9xVA^Qjz0n})xuhxx2C_V6Y`v1IPpn#h(71f73v= zf&TgQ$4$KOJaFrBqP2F@NteAdP?*&!I`hRF{6hXxpyhjmh9)L$=&45`nAwl>o{2Yi zy89cI%srVwddgop_ot3KZIgksrysC}T&Vm5+Eq9I2ek9_>|c(iD4uT`V;R0uin;d% zF7(TUTmQ0boX8-xDOcVNBJbif)8 zM@HJ@^c}yaqp6u`F%**_RM~#?gx*RA0leQOz>xf@S0@}rmo(6tHFBfizY0*6Y1xO* zhwGZ7%jmQe+=jqk#Mg)eBq$sJD^KLHJdiKgd$S3~#5817^i(9xZ*GcsNOOE{a_fIM zv`>r3xIs0 z&(JdS*^lk#YZdZeUTur*ht{Z)QBQ!L6pY=JYu%nkWTmk=MAV`UjWBmyt|Yx`)*UGJ z`Ur*C>2|uLIY^ZD`3T0zh0m){FULz#!0hmVQ;q0a#j}-@YYp-gYWmah<417Lk2T}jWL7nVHtI7pebc8zA%_7_6S>WrLR8r76IW_io^u!jo^*#|3kl>65;76N~ zrX`JM$Ov>^Wza_*%2gw7bzK)@D+5N7hb1!U9_deJ{C~pN)JOjQN$FKZ7rLS^3!i-Y zVO&OXE{V*ss4Rc+U1?F>M0Aqub>e|574~QXFOAJdQD|6iXXv+?n&}yRsY?M*mW1mX z)WlI=681rcA#KtV1B5?Wi`me#X#k=@!q$_)Bp@Kv!)y|+VWna@XRBg42nw^~Blj}GcHiA^ZK^rW3$D$y%X?0c*=t*elU?X1gXv>cF+_;` zVV)rCrQN=ce9Q5M4@UWBH3DJ$BF+6_Mqh`9RA*J-U&UuLN|@j{mRR)3k;4Y^#%lLe zD&R$5_f^Z3Oi@vV-RS#CA~z)!B6d_$|Gzqxf#Ld4b4DncwVF-21kK~K#%r{@w_?5>DYqF zKalU^Mg?PqN9qLxjGv3;h!OcoG{UP6cu$MpHdo+nR{|d1?6|#9FHPafm`F!`JX|6= za9!b@Ida6_!YqvZy(b<1Y5l;6KS@#IjIE=a@)JntUGgANPRNlfxu1hnQ@PyjriYqo zMie?4<_vBsD(j>n&aNX{sl1xl2el^kSR%W8qn=`jm{n?yV9NVA%;$!T#BaHi#|{w2 zemR^mc0j5`dDPV7+Ww-6e+}F+M3#43%=O=C)GwE=gE5*~g5ReTX=`r>Jm{V)$#&TV z3K?YUKx8vTBldp;LNa1tcc_=;h5C8}B$^xV>yx~D`yK?^$$3h7^7}N^MI8f4Yz+b( z6_l)of$84=Sc{UW$_Y07JE|iPY`*E?^ydZ6ya9-tuxg2-7ngthHdWG*>|J9t%~Y9L zArd|MmLT%F6C94y{1*Nr_F+(pnrCqLdlsWZX!1GVD53wGCp>b>No;(WfZ`Zl}Bl1uS*>%1Q;OFLMST z4Q0odLYpkQ1jRgy)d>BEVNut1xFEpd>y3_04zF-lL+U!E`3Q78%VkH|XwxoaK8UFP zsl#mxMwe_4jKUKZHX-rhED1Dsf=YrE{1s@YU)8sD%gjFDD}KqJ>D35!Sb=&wp|@~M z`=i26tvOR|nhsQxj1NSPiJaC98Xm;)$?DoJ1{)IF82)cBsg`|fSz8y#V%q5^uBAzqfY5>;6~qfjlJ4fmwv{Lex78=ljX0; zB_rq>H!~Kz0?wT=g4k_jL@VqLLejzX_RY4edRckx_fKXChdV_B+6lcXlINV&rH8N`iP7kxi^uI&6(um!(ScX#Pcp6gsR90k9&am8PIy3|Aq$2!J z{sCcy3&deT-&fwqno|C(7AU>eZ=A%@imTK%syW}e6hhm;X}gf{qyYwjWsLyjr^SX} zf+}WbDdL zZ71LZ)fpp*{`#XG7VcriQT0^Fy|R?Z?b8(N%_E1kJnGB51McQA4)O0PT9!XL3I?}* z8pM&xNo_RV1~-)&5Tp@db=CYCJ(5PF2U6zkX5wagWYe+q0MC=k5WQ@P zEk~P-n`}$U>*U=3?jce$*?0~#_Jd(f2U#@Ob^vobtJI%Y zD83;qga0zL!#23)bR9Is;iSZ@`hc%};T1$Lh6hZ9_14D3{yIjg%wH}H6@YQ3QklkG z1O3ML4o9j8Lf791{*Y~98HceaO}nCB#>WW4JkSNuAWQV45}JnWIv3Z(?XOj)7Mks*%<|D?OrDtjCx9hiy^WP~x8D_iX{7%7$vG{cXEl zH3TMssiP-S@%*k>fJ3y@0+0T2vwC$K;KV~zV!S}ebop5~$Ok3`&G7^mBHORugxq#E(WSFPF-rjpDoG6Lx%C?Ga zmpJiAwr5@wXQli?{`uFSYV-$O#q!oBG5XK1#fR5-u=)1bNVrlZhTr^{f`1`!H(SE$ z_Xt;(*h7M;sQ{#lRjN}B{eVIA(K?u0Dn;=BtBk zMlabX01H)!@vvD1Yuj8@V~LJg$*bVs40nM`Nu&=m0a$OI@HGpo-KqTU{OlY5=@D zt1~ERRq9!>rol%d)1n0J+fUbP;3Cb#J%$QYEl>D}2O ztH=IAb@h`ukx()RyReHh=;~+$-0VFv4Nn5-1XJZ+k@XVHq#;lZeS?hv35vF>l)XK%5lz{a}s>zN|({y-t|nYJUXOQGzYb3P+-e1{)$RHXCgk+G;04B z549EkTZA;fc&iOt-ShO^fcQ{NS_-hPPxCiAtInHb4T|XSYAoIG7NdJVHnCz=^2-vV zE+hB*;5GYq%z2jYo9-n{pA)zit2MlfE%S>kP^zWq=xYRAWvIuGRNdV(t2j8Q$>FPT zkex4>Bu1dJzqLAYdm!!aWoa;?eXn)C%=cqf)hDV6jz<&xVhAm+gC&Yp<)MOBIES@DMs|4UU5xS;s(-~RebumWiL!F~w3 zA5fDeZ)&uoW3s9?qmvE!OADejN~I^Amzxk`swx`PwqICcP3C{|1X;DsU8MO+OT#^l z+>(SO&W!dcYsdt1LQDxR$;Mn)yhm20M|^=VCK&TPMjE|QD7GQ0-Et?7(vC&rSfVF8 z*Is8+f3m|@feT5A9Pk-;j%x0!^_a`XPwr86(alelxHyZUb-wORQAclx} z{J<3G|FVEsfUNMZjqBTfTfAX?CZh#OpYY(hdTyN{$O`VVtFSKqe)mHU_UR`%!KyS_ zCH-tT%_S`{=(!P^H%Zs&Fz49U&g)@{PL$Wf>{rQD9va(3IpUXhkiTat%V0tC{ftm^ zoq+`n0z@?H9&vZxlHKvCA{(@0YA=_3hj38YCL%jYhqU#1P$xLWMPMF+1Y=06KD>hg zZ)vUixc6&MfSET zow&Ol9^aOW?#l=zP&9nZTwPVl^R?sWC;akd0t8YOEC#V$Mvl4uIJdx>qusYqk9rzT z7F+zJ!sY^%hW`kYXdrJ*W$`hx5hrHK_`6yT(ZLz=vK0qnJ+63+6QDyHqyzn)u zS}e&`N|E?A@9x^Ta&x=Eh|4vG9cF@!zvkTX(-N2q+8SLgZV_5viuEd-&4jeeTJVea z8glFC=BQYx)~1Jjr+ba(hEZaLP+KGi{kJf-W7pEOe)$P^lu9H-TnL-xKqmK|?*REf z6^*A$v*RTzK|l(hT8m{BXYow~+JCG4yA*%ISCS7IQn0TpqI|jd!*Zoy9pE@81*IG2 z%*w)fmtm!i$&MYqez*BN82E#y-q1F}%~vau0mDn$s57UD8Z9d?PSi%!y&k#7pQPr(nhpX@1G9ukw3;$>3R}GE*kr zBv1kCxJm;4y{HdA!Sn$*CiL2$p(qvPLloah8`AByTdwkIbIwaB6b3JBm zpm{6(0!)mxMMXYW)bzkJ+{kU2`w!m>S<6cme3yNz)7Fhs_S^#s`o(?b3KXMGhfY^V zkmXPt>qwxKx?X>r^945k0yQPVomFHjGS_s~Xb@_!SH*luC~p4a%m!~~q$|Tb0R>5Q1BKBgH z2UMLL{*?YyOhA7IWFRBJ(W}1Kf1kTE{o~2AW03cJVFD2*GxhqM!q4xMrvOVsF=kiF&c<82EMGU;s+9q5^r> zCTH{Sfq>u})=)c$1q(MoH#-TH`?IxQy8Dt^354*E^SmrOnS%E|hInShbr%C!vMQKI z!ui-4sa_^~na{nc_aEnOO;gibnDiu0_~DM>K$68XC{f&j`r`56Qjbq9m(#PC;daKNY#(Uja$t8p_eDAj4~`uOJ%C7}8Z!r>)-Y|rvCT>01W;KNI{R&{4QCRE3>z$Oro@^j z8NSQqh6hs-cGCEY6Cl8#fk!L-lD-8cnHskB`p6&>pXc>oaXlu+{Bd8B*=s&M5O~rw zC7|Xi?;ZV`|Iu9Ltk=VVLlM}n^i= z?jt{DT+{eGofzZd_{~E})ss@u)c$I8xdR;d?pDU-EmXXwK% z)a>{yTUDY4q&mcd{ek=pmpXWFb`#WGRxU67^(=li4d;n{f`Ai4vrHH8oY-zKmz`gu z#HY){{>O4`BDbWg9@Pt%@h zB@UB7n@6i7i3uPf-Z=eA{OBgtg0&v7!OBX`{#mSw;Wia&C}cJkD-S?YNKm~ROy4D< ze;{XIJ{HTY#SH|{C~!g5P`1%{)6L=$M#B;*pZBDhdcxdtucCD(H4eVOocY{OQzQPRK+GE%Dku4f`HzU%- zwG8gC0A(ZM#0hrhU1GBBaF#!@!*BV5)(#JuINv4>z(Ji{!2;bAJSq+4h=8huiko&^ z09~3u>uBWa)PmJdso4%H!0q<1^PDh9e5-Sxg%T7KX6ah91y(#=-7-fjr(Vn^_cPD6 z!6`Qee=c_iOJB+ut%-?43J7UUG+^Npp#8ItZ_57#DO3>l|5=Fj3?hK44|hLmyVl0y zA>@ZoQKapS87!N(zF$2*kFC!uHAWSoH1Y1Rc2+q_9Y}?fELGt;Tp9q zh|SdS@JIBijsI9LEG+sj)V*g=lkdOv3xf3CkrF!6n^Gha5a}YlNeNY&G-*L%DAIco z5EKGZl@faIy^3@VJ%~zAL@-F;_uT)z-|REbJ~L;}JpY;JyvUop$V`&^y6^9Gt+hTY z{uBQayz0*c$C@Vd=+4;s{oc%_K38P?2Ef`yVW0rRs}yJPBOc@FTk}*)Czz;hvj4+_ z2dy*X7UQGa(cTzZDk%XUKco0zW<9_1i2$bc35MxJUpq=0GA>DWA^)&L@3Xe8x>Rh$ zLwv-}=+)~M6|mtBXU$~e{x2|d3{eb4??d&dEGz1`mmiNyN$r>%;YL`{xnCn5x&6`w zW0VHg1I5BYCA!W9yBXlDdmo!_u05#YT6CweN&8&(dlg@PserfA#XA6QPkQ_a+-Cc& zCzC{Zu8auGpmVHfjK5Gr`=J|oC?Dp1v8Gb1bJ2k|Q2)%#~*(SdqJaY}|GRWNZ z{q1{H51p_coB1cQ+#jHMToYDV@8!i)n3>teDM&M<&FP z4RJTkDS92b0=&G$`(f!o_|S;x07BUf!5NK3FbQDGdwb`FJOjLntTTl&A5h+MThkx4 zEk;oSV)JP~;10d7xX`br+=gpr&*+*F?)HjaU#e%x$x;uc3ujCZfa=2_=Sf#c3|?w$ zGj@}iAX7ms2$x&6i6wA!I_YpdxS=ydee@y>YJ#tCLVD3d?!g)}9{I-kMREMHFVY zRW0_jKT>x9QWwG5{E~S9#B>HiTaA^tFLd*iY5=?|g^01y4-(TbqJY%>;;kKF{I8J! zC>N#;sHgx%y-1yAZ2ptkkheRo`uL1PW!ol^H~*u*IznjA<7=8 zmCgSl6w~zevwtNkB2Baqm2eH*&#N>b3I`fQQvXhd;C118Pi5{(Y40-~?^;m%b1>Pg z#nOzJSP?Nj7n6v(aM601T1s$PRJ(`xyh(kw$>gzY`J%88$jeOUen>cO2=1}Z{KH_O z%OSZItxZf6yrC9-^Q!M?ixHI@d?mPxRy<|x^L_n13Tyw8=Tery^I7dKTrYc|SG=RE zvB_S|dn8=@;p=9to zIvVz?C)76k3Et=O4rX0IOhH@N6V+7UdMjWROs&W#ilZa9z6IAPiY&BnrU1<5OW|cq zQYcaPtIX&d^KqR~__L2_5#&b)YweL`w5I^+89zsr=&z5gf9d}%0LcgO#%gqU=1YZH zoQ}q#8b5y4HhB<1F`q2iHXrHW{>YaxPQ1iQSz7O+{$ow34>URA<~0K>w7+z0lS)lM z2b193nY8RJCTQMaq^x2?G1-}(k#a{9oU!KIGln5#{rrchK7Gg@qC0I_ka{R`;I1Rt zroiy!?6vQYcGin=6-;&`;3U0r#LwcQcg{7Mu%Ux}F)u8lk@e;0C3mNVi+*PBl08pB zf@8V6xHEsnqRAxk@h96xRFF|*6q2P|wNj@tVlXVca?O7AeU)~Govp5r8_CVD?&u%* z)>n&JIz$jT57bv^0<^gmwFO0o>*F=Idq76^yw@j2txMd4mYA@UVbAY=rsT&vFkFtx z0O<}FLIIFdRRF7dI=XuJZ{N^cE@ib(CZ%mfMCU(cd`h#7tEi5+-sKE%{3KH@8SM|c z1p+YTqJvOsxD)_qO1qX>Y+glj=$t33zEtG1a1yU0dCHleU}-w@E7JNlIam5Hy8Fem zC$)!9?xZUgWsgf0X~e@cK~irVoszbS$nRI5p>|M!m`lI?X9yC@D={Y(1XR)6m%ptZ zX>WB;b!AwE9KY68lm;5f!(Ui^KZ!)h!>YGu(`9-EJVMURyk11MGYL5`-EbT5tN zGozJ%M2)Bj*Wdbwh#k(3Z5u2Ay^=xj;4@oR*PVxEmr6XH^A1C~1k|h<-@m@dA6*to z?eQKu_$_$8vk0;S3Mc3p*EMOB3VJi|E?(rvg2L+2pp5$vqD`Rn)%rF5ai4 zSwhA*p11zYKYv|yy<=!Y9WCY(!c0G-xBA68aTvqB8 z?OiBtX)M^69o$fgb-<{zRA;RkGjPykSikcTHjoS! z_$)0At_n(8T?(f>Y%Fc!shYoG6)52tt=PbNURe8Q*idIwsX!*B4$KG{GRTxdoj(he z{-fth%$SAZNPL6qp<|(A&&*mEsi9y9( zXq$nt{Y<>%%}XBC^$fJ>&zIm@r9lXLNf%B!)(<@Eb0^>;l*v|m9vr|kPN{0N7N&Tb z;qSen#*X*a12{t{v^I!%TIhP}@=-|O!)q5IN>9o9Dn~sL8M(N_U)nELzV*;r1ItX{ zDq9GDNk4e`4R7<_}q-hH<`{>*7zm6SArnKUJ171@JnCo^nbkaODI(2_QkCXc%Q1SNsEBU4)y|I!|J zpayn0o_+7?X^S6ETVfK6ahYc#}|;N ziEqu+N-2FUoD*6MZ5ksKKu1x3Am^zF#GG^_Kq&g(0bTzcwCn%E_dx$F{ulje(KaGE zf&$_J#54-XqtwLkfHl&qleBfy&vJAX=L<|b)^*FL#Otpn^|DM}i$PQn%g9@BF`TB| zXRsU=XM4>EB}c!t)#J1wo!(~6x4;gtx@g(=Ge-wROYHXie<3RR`t~Cd{*U$prjx(V z&L%mV^h?}qf<_|vVAV8_*gX|ge4;RaOdI+s0=+jEE63; zgy4WJXyZLGU*W+!^q}SsQYWhK)71Bcav%Aub7Zda<%V$yiC=BTcFXKsLG^L*F$68t z`nuP0FKbs*0d<(W`>(B*9HC!M8@j2wc0{t|<6 zNtD62rpvyO?<*zudC*7Q^XVG~JV)Lm=*onabY4%SbNYjhwnC{|Ufu5QhD`$YXM;3C%rX(6VN?9CC;o2*9!GYp4oo_<$r?&$7&t|E@_u1>e17^?BE z$REA|*^!7YZ1Gj58V6jz6OXGTJj^o_u9L25Zr=)&G+nQbH&rBeig(irHthCuNml~>(5v}1qy&DWouud+)TL&RTig3i{)x zL+fuY-(|k8p2vO;@=NCx!{rTk+?suaTMRY%v~{Op6+E9PNJli0YR>VCB{SnO8AeSd zPh*8|A=?W1xFc=gJg42m?m?)lG!33} z;Ydh#S20_U+vIkHI1zTZzl;6iO9tKCVCV(cR$Gi35gs+P@|mEID_?m%AQ&CXRA2`XMdI$&|GeM@`Nq%eYgpQ!%Z_yuKg8KWY@ShGaBGrL`EKObt@nWPV z<09y^iyd)R28E-WjYC9MKVGt!zODmyE~fu&#_~N-IEU(va6xik&q8Tx&x|+-I!o6B zJ0udTU-G4-eX}+eM?13QXdaiWCrdD`w$_n-p6S}!7prtsL*qLt5K~2hjX3ty8Z~im zULjl#8jKTOdRNuP&V5r`+9FEC@;ccZB@4tM(lU%3jciFw}sbCzLu0g40u39T@ecdFNxhDFM*rk z-`2p5@Oc3k6w^KgouTRP*pjoY+h3pN?(zD)fa^_{KY0POch7W=;&CopI>Qi34>l0x zv5&9Jk&J^ux}Z_POpLaGiYI>pV;A?0mjO^@kjXT869EE*AY=*BpErjPq&RBN7eU!~ z2NG;;AAwh>YE~-}zqsDsQ9eI_I<6zW7L;u^J-3Y*bRNT`Ls$MSGi7D=6c*g9|%(K;yH=m)kAS5^JSKZ{CV2lKZ*r_8(0Y%p9kIPF62CB8% ztN;UMU}%lKK``H^2l>{mbI5IzU+In#f`;>F_nm**c(MgT?HF{^D9~1X!K&uWQIGl- zPJVY)HY^^MH+3X)Ve)S&*`=gZ6X~#^$|El^o7gco&K;hiA(YV0sp4izMqcY|p+fxk6hNm#nm{8UPM|!e zFR?rLE|MUyjCqJ9%(Z^&OeHM&B$egSX&=4KHB3D9F+=8p?_WOJ%f4Rr#HDVmOQ`7oYK-zDTz<-3*X*T&z-_Yi$J z)or6eP-Kx9-n|(95Gcf~h81v19nCEfl9j(hC37ULwORF?OD3L@P!(rG3s_9_Cja9} z%L9qt!j7yYH+x6iTAOe$)(fTDoMDg>3KUhEB;(5KW!W(-kSd+VVoDg^HE{D%;8Q%L)Ee-HoY!m!ft| zANY3Z8~XQZWO%bJQ1Y{uYWftl+a1{6_q-&PhD^DMdGz1ouNIS6+%3(1zF1kn!7k6k zfdYwJ_|gY>6O0Pn6LZ2ka31K-Qe*tV>Y?+11*^-(Op8t7o0M&Tt5aX)Zw3F)5$o=U zkN61a=kAg^&$fG^h1no9KP+^oZhgz0^XNea=ox_-Ux-DHf=Fb5)fg}L#Y z)@5aN>;!VmS(y1-&~~)Bgbvon=G8O*(LW1S?yvmp;@E0fDrByPS_bnK+ zXvv-2m3<Cc!iN(+7w&rd>8q#01E55 zFN%sv1yz+db8-fh-7eQckAOPE0Utkr)ZY)eS?Ah2f6d*kFcKjzd+ICoKvMNNL%n@j zmk)8`1&kBIv4`Ns(Y0}B{p_?~F3$8n@*|mK?-M2-+p(UmNii9qsU+p45J(FlKZsB4 z6}_H8^7?_<0TOhtVxBq>7}tH9_?t5`ZEvwAhN#Ns+Ks5$sjZ6YhNAa(+R*cY`IcF9 zpB{czs(WJlh}XP1RxU9%K_~H>IQ@o*Rkm+@cPTE3q>7ma$HLhZs6DtXUbqQgI0>^m;-MgG2yiP_dHy!q8N-W&!dcY$b}&H zo1jH3Cevpm8@*&dp*Om<6k96b-YdS{U{_tTx53Zq23*%RJYF{Fw~uai6OD$E^$r69 z3&3ME`f6pc0yo^6WuM(aGVixmY;0@@OYnYP&9VL%qi*XN5+c&V5&!2>imh+)-@{a9JB|(OC-F+s%{|<0q+6m)g}!=|N8DS!E&U|*HR$kp^&-G zv#s02%v4c@ZQKp3k&yd`HGaVoz|egVCSCVbS@->e)3THcBs zN-M_wIu`Umdj1S)CnsMAt36i0Ji{oV%K=d%FEZviL;HNei!%U7LAD3n}$;09%?x?6- z{2zVnbx&PByXJy#Ff*G{GR(oJW3MoB2Uj!UoJ;9I`FTvpwOWEkzJ4p3p6`{x8~UZq ze`CAVFR8d!bzb!bf#rKEN*P1L=`=C|6=WxGs`&gBX1yjVzV}}__258<>E5{3&GXa_ z+~rI_kaNzR@mjY4o{9L0Q&(!vObfFg;^sU%{OOcXN$cWGy;JLJ)Nb*r}?mBQdiQErmY^$ zM?5COjP>NcR^K10@G0!7*+r(PvtGDqvAp}&%xfNL^*`*G5#CfIh>$#ZX{xJ95vMS91U0hFA;2ElCspsHK~ z+2b|m1!ECcZ%3@mLucL?29RyDEq8^~J&Uw(-x7gO9{d4C77GKedFv5sGzB zCpt-JCte|a-oc&pH-3v9_-Rs#n6VBe+bA;lz4S4MTb;e$$x(`Vfr#Yi&Z+t*35TQt zAJCIVDktXUA>{LrW_>(~?o|p{qMV?di}^7$^HG!a^&iG&(>2yhg9!!6q!iLLp7_ao zDMWI1wX+F*?nY^V_m)6hNzlU8^fE7lDLq>AMq(f9$*bA|5I2d7ZU-gsg?k*8fh7FW9zdswZeH~MIfRn}FSHE-t zau2oGCX696@|hYN^ldeS-e%2qf?4AZ*}AoQ1OKUsCPPE!uUzinJ?lR1-rIQgQ3#0| z3ruwg2<@Du)1Wz8D8Tupzn~MTay@e<7p7|3UKeCZd3^U*&!6K5Km(v8HiTSvYj}^M zby-tSnq*kT#xo|D381aBn&>fL*!rY1_jhFo^rg=hcXK68XG9FxnC|iJHn-QlJ(6oq zu_=AvEX}|iYsSbI0R-HU7Hvwv7R{%kqA8%9?IckOc-`7(r9 z?H-_yrlQ(dyC^1Y5{TSCY;9q+ut1cimm^XyRZK>_HQ-=7T5mdrv({lEu!VK1E+bY3r zNk;O-DTC9eQoGaXc9v)N5Yt#n7SZ??k-V(Y42B`E<#*W2-1Asq$X6t>gH6jKf+K$tHE8M&Hy}dQ-Ybi3EHd2q7 z?JdWz?_7W(SU{J`!7k{xFA@we>x#3w3n-s$PuG^VE~s)n6{V#pvHG`hX1=2lc=@I0 z2oT$?9Flp}M;lB=ZZ-FN_IC~`Pmwu;=J;=SO8^|z=pFW~A29dTLY!yg(d@%Asps{u zG}5ldIVq9F)b3Y7&$}OGj&d52N{oZZU$v173KVx-z3$fW!wMtr6^4BN^7di(B`8ny zZNoRlqs>3h6^h;*o!I9Q086B0{X(zmlzaH?G($7puGTiv9r%0Q3i12r4}V~5c6coM zFL$>7Bm<`^yYK*+VOv!}?4?QfTm8A3^7v(LnS~?P3szcnmzgtlxwEGI3n7`)#py{JP9CxH`G5CsjJOAv+{rufLfpUAD zcSjow8Vsjg0*@C86zqSe7t*z<=cv!PaL?-3y%>5I2xe#*Qpwxio8@cBizI``!-4!Syfz#KsGGN~>*@>i-pz&?4boYpeG#MpLRl+_&X9 z36uPb%xD*XRCbpNlm^V~j^CrieC` zqS_W^YZc+&jseyB%qxMR|JsCME3d}SI$%ZPf2dJ^K9>33($nt55@@~J)Od$hj8<7L zswG3#@5#mcJ`Xh&&%dIhwh;*%q)S0pf%;uok+ryu-0ca9l zEGmv*gO9}w?L9008+Y!DC7EyVnbm7xfp zy||r$Pk&>3j$+O!q>gHDD93isM-{q?SLZ#oVf{qTmkNXd^3))&G+J<*2}j-H2O`OA zQ*w)J7n@!7?l150NIW!CRs z$fvR%hK=6CMFRp3@Z~Sn4j@Fq_XQ1??|Q#9-^WhK-=WxRnsh8<*yz|{efx+W1$k;v zXauBF#!|pgZ0O^3b9`K0tcVFk(XKNsgK-1)6Pv&LG4qTooPAMhAx|awl0Ajv!TfQl4~^_nALM8LV6-u-@Rc z{b~b%*5>{l!3@^|oE|{U6hJKy#@~D<8Fcjoty*Yg+}doAFVs;$VMFtFZjm!tY1#@R zUd%^ZnR8Y98=%3m6S(nCJ12}kkx%i-wFWm|B2liC zimb-&oUh<#cTSS8LD=289H*=FYN(DEI*36*-c}dyermwB&JyPQg>pccXv=;XK|_Gx z=#RdDg}ps}gLE;5me(Uz3>Q=6Z=AmeEOUwM$%QNiIXs;|DsPjyC0ZpuV=7M)Aw=*cVK^xu3 z4i2Eccn+UNxQWw!lSt2J4YQp6p;mY)2@p)IaNZdAsNXYALCKJtVSi*#66#mP{vL*;5g5+2{6U z%KXHBRcm7x9*tMVNx9tVL$K6*F23tmlxKLc~tUZpc97F9#h4&k(syH$3Fp`y! zm5Pkru@g^eQmB)cd-v|;-Ag*W>d37-Am$bycZ2r&*7Qf-0xOJQn|7#dw~Ifg>*{G* zb!O4~ZZlwFtlojdsj=hu^7@~{R{LLc)!fSplXOmUdmO4Sm?`Vxm(SymFy##Ub8mU@_8aGKfR*PkfptvY9kIv=zT~)Uz zZo4z7m*U~ecq5C*COL)XrXUfKoC71&2(j!yhy4*3;c-83sF$HFZtzo}w`e9C8;4ta zBIn@8^h`a~)~Hb!NB@UwHN=zyAD*(r^Tjc$?#X_GS01hJ2U6dZ6o%f%#}xfl0WYFX zK=sXvAw{hC91Mi|tHuYo`a;dX0^ge(Bk&JY;;tW7g^DHLIL&DY3aS;dCm;ZRQ=uiaf9bf#pq##)*tq!3ASd{~d0EkukxaV=mUFiHe5cXO4G zeZU_Wo%$RGf(pg8GbED))v0-=r}a_tEY#>Oqu3%rAVoPA5xL3i3_O3luSzRD6=wr0 zplfIyFWlM)>B#i6gyc8+AJ*6f!zGw1J(g98HMinLnNEv`$S;g zcbzv>`xal_K??cmuMd2baD{M8dJM9`H+2UYZK;3v$oB++s?zdpV;-Y2xR=YH6Zd7Z z#^rgA`gH#d19K?5>+-cQVpI{rgpl@}VowQLZC{=|*41hmuAlI4a4n|(HNh!qPE^~h z%rQ{@nO-6O{0Ki1ShIJ_DEhbIy(y#E;RLqL5?GnxdQQ`lbLl+1-msb0I zRNVj$Dn4lD!I%BkT_*)0in%!0vg}0EcVD#pjXI*AH(!S1=1+!2F_o`+>EUW~zWnLK ze}TOd%hXc?@;o&D|Dz-EJ0 zdQQ8mJJ)P7DZQc-Fs^A?!ANZprJQ3n(9G zeDl={wp5H?(lsOYPlCFSlJSxhe<8<~Zn}7MOd2Ah3%{}l9f1IsubY<#l1fp2@fU~0(AnXsmJ3ZBmw78hNIN?%Kic62a`a?>jaSl* z42#gF9if7VVJ;*i-0UjpSdAa$w{Z(3+)LAEY))$mbAAJhK{A z_j*Z$vjwfjV|r7|VBv!s3_c*ilk^S~MO4Y@&A$`dURJ zn`M~cx_ILwciuz{i2F@#i)#Yyq-)m3ozTPSI34?8x9CWiF%Ec4xWE(TmojixUT2-!QGqc{Fx*1e9XQ{zAOB z!9`vQs81caSy(%$lE!%3u1x=Vz+%?S6qCEH7x`XKWfH;cJt1TPK}Aq(Y@CA=Z}o_) z*1CO*-!;#%eoskBY)vTR{)UwM(mF4J*-S$=)n(TrmJ1?=kTgbYQSLjc&?8pY8SY^%825BpRgHI>jvlk@aVv zGIr%8cA?F@9Q01b{Ykj9ip1Y$k38JUP~Cgs*^DHGHyPesORC&_RuysUU({?k%xWv; zS``0BrhjQ^c|#X{DU+V`5if&-nHpIhUYFegUf8zKSqUaZPD(kT{ z=`;vmwWb3SNf%bea&VVRMfjaLuoTK0!Gs?v!{?cNb_&}V;L~-@ap99qjh%j*4rVn~ z81>%SzfwydfZT}iNRLA(;nEVfs8R8q{}9C;33YhKO7UbCD1XZsE%u3x{F({4MWHmxg$>1@H1H$A?%t2*gVuh6MDJo}y{?MzZ2_)Mbo@_fxw&lQbgP&c6O(qw?n|{q?n-^V3e2 z9xC9l;!Rs)S=K>JW_q?y85C z7#SisZ#{k>tPXAebyL$#>8nechCuR?X=%XL(faI-*V$bppZm6ufBj}=0c2i0xV7sAE}@dmam&*yqQunjpz--%kjtvQ3{hn_>v>5Mr!EMC}kzq zYaY0g4=ZAS|2{)qQ^p zMdJB4B-5<~Jw!78Ie)h!OO5AC*2j{|$gZaBzRYybtHwLj5Dr-2HYE8>N{yC&^jcE4 zq&tRR`|2=&NoaN^!PUPs#F~t+NFwJdznAEV2Lc2vi#U93`MQH=2$kqu^BbuR%=c^P zg}YK#aaL@@E=lC(0Y^fl3?_nIjSn#HMvUB7!nlt}(fvd8u)eK3#%9&fN76*YhbG!c zTu{6?bkyc;^mJgPEa`!d`jyPTub~rF8R1PEXtO|2^(lc%&Hd;Fupxe;k6vnT$nQ&* z*d-7K1}~Bod}fJhP??e5QTSa9t%A{2%{4VFxNK7`qh4s3F>)n~qm;Xd&Pw^W;BuYi z*C;Ia|Ea!DoyCQJ3nN&~ocunf@2lT-MQk}N>bGAC`f+V79)bQIc4x;oLr)N0sydu{ zUMYNSD$BBUhDw$=a~axT#dBR+cos=^=56o(h=iL|Y6zV|M{Rtvwj0L+cOn_ehuK3qf7)f=>1*n3JPrRV?lD#5uGh9!4a3GI%sWa?{^m zm+XC;BYYG17N3e0-Ub_qkVaGtKNqSDM!i31QPK@AsIa*06OqdfyBywGWdAHuH9E+B4g z(Sky;xHjU9iojI7L&e8__&Vrt1oP5h=ez_fsJGT4!CHx(|1|;efh40{9KF~Jd>YzR&>>^Atyr!q6*uq;_ZArBkbWzI%LBpzw+gP^ zYg1JG>wuJs-OjhUu>r<*js`BZRx8!>{dOua(Th5*9708R(vL`4jow;G0tZy$B_7Sm zD1uj_?90OAGpZ~+1wj1~$wI$b=Li~CIX-t};@KludT+tLPmOg-=WffSHmg0#ps5n| z$bELSMt#FPR<&O1MO%O04Lr3G-VZ0ZgG}re1mfn)l_g$nY^~^L$=@F`!du;coi`%S zRtxjFy!ge*RnBWAhUqbzUnMX`iO%>4C+zj)uX(>FEf6n91a_8xHWdog#d_tQIr+zM9r!PrVJWx$o)P{X^E&#adl7pzZcpm)R zKV7|e5o2^m)wTUyb_=gUyU*6~8KxJ4AGkDzo8XtqSAX&fXRR2`ORc+JvK2CGQg^ya zsQt-Y^@Z85>e#uG7TsJ%O;0{@*=>L0lrX0H;??Js zCToW^bi@GoWPW{;y3l5;ZU666?g9*~>BK_REw{Zb+A9m}&IMX@C%fI6L&s&e) zBc2Tvt$RIr!l#y-e9k%N6D$Q89GxJOKR|2ptpIV28P1a-!ZXZ zdOa7esR64b{7Dbq3mp03aUwouucwM8$h@H?DpJ0M(?z4TqQ&;!?oX0FWH@JC9N!Dz) zqanL(d3~gR_&Fo;yo549;jpB^)qW7A8ZXQYXsZpHCAQIW?~p++Q!-m{F{U(73K46# zOMj8n3&=jW0E}g19zfTJuoqe;i4fpkjLz$NcfhVoqa-ic&1(OXI8S(NW?{KE^QsyE z|HfU<0+6j6epe!msJNgj3G<~gkLE5F6c5>VYZp%n9{C5aq8@Ivk{rpbGHc=-qTUk} z8dktMP8dV!`-&dFCk}mRt1iLbz9bzA`a&CB6i&_luP&uT?LR`Nx zI22U`9G|aN0ms%JNK(V++l+M^vPOT(qaT{!n}{{>kwaHy5#PG;C)a~3kiV?q^sx|E z+oxEO{GHk4UT_0G+^^2Zn?t&Uc}mh(NkUoS0ZR^lYwC!3^y*{v?FcWKn|PEier3l8 zYCJDpy6}C`A*fS!;yfhnIVVM)hgo5N@a0-C>T?7dim|AS`;8>W7lGpLSq;n3?z4$6 z|BYR{vFlS{U+zM?JI?588v9rqLwC0@wRh!t5VP*<-Uqz z{z>fq5*@AeYn5?s;{9>psAC77tEBBk0`WG`*i*!hf90}$L}J-sF!iCX_B`40jc5*s zl)PkYGJ~G$01?sGo&2l!*V3@fu_97zE+C!6R|6!aWgFLpkTD^X$xQ7kQ=uSrMorW@ z$6i{_&%Wl`t3_yI-vEyki0uzs!}P(d3~i%0=rL*PY7g6!s6MRD`3rpTb_AMy5DQ}U z++?`(c3tt*q%n&{D`ovj=P2tA({znlpfXn@6_fhv69{93=RZW~%I&=LyPgE2ZI3PN zQqO`GH?#G`xl-F^j2Nta<5LWLb@KR{cygs5L>D9jI_MQmL~v>bOZPc(=ViW3Jhyv4 zC_lq_)4HCA_4$}G(GS+YI+u3{{#O7WY6pMK1vj1(T5i^RUv&RL_zdLm*ORK$q(t3^ zDe;aB)fKk)U)n(*l21o8kq^z@gaP$&ekowB9a<&LzQz?PG%XW`QE-#?fKT_2ZZVll z1Hy8Eb4>lE*?BpD4_P93@Az@a45vywS}I9-T*S*DKKotkS(Fp$BiZ>a26!K4Z=>7D zU;z%Q9ON_t5T#_&!eppL4`@kg24GAx?Fs6T?9+EaplJ-x~e z>R`}y@))Vnx7KU(B@rY25lwMqP|N|M>?OwQj1^J69do#g&#b_UOh|ugN|JbOQ;~^z z7{Y(c!$^mNy8REPhTwXYzl-Js{-O|5Asg>pi6^|Sl< zdrgT)!qbOjXaJBZ^itLa{(K2c!k&KC^YoWBj|_vMpC^-{r2ceGmbf;!&h{zhnBAXa ztP<|g-L(jighG~l2|V6gMy>m7ry_Zt46$sANNPcy2F2z`-@SK^e=6SBkWWHLAZ}7- zJMmF>>2K|A2)4~bn$AECU?qko(cDCqtZ6qoxfUTI=?(p@##fo{qtGV5p>%rBzrMA_ zaHqMm1JzW^6~mq^rz!rp)jlYzuIxG$T4FiV;aCdsk5X2(0asJy~S<3n>Ir z<{45A{Laf>j&OtPV{>D-knDE&@F9dyE6zTKl3Aq0t4koGlgy1sHRXdcL~ajng?7E)X1DD z_mkK03uP$ed#d7h`<;9oIEUO<@0n{B{O#z53JyB#$7lybjlM`JD5?yOobM%~>I-IM zxv9mIqYg^Edxnbaa|2PN$9F=8A*maR=>x6TBCxo8BJ-!PU9QY;AJgM&9)FF{{@;*WFMVo%{Hr4yPTh?fUbcT%BdpMzEHy|p^CCa)*80Ex~yOGkh^ z&h#FG_r0!@I0#+01`ay<*F6e;r}}(GYg}G0lUwe6ZkCRqvW2nH2W65h@nFYr(WHoH ze(4U<*ybYU#g_5y){gYa_5$>Vvb5*}SpXNdyH2pZ@uTvA2KIy)IOQ7wT-bVknAw|N zG8OxlTXknc5~G_hR2ZYIG^27HwbcbPC@#~DuM**`)kAeK?`_nu?%6CC%r^n&Xfh}mmC{N@hz%uQdi)Jl&J-@QVg*BN$xt5T1tb9Jtb z)FqN&f)Op4wo~Jb0E%=E?v)NOsY@*h8j9KJo-b!o2<9yAK5Cy*HXAn$i6$OZzq@5T zBh~#O!1suyLDn@`*v96u2I+H(`?N0`aMb5!*yae@U{Df-wA-~;e&G(Mu%WhmQcW_w zJos59`IITg2a@OT{jtb^Zk|gn*w6?s``lUhKSWF(f7u!^*0+A>&daz<1cy@9UG>vs z<_tM$=9p6Zs1ODCM15Biy{b@C!A}j&!xV;#B-^{@MYPhayQ>7lOd{g+TvFZqBN7w} zH%AJvALo{S7wV`lp@Qx^leP3Me9zjzh>ZT+O-#D?&edOoo7*zjz!8Yt@+o$!Vx$5G zpR6Mj!FR)@3qO2O&Z17R^)3w}XplzJlQL28WkL)2(EG`A3Jl^DHd1fl`FV4Txuf)A!0&QmGXo&B;kh^|(Em70O(C-JlFcju!j zJBLsz#3+)yJNQFy>efk0?%$c;0e6mQYs;Q1C+dHAesfISntrrS>^I5QOXlIu|3%$< zMK#$*?YcpF4PAObnslW%iG_|7ks=+XOBaD4ApsPS-UO5?9i)l$UJ`nfBE1F#r6z(9 zCFEQ0x5oJQUSqF4*4Y0Z|2kL)Im-bf7`le?h%z zXS-r;_c`Q9L@Rq;ht%gNiD7tcf|ZFM~rnJJt~_YR?1l~hsDu;EcnJ`eUjrtnMw=seJWneEXZw(FP=k^?^d zNEbRM6Y`|58sbldx8DDQ46zidl!|C=miPgF?NF~2hY-d4pazKiIJWX=`H82&Prx!q z!E(Q$`{-2h$WOX|#vFMn>ascwHW%RvIMK&=q<=+Wyx}*OS^M9!V}|t)pUTOy3used zl>F2#3fFr>9sZx7_Bsw+!2p;KVB?tCE8*;GFRZ_}qwDRoQ&-dx_trR#1)OL`L+B^~ z&%t4s+_~*lZlD1k4spVLTE9eRz0vV;j1y^2b5+)|)g48rf$k~p0=b^uyx`62NiUHJ z2Uu0%=o8!QmvPi=aVQ~E(f19^_rxRB8xojX1<8`M-}A25T)HEGBx$q|c3`lJM^E1V zVV2*@5Zj6X2eivXu^xhsuxaH#wP)IdyycU^yI?$hufxQz8n5&hWsgC!>inB)g`u|6fIEEwbFFoaOqjbh3I5+^pPn40RZASZ2YL4?gieg!@)eH(pvY4|$Lo zkkRs!2WLFW^kRubZ;Nt_)Ug)DasMxo=9VRJB|N~s-QqpkEyiA05Jy_)1fP`q-k`oH z%Ikdw+aaZF1uf((0Ha?r1O@E+5b^q)dLWK_D03Y=eRerjPqkwvXDs?o_z!LSxjMOy zC}l+4^D9vCf5J~1Rsa}dt+40u*ky&O6sn?hHm*$_8{&#zMu!Mv(&6gA^!4%JZJk$rgEJ6>t zl(-C{7NRk+>&m6rWEfHlVy5K0X&jI~0bkfWVi-fOOU$U>seJD5-khB=XJQblpcZ+@ox@@*=|D?)-#RURh@lu$e*c+Pv{Iz&y|zg4o{3&jY6&(@ zFGhMZ->=}vCaGYn`M^qoR+YV8NagCdTPDVJLvYk6__o*1ZkH$qI#d7*A=%Fk3`H>v zZC6#EPGZ@tmkN#C&JvBeeXPz{|47|h!NG3E`A!nd#N)nv0Zl_4-Mo)EW8OdX&j6-Q z^)2nY%Ie{;TKkONGh}oDdpAG)#IH!&7k8np@3>q;gNgK_` zdQNKy5x+TbA;f+p10c`?YKja+^@C7l3-TOFeqEF_>MC!=*x#xEM7##9iwwOzZtPM# zz9V5Uvu#y(^4;#>n#O%lPF861Y($$jTkO+ta53BrFNUQ>b<=+f{09^RW#eB6wBs$Z znElS=+qQ9IaQy*=UEhQ2qm&$!`M>qgNyiav#Aye14F(&7a+r+eQn}Hfc0+5T+v{;b z@^3cpG&*#r;NKu|1$^B%;Vx-w@W85^cmt(V{>CVFZrEyK$2mWD4kt?Gne1JDW{Kr) z7Q`5l0XNj|jIZBz;46P~Xnkz(c+W;cvy?-8SNnsC)5^6@)V5_m&}A+FOPy5$T4tJ+ z!5RC;D)k_oIJaMfe!Yay>)jC$&&w07yzrmt|G!1Bo_RdylmOiJpYfO#i=?$0x28pK zUVxx>^HI|xH3_MTIO1srtJ;dmo$I<+zzUx`FE@$_Z@*%nWEL$WqSkh_oq1$J2VVcI z_MRbOE2jhEe zV{|v;x2UGwW}?eEULZr7I{-Rf`$lH`5MkH_s}lh5{1Im|=#Ay%ZJB{YMgTUV+AfkV z@K~y*TI||GlVgWl;a1`Nyrs=MqzU1kd!POTE|4bX5^0uC>AsWrT0%@9BVw38=^kZa zUA6qwitl;M%$e47mRl?*q{hcF&!5v9hn+_BU|sZJ0Hm zQ*^#?H{cjQSp^*96K!kl2oUI6MEFM#2t;$1%tt`rTX1Tq`EFL+VAoY?sg-{E0C>r} za7D;{y2>Y7C?M+DEj^yoNr@r%qRrp}FhT^jy=4!WE~u|ZC|Jpl4LV307TT*gO$O<4 zMyD6O0^%=pOVI_WMx9F$=@^`j- z(IMC!Z+>}~@E9+f#okT%tuu$aZkiHi;Bnn|THXa{r?Uj@9+3_qG; zjo-FBt(I~akDH{LrwvT!Sbs!eSO2f9Q5Rq&y_)JPSSsjuU_!936CUHTk{@tFU|viCU8}>c(D2hdOYr+Ko?7_>$=-(vT0Qd@&H^ z+$Lk_e5qg&L~4XOAG|6)8{-aB92+14b|??l=U$TB&-rPJrF`^ydG#hjhR}71NEU=l zqB>l29@!L~v3r$s#kUTYnOy0)()8a`yME%vvE3CW1GIaVjJGag)aeUhsO^Ph$~R@W z`-Ag}j~=vZS%Dl~Bss9PWyYx%l7y)#J}X?n`OAVWFxnRkbFy71Oh;)5r?Gg~kdZL2 z?Pz{jzq8}YxRnI)6f@;;j^avT?-aXyMhL+MM-^c+Khz8XnQ{okEi0wz38|TzYYplNElffh!Tp9nJQb~?PFsFZurkQ~#4YnNRYXym zSAp0&wbK^QJ;7+6U_qnaSA2z+-o)vwuhv?Sw?z%1wk$%57JX%ow?B{$js}eMnzSAD z4yD74w~^=*GW>!WK5A>t>G1DyWQt9ejBz983J}v7LIenymf1#CVtOw{P@6 zAaCe0Ey`Nx6|41OFs?UJxvaA#DP;O?FU5V57pl94==_p}QS*!LoJ&C%ZQnZEtU}!~ z<v8z`-Cj(;k;Mx<)_$SG!6jEy7OT^ zJvMsRz=O&nf2WF7Qi(_Wgxl#x3^WExQWY%u1;5bL)Bo- z5eE{cK__0u+6~8iSLbV7JEYXvlv&>1MxyFyQCsx1mT_Sk(?K@0t?a=qMkCxUYLq-Nuqb)fIc&p_qg6ArM z04e*i&YipsPFg_miQodrKhHWP+Q#@PYL5@?!oNgce&RT!)1&Bqh#$h{prPcQfx(-} zOsYmW@d;ml2(`brfSM5KS6iRO4&L`N5%sr5rSV$#dt~y#R6NF@vEOxQJ z#B^rI<-1bOt}9)AMyTR1Pz!t<-vZy)c*!`q*IG=j34|2;M{E1hk_$$%*52da@prg2 ztwp~Ih!{#p?Ehdp0J&5jHpWo(`kTP;m$?m6d2;Za zOFD}XBsk(Da5m^5qBt(U%tz)hMaS!FQSav)^?rg(QGIj)t!e87nsbTd6{qvK9KGh5 zZ5*rLowoZvrZZp-!`$%isqgj@wzQ99_)`!jMMJ(X5h?KNMs=t@eRW6B=V8ZtDi`#|=WAER3Y0CONJdoodlJgSa|^VtF{i({F7??U zWfMHhK?gAorF;oabYOPI`o`7z99AzFXXEUrQ-!6t2dlx@;^J~w3b$lw2Y~NE)SxFA)9g5g*jxFEM%4Dh!Y|hq=xTC8rPEGg}CGARygbqM> zZ3%Z;@Ro|@9sTDm6#HsW5vJy6;{%;v^F6*VZg#8uoN1-`yRun#IN-ZoM`25_1T3k_ zosi8O3ul`wnweErU#Vsv0rOx&_|}K_Z02`7<+dSxGSS^ESgmrjZxrHA{6MzZLj9tF z&-9ztNIk){yAzRAWZDReL}Nc=x6upIq3f8eq=l^mxHwkjH$oAMGSc&ORL2Nz+krbyZI@HuYRoH^7<# z5fvmkTn?9SXTcrs0KlT3XTD9~yAk~~^MxOBE;n3x>eVEk6Wb0=C}3{fi21dzqi1lI zH_;nEDY1i|aTM8^4B9^NU&zleI=Kr#QP}`J`%scR6pZ&B;3`;oxj}pH+*?sbgoE%l zuE=|hO9DE^mC)uiWN-9RgB!pYCu5^SFAHK`^_)M<&K}I79F7b-U=mi&R{`}o&GLHY zf66cVd-K_50sBVsV&304g z0nZTTRUK+k(NU>W{wcKw-Xo%%Ds}l)tDWRns3~p(1?Gesw|slr*o=*g4h`1!5ETwi z;b6R;l=mV`tgN>mUyQ56KnD#v4C)~Jk$4ZH+ftEkakR2#NAMBTr#&^xOd;I-7q}r# zXUHDloPzK9kzRag zFYyk}7v;;Sg{dRkmnwUnQdKlwkp@q?HEOEm%>S3s^M|+R`RY|+V4jGf=%o)gL?KB1 zyt#frh0Jja8^p37p?jzW>lvt{gcR7CGY$%)#u%?r)5!r|{4(vpRpEEyzOR<^9iw3=OgZF8$ zH0?n#VQ)4(lp1PKD`DB|3Sdt1YhSc^&Ha?= zU(fBN8Ang+$dPu)vdEXsI}P)BzlS2M9zo=zK*R+$ie%1Ku>uGinn^Kxe| z5-8MOZJ-L0HrO?om+gSLBbe|&I?E%3Kl8~$lT}S;QXg!3NnG_^PzERkq|!Six?RVI zZ@^f--NU&h`T~p5O*T=QA)T#Tql1NJUa^lTK!-inJ2%Vy00@2x&~*7f5>^WNXW@UC zd<;;mvk~B?U~+=owDp;0&{6i^2ONa`^WJlE5;-)~&SfHOf&I_0lZ$>s8UBGlG(B`o9!+ zn(!_CE+U`6-Z-m4gRi=e52+}!!(U>|F52gtzrVgy+O8v6udV<4hVi=~sol@Y%Mx*T z!>yVJR?|Hm#$dcDnW3fm{BH^F7Yvgwr>P`f%xnhfN9l3JO6ZfNx?U)wy56OZHGbWf z{b2c9MH0%u%vYB8M^6XmU6ZHRUh~_`Ko zx(L$XHq+m!A30FYBLv_qcv2uPn5~2|W*^(GiLGOvA&>uB=p6}=Lb4ziL7CG zNxPpdchF_Y`?{AWX-w{(;1W!98 z0Xxxb*b@|=1U?qInP)jix zxeGkGAFuW3AV@?BH-Yx+e90=4hF#S_h;Id=f$cP z>&#ohAaf56W=5cid{Zc5A>6n$EbsC${3#xZNv6R=wk-Sa+^ZwVZN)pzj6Tf-T~Ose zbgUjuvNBO)NnjB*y*Hq>Vxl1_l%75!pYa1p9q3JZ4=^{_`3^jcFJ9cYXs~NcVw9y#D<+9O+&5todc?l1ET~-S zEln9t5S~S_J?|FtbV#tSIZpg|m91@c$O7MRCl3r(?;{JS~vD~4_F|yp7IbI$neJz z*x7Tj>A*`FUK{x1_AJ9$*Cd>zb7E$YyRB*=npVsg8qKa+V=} z;^UIhGJ7iX&-F+Bh5>G|WVL3w6e1SRrpTyZ$31#AlJlN$N%`c+9;TTMYqJjA#=upm zNvoetfVIaX`B|OWIr8m<2B%auKM4KV0oheCP?@r_1^{qWGZLqp3E?Z+P* ze1K$ICiaCw{)g>~_CTfRYFI_LcjDh2K*YRZ?mN*I($f$8{0ef+4c@BpjX$q zyh)t0SeLG!h9jjrDqu-7nn{m6k+*fGxuc1~da~x=bx)BO@2GDnqvmcv%9&$h>&HZ)81F zQ6@5`zMU)R{{8`B-b+}lYwniz0KV&Q4?DHCJ;8C=L$T^ELCNZRfo|eb14AT_VjB`4 zfw;{m%uPTXAR2t;r+=)L@VNioIv6TRW<5W1_~7PMOvF$aNZFY z(<`&ov1OdlC=C27J8BA&V^hMi3`Pqt)f7Z4mQ+2hma9(|s`3QTBqcSoI+9=cK zKu4f%#v9}0P{BkYN|Wq;cYzfikS~~XW4D-vIC*>MPGvzOxDRlNLTKcH#^xqGVA0(= zw}1V>_o;I1jS8s6hl0zcN5rpb|Fx zn#tTflY=1@xziJL|BiNmo#+uME_6#f51X!am5-pdHT>kk^%8?Qee_N?*3N3RLbAdx zUt4;dm7nCkbJ(|86OFd3%1#|XQ4GXL`x^*WfMPvPdbjD;((*kqU2iFqx)1#Kggo0u zOyqJ82hAAZ&__Vp-XR9s_-_@<3cn+{ejS%E7UJnGCtpSx#eUbkWGv%`rpvm41nJaW z{sn>^KvSMWFk%5jVI3zll3=s764=rWW|DXjy-ZB4w>_p8hhC9U(a$BfS0Ko?kMEY_p}^d$3-f22oKE5yU|oJ*%4h`00O z7cc_`0Dfo6eD(~?u%hN%+EKzdp-5?zV9X;PwMy3%r4SVh7y`ltIhYdGc~2e08Emy5 z2j4jlm-}hE_ZA^U97nRk-L_ii^;UC7A;B73n;y2cQvRB&Hn)Q~JSMC9CHYo+mG@G+?!pO4hbZN#VvoUZpvndA zB#ZTs?@>&0_PX=rZ2DdoO$+8JwE8hdRHxuBe z`u}=St`txppm1JnaPPrXlmiR=4iY0jr(oHcwtlU?-{}#lwzaXM(j@-0k&ed^8#0GK z!TN47)M0&%uL=b*<&XmN)H;8n@@Ouk#>^e+EOcLjM zxZzSbB|su7fW@pGb#iYWIC(m?S*c_0mthl6$;`CO)=mROIeWc-`l;0|TJ`}6d8PSo zDSSMW`#SlTG^D~WHXmVa?uM^#x2&kogpEs|A*K*A9RRT2<)^*ew}qG46UW8=_1Hef zrK#`bGYT}tKg752U4X_D`sEhhW;4w42qBHTGxVWa{GPh~!xwooU|CJ3ZowpFU2T;y zCVQnzIoK4+0gO<>dk*r+bY4=VmN!l3Bc=6%LNdP$_6X@8zZj>ad9_z6aTtEZf$cfs z4#R+#LrwAId{Jd!3S<9@C-Mdqyt|QN!ueJzi$t%tTkp!KywZ&H{tTV_VmDAnwCBAz zXmnF+jm4~ff!S-FxD$^@f%G%g;yJ(Q;u*ub@WXp-yFK7;0EGD9s_s}r?0I%p>Q<~m zqWw=F!iSt&Yr?&;ouvW0_o159m zc~-1r61OQmBvX^uT_1E_r@2<9g?KhM4z#~3NZasF$&?(JJB7VpL_R6~cE~gxZJtrf z$f)C|g{UOXB5q!N=u`vdk3$$>#o$C;uWCOirMjVHM)FlytgGf?{Rl+3rHzL6ml0xr zVTU2B(%yYJudKdY@4MaN1P`{A$a%wY6nTxLZsvr`6Q~N0Br0J@j9Y@bi|Cs`PYGDz zx0g$TUgN3kNGXVqT-#e_k;5_)bD!*cKPbok0W}5p*+GEu;oGx1y76D`*#n<8tc7a2 zHt7zNnS82QYt56@bji|aA@2|NANGK(0*1+7M^M>H zsVK@^x)f^u(BMI6JQCEYbt&h;M~lsvD8{!hujuPJB^zaPvTbUz5U)O(Dff+lr@Ur7 z>>~bbITW|hAg&S7v6O$4nc$}H*$0(sN)GbzYKZZV5bexvDy&OOjEuTX@d~W}^b-0Y zZJ#Mj+44o^O2oUUM5~dNlv`khv*05nt^l=hT|K011<$twoT;dJJzv*;(I{)ZTeGqy zaY@A_EaWFY*+mr0d@oGnUR+4C*mn&iR?j{q~T~cwsTPe zD}-o}7;_bV1U(BJfVuVYf;SO$ft9v!7o6Q*H2sH`Q1BO<5orEWfb><$L}KehH_nj* zUXS`od4y=s`xe$Mf3hqtC7f>QtMUt^A z+cEtHRxg3m6<==F*wi}f!!ca(OGTZUbGR=_Gc{QD;t&iXj z*TFdO8xET|v{>NyXIf85Z|{45Y}W$x38j6#@7g#YTKE5dV+pp^(}ACJ&H0 zY^8Rxi?4lXP0(f4)-@fs*9melDgI7ltlaz+;!ALyJ~JkR8_Xd+9QdLNjIcq8wghm(?>pz3 zxGM}gGR&lU9rfH^u*$J%*@EqEHzEMy$|7qy8A}`J&Bd~l(=IwNgQ3?H84X5KWSgD z9lm@kUP8z2b59Tyu6hEwca6Alj-^@{QzfSUI^qtA+i21M1!14U25;#MK*;xds1#}ocK_h`Y(mf zo~1|2!#j2ZTlpN_Am6fsALN;7j>GM1%( z5`D5N^thFLhbMvY`d%XV`~y}ds>joT4Np}2pa3Sr*WRq&ZA}tmEvmj1p_I1&<(5uj zM)cZ>Ra~SWxEv5oK%HflHU|E-#5GR66*bSY)rp?Am=DPu{Q6~hS|wUZa(ur>j$9e! zeL3!NOTFs+iEl#vfxS9}8m?BdYH;TV^MPugOXQT3{zHR?y&%8dtryoppZ`RKiQ?Q) z_BU}Yhg;hcQ;9NLM^5(GzY6ntVTE#S@s1X#4~%TjUQ}O?elSet+WidAjg#AwuE7rd zi^uK^!*CMB^Tp=Mx|`=RxW}CRA;W0WmMh)Wx<=J{lY6y&o!*!70DPQ$8=#2N;9eiJ zA2&QdywIKrH0b!iDqiAM|4#F67w0f1^tMXB;S(B2V{udRVJzT0{;cLz`>ZkBu7q6n zDxn2Xg5-@{9883Wq8*m*uk;nrO`)e4U?;{n@6@))Z(0dYi#$1gVx*PlFNi+rc!KJ4F=&f$nxkQpf8hO=Xp z4tEN!b@Re%joF`@It;-UZugtYU+uj|cVCMbWR*~Jt0fn8tAKdy^U>gvfj_}rgAz}! z1hmH^g|i;DEx4pg*NJd&kD_CO2)yGqNo?1m04VQ~U2qw&;9MTAp#-}l{}KnUx;1xn zWaU%6)0QO9=Sng7^m54VyZL)BQ8IHGASQnveD$gE4kcPc9p?$79BjYy$$C8`Ec1d|xIy8wmJ-JjAJ&ZcGh{V% zVH^m50+{6A$!~#E|2S{kE{s3vACOtnQK^mdFATY4_U)z@{{0E33)wSRH#8J5^CI1V z18;9&irF4!^9cLxEQ`@r%R_27MQ(H~SzJuRB%O`_NX6vdjn0dIeSa(jfvYm9rWGW! zUg&0*iVw4tk%3jl4vRG#_eV3?w9s$q8e}R*FepwTMM8nfH10Vx!5AE_=?Y{Jw<-v`xBt;^|y1>_Pxeime#gY}?#J_Cg2PRnXM zU?s(5*3eHjh1@x;Cgz@HMYzs3jHe3Pfu^KD$2 zV9*JU)OB*ZX85C&!4e{dsuTzxG)^WCn%EUY@<|kIDbH|Y9DDlnbTVxW>%A%6*BO)L z+_0P)7jWmF`7&1@G#CiXKxz#I0rb3`_b?E~pR?6j$Y*&@ZLKgq<-*uBMNQ2~gd6{L zsLGDalmiuk=~5k5-uHC^)AQC6c)}X5Gkho5g6K@o#h)?gejZ!4EEt;SP9YB|!|^)0a$v&pa8rZY}(^y?tI9 z#r`Z-{1Y{gSit4#H6?O$Py;U$K65 zU$lo{j6g=pq04CQc0$$CF=YWhecLyls6f!h`V?b+NPbK==xm7-a3P7(A5pz6mHEs_ zhi74}pz-(AVL5n!lVzK&tO5E~W56faidojn-mw6f%WaPSHU)&pr9Pa zm@rwjw{G3pp<*?OZE-H@5=Ga^Um(nthq@&W!3phkhfi^JgWlV|j6kX4<&(WGfs|e_ zZf81*SBBon;mJLnpgyMTC*JXmPABM4ZfPp{<6ZI>HM8)t$NhgkjCfUgZHt}_AaW7k zppBWuSjeCS7Z|n1Vn|$`t1fEq?rM9}Ay2ZS`}CTk_GVR!C}}XMP&Q#2pHWHx2xCcJ z&+x_=+5$#=m|&!pPi-Nr#Hw@flu<&9n*XLL!#aNARWhLC1bR(u8sf{a^j_=a^ZXq4 z3}3IAKeBzXrdA)ayoM8dpHAMMvbOx<800{z9wswTx1xk1ssIMoFb(fVWwI;1a#U%& zin?Q&731uL}iuz>dIJCI$X}^II*esX$JphxQ@S$VtCjJb`Ra|&pY#}bGBmR?0~OorIj?ZgmFXU z6P{x%VyJgH=s7I%4fhP_d#ks$u# zDXw)Ie8Faz;JFTN0Y(47^jD{0^?tqWvUP}@(g)_I4^q3III^yZZoTJIeIfTt%OErJ z?tgD4{~tOy|99XI`RD8ZJv^s3{Qn~2yi|NV2%(T9vI2c(ZWj-V8jR3oP<@4gkIVg( zYS&$ld;BTibJF@EKWV>l@jpEwu!{DoQe%Z&90~;bR0sFgnb>3DL zPBv;=aMp3-9iPEbUi}N>)|Q6WmG2WmMycIfZ9#1yo+NGHe?OU|BAogx)}SNh(Dsz95;NTP#>Nr^Ql5+sK=( zJ>Q`aa{etyHn`;=8uX%@117S((7!<^46Dsl`Qy z=UI;ye>Tt9t&ZyI7HX$1Mx;lX=xyhd>s5MKmnFXNYKm8>_5sm3 z`cm57W9-mj{WkbIfp@m}n}$H?mfpMjqHRxJYqCnh<)YqpzAg51TSGBg)WTinIswbb z;x3eh5IQS3+v(|xWPN3u-YZonLSarf$RnT)>+Bz96*C7n`p55l1&`aw>3s{Vs;hUO zf9$ZzXfxW9SQ=Jbxg4I$@SFZ9@i-?yIJ4MSxCX5Q=BaozmzobGqvNjLVs&0?2mz%y z)0ULXc^GyLnAgS;%su%EKkc_r2j*F_rS-sW$IB zLWvwh?&FuegsK0fD6?+uB$`hF7c)@6OL#CtFm!T)pm@bGcGo6nV5~V+ZymleWYPFEP~?12^<@4zG82n*7~I9sVSJ%IQIj zqlQXR*U6@3?^tr;7kt`Hg?BU1ExvlCl12)oQu;5ypDpF~h>TLK=sBzdKjv18k2l4U zw?qMf%elwF6}IK?+1|7F-pAFlXiFM2uyrH`_O8wotr$aDB6_@>|J-QmWy8pjRH_L~3(sk=KOV@_Y%{hhg zL7+g95BA@%&~1xytdjj4!LZ1~qCLMj)OkKdVIGrtI3xJyy{(*It2k#sey3w2W#)c9 z-@=(f?C%=c17oYx?>TSn`SAxWFiwJmFw;(8l7i&nT@YQGF)+kzBTgc(t6N?^{Gp+W zZ5hnd7<&YK)ry7}ZjjLQpLFaDE00D=CsDnW$jyyZ?>HC5O0x6O;(`amB2H0g6sZ0P5NQ7-8MCnKYO3>!DudUrox2mfzc^qZ}}-KI7}%b zd#G+2vMh)0Y1)C%H}P_BvqwCGD}9#{F$W#@UJfaZR#WatXA~a1 zI;^9JEnj`ktl5zVNN}wz=iZP-oR~>V9ZQLZdRAm}775$&>VHX{V+^6_@%#-emO0`d zY&wA9@>kjFO71=uOVUk=&vyEY3;;~a>HCm06*@hEH|}iOB({hbgy+CLW02M%(Ha_$ zkocjv><~v#Y&Ocoh}eWiHNKnRNo>}!Vp|cP;676B?W2gj-wP+MbK-6Q3zxfG1^Rx#Cp!EPO|cHufG7x|PW*;sA@aK)RpZSeec82!=$@rtD^^mK!yY%{gpWxp%npd# zhvI#=lF6R0OJm&zx?~1gR8``~KgY2~x>*V9w&=vi8h{*BXcL0#-gziE{!jafZ09|P z9k>GEWJWzLbx(z&B!@>utGE09=g!ZlLY1ZEPdr8B(i%PHT}FlZBe@LgafsbUCLfeSbUdO!756 zz_7m;J}F=GFkCAa4f*T3v1OLWrFe6*O017ElIqa&B7NJw<i>6v!2D^8g&d%xnrY%W$0(<9oLOb6>YkGd*^pb)a}VWDU~f%eu*Cxt4RrSB~f zJ1&ZcqrDiZR!N2sk$=ByzqJris7vafEC-g|H1U|`_e7cIIK3p7xmws=Ib;lzjUa z6Zdaf4f>Clb>!%;?*=RPgg!A)Ye~xe98k5>Ad<;Wc}u#hHVA^`j5gf(id^*c1cnWZ&@_=954Q5!K z@hnl<9rxpJSq7<8ryrVGDb9k<&e<|mh?8UVHYiecH-?*E!>y>t1?ei2@DGTAU{~?H zhn~oeS5Q|d_Q;m7oAA3NpZM1Q9wTx$ry+4@FN8NIxnrhB48eitGmh1OekIVdZGH}O z-MlO6EKBu^dhIjY+H9H5e0s8@vcAOG10sjP-Ph>XeO$={d8!-e1zMrzcE83o`L7ki zPb%9iOz#SX*Mw7Zhb#Qrd!s=Ln;Pih*u_gG(w70AKXs+YDo%8I-UYIc!a3hX{PI(B z2?*CCjVDD{)HM<;FQo|bUmNQfH4;>BDvl()Iyl1nxQ+AEBnoY#+Y@0V{~K|A`gGu-V4cKe71k;- z{>WSRc<+@h%sMc1)BJ$4HaT#30uAHAg3TH^&CbPBd-9;vW4`-^<|S{DBt*&dua-JE z%5(F^uiZR$1XH}nc8(0+k9M0k-HKoT$ljIxm{(-g(BdaaZOVGw$J+J;BIeOe z+@o3}(ousPydM+4BLdYv13y0+jzv8-fPG$REAZlQWwU_R7xm`g)eM zfu86Ua15MXBAHR8&^OZaUmG)zZWtNpmj2=m0trsx0*~g?;z@U2rQIyeLz@()t`t>_ zK*Q_4z&$U8mY%@Xz99sG)r8;4$<=4<$Rnzwr;Lq{2IT}LBxfm>HJ&l7mRTA2fgfMe z{#3LUsoeHWRNZm@xlTLdBJMx0XI>JPfPAaNA`M;PYrJ{js6qP@wpN^~|0S1En@X^r zb@?*>9E+`~DL}6Qbg48_k9CFF!Zs_Ct=$N7j%44PXe5j6RG;yZoGE?^dya%GtvWh)Yu6g=3Pz*= zN3m!6jeka~^qv2Z_|%J9r@@ujFC1km%PN#2U0w(jmtAdfISgurFE6u#Ej(&rsZH^2 zVOfjRb+dE?c|mTnFY9uUy-tnz^IIznKxTcJI7BvTB0O0NkmoT=IoybGpW8cs z@?a|$xQ#ni(O7UFH6Z}kzqi?bKlP=;CHO-oldnV1_cH)RoxdkR+JCdI zp_BYl*rQu)y3+w8!wfrLL8-dEQRv9y3XMHd8XR0K`ZJIX>!kPjm^!-1Pe1(=coJK~LJdxIdQX&!Lk=%HgmjX_7HNPXc(dUE=4)Ye<7eDGrLV zPDX=S@R19B_TX8oB5(cQ!GF>!L2tBS5hl8(14HBYE1WvKr{a+f+<4mF%pO)a7&B(f zgSW6ElUuhQ+Ii2L^X&MZ(8tgFSjSyfJ-kXdbHPHQiGuhcjBCMG z9yglZ;zIbi#&3EekZXiTto1tm0p7M zB1Ae!NGQ^qfPjJ!6;V3Us|2J;SE_VKC`yq86oLu)p5^a-_niIibKaS=XZD#n=Z`h{ zCqr4uv!3U^@9Vlgr7Z&G{6URrxkoywum-o)^O{lXnHPMuC7SYkS0ug0x|&@iIRkhs zb2pMS)(i#h6R+HlYYmFajXAo&^=Gpj;6B&^H-0N;orAz(6nERq@|}>V%Fw2{_^Ej| z_$AAFQQHxiX%eH*B^GXa9dDjmgV0*Xu-k{%e42#=w#RhrOI^kzAl-mhriOV z9j|cbnc7D-9i@U^<+?oLWVsciz_MgL3~PP~!>1q=h#L60JI*0dkX!$NOz!B-Z|c!% zW*UY#gGu6Dfka2r9iZGWc-@kKkgPBg&+Qx%{J3;IIOh3g=bJ~vyodJ%+r;X!O%CDw z;oJQvwoZcpEQW7K!RK+&EX_?K=*rpjjXPYNp_U*dbhf_e3q*Gbh_n!GA+loxZ+Cb- zG<)L|l{u0#_U(}2_<+>LH*qWUq-KP2{vS}KjrXeHX2Zv4M&jjf9T)9m1G+4r_qc>e zB0)s!SW}WN!l7_|jd)M|BbteUdvOot-VABCTHzTx>|E_sEIK`@m7Y|NGS{2l52@zf zF_j`oa@$D!E6OF1&-lAudHe0};Q3YNiCSNs#;yD{bC&NX7geJv9N%u2Z{7I8IcI$} zLge~bbpMmY5zRbmqj3S;^RwJ06g|RdmZC~TZW{c~Xs8}TJkYG}&mGNX5IJN3IEnBhK=4 z0dYi((=HaZ;EF@1b;?M@X_{b1Qa_5g*=(i`$ExP?5Z&_BX07Roaq7!t+gip_b@9I? zJ=JmI9#7>rZ#1NOAMSRzdT**`{}oVt!?OL__74YEGr0~R=R?35laz&mesq%949oNU zYl(|4Pd`7LGhdXOXRJiTFfowdbfWt!kN8bzlm$5~nrD?#Uq~NRy|sN+AT9|Fyfj?@ zVS_b->o?15k}u`ULSFPA<^uAg7xzXYTm4*8zL&49&*)l-5_9!gUd4@h^9ifNlZrW- z34{AOwc&ET4ZS`Eb4go`DGnT*aOEDWhq5MA$!kb~`!FD&+;iD;jphAY@AiRectw#D zpSWR+pi0Co&RG6AO_xrX9)Y`I9RLgUUlbc^nNj*Nxph9JD&8Z#MT+aGLNi#+S?|Cx zu`e?f@M0ikm*-&!{TWQtyQwT(OlOktw;H_%Nup^2Pp@sg`wsr74Q_71&mAjHYl|VE zQ+aUS9o0aY&80_7g-H)G*7~2u+e-;Jt4uQ1U^ha_Lr8yRzfNbVzgPLm{`J;#ji&W~ zv`duR?IxgdP|&q~O9x}y5{>)xjPL29eTZS}Fd%XWa;|t?lc}=M*R&a|_4adv{pU5m z<+`4QZv(cB*&y1dplWamsu9+YPP3}>E>@c9{qf;L*28_Ajj#esMvtLG#$xi|L(3p3 zs^h_ZFcvJdLK)RzRE8z0s;0jASX$wuV55{OG9uU+(3j1das51Pik?PTJS5W3A7MLF z(+H1;G)yFEOBmyK{~%jRmmEzZ0Hu*HoOM;v1hX~HEh_#q%r#6I|NhhJf<5-6=S=yR z4uj9`*|X6}CXKS}@T^tZdVl^LEn_Q26KHHijU3jxKNEs*n9Xq{{H*wshOdM&aJeSX zx@Ol6L|-``8rbSyx_x<@=6A{QYqVqL{mCwFvf9_5I*rqKJtUx`$FM9H17L|l>e{DdDB!-zr3>NYvju#AyG6h{O*kt>7-q7K39?eGt9gua( zgPVx!l;L^OIOeZjU0rfmz1h~15t$@P!&tGW?TqQ|RJj)Q1$2r%>3d9hT|0!WTsDjd zN%kb&bK>KlIO_Fhr>B4P7oV(gF9dX!|FMI>Qc5xpw_Z1YqGX@0_(_P~x5)R-%RVdq z!piUN4`h#e!3r)V^kNJ8v8-&{DB6%+@V`96)xn7@3q_@UcT@~#NlK~yqZ&_bfJOi^ zm$3C;nuiAVE`vNq_I9xl4AbfR_U&yae@e@gOkMok)Mw7^&oe5=yL8T0g^a7%@GPt4 zJbT%7g*dUE8BaDA!wVFv;Du^-S>6_#^1gX-uV^x9;}-AYmXQPGS%OngZVhD~u$`*3 zAO0k8hTbuwS?pxBU5vPu{irWlG^jO0@ny!hp$U3??eP;LKs#8#nd4>4@{V}|Pmk=) zXzv*w?lW~rcl31AY&xrL=>Y zjBR<&YkVX8IF^RGjf5%A-A8euyv$S3d=Fac2whd;Q~M5p=ZQ_4RXUn$#nRCB!)+!G zfRrxYdH0=}*1y0-K(8tKf4F{|$E`9TED7%itN{E_UnEsuzV+?%DYKwQliJt*T>0%f zST1Dkl!af#ZujXt37ad(?_IVhiDN0sDWQkL=*N;v=Oi+8uSP~+Q1*WnHA9$1_ntzAyd3to^Mzag&-UD~=W+jd^GURX2-pAA<>uS;Sp4g16H9jbu zYu;R|kF5vYcxA%(qUa5ttrbMMifhrZ{21aJ%1>^=IToB2uJaoVw{injKbUjXv3^%4)sRtEKjNhd~*!vlexPX9N@ltMw-XJj5)Sr9W!* z?Iao>gT)i;876R{D^ z{jzzh1L+A`i+NRkOhw;(LSfRo%=RZM6U{N1_Tnl%PG) z_Fb*&APLe4AAqIUfytsm8U*lh@pCZ~GP>Tbm-M-ZF-)IPl~2=?E6nNxB$%=&BtpzD z1FEoDB)_AN4nD)jih{WNBmL@;?7PE-3tJHEWxXVhY*W8&eRoFr(XT%J%3YDAj=d$El4#y^2kEUo)bU{BZu_%8y)mhD;4BwdPZwIEhWB#%vSX zY|}v9C{L0)S^um&7z_!&xBj|r+P?QVr`Ip!LB#tAZ$Fm_(wz52r7UJ2+h_SZpkDAZ zl0*zbem#=`xmqxgzAoieRh3W(9fO0II2Fd={OuKGR8$h7EAu!S5TaUce}r^nq_jwb zzZv88s^C1o6SVK1$w+X&s(|VMXxa_>jUxlx?0In?ppG-^Oshg2^_jN^ZL< zCiQVvf%;R3D`kX7afveTB--|3#6+`wQJq5YPS&Y@1Y_(1V1Gnc0+8UHL>62(LBLA+ z3qLFS8Uq2Ga*wuD6hw?&HDdVO-KARDzSNE*If$h=3=MGr1tG%jk+?qwdMG%YUL9&( z5*Q-2v6Y-=z9B4feOv=XO&k=y@VNT~xhKjG{35C`FQU2_+)WdfCXS?DGaYF_a^Oyq zl2pg2B%~06kvN0b=wIa`3$ArwK)yo!877BeVpRu>`CcWCE2fg0CN~VN7K_z%3M~IK z`DJ%~Ujf7RyTFm@zxy+)0S(+>a!74b7w>d%l=x4|{a@na@$Vr%}yPJ@av5djE#hz*TfB=o9G91lAJDf;uBNJX2)kDz##}t|7{Pu zy4n62qj{$!O(CwAMJn33~k4}8SL2qR02L5cn$~;UgIT)q&`iZ^t zJJGC(=b&*_Jkq%-m1O;mm}y6p$KM@vO0XqIMs0i&ed=<$mB%n^AiUo4;#OqCIN7t{ z6ta%mS0^N{b3X+NLm5YV6Xx-}{5x+6H+}{VDD*z24!l`O2V`78=ftq9={oFyTh|nb z^c0;bJm%h6MIr8XBIi_i)^Eiq z@B$X2>SR8j7B_x0345yFpk+wUAF&9#7hV(`0+^Iih|WJEHIM7WPdt{+O7?ivmNUX4 zoM27?8Q*GiR*$#N-oS48(p1|e+lHRKwbd0D!fhS`ZY&@Xd%K7UYm}j>iVLSez3nGMx|W|FN$x9DppGu-IEtun)9{Q*hvtNb8`&yE8fHAd)T*Vs)? zB;zY&KGTtSRpR1UqRW?tw^wdAUN#F9hM7`zC{Tm^xpMDP>K4n=!|n-CfIYA@RtXwC+RDHr9C)yhv^%Pm(j(<#d2Go)XzS0V zKYhiF)45Wk;gWw95@fW7j?rl}>eb;V4&K1i&Gjy$dPB1vnm*-ad>#`BGFvM z?;x;gdJ>9py?=t@N{QFSre0ePOlTtJXsNy=_IXm;QciK}t9}1~1Q3^J-x0vkA#9~P zirupla#_q9eWNw^&rj+qSl-s9&uFbtq5L_z-_<((`@CIbWt)(xFDjchZ^ zUmt|Vz+@%=)ROPqRu{0oS~A!SM@gB2eVGM~Sa-v?;ZqZr)6+$0+Di$5Q|2&jmv={4t zDeaOV#*oBEoySOT1^rV%&`E-@cftv#4%#Awl}g$2$QBo#PlN2JK(#Nl1_$vp5;-|J zw|BauT1d3v0F4%&u4z=cbIoA0I{qYalcd$xdkNH+axDcw zgnyXV{`}xhQb+MQ*LaXywt0}lL=qqrPt%qpI3?qvBKyv5+u_$;9|RoR+mN#dB{qz6 z`E~XIuTCS)4io9cHn5r!s_NUMmCIb|-w)?iM59(kxpJ^*E+Evc`yWtqVk+Vue)~p# zU)V8+@d>v@-w~hRMc!H1Z|8QLC^J>Bt8iNRsXRq8e4la&^=Zb>Tw9VO72ObO!(R$dvJRxqKr65?2VHfm6-NRm^EDy`OJZ@^#v32WZHdk!6 ziEvIqSy!~`IwTRHzlKSNT!16)l`j3L+p(*DGP{EC5YHBGV@qTtYJ`Pp8HuJ@&uS|Y zqi`4WkIx!A8Yj22XPN{x?jDifA0kKKw1y^3IDeW;x3>B5AI%W@Ks+0LA2mu<;y)lJ+u!9J{T`Nu`(L2}D!=qtPuZ-r zO3%$YcG$RG6@E!dcu(@Dd@YDu{D?_i6e8rO8qIbRA3s!@}dGyG_Flx>})XlH#NtwX*0x-D(%u#N& z$=YJ6>t~6*B&iXTt}(7O5vuT1-ZO&5MfbqaO4pYH*4wJRa+zpOB)gC59EEwNGk1D* zv?JDQ(n}nPbV<{;+CI2+@7v1&N1ZesAAsn$qBhm!$m2j@z2?-wlNI1CSl!;g8D^<% z8!{eosYPfzCdSO>f?T4L%-OpxW}?eTABDlp5X`jeHo@~pt@FdvlIP!YuVBTc-wHeo z1qtb%>#93@rObp*!qj6Dk&*~o{IAD4seZqCOVSa|EqOL})>e1wk%YT8(?cJMU#a1^ z2nW6Bg$5k#(;4IHCkp?pNRDv>vv6};bBbkZB##EnE9Zct=ON3d*3}dp0i-y>wKdjU z$Y(!-u7o>wLCMAsD}D9mKs5C?T#$R+K!E8B8}KxEAs=griTCeRt%bz5oA*J$J64xx z7fP3?H*OrN+|cVRr>-nFylqH+>3OaTa#pH+B^)=n!iV>GfklC8V;Vc?TCcg#W{c&q z)(u!eL(J-4F-4!dYN%FYv_80ROQ>B(CKdDJkLz15I*76^u3T?n0{u!aWU2h#(VWq3 zO-IncLHf+oRFB&e`z~0M4EAVKAEt8Nc`GGJ7h84nV`#=-<&&63%jP2g>v8c*xgPpTD;rqlX_8deIaT)zAN2HH6QxoiiJc~m!2c1p(= z!|Mdh-B|6s@A`BFAKv)EbFg(b%NI@ing1~}c_>eSapA1!S2fH5f3PoB5{^*xv7U6) zdsul=V9QOgG`ek~#&x7!PVz^7El6E7p-*y|8f!-3R)$yE+2!-uq0uCka}fi&qLVrv zu$1E!cA_Vtu#aa2+Bn&1iK1Tq7ICDaZ)u8^-+4dVNNVVL+@HPFa+ei^eu)|x_IL|i?~lm?t> zsdn){(>)17>+cK9w8EC^hZ2*mC50KjT<@s=D3Eb}4B)=W9hVS?2(aGWbvT#L=XQH8 z?^jLn!aU4RxQ=cfcC$TgV|ppzMy&~mW-#1c!sHv1!DdVY7i$-*G&Oi~9x%i*aPuC& zaXq5l_S9HrfZ4$_M!^*PL{tEBHgy%lKcQPU>){85yiNY`}$&N`6{J!DS$qDt2 zuZ}oJyZhyy&dCIeQJ+TwEY||PwrR@Fihc{K-Xw0WMef^E&nw%zi`B(@`jri8f@<_8 z9(r{35nljS8s(Gysdv8IkIj~vRqfv8mKlVWZBco9Jw4rXr}24mIDa#n<&pf6EK1n` zITWsQpI{ZEog-UHGWIHHiTPE+nUhCi-(l}(qqWzJe;b71w;e5hFb*1eBN)A<%wJZG zSu3$(l0~81H*4g31(hdsa)C}=cV^Z^7vYfAt$a8)2uU-;9%Q!H_M3xN%sui>SOu@)o;{U$>wWPvSAuR}Y4eBg-yTd` z+m=%n(TyIjWHixi2F|l>(8YOs%CD>zh4XAZ2i;VGL1{6Gqc!wj{tS`}dG_f6a%KN2 zCC_$l(qas{+RPoVh%SJGem;D3Ue)c_pSca@RbC3zd_9vjT*cA3ITKsBHQjVsGl?D@ z`ZHUpurG2-=3-IVaA)v)x1*>w&(bI$BrjH#hgnGc8-mRZ3@jR~)4m@)^Olj> zf0Lr>_3c`o^E>?n5Duv(aCiF)`0-h#|4*JZo^B1UK;e8tc7x?Dzwe z%t8d2K#R@;A=Fb%xrj_GCV54TFpvmHq%IIk?veZp*S)mI-t}+vLbBNIn{|{Jc%Hgi zR0VVO7H$>((kJJe=Ifrt>pN>;T8PPgg4MdZ$n>)D) z6gzYH4`_d|7WK&whyj<#_~~c0uxlG@|0cyW;`$DQn0)Nx=zwwce!t7mX!6$@(t2iZ zEi1yB(Dh;scHzodgGC^7wwn>NSLxfV&*NP8Id*P?Hd5AnfBpj^N)$*T;M4QZ{BX@t|5yxfRm;-0UYmen6P?raTQ8fb*@GtERmtIX8?KYjUh zCD+CVYWot;14$}zI8>UP5?AX*l_DTbzI=JOO=UOxH8p!%R!TFKFsRC8jpmOp-frp^ zQ3_klw7&HwIShA@d~apfZZVJfIPZr*eLtVnbh5U^x0Zr#oL7z1x@tnx1y94XQcaz8 zV>V?+Yy0Uczt0-#`Qta}J{Vidnm6B;x_yIw1jUMR*I_ck&sh*l@TLQ%I2lE~?EBNM zmn%MhI1(KgeuQYcl=4(nV!SGxp1)_ct|d*=Vm%6CjUk9V1t7dZ}Wv3^u> zuml?7b5P+_w-j!kda82+DF~qBTd;5TO?RcuE075pC%`j=hbeY`R>3vTXxb)Kh zn|uS;0D?DR1cpzHK|&F(_=0e_1>OUFp(d-!30C^CdXy#R(gW(xyc&|c)|s>~UvK+# z-Jr)IYv1l%J69d~8VG``rl6JbPS{jbjihf`Oyfjj8P>23mA|cV?|UG0Kv>6M3Pl7) zl&~bsHq=JF+rn8!lr%O>ZFWLmxHcJiPaBT4o)$o;ZnV<$xT{Tuq(o2P3NdQ!Zg<{^gPrgKx-WRQ7u)-VF(pME)c+tk8*7seedIF!iD92JY_|dJtF_&6Z@6*!2`y)r+XbGheMFy3vH9~Ry2oo z(^BPaJ@8KkSsLFo#zBL3}CEyqRHXBBQ|3d7*bxFkn%60bz0Wj0|4OjGKe; z0BTzx^2@E>h^u1ItCDLvT`hhFYFqfMH>Zd6eitS{jJ!ULFt7Dq;hz*ef;ZVGRXaOH z4=FymWBwp#sI~*;H|A@OPsm?`&mZ(=qh5vE4QLd;blrZDKb+L8lj#)(hh4qcftU~9qY(8Zr3!A=Wd2@GkKjV>Y7LV}JN@psd2t_Ny zqyLP*OfNU6{{DCaE)&0J`k^(`|99{RsQcPeeJ*D``Z9sU9!wlXk~o9YK%FQ0Xk`(^ zHWwx3?d-QNhyrxW%9jlR-9wOjl<8~7F9;F?#dK#ck?g=P{scZ>;TlEAf8=O?-U^v5Sh67z(6|sX`81;mqknF zb8GrCGXESu&T+NJocUJ!yAq)iaWwOI*G;vw?8(-VeM?>24>r*(im-Aa-l-6H0*s@J z4?%~QO-BJ)n*(vjh08%!+&N+&mwz?i<62f(esa#a&tF(Ro$8#XbtYNni?RlIajwt0 zeV|ae(HdUqU^^7RWLW=x`RI}-&7&UCzzyf$Ta`~)ZsLxG@nXMc->>N~Axu(8Se6{G z7DXEq2IIhhUOx}FyS9db{R&KSqjbpdopyIha`}qaLY4T+@h5MY?c}=W{|~Xt?WzdI zr<3ZzTLc#|Bk#{->1qZ=Zg(NRH+HdL>Spr00`1~gasuv!0!fw8MEH9fiG2u3_f<=# zSDh!{okx>3C!#tlt~op2tyHH>22rOZhQ-B+5Q4-+GIZccK%2g+*hk;evnRq+kn4n{ zE?&wgJei3d<&N^&-b{53Y~?3o6oowVCzh567UC~NlULq!5zi``4i*e?JyzFv27n9+ zZ@isR8K490PpLfTo?&pc0h+<7hKL@1vac!Nh5+CFAN9EZrpx{N$_2Dqs4<>sE<{Kt zCQAHx&SX*vCB5pjtjZ=`m^9D3xyWcPW}RVU9c`7{)lg;U-SSwu^cVgc?D+zASyhb34V!sl7JLM~iDhFUVH5Q%ILWIYJnCdho$G6PKrtih*9=R`1sek@s zJ;8Nyu>TL}ACM?225?x@Kq%=xM8=nSuYkYEnCDlu+?_dPQ!O5o_!@ZA`I44cv4J0A zxv?T?gzP>>5_b34fT*`-8Y#$u1NYlUG4R#1hqE1=7q8BVxP%=;===gqCoUdbXahHv-sv=1gLlgfnK zXVMNU^g|w%$*fxlRK^>~zXQU4mpitU-&*C<99H(*8K4u%W|dxcp(Wh4zV98!;9PsW zhc&Lr!&T)?&;1^$GRyO7ncH>Ox)4wBU2<`&T*|f7c1ummMI}aJjrFdhUJ`mD^oucV zdJD!Nb_EbU7p{pJT%yfP79-jd=x<=A$&m!{sh%*aIcnZ&&t$>UOlP=~qA7$=>=8Zh z?pS0(9Z49WY@SdICFZW1rv&&mYYVj$&A)Ec=U6h}4T)|ae>85ZyM}wT_Nz9A%l4ZH z_2?jo#Y7wfC>Xsu5o~7#hzt0O-;#bi23<_3;*3RJ7YlLi=_z_9IcQvG&2Ix1Bf67x z5Mnc3Lf$Ag$a>yra4wFUYP0 zd2GU8R2G+~lXsggs*PmsDRk#xuxX1g@Qr-?qF)_yERy)}&7UA;W~!RRZe?0ufBR!eEMrKNbKi8M6b zPYN&D4i#^wjPpIY%YUN2_BE`FKURMBi6wySyCvlsoBd{L#bT!^Iqp-JvwG z3RGB(YY6Fr=sHh7Ft_t1xRlN%X?j-t_(PxQS5$v@d6L1SkmkLiL-LT(PY;*U1w*{_SbTbyXQo*mEEjoAC>CSu)lP+ zn%gV#Tbny8A&0krnn(E?p8Ny)mK=U=qLC?Yd6Qb3ct`tSoFjBa_5S6U)2{}|7s$zr zC;RsFy?$Z(#HMveaZ+!K`8j1r^{}(7Jhu>cp$8#Wiw(h9wJS8Cx4~O}w{rjG7BGXu zwU+y=^6DC=ZVe|TF)1nv(18L?G^t=nRPQCSJYFdEUF{$|`6|(~?^^qL(fW9cS=LAm zfjkg?;)$*vRUw~_3>lQb2MEg8CBGOa+SLtz_>6TIJyiZ(% zTp!5gvjB*ap;iZ)kD;BZ83DaQMs~Q}?~n!Gb-K(R^OSrf``7J=UmZyz9~+L0z1jIv zWe0_sbAz41Aw8N~UC1)uJ6Z>8wY}hkRW^~Cw}6?bj~`Ckl>9_DpG~STM%b_4)P(m` zB`v1-W5<~&c+VH#V2v;S2&mu4_K8;Y__NN2r4hPp&4AN@#^<5-845h_?Z`*9UsscT zkkrUQCjsIk^moc7V!YfUd{9#4Q|O4W_D1%B56)HRQz`l8grf}_|!BqeU!jhN; zUypP&`zhK|cV9nn)iYD6Kf)J_Yc&enH6Nn+pk2-4xN({$iGyw>Bkh z1{ToUYq?7nFC1^r@+VC4Z(aeziaPn$fV&F(KeIxt`05g;oyTdhY9&r_9g0FJDGH;5 z(NxMOI{R`pMBjCu9ykkSL>q#+SX1rxBcke7XVtCIsl8)4(Vvz(brnBJ-C4l8F2ozH zpKBwu3^o@tecd2Q->Oz#o;Ta|^G83v-saes&1EerZ6xfL5qBI%6hShO^_%fx8Snla zInMWl2%tR=VZq~sq)+rY5`gRGB~A$A>(xa^fbE^&6ZPDEm?xlRk{Xar&E0#$!sEoXnXVMqB`r#N5gu*>4gt3AUnwBK)^RQ!jxdX zFN!ms-1)h{uf_3cHc$brfc4VL*2~ac`uV<9PqJrtPMIJy3&?Ze=!8*%RK$XG)hQpd zl@ysBEZ}T;lL0)iBLx&~@mFoTwhDtQm!5TnlhC>RvRbGa>mA7a-h5 z-bn{dIqyopIHLV|;zka81GZ>atT6%OS2#iB?{6@cUE02@OvyR{#bSRt%l_tzm3sJL%1>rQO#c^3Y}@Gkb9ej=tvk$i(#NC3tG1rA}^^ZoF;3w>kkqIr%7#hE8os%qOJja^EBMcp&Dlg5t_ zww{^dLO{iBg=E-XuP~(wJ)Eh3TbZf2@gUyKh3eiAL?%3}w|zA=WJK-)(SO`b|K7A4 zn;xyc^iPy7Xd_z}#BK~KIHMwNM!cp>tTxo3Ny>Vm1?ZHfmJE`$SE2kOjmT)k0Mf(Y z1}(LP>C?752EJ4T&rh&~YX<-wR>-@U?u$;C*e-WLVCm$-vU;{Bjlkva=lP#kY_Lmx zMS1H)e;@;+z5I2e^1IooyA34k=d;+Q*X#;h-C+~UPu&8^nCp%+dO$g7sg^>d#b|ZoD6fN&{QMp?>>uUsHOo*A6zrbo zSA@Jc9OnO7=K4V%dhryXwtRxKlDP>ptFf9lerWVnDu3)li1jx<*?USeyiI3oZb(aQ zc>XMiI9Bq#m;n$QQ6{^zn6!)5_AfG-v3xD0Xe*mIxpvc}!7b_O`Q!%kfO!5@uCCXB zE}UuF-#Pt#XL=e&R&OD!tY?C8)cDufy;vGTR|@HPi$Q07?G0E4f!Y5?aV&w8Bjcvt z+kFqNgi)avk8I-xet0!s{|T!v{bE7&QRT-*EdR!mO?DwB(_j1H{XnYIUISzoIcyH) z>nN&ENH1<$H{(XTt4H#|R_;d{vVtmLm_6BztU z{no&OGyhUS2D>SoMA%zfAOKe%Ybqa6?xT7qKkQ1GeROyB_{L?AD=Dp@oGcRu@W@4} zJwFIe=rv*LIDrVOA?6s?H%#vx>{ZveHQh1_Nv+=5%WRFU(CzWqV86;hQ$zU0DhHGU za8Np;q4ozv_G`=YYEp2?GjQzg_k;PwwVyGBN-ThxB3Oho&qQQF z`EMUB5^7sD|2GAXaX(X@1`k$SeN{TUd-zF3wt32gRid1sUr#6ZPVE=e{DUrq!Rl~4 zLJa>iCqVk-m1)&3T^^HGx}?19MA3XNP5fsE>fSA`U+8}Hh47Mr)rQ2hT5TmvVpDo# z@Ao;okmD!gTQV{BEN;4%$-^I<_`t$!1)SGn2!p+H^I_c-8KS{^gbe}H$4eITEOQup z$Q|kKHG|9gYV>?c+>};=7fAkdBr4-myNfv;gL)vG)ta!f!Xzw-nN0+fx1MT}`vM5A zZ+ZNuqnn*9nR+aa-zB@I9Q)CboN${Tpr+fWM|)@X1$%7Om9dd(=kft`4X?NMLbJ zR38r#e`L(r1|qk;!9+H_&=wE3?mq@jGf@(;jhP_t;`{90bJpwz5zrf8jVoDu>1~(j z$IzV{Kf6y){yGR%pXm6*>xp7A89C)%4X`AcV@>WOW5#QSJeLgbJ+e3nbAIqhvfUYJ zGUtb1?NNoXkwe}PFOL6^emCpSO;@06>pE@%Wih9TSvnE){cXQ)J-CZWfeCb4;HB<= zQR>HE8!WS1Tr@g&-riP)8&Q;2pWdJE+#^aK*Rh43kHeyqDFWmNnQIw|TyF~i>MZ}0 zJd?HMU}_qk5n*Tg5%yy>_1`5~REKhX2h>XdK+~wu8p&65Q`_883?pQ0r1nxSN2i13^@nyXp7=fIKaJ>fA(r z4Uro)I#Wu&_N$vSu=0au5Ef_{#yyscmhD90(b#L@xxuIFv5|bP_gl|Me%3UX*O-Os z*4EZrYqP)CPMgn9^)Enr(cez`y0mA4Bcoi*MC~H{)`~bQmmv*M=Dy6etlWBa-p@TW z=cPXA$bPSFS;f6i#rT{BeF8Fsqu%|%_lMOinjMco!`)3E)im6o>G*93Wd#m%7ta{y zrm(PBgu#P#R6Ko;om{L^Zi>1RY4^p0ju$pGvcOMHdy2ew#EuM;>G1O5p~2gwoCy$9 z*GQ0aly3!Gb3Nu@8)AW@;g=nzQqex?lGZGV1Vbny>x~~rP@3v3ci+7OdOLE^9710; zo}!A0Q#NPWQ8|SEanHHsUm~XP+9j8<{n&Y|`!p^f_KGtSLgfE66^yv*#arq|R!DHr*3wgvE^MiX}z zx+;h~1k*m)d2MwEG9SM5fsjY;@??W=ml(lz$Y6M6xWg=8CXemI%t4wMUKK)w(-_`Cvbv9 z59{}@;Rk((H$}SU+~j-U{tZelN?tRCxtYkX>7z)O>vV6=-gIiorRjL4n>rDj!q(@> z5$kR2rW#}1QNZct#`QGl{YN$$)NxX+Qp{0b&WeF<$MuxURe#05p#Ph%f2RK3U}oAL zm5O4}@d8#x0ey_NByko+=hIHxyb8A(SS0ut+hp0)%pOrK#|@igTRfB0QAf_AFCruf z`VL<-#YWH!|tJ6bqC| z|0XAg=Jzl>d9?u+jT8>gInHPzbgszEHC0*&Cghc^xM_Svg(^h9`b2k5w}VGG!9{8t zHE()cFx5$gJK#Vkoq*51zQ90$)=%DQGqO4wZ300pY^YG(LeQ`^WhND-6$dA*`+2#N>Mlq;QngS=gs=#z^@ zfsnl6Q-(xu>^vd_2Zl73%N%ID$(t$WbJbEaD#=#XbGAI(P~h7 z8a2&}qi;9pcT`g`Uf`H(ewaR>OYhsi@II+2Q^*_bl6k$YrAVe)$hfn!E{j`)L+8^r z%_GOfdL3D>H_12G{+294izH35H7rhtD;U6b7#?{KLz}xYq~X5a1H5IL>_GV1=OG6- zPvw(yF;OHDgbuN&4Eg1cXC`zS`gF;0;X6T>5*%jjgLRO)Qb!+2Yt2ypvVkpDN5(b7 z#KDL?KXi?bPvZNP#UBOFd>r@1EG@EcJp|FTVJj2>8Dk0g##cbrx*#%zMWwlBOqq1f zO<8mprHyCVzc|UgT+x60o}STU@YY-v^4nj(5-a(}T&HRnK07jEe{cW2X$jr4yztPF z%M96u*w-?@owS!?BtM#X7cut?1qpX`Gb)- zb&2|j>(*k*p0Q+)!E;wVL(U9x9nP zkJ47$?BTyj{QW7`M{1X8U-qZzh1(0Jrq4eJNoJK~nf#p-XRt(+MoH7}nD^GE=+qBB z>0;2s9~crg=i)=mToxo|<@_GX7M#{V^|plz_r~LBpWS`2fC>JovAba|gpR{7g}_*d zEysa`vEzp7`6Ux*W{B4v`+bYe`sC_2hT}G@`cZUP z5Z{*ltDW}k@Vjk;4j$!0F(*bns6`E|3&QkkA@(5cInkJpvRudu7P?VvO^(#t`Ug}! ze}WQHw7qz+Z(C_ZGF?QN0!&{RzAHW~1n$ayqj@iQ+aT3n>93~Ho2^H zy!$L-`Y#sYF{+=xb|G+gFvxcy+eZ2^GOeyIZ^is_pOunxX_(}LrsIl+4sLbc)70b8 zv)92u>ds&a5O*zW*f6CTA~+q?dOp`E5_#5Pe9MR%GyeWAd47hEHic8vX%{V4HQlej zIT!V+*~XcXeDEgaZtmY5L-+4&;eU2zQQi*2KHAA*nG+;Gd<{5`&8#@+wpTJ!Bw+0kGda2 zfAOj1G%ZA5obN=Ukk*&ZAm`$T?aE6*&wCcP3|XL=nz+Z@4!E?r?_DKPq)WsRY<2!V zR}gMU>4$T;+)t%s;gH-~zv~g6RHl#a!0)pNCg`udq}Y7DU+Szih|BDk0;i(*LgXNN z{<912eP4`d1Iy%&eM4+|vJ4GvBwadjYC{9&lc;7$e`XAXl`Qtk1|iYb)QV~R7$K{f zuI;PB#+$VHHbK~M@6q|+{m7`lkij_$jbk|YHnDDf-xX^~oNBxv8xW@WytFW~TeYw7 zl99_r-8LNWxNjIhge`{?P*_SGwp!Ms&^Xb(xY+il$1GKJkIgx1BY?6=t?C>z*~yY( zJ=u+XjvUkB)bW@|fSliK?j$%A3lhdZ3RI#VH}j~fa`#3u%yM%d-ry8wrXN)m_@Kt9 z9Y(Ayp^Pt_dtNL>G$MfS6C_ftDC)y2i#OXgi?+PQnv)b0L5G9t>gT#Vrh-43+q84r z8N8gEqh%M3RmuGR zV+0b@esL2r(+WxS`^WF^`~%W!{0Ee2u8jBx^!pC{ACPs&PsGa0IX#jdWjcT55^>^Q%}&HDU8ePPm9#BXT=^c(8M?Y%UPm=xm3E&O&;NSS-<*Iyl#M8N23 zgV#N*2tYr7B^7ceHK9g_o3%Av6*RPF~Vx!%C&+C3Y%|$NI24KG0@SF;?vxJ z*ECTybZkVt@3gsmVnj&j>|O8>pr}_SF4pR6y^9GFUEm#@kmfc7i>SEY8=_~wR`WB= zZz%wsiK-7VKRqg>oV5@wsTs4Yt{Tx^^nVDRm7^mj9vc$%2w^tFTKYcj>6%W%IT^an z(E70pN$qfzy92y0lBHGNgDSEa{B02MHAmGoiA+Qg&KuBX7VP$Zfh`ZIWQNf`baj=U zROGwgN94ch8an>|<=p7AI~(*rkQjo-y7?zabg?AS^A+L-z`&{uFW{5e|2jiXRoj9| z=Stfca+$ci9`c}Je3b($;IuFq{?~>mNdJUv6uA!O^cIqu(p9z7TH*0bs$^8Jw*F0V(Oc9vCaHVb?_53>!uf-2M3W6CRXCOS< zRK)Fdp~P1Rg|?DRN%n@UOLtyM4($En+Ql^G4+yH_ApPD=unS9tXs~xE z!OlkAI_+9amx*Q@b$wn|(gsbFRhR3V++QCcZ(;Rwz!w9v4)3+EOn2=Ezao+xO|Tsu zqznDi%~|L99@sE04l(dw|E#y%_IdSbMdjK*ARe+hNwH^r@qiUT6D3y=OZvljq(lv( z{1lQq=SC+rv+WJ?HkwU0&~~SVx4!=N^H~En=X1 zh1D=E^rGGblFvl6m5Z8Xy=N2%uLQHnw zmz6iI4sulhM6U-EU9er=;gO&JrhB5wUQ{mNc=JY+55^WNty6@I)(k*B*7OArz zMlxF<>$QFT1&QyHh@4=lVsmg3S!kq%=}U%x(m!&DiMOIIX8k`&?0elzWAR z@O1D=ZJzQOY|XqMykOejzMb)#1dRE8R$Lcg3;kCx=z6iVdL`NQaKul&FP_w025%Ct z>fA%H0k+^+Sc509#i*nb40i`7%I%fxLNm&+4Bu#4q>e-cp7I@{WpxdO8Oil;Qf^y= z-lSa7mHOoZ7bMf4l~Q<#uDuw}PGbr^0sUm3GMW!RpS==3W5L&Tr>y5wJ-U2&6cG3eCCLj5=C1I51alE9YCJ=U5&mJ5I`Liy zelPt7U>=MfPM_R8C`A(KKic_xpZsYiXr=_xgn@h^qEqP}$%Q16mmrMU@y*g58!t0PwbeHtkr26Q2 z?4+7Zd2ueykAmoYMc`l7zm)p50qz!p4ej=y`M-UCmUgeIbfBJQeQY{$A1Y$c2*EtH`&wB zX027u^?sSIDcd+>3BUpM=^1>zOfo(o!)bUT=8V1{fPYOT%RrWRH)~?K(ccJKIepT5 z54oSumNoN3WsM##rKr2pd@Re3Y~$EzRg1V*05YuNPcxOyqGdY@kwL+Vn9c5vaF=^& z0g=S?9Ny#-lQe_|&|9K7ynh`0O8pXi3dffr4T-O3ELxHYMip1_58FMS48Pf$;MW?6 zEWTQGo}uoahaCZZISoOh7F#wPqk3-z+CE$PcM);5jMnqW_W8~TVR1~zB^wsVHA|e< z|47rW=FESUrFz$uiw~a%gdVSp$I1$(!5W)qt4WNliiF%x*ceS#nfbDKClE?|rnoZVb>v!z_toNThya=@HIlNmlX)``ZnsGpi;~-sv2Te3 z4Sz?y_yP>2-)g|v)pzA&xZ}7;>#u;QPi+0J@e(>w(=2I^U5($aoiS}GKCqBVGiD_m ziJScr@(3vGlmN_r{bf(S)HK}bOL^L+R!7wDXK6)-_ahkie276WF4-5mEfvAT!^>CB0}9STVRjGFv6kq_-GTk&SSeT( zkn@qYT?JnHlKZl1(!%T$I&o=BQILIG;mrq?THwse{>U{dkleV&yGhuf_gx6;0-%j) zuBjhLx!k;Ajx4u`NM{>}5>DbSG*83YuQF8yT>*Q4p_smnj&mgvY5)NO&!2bu+=whaW^nxLg40jUQVl2 zdagG_&`3w(e|5Y6SLgeGm&f3L=Kjm*cd0L50x#)=Ek**$2Vq@VD4oDcQ=*3D8zF{B zJ9+8H`c#xlIZ$pxXvLRF$4GC)?Q8(Qogl@TH)4b46rP2`wKoygJY^)h3` z@u<{b<@LVU}qn<&Jpy14CpD{*%}X&i+xi|2SlNkg4|SqNPTOBo(D4s zFTHdD_To3o`o$v<)P7`Ov*|xme=l1w@hcZh`JS1WZkp+Uu$4W#$3# z#M4?>5=lq>v?%{vVrPV}Pv?`=Ayz{Y;Sp|dL<^Yr?&r3v51l$t?7a}BJpUg*a~^eG za%TzT)NRpWHqLyc^FLyath4@gdzjYFb}NrN1qmh@JlE}LZXwx(7wLWxdT{z^7hAc> zY0`7Ki9Cx}d5+tLdy5ZPD)VA5jXHB4Pgj;kyJt~)TIu$F4Brq6e@cFf-E-o-+1N&d z8h}cpA&AU{NW!1YV|eGQL)ac{f63?Dj#!TUwCbhN@bzvKN0{Yjl`A6Dr@t7d)7%*X zIckk-Qya_O7eqd)XG%W^jW`&5>H$FwtvCEv8A>t;$mG(R)qN zY$4AkAL83mZ&SnVylT=r6d_b9^d4>~XcQYwcqh1M|C$8HpS6omf8M6M)5 zzE7bh!{1vJ8z`Cz{Gk9y+AH z_2uACmF)%5d7)O2SRG23o1G9c*U5;|Lp3!lz_q*q(hE_PLTS5w!aHvE8>wNPc8nc> zUhXt_Aq2GdRy(KCb>X679+hVAOgHQFp;z}#*P9_>WuiGaE&dAE-y6bOfU4=eVtb(d z(kb(5nUWj|)!u$&*F-5cxU49d>s0gjBKeRqnS24YTv2%T`mKl?S`y&t?!Uc1r777G4&KxZ0o38I}NAV2kk{Pn{5MgXS{R1Ke;V!1C z@S3QMxp>&cBjIw_Cvo)fSx{VY+VgEQ_qR$XgMo;H`$%PLC$tG5#tVkJztwq%;rd+? z!eI5JvivEmPn*vti1tgj4ZdPCrzSR(4*qa6r!(T)+Ng-@nlaI_M)cLlycxDMz*g;r zDjmTAD?VU&1O5bC(64q=2W5B*7OI(nLNNtJXOm~-nRGu1?{I{!}p`7lCJOk4&kq>OGYJSc#FMra#5i$m>&?|9zBsf z|4^=+QmtxlvTdoBdl7KZ@DGR_GFTIv51y5coDm@~?ymNC^S4T=n>R}CzD2(85z*VN zt7$*-BxqKI%&s%ypQEUmHlF~7o#kDNIRQz=sGYqFR&)JMZ2$L<<-Mc+DaiLEfg8P? zZ~F%~8aIZ8t?%?Vm$`T|P^UI^PuyK;xV6Q^6Ryl%YN>Y!?nT__CbNQjWDTS~to)J+ z+z$T6cHR3decoA+9&OO2bOI>g->EeWZVXvI2pF))iV^J;Mj>t$<#cImtMpX%G~ZxO z^!h6GeX@0A$jAH7;(;>qh{5>>{ONpCo*24s`GI6*ywxL|BMRP!OYCt-hfXS0Yrl=d z87_?jh8>`7f((R1h(fOKN+>g40Bw`E3ES?}SM`mc(iD8c11+J4BU`sR9lJeiejpOnD6Xl2?PD+` zIrNr_Vi59M`C)P5LACz-wwLziv5x}$mZjUc9()e#sSY{XEq#*lZ|3p;#MJqpDho&n zq)z-@Q5}FF4Uutar@-e7D)|CoA`UaUH(&)lZqai=Ap+QEe6T#sH1 zb%#0>#3>nu@uhZ}IjNjU=#Yi{`1YXEIgu8U6L?&*loUXeUC8(p+bkaX+)$ODm@${O=8jrp_wZQ|1>f84^ z3UrvvAhsU59U%nE(Vdv@2sx5)6{4PWh4U7(oqP0$|DL-46@*H3k%cvThGULE*u^IU z(L1xxpW(U&38{OAP*6ulJiS!KaqVE7=5~Fd=`)t@*-Tl;X>yhcu<_^sWa$K_+6Y~I zgzSYTQQ95S-pFkAp+}d~-ySjPF|$y&Y#XbCqo8mjZ2GC_bmtrF<5BIdsqn5DYdrcOdt;#HCye%4x676+q$M@JnXLwrQ#kUae3JaPl?0UZ4 zslY=wkK#Kmi}5;I_$;idA;F9}L`Zy;h>degDuv)khMXV;68mc_HA zh41SxP~)TAscjT_TM100K_@m%SYp$wvJyO|P%}V2#z8cY`?T_H)^Y-+qv27)zWi9L z!HN`ks!?2l@lRIdq8q)4u&se?r%hWxr7aw`~ zfFO?@4wr>8!=%ycS?HBKj=>#sEsBTIm1!lG#e)N-VdA-wL&}R|vKN+U?r0zoQWky- z7cdwKhKU{Zunk9Tror<@-IV)EmjxFC(#8Ufjp#C(cavy#GM=RTua`e~J_K}CMF_$+ zpt_>_H;8^OFo{D&2O2iUx${&eWFi+S{EIMPPjgjumX!qVVuW+BL5N_;s0}^7WAjRG z!vObw(P$XT=lyXRX1#3%8mQIdHIiWEO{gL3G&{Av%B3F$XmJoao=ol=zamSI7v$5d zADh1&Wbh8hu%$)|kvm>3JG3wiT$QWxzzy+*m0v2p!7PojixD!_ANSBNKU3m#Dz(C zgX)=C*>g!rA|KAd#nA{FZl>#LrKw0JL%w}gq1JkutEonaOb~*4smAFl1}=#J~+H|ZnGAKQLA4V89xo=KOxV7LDm{Vr_`6O*5%<%zib{b z5noe~V*e~3Ts{PJ{n{H{h>`9U#D?QWKi9mE8SySY^wopA!LDb$MsU+v?!nV(J*nSr z9$Cl3lAmY_y5#Sh+VZ~syyeLvumg#N5CM*59dzPG-%tK{{jQnBUPPvQ`$}nL_b#!#1le+RcW?BWD^G32VrR} zxV@)DC9eFFkDMy4(w>a2wk_Rz7syS6O~8X8Wz%>8q#=e5J2iAIIjutEhdqN}*GjTP zy-JJgrFA&cSmvl}YuP0Qk5fNL@SH$C%6$3P3=<~f8^1O47Z?cZGHrVZB-znlEs?mh zeDi|2+u|UFA(TM6t~7oTT^9X^lUHvRJ>$pzOH@0DiVAZY$RX@#b+{Uv-b(r~;lq$t zh@Zvs>iX)Sg9H7lm6g&khj&Flz#CvLKgyR2QshS%sd5n{I<7vba?1}`YxcJQdZ^NB zjcJ!txrLcE%`dz(ITNsqFx#6a=CqbqFPuEjZ;93Zqu6|KaXCy>HE6 zY9NK_OAS`&(qaLwRore?@nXGHq%@2uXwZodQTx5@s}vnqT^-u+=ge1%CAB1ub@K`!Q6f!oJFS$F}5ebQJNH3$srW zseQksA2wj@78%pE>)yZ3f&pf;LpHVBvt3q49> z5P;pw)*&H$T{_o1EyR--1JLrUNZ(KjJ+ed(+%1V?JvL~fn8`>tyf z>dF2&!n~?>QwojtXcUiZS2+Xp231?Xlgn3zthGJ}r*eN(fJi!ev?1?g97|Apsd02Z z{Ne(n0T%#vr^cM!877jC;Sy}enhbX7y<@vv@88ZpUXf5G&O+Mbu740Cbxy}VQ6Db_ zBSw@%oCk6UdFQ{pfqpX)(sY&Esri)v@5Iv;xk{{B{ro;IN{D$K`zlG2cf^i#ML~gr ze4JK_O!p!iN`!9*$_>`ec>PwlM~Gx2x8(bTRJYS-@P2+d%qj(OF7x@@MN)Llzeu-U z?&jowiEjIv=>ul}NpyR3!s`BAvJ$b^>oD~2U!vQcs296qVY{9Ge}Z8`Dq%r=2;xxJ zD(S&2V}eR2mL^W-7hk>`eq6NPO3wU{cRY5(Yy8_l<%_hNioD19AASHOz%wF<9gO=_ z-#{O=oi#8O7b0JWB}@HA;zrXrAdu;s!lW(a@Sb{4UZ}!RcobwdEPJ7U>GZ38H}0AP zE`l45;>mi39qj(VSn}@W3^-fiu}m1~g@Sb(k8SlWa?qUV0Zk5&ZlmgQAwMuhhG*^% zk*>$_=WJ@fw{*>{+mX5b)HZm!B<+UL2Q1%y@3NNd{sxJ_k89OotjIp4Od4d_{0cE5 zg!rw;estWDetiCM=-PYk`86F7t)@zepT;hn0dYo?Ib5Y*e;Rn~4!w4{>z2ymRTI$? z@Mg|OVKCTkQw;#~%&9?F^nvOwP}I_BwngW1Hx+L9vDaPil?$H~~+rGj*LJue2GWQ*ws~X00pHf8p15$6OA-;-LWq@q} zL%Z95#ba$mW~zNT!FqM=nh*XMxbvlHbP9gBFI3L@DVmr?fWOLL9e#-&KZW4r2O!L_ zVx6oPEz`a zZ^!kbhwrp*Y0>gPI$QVr%88UM)ndZGi#WBojox+fdMtO(KJ~4>y*{+4@m}8&GEOj4 zUSsw{EW^3wPmEoD_g|H-J@Adq89*sw)f>R>yeJoS=v;Wq=u@ia zUAv5DNF?ayK7h2b#U;ZW{9(l3nWEak@-t%XWJtaRSyg&FlxwrzYr%B|14? zMH&qyhwV)Eo6mM?x;*G~t&L%u*tX&w(#G^jkKCf9PtG+3_X0zXq!YAwL=CXA=^t3g zzln|fCgT$7AFe4A*ZXup>XF>xc>wchXvT}&&Aq9m^Z_oB`4B3=g4@F-hW*YKT<~7T ztsLW&Hm2)o^D+~)t(R^5qMgHwQJ3?L^{q(sh|I>%;oi6Fu}Dkt7^b)ORh)k#S2g^a zo!4L88_sIhOxaCOVm$L|lergc&dp4Cb(JlEe}i08Lz_hqjNCRuZ?2Eral6zdAoz_R zhyAJ%Txs=KL)^JGedb-zs7g>| z&FtF%aqlwt0G-Igzx?l&df6IAP@g>tO&*F2+kwD*S1!U3byX8Q0=wJCeXHirzTr(g zdVC1C{^(d=G>yd4-xMVLT8~IXks~Qjcz9pOXhzn9>ru2>eg2Rhvri?iQ7h|m)N{8_gXavbE8-vnP&zAE&CuI~?EEsnu$gWU1;USxfmDk#L!CQT_HRuX z!h!o{a&$Y8$CtP+m)zTY%@2$E1bX2J+h$MQ@>gJA-05M_>Q}z;xF!}Jf@iFOli|W) zLC@Q)wfipAREPf34)G^&RU;>4^%qX>;4UvBO+t-8#tczDO(?VwI|VV&B3# z{$5QOnVXz_eIsP*ng{5*DJMuh$r8()D|TE1e5wuFx60MuSKGg?3eyD%--dx^jaw;k zhNrpsE?k68a(}nzJg>ohE8WwD)SJ8+bK6f^#}%HB82g744XMi_pG}B%R(N}280sYK z{qDVd`Sc-)&&>za!F51#=W}dhD77DVoEk}|eYe!C4C_4uZK=4*cp51c_cHLfF-rxp z4}5Pp%)68Gk{#AR$i+PUHwdU1oG+N_(B^Erx-;TV!hQi1)mJ@KsTuvRG&77TJrqIS z9`ANk1+KoRD&oicW?<3-`68_yGyGaf=AGrdfkvTGdUlBpUZFA4c^iY-*ss&`fAVzH z=WBc)ye4Zx%urMWdoXnHlzr)km79_ zsvW@{)7TM_NPAP3dw_q;|87^yU_rCf@CLJU+An5{Eq|sn3S+1GcRbmTcuC>Cna@Ih z3I*c^;WZFn+7$xi*)KGpUgkeyEfeTHl$11PM4i{ zstylDVM5KY)g7LH$fdwO5>6er#l#Y>&^t<8?Ka_D>ZSS~?*#aL?eT_1TBri@y=iK$8yL>2!A9dE znFQ;Y*F{hD1lVy?k9WK86N2!ixRhSzq^5{B4zAbsaw$cB^JBa!;#;{1nAYr%5HAF z&^WrKzlMGgeMGjQTgN(YV*kEQuq5Mkbw{r$kFfBo$A9lu9)P3qQ1sCNaE8j81M%q- zmIoHM5D3h|J_(iMZJxy5jAE&BK~P^SXYf<_ayJ@Va8QFZ5I`*?HbNArvD}iybF(>H3V>MdmeBR`VS@9wwlFuGv z5>)*>N-4c@^|iblVY%jPrfSV<8164imcJKl#E&r;JKbVPt-0KsI-|D5YI>MbdiF7b z6AlEp;sgdVZNza&^=H|Wc^uxBR>K^oHMa*acik_Qw26i5Y=5rh6DThBxPP(_ybTTi zt40TWt(&tQzkOLUfcvgEt1hft88w6*e|o*FXWVAu2QHMw8NMU1RueRFo>|Bm>PNsw z-`K|zkyqZyH=$h|F-vCJhd1|#Sa|5p zOok5tz|nq{VNVDXKArJwfRlgP)3G*l})|Gaa$ z$=sj|^M1y1ir!9goSwl?VPwQs>o($<>c_=UcfeM}ZXFl^i&GZle}`GWDT3+zqNnis zQEJ<&4(p_jyZvqi-t1BX{v0ps;a!GjexV;k*#IM6?GEz=r_Bs#9;cw=*4 z%TStl`C91uM**X%R6o>iGuYgqY`Ha4S@*6v&r6VS=huV=+gv6AR)rx0SSiD^pFoGF z`~OOC>>@@@4T^KZ1r}*y;NY*SIRSSAeGOsH^E>lilQ+B7s<0Ru7nLGMv>CI4{n8m$fzttw$X;BKE`$cwVrnuI zAly||qEF(}&QXyC{f^|IEp7gfBl6@bI1l(;YwS=uc3~5UG}>StL<6y{zYpuyd4(!} zq?zcA<$rF^OiVSuzcwO^OH_oE|L4fs^_PWMG1>rXh&@bY$p%zFt#J!$lqA$5hjc`a zJXCeWe8_;B78R3As=aG+yi+x7W57nDQK>q@dGY!0=FA}nkk3~q7hYTQfaAK}Asv}H zOJd}nU{MMwqX?~mpN#*)bYsKgUOHMPM@Iz^(Z8O$nuczn>*4I^zR(CJK+Pt*7T{l% zWAx@rymvEmo5KBF@>@>=K`E_#9NrNIT7a(?1;H@l?2y}y?E&F`gR^}P?A?@O7GNrU z64oYdn){6jXA?&F3fwL_ddRHBkkfT%XL7ow*M&kv4%fyW;I1w&CmrQ8U>~N=XI2Im z20|g!22{zLaLGV_ZhFn#+L31d52)de?)sj%=vb6@*TyZ-tIN+4Z?hwSt9yl}FPY{y zH!b`QS`hvmdFB6*v1^9Z3@}a3Te?ZLI%uCqNjxNdIOICnqlHpi;od|86e7&F97$4%KjozxRY&OjApZ+C9JXQr-T9b;_7kk6k^P zN7^0eNN8t~j8J1fm{Zv_;y1s?a*{lKK>j-a99v1o@Z;iw* z+ioj=;-1>kxPCjr`tewgWH)!G8{G(u_{rS@%V1yt%5?i~13vPrh3t0=__NB?8=vcl zMJo5?TKFn#DM|ZMAz;{k@7uF}1hZ>CIZj5OskG$#>kqu1+DNH(E^;&A-GGn@VXz{z zJ+iX*0D%9f=UTF0_?mg{da%aoQ$m*N6Dd!5uvEz5Bj;qUxv$g-UewlPovR6I5*f6Lwl-oIc6va2fp0B~E ztp~EtFF|)7bzemQK1UjY>Y|D@8o_K~;kwM92aGSjSghm}q_M1PnSLB+nz^Ld(jL31 zAR6Z?dI0We{I?<({sjpGf*FAjRRcea8CN1lg?wJaeO!85^);K z2l~<#Jrr6W8UY0$Ac))DWUw7I=9j<7`LY&f?pA(Hy&uT>(o0h)v6q!>d(>0_G!wrH z2r&#X%D7?FVpNsN9pn)}Lv;Gh#IH%kZP%FNl#Ssf0Leo>-wj2)^A*bB0hf>AZun2A zCHVc#P&$Am_4~P!F7_&IhNIERC+|mJa_`yTTeaS!rLspjv;J>W384V|r<)E77sK`5 z)!CWy8X08$@aA2oyNOAFKh=#eBKM;twT7qy>`|YR;UIVPI-eo1aeJ9Qrk|mnrg>-b z$E6+>xwf%askyrY_*+9QM|60o%SIAV-pab5Xuzy;(TuxIeqeSX%m1c#R(~doyRA=)iF2dGe(s5^fs$B8?rb=4@rVG@Ub~j(cDb0cB5xhbvWu2^zEAJOEx~zGa~l${3iYfBHL9al<$DW6oB_OUdmz z6YEx!o@XGIOHKW&*iPmzUt&?vSZ+GrX#iuOkgabmCzxiUz0!BvocYc>)-f`kul5NA zao2)yg+;(8uwjf9kG+x8^&pBbS=DMgf2cx}Gh zMlOUZJrsFYY%fTxFg5lK$eREjkr}dc^)?){)QIjMQQ&thp zI;_E+K84|_y(DH6jYgUQfWG}cQRw4&@kRsVDT|ryV{v!>Bs4jQy59Vwk{k=IR}TlR zPF1id0EDi%d=mO&6WF(4)cygb?r(p+BKv_r7F-L4Qd_xROXD2A#8(WupShK-^qwvr zr}_jSa%i6yd^osOd^ueD;`uM|P$+HpJ^Tu~x;_6Mpvu0gsL;7#DboGq*=s&W2kY7* zanjgH=_!jnxE>(ALT|)((m}DC14?Jh1ZCGu+_2lJrSoZ3`Hed}eEN5mL3^ub6R>TD z>YhIvQGf@9mvATldKa}`bCu5jrLMI1-vG4%y{ABcnq=#rVT-?d|51{dI_I;hy!w#B z`0A>_R*D8(+7%eud-Q&t4?n#b1R=pYml*3-$qo)6_Ar=D9+gV40BIP=?=_g1FZlju z&C>X3D0U&fddUUCTy>~og4%ysnz>*f`?|HYMZ@CewHI-NSWn@WgXc|wxY8h^6M0~{ z5x=gASFReZ#?u?g`KgG%fGf??CH(X$;H9sWCVS;OpY$$O+~DS zh@P{DZGxs}Jzc~dt;?x|uxvwJv*E&5#n+;6&blmJtYhW3FHd$fUEbU_yqQ{;rQYvDpX6T)t{_QEpDDYNwnmSMNnGhv)Wa!Z=D`SQ2{(C^8_cEe3_y1gKExC&!U#_ny^Legf zASlySW)O%u4x48;*v-F4Fi=bQ8~ijV&j;=8vluy&GigjwU%%=3o%)>@1Be+!1o6iI zp9eAl_wK)!Q!8$#@$Iu!?z}h#(<%37IWd(O!tckvCzQPRN4LU0b44e_9R~|+W({)E zpc#(_AIMus!g7qLTI6jWkvy(x@%fk#Vh(=&$7nY0*i|U-Bex+i=JBOsp@+{Y>N%Z6@& zXvXJzbAXZPfMiE*W^%yyPl|lPqdhUpkV# zog@6=xkjAgN3N+7ploOUqsHmFal5fAbQH(mcDp~&@6^)t#O_%bjcAc%wMx_W3$6zf zFL8?)UR($IY0^43E;_EPV>Wji7_)mA8@iBodY{K{xY;{_S#LC}IUcl!Y2t=X6UG{6aCEnLScp$*BU$3*fIqG;z;yHC^+ShUZ9a8N9 zzO8UFMaDTe1bY;&B7mbY`Ju|`2DZUo)uf-g4f&>wb~6N=Z<=T?Z)fq``ru1EEkm=s zarz!R6kUdgYT_VEjTo_;n5v>n>hyE3zCyYH6{=wh&?t~OwRO!455>3P-1M4%T=VR2 zEH}%g$A2uo6m4X8pcrA7&|*~+rVH2IEd>tZHxjBjSaD1cZV`3;jycLV&qG{9^qG!l zOKoX?PlUpY)NY^R&s64uAVJXU&QJ@WZk-4n7RQMjFF3052l8fNrai*7U@6*>j~<$M zeI)}CbH17I`e5V_UNjC_4Q+h>+pSEj4a)Jg&|qfi;cl~XZbh=d5-AKEt;&x->9di_ zLW84aKUFqe#@@@cELnb1r3_8~u1(7MvQ22rOhI9UvCpgl za-5w?sxh~f#BbS*Ck2M?JVH4QLt;0`c5?CbW^&Cf2Hin=Mvl#?VMCkj5lwZ;pB~qN zZg>v{6QOICrjQO(k#MbV1QC1%Q`1-c>zSaogD;8FUYX-?(gjiYYl^LD_XP`l*wypn2d;5Jk9pvDKw+;gQr97?6)fK%NtiJDpIqhQ6X zv6kd!tgG9q+bH>s{wX#Ei{p5KrgQ)j-{Nn!LHieqacSIwA)$9~$_b~KzrG2$Id45R z2&{VR)Vo1s4#<{Li(mls`BoxR9q)kHdkv3jUMH=e*3w-c1kc;^{~X0`Q2D7vt1-wz zqV8&zubHpi>%2{J{2gsuMM*Ml+un{#pL%O?2>v(?d2~HpKrEtqWB)=vst8B4C|+|A zxz=};GeYYyCPwq|GJ+ARA-SOf$7q`VTe$qE))7chjqhKl0H($Uyz2PqZ=96ZznKDBXqQ&g$n)0~S?$t=K*}G8 zQ$P>Y%WM_D{(E#X7^(kv{o(#+;lB*uSC8UU$+7$RXkf|1NPrvDkP@V)E&e?6$7Ek< zvV_Y%m2Md~CewzDKf5Uk5x-u2dq&{jpa{7QJKUM~sBZdruU)XD<%8Jg@aHT7LwAz;So|DzbAg!y#!kbD9Y06(R`f78GRmKRy$FVQc~+83I) zBOa8t>xk581H(zf5B{P%wF=7sKG6VGrTKCi;bKUGHzqMKGg zemW*Su2_EHXsM=0jaEhPQpWP7^Qb$p7ZB;yI< zp!CwfWgUb&i=7$>ijH`KYuwp$Ywfjf>JmG!`b=^VDiN;EN883S%4SV;X4YSf45j+T zC4QCHDZpm443pLbEZ?Qmqw?}+qxbX7eUW08>4__3a<5ybB50S~u4w^DeR4twy&?6U zD%C8iX+}1F{7*|$+q)lpB0MjN!4A9>IAr<<6$}Kt5ON1jk0-|V4`s{u8#0Y=*08{? zO7)-4_FK*%S_ej6k4rJW-cW2 z+1Qwm#M6-%px(S|6~ioXXUWpHr~`%1XBxk&ypUUKaCiJCJCyMg3+vWPn^K{_`nn&< zOw=|~QsNg$TWlj`FY2(D13)tZi3d_Su`vJ3N7&4W;{H-+S_3E1MxWu^->b3T%BMbR zU??Yh!ou8uUVV3vn+q;D?)^I;8MZFiSA{Gzl>u2OoAWYN&mSRj9xqhx)ONM- z=#dV1FqG?(km(RblW=K0t8oH$5N3JpcM2V~ev&6Ylx`$MDnRcUme<(gPIVw@T&~2bj}62YFR34sGSd8~{ll@AV`%1pypbfsY^XD8iqa)A!1b z*0r{$&2SDp;<-sc{+Mk~mi7|Au#!K>+%KagpSF^)FOcxtz65E^G zf>euxy$kQBp?9>i&F%`PP%5Vlze_R*+&$o=GQR%dFMe0J(UiIJeL{xEJD&%)$hkG# zR=O$hWkBkH2`Vw9k2|Ji#v0cDBx&;8VfHY2>E{rNP?Y&`L`qJ|+)L@~d_^Bu)`tjF z;~*$*az`w0AH~C!-M|-t+59b^I#2kXKl;Y!b-2Q6W>rmT%k)Q?=z<iw5ow@gaf8Kf1jFcuM&5zxkZ zeZo=jT(6v8n+jdb{3oXYR&&#vD(>*cIF$!R66%QJ~Zxk!xGHginA%cIn2kp0kCsz3_e5dmUv#_wUj)LyX% z1s-xy_AT%^V;yB2E#$9bN4hO4$a!J`+j_W znbW#r?bL5bcRi-oFy9hi=?mc=uioS0ZA|6;ux!BR{iu*oPTWs_;{dYzT3uO(d}9k5 zzD|oL1{Bf|O`PyhmJJQ=QNYW(3|^<^;~V#>ekLULp8Nw6rNv4fTrRpP0_ zXjcfxFuoL!mb?wucdIo2TI*=O^_)^l7o{0JLRJ2B78tVk{JppXbTo_oiQv=VWF#aK z?&OD~H)(5&d)dU={~=s(i{3*Aj?18kVLc6BdKD&o()}eU%t(t7^uOa+{reo_3PGVB z%T>7fKxqo?aW(k~RY6tY*4kh_I|QJ3e#+@BsDCCzhldo$)Rc8o;?+*Q0J`1b0pOT# z-tWs7uw0azxhrkiRhGfEb{?C42SDqy<>$G4R_())Wg$I;PgQYR1qXv~uG&f_B!gXd z4$UmL6a51xwq?A-`edJ(xr%4CeURn~71;k1FrY@;O$3N8*wLYp5Q{{W!8e-SNLO_50*Lm}dG)H;7OX-jr$T!!w{O0uXbteCPKA$_Q z)d{UaW7-cnA|RhbiE3*6)Rrtaqlzo6^aA|6D92^0?bCE5wi*B&M^HMXfL6p%#zpzu zy8-l_%)devS0B%VS&4ndB^-G4y+!1V{@j`U%u3Gzro z+UBe>J!vbJ$fm~r39qjc?ap~o8cEX0o=vCJi_D>r*BlADeIFH$DCDVSxe;|=g+2lR-9>5K~ zxfH_B_AEci=f}VELqV9dlf==PQVR#`ZWuuup0=e%cs6RmC^x<_- z&2eI)qSsHXT+<)-$!Mt!=BfQASY7=idyMgZsW4~U`DLp-8M^oFy;Tvg0+Rt+1=crhc-5vy?c z6NeZFwfm|xxRcY5v%pv~qED3%@0#Gt9X-MPQ$8vhBT`*;zn^-Xy-37XVy*GE`Xp%^ zk1A^MKqmP;go*GFS2GxwWy6EZ|5o;ki?i8(R6juXPPY54JeGU~6UtUrHwrgOz#~lx z(w5+5?Nr|Be!qfV~#PPxV1K2?m=LmH&WHOf9zsqIVKo?#)4W zzPX$5wfe}ruhzR8=D6k(>QG0^PEQI>8z718f25b4nWY{SCcuw-?QM^F9ZghHym`MK z?)t9{v$CuEh1TbF-VXW6wgm{L<1Qu{%6VQtPXIzg)2lk!0M%n?GPAj*IA#PW4le1$ zK})Cad{#O?>ugYf*UtMqSBCcfO#R;ih|OVaRkN;2WRI4cX`+@7qj7G1?FMJjp7~U+ z?LJ4BydooeV=8@nRV|Lcc_E!-2f5tem#@SZuLa=u@tRorK{cvBG%;C_+kQ}b&3j$# zN{Cb^j`8iZ()gQw6(TPbMS5Pnxb1eL@#SBghx+Y)nT1&$Zz!*p$P-RZa#PdiDGxcq zX*40B*W+x}6;%Yut3p{1*JHQK{X9~Omxp{V4yoM}Z>1);8n}JjP7V=<-3$P+Mg{MH zJYvE-j-wwCHis_WzO-pv4xV;a^AnPcclaWGxwGHu_29&7mF7E}@SLjXuMJ9HH3l6g zY&@h5#}(yK?BCWNiAu3zvi;qJj!9v-4~B-6G5!TOTSmAyNbaN%x?gX>{lYmfo5n5o zU7XB)e^*&)WF8k^AM4r~%hzLBcB>Ui$H2Rlw1$2v|GQ1@OqaZV%T;QqlQ^wFyaNPp z7;;bDHrmkiysGecSyp`Uqp4ZI1Imm9-!&V_OD=#ABK}TAuL3rOJ}o>oFf%-kO5<7% z`j}A=kRxb*H~qF4!+m!w^(4R&EvTh_eA1u zj>K)ApCC_8J<--oS;o?yeHBs^pc!daK(_OtD*bgo+tL4^?!BX$eBXV|P^94W@fE(X7*ld<}Y9oSRwDj zljptf&wX9taLr)P;BvDC%P~mud0`aR^{3VSqEH{^C?5(R-IZ8EJ0PXMBq6kS&$#0` z`!j7A@bXG-lARCd)k=E0Jq|C=d{!=xy(kBRW=e<%!WSG`hw!xIatuPYFEZ5-9N0Tr z{AnQf`2Ewky({VZ^?K2cpc`}L@435;G}MC#S8;Yg1Ctl!>WN99kz@a;Wj`gYd$CyD z5%?mw-ABlk?#BBQUAs#o5-~9m58SP_jfT>H71C&I zbchksTyRj#eluV*1$bM2n=#T7pXq;mMKz@|lV=?8&ZjTa@iz0%lBrP|?$}dq1@xR7 z$YGp%&}^3z>hcLvpZD_X$5kbrgv5@duD|9M=31j4-41@u@{zB~7s|Zno_lgea2sQ3 z&Mp`nOG)m1f&MCp@4MoAd7Y4Zt_TlA;h;UsJcK!GvPzGLMozC8p zjz=x!-JqJSey6ZJ=lWm<^72A-Rki6zG`b%K3(d04+?7fXa>prL2_Chi* zn#MK^?O9Rw@Z}4hOBCsfduQSf-z#ieQdm>fO zm9hjLKUG7PJD7EGr=Adv^IZ`I#-dxh|6u6xJ0*a{@Mp+Yy>^v^~&z z#gr8^wbdLXuBxh`DC73=R(^^NxhCXb?#jJ*zGULbh}0tm$V&EfQyQSFUT^XB=<1q= zG+ZZ7(GfC06*76BYHm$2Fo>Ic6`;OI17vbDmjZBQ+~Psx?-AK){WyB9jr8GfiJvy^ zpgwbX+Akz3x!X0q-2^LM4giPHV`v0I4W%y4u*8F>|5V>`qvRel<*mU-q{EyNm7Lj? zy5ymZlj`I%Xos=c3;-jbi-X3m;}I2~EY+IcmtK>B6(m=NEAu*o136zMHPcYhSVk0C z)RAF;cM$3BK$O>@=p+1(X{-1WPj zeXcyjQM)qMROqVY3xI<>>FTs+Veu|UnTN#T*K;2pPYLVCb!FH1nk}k&f^bNBM-nj0-l)?G0EG{Adsu zv<-hZaEmjf_pzQh4{&@48*u#A{op4f4BuCU^XV+`>*M?zrW+&IqD8`>PKHeC=;mw88I?W{kkV5A-ao>WAJx@>^))TPxm)@bq@zC zuD*Klja!eKeFQNM7&gBTsqnBC8NGXm(gU4YH7qo?GgoMQ$rG?sM%N`#_ ziheY4W(4L0o&Si2tBY(y8mg1-j+Mexw8JOwH8=t|vjk()cR0{0?+?wdefs$ISZs$< zuV)#M3nu-|`%cK^K*&ydDA_-ITgJDmo<&X0D*8VWMdzW*(DeUCa$=hQp9HE)Bin@} z{yErlEpkTOs-H%Q6~dywa{4zX$GAj3aI=!p?q+JH{D~|KQW)1^KTd?C+(Lshg~Sqv|1nW;~SxO6TG-G#7?| z4|n{WA8>9#BGoy!IFPtxLh7*1ON)dlk;TDsjujc<`c@k5*n-a>;WdQW%&(p$6#^rm zfw+b1k4A{WQizPJ5bF7h_fku&A}D-$fgC*laYW>^pXS_96`$7YE0h@keS_;aV`6TD zQrAU5PnJS@PN@^p!QEG+{Dn%TwWq0lNDsR)1+9``N~i{ZKvG^9((@cf?z*0h)IIZ0 zK=pdg7Y8!NIT2KFh1jI<-_X}tBV#dps%i>V8^y1_79Q;jwjOa`TPOn^c0P`;KEJ;< z^YhR08??RUa(o+d4~$$L5D4U1g6@{>+51Rg6JF@qKaKxZ!BggFtnByAF1aL`f2ONt zHSPpMsO8gT)=p<2jb|_(K4w2O-DM96bU)Y7clFqq%_*=)rEY!N#`=H>$&Fp=*CZ6&~gqs|!vlUAb&%hosqN z!yj-RT-xI)Rqs`LL(b+{{dXeGiR3SM>Fi@cLJQ@V&H*n#XWF_U8vPgVcPm#f8i+wzMm^y8r+|nzoR3YBugrGCLEm1&k)5{4SYK<*>O$(&^-JJm_l1UOcFCWB9T=lFSHZzJyi|-_yVhzitGny4oOM**c&Et%`kL)XN>}9weaP0OSCpbZxCSHrRE@&h3savt+_t6qCKN7bGEx#6LVYd;cLCBxz-8T1rx>qt z6P-jot#rXHHV#whJrz!W?kU+VwVa+POGTM$mD?nTbVN+5i)M97Fp{|(UjG52Vii3u5;?8>~mWKB@0&C1FqL}KNQC*ft5`@GU=37hn(v# zAx6P03P=`!H__aqyie2#%II5ucsbmro1L$V5#JbKA$e;&E9~2^HdaRo#GMJ2mz2ywy;?!$@Iq*s=7bY7(A z16$pdKCZrDnXki4o64j1qtv9Wp0k`Mu>|N!E@%-3{^v}rdz`K1M=z2y7aU3(ZYG+- zyLamwtwU|D%xZrBpfTKSnss#{m(MP4td*w%D$Js|Fu(b-)69u0nt79wKd@wOfpKQf z(I_T|9Uqbztq#^(sS@hsaY`r8^83+~Vxz}QHab`oEaai9dE|7UtWpC}L_0&tVWMBr z4NK`xE6+XT6P~%>Ii8vZ~|ERbGPLY&fT zr>SuD(o!S0hf6ODBI*T0e}ZorDbV^TkHAx~{Xl{7Aa0tKcnjV31(%WQ{amoE{n?RK zZ_ViW10g#vQC`ZLKCzLoKMLo@h{-H;XEnGRlN4=n{li6Zpey}GVW2Owe=^Ge+wFu^ z$juTxVO%%J$4Ih-sM&&V-u-}&!&*ni-*L>x(radHFU^MP{>ZCuN+#9z8fg8dn%dtA zJcn}|xE9W{7aTW&O(NX}&sjXLX^I#XbV8a`X>0;ZgvAT@W^V?|++1 zY!}jDQ{3Hax;>}hc>BId4f~ZC$IiA2L~ZcI`#l=`CO#Yz-LhpGzM{j!k8HhKv8Yo_X%9LA$)-**3%^2`EFZyCe4)UUOf+hl2*WcS=rn9; zVz_BK*gh}FcqEvU>_Hngqmh^!7rCN%&mO>@ID)6_&SW|>wGcj^-k8%%m5djDib&-U z`$pIPHgvc!k8tl?7p{R1=wy7;p*FR5JT-;78L=BjU#n}$OmVU8se&dI$0B3Z$uF`{ z>?LHH+N`PCHk_=CW^y{vt^$hjlI zho&22z10kGVcgzjE@u~%V+`AFUf}C_ zkc|^W{j$}50iHT^!(_gz%fet~z``zC7NCf;M9n$xYWarf-F_EP;8Y8>w1y%KZDV-@3$ zthZ|oXw&ziicg32JnT>S<(21!iqHLqo}FJ6C5j<`xxbD z&_lYtT@{XSV@~;)RH#rL1*t_eV`;1Tl44EFeg#aZ4Y_z*D$UusaMf$S?y(XSr75|l zaVa$Y(VhbQb(z{(fbpW$9WIuqn;E2)m|s@)X+h*MbKU##0G9b!QcmRoAvM>@j+= zIme;ifTQ1DbA2YJ&6b>;qNt4$OuJ*UG|acFGs1XOkMJJ7wX^@^$*>j z5%_c*cbGxsXFY+kk^Ms`sr@Uk%{}XC#COg)is&Mu53V@lG?(rcN)sUv^KUxT|1KGArOz!)ez9 z|Dl(oLkYL}^|8zI>tSUJHX{;Ao^3l`&8Z&3i}4=uekT0_?{G#uFpo*#PQ8(#9z$(5 zZ~1abDyuh3hU9)r$F0;tu5a}H?0ew)*UR-z-PumvU^Z0)JWnu^*-UuMy9DX5sQMT+ zuE#K@c>5!N`Pny7(aB~tyyqhVe=)4LLzXCxUs$?|mjlYB&r3#1tt`9pMrSBb>-r*S zzUq9UEG^h713w~~U6g!Akijh`fLl2-&B-d1P=RETX%m)3z^Ebgkm{#w=zWhO7Ha9# z-ul@5GU9FA7?4@^tfsOg_>77hK&A1juHYOz(h2JpVtkjZ;@y7EeRm5dZfAALd$ zK>6TKg+w=ikx+yqp44{DGg>~IZ8fM75GB>NR831mm4ktc6fBp-~ z{#$Qtf+&=KPa$4zh%Ir0+0&~)J63RCX-KS-?5gf!qPP1g8F2Wcy&P{|61*sFln#CT z%fc3Cf65aXalhvIlJm7x^Yygsq>XQh+z)J{287pg2bW{B_@ko`qOj)fHjBV#7LHXR z?xaZn8>N0)+d(%-zwBU6fy+Sl3)y(l-z#Zie5iWsdTqs`p?C%0dEw*R>R0IESyDtA<{T}k~%)jM4 z=nei?Tu|qVCVgNEiA#V6M&NaDp{T+NpZnLA>3cZbO|Ep@aM2V@UjLB#6#&Y-cJMwr zAhL;e-l0gxGSb7fzF&=1!n0W2gW*;rCcf4dW+u|gw$d%0#7`%3XdryXwL)CZIpFHw zJFdb#8zXwWL>4n*TojAk*LVxlvG$2FgRrYM8U$7T7G-XBYAeRf{RTCUzCr#6r$#N! z^)2)3Bf^F$9hg@CRuEsJHltA1Xri)6%8MBN_T=(%Sv`R2zHNZmh6~`GViBsI>R*D$T6SR72mg_TV`{E6IFx zD>zqKw7Vc~Gp+-W6!XOA@?h$a%rBqKSTv=^P(@kFKUsXaMj>qfTZp_50~Pi6>EME0 zpPT7grjrL>fiY^$5UH^S(qs7Rr9LSmUQ#v^P3E(f*B-{RxrKDHx(f_5_R)xMA(;>( zxlE0c(7vG4Fja^$&L-L$sZh1oO;woHx0s!h=c4QKHJ-jQEFx%Tgq{AM=u_xT=e^DM z*4Tt>JMI^G$%Rv^#YQDA&3{G;#(_Tdr)E${^{6VC_VV05SLr) z!r=?GVzL|plq$G8+2z!|Iw8+PO04zlK3VFy_%f4V-}5LLYimCZ6-u)BrJnx}h|ij(*Q?dDefcBy#J#?^yx#w2rOa#SCjX`uZSIcb@jNUCA!Oi7A;fp)*B?`!|g9_V2cwrI@C>UHWb<*NqWHkQ^P?Vc>_; zoVxWm(_CK$Le|Ov$&2s(**oE^pjy$KzG#L{xUW|5a%Xi|CYaX&FZ{?=tO(hq_QF%o zneNh4bPY4UwO zZ+-lDH;!!cruY^(4oVK7w;@!PF!Wal%kcqkza-Xt$kUVMtE%?>loYsZhFY(xlmtWQluhZ@3js)+bIVaDLjlB3L~;RhDV6ax#zHe z5T&rJ{_1h?oLmpOaScr02#Bya!d}3Y%E!nQ7Rz<6HoSVP>dZrJ*lBiU@n(~6gW@VQ zdupxnADAN0i)Z^UAww-T5)WS5o!3W#O{w?Qeo%wo73B<`%-&m=A7DufwSEx)#~1n- zu6j}1Ax)GA1iveu4A(#Y@qhO@`Rru)@P!`Fy$o|U0khS_^xryr6@cx`A94lGxx&cN zAlfS+#0=ezqjA^GxU%rrR5)p=<&lr?dUKUUY-1-8J^2c#5g$QnkI&-{LYf_Hd3cS{Dg9=ixHj2fkjwqW{|(bqAEA5 z>CrvVOfx)j7C+pR$L+o>P8UzADWHX|4&|Zjkp{R*e}PalX9#0-!;i&RReb8N9@T^^ zJH!Yvgpvr=fMjTHlaaBt29w~gu>Yav+?6hcE^Oy;0Clacpy&ko3jZwYYbuB*@$QVG zIo8?_qC9WxEyyZ!pAD@bJAr7>OMK*M6hQvE*F265{7qYtE_vejY_>t0W(Ax?k#(xL2^2w>4Lel3V(N z&a~71UEuV;tO=6B66Zfnjb84rT*VGH$-EPn6w~oLugS4_UuwThdKA@8xvw?0zQ$nF zY3}5-IM`AVUAMs?#Pp>a!5p{LjX4NC-l64e&cK=I~c>67_)1-s_$+#tUfXR z{x#yE^sO^8Dc^c9%GecASuiBX z!??0jyI1quf{f8Hnx=!4P>oIM&L_ zQZ^Id*9wVP1<%%FfEh3MU`t$PpQLC{TVe$5=m|CHJ>d#&lGkoh_6vb41Mf5a8ufHT?w*{|j>e3yQx~Abi4HQ0h*+A~a-c|DkQck)6n`lqad%l$-qQzsLU9ad>IZ{7KDhvoORKAs6sxHWuVnlcCb(&M5P$;snkL zSs60?wT?C5(gp&2w8+^C_Z?tRGKomtRBc3LS9y94rVbuNO^XQC}GI{F1(I?<1}v}WR3a+%p< z(oc>7H-xN*_pUCrr$4<%`ePH^F!l!h^6ScNq6)tKZat4k?`{AybgnOHeK;(3Y^h+g#6@SeL;I1VkL9a`&i*bbhjFiS83H+TLPBwIDusv81uuX zU-QUG1wqPm9*XV9@4Y8wW$98{Ov^8q+u&^ny=bznWcEaB9m7O+$2D<%pFvrj+vMXz z#ht{{n(uxi&!|0KeYvx7$OTKmM6NyQTs&eYXP^k&GgmlIxn6+(CutS|N7XE{fm0=}+p}*Fwbr9EEa0 zM>>E(KP&yU%nF>`p!9^%p`I+y1RiGha*O_V-}RKkPPJ2YskKcy)2b<5*dwapOu6?6 z=ituzN{IJ6jUyxw=Ur@2ICwg$nXlTQZ_pJZ1jyPF&esj(`u^l$|jO@0C+3E~w z%-&~7D;;0fyK7Di#fFSur@dj;gg;tcf2b4?qnjO-q(&7 zf13256=$?+4{dZTEGKdk7W1}Fm)t976qSTGQZYl6{}#QLZbn zA0GhjbY#ymGaNXK3s@U3YkgPc_4W4rM;F*y@TrFi@(5 zfyB>wG-Zu`z-luauxQvR`li3Ib6T3m;oDdv*LA8`;a76{PY`jd9Tio@1;QB`R4TSqlKP*R0oTH=X2in~;R$T%& z7K0L5{h&f`XNadSI>DEB3A!q!dnIx6HdBoW-j*;__cjAl0-!gQu{RwIC_?%wBdY~W zsEea=y!c{{nr=e;iVD*J(|r5-0#xMksj+XQM<={dD?|$)ed3&p8H)h;A2OwfJ<0=ixQkX7V=1XRqA560WK-73gwEadf-{#_@hDHDD&!K zLulQ@%2w@XDsL;E>keQJzjU4f8hR%JKh6k~n2C$SY9*BR(1W3I7LzY5KO8qbbRo(A zu$lbcY{f&^=iSfXW8x*@D{d0FOLdyS;*ydi;9ku;au26mBN=1tM=$#MlG>cT zT&x(m|FNC@|JuC%{oe{=jPn4{bP19MbSo(ua$}(M@)Ze2JspyhZpR6CPL?<_^euxM zkK#ai=`Xz47#@bnGAx)5LYqE9aj|e&pyfRC$ks0e{O~VG?~&&0x+b}5lD3~M8JtzFAH~Hg}qxx6?hGD9X{c26{$_tJM`p*ZT2FWQCi+u zH21db%b@IOAovElc`|7_|MFJn`lll6dCPF`j>p0#>;mW4duZm+^U;JE*!%Q?L zR*&zE54Y|w%};HHzZhqi>Bg!7NbN@EiV?%FQy!(zOV*!;pEmAqvCWvu1YWy4659JZ zdPp^cRA-cL_1#5!Ba#xXGYuf}5$jsN6YHx+;Lla7deetf!{U89_qyp`J>P-S!%9#3 z2*0rhw0amdeXB%X*LTEJ>*d}Wsi0TFU&dv3z&$JY z<5BEEb6_Qlaz8Ch;bMPvoBJ8LJ*hG%_9S)%Q3L-^7v?}AM3akBb!oWm_$uxo&QEvp zfhmhxW6@ozbw6bjDcMoBL$>U^Q14`V7@M5F=S?fib|6d2&9C?0v!r@sHQ@SOzJ?3d zRY`P@C+W*JL~F4%fz{v~&Qp0Rcm0E(u=iP~Ld$c{BfF@>A=_J5ydwx`n;k3?nB2~d zM@foCsOn*d;=Ww7H(QSz3|C6$B-m%%o;IZUSui9|AX z+|UCCIAa5}C&X!$A@yrALwBm?>q`8)66aBM1YujJ8TLOSI4&Z?j6!aVx)=_U&SdCiMRx*n*B7o{m7EdZ zqC6E97C^(_&b`01S_vT1y|Y7STX9OSQAYUGe`cV&_8j#W8J~7E)z_lGB&2-%DCn4? zCv5DO6Y`68&zgT2R@0>kX(WD7Euqn*-^saUBmN)5LUXY; zV+_1_hjbiJ7hTZ9Q}#a|a$PZCYe?PIaq**!-f({Y@U+E!_qp=SoB!+#-;ccfuO&(O<{)O?*93(GB4G zsGF<$s4!kOqB{gu>gP)GE4MZ}l4u~>pGyVHP`ie2cpdzGreNuw8{@2~6}SH`_!e2U z&7B!qeEU}cB?vFdNWy}j2%T3KLPZ;;RIMd=IApC8iz9ztc`B%vgNb5y6p!=5jnv$2q@K;fH*pDsJQ#dkG z0!_V~hSE+SmF%^uDw<9`2A<(Ig!VUN?o)^SFK+{-7_f=b_lwWsM}BJ;@4l%XsEM4B ztH@2+dN*e`o-hGTG2&oc%D5X4-}}oJJzp2=}yFCIPUHf9rH3^|03S*0zqQXDkP*GDp{M z%J^6p-M&g95P;LMsR8_@O8CJUU9Y`Qb5>ep_U@kbx=p*?tykU%CiZVZ1zs*UMemVS z!d@8BS-Fn)q-m`U1kj_rtpPyC`42KzW}+^8dpM&4Mu3D6hFy-bx!!~i2(O+unyQMB zS|Jnq7pSrBbHW1i6se5H)pG-l^T`u_TtKY6v%2WZxzSAT=k5aUrnNp_gVlR=JcsqI z$PP4b@|y~&X~9Z&*YT!(NIJmapeY@?hHno29bo1sd}!9oHu!P>t#P5*&ib#9rd#OU+)CHY6LUgg5rI^Xx7Xiy%$MBD zX^*CUuJ6tGm9ysDn+ESTy|^~evf|&>`H05&nXV8oLUd`Z<^DPWw0sDUu6+Zr8P)lfBfMKgBK?$!j~D)d(}MFp$6R zA8AA1692^*3u)NwV~m@-O9<|5tNWbRYQ5)v%}gl$`})IngcE2bQnG=GL6)RC_gC7# zM6v(===8_w1ju9pJy7vgM2xE+_z8y+Z8xICYrv)cDRzm=tKmZopmH!FYPDv_|_SrAwW4AJ>M4av~ zHc(_Ji6!*E6}-GB;Qlm3f^wg7kAF3|iV@S88L!U16~L1lgAkmZb3O?;sYR9WDw7Qt zbIUa@k6e|}l(^@+9tM>`%+9eN%@cVE%7u6R7@4-_o=5C{2bsU0UZTPk*2_yrd;~GR zJgdf3@0sFrG0^D#NR(1}zhWlybt=!R<~`iPDXe@l#)kf>PXsi$m5%~1C5V9SJq+Bd zQuWfD_?WK19+e{GlUPW@p=iSO=r$FnGyfHYbAn{g@Fd{Jr+f66|52bEzuu~3C=v*H$c zn%ofnP;q0iUG%fBO~C&dR5Y?ez~gP^v6B70?-Uk0#M_bvQjd10`+caUB?7gjZ@^DsA;aXUDOw=jRN9!0PcidWHOggzvg05v)1_~PRzYjq|w z(6YccaNf#Xc$QRvpJ)HF!kCC=z2t^PJi$x9R(S3nScQHc(m~~*AGI)cB%V)uqykR! zv=I{{IYGWb2*7CwKX59&%j`t{m3B19Re(29?=V25@_;7OxzKt^Uu<3PzV@^EoZFDB zr^i6^8<<#??oD7Kxc1&@yPkH{M6#+4 zXt5|~0W0Le?n;ijEYX+{LtaDRye}!8QHx65HtwFjQ5etZJQc6}NMC7xQnIB)bWamc zhDG+$J17`fcFko*p|}fj>$m)8IDEdc>Hd6lSoq+3%$GisWtWJ`OS) zaaU&oe)HoGT~JbWM&j-1!?wM~@)2)tt_s(V>S@h3x4Pq&*z zuBUbhY*+4c-+l_RPYo2#6k8-Yh3ahUC5bEOTuz zVQrsl{^MHEOgv(s13ZILJ~x6%b@$wHAIFL! z78t3yP#jz~tivNSJzaUUcrL=fHWh%%WvACZl1zP^VgH2qj^zA}1iR$2Dexml@QMkv zmUBRP=m)&zoi5FpK2uDc2*R9t^QY~G1<5hGn|J5~ns7s8VMdnI`}0o9=Eb{}RxNON z8T%+9xL;kJ;CcQOu7!6BMJXx8<}5$@QHZXOQWo}gJk6D(NU%<0VBMJO4B#4pcmIbo z&WeXA7yfrGHwwp@J&?Jf)qtKaq)^(Oxk1aV0P#93?`;-FH6MmqK8F+hU=)oGIWtso zBJCm2qx?h~Y-#yn{#|8dL2z7LG|;wFklNq(Dlb6IT4Z_r-qPj~_$|!{rUK<6jm>7&B@^)Ro0h{X zk-m(h>{&8Ko|!q~$2uoc?S#^hLfDdYY(fYmT)p%BgPE*sV3Ew*`yRK&*7uLJf$o=@ z0U`u0Nf^g7uKc)I2abLU?6vNikEM)tU%VGFJ7pu+EsEz5y{SnQZzsf^n-bj!+4%#Q zDx|q(h?L*FD#xR$mXATYLz&{+?cq5qlX1F?l^wJ@h#PGe(M0R#xDNd!f^I|m?CNvR z+=fco0k%|mON4tLg&X^(;NBWd74BrGmnV8x1y6&FNwpet*-)MY8%ya+UA@JXW%RCk zP94MN`Hg=EBOL|d><}b^e{4*vl9e~*icFMS-GAy3^~xljMSJCw&_@{n?KUCnJ4Jl* zFS6Q;y0{1Uj7o<0w3Hkf$aK(N{S5g&QRJ{fd{C2c*}uF@Z;wAwzwOn*B~-p9WRFG zeLL@oui5gfAe3Pd4<~`W6cWC~@1itTBtdPEk4MEa;2)$i(P;+1nNxF^96 z1A|}9e~C9Los$}R9zZ&JvNbE@zxfjA1vjmXM$OGpe!5Dji2>Dlyku?kJZVM4&P@Gku(OMj|E@FfG zRYI;>#U_U(;!8zSNkfAKWW@)La}%L#K*7)y z&33%#+>3R;FT1c9$i#fJV{!1stutHxxN#jrF=avZzaSiNA3|~Y!@?;}s+bTMxB_N# z`L3rrgU_(EZ*i+#(8*-&z+{@nn0*pv^3hpW3sGA|&Qk)AF}RvHCc)G^cDn0CF?QW; zZ-O&RiOb#zRAYSm>Vq{F1|9YzdNKKb*r%a17a|w+$JV~RNCv=vvf9n0Uj}=z{D5Xc zqjHOEginM=19X#5Y9U$1l#)CYsaUbB&WV@=bEz|JmEOfA)-a@HIMlbrcw9|bSZfIG zupbz>Y9=O}O08TT_lpeiAMc!*>47j`%D^=URag}yJ8aBEiStBPsLA=Utr@df3-g<< zl_=!}Jx|1yVD|pK+H);~qInl@LhNmUg?uG37s}l0qZ&Tn27UXy3IpyX>q^Q~F6Ppr z*Ur=>0N6U?1?eb?@kE#abXXXP`coYuu=)qB-(T@x@7tm~9apGH)uX``JSr;Bb9CgR zv;CNL2G+aCSC{^Uxbve!#3cww31JOJewh^tVw4*(>U=D1YgE1@o8#QlAcKSHMwq zEFg`_?8%BMnp2xNIJa(`)Vh`t=gRW%<+-Wn@Ia`Xja=#F_e*gkC9Gqp6G>JnuR>cv z`=(L9g4QYgMuFKPt)a27$-1XqV`9+x3~-e%Z@_jihD`v_mSlUvCDSlpgoYjao>Ij= z;j50odv1PB$}C`Xm=7`Y7sL+_$zLS6KAXPUG%1aNNj-0DjD_4$rrn>Eh7JnF+P+8*A2fD&C74c`@|Euy6p(LX zrTGbor@b8#s5?*=-epWDEhBjXAn!;_Mv6oKoS!g_HwGgUGw!7K#^WvY zOKsfV7dRx;6>|B=`2MbDZ*9))&IRYspmf|N6W?>j!Z)*EbGL^dFCF-BM^VvG-yUPH z%|ie&t_IO4c{NCP5IFX7q}0Vr$K9Rtr(5n&6gyR1>x$#;M-+OZQ!}D?-c{fXHx_4M z&=ifQw7L9I+wjq<7cfBN&EI&YF&lnk;LI$ZMMv#MTMbz6a><4sUxemRXl3Pg4UF&ZvLHvRhrcE=_NGUtWpn>nl3r^*P$m z54_nUoMYj#AZ0QN)V)r0<3T?(`VYAc_k>>qs-{tszip{2laZ7TW2}8Pm>7D?HV>3X z5{tiZ#d;-mU} zF-bA3E_bANe;3aax&{M5siPy2)0+|rH+7RK!~`YJJN@fYeIo#B%Edi1!Vd1WW;U&-1 zvu8>v4W^`{i&q2fo<}`P%LFC11A8eWpJ_ILakhwIu2)hWHi}{g z$z6Em&NTJ7`?>e5DR;YF{HhVdxjb-{!miVxw7dMa3Fd2F-xa@?=VjQ{hLWWs8jmu2 z0CK}BWABI<3WB|`=G6<(qc8q~demnlm;T}_ygOo)mU+)gv>P3}%+$fUt;AE^z4y}k z8g~B{-~2OWyA;jAgV?99UX-fnS;c>3PBR%P<3ZBxd#DS*&2We>*b2Rd9anzC(G6&W zHlCUEVfz9FD7jD+S6zzqFp69fk6Yy>EbC87DbS1d;4I#iHYXGtHE40Y?tM#1_murD z7{xgBkq=(|zd{8J0>npi^Q+O_A*9l@zp@6cU7E^Rmc)M=ugvHDs%`DwBMU7`2ac>) zsH;LxJbwaF?`7ZzWS8&Zzr<%r6@L1a_NDR5#BO>|y9o5{VWqK>iXqika+*ey=jI!vYhRt9!e)&lewR0FT6a#^nO7_*ED}XeVf@ z3M+00P79zZqGyUzA*D*!AQNp?I~oa=YCM05_#U(wU4%RSbL`*cO#CDT^dq9S-o~>C zKT^DPdHh^=S4;jo*pw*7Ola?2Zj_3{6tj+0>iDwP<#DCnJNeo1sC9JHgKFpFX|>8l zI#CE~z`0`7FBxDqxlvb-9xrJQE76W05UFZh&SH}xe=U`lYn7Nc11uhTU=N?iK;F#s zs#8qSC*bd~y?>gjTp%o`bM5xkx5;Y#k2E*F*!4nyEn%6{V%x%_5Gx#K_@gRk6g$nU z@6CGeTDljl_GobgUyb|HYg=1mjo`?lL!i+`e)uI(v1_b~+~@%X+TP z>?IPf517LK>mxk=?Gc+~yVA1)qdS&_tAI83&qHayvV|?(Yf%akhn&rAG-V)>_isAM zQoVPeR4*;0Y71qr*FIN^)1}r_HDb2CeS3kz+Zx)v94nwkcv_)fMIbZF)NRj!+%%3& z6^{8BiXZ%(5b-_z+nxo41sIahBcP>nm3j>9&drZ&ug!z&zfxvgzX^RK6wkM7)4J8G z|FSbM@_(vd{P$4lzYm!HfB19CzvKU|b7@D=!kud2fA^Jg)GG8`8+b0ZCM%|H$F97T zu+{(IP^Bz`_xDQI{c8EoS@d7fJ+_|$Nx!Rqjw6+{6S#~7%W-XJx5q$ls) z42V(HMnXvNrWmDYAWM89-os-sve3*qDdAC%atmd1JiFUhs?0x7P1*>Ik(VKtp_@?0 z%kO_dhYwL|e?d@Sg%{*xkzxPAcJk(MPYO<>^opHN9}dv}USLwbOLir_E43(GE}~Df zT8}V>JJKTfRn2X+37@j|-jTc2V()lGm(T4!|Alxoj+2id90He>JsfHgk;bZS0R8GA{Pr3RQFc z{!03c+yXgs%c?r@&Mb1?=prLmL+#!Jf+nWo;B_-XHEUz{CCACc!`h1ewG6}12yQoI zXT|2t^SEyGq5GfA3)l!T0j{DKwwE>2?GA96%^gb2ue|s+AdZ!7bvD|g* z$TVi=#}bguObY@Q!9x9C(0J<1p!`30iT^!-X$X5YQ3$t`j!RJu_Vo3ii;$pSZ|{44 zv2!!`tu}A|>PB_8>)seQa-bQgR$J}<1s%UI(SYj!*O5d=l>Oi@h#TPG1Y;I6KKK?` zSsTv0rsQOZr!WoRWmcI9Whm$vzW4-LW6wvTAjjqf#kN~!6aMO5TFpeMh`w8BW_nGK-Rb!FZ{uOXaSgf9-s)( z#8mUa3{S0peD=V$G#x2cGV!=nU1~o53z`9LYrk9MYN$XWa%ogm@U#!6sv9ff%IxcX$|RQ*$gj|k5|*b?EZZVc z8)haVcK(lZS}561Om~4!+qM1HzaVR)%Qy?d#$V7S;$u*V-V*(2l zH+3W^9RwPSSI}n^Bfx8rbQ#{5k4-uwSwMC|j&B*|@uIJEa5{sT6A07$QhDcMkI+u9 ztkwdSl5CVd|LiSs4T}2UVP4SClEEe!-fQQR_You)Hpc$jX}9*YF{vDR^9XC>oWopQkvA6pX%-p|eBMLQzQvkLF z(H`^x`Z7%qdgJ^5@Ou99n*Qr`%>xo-xvwwz2}5`mC>0?GyFEQ3A=<|1h%ai#+5;NV zWV;RJGv5QKy6GIW21(4APdX<9mhdlclf)IcXE2SW&4Oj0o>|c=^50z_>3&#z|AAZ+ z1H|RlMZjW8aGD@%B=}<5!JLGd2e_r8Lml@JA)y9giBNLKGkpqv9WPfW>~i=qngrI` zKrdik(#)hIwKZ#gxVD-8gll!863+A&lv;sV{%DKSx7I7I?jO5%SMOc5YuBzYC*vpcz!@zyO*Mds z2moGy|A3PPz*jZU`91*X>H@+508juVMC<@5h(H!Vk%;4OtVSdRKz`GS0U*j5Ao-K$ zHh4cxU}?YK`QuLff%q>DYN`*Azc5MdDeK7&AgAx-;p^e!O26_~!Eo|>kP>TgBpFWY*0J|I2=0B-KS z-iGQoxy>ysxyk1NDu5WE21J1?wstq$r}_90LV`DWpexjv!3F6 ze_+wyJjRA9AdeE*Bs8|R-i`o3cPc-(znvpk4&AAYm)-51-9h{g#EN!Kw)P;N2C=A{ zn+J&LDL^cA@4uAe_+PNC?Y+Otw6%5o8~>pT_$K(_Th2b74{U>e|MCCi#qEI~SYN*r zFLp~(*!YSX~Y!|os7*u49ZE|>hElN+6(AF-0$ORbSlGXIkOL) zR8Hx?dA$7$Pi5u@=|As#tDWiy@<3SKd_eTOe2BQ6?eA}awg6G`bu!if>EOE%a|a*u zQ+a;NVDE5C6~v$n5PJuAlRssHfYx>Mw9M0YBRwDdwsnvnlIHM0{ge*YGvu?6ztLas z4*Gf9;VRzWS&21Ik814CnwVz%76q@B{NbzyojroWE~ZZ-amR z@k9-<1-t=AzyT2Zlk(RaX1|}fgI6QqA+Q6ugFHTezjyQZQwP8wq~rgl{_(CDaR2vH z|KD%80;}K`+(_6-6iMVsbik_;m?cT%NUr_$9yk48a+0Z%-6GQisXiQ>nTCi;jQN>4fQI{*>b0Us{1J`BzQ+b1DCL;tbe>T-yK435kZ3LpmT$ zkS<6)q#EFcAR%8My^yb`_)nSDe%FrU-(NHQU8Am`4$gn^TrRl0aT)g~|6dx|f=^Gs zf7${*z7B!DpzQ%F9-cwo&W=vL+{&OgcHq`@w-XoTmb`pL7649t`za3q?6ds#*+eYe z|Dbs_0>HHx&@Y_2Rqj(R0I=l)fSOeRV8r}`#&;yJC3%~}faVNkHlsN!^1CN17AQl|`5`h#T9moPc z0!2U>Pz}@rO+Y))1M~wUz!Yc~E5H_j0x-Za5fKqN5iJoT5gQQ~kpPhhktC5Eks^^Q zkq(g|kpyQ7KUkQ4>)Y(E!l|(E`y1(H_w+VhAw} zF%vN-@kL?@VmV@EVr^n$VrybYVlU!{#7~Hy6TczOBrYVbB5oq?As!)~C*C4HfB+C` z2n&Pk%tN*zM;RFA3NP!&_PQB70rQ&Us(QeUGspmwHyME!>PGj%8R0`(Ei zSsGy)6`DIVel*W%@@N`qCTR9)X=yLgD$!cf`qDn9&8Kalou$RnG17_CY0}x#A?Q-+ zs^|vke$Z3W3(zam+t5FxPogiU@1x&7LwV-n8I?1(XF|`Uo~b!AcIJSAk>Lu19)lah zGlowLT@34I$b%ga78#kLOn=9K(wraK+b`o}Bb_4bR_B8f3_H7O(4tWkcju?(Ij>+@H z=Y`K3o)12sb-wrf0p|rybxu#tx124U+ZR|aC|z*5@cKf-g$*u7E=4Y9uGd^hu1#)c zZe{KV+;6yBxluglc{F(Zcrtjt^BnUE@|y65@s{w;@KN!}@!jWp&DYF_;^*Sm<%jbZ z@J|X*3djjK2_y@22pnD%yl8eY>SFc9bwO4^O~HqP1%lH;v_eoJFQH7K;Y(zfZ*QFqZy(J?U^F=a7-u>!FLaTakM@i6gf z@gEZW5|$D#B|0Psmt`(LxSV}?>dM(GT35oZ)Lz+>6qdA;OqCp#qLosULP&j)LQ9KC zJ4mNVkIOL1=*mRNG|S*+ugZGM7Rzp2<-dCO>bt9Ba!hjia?j*CuaRE6c@1%`{@T%X z+3UX7%dYRri^;pnf0AEU5L9qb$W~ao!F$8@M#hbKMQ+8riXRl`p*&DqXeM+~iBHL1 zDOYJt`I54Wa-s5%o0o5T->kfOsB&E;M5XZ-V!8#PEZv^8F8jA?Re+G*x%qO`7Rg=n>E(`XxMr)V$g2R69-ybG_eyU@Ur^smzs`WnK;IzMVAb%7A>6RTh|vgU^vMWgeA77Ic*;b`#LuMJ zl-~4?X};;9*)6jKvw3p~^APhM3w8@JZ#dj=NN`xY zf8&0_{dLD1j;|ayoS;rgPTS5Z&MD4l7Y&zmmqS-Q*F4vg2WAgS+$h}cy4AR!b$4}d z^|;^>=rQ0a;u+~V?{(cP(F^6R?VamGTb9()R{384o{1yFE{jmY20p)=V zf$o95LBc^#gVr9ZJX)IO{^+%s}}WMI@}bbRd2*!;Nt`1-_y3G}4@@<@@8tmitpyl6^aB7D-5+zW z-yB4L%ojmS^k+UL{}cXWes=l*j3IzLB>*sa1OPgX0FVY|0T9!G_tVhZSP3AOlKMM? zl+)yMUrO>fAfoXGLlf-D$v!6lkb`q*y!Xk;@%xh#d^Q-fPXIup*B^7fQ*N>|0PunB z6!ngQ@SoC2BfvmGQcFz*A>svy8HgYZL?^8P7brU^7?^ zsaVdjvat(X6coB7EG;8@Rqon#m0PN6>Kd9_M#d(lW?)EWXYX*|(aG7x$Jft4ATa1* z_>+jpsOYCL39nu!CMCalo06H8os*mQG5=Fpc|~Pa^_QC3=9bpB_Kwc3?!lqqkP18x0A>gS z2`SGNva`wt>qkffgv;T?*t(xhLA!a5K=NyFp*J!(H0pQ1r^2bMD=H)`JL!a6aC-m1T2IIEQ5rE zgdF^ThMJQ4%>Ou@%zz6!r6-dB4TK0ZCI|xn1@PvXF+#xqo4HuMBcM735#doHDwr@C zh$||))yC<=8y;y$(aCRSeMi03-J9mX)5B)fRbqh+l0PVpGj(5Zwz8S@y6@H&Rt5C(0y-XFwMPB|sB zFS4plAAz?xJT2z-}x>(;(-0GBr-=VgxvRFz&dK8X}`bs z%GS=GRQAP~JzbfetQd&8+>31Cx8Ly^4-@f!J-;{&Q&!W`lx8we)l^nx(kJ?tIn*QY z6@iLnGX7b;!9x7R@q%EqqPR1G!kKqe#`&V^B4O8-c;bSUMzE1N+AH_;}yc0-@c)ZRu#5$%x6iCr(R`_E*~j z$Zvlrpv!Df97WD&JzR^dD3}Rug_V1>&PeDC-3$6r{G-B-w*E{jwL*f{GH>Q1&3j6a zuU6r=HH)hEn9fNXXO)lRWilI#EAfSJ0qA!2T;==rryniWA$qS2*x}7r9u8!$l0s;V zB+szXZ8P81WS%#jL+Qt}z^qh)?2aYj1~t_+PaR{-E7Ss)M$w0&QUpD-yVK5(kUDU%0S-B`mqSYgzj=N9NxUn^PSf1=y^AX?X&l4Teuu%DX zhH77IBLN|r70qYYZF~1c_6|+}-WuzgT0~mUFrFu~o(toL&m^2D45ia8U}I6MLAR(y ztZXc%KR(}V-vI7M+LJ~q-BmU;c}#NQ!^5b!ddG#5C>w@Rg@7fIl_B37WTRF+Jy9gl z3lZ8^s6LcmxXi44Uk*5{^eO)b>`hAOamDS-7+k3y_EKaHG*SRt>5QUoH-cY^yvfn3 zY)ijPR~~EVd5=7tJGF;AY~eBUFZFpC2i9)UEWE;lD^UEvkCPr&8MF6&LD?FuRTpXU z$ia3X@sY4H2{FZ|Wq?6_nvZ{va6(2wE7n@Rg!1C9 zr5O-+kB%h;2=mpJ)+(`Oyd|=I!Ft9x@vbuYN0J-s`|554YrF!6Ds8*Qpxoe{*fKL>&#D)my>Ioe$2~* za4)PaS5p=mU-{((SRiAEVUSbJ)wqY-bA%EOT;IYTW_NDSE`S+jaRP+BwYCWK?^#4f zhmr=?X<>Cg>b&e&v>*{Ry`*8jmM{>ytW=}eLvHBPvY@@)E)N@8!s656j28J)Lrq4$vh}oG8QMEXp9|*zjJk?}hh-3vmNF`ppyb%R8CVWR$ zz*^ELeHNyn458=ygdRJ%y7tH?GR&CIaMSenKKk5ALm{m7nLB)#_`=Vitf~`0oRAhw zs^@>FY|bOy118oNm|2712<&Tt(be-62JW_-_2jNUq%EvYPNt(|XrW5F2X-2TyPyGh z0{o9*sh3Uw(#ykns8%FPks?uGw@1X~PL;rDQ6AjmQ7@OoE?zdDu~?DbTzDv~VSrC`4BiTGeiSW(N} z@c3sHC0L;bWQjA2B6mOXKJw{g(%byTWZ7!jS}8pml!jyiU%Q2cxp(#jWfD*2q8smO z-idvh>p|=xyeRXS1n z66u$&?8R?PIpkJjcRL!F5h~eWeO0C3&7r>ji>JnOR^#$BpQx`a5gD`CJtE?# z!cF|Au8rxHS@l6lK!35iYkK~$S?nf~!&g(0BK~7ycm5sCi_c}24*eo9ppR;{TrB2V#T^t|$9Q-=D z^GWU=`z=P1_X+!t${%?UbY2gIrA+K#5h+kDp--b`v;mH!L#>z{X_wV&WSrDAJGPtG zxgM5l7RXw^Ly z)gyJDRF(8?G!-ScS z>4rZ@i8%hSBDT=kN%H7>o1IQvucQp8(s)1;)knnCrDo(wirM?aw=18sznNm zV9)(KXS1I_Gz?$;TI*Z*? zu$a)w&VQt{CHPjz!9fV<5fSzfZZb-+WXjy}`FSzs2#ImRe^lfO9acQ^6@4x|vv#oZ z2Fvn<%P+;uFp-_>8$Nqlk6xI3_LrNV4`Ut~2z9FiTl;BjIwQ8Eg+PYoTa zP#+wxfq$7{3WNbzC4CcU%7|vcHYk&yUzqa|SRby4{|M&^UX&nL^~>3ODArXr@-sx0#B9=UaBVq}h`ZqK z_FYd;Wvb0}q=E~yx;jq!_&Al}bmH)ZWv`1hNsM;(>Udj#2u)ZX8~K-}4a-{Z)#a)> z95PqOFE(LTXSxojR|DkUeSxcW3!DL{e#}e3E*u2H(#h-PrBgcKmWxGDY zYUvDT&p&&lL9=Zt-8tRDCw1*(L0J~r%*&rL%nMxQ9vw(}!r%!&>Dw3Ez&hk!@m}6I zWtFMAUg*A&tJKZbqX#`<#xMJ{4j&F0xT-m_34WpySuB}M#_VCw9bb}Zc)56-U)jfG z?PfpV`!!)kouOmbQM~&_E`2v=I23h>C zjlmxRZ%w)r>R!vzQL=4lQKyJ|O~}Jffb8y9D+#ePTlkPQ6ICX||7Kf!gPM@w4Bp6M1yFUdxjq<_#gthACEwtktEMBIfQu z#^unXvdE=^L2iLblubYCf?b_E41NZY7fntEKdL&6YrWa+AO9* z?@>B=y*k=T1EsJxx%sQ%ogs&^0@oH_MR(_zX#Ik*i0oBvd*Rj1V$E9bfdT>tzSYRZ z!0373!8o2fvt9xdfX`^SKp2A27ByUiUr(~VKXjvLq-c=hN8yh}DjUi~ZK1nt=FIn~ zK6CHGc+5|LCiN9GWNtc;V_vXT;+(VT)iG0u!Jss<%2Hb_>&V?;N$?p)?nZPcH=UT? z#FUlItfecqq~2>(VMg|7D{sDUh+U+ofex-dtSEw|>uaygd*X+HOj~O!xaDkBtxggo zE~-6~YJVuQGaXg?LY?fZS`EndYvO^L#T-FYD2`l}Zg19Dks z7`hWrh|>ZvC6jltX9#`iXXdf*KQ%mEa4n+yHK+geX+RInRR(FRtlI%yNkoj;RO(qI zD-MFL#{3)tM=cBr85T-)0=Qtc=G0PJbkL&T&YQVqzP?g;0*n)UzKNzO3-Id>ysWJ1 z{E&<%PhNoS@EjWu+SA0a7RR?OHX3NqyU{krRV?BOP3)H6nA=kImU}$YrAreLV&l;$ zFC=e$d<%>TF`rGK!FoBPqH)AS2Z*!IvIRL-ff+JeLO<8cle;5#wx7~?#;`@by`YCH z*EzBiu5ka<;y?VL_Qqkz$c}5#%{A!!dQtBest-v3t4_ zO@`~>rnH#CE@ca8A0}qQlx4G|)_O&8@MVUh#xnQaZ5l*?%`O;1-oU;>?PFN$@$Y|1 zuy=ic-)zv1T`1fLkebYC$PsI-F&_*J>*bL*Wn(k*GF+D6acw{7V*`y*yB>y0S(;~F zYl>6sYh_PNbxIDEO0}*`=qj2~cG5?OcQuw0r40_Y6mCBVv+%3;K5KgtnWQX?_b9~V z37F*g3scVDHG4-GG9rwmh&HgI9k-_(ahYFC$8 z*gECl4@B5HKOLvj%cfSk9#mBks%RLK%kLcT{c;(*oxEA!_ZDhbAVGEqZk`Uqrz4#? z=AsEibwiX$RCRRm3UWm32jon$en?hV(hP)|_Klji3X8mKXpY(PTGQMkD7ymU*k&D8 zvHQG%X@iUSCz5*Hda5Da7U32WQe@OA8(aU&a|U?IA{twcoQA?`oolq;nsrJjh+NJmo!e%8r;lw z6v%W=Cj*V;a=m<(6IZF;ql<&K=F1}y2RlQLBa;?(FW&6+l)TCN;RJ9{>SJC*mOX!O zw*Z^!z~rOyqRw>>tiDnnPGco#*nO>gy!vJv+PW)JDtu*Yvi^7>IRfg|H~xGIhLu1e z7b=nMh^XAf;69qU_m)#SUxt!aCeo-suF7XmE?+UBq7?19$YGDTN$BJ%FKN>YPh-VK zpka#`SB@^|!pOm%UXrknd$N1!LL|IN<|9)Ll@7vXttC;38)H_M zvU}CJF;iIy@98YH!UK{e$8w+lFxxI5pi`MxNYbc*;!F{l~(;wzV0lj;~oo3i-Z zeox^a-Df*rbWmix33|DF0`y4tCCP0L)E3ZA8I-Lb&K#Om7FP!#Z-4qH|8yX@%`!)q zjM2shIijj9Q(^tv+i6^tcT_OW-}>K9ZtzEmtt7sGM>^f&P7y$I8aEt242&|zb}hjG zLh~%7Hi2U|`YOYwjo_Ck`9k;&`&IR6&zlwBIGFoL+CK)qFdH}lN~0QS9WnRuX+L3+ zp}b#XjmPdKZ2lr>8^XhV0(f3@FnDHv5+9jXnpS=Qcq$ZNu49?e>YCVav=rtIKGQd6 zuz?p#I4UXB2{vS&o=Bto5Rs8Ab%n)l46DfivmX*7h&c_jwytB9T_| zu5}@u;li@nS6(Q~HSWT$W08woIHrPmiRJ=m+y${A1%?7_aLZ`_w*suAUd*O^k2Xa! z`P8-h*63WFXC=JtYV&0uc^h`{pA^~P8rW)--W?R8S%SqnP@-+y_RCNuwN-v$Un(T( za+g@mSy3M$A&u{ntOZ2jsH?3o21Jua=nb6E7kqjpS6Hn_SP4$k4WC*Rn7MSPGOc5~ zx6Yki#Qa&A`Wk(*@X%)(|7Q!y@hNWKI+omS_&*HVaGgCID>2Hw68|L!Re&qOZYLIH z{7OcRb);NvQxQ9jVh7Ea`w7%n?d-z*Jz(FB4xnoECGsEkAOR) z@@}FfV(PW$CBm;)RL`MaZH2O0oZYzteYt_>{pNz@#5x%d2aNtc)IJ>pn33>?24|7B;0DQm|}qB&>H)z$T&ddu}dvbiQ0 z8XOGYT^U@93vDyCIj<-;R*48Z0V-tr+B_o1k}e5i=&HRBC)?i!@x>(^y$bSCq14}G zSh(}*yG4N6#*I)5!ngGD-L`M)nBrli3+w`1b}*F689AQd%z#Dc)Q9Z_A3t@V8#B{$ zDj@3I^eooOHJ&fU>wHRX;lS*MmXsVgWV};s9^-1z=qj}1TH>THQY(s*5=WTPo z1b4kCKTq=_`Xh8gU#cx+1!3#_$^6bn$`kDyMbjQMrJuX&y)T&>-k{)T$HibB7WAfk zFnm>G1ZtcF*3AQ*zOeBnZNKH}nrYRLxb#Ymzhl_uwdWhMH8dtS!?Y#4A}7udCX0T8 z?FdZ~X1r^UW)x?4Gfa5CvC3eqkl#e0i76*Qb-XhEweJ=e6=4=ysTZLj*I*hiokEw5 z>ey4^+?l4A?yw~Y23bEfh_@N}8Nbtls&0dU(S(CXG#4F?;oPL(x0~PDitc`=*|V5d z^;^#8ryZn-5x;$vAx)$gX-_{fwwTw{M+3i!EkWO0>@&fFE9(mf+Sq}v^2)$b6=#Q4 zVrAnXjv&3)#0=V^E<2NOnKFE_B0u4qBKKgZ^w;#3_?XG4Dcd1;-@NC>6~Lv66qva7 zn>~9HIrRq}&!6AE_!T;RELiY_Wd`OjQ}p%Ry>*k7lAWybr1aG>?cL43=Wsr7nU)2s z%(V^1mni?KriZO30JEF49}0yMeW*jQn^p0^*l|tojZTI2((#)&U9jLIA4AM11SD5; zl_o{G25vXEn2g+wvnq7Pg!90Tcg6`atFN#_1S*6Vs#`Bi3MVi$)i!p8lLr@!uXM)P zxzE*JZ6D^6>fLJyIr_ev+BtJviwE#;8pxcH<%q^zY8=Rjd);WU(673+bj!h7V+tir)Sh*T6k z)>;CA^P|4@h5P9`w#>34(X>U#n1c{9S+|=Vst$J2FFTRacb4d`BvC~?aw}R%FT2G> zcOVfvsrL0)HgqVZP&D>%^E{nNq?%(2gwDe{ zJmR=29a~O-_%u1}wJaRfP(87GZe6go*60afIJ)vBx;=W=J|^c3O6S9wXY*3_MBgG{ z^+%h>;#Tmnq(kJ(s!OveKaXc{RiVL_vyNMdOa?mtaz(a&cM+2loP`ra7nkJfxb2Gi z>Z%1g!0Hp!-T3%u4oQeb2846)5xM!cKDjOoqL^y0ddc<{VRUDXg~m_r7q02&G{3)I z;GA?t$+j5l=d*P=r8qVr|FeD@w7Z>^XO1<)pz)vJmloRgDuS~6{RAVS39P&B^Fg`t z^l16+ZLMbyF(r!sJDr%TA6Z7ey@fWKUyD@aftz77yc|E_z_oABUpY9Z<%!99SDuQp zeIo8p3UT#$-9!{QNizPsMHX6xr6LO}5B)|*wALbd*jWSo59{S@T!eb%n*SRT%so#L^(!;2Lbx+&9=RxLrF zW;E)ltdG-}nIB#eml4sTUnoN!78bc4ZBESyy3LHq&^Z|yX`9ZJ zVBB{DumWxzmh16F2p7cTG&wLPn@@Wc+6SX5bcY)iJJ;GoW#3BbHnn139kYFr<2aNl*8i;Yi<=BD68L1|AWK{hPH=0Rgq%XEOtG?p%GwzV`B!@Y6 z`_Aq?QG0i4;V_Xc#Ip+&SUh?SV;RDw(U7{p)n+w*x8AsPc+M?n`mBemS$+9k%WL{f z_ir|yrDLVxidW>t#uBJh(7Ow&;I7$69P<#I8L_DCu|4D3c8-0e5CgsMFy%R5+3Ju0 zm4nxqzJFv_;Yd?@Rng@gk|Wgg1Q4+~+^(9Qd~MsK3wo}2J6X@@Kw_aJUfLrD zN?(5!&DYX2ri+$Mcl$V1_=u84{>`XTF`3%RcMi_BP?ytn8609Uo(em(a64iV#vGX3 z2qW`z_CPKOj^Zwt2@L!2mQ3&lM<=MMiiWNZ)wA%}3lS5ZNwk*Yh&N8y#=oixX%$+9H1sUC>6@LMlRxsUS%Q_C zBuCLdm)U{MBX?+h&{hkTD>0h*@S^Fjn9^JujV1dA``J7F&BoWC1e)>hCs~jD(t{-- z${n|l(LM(Ff=Y?Wd4d*}w{dis@qx0oL zZN08N!s@p)Y+`C)M51W#2wjvt2dUxdC>hU_&zbWFQSc$s?Qi! z5-x|fV_hAN@4z+jDRAX%oO3WVii^3DzN#g6dPDDgz7t^NEZuM?`O0!9h3E5$c+T_h z;K|T9=zQKG7fm*n587;fqu$zwVUVi>Ic1uDUv}p>3+|@8uh1v5HuvtwQGMK8UGepb zxm}?j*-7s?%8#wZg5`0Q*YihepI}zJkKBfecJgqHJz%J~1NHzJaLr#Mu)Hahc4%$% zV5(&2y<~g4#6ga`c+{I1$*&3%tYINO9n9RC$Cr70%qBU_GD|0uK8BugF?Gwd z#(sD6AJx8C?Yk#Xf6Oh$9T1%~Q}cXLBMMr_$(rBQomUomkmb5PZDh7nH;QL_&dy*E z{_*QnSHp(v`-YvO5viZC^B!&QIhOL8q114z{=2SLdW;qVV=uNjKP10SI@<`eUt@md zPv-TG3Jad1fi3M`z=~*KU7bCOw+Lsjb_JVzs!N?a>2`$YE8$M&96J1(r{8aaL3lXh?6hP9S& zxjUA4w<2UwM3^_mH9ZujYq>FqH+#D;xb5dK-xD9pxOD=QAMWr4qq!QdKD50`U_moa zSO3cS+Ia8mCVfSagCJG;4mWQ^+ZEL z^N{i9njsmAq+qKrvut*@iGj<8h1n0!Xvp-%X0K^^KHrhSa@c+nGJdvPp(`~%hrM_% z%GV8oxr8E_)DbzK^10z_t>@*z=^M+P7SG8vZW(iJe`H&%Kg1Xe6~aZ10|_IFOsK^p zONBwF>#th4x@ZEbJr#yHqpM9B~f`hHOCB7uAm(f&f#RMv3;nY(|SP* zsrv<1&dKcF({)l!e&;)UQ}09yXDAE2wQqS4zYd##VEnN8D4!OtGYuj*{V(Msp{JX5 zA%+BL?*}_JGA|}Ag23(63u?1QE5H-O9`?F`(ZU>4^F-9%*XvuZYO|(42dsR(jWT*P z_1*VYEx|3(ZoI^=hK^jqG-$9U=yZ%J_AzR=i6JEK&ami0$|%izpTPT8GaIc0r5Wz% z%;hC7w#5c+R6LH!YJmh%j*n`Z@*v$}#Fia=krGOb)ybpOMq!oS0Y)UH%OBBJYSVv zhq@q_K;II0eg)sra!HY*50$Lvaqdp5ZxnhLGmaL8TCGk1lzHu=OCC(WE_W<%m9zV4PNYKdN}4%!luX z9FOJwxR|vtJ7Wb6k!|{k)Oq)GaEq53)z>U0yL7CjOE)vQx?<)y=^^N!5}@A^A$g60 za^YrJU`ZOnuH-R{$u~V3$_RCipA^)x7Fm>t-%XhuY}t`LOrNmiY?$MnxnJ!VzdD%_ zR@&XhasHaEb>)$Z*_e6zw-eyusAkX%xWixPlS&_G=xr%GQ&&*6yJv}rY46+VL)EoT zmTa3XLZ5JvD9DQ)j7|J3lGhu0U`A8rKjwe@lFZmm!;U-5>+x)Z9~#jL#a=RNghiMl z*cGgD1NqEQhfzftsb;9}>CD3cNl6S(=&QVY>y~uhve~FUi)(VwR}GmuHik=rr|LZB z(`vwD2TyE7Yf-S+@fNQ<7nW)xi*#9XI@<&*Z+`Ptef^j3?5~(Y$mczx8+gG1-~}!i zYXF{Wc7c-m&XK~f2~)>cEzzp&HLf?}C*Jx`OPiLm*~fq95;w`a%euqXA|WU}l^WkZ z`EdK^{@~cx-4dHT9Xl>B?FF+V7 zC#d1I}(3yK}%_@%G)#Lzby}q>TP!UU-ME$7}az6pIZE_~atDLgf4hXr;{i zcbXm^Bo|no%Pr|gM_0+2n4Aa50lLIr0WexPJ@zV^qX}6B-629-wOZ;|qN<F{O@pkfDPDFHleo!7)?ICDyAw94shzp@S z>$Ook5leHVp`+<63T~+MW)_19_4$f^`!7R3w?3{spUeLt_LEB&_w2eth>fn(Qqqe*0H?TPot&By7r&&^dO`R(j5s z!^G`}<5|r$U5?1<-T<1N8)S+FCE?J!9us37P(Xoxi6cIm#h;xnXX*XJ5I1Q~ahPn6 zdZdYow!Q6M(j7N>Y9SLP*2RYX;*``9)g$q*@HwNDq`nb?2Xf`K@p}Jl3!*(Z;EY(? zMG1};bULixb{KB`!P#is`7oI@o8{$|AZk@cE-?pz=>40!Kq3thH|`baLes(D4+x;5 z@S&h1`eiK>8tw8~+b?I6Ygvs-6t*AaH;@wZ~ zukdQ69htwW8zO+(H^+bUASmLL1_%Oy!CGZlZ8sVODsIlIJ3h#dXX1-Stkf3{?V~h% zgN3js(={?^y)Zb@u}x^}g-htD@K8$J^(rh9Ro+?~(O#0Je4&6Y(JxEaD{gczB+Gvp zASGgonmjKlC262$>aMrbp(i3gfkCdvA{bH}^fN?u2)u_cFDB)&Zq4|-YNRpN`yUpfIE!#>kDX>aY)tq+XH z-TV)s8Ou;R=)~4ydN^Ersbo?D6O%O3AcVV} z!06@ZWHLYaz^$FPLX+vUAQ5v_T``~D`zar;r>m%QJBNs4eHU1TdMoTv(%c!rL5m&S zC5Nj|ee0}f*-2k)r?Bd_B)?6jU6rFbpWJvi?Al6&v;Nm~kveUM-j(k90|RUElCp9E zk?tJhl22t1tv`O8+1|-=pPZNq@c987H=FNhD)5sZ>|`Gb&ye z3-Wb+{tswXc|Wjdwu&&z&{f@M!X2f`?!e7ocaF&|3??;9|q9sjmEGBlE*K z3778ir3WeSF^C!(PT+Yin09Hp z{{EhP7kd!RgYn5h)k-C$-cS5jay)V@C%ExVbBE5Pp zHdGudh_OTmM2Av_ltIt7U>x>;VxNtfV&WymdIyBM3`-rwiG*srs@+#&F1Qaq)!e7yb0ilYKW@Av%=+1Ue8bW8xVma=D0L-xc1CvH zl;uttQo9aTRdqBuinQ+QflYk(hyZ&8XqA9L+IC%iw6<#UqBfE$;MsY?x6#3XINaPuwZjDBZ6C9&6#jp4YQ5dP$H@wv6U$>yL%|T17W^gDEb9O~#ADu-o;r~F z_ApMjz|||Bp~*`;>*MrnSv9WAnfdPIr;Ks(zWe9n3!!!%6HAN9PZU&_kdu6v*pzXc z0rmq5pZmSHKqY&;@d|UcH_u&jAZE&Xm||$nf4ZX`$Ci(1Qi0yTh$e3_MnMSu?2I;d z?yTGJ)}Wa@Z8}JL{Z)c6&4ZmrD*}Li_Gb$=ugYeHmro=U%AZoNd96p zi}%NsJ9feG$=;==%u|H%P=0K|0@p-AM<_2=kYpd*{k==~MOD-eV`fEXc@Go(N8r5V zb(E)){Ov0?cE=3haq47LswTRl)g$hZr3`)xn_uwW(WI(WzNqju*b|;ZU^Y!5`J2bDWrr$WxOja+ zRa=QnSwE;LS_$46y4&=9CcdWM*|ynbFK0EVIz1be5`d6(Te@y z#$`6B;2NTUsZH&yN$a)veoxcl-Hx8P^Otq_w_<3B2wG%`zNv3=4AWMD>Ud1xg+uxk z*<7lwR#menKc_2l2=Fd#R(~g&RXO7s39J9u$7>Jt=FvePWa^1a$9uVbph?s0@Y%IC z;eY2*RdPFN$UWcYm*T9W!oA_5<%Z+0zpk!E4X84IfAFuiVFRn<8Ypp>(dXh8uze-# zmp2u~`=#@@rH^~U2B|gP)JJ4KjZZ2|Xbs8;c%&5xya?3-80`Gh4|O;Yxv~v^$j#Ei5AvDjoM#3+t^;K?gR(>td;X z`G^4(pn?i<%5evDyhc6y9$c(E1VYtbDVWizsd~LMDysEv^k_D{NB%6S{+gRrGBu}O z1H?c>9&&I|8|yLqnL?IuDD~c?^|dLp-VZS*p*N~}KYndl^wa3AehS(f6s}2R=jVTS zHd9hXf-;qpkh(;Ju4~a<}%t!?Q2M zYSOE$kUT$X&CT8;T975f%@7u9QY*9+O5~Q#gBwv(Ma3t(f8^C?{lrCTImVv5ulv8F z7=bhyDnD6&%8=OjfqsP=mnDsAfW92MHq{tDth{#gj@8$LJx_m#C>DhY@~!Qq|IluK zO*H%|aoF5@DSvrb%SSvZ*)YLxi<=+qWLiAz;#Zm}i0dkJn5oMtK(s^ma4ZeeRGowm zoTH#l=pRW=E4!dNzGzluy=F(1{Sy3&Z`FubZ5E>ju`>(>lkULw(hm!RNd-wS=|Vt^ z7~tXhVDg}w=%ClRLI~ zXS>E#xc;D{c<#K8CNy4nFIj@l6-V(QwPcbIi-bs|;G_Q8uL3F&bL1oLYUEopQz~!9 zb^F_F?OTRorE~YP*cqKZf45ez8(SJ%j$5i#EcUnjaXUDj4_`3GPzm&}EgRULq9`%4 zybQ5-2wt<5lVitxVJ#ZG1Ivo)NJE41yB5J8jAu_ZzcYHlk?=13!lO}~kFo=Ca}pm> zcZiW#V?7Ce4t*Tc^o8*b&d?B18m!Iy9^s*QL;i05z0RN;GFGm*%7=_lAVY`|gzur+ z6(Ae>2JR2kcub|btuXUIGj1pEvT?-&}{{oO~ zhQ48cjjM~ALPPR@G^f%K!$k#l64<$=5t_Mg_znoF|0xOJ`9{ISE15ron5(`I zO23-3rdB-R;Cy?Q-?XDdg-Arm`{E~hh$=L5xF*%dZwm8FK%)M$+UWv-u7CmoVSw!b=OLG0+71@I2tO9y$Lr z&&54Cu1u96H8$C(gidK-J~V!!R$uAM`O?24!=@{J=HV(&xVVNzhd$?Q@c5qde0#4_ zlGAhRrH7LITn>JZ(>!xlg5TblRVhSVx<8?!q+)b<(bht~G&VpLx>C7?|2vzt7+`*p z3{fwk#}F*c&bpISce19wfJ&Grtw39^;3a94sJA>Ft1xR>^Cq?J8)<~UzP_z+g)me` zGa82@$;<>GzqV}>+7O_d0pLH*%<;P}ouApiub8|W1+lHHoryWZB|g($h%LC05l&p~ zl~xZBiH^6rk6YdW@ocQGX?O!V8HeEj6JlwGJU2NWN}&1H*iMdJ<{%z5s*n9O6|HZ6 zU3Xb8{7lhl$A0G$(Dy_2Xf(z_c1r~l0-;Db;-IA6kL_W{dxd? zwcw|esdtO{#PPNg9dW$8sqVn*oC9xxkufXXnGS zfs_ddFrRv(a@x){Kg&*!wdhPy<9u#T#d6=eO+QQ@T{B)@n6d*BV!~0Ws8L(QkL{iq zx++PwHF>U~+RqIRwav8A&u-m+X42NP=y=G*TkzI99fgAKAXiuwlC$pMq78@|n&At* zOM($$ljuWq-+KjF150qrz)O8ub@9PzZ5CFdx4);|mppzbOX8wk`)yP;iaTFJ#2Lk2 zwkZ43BD*kZ>YVi3Up{6&|0!iqyh zZoscuE3D_zc=Fb|Wi~pye1K8>^2X__PfW58t0eFl|C|F&KTAsxdo(X_@5j2ds+LP= z_2s})jSPa%)uS`t~QC#ULEc*>S`jYXF`QA@gkui`Z#*r4Af!Tqvhx}Gnb>NW_ z_?U|~8+U3^;>G>NQoZdJ-xFe$M9i29-la{PlX+?_0Fh9R4#KiC_ue`zJ*hDC6D<5{aOtTXoK;ws#!os_#& z0CPH^Op^VUPS^n*9`QA-|MAPrtU`H6_*BTGfthjgtasWSg_czvfy9SJaYfNAOERM9 zKK%3Xvs4;cvH@}W#nf=)I@R)%obr&KU0(fXeNp2k&0EDdg@f<-=#`gxl*p_>eL~*N-C2Deuu6fc>?M|g{*R)Tp;ws-rQCU`mbI`MQcXgkn#?RQn zyu{o(a|&@Q!*@OVtDg@Ol~>k=Ys&%X>1G*KiM(T8F~EA{Zqzi33rk?L~X(-*h> z-BvROr1vJtu}F19^1<`J8@&*p#Z}goqa%?I0QT8R3m$~vcY%^h%lMRJPg3VBYsft1 zlpJO9n2oEj)uZ$Kf~wyK9~OFNcAg7Wl4p_IV`rO8~ z8TlGRHD?~#0euK%UQOBog=I2%ATJ>EKw-#hyB*Nv*?kqeU)i*QwH=gPv(6K)^KRUAdbv*3b>ctNn+4Jc4NJ1c0 zhxr1tB`LcD63Aro;$I@?Rd+zK0I8dt3tFtVlfyc4r=E&)7cq)wU$?S@mI zQdtVkd8Dv_`Mpy_)m7_^!Teh{-Y2uQ&+E8IfC^DcJ(#=hH2r>`nx3C{1zCY6LA!M+ zUD_2)Q7W;(F;aO(tbIgIh|j?*L>wn;eg5{w+n2+)TL0!l$RB>|2ae27fBO4(FY}L| z?SGkVYLN9P0lMTADgw-jV0TikC~|=w!4yMEKc zqj*}{d5uQiOi5iGjmiuM(F%8m$UmC5|7Fki|1T4>SO3dn=%*yXkkuJ^sD_hlISFn# zD$w-K1DMpq~B_AK^-2vHJDivTxdR-HSpUTO*`E)*2j+Pw$qsQCzBER?d|HD0O zUMu}yU5+ZjR49TIh%;B1H-I&S0@Mf{wF)C#bOnf*LZW85WA-Q$ilM(J7!SFo&4mqxo*&^cW?N%PYW++T{+e zpFXsf^&f2?-HmDffOGs0k9+?d=UA-ySI5wAI1M=(z-d|lj&hwa{Qe+a&ri65)I+nR z$zOaR=_*0#D#nfmrVEUumS?;W9=@p}@njdM`46P*-*VqrgZ$SQr2mZ6v|I*|8bP`# z)sOtb3_xo9@r@87Y^ja#vY-@&Q|@$er@VBr_r``F39!B}n%cOF)ciAq_1|vA+Kp=d z0+s(;I)Fb7S^vvGfq(O;W~YxapOKLot|@{(aQ6-M@YaiZL$XVY|9%0whAizuYL+nc zC&9J)B-%!yzpv`-gH-)e$^~>-l&Xi9Mpj0R2$K^Tu>)FHMNUUTrmWCWtj#?vRR5_a zd0z}s@^SAA@K1sPZdAg?eYmfJ*^{&;!X5Kso;kH3aQ-Y`ja>E9dsiW0=y;4165FW5 zNby1*p!(0%Zx2Uqz=ZUDSWr$GS||HU6Q7^yoGFo%gDKuWtIqCj6X5OSqdH(&TjjC| zzGnnY-3F9~?$i%4m43ka$w&e^vW}bM#LdJZfzYthMprx0)nnZJn{HN#pz^+B@7s*7 zimYENsK-u2Edlzq@r6H@RFLq|D4|kt&^GerhBWe4)j%d07WP<86;viSdv4<>O$;fQ zG;JMvv3>5D&fV%?oAat}G;yY$HIuc{$VQNqGgbbA?9dG5giB^SRhXWDUmWa?@e!+! z%x+3oNeUB@8;Mx~1?G8747Z>_@BoqpP)O!UVtv*XiI<|FXaE#?9g_;m%Z~7ddfpLR2Ld#edI9vOwQ;T33X} z;7rp=gBYpQZkEy6;JjjHK6*=3DZxhiTfFh? zqIWJ1#cQudT&WK3#3tN+!#(%i>AG({`&I7kqonFdWLfFH>4F~YqP6Ew(?iy zpXVQrEsT!pxP0M!hu?mI{)EM89r^z6EAtuQZ-TkZnjv6_>rs^nWOJ14#8^xt<~Vh; z>2hHL1I?uYuF%h+K~kh#Xzzp|M zhbPleB-_^ULu5$93b%`M>&?)nax={;IN{P&*W=GJdtJ8zeI;Pp zvpK0xKd(DsVs}8H{TTiDiGG)YzO7o!1ZB~7KKw6Q&X6fOsuW)@1fJSo#Yb9nF0pQS zoH$2(pwX$?h*E6j@p0`|KH_k4xQ{z4V<@s);Wv$4vrxDV^;bGvXqXB1Mc(UoLr+sf z3mZRRYbztAYu1kB$FA6^vRuN&%^wvl955wjueo)S&sB@#^)a!7X;WIh4&PRyw^>`w z;ZDlWa;%*i>up3Zq`_}hBku|o87VU{wkpdwU)+1JkxhRZILu&r}d~BP3i;fzDjIeZlbS;0Sm>tUmf3Uo@n}GoG0|u& z41ZLGBN_r{mMzUFytK+TR_iQpO-R-@(9#+~3G9Fxn`I&MY3%?N2&naqH2dI)FMW&> zMhN=WN|0!d61px&+}?K0ZuJLb*b1s|5064ds(9&S;JZR-X=<;_$Ezg#UF8S?ITvJQ zG!mYcuw>HwK(ydijO2=Uk4(bl1-%Y4ooY*DYbkVDb`{+L=nlb}z(?ul8R-z96e$dG zA=cc}^@Qn=eUDvlB+;EuAMu1SS6l_SFstZ$e1e|* zvM0r(&?5RX8Kw(%m-O=Rj{tA|Ee?rJU*m1D-Z%LO%*o^gY5)%-L>8;G;{ra8FCtEp zMK{xY!o>S0({yx$`@%-_FPGl`#D8Gl-mrx&rYI?=3H1{?)!GmM2x{n&!O?X>bc3P$ zq@mAQ)Z|UtHD8(KTR+{TXU>NQ@r+)}RcSAa2-%~)1EQ2rPm^MPr`g63Y@6?+$1ElWS*nmiUmY#R2Z${)1j+BU5+WxV zqQw=fRJIhwHyYe==8a_FXzmBsh_=NY&=HI?3}|fdii~f@jBGZTVz_F!$v1RiO497s zH`L`?e!!7&35!Q~T7NEPgKbBUwM$JwUYga;3 z41sVd)-tCWJ2yT4hHOZvh(vJrkR4i~Wk69nI%?Q^bwSSDqLwb8l#R6`6uX$a9!}W0 zCikkz&$diRr)LAwjKspGAWe{;5CUuSTQ6zL#`3h&LuZP}btQy(WWQ@EPS3u)vhr2t zzP2?B>8;xzE^#g&##z=-{aEQ-vJ*ayZ;=i}5dwSgl%6LS5VF-g^m5S;xL$Jds#tS; zu>I?AIpA+iXAfA-pnmlT0~M_0!@(NtRGv*jcTPIBqSaqC4fbmN*Zq+0Ve@d!+KG~c zM8%gaNbmdx=>`3wBg!gTChU4o=_*mRCjwQb9m|B^&h%Z4B6>@#?jq>-tE1?hjeq6R3yM~ljmToTfk%UPJK04pMc!>2QF_H^D zH-!RIkxls09Z)m)AYyn2#0L~9$65+qEue#4v#kt2I!S+Yb3ZUNZ~Kn_=lP;H6v;== zr-Oen6kC zd!OXl$suF7)|F!)PYtdmYT{c#WMJZ+w0TIwu zc;kEVr5f(@*{mtW zFydy2k<+B4;ah7gOVh)9*JHPe(~soOM4SYTJo)a!ya+!`nrjW`Mdy9Z&6a<-cGVLK`8Rs_^#Bq|-Mvlgc=&_MwyhpHEQ=zS~7OnYDxo;qmflLje%m34`5Mw!=m+2BJ8VNFtfNlZrbB1ywR1rgI zXGzdzb-Thssr6d!4YoiDouA*!f224{OwNwmnfkxZ%--6Fn2k|k~CuP|YI6!)o z{Gkg_>p13U(bHUed5wrIE3wdIL4C56^S%r}VyYAs&t)X}Sh}ou@wLv)8DX zFw$M}xBL_B>cRqpto%e7!9MUWQzC3@?ly7mBpu2NMkb(w!4ZNwuI5U*qeDIIG9hFv zWM(v&J}51_VPMww;Ebinz4NKTN&9ae-1Evz;`}(897^aRiUH1$(p}ETu4AL}^iPUM zQmv6P|y46rG(Pim- z@}{$nmm`w{YUY-Qyo*SPW|3T-q7eJ?YhW}zW+(JWjR_d5b}r9lJ8EXE^ra%N!ieb6 zy}EBzpN+P8tR{G~KG)q|hGA(G^YUbU7gPj-C*1LEW~MU*=#4Y+T&?@V@N+~jo&(NC zbB9B|3_%=FVFFsM(M$ng=}VT$v-RK}H66~~7bf9b((kB_s%k8{ zF*`3aUZ_+CIWY1h_Xj80JHisCX}Tpjbc7n$L}jNDjYNxK5z18731Eees0SZIh*|YGW#~+t zD@Zdx6_9vOtg2e_?bLyy)%UO2RmpJ%)J6gd*F$+fq`^B%MG=%y6R@LHh34d2d4Z3V zaQZl0;`5vB-hA@fr=F`%b+pHqM5TXg+sm<4%3sjbecg6ICcR8S-`Jx87G!B+3f2Ov=| z74+QV>Aj?~FwsZ9>pC|+eW`P#tD+zyDd#e5?eUgG6XF;(j+{h!UCxLC!cdha4FM{! z9cWDCz1*E?3qN5qxFW*|&;D9-TmFV_{?lOj{CjNjTtlQ=@5m|?8%67Czelae9;)MQ$bV5>JqZDQ^w26_m%l zM776CUFW3*n-Kg}wU+!c$9bUV1N^ejoH8-dwv5L^%o7_Tyyhpq>a|>fbg>z%Qeg4 zC0toeSu#;ePlr}hK0hqYP@2VfZljbCZO9U@8b~>;Wf4j>A6IZEO_~1qXm%O1qEvE5 zU#9E9OHHCm%T#-p7o?=M9v@b>pYaLl7!y`^t}i$#w8qOPQ$N$%kEBP*DhsiDyX9W3 zC*1M`;+yxLUU{;g$q5HjUlEX4Tgq$dv%+83hOdxU=S8kptxX`y1XcM@qc$Chq5-1! zF*PPVCT+9v8=1h??-pj&J{_4BV|YhTG`9;q2Yrfsv(-ten3@CDZ-*GF=y>oXX1{fF z4mHAj2<;gmSZVg12kjB~r0>>=qm{8|N6{Tmem=}+>7Nz`ROU&z3DiqZOgMB8#0AVn zU6~AtQ7=uZGX1i-dHL2Cq32dDUkvt2Ikmgb8Ax<|H9h{=eUtSW=Amr3hRAQ+uAhh= zVOev!7@1XfpwP>OL)mxvRQR&wh<(NJcIUprcH5_ijGI+h&fg%admn#v2C&3~$UPX%!BYKtopI8R;pL_*M=qdSay!ABsHY6%(4w8%Y;b&dEdCTUT8u zmFjCdbERJ6N8@9CWfgIsr!4&&1(+od!gW9oVE%GaBwVKu93p&5<4j4-pv+`VZS~jD z?`JCC5d<@=aaT-C4}*@5f1-vGz)frz+#SM7b6xi+NP^cK4-QB zn?il(&IapT)8VGL8i;3dP3CW9(bqpqfGb6Z@dM)TQ|=7wqUmqW0c}yoIF3<8ND_^x zy##W+#>oMhmDVV@7N9qq>20fF(w$xcclUEdRa?JsTdMf1G=(A=aU6F>a5Ez7Bo_Sj z-_E5q2#Vi-0?%7~>uSfp8EwiSb+1ZI-xko&nteJ}e? zul=@T)340ZF3ncDV|!x*#9*0(U@nDyjPj`m%fJM@g(N6_@xFQ`v4+ z98spvNcVfBzB9g8-1PjagwkResr6e=q>UQ&Gwq0Dod#9f!6lcA|B_w6I|GbaJN9hB*Nv=XG{ABU z<{)82qr^gP`F?#1uxXzS`vI-oSQ!^~K&^nz*#NTP>JI281c*kMWme~JK-h|8&Milu zU~=g+a%>-fF!vh($U!eaJQX2?T)lkfcZWY4*(U+S_vN9?$R9(ogP$pytG?e-KFYfz4 z{TK@Q5^`w9JJIlfWAw7Vf{^mhSVPgcnGL1Bd0{RRCirwBzF}KEErz?1^^Fv9s>1C4 z1-65OS5LXe8U6mbfl05C{Ty`U4yfQ7i)TU;U5DyJ07qyX8mtqduC^^)W~n$9vOH!N zyYGx7>%Q;7Z-!@YBTT^wPXFK{cg}tm&ni7H?=}bIyh^@G5L|bq`JmI;>5M) zj^^VpuiSfO8^kIa8!!~*f&AaM*P)Ao*Bb#fmmSceNrypoZCj+Vsilw@ch?T+cLVKr zKUmj6PwO9D;Wq34pkw<1?OWOBbT*!J*5{>yLM-a0%lfpkqKfD-H<{UQv3~oV)3Y8Q zDVm-zGqJ+|&J0F>m~MOejQ;7-zs&#>zmc0I$cC*1XfyPojB-4h~r z1{ojzUpB-Z{^y_V=UEK=lISwv(9vg~ids)<%EG8e{S8{TUqQwo9baRf%Q+EAn}#ku zQpfvk-SWYM3nc)c?0!KeT^~rN17H;J!6s-yN}=^a zC!V>_1~)x#^99I!YSLXgm`awvP^5ygd(!*u-V|&Zvn$B^o84$IcEi5U6EvoYl$st% zPfF+KD&xj%19b{x;Ep;QDuH(15qgpilw=hcwvJyQ<(DVA*h|-_=VnLc|e0 z)u#X2QY0wzq**G;dE_WXZXco!PEr)R}WcP>-NYLu+rO==BK`6v|#dLp3~LJs17Ui z22x~WGJK17B9!=Q0uzZjR%ZCJVw``t=FpV742~FjlYHPqSz^GGD-IFtBa)SRM|jWs zh)?jL-vOD_1DvFzE0IcQkgqIZGZJozrUu-t>W3Aq*DA?e-#Y{q9W7~SJuY&{lJ55-mxbXxESsG%(acQ=K>Fjjsun=eBmJQc}C zFE80XdG>aHlba?B#Oqn3FvAOn*3l*^BrVxg^$TZ7^jfRHo(m>fTpr+J^m#iRwJNhn9Im)tcrY6_x|y> zL|3gs+qDP!c4C?eAEemiKi$?pwntHa?3Q89Vg>$(G+K7!;||Du<+$ES{W{Zwby*XL z(rvl|<16wpm>q7$NK@hn-5!2cV={3RW>WQ~y8lGeWm~M)C(ZryePuO4UsSoCdpvu7 zLGL1ZRFZ>AuGj$;I?;nIUQBxg5~&i~xao6>?~NDG)WlEdV4(T{{q$aJNHeWER_Ecvh!$x{!tM9D99I6O+^Na_G`8nujJ$gRVk0 z%|=sCG-(K>nAY|wv6c8SqMk(0Do3Tn5ObY%7L1^3+?X0-5;!1^KbM zAG!5SQ8T*cpMccLdDPcS+W|lads`Cp`hXYE*!vW<5luG?G1sV9vq-Dt`3!TuH*{s( zSW8gn+(^A!-g|ZrlN*PP=3tn#E_B!7%o%SRXt?33!>FR!93~oLZy1Iz)d4a)#}^t?eBtxGOwx+NJE zg*eR|vXRF)+SgVZayVhN3&hG%AD!;+;FgIqUbf5UC0AD(kwrQYOhp5r zUJg@+d9_guJMQc=Y^z(+?`6cD7I8l-Fm=K_qKR+M`;(C%Yl~~&19m`E2}~1Ih}t>P z8lDd~D8xjGkYHg-N5Q&7kHk>M#bvr!UI(e~Te7P~54~T@d(Xz(`Cd}*o2^JhMduj~ zJT*;73V?6W74hqktB&YKQ>1!fSinI7{tNc3oPv1PYqrlGo=~!ms!Sp0Uh2A%k9iR zdcRKe_*&&ATG1II_Dn+eYUxVV4XA`$?Z(!k%gK|K?}sC8P$$(GDjSD|L$7l`hws^xbPaou&&n ziJM6J*27P~U4A`w?b;WgQa!(@!kIIYPo_j9XRDS$(~-}*9-PlrOugq>hZ1Enyp^Mr z7ieNLXag*R!z}}IP!SUh2r(V1xROzY3;owQ5M z7^y9%cO|xWd0zH5P#+#!MQ9(+@OE)vxXuPBaT(o9{Jls!_v{~8TzY6_GK3xvIe;7m zVuS0_Ab*41Wc$CAZ2!C6gLiHC4{ZqE8TxC0yH_%1<2bSknE-dqV|H&sgf&i5@8W!) zl`3Q^t*{+R>RJP$hd{ZgI)=;A=lIfFc>|yKt)c};G6g^rJq>W^6=6=={b%oAX^b3q z@F?+|8y>2K=kT7F?q=`FS?^JiZ={*<;WTG3RG(;Ian_thQ4b=i41e}lG&N`N9;P4_ zLljtZamrVU8IR~#rN6`A;d4F}H@3}~JQ7XFZO$Fgs=q9$P`_0G-vkrzb)IXCB5)M5 zW%A22iY0JGjf~MF>lLKm$6=d8Wy^Uq%ht54GIV zuRs#?pkOhNUxwXKs<O*h+T1KzP-qfQcHK0)xb|pFq53FJ zr{kbeG$_4}3l2esA0nrgz5%ks@%~PI2X;WI%7}(XYjCtAG8TdGd5p(-U(Aph@1zy! z(bGG(nEMB;#`|e~uoaZb3>78ni{&$89#$bu5^Rm6(hd!Bzu!~&yc>5_U6=pjdV4OI zWJuFD@1JU3y`JX|X&OVQyykcptH7@%(W_syuiUU8FeMNu_S#NrvR0XFUVb<0i`Fep zxm8xX_EFe6pID_!|1c@uKId`z!Ow7qAPpYs34*`N=Lu^CnjBm|*J{xsXzgQ4H>SEZ zRb?fW;)d0@j(u_RZ06Br%|z?0y6yL!r2gSfkdVNeyZ`mFUPD@l=betS*_>^3=+xNSVwMq>oY20q-tSRpitlFz*^7LA)E-21grx6o`WYyrJ%B&IQg6I-< zAE`)VGku(Kh`Vp`Ic;9qOFr+p#0=kdt-kl1EXm;&xFP55toE5+fRt6jy9nGeO3*nc zeKH<}N2XP~&UviDCSk9TjxZiX3pTVFBLsIP^@PF|CIf}<{1)QzVDDD(>RtnCEk)iL z+joyBpL2XGCv`2c03BX|RSDJLW;O{ck~?Ck*GgY`B8lfjp`(pbiOO9tseIi%B0ra} zvYszkhdee%)~f-Mrl}g+OeQ##r%xl3>qlFO^|--tsT~l!@nl?aLnB0_g*%hEw``sL zGYLvSRFJATo^SVrPGCrOW%0_KPMb$Xa}y~Y$Bz}O@@uwmxyB*i9y}uGqx54z%!iwj zE{bWul?2oRA$?`DXOfdRur-aXf)1l$JxJ>L)~GsxF51OkRY!-?9Rdp%wzCt4qE5QS zb{4C>a}&YpacLcyn`O zLUwIkV&E6u;t%0I?>i65e5i=;tU01#7EY7nwAG4Y;l2X$-*es1mX*6Y0U)nnt{Rk2 z(j*Zo8mf#!h;tst2<>`*jWj`5WEde%Z64~s(swTis_eii*3~YRe%kC5=pMXf>*`^l zepQM#^+YE9blIR9bP9ID(iby{kN#=3h(%$Kn@Vps7kt_Okgu9(R1@^%Q&EOLM>8I1 zF#$9^UYQK5aUxqbKyk_6m5WBQc7S$Ose`P!`5@Lq&0rZdNNQ!Un-gK~r1N-UYQ7HnY(#x71GQa_Hgb5Ir<#gqIv5(Gtl zk=m9-lf_yldqa#*qQ^x23`0x7q0ECoWS0EX5wrJ9d2z*nnw&RNrl{Y$;QficUHrb8 z4O$`>gNh@c)_-9ug23p`j6~*s=8xEW)^IaY>E*KJwDeQe(R!|Ki5K)%zYnblmij%s zCU^djDCm}p=0wF3o&==?6x9?{4G5n~?FV#KR1Qpkt$ZsxW|n5}r!0FrD413BNUYNX z#<=OWhwJb=rPcA3z}rQ_oATCv2-6CDdD_jb9!fdmGeC-S!NP#+3A?ypA`r5|76yu8 ziCD3`(>_&SeYUPYS8|9tc+qH9{!NkC27bhL0tpy5NSFn7kP5pO(PVRGVES@MWo}KU z^iVorJcQZSyO++lIQ1^Ji*u%9LAs9P!gs{Ru-6=pnq8@@xW)}9=4)#zIGg;sE zY8Rbbov73uJ{YcH!rx+eN$Cgu6yps9=oVEc{*@fsfIPGVdf>ciinaq5?CyhF0rM_y+4t3b+!4k+a`lIn~1~|3%TeOnq)9jHi$Tw!Z(O>3|6x#IVSQH zBOc=rcx_DM^x`LEnU|BeIgze@{~N!U-=WE;k%ylpUb(t2KwEMiXsA#D@T3|IgYfK9 zOhLG6P3UbGAMH^Pe}xkbCx)1;&e^2P2Loe&zG0D zJyfPTIyQRL&^WX!Su@e~jrAFsK<+D)LV&_Byi`OIq#y>$glq}C!7VTR@~*wMYNy~j zGevcA3GQBs>3(xFptKFXz%yGF0kw?dI4$b(wuOsz=a|2A;yAhxL+Zd*Okf@|Vu2RA zy*)?I&bJYps0ddc4@T3X?ZzJ+~9dArMhHYIiaNiB>HGzv) z9TKnYeJ7sKsVDgwG`iFk#PS4`t*vacxjIr9Rc+05^brYZbEg(%fez#`OL+2d zlVBRDCxSA9N({#!Oa18_3OD0>mD4xGx@Cnq3tm8#e;os6fMJ6jDC z3y9m$3Qd5yNO?i+EMWFSj;&xGA)h6$3up&Bt@`4dX78F43tpWQ3oepUYX=`Vd2V{< z&_OYgCDlOL24Eiqad36ep$a)2_^xs2$BO-z^5B=VmTyn_#dns7O($Nh>@j^9=+eG| znRt&ZO3V&)|1?flTa%H=@_Y0k&pEGhG|@J=8uTbFXwNU3HIfS@kg7?8IQd6Mks_`L z98D{b@9{)AS60dEMikvz{A~B&S<9Y%IHkx3v?SK7?I3zM-gg`uVi(7y!N5#*#KxEuPlBo zXyVAJ+v>c{7?L5!2uDlWnJTh&-o*8anmVNXqvD$Ck_rcP-RFml6?+21Kdkz{A0A-t zry?iNAsT$8Bv>Ou0C%nT@w3%}f;$iQj+V$RbhjpYi@jadQ|jkO`(;1sGqsd9n{knr zb@&{gSzL?tsm42OEi)7~cmap~3fUU-t%r8*GI%wy_622y6kRlh9Fd&V2>#IIP-Drr z!lPvT{g z>8{l=9V2(f4E*``JTv8I{Zx1(e)MyPkR^oHrBTZV4}@h8kCz*NyOeIMdZ0@Zw!Y@+JL)3Fh2_*WsL~Yrqe;vsp~S z6sv>^nkPFE2{i)`zW8lHY@-PYd*iwX?=Ps!J^Kdo-0q>DsGz71tLe*zyD`Dd+}~GU zr3nU9R|j01de7$g(LBM>(}a!K-t)6cIU>u~L$BnAC)-)iF%`pCr^?GKq8^6uWeS>b zavd-L`A*RBozo5ld^&M>g6u00bDTJx9eJNtn{<$u_l zzrmRTMgE2;6QnL4zSYSTJcOLT1E>lCRD@m~x-!6+`H=(kWCzk$8FdoOGs*y54p0PE z?*T6f9J1+hH*&6(X~4nM-B$jS(_^S%BP#SA5O8t86SalXHS_0~aOCt4VCKeTI-=iW z2w#$z!4NuX2SnsTHa?JL97b*dH^ZIm_!*#D4^~yB=}ZBkp>{U5~iy5qCY}E-vv`42IkAHlsuE~9E0WS))hi@2Rf%u3LBm~o4bu!1|XR?;t1h6&WQL=^O#x=00y!T2M}u(=D<{Q z9Xr_}!Z1Vwz>*GPenLDbfZQ1EaRD!WmDj$ja(?7j^Ml``Jd!!QEnC*?P#4qDj>WOW zVV8{McbzXL-uE2K`elrfEZNMgja#581`2BVL7EJ)*L_)AE{DF139TEWa7b?AT~;gn zibOFU?T>A}FH^eQp`@QcLDe-B(-6p#gm<23tuzpb%``5{=IP(GtLj}>YJ9@uYVqFX zPNr|Wc_)M$2pf}Q1-} zgy#8Gk34OEj2;v#miz82tI%MSD8hN;hh?2(?O2uarh}H2zZUm~M5ize_=9WD?7CT& z(G}ngd2ViW!A#aX_!vs9>j9TE75Trq~5&wXOOrfomZB?s%N`qET7kX3( z=~&UsS_xk_&lMOeIt|ri_1P>{_{)S2Px`VcXWlZ&H7b3Wf>`&qz?AwobN}Kp`w2ef zR}1`cn>D4^=8YEU|I-`jIVz3vUYM0UTQc#2fC0*yA2Co25n>wj9VN^ZRykchR$a6i z?VfhoEG2oC3g?z}4NsDSxs22z^wJVd;vdSD1%~jb^_3AlG0Jb2;?27M__!gzgA%#d zkYvtc*w%0V|JdIKtVSI3^pM96NCUF!h+Ng_E&XR_b0M3w+c9$h9I6jtUWZHyIY8C` zg%$D89grw66#tWfGyeubz5Y0bi3F@)0=zVL@B4>Ix&4BGk3uR(6lB3%T2TG-~!kXk=yvf9?`RmnJ65eD={vN+ErO>@hHHzB3cyJuYhv z)u!>s10x=K9scXDL;pYKff+y%;Dk7FPJsCqgpX?o(|mraq49y}j(in##R=(cD8%av8XDfsgvbKf$=`eEtR^@BQas zwRK@7O*e|Sew>dCql=9D&_$81j2BWk zRBEPQ;T?IN#N&GCntWz{YErF`b;`cxDqu)~Qp`qnKr&$x~sm~D>^|8RqbJOgXSBctq z_l~61*T)EDddkn0g!MB^LoDn>WQHzgXwS9UAfni{+RuUp*|Md0i^@0 ziNp?wrUm}v`5e^Hdp&fZ45d9Cc>USk0R|AKAW&}wWIDEha;~joe|q=2K{QN!QjO<9 z*M{EOQ|z`IR%fmE`? zg!-Kppqzu*$Lv~#^wRMX$W(dFO-h{{2I4u#x0%0a8g=nT)>y0=BCId!+}-4y19{$1 z`NNWPvVUXBKR5*{{*QSglpUZhym$#P7I;e$vL!N){gVqoCPg0M*M9T%Vt{?m{6Fly z2UL^k)-R5tVn;i{(gh)r zE?sJ*1_&Jj2_+w|^T(-<*SE(ZL?p z8DRf4L*5WvepChUFn(~62|q63U;ck|die4eFz6`oAm5+f{Jt$3wr=`j4j3V}LKGJ} zgjvL(n^aY}Jx>#M{>I@F>DyvQY|d2Pdung@PCJ>A?6aP*AsXRMZ|}k z6R!PSFlY%Mn)LBuS?k$=196GDTSbs^(WXIO2`f&Gxgt#wLuAf&^(s2&`0pbc#2XUr zcP`nf<>wrF-}0KR$E`AKK5bs1GvgXBDxsM-I&=Kol5+Tooz6wER55srUP7xZ)SrM6 zY|$`!#q^*kl*4wSWpe3~la6+&p8mhGmF`C`$!3dgOgO&E*RimNZ6pgnw_?>zmrWQ^ zOe-$;N(TF`RuZq8xIuG=r+;~$R69OHNrqKytgqHg? znaKu^iayy^cz+^c71IZWM*`L@HL~Dnyak+U7^LbX@!`WMji*ktT~`l^*GBfce#%J6 zyqTHdAz9plRpQN4Mo8gDBcts4YE*`Lo;xloa!(j6$!MWrV4#wvR4Wms{{SQWzC%GS zSI=CFzAGy7NRLirfx<@d1iR~3wtoKXWmtQjij?DMFfY)x88O^fJop$!mRT}5vzaMS zhB@HvEeAi_*41^ZC_?;!;=?n$iw}x!@%>ug$tSFy9YU5XN&#W>3Y^p+gBNX->xFNw zg!&CeID(zZ*8)w8R)kv~p*2x(k1)y{#bRbG?MVaPq=Iy(yINBJ)?BaI%8e&~O+~m~ zKeIK|CjWf6N94k?(09zaw$3bL(LPjxv%J}$O&%hR1_vACvKG8yx%EGqBALT7}X)a*86jRk1}k!;W&+0d&PYdt3eM)r?mswVFP|y@-W_ zZ<9+^d{noGFXR<06r?2HXwN0#BJjkyJnjko7J#_}?JGhUNwiWfUB9wv74g1?mg3^p znxx`BXA_D`R!!In@#-7L$ghQ|5lnMuy7r(a0KA+q1K= zPhKxAB9Bn&SVulib4rdUFcN`;yBTIo>j7MsXc=)&u|*2!WO3Wn)PixFQwv2q(&C)T z=4)BD^%u6I7OkG@yt?+zFwMevJkOx2`@)&_kLEKd%W4f}aw4wAa+%K!+bb-FRORIS_HAFj7+f^l0$Ya|u1VNR$Q~O+%q1fO`Irb~*PI_j#LFVAgtn`|l z=0kqXadP#z&VSaTX)!5SGMT$H{2bSAZGgVuicZ2HW3q|NurFfa>JXb!u? z+`!0V8qv=%AQIdbyYhnTyD`GTuTm9$m+MA_w<(25%YOd}^b%5-W(k z6~J0g!fj9wklFr#rr)z_<}$9LNQw~?;xM zS9pX*S+fsd+F>GStAS|FZh=Zc=7qcx72S`)m9Cp!M!H7W1p7+w$l4Gg{&0rh;n0cW zU*Aa=$Gm&nI#*N>nL0Ccar1h&L+6n87nFuG3FZpLTy5qt`WanjJFTD`5?0hzooB%DwoS7;O* zPu=?_P=^1`pS;fx-ssP4t;qAFALm}Z5^O-KVb!uY6G$~;C?OTP?qf@udpDN`YfPGZ zxAgAkZ$BmRQwx5shreSB(iF_1+@lXK^LcG>1RoJy2mxmhAI=5XZ= zl#Xr_Pxd}<#^ww&68@b7dWJ((txF# zv$M!fmLJGCFvuk%b*6{VZQ^EPA&k3Ue2<&A2X(hRnB@d~<;`Wjmh%pyc(syNMnn{< zs%a~B2_!r%=WSp60jkoaI`%x6MjG<~DFi5kx8oM2q4CR2`{vT9NxPF%lbvOZvuJG;jyO! z558In-hvhSY9&O5bCgFFN>Ff#4NiU`Z4|3kLSop1zcp z@Hc**mpUk`H_JKA8zRm}V(lDZf^DB2l`GwgL1aFcnc=!*Wkj7e^NS+ty2!Z}^8`xC; z|J-jxBe;#w$NnaNEElk_k6&$Psf;2f7N^>}3G0}5mbl=mbw==9 zBZNanBX#{m0(exgM5cvN)ZthjC``7z@e+;Gjx*u8Da0Wk`dsFYPM#yiS&}J*&NQ0= zuLU0H)N0AY$D=0}^aJY$p>k>RasDZaP^PgCHk{z{+I(3>29Kz2XhduB}V zN3CHjpyk*injjeewS)-njxk6W$M-Q;Y7}Y-^Axz!+WkE~nzy^aCxX22Ex_SV>4*OF z*8aG~zxfj`OSBz#=iH*Noo7q&!p>Mqj3P35E!k9N=irfLK1=A^S^`>Ji`McZTz>+Q zqj=AoCNg(dbSsqOr?tU^y>r!nNW)U4UEk`PF#aJO?e;VO^*3aJ8`E?YF&&4g9#E1; zg6`069NU?>7$odP$N+6&U@^be+K!mgrj-7l0Ev;BAS|0b^$ncvgj_OT?IZB#zjzfk zvExv*x>=iq@MXRdU|hCCiWnTs{XGR{#Q5kr_XReIrjL#jA0$v z<>r4|4_JBNp{RPi(hcvYn>MUE=pkvJ=)t!>GEja6QV`Pu2@oXEC%Flb4Qh|4whr6L zw_nfba$$!Prg;_8I&koyh{W*cNy*s2!xi=wR!>*pKiIxeyLfC;1lMC2@jU7-sV^OK z57PzZELO~HV-mrBgali4Jd4b^#PmWx%S+H-{i-cDI%r)+pIxWWa1UiQY0;%FIKSD1 zoSR&(&;`?HF4D-1Z04o`anyGECFN)GK^aW<=_PT~uC9TYliNHXse>&aY|cp@P1&$d zgnvFP(4g4x!c?~<;)+A_0@1?wq030gQW{)c9Un7>x$G%3FiH&q!~B|bgqa_q%^IRoZ{;r|BhK#!aRXrO6)>!f1H< zS1#FRwQ}qRb5h2S1`I?|mHoC5Zhh^I(e&)zdxcT0#E=vAFf+aV5_3^UfAsDdy}lWD zT(79`Y>uWlH=ehNqeX*L)xr5s)2~D@V1k~rGv~t36$zCpbcJ=S3$q%omH;zH+;%)e zs8aS7;~58-)@)r4e=A3ut>MZ3#0c}0?{hR<)T3MEC$A7yaH$zC>o5=gL@=58Hg@dP zK|6r3p zPxLDq4)XOpDcT}d@x(1Int5&Zwk(INdAoiLc57hvz}3-VBTEB1sh?auat&(#>1TyreaAlq z=g$@Txl>e?CN3M8t-r(YmN7ZhP=#jWR`BYH?~_ZheCQ1{tRF#!qe^2CDF>+X}+&a$hZM^}Ukq zbQebBMWp>QUx0~ekzp{UnM^O7!%v0x!-dg@xv?0SCx5jFva^ffpHz+Va@rq?lD<^0 z!+LhC>T8qnjT4h&sRvWj3>q(@GT$nuywlRU3tUN#?7=sIeE;2B=Et$fq!y()kMFyK zZ24zn8y6EA7t^uuKJX$&fh6)G7bKA-V{;{w5>1N<;5G4jAp!aQzx^IGsP|#j`?5i2 z_PuX!%~Y6~5@+U~#>~CXbKEiT`aSdbd7z&%`l&}hm&i{u^1oprQx{#P3!EphqnE## zR;z}>^`jY=4|mu-GZOO5^;t&)Bukr_AE2of%X~>#OPNtNDI2ZqNL-0gP~qN(&u2}3 zBh0wBkYlFNHU^)DbLDNqpOc2;+_*E`Y=IVJ?$k%japrS~gyTIcw_G>sP0Ho9g;AN* zL9oGySKvxKBtUcANsYNpXh#q3IOaA5~j7!9t_6y=Dr*1 zq>ona0sFq-(hfQdpilcNuaRMce5J<|(!6>Xw}>3Uihb`&@l~3sn7aR*H_iFmZkcTgMNz4nS& zOgscT3Ca`fL(DM)t?hi@-iRT8s=7QmRCE9DDfk`Iqz?v;`I8C>R;Z!B9#ojY*J}4N zy^X#dm&B@|qtrm4@z1b<<1mptg*5+Nt)3jE^m~-KFYx++Kr8-Gt3k;R%&oM55~u*= z;8`+lbu@BO1>_5j-_a<>NG*PV=y2{nn`T_r22fvm+}h1n2N|Wqj2Y0YAC+osHKwlA zdARP70v#IvmE#ed+z@Sy!7b^4XHR&=y8lr#HuD!R1mv%O$eV&B9aw$yUVRJCM%m5a zsYERpwJkN{N4=Xzy*spVGv8jn=jSupaWkfSpc3$c84G6Y@TGZv5`gE&X?OCp3$F8LC+WBwPrM-xi+V{ii>S zjKZhgh~)}3@uB*>MQx4&rDA`& z?nQmJkDa{sUwiVXJ^BfskG<_ss7Z&(3$^1L)06i; z`n)&v)fWL!fcbAmHo4vf6R&=L`j2UJ{~wV?`=BL2Wrg|ol z?G4Q*cT4^J^!HJOKc(^g(*G%qpVIhg8Gc%Z-$rQ~{9Fq^*TT=W@N+Hvj0%2Q!k?D# zrzQMp34dC`pO*0d8A}LTZvE#FgYWU<{PpPlg@j+2&G#r%CNM@lDH#z-_JmcW4eIG4 zX^T&w9C$7Ve-xf0OPq+Z9mbOKZrc_qgr=h?u~+DDhVP4}Xn{^EEF=Isthz&k4@dMc-mTQuKbRSD)W5p9Tm?gXh0^s%>|IR@<+sHA3;WutqDVdrx{ z@u%5y4?lmcp~P?%@eMQG>!(|)8Ohv8d3>_2r;%BEfkyDwHCFljvR`+F@hxVq)l$oS z!*J0~60mJ^NCf*)i8(>cWcDv;1XFxKmmGx5@vlZOeWYTyUSeP^sG4lm}rAGZ#gY9&pqiyW>k z4y;<`DJfIn9nn1-=I{lwM|PnG^h=SCyLbudlN|`N60UFk&RemDEQ(l6q}Wb4St>gvH!)2}9xfGcBX-xGflU&QVabW-Fr4xASDt zD)cCS&bov%R?gB$vkxcSmAV zO-(&pwL?#=oIpzJ+9qV@Z_Foc%dOHszE^8thlm@iP|}7+hRxc-grKS2Fy3=5OC`2B z6Lz^B^qk{YbBt)|nl=FhA^D)epp{3$A`PmXw&SMP&dIDG)d>g7>{lh9Pu{Skd6QgA zVwqd%-|PxR3XK+|4%?w;P|*ue1rkdTW;)a1pJ3}0a@kycOQye4g}cMeXKLov_QFXq zLpA%rVT2+!-pQ!mp(#w6>2CTIFGe3Jq%D2|la2H%>HtPJE~dV3XDgxG@%lm8ori92 z+&q7o142UkS56YSvjtzku01lS#PPS|8(LZlR68)y*a;II80kiy*2mE$AvYE)U*? zEJt~zGEwKSDv{5$cTR}Rt*C=fjG0SL&GAAGA`D-tc~l>x@fqlYlnxedmQ|rdJ;hupkHa~k19op za`Zo13%>q(VSiEKr?6@L-fn<2<&S;_?c}^<>amqD-Ddt%AH8XmUxPmRmqS8wL+vv# z6GvUPZC%P69Z4{xV_T1Q-uht^nS+hzm7Yts^|TQ8_Cni*`mQk74@bS6ZzDDf}5^j4R9+&1Wa-zDdWxl_jn{11Foc!(5I;6PyH z!UTQf!Ju3-Gkjo~&wpWNlfAR1M`DrB%OH`dS^cm}LcNdfAzoq{a5+-c4Pc1VZQA7} z1XU=NX;Z$l@#SMX<(SLn)QmXt-6NJ89zWl82e#{O>8>l;iG`^LOXS4iQuUG@PXE?^j@_Ug>-{vLN5{dEK5W z$@I5J$zE+MSvM+pM2<>~1J)lS^5Mg?w~-dom2tg6>c>t#jAC^{-F4uNUCsOHJkDLyV!yVXOmvLIA_B z=g$jIvQ4f}J+a{5F#C}h+h)A)xm~9@r8T=)mZ0sdwf)MzzuYkY1JgW*0*9+PLql*Y zi1VjOiKJLh98U-KkT|`6neUCrH=^C&QS{%C^grJ}W$>TY0m0?Ae;XYA7AWC2F`L=S z91-qoqYL`Mdv^24JGZ_BL=j27U958yo7iQ(Jvmw*JtB$T$E$aYY7T212=94UlMy|O zEhm5V0AqJ72G^pE`pN$JckCnU@f%65z4mKF&qR1e&e}!n*8in#jQ~-Y+NZ%Z@7E7# z8d#)PMRODTMB3#RG89>@XNHhnqqVV%^0AC*xWx1sz+Ast*dC?l-u*e$7$5zaIrk}k zJ2P%zll~{n@%KC9ebXz4db`CW{pEXUfm-`2b z3O*EOhxxoZc+4l#*Xh4R)D zZLR=N1jl&WBz0q-MpYRFN97 z*~iq^RL=i@5^yj`@&)tq`k8+GIbwz3o*?237V~zr9sw2lHBndY_WO;L)_HK}nr^D3` z&FS+8XNt=`<&qxku{so};dsHc{@S+F*Oc{P)=Y5(iF2yIM8JY>`4kP$uvb4JQ!5uc(-+7dt8iu z=G$*zxU%0u&T9JR)Vp&0Mjagr8sm)Ct7{h>$-}?+e0=Zx((3r}dHi;6Bu2G{eF43R zHdA%suED?}N$;z&t~2AcXs1&WU~4n83UZ%O@aWmms9L2PSVC{}b_JCqF*X;m1~uF9HoP8V$6(ofhO7L+r2;nE?T6n5GQ+YzvwnkO=w% zPOD@bFP!^&bZT~Q7-|8s`E~S`Caub~@d)y%rL~U&8>R`_64wbXeZ#@EhzD}?r3ZVe z-bzQPwimes-qXHCdd7P~FL+RV=y6Tfhhk;H$izdJpCfbMQ4f~pPK~tYm@7830|4BX z>d)%=rVAKA|50A9&0Py;887oq>%a#F9`PQ-IDi;raX;pk*K@B3Fa~=x25KU6BH=?& zGK{WJynC7NB%Ec42Q+b*=>O^a&!J4OWDXZ(3|72TpnsTMN}dNd8zBvts%)m=Q*-Py zf>%6a8%1x|@iaswLy0<~Y?bQjbvHhNp(l$b>JO{_0`s}c&_c-b%96mHZI z1TcWMUVChCt6ZTpwmLIfCn6O=oK{b0LyH@2NS7 zWOe+J*v9x!>0^t-2V%3k`^FvaS_qb93vS3>-{Xy~u^+(uC121vG8!LdA6neg(=-?v zl7|l~nBEUCmv3>^50GFs@b{0As3XHloRjJQKvAo_#>KWL*I^vS;yd!k7vag8p_Z_l zoiik2-aBu7n<}w&)HSxo#E-iW(Ap1eVdp0Ucopi;PY_!YuIBLAmYMfN9$2 z^~*A!Y1Xm0Si)G9bK^0j5(ml6)-J<6i7B`b4Q%1H@#<#bEl{oo_E^(HRbo3gjvm6s z&>N3YEwOCaE?#5&HaKVi2B9}en3cr7Mu6KAMX*bNKC@-RJ@yn^tiDK*p62qXkP%M=)R{)nz`fzB(2)FQecoRH0cz9F&Bv zu>~2(p%EO1Im#?7B*RxNkU; z@X2p7J?|tlj~Ec;#EF8hvm>DS-u-rE(N$7Hr;p)f3Y7D8FY3-))X-zgr*P8MgSNx- zsuH^trS~^T*%kk4S1@yd>(`z?=`i5}6J6%J{fw48cp#%6#yktpgJ*NF^Ej%YMEzWU z=r*L?8?Qw;oYT1S?^gZ}nD3A`K|`GZa44Z_BD5DV#@qwv54izu1uDQPXp3!L0M?{|L1qYG%@e^Vy9F4=!mq)$?i3=0K>no(B~Eeq^h#%9Kc zb5r$tKU5O0JmYyd=OeY(y`>6m zyf5V3!^{ob2f%I$Qk$qvc~BjxvlySvLeAG(bQhCvTM;~$42g=+4|-d?S*ohNyx;!H z8|r?C6R^ugxMWY2@~;}CfWsd)mhGBPZgO+S+q;By$en1pcNE3+yatH>1eW=viQF1( zj)vkt#f7hLI$DA-VO|(?6dlkEpodNjHs@O0qfYe)>~5(2>~~E!<>Uj%UR8!6y6+~qU&mXsxP`$q_=ESe$vc@^qN*-c3(}J^D%LYIH_4yb-v6`NN{2`q ztvn3i70p`allaKnzxaqa_U$TxqZ(>zh5z+se-z|ridPc1xed{(ALh=b*rUX2_ifV_ zt=UnxyCwTzbz#OKXLmUVSLgIuYyjJ> z=7c27L;W3@@68}mEs^t{dkm!Meu7I|pjn_}JxvzG|%2e4^!OD-G-0 zxkl!GavY)@xh(||rJ_M95ip9FO*uGTTV&d84lFA;7J`v@i2rggY+t z84N?8;2lsN6{&+537uhn&Hu)axL2AhkI($fc#z{c#GWO%Q@t1dQrq8D%dCfj1`2vL*=JYQJ~(<9B3&4#m$*b1MQ`OeLuLwIO< zIWNbGB0jfx(K+;34AcJyHAb|+^Vd~RjSsd0>IT@71q$hpxrTp6xwyCL`VoU<|G4l# zZ>bFOT{f(U3)7JQe5^PoQ$eZRTPHC64NikH{q9s(bC`KS%joZ65mdIs1MO1*0t^@f z)(H*IP%Tki#pESxkzN9V;M%>5dk7hW{KJFs29RtIWK!Nj&ykR{C!bm~O#3b_-jmO` z3tvwU_osOww%vFyhE_dvCajj)HBXUBkM+)9xNhk{hX!FBX`KJP0_-=oi{I@(O64`=+aJ-k;^rHVIAyh%N z^!kw_BgtyHl>P2_F}(ws>Fuq(icPV^zY23offWWV%->nzC5bG(LhjXRaXPRa?|8%a{;v+BTf)Pnj`58f`Esl8!`U+dz%0_UHa|^iI5zBnQ zbDHuj$q560Xw?_ z3Nf0A5cjlrdi~f-C!tM#!^s<0>oi}Tb59e?P4YO<8wTk0X5qT;-UMX3{j#^`16nIo$QqTQ~wv%ZR%gI+dt{3;J?+;Zvo(mEF=msK>l+? zx45l@x1oM=kd~eDB%&+ae1B5Z&5yVj+`S*vWowSbnEaX=mGgG|oSv(W<=o5mnFD<_ zZu$NVP_7DyhgUB1U4tqfv0d4&ROIXe10^#>KyZ&nxzDI{@0f|#bojyLLf%PFY_35LyAvU8DVml76{YdE_H(n)@_51jH1O;^y1=mlY_ zp|jaJyW3&SupfdWUK6j5*9I(ab1@ls1&yc6eCc1K8|%5v!IYP`N~8(4Tx=+0qrVRJ zd-^&VxMWGxt4-7Sw$wfr>?oLMfn$$%D&JIM+VT)c1IGnrSMApT9TS@;)-wDsT@sRCx`Xn3TNy5HDU!j}J zd{OvZ7SV1F8h>u?&az}R-F9UXwN&=AU2=e&u{ed!o>MQQ$#5NpQeu5EcYdkn@1)Qs@dz zkh2fgh3mvFlsvH#>hTqGmU8uWgQ=~) z=KBvsKI>E&g5GEd^`q9`n7u8cAoQr@LQC=?xSfHe;X}txz5VV>GO#}>VIT7pU63bI zP+lTcO$Pg1olsXiw4@RXN=Q;+u!UG5)lOW|k(aK)q`BS^#I)3(Qxsz9%TvLXgGu^Z z)ssXRy5^ZR9`GYerN0S{nN?+;*E(61?hq@5JUM9VpOEs{UJP2@C&2Y;+k; zGZ5(pSP&2a4iedR$QVoOJI-(+D;jg+xoumnae9Vmv*x=c=&$XK6mWGQQlJ%LL~V)N zpd1L~2Kkz;yr5B^ggW+#ipXWYNoDPi9#J2EPs%11X8ot&piuR(p9os##;ZmMczoM? zVQaPRqjFvPGO`f@f+re z+R2!XEhV)r;npy{1!Cw2L|42x6aSnwo!L&kXlPcMT%>;v(5C&5E`A95&PPAvg3$NJ zO4_%@@z;5-_P@$_e=g$n+QW0BzQS*%z-=L>Ho8dxMwew<9*d=0E%U+lTv#CU@LU`t zZDJ>?4xrOc+&JRF&PeobH!4gsaoyuAZu&J0mOQTGgq3ONOphs;Rc z=05sQFk3L45`zU5dQzX@v?mb2wAp(B>N{B19#A=32e|JMjJu8Qj_h)=rrU@X+}}U^&S_`dm+F+^s}xvs++B1gXL}Zgh`>y8JUe+lT@| zAq~x%Y~Hatm-OA6&4hiTG`tdL8m<^bW?s~xU$AX2FvI>WS2dr60r_pIdaMUWRuDCX z=YGVuOe}t|5=U%(kZhJy95+30VK#=bqDTo|o6VH0ympl7V(7m=qC0QAO5ap@(QyLQ z-|=Wp#cxG^z+ogahy1bP@_VX^7TslIk`BvS%4MM%e1y6>dNoJqN1N-)r(#bmEi&>1 z?WVXmg_48av0cc4=O}NvnSB5MS;3L~yuuOO+#(>x$Fg9pxPMov7Wlr>o$zC&`!6fi z|MThpVtxHqB>&f`+1>*J6>z2*dGkCD27v(oAKEk4hr;E2jz14|+y-@j%{*Q6+v6@ z#H&0EPByI7nd}WsbE=+q7yratlW`?uy~){8y`=qA>9H2X#UckMv;q}()TuvBUE?_vGhXc6j}k$vkZ|RDl-{d3CGWD6Iejw} z8Fnf{{f&(!hG^Exb!sR)k(}GYAj&#ox}98gKV=GpSo=o`yXJOb^HSU+GFuo?b`}=% zMbnsvp!oAI)d2f1f6~eb+`sHLU}O6sIr}?8F!*j!2>h5Bw)6sftik(IP9ZVCbiDKM z_W&lw1C*%-+2SmOPJ(OpdCa^`|#k-5Ru7|7I{nR&{ z6YRyE=!ogGfX?%T2b}hl4tk=FSCf1vni;!7sV;ME&XEC~y>_YY<5s_gQQz5zKkUTM zpFi@wEqDB|wa!1Bf_8Y#JT_Pze4t}`=hq%CwsxP&o$l)_!UWE20lA4|L9+=|GP!GU zlo~&co-*1V6!$7?`1y)Y3wT}IUt9|*SELI0zPvx}?{Y3x`wm}b&7jr6L`tf*?BT{^ zE{5dSau^VUl%B!tWugWU)OBDJ?hO5LSb2hNOn#11Yq^l#J}`vhXcoTytL$+x&HM2( z3t@yk+$7D-94+P}_Bmc-pE@lvOd(fcrF~3XV~&f>Lxt>0=Upog%nrGQ=~eEyp>*R& z<7($JUPrivWGGXq#*k~Jz}=7A#Or|PCO?8uf&zB%9Mn|x09ChYCnF>EYj2OeipaLE zPFq@qN%U}}aZl{?r{=bMOt0<8THBXIuP%cL^A2OkB1ryXRXQ9=Q+N!vu~rte<(bTT zckZ>b3S2t>_2kEVEyYl)O$QD8Osv(PN5C`P=#Sy+2z#ZdY-f62E|K|Wh>0oMOPm8~ z;nOE=XLfO*RE;RAS|Fx_Rom3lmae?3y{ zu3*4Zv^G?;qs7FQ+nONVlWTxDcw4te_vCSd>{vvq@7ZpYB_N)@uuQM>wNVq$2$>z zmp{pBx^!y!#kxF-Z=J!+8OfiBTKNVdF1+r8H zB~bMMd&y%AT}PSm*Hq;OVN^NW>F`H9S!O|Hj3BmH3pJ2|`jsyK%1tqr>U&vl$37*) zb#h@F(yTUK&fXJS(WPI3>lS2GG9S@WQBQV~9}sq;VUP3!%~6{z#brv)@puX<=sH=~ZOT|w?dTGB{CZe-E%$c#b!D*Kq{?j(AU2_>+Pp|q*Q zYQ53O?Q4ZQuJ4-~OSW-x^RYp3*2t;5>&Ns8kzNb=7xk)SBtV>X$#eF7X(UVSfs4NM zsQzlscBzw0u|as?&czVUc8x>cDRu@Eq!8IMv764>w9IB_S;K^>XQgGr#$9Wgmy+oM z25pMi6`tW&S_~2i*{vw42L=zbB9B`nJWTQ|e<}CVPKC>Hy{i{#3EFeQwx-Ro1 zaity}3U9|+6!VMrhwC(#+BPz6o(}0ydy_u1j$pes1bNC+hx3X;to^KS-n|}Sef{Cu z>9vz4gTEXaXhWqLl6ZYlltoVUMFU@V>KkW>C5!B7Y9uoEqx=-*#2Fei@{`=L<~;az zOM66DT+@LI)7xgv-Q??6?6(RKZQc9qWtf)&^Wf%i>o@3|+-Qu{Sn~@F1Hn+)+y@%6 z+e~lE4;jhI>TfLGqOxm+#Dm2UXFwF@z`lVg-*bRe45J}RuK)-XMsO05LmrQmFbulb zrWkNQ8vQnm;!zj6x$W82ci~5lsTElCD?9*0#ykMC@o6TC;mb{(*VX3-zJpRs*hzb- zvK6VzC@A+y9kH#GH*VjxD@`PXFNAMDdl$v9L@ldXN(}PrSVTa>7CUE;+!^vcuWLRh zzgPm=0}wvA=q{#c<({KVQy|vW^yD&}@MP3dlrQFx?NjEX@+`7NgNCw)yKJ4Coqbr2 z^qTtY?CRWACRIm*J=gfMPkZvJwYaI!?QX|g(=+8U`)g=3kLJ76QgAw^x9i>CQsshJ zXLV&xvo47RJo8wo=W~edgI>=ppdTyO+{z4MtG$M8NAthaS#nLxr+w})3qIFJ7oP6C z)~Kw~cu!{a&|E9ihH<8a3cs-yvfl9VL2{3!?XAA)360v6)JC#hq|>V+xn?P+bC(=f ztsf{ZdCbBbOcoyXF|vHNsJ0Y`ZgzX!K&wg79Zt}1@3IiA_QnU0H_P$1KxN%Q3?ltD zad%|98}wd=9Jd=EdVtp`g0t={;F;X48L|oA5_C|rlu4!|C{VS6KRnwqg*H>UXt|QE zP%&BX=u%N@l4t+sFXy7RDXt%|zFdLb<#cf~-!-z5d%RE^yg#9D9n&=#y|I-i$hKySz4HRo5o@42p#ve*;|U#)q>6I+GM(adrl_KcYv zV5G>A0`d-Gnyk0j$qDcAq}FLaw>!1|S<1B|&)P`FmZqi({_%UvTpiwyn~_M#7N&K2 zHByJ*-eH(~v*Vdf=G~;Xpj4fo{OO%0Kjsr`n+6;f%+aEFDV@+g|1ol|BC2d@07lJ! zXDIbG){5ofsH!Rzel@uo>^D(FOPe}@;k0v90Yv%o1PSJ?uIkH0=ZM{Jbi@V`1%8!V zjrW}-OXm;WSzmPhJ<*j%My})d&Wp+Rynx>q5O}8lndN*EfKvzymG&k$+Vl|%`(G#yQMR!_A>^e03 zLh#;TnxS)&S)!(5E7nJ%btGyCb#AL$$~olb0#+TquL(>ja71-k8HHEE1uz>Y;ajbl zx&t^dTRQVmrP@eLj!)sWoAp=n8~D6#Y%6vu6l)yrEi?|(muK##lgnTmAqRbbvLJ5- z+L?q=@DvynjI&A`@00M3jSW#7-x^X;oGkeRGx@EsGKEN z;DXf76;fta+Q9@g_l(s@SlW$Dw3@apn0D^imq^R}YIQ}r;(kb}m=9SYLGN9=dv^?C zisIquRy3TKk?NyHFtq?^rUEM`P6wiusMJ?vjz2Ss?Rq#mV8HAi6a)37t^{M6dx*nE znIup1GLNI3J>%>*hE4b65{m33(X0T%;jd1bzaAPS@L@VE63c3wXDAx+ZVZ?tiPY^VxP#Al1w-WDM3$OJF!Dg_8BWc(I8=+$id_;jl$Ea#u6{5HSZZM6}NfZ0iY24o}~f; zp-3|>=gv}vSDuaoa=MdSGoG9@wMfY&ex0KlB0u-$q9AoHI65i>RuhY zdfLW8Og2tT$IEfpsdqT-dZb*c2mx%AJhT9Zeabnp%m)Dz+#1*5iS!rct-pv{D*-mn zFuTV^RFoNxuNAP89k0TA?~YMsuMFI7j~O1d_1kuOMc)|>2fykL+=3)os4(0K?I*gui9%e1JHo#`&`p!2Rv~kj%U+&uVVB9;cYZk2#6#?~Is8G4; z1h8CVa22t{UY>{MCr~bU4xqX}W+uNC4}OF-{u>5rT5LW3Ezv5fi3%Io`g_shlrvJA zl?Mnvr|b^Qbm^8-cxA99@W&=oIXI&hJ_sm07r>Oe#Q{z!)EYxvI*aWs0&^o?>(3^B zJ15T?4Zv_sbGCC1ELWVd==VX=3w;9y_E5k-u2mFW#El+f z75IVmQPogC3@`>(tV?kBF|f6Z=51L2?Lv4lX)ND4hdKCGE9g3yngCIg8L(!xWCcbP z7ub&;KYxHLGUYk$am3h6VW;5fJNJNHRvT8XSFZQorBXJLz3U~fanII&z~dH$SrKe2 zO*p8%i3P zuL8=KM`W~wBAL~4TxiRQWR7DdYLfdEG+e5pdCg$Yow4N`b}x|o3lm6NN(-!1-Y}A@ zase3wWhIO3|H;VR+lK@h|S$Pizz8Eyu!a}ne8hXf1bd(ilKCIA5>Imwk0mskJ__!g9s z0mcLJa z2q+84ZX+ZKMOf}rr?0sNZgc_3l|KbG%-*8!{DBPqdlxvTaSb13Y>8Vkgx{d8zcSb( z4p@6}OZGP3HfG(J3lF%DF*`?JlSW}o-fQGaue`-5>`fT0l(@0+95k>O2j-Er$;@dy z3}cu)W@|6==>~8M2Zg->+-T|me90+g8n?@cag7`A#+4W2?b62E$5dvY@2EOwZYvj2 z8+|hBUi7Y^6$Np{*@7iQ&@N2@v`QhJSQdvI;JDbv?_nlLZjdznR3N0daW{WtSA96Q zK4N3>9cN>B|Lbf#M|NhT@ZLwS&v~8*srZ^%KxxD!Od>Zzx2RGU9p@fg_p;jPGO^kt z$lAf-)X3Jt<3_=8<+elfct~u=<;Z6yk+4?6z^(1zq+(ah1LYnOfu9}tvQimmux?7eqf zlWnsuid_+njjlumLDk35ZLKK9khzJoPy@kX^krqJ#6(x!yA|)zKS|Ys( zks5jkRZ1YCgoG5&t?&1KpT6JHwa(e={Px=W=O3W(0C_Uc+%wnATr)FVAGb<#;9b?T z7h6`8UbOJs%)raK*9)sI+BksR|5t*QBv~dQr8T;$o!tR4lWom%Y|= z&Ga1NJx9J2nvK7OK_!p8dq2>M@!V|p<&S$r4>e6Y4qJmqIl~Pe!7y+i2I-^ zzDJD-zr#mqa$qH4bk_^2AGQ5m#6ZR2!SVL{HQvow$zGRC-SV zaDFNME5mrTy5)8|d3u+PeDT{cB}?}VxcT)Wmf7?4dTfNBruZTTn{$j3ANKDHIe7B5 zwcvS3?m{HG#@u^V)MGK~a3(14C?qL+xp{rg9!*ao%g-MQ=1{klo_}^Qaw{uP-DzT2 zd%Vf9_F;Fy?v{*>>~y(4T%sZ$-Ziy7-<=Ic{5+x7Ksh6mp5-7xvDSeR z>m{rssl(Y2V0d|ceEDe<{k>4K;t(G;9VJklIhR^nI!hH8#0jI0|CQ`C`y9fUfPK@HzJ0yjJPMZ{!3YzWyY)z7}ha{cBI<2tEL7SoWw z-NDQ>SQTr{*W^EB^=v9|U26N)=BW$Rl<0)NTpmu}UtVA_Up$K#cjF44^SAzkunfdn zL}R_}CJzhuYfVx96zJx^1Ukc>fP5Vh;1wis1D{DExGwPDm!HQ5AsrB*3q?2p z7bGDOe1OsJC2|&g5phQdl`xlY*{6wto%yWYFnv=Mh zA^5z_p=p>J>*_i(Q4I zQUtoY=5TBs9iWq2absT@=f)GTk$i42d$%t6GHp5ZPgoM5k0cJH3L^0wJFYxOz0451 z`6Ec6hj(+2QB7C^zG`~g4$kyM^y3Ov7vm?nXvW)2>iKRTM4Wrxl@um~)5lY}zUu2GB0@AVTD%9se)-?G%rv2Z*)hA+!X{RcS zXXNt%_w^wiRUMmiAq@xGUA?T$N2cM2D6$S%D7~cvFKqtr!LsKE%+fa?~BOWYRWjBzWz_7j&w!dmb*girnG$;W;-)) ze8Y5cU5KV@i&eu39OUqN7AOdmbiGscw`VyUZ5~v{Oqr97`}s~L0`%=Q26T&?1YoT7YoT|sZ+HUIzS`s zYjE9PM4GQ7MxC*fcZqJ$*$INM3m1>wUcf^DR`LUi-~-kkSA0YkP!}d~474DN_>rhd zHU{guzq_p=$#`U(YoP9s7BX2F4Wb2+RO{PsP(l)Jr2UbcJx@s1ECC=7tU7~L?Yo?xZ8%`0PB)+Y&^ED5}FwthX2acoFVm))yZ~- z-9X*-{&I0MbL?7O5MM|Vc*}qd4=CEGC3~qYMhF*dSr#s?-naB55Kg6#5H)ScU;hMz zO(3rM7B+qLX}IFOvLL*GqsIw=BGA+^?*jSDr{H~fFqVr3=zt&#v_L~XYy@9<@UJ-# zrT>hjk zwtA{(pF5Hwo3Axk*`X#Ms&j%tvPv8nuv^)Hufc$Qf+z3dO73%nMisk(C4WK9PU+V< z4XSSj{M84fg`IajCx?3;V=Zb|9$Fz&nDV~SGXDgv&~G%raNV(_b7O0RBa9AeX?Vyh zt=O9@8RU(y^zsruiGYTXq&PY(2ewH^N7nRl&tn^udy~Rl zlZ@Bzebt;|seI~mzv410?fbsgF3KnBM!fAR23gSY>gSlt%*qfF^s_n2$|dcNczx8h zrp{HnPWmQJ4I=LD5#*h>v%ItzlYUc#0e7KDa5iCVJn3uchXHl@X}P^No7N$v9Tg;? z-pk#zw>|)DK*&+crCUQn2x1enhE0{>9;^){#Q<$p8O_~dPj719U4Fj;I=mu0(h}bp zPd5$GQeRui$~=~zna?brEsA&*;~;xG@~lO$)Rn5@E~!~|o$JtU0dbYXW@FWHY^)*W2@B6guxa8OGSSrEJT#9UaEQqObCqpyvbj z8QQUk?OVu!yz=p?7%r3Opz;;gRByiW2?QaB7r87UZ1;{JR$S{6v&2^r+Ml1~==UL) z`?#2{vvP{)>szbXcHY=~T0T5;kT8cVTnv|9Kspu5)fw=4kf#I*d{va|FF$uxDa6;p zhdy|$sKs7;mExo_PHxQDw?m+Y>HJD0&k=qiFUl}j;Pbk)RC{07zIJk^^&3{@K)!wZ z@+)C^i(peZSQj5DtzKJa*NZ#dlXT2qrL~67X@&y^8@}|A^lgHcPHOVyjG|419{k8C zS(DuRm{A~TwPt0dz{>MaUzE~{kYU>x@T$-C?TMBTlDfD6_I~01^LvR;LB<3C z{C}(y!T(EeFZwbF7hbkdR&!SIwqYa$Pt>(+&~#zED?CK2D@Qt;3;GX{c#R($VYyy#1N5Kxo%R0l<^9t@dkT zAq=24JV-3W;jYeAGF1Bkq&W{FR_#veFNDNE(6Jb?owiMM@1i0PNY=#f>LI^0{g+Ak!4`iJ3Bi(w=Y$}h>0HJ)0okT!IH3jTYj(IU zEFXqvn%=^HO)%44k#V>j|9*DtKdKY)jIp_u-y8MscM8qxRR#;+-cAl2&Q;CMVOw^5 z5lP`fSNOb|O5;gB5g-Yj?!3a*T)k$Y8p7YI6E^v>5~v-2y(2*N9{d?3y?@Xdft0nsD>nz z4h~`EgcXnib}SRbr=TnlKLMMe{n)Y|0Ce#uuVZO?S77$|0>$5`7KN6xb`cm>+JaJo z^F`Q217bB}dqqqh(Z#AEwT_uei;L|*`_I53|E7bJTJE_2Si3E`wx+XAEw87M9l8vZ z6D`1dO3!6?KX?b>OOZF&dIZa0MJ};!qS&_8lC!7as}_>gzKHa`r~?5dRRHA1t|uS; znZji}?o^*|l{>AD>a<}ya=r8-7xxjzkGDpG5fEIRqRuJWBQ@Rj4l1c~TKg;RnyLP& z954v}p;?eVBu^oQZ9;I|?!3ayfQM8=7JOtv+dd4lotUoQ94A0QlqItn8NA*60;tW0#}?@et+bk4LFt-L>LJ23J8$&BYdQN?f-B3>2{>% z@6Y-z;kIXAL_U#02Rf)~?c%J3lq_!k2FQ$k zQwG!sh|xbI!DIs*?;*dA3+h+`&7Vlr@H-{?HyI$CfBwiDm;l-f3<Ee5gJdHJ2N_G%USK0Y#!|Zr@e7|&7fQ#W zBG1qqoi8$6TE}o|mBeVDMkYpEO>Um3MK{~XXA9-;k=0?H@h;BJpZGM8dy$2GZP;Ca zd=>LkrL^s_i4!0@P*_JDKjbqmMS_K36IDsa?`J!)fKGrK_G8sC8u=$KFeCgjwDZ&xSm#5nxl}nr$PoEt zC7sn(*i-NNgov7|)z5%Iq`5iLw43qs`!zp1d@eAtg?f;q=l#|RrqkiRO$ z^I_int3))YwBB9v^V8l63H)l9*l~l861rp3$^9JY6W3)C>QH={9VxYzv9qzXGl4yH z4>LE2$QH_~oBte1r;b5N>Z8eVxL8fZ$@$yPVwNlBQB5y|IUq(2cJ3baCi~=iI0qZ|Pt3Cv@9uP3@)p z$||-DI25YScGM8F2H)jSM`@m2^F*;iM~u$(gk__orbjN`!-G8M-*zdd_dn&UeA0 z|L#s~Kt=-#Kn~_9(XPiHdjrfoN*Sn|uJT}0Ta4)`x{Wy2%UTjxn zYf-M=GWJd#!K1>BxUcoTq&e|f@t-=ymR_c?B7BD}KSe$73|~ED7n0b|lA5qZI_jLD z7UjL@CMGcg#_(pcX7WRUG{ z(#8*_i)N9Ib=tbqQIU;ybMW!8<|c5__C} zfiY09YGJxnwk?Wh7?AF4J((di9M*HJ1aMbMjJ#;v*C2hh@ia2N`({yEI@8&k99#4Y zKw;a!ZTNq!oKwDkx62+sx69o4Y1HicA8qr`K*>3-=`sOgt&9+$q}ZI1jLEz)77b6) ztx2^fm=6pO&J`tVX%uh zo*-uD?n2WizQ&bwkUWn6aDFET`fA#L?-5CMELp{@C~p(dIfDZ14zUOQJvapQ8}d)E zfoK$%mv`1SlShFWj@Q#V^hbumy@wr=jVzjw@Pj<*#!SJ_S><` z8N_~`AWFswGKsYy&-b&CCzY_L7eoga6_FiQ>_%$4Vz-w*b}@@P@X%so<|u~$xMwbF zki7eG&VW}oBX2tS?`5D2^v|?-zDpJAaBTOazXGYB*|W8dM1F8lqpOhA>h^@_?`~9J zTf;6Q+G8t?KrF>HyW9z?Mq|Q0&_e2feDNLD{66GGsQrjL<%kPi5$tU!!(HsJcgg_W zijteg87P_^P#lo3{RK`V!F~w65C1*%27Iue#+V?S`g!E7`p2+vMbwsg+~|JT?2(^k z{Zbik_Cha47cr#3*;_y&>k{g_Ov`Z{$Bf&jda|7gXq>`?iCX#qEuC#i1?WWdI7o!p z;JN^sygD{D`KzP?x3Ke;Fgqj$=ZNYwMqY~ZY!jew=wW@gSr`KI0pK|>ALu$I)x1sV z{w`m`Y22xw#|*Z32mqMPz5<~O$|Hw59YDFuGA{=m005smux)GO8ScpVINsarJj|r~ zU+`k}H+azmNO6hSj%CnHL^$MIor{jP1+Mg8Zt#aBnzT1WfLvj|l7dBzIcKD2g9053 z9g5eYym?4#;p*d+3hMl)OelSWmPL%rocZx@?q!cPD03mv<6koz&BuOW_l$Sc3R8n4 zR`yV-&Yf8BF8WEz!p=I>Wh~_$A+DROm3Q%ZQOqQ2{FkXbKc%WCzp#h@S%8I86aF%D z;kpbW@V`eJ93&f0qP5xt-X&ylsF>hl8xF@EO>+aF`Z?PoyLBbMIrSS@qT7J*zeRN=VKfY z9qu;PC{NRZaja(_Izl~!x#oia(LPmog3s?fa;$X_KaIx5|0=FUp214|@|lWw#PAEa z39~;%CPmc(NzEyH&Uwt3IB1e}A48BDS;OgHV*{^bK zKiEO&59zXRX%m0yFY+ePf5CE)ko+!bG6I{L{D;Q}(E2}tO1yJ`Or)_PwWsTGhj;#4 z{>VL7M2lvc*V*yOS-iufZ5OHjg!S2;xt!4+hhHR45;1ogPyM_`(7fRH)$h#qZ)tq* z#C}WTw=@)>Go@e@3!vlxoA~>r{FVkl_4B{I#_zN6vkYMKf-j#0gv+8y@lU0-hLFtn zSg705#LA7XVnyU$tr2bLsKOT!Fiu3d&tQ3Za&Bq2K2h(Du}MUkm~eJ1bLGKZCbiG1 z!^)05$+H|n^VOgr139w>&0~SW(^|u?Uw18f^@g&sR2|1z<&VDi%nz8S00@f+$>B2tq&>dwh_R7u_N(kD_< zm!KjdDyeeA#^Tj*=v@398YRpbClVI#rg83Uu-ieBox(&0V?m#6 z2br-dX)IxLL-r=!;%O(pi;nZ@b>WKyn=8Rmk&FPE=4iqRVK!bE-MT~yP2TI9}N#Rl)n&SX#6`HSx#zdBdJ2< zkbj^!m&-%(jX5uc3SdI_F3g5zWT|i)C)xE{oJ`3jqYE!PwxfarZ(Z+)1?Hf_-VzIw-V?^=x1E*@y#j8T& z@`s)GOnddz)}qb20tS~9P~TBeA@*CbTLwrN0C`2-t=jn6-- zxR4h;dSOTC(g;=Qe$6hmR^{8uZ!aF)ymy+s3?y$4=q*b3A(jedS(F%EBG zdb{Wk<@67g$!vS1)YzwyF(P%maeGH<{cziIZjiZ{lajNbe~iEHRb3lDrm9Pztf8w^Qtv&gDgqFvN$YubYNDsa0``Rmq=t zL!q##g1%#zBZ9YZC-{0HrNjHqEMzHPg=4oQ6_uV;Jz(n;mwP8~i>4QT$#t=`TeX*! zKUP_qyBu5T%~hq%wwCK|&(n_^dj~&#g;)!L3M;`M0WAgmyD)o1}Jgwx5}?g*9EO2KY*O3-H|-zLVHM($~v(YMt# zR=i>nzYV_aIO&A+t=`^?F-EUS2{~UxJSPf>9JuCMmvh7nT0k0a*SH^yg=DJ;;+p!0 zKhPk28&+~T4%#_NjYFUmi((4~JJve7v7UCSXp%p^*2eI`=wilRDXH}2)u*$YYs7VO z+N@yxrgs28-yz6gJBN0f)iPN|-1J#nCiQvI%cykK^!wIZk6sU5zlgEya({L0u8sX7 zm~7rEj<$I-{L18=gq=)Wgq~radXT~DRrJmkT6IRm`e!LvM#g2cg@!r$w^LTs)ZE^9 zX~oODM~K$Cd28WH;~__VNzIOt05D`xO=vh4D9(xN-$4@R?-#rx?ai%(M9fo5(_#CI z8q>1;^|g^UdkX^Yu8ch+t=RRtA>-Jq;>)z@wfIkjc5`}VDSWAqojD6qLcU`~DXWt& zTX#6V4uQa1p0RD)sZr8UDHYEDB2s9k?I2yTqhv>uIog9jbKq*6_KH)UL>IV^=EJ6- z^yJd?L+p7@ME@6&P^1LrQ0f8ZSn0m<`apW!O4EYq8VTO^(r)9-#Rag`^>blw%{tef zoJWr5rSQS958hFqOtu~?IK+i-O|SB}&w>O_5@~e>K~+^-{e6)n>5xquq^d1W@)n!T z-A6x<&Y#H^@=#z}s3#KU)ml$!W%KSeB?sUeJ76qRwm4_7KSGK9gf(1h8nQ2fF>$Lu z!t{*r!z1?K@E=7D6X$BtOWDrXKXC2)W!Enn5}%>*H{kjvL-p1(gDUC7QpaRd8O;7X zlt1#_v5R@371h`VU(LOnUx~gBciWj!sb}e_yuC3HIb;N~n1fuWb6fnPU(!mtg-9yx z+1C?T%99-ROUUM_fDN^t@zumhm#hapXD1XKlcK_Mj0>VaVqcOHGD4fV+vx3WB@ril zc6ftTVwt=DZJ*fh4UT_xB>evOf6InW$d?8|JZO+RrNm3-A}Tt_(46JcRreTC+q`wP zPXq*@s5P!`iCw+p^SI;F?ke!?QxRDP)?nhO$w3*Iy*+Dq zoAWj9sdsJ-DA)+^{y;IcVxt_gM=PUW#1Nhhnbq7^cyUi3OB4Koo4^ku7h#*=Q{IGk zGy#rt4>X!xB$A$)z1^C7@+q{2v|o^fU5c>)JH?jKgUiUb^XsEtfO}p2viYS&>(+!T z+fbaukX3ij-xarp?;Hq6R1_&6w0Y#vVvdSM6<#9EH0mMPU9DR=NY)tJi^J+$n^}^u zsbzN6u2b(TB`Qw`u2sZBYFD57(9Hg_ zVT_}zN=SyTfwuZZO}BkJ${XV6TxM+&#`uH>F(=L{zqOrBpGIWzJ#DQ=FY~RL<-<2| z=ke<}+NVTpKI0%mZyzjGTb$G39m}~q5K(i@#4mW92qybdn90I&XYiI$3)jy zwZjFueEdz^)pV!M-b%({rYt*>rLtO2F}WJ|%*oqHJIvgFecFlITh|vy>ulE+j|f&0 znNMaMqEmw8)R;-Rm8;zQgG2Z$=}&f+ayV6Gok1k%bUyIcPcy)`r1d$%9 zPODsK9|(^Aj*IUsX%P(8#Dj;2~|iY@b&;Z!t!eMJ`kIKIMVil<~|p)v2NObsg#=L(h_GZm8p~qP%o4 z;$8I8lY1`^l5kfh18sFc$Gra5&i+b<>9cb_=7^%~ye3<9F{{bE_Y1;Ro!IvtT5rME zNirX=&$^2{J3G0IV_qhdde20Jc!b#;+Fx+p+SqQlGP`d1htpG49SeJ%KlF=)uYnEW zMX)8%clc7cxV2AHiS5Fh`nKg|IgQ;mHZ+xWu%&0^BS#(W5JyCwGrC^bAJuADL3H%a zZnE(*a?TbsPtsln?QznMWTPgX=Q}OaJ5#2YF#?#K`#CAROF{|`g0awQbj*(=q~Cdw z`6S(C*W#rXFlByi?19H_M-2VuDRk5E8_d*RT3A1ed}~sx+H?E)*d^8i%f5+KnPPh` z-eo97LY&yD{&Z~+0<6azVA9I<-H-81L-pi0eYCO}CTIpcQTtT6b1JILG|x!d znHj5)tfz^&bHr(9;qljZbdX8UIr7u8!WG$l@D*1Q7I1F$+FTx5rCTu%4b48A{nA5;$=W|@ZIoS~b1AXkNAEw_?lnx%NE<;{B#ml=KX zgU%o6bCAMK)(UHpE*MKe82K_T0;`CY;{U-sF4@ubS^`zz9~Qqu>4T_PMeCW{X(A%K zZhjEibt9Aw5#uXy+k{AWwK<@?7q^&WM~F zY@)U6g4yjZ7<3tmRa;1* zcG+3VN_QqY%KHz;tg|V;|Lm;wK)sJXl(C7KT4}J0F5WZh$`PPb0 z3kJ?#lJlEJ>iaBWuMynGN^@R!RwS%rZdg0YR#VK09UYzEo{u`CS-sKQ+*l@m=Mt#P zpuF;d#K+Mo*vHiw#6R+#1J**NH=boGISgcXJ(LPuFq^5tb zC$ZKv&~`U-?gO-w+An$CU@Ju~ShC#w<>M_> zq4tD6`*jMI*OY7SCb}#?WaoQ$;4WlrTRiGj8(%;BZL5MKnlY0VN$(EJJbqVX@WE8s z8!%CLkCO&(q z!O(SOvF1eNVdc{1wKtWOO#D}v;gO}rib6Ycm)9w3goqCG)Tgh6$eTd-s~>X^Lb4W8 zpc+d)fT9Sc_^M23F>#rd&mwwG?CyZ2<%7|(7j`!>9J{Kw&~&S})N1Ixtvqm}+;0im z5>qcUp3+EKZGqP26c8k^-B6iXbRcrMWJ%A`Zf!hPzKa+rZ3gUNa^@p!`QTi zZ+P$Ye0!;x!g})3Ec|u&Qoa^5*9a!>xaUK6{Zo9FZ4IXOPF{8CQR^#8gXh%u!=AFB z#k62KRn9FsITS13P(F8uzd3VTjK_4nMBBL?XP4i&v?g;6cKZ3w&1()Ul6z=wGF6_D zKK3~W8HTRo%3DQF?6?}&H;40#=FY+QkwiJxY(35m;Q9Lo%aa53K>4}DC4IM5n!&cv z9(vn^r~i|ElD-E!s~<~>rf0o6zetQbHy-tM9T_x-S_xmlUvs6hv?@)-J};;)x6Rs# zcYpUuXYT2R4zh^AEqBjvJ~d0MfVNTC*Eo}XOIYYK+;T(j^zr3$u}u_*-8HYpG5s!| z6_rIq&uN?#S+)`J3ZuwTWtR3j1mWmwIf$nRo{ppqon34{5bFq$9eQ-I@n~DGq}`1V zA4E=n%JM}ws-C?%kv?VPOndcKZaOAEYhuodHZ+gSY~vt>D~9`?z>29zakv9~{W-

    !b*}H#vq?_wzp`ZDy)Q>>UOWaY$|GV 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

    ![LSTM](../../../doc/demo/sentiment_analysis/lstm.png)
    +
    图表 1. LSTM [3]
    + +情感分析是自然语言理解中最典型的问题之一。 它的目的是预测在一个序列中表达的情感态度。 通常, ,仅仅是一些关键词,如形容词和副词,在预测序列或段落的情感中起主要作用。然而有些评论上下文非常长,例如 IMDB的数椐集。 我们只所以使用LSTM来执行这个任务是因为其改进的设计并且具有门机制。 首先,它能够从词级到具有可变上下文长度的上下文级别来总结表示。 第二,它可以在句子级别利用可扩展的上下文, 而大多数方法只是利用n-gram级别的知识。第三,它直接学习段落表示,而不是组合上下文级别信息。 + +在本演示中,我们提供两个网络,即双向LSTM和三层堆叠LSTM。 + +#### 双向LSTM + +图2是双向LSTM网络,后面连全连接层和softmax层。 + +
    ![BiLSTM](../../../doc/demo/sentiment_analysis/bi_lstm.jpg)
    +
    图 2. Bidirectional-LSTM
    + +#### Stacked-LSTM +图3是三层LSTM结构。图的底部是word embedding(对文档处理后形成的单词向量)。 接下来,连接三个LSTM隐藏层,并且第二个是反向LSTM。然后提取隐藏LSTM层的所有时间步长的最大词向量作为整个序列的表示。 最后,使用具有softmax激活的全连接前馈层来执行分类任务。 更多内容可查看参考文献 [5]。 + +
    ![StackedLSTM](../../../doc/demo/sentiment_analysis/stacked_lstm.jpg)
    +
    图 3. Stacked-LSTM for sentiment analysis
    + +**配置** + +进入`demo/sentiment` 目录 , `trainer_config.py` 是一个配置文件的例子, 其中包含算法和网络配置。第一行从`sentiment_net.py`中导出预定义的网络。 + +trainer_config.py: + +```python +from sentiment_net import * + +data_dir = "./data/pre-imdb" +# whether this config is used for test +is_test = get_config_arg('is_test', bool, False) +# whether this config is used for prediction +is_predict = get_config_arg('is_predict', bool, False) +dict_dim, class_dim = sentiment_data(data_dir, is_test, is_predict) + +################## Algorithm Config ##################### + +settings( + batch_size=128, + learning_rate=2e-3, + learning_method=AdamOptimizer(), + regularization=L2Regularization(8e-4), + gradient_clipping_threshold=25 +) + +#################### Network Config ###################### +stacked_lstm_net(dict_dim, class_dim=class_dim, + stacked_num=3, is_predict=is_predict) +#bidirectional_lstm_net(dict_dim, class_dim=class_dim, is_predict=is_predict) +``` + +* **数椐定义**: + * get\_config\_arg(): 获取通过 `--config_args=xx` 设置的命令行参数。 + * 定义训练数椐和测试数椐提供者, 这里使用了PaddlePaddle的Python接口来加载数椐。想了解更多细节可以参考PyDataProvider部分的文档 + +* **算法配置**: + * 使用随机梯度下降(sgd)算法。 + * 使用 adam 优化。 + * 设置batch size大小为128。 + * 设置平均sgd窗口。 + * 设置全局学习率。 +* **网络配置**: + * dict_dim: 获取字典维度。 + * class_dim: 设置类别数,IMDB有两个标签,即正面评价标签和负面评价标签。 + * `stacked_lstm_net`: 预定义网络如图3所示,默认情况下使用此网络 + * `bidirectional_lstm_net`: 预定义网络,如图2所示。 + +**训练** + +首先安装PaddlePaddle。 然后使用下面的脚本 `train.sh` 来开启本地的训练。 + +``` +cd demo/sentiment/ +./train.sh +``` + +train.sh: + +``` +config=trainer_config.py +output=./model_output +paddle train --config=$config \ + --save_dir=$output \ + --job=train \ + --use_gpu=false \ + --trainer_count=4 \ + --num_passes=10 \ + --log_period=20 \ + --dot_period=20 \ + --show_parameter_stats_period=100 \ + --test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' +``` + +* \--config=$config: 设置网络配置。 +* \--save\_dir=$output: 设置输出路径以保存训练完成的模型。 +* \--job=train: 设置工作模式为训练。 +* \--use\_gpu=false: 使用CPU训练,如果你安装GPU版本的PaddlePaddle,并想使用GPU来训练设置为true。 +* \--trainer\_count=4:设置线程数(或GPU个数)。 +* \--num\_passes=15: 设置pass,PaddlePaddle中的一个pass意味着对数据集中的所有样本进行一次训练。 +* \--log\_period=20: 每20个batch打印一次日志。 +* \--show\_parameter\_stats\_period=100: 每100个batch打印一次统计信息。 +* \--test\_all_data\_in\_one\_period=1: 每次测试都测试所有数据。 + +如果运行成功,输出日志保存在路径 `demo/sentiment/train.log`中,模型保存在目录`demo/sentiment/model_output/`中。 输出日志说明如下: + +``` +Batch=20 samples=2560 AvgCost=0.681644 CurrentCost=0.681644 Eval: classification_error_evaluator=0.36875 CurrentEval: classification_error_evaluator=0.36875 +... +Pass=0 Batch=196 samples=25000 AvgCost=0.418964 Eval: classification_error_evaluator=0.1922 +Test samples=24999 cost=0.39297 Eval: classification_error_evaluator=0.149406 +``` +- Batch=xx: 表示训练了xx个Batch。 +- samples=xx: 表示训练了xx个样本。。 +- AvgCost=xx: 从第0个batch到当前batch的平均损失。 +- CurrentCost=xx: 最新log_period个batch处理的当前损失。 +- Eval: classification\_error\_evaluator=xx: 表示第0个batch到当前batch的分类错误。 +- CurrentEval: classification\_error\_evaluator: 最新log_period个batch的分类错误。 +- Pass=0: 通过所有训练集一次称为一遍。 0表示第一次经过训练集。 + +默认情况下,我们使用`stacked_lstm_net`网络,当传递相同的样本数时,它的收敛速度比`bidirectional_lstm_net`快。如果要使用双向LSTM,只需删除最后一行中的注释并把“stacked_lstm_net”注释掉。 + +## 测试模型 + +测试模型是指使用训练出的模型评估已标记的验证集。 + +``` +cd demo/sentiment +./test.sh +``` + +test.sh: + +```bash +function get_best_pass() { + cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ + sed -r 'N;s/Test.* error=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' | \ + sort | head -n 1 +} + +log=train.log +LOG=`get_best_pass $log` +LOG=(${LOG}) +evaluate_pass="model_output/pass-${LOG[1]}" + +echo 'evaluating from pass '$evaluate_pass + +model_list=./model.list +touch $model_list | echo $evaluate_pass > $model_list +net_conf=trainer_config.py +paddle train --config=$net_conf \ + --model_list=$model_list \ + --job=test \ + --use_gpu=false \ + --trainer_count=4 \ + --config_args=is_test=1 \ + 2>&1 | tee 'test.log' +``` + +函数`get_best_pass`依据分类错误率获得最佳模型进行测试。 在本示例中,我们默认使用IMDB的测试数据集作为验证。 与训练不同,它需要在这里指定`--job = test`和模型路径,即`--model_list = $model_list`。如果运行成功,日志将保存在“demo / sentiment / test.log”的路径中。例如,在我们的测试中,最好的模型是`model_output / pass-00002`,分类误差是0.115645,如下: + +``` +Pass=0 samples=24999 AvgCost=0.280471 Eval: classification_error_evaluator=0.115645 +``` + +## 预测 + +`predict.py`脚本提供了一个预测接口。在使用它之前请安装PaddlePaddle的python api。 预测IMDB的未标记评论的一个实例如下: + +``` +cd demo/sentiment +./predict.sh +``` +predict.sh: + +``` +#Note the default model is pass-00002, you shold make sure the model path +#exists or change the mode path. +model=model_output/pass-00002/ +config=trainer_config.py +label=data/pre-imdb/labels.list +python predict.py \ + -n $config\ + -w $model \ + -b $label \ + -d data/pre-imdb/dict.txt \ + -i data/aclImdb/test/pos/10007_10.txt +``` + +* `predict.py`: 预测接口脚本。 +* -n $config : 设置网络配置。 +* -w $model: 设置模型路径。 +* -b $label: 设置标签类别字典,这个字典是整数标签和字符串标签的一个对应。 +* -d data/pre-imdb/dict.txt: 设置字典文件。 +* -i data/aclImdb/test/pos/10014_7.txt: 设置一个要预测的示例文件。 + +注意应该确保默认模型路径`model_output / pass-00002`存在或更改为其它模型路径。 + +本示例的预测结果: + +``` +Loading parameters from model_output/pass-00002/ +./data/aclImdb/test/pos/10014_7.txt: predicting label is pos +``` +我们真诚地感谢您的关注,并欢迎您来参与贡献。 + +## 参考文档 +[1] Brendan O'Connor, Ramnath Balasubramanyan, Bryan R. Routledge, and Noah A. Smith. 2010. [From Tweets to Polls: Linking Text Sentiment to Public Opinion Time Series](http://homes.cs.washington.edu/~nasmith/papers/oconnor+balasubramanyan+routledge+smith.icwsm10.pdf). In ICWSM-2010.
    +[2] Johan Bollen, Huina Mao, Xiaojun Zeng. 2011. [Twitter mood predicts the stock market](http://arxiv.org/abs/1010.3003), Journal of Computational Science.
    +[3] Alex Graves, Marcus Liwicki, Santiago Fernan- dez, Roman Bertolami, Horst Bunke, and Ju ̈rgen Schmidhuber. 2009. [A novel connectionist system for unconstrained handwriting recognition. IEEE Transactions on Pattern Analysis and Machine In- telligence](http://www.cs.toronto.edu/~graves/tpami_2009.pdf), 31(5):855–868.
    +[4] Zachary C. Lipton, [A Critical Review of Recurrent Neural Networks for Sequence Learning](http://arxiv.org/abs/1506.00019v1), arXiv:1506.00019.
    +[5] Jie Zhou and Wei Xu; [End-to-end Learning of Semantic Role Labeling Using Recurrent Neural Networks](http://www.aclweb.org/anthology/P/P15/P15-1109.pdf); ACL-IJCNLP 2015.
    -- GitLab From 978a6e5e9b2d64ac6c5007eb51846d852ece623a Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 15 Nov 2016 23:53:15 +0800 Subject: [PATCH 0033/1503] Update gpu profiling docs --- doc/optimization/gpu_profiling.rst | 52 ++++++++++++++++++++++++++++- doc/optimization/nvvp2.png | Bin 0 -> 495117 bytes doc/optimization/nvvp3.png | Bin 0 -> 253700 bytes doc/optimization/nvvp4.png | Bin 0 -> 283198 bytes 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 doc/optimization/nvvp2.png create mode 100644 doc/optimization/nvvp3.png create mode 100644 doc/optimization/nvvp4.png diff --git a/doc/optimization/gpu_profiling.rst b/doc/optimization/gpu_profiling.rst index 583c2d6cae..efdf5552c3 100644 --- a/doc/optimization/gpu_profiling.rst +++ b/doc/optimization/gpu_profiling.rst @@ -8,6 +8,7 @@ This tutorial will guide you step-by-step through how to conduct profiling and p - How to do profiling? - Profile tools - Hands-on Tutorial +- Profiling tips What's profiling? ================= @@ -68,10 +69,59 @@ respectively to avoid program crashes when CPU version of PaddlePaddle invokes t Hands-on Approach ================= +To use this command line profiler :code:`nvprof`, you can simply issue the command: + +.. code-block:: bash + + nvprof ./paddle/math/tests/test_GpuProfiler + +Then, you can get the following profiling result: + .. image:: nvprof.png :align: center :scale: 30% +For visual profiler :code:`nvvp`, you can either import the output of :code:`nvprof –o ...` or +run application through GUI. + .. image:: nvvp1.png :align: center - :scale: 30% \ No newline at end of file + :scale: 30% + +From the perspective of kernel functions, :code:`nvvp` can even illustrate why does an operation take a long time? +As shown in the following figure, kernel's block usage, register usage and shared memory usage from :code:`nvvp` +allow us to fully utilize all warps on the GPU. + +.. image:: nvvp2.png + :align: center + :scale: 30% + +From the perspective of application, :code:`nvvp` can give you some suggestions to address performance bottleneck. +For instance, some advice in data movement and compute utilization from the below figure can guide you to tune performance. + +.. image:: nvvp3.png + :align: center + :scale: 30% + +.. image:: nvvp4.png + :align: center + :scale: 30% + +Profiling tips +============== + +- The :code:`nvprof` and :code:`nvvp` output is a very good place to start +- The timeline is a good place to go next +- Only dig deep into a kernel if it’s taking a significant amount of your time. +- Where possible, try to match profiler output with theory. + 1) For example, if I know I’m moving 1GB, and my kernel takes 10ms, I expect the profiler to report 100GB/s. + 2) Discrepancies are likely to mean your application isn’t doing what you thought it was. +- Know your hardware: If your GPU can do 6 TFLOPs, and you’re already doing 5.5 TFLOPs, you won’t go much faster! + + +Profiling is a key step in optimisation. Sometimes quite simple changes can lead to big improvements in performance. +Your mileage may vary! + +Reference +========= +Jeremy Appleyard, `GPU Profiling for Deep Learning `_, 2015 diff --git a/doc/optimization/nvvp2.png b/doc/optimization/nvvp2.png new file mode 100644 index 0000000000000000000000000000000000000000..177c9db708da6863d1075f3e615f5962dbe18b29 GIT binary patch literal 495117 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%0D46CJAvp3iBP*2C`q}xx8 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�Bd8$Gpa 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

    MHup0L%5(@%itZkb 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 J9~Qi^{|{Hm!2$pP literal 0 HcmV?d00001 diff --git a/doc/optimization/nvvp4.png b/doc/optimization/nvvp4.png new file mode 100644 index 0000000000000000000000000000000000000000..51f2f3e183295de6cf8ddaf2b3b8a0862aa35f01 GIT binary patch literal 283198 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 Date: Wed, 16 Nov 2016 00:13:17 +0800 Subject: [PATCH 0034/1503] Replace md to rst for doc index --- doc/index.md | 24 ------------------------ doc/index.rst | 1 + 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 doc/index.md diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 9c5ebba9be..0000000000 --- a/doc/index.md +++ /dev/null @@ -1,24 +0,0 @@ -PaddlePaddle Documentation -========================== - -User Guide ----------- -* [Introduction](introduction/index.md) -* [Quick Start](demo/quick_start/index_en.md) -* [Build and Installation](build/index.rst) -* [Contribute Code](build/contribute_to_paddle.md) -* [User Interface](ui/index.md) -* [Model Config Interface](ui/api/trainer_config_helpers/index.md) -* [Example and Demo](demo/index.md) -* [Cluster Train](cluster/index.md) - -Development Guide ------------------ -* [Layer Documents](layer.md) -* [Writing New Layers](dev/new_layer/index.rst) -* [Source Code Documents](source/index.md) -* [GPU Profiling Documents](optimization/index.rst) - -Algorithm Tutorial ------------------- -* [RNN Configuration](algorithm/rnn/rnn.rst) diff --git a/doc/index.rst b/doc/index.rst index 668ad75a90..76fb7a3ace 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,3 +8,4 @@ PaddlePaddle Documentation user_guide.rst dev/index.rst algorithm/index.rst + optimization/index.rst -- GitLab From 25d7ad0de75c8c34eebb3d4927c1ea9e3c82bf67 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 18:36:37 +0800 Subject: [PATCH 0035/1503] to loop --- demo/semantic_role_labeling/db_lstm.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/demo/semantic_role_labeling/db_lstm.py b/demo/semantic_role_labeling/db_lstm.py index 943076d914..8788ecced8 100644 --- a/demo/semantic_role_labeling/db_lstm.py +++ b/demo/semantic_role_labeling/db_lstm.py @@ -120,24 +120,19 @@ emb_para = ParameterAttribute(name='emb', initial_std=0., learning_rate=0.) std_0 = ParameterAttribute(initial_std=0.) std_default = ParameterAttribute(initial_std=default_std) -word_embedding = embedding_layer(size=word_dim, input=word, param_attr=emb_para) predicate_embedding = embedding_layer(size=word_dim, input=predicate, param_attr=ParameterAttribute(name='vemb',initial_std=default_std)) - -ctx_n2_embedding = embedding_layer(size=word_dim, input=ctx_n2, param_attr=emb_para) -ctx_n1_embedding = embedding_layer(size=word_dim, input=ctx_n1, param_attr=emb_para) -ctx_0_embedding = embedding_layer(size=word_dim, input=ctx_0, param_attr=emb_para) -ctx_p1_embedding = embedding_layer(size=word_dim, input=ctx_p1, param_attr=emb_para) -ctx_p2_embedding = embedding_layer(size=word_dim, input=ctx_p2, param_attr=emb_para) mark_embedding = embedding_layer(name='word_ctx-in_embedding', size=mark_dim, input=mark, param_attr=std_0) -all_emb=[word_embedding, predicate_embedding, ctx_n2_embedding, ctx_n1_embedding, ctx_0_embedding, - ctx_p1_embedding, ctx_p2_embedding, mark_embedding] +word_input=[word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2] +emb_layers = [embedding_layer(size=word_dim, input=x, param_attr=emb_para) for x in word_input] +emb_layers.append(predicate_embedding) +emb_layers.append(mark_embedding) hidden_0 = mixed_layer( name='hidden0', size=hidden_dim, bias_attr=std_default, - input=[ full_matrix_projection(input=emb, param_attr=std_default ) for emb in all_emb ]) + input=[ full_matrix_projection(input=emb, param_attr=std_default ) for emb in emb_layers ]) mix_hidden_lr = 1e-3 -- GitLab From dc283d8209013c172f4cc18a6fcbb4e1eb39402c Mon Sep 17 00:00:00 2001 From: gangliao Date: Wed, 16 Nov 2016 18:59:10 +0800 Subject: [PATCH 0036/1503] Fix shuf/gshuf choose bug #487 --- demo/quick_start/preprocess.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/quick_start/preprocess.sh b/demo/quick_start/preprocess.sh index 58a72147c5..c9190e2dd2 100755 --- a/demo/quick_start/preprocess.sh +++ b/demo/quick_start/preprocess.sh @@ -23,7 +23,7 @@ set -e export LC_ALL=C UNAME_STR=`uname` -if [[ ${UNAME_STR} == 'Linux' ]]; then +if [ ${UNAME_STR} == 'Linux' ]; then SHUF_PROG='shuf' else SHUF_PROG='gshuf' -- GitLab From 2b1918d37f7de50deb6c1956a2f5fd0474108f4b Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 19:04:17 +0800 Subject: [PATCH 0037/1503] add training curve --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index 69378d0d4e..5ade1ee8d6 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -144,7 +144,10 @@ paddle train \ - \--load_missing_parameter_strategy=rand: random initialization unexisted parameters -After training, the models will be saved in directory `output`. +After training, the models will be saved in directory `output`. Our training curve is as following: +

    +![pic](./curve.jpg) +
    ### Run testing The script for testing is `test.sh`, user just need to execute: -- GitLab From b27f6431292ccbd8bd07d3e19ae1950b3a686643 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 19:08:26 +0800 Subject: [PATCH 0038/1503] add training curve picture --- doc/demo/semantic_role_labeling/curve.jpg | Bin 0 -> 53277 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/demo/semantic_role_labeling/curve.jpg diff --git a/doc/demo/semantic_role_labeling/curve.jpg b/doc/demo/semantic_role_labeling/curve.jpg new file mode 100644 index 0000000000000000000000000000000000000000..baa35ae7f0a0b6c246f3a0d331735477ab8bcd70 GIT binary patch literal 53277 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 Date: Wed, 16 Nov 2016 19:14:52 +0800 Subject: [PATCH 0039/1503] =?UTF-8?q?modify=20pic=E2=80=98s=20size?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index 5ade1ee8d6..19776c272e 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -146,7 +146,7 @@ paddle train \ After training, the models will be saved in directory `output`. Our training curve is as following:
    -![pic](./curve.jpg) +![pic](./curve.jpg =100)
    ### Run testing -- GitLab From 8b92238f2329301242c6485559c968b1b1af9fa5 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 19:19:06 +0800 Subject: [PATCH 0040/1503] del pic's size setting --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index 19776c272e..5ade1ee8d6 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -146,7 +146,7 @@ paddle train \ After training, the models will be saved in directory `output`. Our training curve is as following:
    -![pic](./curve.jpg =100) +![pic](./curve.jpg)
    ### Run testing -- GitLab From f610f6d0930d4f1b473cd784b028ae558f8be3b0 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 19:27:18 +0800 Subject: [PATCH 0041/1503] revise test.sh --- demo/semantic_role_labeling/test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/semantic_role_labeling/test.sh b/demo/semantic_role_labeling/test.sh index 804f722e5b..0fb9805288 100644 --- a/demo/semantic_role_labeling/test.sh +++ b/demo/semantic_role_labeling/test.sh @@ -36,5 +36,6 @@ paddle train \ --job=test \ --use_gpu=false \ --config_args=is_test=1 \ + --test_all_data_in_one_period=1 \ 2>&1 | tee 'test.log' -- GitLab From 65026140895d4cba355eb53734480bad85bf1443 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Wed, 16 Nov 2016 19:29:51 +0800 Subject: [PATCH 0042/1503] revise srl doc --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index 5ade1ee8d6..add2e54fad 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -167,6 +167,7 @@ paddle train \ - \--model_list=$model_list.list: model list file - \--job=test: indicate the test job - \--config_args=is_test=1: flag to indicate test + - \--test_all_data_in_one_period=1: test all data in 1 period ### Run prediction -- GitLab From 496d64ebdb3f0c81f4684b9f48fdef90f9d94547 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 16 Nov 2016 17:53:31 +0800 Subject: [PATCH 0043/1503] Support rectangle input for CNN --- paddle/gserver/layers/BatchNormBaseLayer.cpp | 13 +- paddle/gserver/layers/BatchNormBaseLayer.h | 5 +- paddle/gserver/layers/BilinearInterpLayer.cpp | 6 +- paddle/gserver/layers/ConvBaseLayer.cpp | 15 +- paddle/gserver/layers/ConvOperator.cpp | 13 +- paddle/gserver/layers/ConvProjection.cpp | 2 +- paddle/gserver/layers/DataLayer.cpp | 4 +- paddle/gserver/layers/ExpandConvBaseLayer.cpp | 30 +-- paddle/gserver/layers/MaxOutLayer.cpp | 6 +- paddle/gserver/layers/NormLayer.cpp | 3 + paddle/gserver/layers/NormLayer.h | 2 +- paddle/gserver/layers/NormProjectionLayer.cpp | 2 +- .../layers/SpatialPyramidPoolLayer.cpp | 13 +- paddle/gserver/tests/img_pool_a.conf | 2 - paddle/gserver/tests/test_LayerGrad.cpp | 63 +++-- paddle/parameter/Argument.cpp | 2 + paddle/trainer/tests/test_config.conf | 1 - proto/ModelConfig.proto.m4 | 40 +-- python/paddle/trainer/config_parser.py | 238 +++++++++--------- .../paddle/trainer_config_helpers/layers.py | 26 +- .../configs/protostr/img_layers.protostr | 13 + .../protostr/img_trans_layers.protostr | 11 + .../configs/protostr/projections.protostr | 2 + .../protostr/test_bilinear_interp.protostr | 26 +- .../configs/protostr/test_maxout.protostr | 56 +++-- .../configs/protostr/test_spp_layer.protostr | 12 +- .../tests/configs/test_bilinear_interp.py | 2 +- .../tests/configs/test_maxout.py | 13 +- .../tests/configs/test_spp_layer.py | 8 +- 29 files changed, 360 insertions(+), 269 deletions(-) diff --git a/paddle/gserver/layers/BatchNormBaseLayer.cpp b/paddle/gserver/layers/BatchNormBaseLayer.cpp index 8052b35ec6..7bf4c1fd5e 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.cpp +++ b/paddle/gserver/layers/BatchNormBaseLayer.cpp @@ -61,15 +61,10 @@ bool BatchNormBaseLayer::init(const LayerMap& layerMap, void BatchNormBaseLayer::calFeatureMapSize() { const ImageConfig& conf = config_.inputs(0).image_conf(); - if (inputLayers_[0]->getOutput().getFrameHeight() == 0 && - inputLayers_[0]->getOutput().getFrameWidth() == 0) { - imgSize_ = conf.img_size(); - imageH_ = imgSize_; - imageW_ = imgSize_; - } else { - imageH_ = inputLayers_[0]->getOutput().getFrameHeight(); - imageW_ = inputLayers_[0]->getOutput().getFrameWidth(); - } + imageH_ = inputLayers_[0]->getOutput().getFrameHeight(); + imageW_ = inputLayers_[0]->getOutput().getFrameWidth(); + if (imageH_ == 0) imageH_ = conf.img_size_y(); + if (imageW_ == 0) imageW_ = conf.img_size(); imgPixels_ = imageH_ * imageW_; getOutput().setFrameHeight(imageH_); getOutput().setFrameWidth(imageW_); diff --git a/paddle/gserver/layers/BatchNormBaseLayer.h b/paddle/gserver/layers/BatchNormBaseLayer.h index 2302d1a8e0..4ea493b5f5 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.h +++ b/paddle/gserver/layers/BatchNormBaseLayer.h @@ -78,9 +78,8 @@ protected: MatrixPtr savedMean_; MatrixPtr savedInvVar_; - /// Height or width of input image feature, now height is equal to width. - /// imgSize is 1 if the input is fully-connected layer. - int imgSize_; + /// Height or width of input image feature. + /// Both of them are 1 if the input is fully-connected layer. int imageH_; int imageW_; /// Height * Width. diff --git a/paddle/gserver/layers/BilinearInterpLayer.cpp b/paddle/gserver/layers/BilinearInterpLayer.cpp index ac5f87be7a..64d3046b56 100644 --- a/paddle/gserver/layers/BilinearInterpLayer.cpp +++ b/paddle/gserver/layers/BilinearInterpLayer.cpp @@ -26,15 +26,15 @@ size_t BilinearInterpLayer::getSize() { const BilinearInterpConfig& conf = config_.inputs(0).bilinear_interp_conf(); if (inImgH_ == 0) { - inImgH_ = conf.img_size_y(); + inImgH_ = conf.image_conf().img_size_y(); } if (inImgW_ == 0) { - inImgW_ = conf.img_size_x(); + inImgW_ = conf.image_conf().img_size(); } outImgH_ = conf.out_size_y(); outImgW_ = conf.out_size_x(); - numChannels_ = conf.num_channels(); + numChannels_ = conf.image_conf().channels(); CHECK(outImgH_ > 0 && outImgW_ > 0); CHECK(inImgH_ > 0 && inImgW_ > 0); diff --git a/paddle/gserver/layers/ConvBaseLayer.cpp b/paddle/gserver/layers/ConvBaseLayer.cpp index 6bc3b3b801..8f358a5e41 100644 --- a/paddle/gserver/layers/ConvBaseLayer.cpp +++ b/paddle/gserver/layers/ConvBaseLayer.cpp @@ -37,11 +37,13 @@ bool ConvBaseLayer::init(const LayerMap& layerMap, filterSizeY_.push_back(conf.filter_size_y()); filterPixels_.push_back(filterSize_.back() * filterSizeY_.back()); channels_.push_back(conf.channels()); - imgSizeH_.push_back(conf.img_size()); + imgSizeH_.push_back(conf.has_img_size_y() ? conf.img_size_y() : + conf.img_size()); imgSizeW_.push_back(conf.img_size()); groups_.push_back(conf.groups()); filterChannels_.push_back(conf.filter_channels()); - outputH_.push_back(conf.output_x()); + outputH_.push_back(conf.has_output_y() ? conf.output_y() : + conf.output_x()); outputW_.push_back(conf.output_x()); } @@ -90,11 +92,12 @@ size_t ConvBaseLayer::calOutputSize() { for (size_t i = 0; i < inputLayers_.size(); i++) { inH.push_back(inputLayers_[i]->getOutput().getFrameHeight()); inW.push_back(inputLayers_[i]->getOutput().getFrameWidth()); + const ConvConfig& conf = config_.inputs(i).conv_conf(); if (isDeconv_) { if (inH[i] == 0) - inH[i] = config_.inputs(i).conv_conf().output_x(); + inH[i] = conf.has_output_y() ? conf.output_y() : conf.output_x(); if (inW[i] == 0) - inW[i] = config_.inputs(i).conv_conf().output_x(); + inW[i] = conf.output_x(); outH.push_back( imageSize(inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], caffeMode_)); @@ -103,9 +106,9 @@ size_t ConvBaseLayer::calOutputSize() { caffeMode_)); } else { if (inH[i] == 0) - inH[i] = config_.inputs(i).conv_conf().img_size(); + inH[i] = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); if (inW[i] == 0) - inW[i] = config_.inputs(i).conv_conf().img_size(); + inW[i] = conf.img_size(); outH.push_back( outputSize(inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], caffeMode_)); diff --git a/paddle/gserver/layers/ConvOperator.cpp b/paddle/gserver/layers/ConvOperator.cpp index 2d9c892fe5..7830efab1d 100644 --- a/paddle/gserver/layers/ConvOperator.cpp +++ b/paddle/gserver/layers/ConvOperator.cpp @@ -93,9 +93,9 @@ private: bool caffeMode_; int inputOffset_, outputOffset_, weightOffset_; int numFilters_; - int padding_, stride_, filterSize_, channels_, imgSize_; + int padding_, stride_, filterSize_, channels_, imgSize_, imgSizeY_; int paddingY_, strideY_, filterSizeY_; - int imgPixels_, filterPixels_, filterChannels_, outputX_, outputs_; + int imgPixels_, filterPixels_, filterChannels_, outputX_, outputY_, outputs_; /// Following member variables are same with CudnnConvLayer. /// There is no explanation here. @@ -144,7 +144,7 @@ void ConvOperator::allocConvWorkSpace(size_t maxWorkSpace) { void ConvOperator::reshape(int batchSize) { imageH_ = ins_[0]->getFrameHeight(); imageW_ = ins_[0]->getFrameWidth(); - if (imageH_ == 0) imageH_ = imgSize_; + if (imageH_ == 0) imageH_ = imgSizeY_; if (imageW_ == 0) imageW_ = imgSize_; outputH_ = outputSize(imageH_, filterSizeY_, paddingY_, strideY_, caffeMode_); outputW_ = outputSize(imageW_, filterSize_, padding_, stride_, caffeMode_); @@ -176,7 +176,10 @@ void ConvOperator::computeConvSizes() { hl_create_tensor_descriptor(&inputDesc_); int outputX = outputSize(imgSize_, filterSize_, padding_, stride_, caffeMode_); + int outputY = + outputSize(imgSizeY_, filterSizeY_, paddingY_, strideY_, caffeMode_); CHECK_EQ(outputX, outputX_); + CHECK_EQ(outputY, outputY_); hl_create_tensor_descriptor(&outputDesc_); hl_create_convolution_descriptor(&convDesc_, inputDesc_, filterDesc_, paddingY_, padding_, strideY_, stride_); @@ -208,10 +211,12 @@ void ConvOperator::getConvParams() { filterPixels_ = filterSize_ * filterSizeY_; channels_ = conf.channels(); imgSize_ = conf.img_size(); - imgPixels_ = imgSize_ * imgSize_; + imgSizeY_ = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); + imgPixels_ = imgSize_ * imgSizeY_; CHECK_EQ(conf.groups(), 1U); filterChannels_ = conf.filter_channels(); outputX_ = conf.output_x(); + outputY_ = conf.has_output_y() ? conf.output_y() : conf.output_x(); outputs_ = outputX_ * outputX_; } diff --git a/paddle/gserver/layers/ConvProjection.cpp b/paddle/gserver/layers/ConvProjection.cpp index d1ce53fe26..161bbad4f5 100644 --- a/paddle/gserver/layers/ConvProjection.cpp +++ b/paddle/gserver/layers/ConvProjection.cpp @@ -47,7 +47,7 @@ void ConvProjection::getConvParams() { filterH_ = conf.filter_size_y(); filterW_ = conf.filter_size(); - configImgH_ = conf.img_size(); + configImgH_ = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); configImgW_ = conf.img_size(); channels_ = conf.channels(); diff --git a/paddle/gserver/layers/DataLayer.cpp b/paddle/gserver/layers/DataLayer.cpp index 79b9181e69..b83d4f44b0 100644 --- a/paddle/gserver/layers/DataLayer.cpp +++ b/paddle/gserver/layers/DataLayer.cpp @@ -48,8 +48,8 @@ void DataLayer::copyDataToOutput(Argument& output) { output.ids->copyFrom(*data_.ids); } } - output.setFrameHeight(data_.getFrameHeight()); - output.setFrameWidth(data_.getFrameWidth()); + output.setFrameHeight(config_.height()); + output.setFrameWidth(config_.width()); output.cpuSequenceDims = data_.cpuSequenceDims; output.sequenceStartPositions = data_.sequenceStartPositions; output.subSequenceStartPositions = data_.subSequenceStartPositions; diff --git a/paddle/gserver/layers/ExpandConvBaseLayer.cpp b/paddle/gserver/layers/ExpandConvBaseLayer.cpp index 0bab0ca764..953c9d7841 100644 --- a/paddle/gserver/layers/ExpandConvBaseLayer.cpp +++ b/paddle/gserver/layers/ExpandConvBaseLayer.cpp @@ -30,17 +30,19 @@ bool ExpandConvBaseLayer::init(const LayerMap &layerMap, * meaning as in conv, we need to swap channels_ and numFilters here for * convTrans, and in other functions too. * */ - int channel; - int numFilters; + /* Initialize the projection */ for (auto &inputConfig : config_.inputs()) { const ConvConfig &conf = inputConfig.conv_conf(); - numFilters = isDeconv_ ? conf.channels() : numFilters_; + int numFilters = isDeconv_ ? conf.channels() : numFilters_; subM_.push_back(numFilters / conf.groups()); - subN_.push_back(conf.output_x() * conf.output_x()); - channel = isDeconv_ ? numFilters_ : conf.channels(); - subK_.push_back(channel * conf.filter_size() * conf.filter_size() / - conf.groups()); + subN_.push_back(conf.output_x() * + (conf.has_output_y() ? conf.output_y() : conf.output_x())); + int channel = isDeconv_ ? numFilters_ : conf.channels(); + subK_.push_back( + channel * conf.filter_size() * + (conf.has_filter_size_y() ? conf.filter_size_y() : conf.filter_size()) / + conf.groups()); /* Consistent caffe mode for multiple input */ caffeMode_ = conf.caffe_mode(); } @@ -107,9 +109,9 @@ void ExpandConvBaseLayer::expandOneFrame(MatrixPtr image, size_t startIdx, imgData, 1, imgSizeH_[inIdx] * imgSizeW_[inIdx] * channel, false, useGpu_); expandInput_->convExpand(*imageTmp, imgSizeH_[inIdx], imgSizeW_[inIdx], - channel, filterSize_[inIdx], - filterSize_[inIdx], stride_[inIdx], stride_[inIdx], - padding_[inIdx], padding_[inIdx], + channel, filterSizeY_[inIdx], + filterSize_[inIdx], strideY_[inIdx], stride_[inIdx], + paddingY_[inIdx], padding_[inIdx], outputH_[inIdx], outputW_[inIdx]); imageTmp->clear(); } @@ -188,10 +190,10 @@ void ExpandConvBaseLayer::bpropActs(MatrixPtr out, MatrixPtr image, imgSizeH_[inpIdx] * imgSizeW_[inpIdx] * channel, false, useGpu_); vTmp->convShrink(*oneGradTmp, imgSizeH_[inpIdx], imgSizeW_[inpIdx], - channel, filterSize_[inpIdx], - filterSize_[inpIdx], stride_[inpIdx], stride_[inpIdx], - padding_[inpIdx], padding_[inpIdx], - outputH_[inpIdx], outputW_[inpIdx], 1.0f, 1.0f); + channel, filterSizeY_[inpIdx], + filterSize_[inpIdx], strideY_[inpIdx], stride_[inpIdx], + paddingY_[inpIdx], padding_[inpIdx], outputH_[inpIdx], + outputW_[inpIdx], 1.0f, 1.0f); vTmp->clear(); oneGradTmp->clear(); diff --git a/paddle/gserver/layers/MaxOutLayer.cpp b/paddle/gserver/layers/MaxOutLayer.cpp index a3de069bf7..b7f1b98041 100644 --- a/paddle/gserver/layers/MaxOutLayer.cpp +++ b/paddle/gserver/layers/MaxOutLayer.cpp @@ -25,10 +25,10 @@ size_t MaxOutLayer::getSize() { imgSizeH_ = inputLayers_[0]->getOutput().getFrameHeight(); imgSizeW_ = inputLayers_[0]->getOutput().getFrameWidth(); if (imgSizeH_ == 0) { - imgSizeH_ = maxoutConf.img_size_y(); + imgSizeH_ = maxoutConf.image_conf().img_size_y(); } if (imgSizeW_ == 0) { - imgSizeW_ = maxoutConf.img_size_x(); + imgSizeW_ = maxoutConf.image_conf().img_size(); } featLen_ = imgSizeH_ * imgSizeW_; @@ -50,7 +50,7 @@ bool MaxOutLayer::init(const LayerMap& layerMap, const MaxOutConfig& conf = config_.inputs(0).maxout_conf(); groups_ = conf.groups(); - channels_ = conf.channels(); + channels_ = conf.image_conf().channels(); CHECK_EQ(channels_ % groups_, 0UL); outputChannels_ = channels_ / groups_; diff --git a/paddle/gserver/layers/NormLayer.cpp b/paddle/gserver/layers/NormLayer.cpp index ad8b92d2ff..b02a542a51 100644 --- a/paddle/gserver/layers/NormLayer.cpp +++ b/paddle/gserver/layers/NormLayer.cpp @@ -49,6 +49,9 @@ bool ResponseNormLayer::init(const LayerMap& layerMap, outputX_ = conf.output_x(); imgSize_ = conf.img_size(); denoms_ = NULL; + + outputY_ = conf.has_output_y() ? conf.output_y() : conf.output_x(); + imgSizeY_ = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); return true; } diff --git a/paddle/gserver/layers/NormLayer.h b/paddle/gserver/layers/NormLayer.h index 2b05be6fcb..9e4acffd1f 100644 --- a/paddle/gserver/layers/NormLayer.h +++ b/paddle/gserver/layers/NormLayer.h @@ -50,7 +50,7 @@ public: */ class ResponseNormLayer : public NormLayer { protected: - size_t channels_, size_, outputX_, imgSize_; + size_t channels_, size_, outputX_, imgSize_, outputY_, imgSizeY_; float scale_, pow_; MatrixPtr denoms_; diff --git a/paddle/gserver/layers/NormProjectionLayer.cpp b/paddle/gserver/layers/NormProjectionLayer.cpp index eab6e904ee..e33b985fac 100644 --- a/paddle/gserver/layers/NormProjectionLayer.cpp +++ b/paddle/gserver/layers/NormProjectionLayer.cpp @@ -24,7 +24,7 @@ size_t CMRProjectionNormLayer::getSize() { imgSizeH_ = inputLayers_[0]->getOutput().getFrameHeight(); imgSizeW_ = inputLayers_[0]->getOutput().getFrameWidth(); if (imgSizeH_ == 0) { - imgSizeH_ = imgSize_; + imgSizeH_ = imgSizeY_; } if (imgSizeW_ == 0) { imgSizeW_ = imgSize_; diff --git a/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp b/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp index 2fcfc8e1ae..2675f95401 100644 --- a/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp +++ b/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp @@ -56,14 +56,14 @@ ProjectionConfig SpatialPyramidPoolLayer::getConfig(size_t imgSizeW, size_t SpatialPyramidPoolLayer::getSize() { CHECK_EQ(inputLayers_.size(), 1UL); size_t layerSize = 0; - const SppConfig& sppConf = config_.inputs(0).spp_conf(); + const ImageConfig& conf = config_.inputs(0).spp_conf().image_conf(); imgSizeH_ = inputLayers_[0]->getOutput().getFrameHeight(); imgSizeW_ = inputLayers_[0]->getOutput().getFrameWidth(); if (imgSizeH_ == 0) { - imgSizeH_ = sppConf.has_img_size_y() ? sppConf.img_size_y() : imgSizeW_; + imgSizeH_ = conf.has_img_size_y() ? conf.img_size_y() : conf.img_size(); } if (imgSizeW_ == 0) { - imgSizeW_ = sppConf.img_size(); + imgSizeW_ = conf.img_size(); } size_t outputH = 1; @@ -82,9 +82,10 @@ bool SpatialPyramidPoolLayer::init(const LayerMap& layerMap, pyramidHeight_ = sppConf.pyramid_height(); poolType_ = sppConf.pool_type(); - channels_ = sppConf.channels(); - imgSizeW_ = sppConf.img_size(); - imgSizeH_ = sppConf.has_img_size_y() ? sppConf.img_size_y() : imgSizeW_; + const ImageConfig& imageConf = sppConf.image_conf(); + channels_ = imageConf.channels(); + imgSizeW_ = imageConf.img_size(); + imgSizeH_ = imageConf.has_img_size_y() ? imageConf.img_size_y() : imgSizeW_; poolProjections_.reserve(pyramidHeight_); projCol_.reserve(pyramidHeight_); projOutput_.resize(pyramidHeight_); diff --git a/paddle/gserver/tests/img_pool_a.conf b/paddle/gserver/tests/img_pool_a.conf index 5938e76112..9bd046b533 100644 --- a/paddle/gserver/tests/img_pool_a.conf +++ b/paddle/gserver/tests/img_pool_a.conf @@ -28,7 +28,6 @@ maxpool = img_pool_layer(input=conv, stride_y=2, padding=1, padding_y=2, - img_width=16, pool_type=MaxPooling(), ) avgpool = img_pool_layer(input=conv, @@ -39,7 +38,6 @@ avgpool = img_pool_layer(input=conv, stride_y=2, padding=1, padding_y=2, - img_width=16, pool_type=AvgPooling(), ) diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index a79dfe39c9..e839851099 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -194,9 +194,10 @@ TEST(Layer, BilinearInterpLayer) { LayerInputConfig* input = config.layerConfig.add_inputs(); BilinearInterpConfig* bilinear = input->mutable_bilinear_interp_conf(); - bilinear->set_img_size_x(32); - bilinear->set_img_size_y(32); - bilinear->set_num_channels(4); + ImageConfig* image = bilinear->mutable_image_conf(); + image->set_img_size(32); + image->set_img_size_y(32); + image->set_channels(4); for (auto useGpu : {false, true}) { for (auto outSize : {32, 64}) { @@ -314,7 +315,7 @@ void testConvLayer(const string& type, bool trans, bool useGpu) { config.layerConfig.set_partial_sum(1); config.layerConfig.set_shared_biases(true); - config.inputDefs.push_back({INPUT_DATA, "layer_0", 768, 288}); + config.inputDefs.push_back({INPUT_DATA, "layer_0", 384, 288}); LayerInputConfig* input = config.layerConfig.add_inputs(); ConvConfig* conv = input->mutable_conv_conf(); conv->set_filter_size(2); @@ -327,10 +328,14 @@ void testConvLayer(const string& type, bool trans, bool useGpu) { conv->set_groups(1); conv->set_filter_channels(conv->channels() / conv->groups()); conv->set_img_size(16); + conv->set_img_size_y(8); conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), conv->padding(), conv->stride(), /* caffeMode */ true)); - config.layerConfig.set_size(conv->output_x() * conv->output_x() * + conv->set_output_y(outputSize(conv->img_size_y(), conv->filter_size_y(), + conv->padding_y(), conv->stride_y(), + /* caffeMode */ true)); + config.layerConfig.set_size(conv->output_x() * conv->output_y() * config.layerConfig.num_filters()); testLayerGrad(config, "conv", 100, trans, useGpu); @@ -427,10 +432,11 @@ TEST(Layer, maxoutLayer) { config.inputDefs.push_back({INPUT_DATA, "layer_0", 4096, 0}); LayerInputConfig* input = config.layerConfig.add_inputs(); MaxOutConfig* maxout = input->mutable_maxout_conf(); + ImageConfig* image = maxout->mutable_image_conf(); - maxout->set_img_size_x(32); - maxout->set_img_size_y(32); - maxout->set_channels(4); + image->set_img_size(32); + image->set_img_size_y(32); + image->set_channels(4); maxout->set_groups(2); for (auto useGpu : {false, true}) { @@ -902,7 +908,7 @@ void testNormLayer(const string& normType, bool trans, bool useGpu) { config.layerConfig.set_type("norm"); config.layerConfig.set_active_type("relu"); - config.inputDefs.push_back({INPUT_DATA, "layer_0", 3136, 0}); + config.inputDefs.push_back({INPUT_DATA, "layer_0", 1568, 0}); LayerInputConfig* input = config.layerConfig.add_inputs(); NormConfig* norm = input->mutable_norm_conf(); norm->set_norm_type(normType); @@ -912,7 +918,9 @@ void testNormLayer(const string& normType, bool trans, bool useGpu) { norm->set_pow(0.75); norm->set_blocked(0); norm->set_img_size(14); + norm->set_img_size_y(7); norm->set_output_x(norm->img_size()); + norm->set_output_y(norm->img_size_y()); if (norm->norm_type() == "cmrnorm" || norm->norm_type() == "cmrnorm-projection") { norm->set_scale(norm->scale() / norm->size()); @@ -920,7 +928,7 @@ void testNormLayer(const string& normType, bool trans, bool useGpu) { norm->set_scale(norm->scale() / (norm->size() * norm->size())); } - config.layerConfig.set_size(norm->output_x() * norm->output_x() * + config.layerConfig.set_size(norm->output_x() * norm->output_y() * norm->channels()); config.biasSize = 0; @@ -1018,11 +1026,12 @@ void testSppLayer(const string& poolType, const int pyramidHeight, bool trans, SppConfig* sppConfig = input->mutable_spp_conf(); sppConfig->set_pool_type(poolType); sppConfig->set_pyramid_height(pyramidHeight); - sppConfig->set_channels(16); - sppConfig->set_img_size(10); - sppConfig->set_img_size_y(20); + ImageConfig* imageConfig = sppConfig->mutable_image_conf(); + imageConfig->set_channels(16); + imageConfig->set_img_size(10); + imageConfig->set_img_size_y(20); int outputSize = (std::pow(4, sppConfig->pyramid_height()) - 1) / (4 - 1); - config.layerConfig.set_size(outputSize * sppConfig->channels()); + config.layerConfig.set_size(outputSize * imageConfig->channels()); testLayerGrad(config, "spp", 100, trans, useGpu); } @@ -1328,12 +1337,13 @@ void testBatchNormLayer(const string& type, bool trans, bool useGpu) { TestConfig config; const int CHANNELS = 10; const int IMG_SIZE = 16; + const int IMG_SIZE_Y = 8; + size_t size = CHANNELS * IMG_SIZE * IMG_SIZE_Y; config.layerConfig.set_type(type); - config.layerConfig.set_size(CHANNELS * IMG_SIZE * IMG_SIZE); + config.layerConfig.set_size(size); config.layerConfig.set_active_type("sigmoid"); config.biasSize = CHANNELS; - config.inputDefs.push_back({INPUT_DATA, "layer_0", - /* dim= */ IMG_SIZE * IMG_SIZE * 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}); @@ -1348,6 +1358,7 @@ void testBatchNormLayer(const string& type, bool trans, bool useGpu) { 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); testLayerGrad(config, "batch_norm", 64, /* trans= */ trans, useGpu, /* useWeight */ true); @@ -1370,6 +1381,7 @@ TEST(Operator, conv) { const int FILTER_SIZE_Y = 3; const int CHANNELS = 3; const int IMAGE_SIZE = 16; + const int IMAGE_SIZE_Y = 8; OperatorConfig& operatorConf = *config.layerConfig.add_operator_confs(); operatorConf.set_type("conv"); ConvConfig* conv = operatorConf.mutable_conv_conf(); @@ -1384,17 +1396,18 @@ TEST(Operator, conv) { conv->set_groups(1); conv->set_filter_channels(conv->channels() / conv->groups()); conv->set_img_size(IMAGE_SIZE); - int output_x = - outputSize(conv->img_size(), conv->filter_size(), conv->padding(), - conv->stride(), /* caffeMode */ true); - conv->set_output_x(output_x); - config.layerConfig.set_size(output_x * output_x * - config.layerConfig.num_filters()); - config.layerConfig.set_size(conv->output_x() * conv->output_x() * + conv->set_img_size_y(IMAGE_SIZE_Y); + conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), + conv->padding(), conv->stride(), + /* caffeMode */ true)); + conv->set_output_y(outputSize(conv->img_size_y(), conv->filter_size_y(), + conv->padding_y(), conv->stride_y(), + /* caffeMode */ true)); + config.layerConfig.set_size(conv->output_x() * conv->output_y() * NUM_FILTERS); config.inputDefs.push_back( - {INPUT_DATA, "layer_0", IMAGE_SIZE * IMAGE_SIZE * CHANNELS, 0}); + {INPUT_DATA, "layer_0", IMAGE_SIZE * IMAGE_SIZE_Y * CHANNELS, 0}); config.inputDefs.push_back( {INPUT_DATA, "layer_1", FILTER_SIZE * FILTER_SIZE_Y * CHANNELS * NUM_FILTERS, 0}); diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 42c74661d2..2d5cd29aed 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -203,6 +203,8 @@ void Argument::resizeAndCopyFrom(const Argument& src, bool useGpu, } resizeAndCopy(udp, src.udp, useGpu, stream); resizeAndCopy(strs, src.strs, useGpu, stream); + frameWidth = src.frameWidth; + frameHeight = src.frameHeight; } int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, diff --git a/paddle/trainer/tests/test_config.conf b/paddle/trainer/tests/test_config.conf index 664e18cb98..2a4548896f 100644 --- a/paddle/trainer/tests/test_config.conf +++ b/paddle/trainer/tests/test_config.conf @@ -59,7 +59,6 @@ pool = img_pool_layer(input=fc2, padding_y=2, stride=2, stride_y=3, - img_width=3, pool_type=CudnnAvgPooling()) concat = concat_layer(input=[fc3, fc4]) diff --git a/proto/ModelConfig.proto.m4 b/proto/ModelConfig.proto.m4 index aea77248cb..3c35075a92 100644 --- a/proto/ModelConfig.proto.m4 +++ b/proto/ModelConfig.proto.m4 @@ -76,6 +76,12 @@ message ConvConfig { required uint32 filter_size_y = 10; required uint32 padding_y = 11; required uint32 stride_y = 12; + + // if not set, use output_x + optional uint32 output_y = 13 [default = 0]; + + // if not set, use img_size + optional uint32 img_size_y = 14 [default = 0]; } message PoolConfig { @@ -121,11 +127,9 @@ message PoolConfig { } message SppConfig { - required string pool_type = 1; - required uint32 pyramid_height = 2; - required uint32 channels = 3; - required uint32 img_size = 4; - optional uint32 img_size_y = 5; + required ImageConfig image_conf = 1; + required string pool_type = 2; + required uint32 pyramid_height = 3; } message NormConfig { @@ -155,6 +159,12 @@ message NormConfig { // fixed window: shared a fixed window for each value // sliding window: have a different window for each value optional bool blocked = 8; + + // if not set, use output_x + optional uint32 output_y = 9 [default = 0]; + + // if not set, use img_size + optional uint32 img_size_y = 10 [default = 0]; } message BlockExpandConfig { @@ -179,12 +189,8 @@ message BlockExpandConfig { } message MaxOutConfig { - required uint32 channels = 1; + required ImageConfig image_conf = 1; required uint32 groups = 2; - - // The size of input feature map. - required uint32 img_size_x = 3; - required uint32 img_size_y = 4; } message ProjectionConfig { @@ -225,12 +231,10 @@ message OperatorConfig { message BilinearInterpConfig { // The size of input feature map. - optional uint32 img_size_x = 1; - optional uint32 img_size_y = 2; + required ImageConfig image_conf = 1; // The size of output feature map. - required uint32 out_size_x = 3; - required uint32 out_size_y = 4; - required uint32 num_channels = 5; + required uint32 out_size_x = 2; + required uint32 out_size_y = 3; } message ImageConfig { @@ -240,6 +244,7 @@ message ImageConfig { // The size of input feature map. required uint32 img_size = 8; + required uint32 img_size_y = 9; } message LayerInputConfig { @@ -412,7 +417,10 @@ sinclude(`ModelConfigLayer.proto.m4') // string type is used for flexibility: different types can be converted // to string and reinterpreted in the user's own layer implementation. optional string user_arg = 49; - + + // to indicate rectangle image data + optional uint64 height = 50; + optional uint64 width = 51; } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index dbe2f3b292..a7ad40e483 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -138,7 +138,14 @@ def init_config_environment( g_root_submodel=None, g_submodel_map={}, g_submodel_stack=[], - g_add_submodel_suffix=False, ): + g_add_submodel_suffix=False, + + # Whether current layer needs to pass the image height and width. + # Default value is true, but if it encounters recurrent_layer_group, + # it will be false. The reason is that image is converted to be sequence, + # image height will be sequence length, and image width will be feature + # length of each timestep. + g_pass_height_width=True, ): for k, v in locals().iteritems(): globals()[k] = copy.deepcopy(v) @@ -592,6 +599,7 @@ class DotMulProjection(Projection): def calc_parameter_dims(self, input_size, output_size): return [1, output_size] + # ScalingProjection @config_class class ScalingProjection(Projection): @@ -685,9 +693,9 @@ class ConvProjection(Projection): parse_conv(conv_conf, input_layer_name, self.proj_conf.conv_conf, num_filters) - # TODO: support rectangle input - self.proj_conf.output_size = (self.proj_conf.conv_conf.output_x - **2) * num_filters + self.proj_conf.output_size = self.proj_conf.conv_conf.output_x * \ + self.proj_conf.conv_conf.output_y * \ + num_filters def calc_output_size(self, input_layer_config): return self.proj_conf.output_size @@ -762,8 +770,9 @@ class ConvOperator(Operator): parse_conv(conv_conf, MakeLayerNameInSubmodel(input_layer_names[0]), self.operator_conf.conv_conf, num_filters) - self.operator_conf.output_size = (self.operator_conf.conv_conf.output_x - **2) * num_filters + self.operator_conf.output_size = self.operator_conf.conv_conf.output_x * \ + self.operator_conf.conv_conf.output_y * \ + num_filters config_assert(len(input_layer_names) == 2, "Conv is binary operator") @@ -798,14 +807,12 @@ class Conv(Cfg): config_assert(output_x <= 0) -# please refer to the comments in proto/ModelConfig.proto @config_class class BilinearInterp(Cfg): - def __init__(self, out_size_x=None, out_size_y=None, num_channels=None): + def __init__(self, out_size_x=None, out_size_y=None, channels=None): self.add_keys(locals()) -# please refer to the comments in proto/ModelConfig.proto @config_class class Pool(Cfg): def __init__(self, @@ -813,7 +820,6 @@ class Pool(Cfg): channels, size_x, size_y=None, - img_width=None, start=None, stride=None, stride_y=None, @@ -822,14 +828,12 @@ class Pool(Cfg): self.add_keys(locals()) -# please refer to the comments in proto/ModelConfig.proto @config_class class SpatialPyramidPool(Cfg): - def __init__(self, pool_type, pyramid_height, channels, img_width=None): + def __init__(self, pool_type, pyramid_height, channels): self.add_keys(locals()) -# please refer to the comments in proto/ModelConfig.proto @config_class class Norm(Cfg): def __init__(self, @@ -844,7 +848,6 @@ class Norm(Cfg): self.add_keys(locals()) -# please refer to the comments in proto/ModelConfig.proto @config_class class Image(Cfg): def __init__(self, channels, img_size=None): @@ -1051,18 +1054,8 @@ def TestData(data_config, async_load_data=None): g_config.test_data_config.async_load_data = async_load_data -def parse_bilinear(bilinear, input_layer_name, bilinear_conf): - bilinear_conf.out_size_x = bilinear.out_size_x - bilinear_conf.out_size_y = bilinear.out_size_y - bilinear_conf.num_channels = bilinear.num_channels - - -''' -caffe_mode: compute the output size using floor instead of ceil, - which is consistent of caffe and CuDNN's convention. -''' - - +#caffe_mode: compute the output size using floor instead of ceil, +# which is consistent of caffe and CuDNN's convention. def cnn_output_size(img_size, filter_size, padding, stride, caffe_mode): output = (2 * padding + img_size - filter_size) / float(stride) if caffe_mode: @@ -1071,20 +1064,34 @@ def cnn_output_size(img_size, filter_size, padding, stride, caffe_mode): return 1 + int(math.ceil(output)) -''' -calcualte image_size based on output_size for convolution. -It is the reverse function of cnn_output_size -''' - - +#calcualte image_size based on output_size for convolution. +#It is the reverse function of cnn_output_size def cnn_image_size(output_size, filter_size, padding, stride, caffe_mode): - if caffe_mode: - img_size = (output_size - 1) * stride + filter_size - 2 * padding - else: - img_size = (output_size - 2) * stride + filter_size - 2 * padding + 1 + img_size = (output_size - 1) * stride + filter_size - 2 * padding + if not caffe_mode: + img_size = img_size + 1 return img_size +def set_img_size(input_layer_name, channels): + input = g_layer_map[input_layer_name] + img_pixels = input.size / channels + img_size = input.width if input.width > 0 else int(img_pixels**0.5) + img_size_y = input.height if input.height > 0 else int(img_pixels / + img_size) + config_assert( + img_size * img_size_y == img_pixels, + "Input layer %s: Incorrect input image size %d * %d for input image pixels %d" + % (input_layer_name, img_size, img_size_y, img_pixels)) + return img_size, img_size_y + + +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 + bilinear_conf.out_size_y = bilinear.out_size_y + + def parse_pool(pool, input_layer_name, pool_conf): pool_conf.pool_type = pool.pool_type config_assert(pool.pool_type in [ @@ -1100,14 +1107,8 @@ def parse_pool(pool, input_layer_name, pool_conf): pool_conf.size_y = default(pool.size_y, pool_conf.size_x) pool_conf.stride_y = default(pool.stride_y, pool_conf.stride) - img_pixels = g_layer_map[input_layer_name].size / pool.channels - # the img_width may be removed, - # and it can be calculated automatically later. - pool_conf.img_size = default(pool.img_width, int(img_pixels**0.5)) - pool_conf.img_size_y = img_pixels / pool_conf.img_size - config_assert(pool_conf.img_size * pool_conf.img_size_y == img_pixels, - "Incorrect input image size %d for input image pixels %d" % - (pool_conf.img_size, img_pixels)) + pool_conf.img_size, pool_conf.img_size_y = \ + set_img_size(input_layer_name, pool.channels) config_assert(not pool.start, "start is deprecated in pooling.") @@ -1123,29 +1124,18 @@ def parse_pool(pool, input_layer_name, pool_conf): def parse_spp(spp, input_layer_name, spp_conf): + parse_image(spp, input_layer_name, spp_conf.image_conf) spp_conf.pool_type = spp.pool_type config_assert(spp.pool_type in ['max-projection', 'avg-projection'], "pool-type %s is not in " "['max-projection', 'avg-projection']" % spp.pool_type) spp_conf.pyramid_height = spp.pyramid_height - spp_conf.channels = spp.channels - - img_pixels = g_layer_map[input_layer_name].size / spp_conf.channels - - spp_conf.img_size = default(spp.img_width, int(img_pixels**0.5)) - spp_conf.img_size_y = img_pixels / spp_conf.img_size - config_assert(spp_conf.img_size * spp_conf.img_size_y == img_pixels, - "Incorrect input image size %d for input image pixels %d" % - (spp_conf.img_size, img_pixels)) def parse_image(image, input_layer_name, image_conf): image_conf.channels = image.channels - image_pixels = g_layer_map[input_layer_name].size / image_conf.channels - image_conf.img_size = int(image_pixels**0.5) - config_assert((image_conf.img_size**2) == image_pixels, - "Incorrect input image size %d for input image pixels %d" % - (image_conf.img_size, image_pixels)) + image_conf.img_size, image_conf.img_size_y = \ + set_img_size(input_layer_name, image_conf.channels) def parse_norm(norm, input_layer_name, norm_conf): @@ -1159,24 +1149,18 @@ def parse_norm(norm, input_layer_name, norm_conf): norm_conf.pow = norm.pow norm_conf.blocked = norm.blocked - img_pixels = g_layer_map[input_layer_name].size / norm.channels - norm_conf.img_size = int(img_pixels**0.5) - config_assert((norm_conf.img_size**2) == img_pixels, - "Incorrect input image size %d for input image pixels %d" % - (norm_conf.img_size, img_pixels)) + norm_conf.img_size, norm_conf.img_size_y = \ + set_img_size(input_layer_name, norm.channels) norm_conf.output_x = norm_conf.img_size + norm_conf.output_y = norm_conf.img_size_y if norm.norm_type in ['cmrnorm-projection']: norm_conf.scale /= norm.size else: norm_conf.scale /= norm.size**2 -''' -caffe_mode: compute the output size using floor instead of ceil, - which is consistent of caffe and CuDNN's convention. -''' - - +#caffe_mode: compute the output size using floor instead of ceil, +# which is consistent of caffe and CuDNN's convention. def parse_conv(conv, input_layer_name, conv_conf, num_filters, trans=False): conv_conf.filter_size = conv.filter_size conv_conf.filter_size_y = conv.filter_size_y @@ -1190,33 +1174,24 @@ def parse_conv(conv, input_layer_name, conv_conf, num_filters, trans=False): if not trans: conv_conf.filter_channels = conv.channels / conv.groups - - img_pixels = g_layer_map[input_layer_name].size / conv.channels - print('channels=%d size=%d' % (conv.channels, - g_layer_map[input_layer_name].size)) - conv_conf.img_size = int(img_pixels**0.5) - config_assert((conv_conf.img_size**2) == img_pixels, ( - "Input layer %s: Incorrect input image size %d for input " + - "image pixels %d") % - (input_layer_name, conv_conf.img_size, img_pixels)) - + conv_conf.img_size, conv_conf.img_size_y = \ + set_img_size(input_layer_name, conv.channels) conv_conf.output_x = cnn_output_size( conv_conf.img_size, conv_conf.filter_size, conv_conf.padding, conv_conf.stride, conv_conf.caffe_mode) + conv_conf.output_y = cnn_output_size( + conv_conf.img_size_y, conv_conf.filter_size_y, conv_conf.padding_y, + conv_conf.stride_y, conv_conf.caffe_mode) else: conv_conf.filter_channels = num_filters / conv.groups - - outputSize = g_layer_map[input_layer_name].size / conv.channels - print('channels=%d size=%d' % (conv.channels, - g_layer_map[input_layer_name].size)) - conv_conf.output_x = int(outputSize**0.5) - config_assert((conv_conf.output_x**2) == outputSize, ( - "Input layer %s: Incorrect input image size %d for input " + - "image pixels %d") % - (input_layer_name, conv_conf.output_x, outputSize)) + conv_conf.output_x, conv_conf.output_y = \ + set_img_size(input_layer_name, conv.channels) conv_conf.img_size = cnn_image_size( conv_conf.output_x, conv_conf.filter_size, conv_conf.padding, conv_conf.stride, conv_conf.caffe_mode) + conv_conf.img_size_y = cnn_output_size( + conv_conf.output_y, conv_conf.filter_size_y, conv_conf.padding_y, + conv_conf.stride_y, conv_conf.caffe_mode) def parse_block_expand(block_expand, input_layer_name, block_expand_conf): @@ -1245,10 +1220,8 @@ def parse_block_expand(block_expand, input_layer_name, block_expand_conf): def parse_maxout(maxout, input_layer_name, maxout_conf): - maxout_conf.channels = maxout.channels + parse_image(maxout, input_layer_name, maxout_conf.image_conf) maxout_conf.groups = maxout.groups - maxout_conf.img_size_x = maxout.img_size_x - maxout_conf.img_size_y = maxout.img_size_y # Define an evaluator @@ -1375,6 +1348,12 @@ class LayerBase(object): g_current_submodel.layer_names.append(self.config.name) + if self.config.type != 'data' and g_pass_height_width: + height = self.get_input_layer(0).height + width = self.get_input_layer(0).width + if height and width: + self.set_layer_height_width(height, width) + def get_input_layer(self, input_index): return g_layer_map[self.config.inputs[input_index].input_layer_name] @@ -1492,6 +1471,23 @@ class LayerBase(object): 'Different inputs result in' + 'different layer size at layer %s' % self.config.name) + def set_layer_height_width(self, height, width): + self.config.height = height + self.config.width = width + + def set_cnn_layer(self, + input_layer_name, + height, + width, + channels, + is_print=True): + size = height * width * channels + self.set_layer_size(size) + self.set_layer_height_width(height, width) + if is_print: + print("output for %s: c = %d, h = %d, w = %d, size = %d" % + (input_layer_name, channels, height, width, size)) + @config_layer('multi_class_cross_entropy_with_selfnorm') class MultiClassCrossEntropySelfNormCostLayer(LayerBase): @@ -1581,9 +1577,11 @@ class PrintLayer(LayerBase): @config_layer('data') class DataLayer(LayerBase): - def __init__(self, name, size, device=None): + def __init__(self, name, size, 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) ''' @@ -1682,14 +1680,13 @@ class ConvLayerBase(LayerBase): for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) - parse_conv(self.inputs[input_index].conv, input_layer.name, - self.config.inputs[input_index].conv_conf, num_filters) conv_conf = self.config.inputs[input_index].conv_conf + parse_conv(self.inputs[input_index].conv, input_layer.name, + conv_conf, num_filters) psize = self.calc_parameter_size(conv_conf) - print("output size for %s is %d " % (name, conv_conf.output_x)) self.create_input_parameter(input_index, psize) - self.set_layer_size( - (conv_conf.output_x**2) * self.config.num_filters) + self.set_cnn_layer(name, conv_conf.output_y, conv_conf.output_x, + self.config.num_filters) psize = self.config.size if shared_biases: @@ -1776,10 +1773,11 @@ class NormLayer(LayerBase): name, 'norm', 0, inputs=inputs, device=device) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) - parse_norm(self.inputs[input_index].norm, input_layer.name, - self.config.inputs[input_index].norm_conf) norm_conf = self.config.inputs[input_index].norm_conf - self.set_layer_size((norm_conf.output_x**2) * norm_conf.channels) + parse_norm(self.inputs[input_index].norm, input_layer.name, + norm_conf) + self.set_cnn_layer(name, norm_conf.output_y, norm_conf.output_x, + norm_conf.channels, False) @config_layer('pool') @@ -1789,13 +1787,11 @@ class PoolLayer(LayerBase): name, 'pool', 0, inputs=inputs, device=device) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) - parse_pool(self.inputs[input_index].pool, input_layer.name, - self.config.inputs[input_index].pool_conf) pool_conf = self.config.inputs[input_index].pool_conf - print("output size for %s is %d*%d " % (name, pool_conf.output_y, - pool_conf.output_x)) - self.set_layer_size( - (pool_conf.output_x * pool_conf.output_y) * pool_conf.channels) + parse_pool(self.inputs[input_index].pool, input_layer.name, + pool_conf) + self.set_cnn_layer(name, pool_conf.output_y, pool_conf.output_x, + pool_conf.channels) @config_layer('spp') @@ -1805,12 +1801,10 @@ class SpatialPyramidPoolLayer(LayerBase): name, 'spp', 0, inputs=inputs, device=device) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) - parse_spp(self.inputs[input_index].spp, input_layer.name, - self.config.inputs[input_index].spp_conf) spp_conf = self.config.inputs[input_index].spp_conf - output_size = (pow(4, spp_conf.pyramid_height) - 1) / (4 - 1) - print("output size for %s is %d " % (name, output_size)) - self.set_layer_size(output_size * spp_conf.channels) + parse_spp(self.inputs[input_index].spp, input_layer.name, spp_conf) + output_x = (pow(4, spp_conf.pyramid_height) - 1) / (4 - 1) + self.set_cnn_layer(name, 1, output_x, spp_conf.image_conf.channels) @config_layer('batch_norm') @@ -1872,10 +1866,10 @@ class BatchNormLayer(LayerBase): self.config.moving_average_fraction = moving_average_fraction input_layer = self.get_input_layer(0) - parse_image(self.inputs[0].image, input_layer.name, - self.config.inputs[0].image_conf) image_conf = self.config.inputs[0].image_conf - self.set_layer_size((image_conf.img_size**2) * image_conf.channels) + parse_image(self.inputs[0].image, input_layer.name, image_conf) + self.set_cnn_layer(name, image_conf.img_size_y, image_conf.img_size, + image_conf.channels) psize = self.calc_parameter_size(image_conf) dims = [1, psize] @@ -1933,11 +1927,12 @@ class MaxOutLayer(LayerBase): super(MaxOutLayer, self).__init__( name, 'maxout', 0, inputs=inputs, **xargs) input_layer = self.get_input_layer(0) - parse_maxout(self.inputs[0].maxout, input_layer.name, - self.config.inputs[0].maxout_conf) maxout_conf = self.config.inputs[0].maxout_conf + parse_maxout(self.inputs[0].maxout, input_layer.name, maxout_conf) self.set_layer_size(g_layer_map[input_layer.name].size / maxout_conf.groups) + self.set_layer_height_width(g_layer_map[input_layer.name].height, + g_layer_map[input_layer.name].width) # key: cost type @@ -2517,11 +2512,10 @@ class BilinearInterpLayer(LayerBase): super(BilinearInterpLayer, self).__init__( name, 'bilinear_interp', 0, inputs=inputs, **xargs) input_layer = self.get_input_layer(0) - parse_bilinear(self.inputs[0].bilinear_interp, input_layer.name, - self.config.inputs[0].bilinear_interp_conf) - conf = self.inputs[0].bilinear_interp - self.set_layer_size(conf.out_size_x * conf.out_size_y * - conf.num_channels) + conf = self.config.inputs[0].bilinear_interp_conf + parse_bilinear(self.inputs[0].bilinear_interp, input_layer.name, conf) + self.set_cnn_layer(name, conf.out_size_y, conf.out_size_x, + conf.image_conf.channels) @config_layer('sum_to_one_norm') @@ -2994,6 +2988,8 @@ class CTCLayer(LayerBase): @config_layer('recurrent_layer_group') class RecurrentLayerGroup(LayerBase): def __init__(self, name, device=None): + global g_pass_height_width + g_pass_height_width = False super(RecurrentLayerGroup, self).__init__( name, 'recurrent_layer_group', 0, inputs=[], device=device) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index d984e84320..fbb28e6caf 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -763,7 +763,7 @@ def mixed_layer(size=0, @layer_support() -def data_layer(name, size, layer_attr=None): +def data_layer(name, size, height=None, width=None, layer_attr=None): """ Define DataLayer For NeuralNetwork. @@ -778,6 +778,10 @@ def data_layer(name, size, layer_attr=None): :type name: basestring :param size: Size of this data layer. :type size: int + :param height: Height of this data layer, used for image + :type size: int|None + :param width: Width of this data layer, used for image + :type size: int|None :param layer_attr: Extra Layer Attribute. :type layer_attr: ExtraLayerAttribute. :return: LayerOutput object. @@ -787,6 +791,8 @@ def data_layer(name, size, layer_attr=None): type=LayerType.DATA, name=name, size=size, + height=height, + width=width, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput(name, LayerType.DATA, size=size) @@ -1480,7 +1486,7 @@ def bilinear_interp_layer(input, bilinear_interp=BilinearInterp( out_size_x=out_size_x, out_size_y=out_size_y, - num_channels=num_channels)), + channels=num_channels)), type=LayerType.BILINEAR_INTERP_LAYER, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( @@ -1908,8 +1914,7 @@ def img_pool_layer(input, layer_attr=None, pool_size_y=None, stride_y=None, - padding_y=None, - img_width=None): + padding_y=None): """ Image pooling Layer. @@ -1940,9 +1945,6 @@ def img_pool_layer(input, :type stride_y: int|None :param layer_attr: Extra Layer attribute. :type layer_attr: ExtraLayerAttribute - :param img_width: the width of input feature map. If it is None, the input feature - map should be square. - :type img_width: int|None :return: LayerOutput object. :rtype: LayerOutput """ @@ -1978,8 +1980,7 @@ def img_pool_layer(input, padding=padding, size_y=pool_size_y, stride_y=stride_y, - padding_y=padding_y, - img_width=img_width)) + padding_y=padding_y)) ], **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( @@ -1997,7 +1998,6 @@ def spp_layer(input, num_channels=None, pool_type=None, pyramid_height=None, - img_width=None, layer_attr=None): """ Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition. @@ -2014,9 +2014,6 @@ def spp_layer(input, :type scale: BasePoolingType :param pyramid_height: pyramid height. :type pyramid_height: int - :param img_width: the width of input feature map. If it is None, the input feature - map should be square. - :type img_width: int|None :param layer_attr: Extra Layer Attribute. :type layer_attr: ExtraLayerAttribute :return: LayerOutput object. @@ -2043,8 +2040,7 @@ def spp_layer(input, spp=SpatialPyramidPool( pool_type=type_name, channels=num_channels, - pyramid_height=pyramid_height, - img_width=img_width)), + pyramid_height=pyramid_height)), **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( name, 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 1f262af211..1a577b8d9b 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 @@ -26,11 +26,15 @@ layers { filter_size_y: 32 padding_y: 1 stride_y: 1 + output_y: 227 + img_size_y: 256 } } bias_parameter_name: "___conv_0__.wbias" num_filters: 64 shared_biases: true + height: 227 + width: 227 } layers { name: "__batch_norm_0__" @@ -43,6 +47,7 @@ layers { image_conf { channels: 64 img_size: 227 + img_size_y: 227 } } inputs { @@ -55,6 +60,8 @@ layers { } bias_parameter_name: "___batch_norm_0__.wbias" moving_average_fraction: 0.9 + height: 227 + width: 227 } layers { name: "__crmnorm_0__" @@ -72,8 +79,12 @@ layers { output_x: 227 img_size: 227 blocked: false + output_y: 227 + img_size_y: 227 } } + height: 227 + width: 227 } layers { name: "__pool_0__" @@ -97,6 +108,8 @@ layers { padding_y: 0 } } + height: 196 + width: 196 } parameters { name: "___conv_0__.w0" 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 3834635408..ac1e2adff5 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 @@ -26,6 +26,8 @@ layers { filter_size_y: 32 padding_y: 1 stride_y: 1 + output_y: 227 + img_size_y: 198 } } bias_parameter_name: "___conv_0__.wbias" @@ -43,6 +45,7 @@ layers { image_conf { channels: 64 img_size: 256 + img_size_y: 256 } } inputs { @@ -55,6 +58,8 @@ layers { } bias_parameter_name: "___batch_norm_0__.wbias" moving_average_fraction: 0.9 + height: 256 + width: 256 } layers { name: "__crmnorm_0__" @@ -72,8 +77,12 @@ layers { output_x: 256 img_size: 256 blocked: false + output_y: 256 + img_size_y: 256 } } + height: 256 + width: 256 } layers { name: "__pool_0__" @@ -97,6 +106,8 @@ layers { padding_y: 0 } } + height: 225 + width: 225 } parameters { name: "___conv_0__.w0" diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/projections.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/projections.protostr index 2b3951c242..2943ab130b 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/projections.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/projections.protostr @@ -177,6 +177,8 @@ layers { filter_size_y: 3 padding_y: 0 stride_y: 1 + output_y: 30 + img_size_y: 32 } num_filters: 64 } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr index 13d0d477eb..9fae596f28 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr @@ -26,11 +26,15 @@ layers { filter_size_y: 3 padding_y: 1 stride_y: 1 + output_y: 48 + img_size_y: 48 } } bias_parameter_name: "___conv_0__.wbias" num_filters: 16 shared_biases: true + height: 48 + width: 48 } layers { name: "__bilinear_interp_layer_0__" @@ -40,11 +44,17 @@ layers { inputs { input_layer_name: "__conv_0__" bilinear_interp_conf { + image_conf { + channels: 16 + img_size: 48 + img_size_y: 48 + } out_size_x: 64 out_size_y: 64 - num_channels: 16 } } + height: 64 + width: 64 } layers { name: "__pool_0__" @@ -55,19 +65,21 @@ layers { input_layer_name: "__bilinear_interp_layer_0__" pool_conf { pool_type: "max-projection" - channels: 4 + channels: 16 size_x: 2 stride: 2 - output_x: 64 - img_size: 128 + output_x: 32 + img_size: 64 padding: 0 size_y: 2 stride_y: 2 - output_y: 64 - img_size_y: 128 + output_y: 32 + img_size_y: 64 padding_y: 0 } } + height: 32 + width: 32 } layers { name: "__fc_layer_0__" @@ -78,6 +90,8 @@ layers { input_layer_name: "__pool_0__" input_parameter_name: "___fc_layer_0__.w0" } + height: 32 + width: 32 } parameters { name: "___conv_0__.w0" diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr index 1be2a7ceeb..c763a95f9d 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr @@ -4,6 +4,8 @@ layers { type: "data" size: 2304 active_type: "" + height: 48 + width: 48 } layers { name: "__conv_0__" @@ -26,11 +28,15 @@ layers { filter_size_y: 3 padding_y: 1 stride_y: 1 + output_y: 48 + img_size_y: 48 } } bias_parameter_name: "___conv_0__.wbias" num_filters: 16 shared_biases: true + height: 48 + width: 48 } layers { name: "__maxout_layer_0__" @@ -40,12 +46,16 @@ layers { inputs { input_layer_name: "__conv_0__" maxout_conf { - channels: 16 + image_conf { + channels: 16 + img_size: 48 + img_size_y: 48 + } groups: 2 - img_size_x: 0 - img_size_y: 0 } } + height: 48 + width: 48 } layers { name: "__pool_0__" @@ -69,48 +79,58 @@ layers { padding_y: 0 } } + height: 24 + width: 24 } layers { name: "__conv_1__" type: "exconv" - size: 18432 + size: 73728 active_type: "" inputs { input_layer_name: "__pool_0__" input_parameter_name: "___conv_1__.w0" conv_conf { filter_size: 3 - channels: 32 + channels: 8 stride: 1 padding: 1 groups: 1 - filter_channels: 32 - output_x: 12 - img_size: 12 + filter_channels: 8 + output_x: 24 + img_size: 24 caffe_mode: true filter_size_y: 3 padding_y: 1 stride_y: 1 + output_y: 24 + img_size_y: 24 } } bias_parameter_name: "___conv_1__.wbias" num_filters: 128 shared_biases: true + height: 24 + width: 24 } layers { name: "__maxout_layer_1__" type: "maxout" - size: 9216 + size: 18432 active_type: "" inputs { - input_layer_name: "__conv_0__" + input_layer_name: "__conv_1__" maxout_conf { - channels: 128 + image_conf { + channels: 128 + img_size: 24 + img_size_y: 24 + } groups: 4 - img_size_x: 0 - img_size_y: 0 } } + height: 24 + width: 24 } layers { name: "__block_expand_layer_0__" @@ -118,7 +138,7 @@ layers { size: 192 active_type: "" inputs { - input_layer_name: "__maxout_layer_0__" + input_layer_name: "__maxout_layer_1__" block_expand_conf { channels: 32 stride_x: 1 @@ -133,6 +153,8 @@ layers { img_size_y: 0 } } + height: 24 + width: 24 } layers { name: "__fc_layer_0__" @@ -143,6 +165,8 @@ layers { input_layer_name: "__block_expand_layer_0__" input_parameter_name: "___fc_layer_0__.w0" } + height: 24 + width: 24 } parameters { name: "___conv_0__.w0" @@ -164,9 +188,9 @@ parameters { } parameters { name: "___conv_1__.w0" - size: 36864 + size: 9216 initial_mean: 0.0 - initial_std: 0.0833333333333 + initial_std: 0.166666666667 initial_strategy: 0 initial_smart: false } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_spp_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_spp_layer.protostr index 8b0a8f2146..ca1b2d8cff 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_spp_layer.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_spp_layer.protostr @@ -4,6 +4,8 @@ layers { type: "data" size: 3200 active_type: "" + height: 20 + width: 10 } layers { name: "__spp_0__" @@ -13,13 +15,17 @@ layers { inputs { input_layer_name: "data" spp_conf { + image_conf { + channels: 16 + img_size: 10 + img_size_y: 20 + } pool_type: "max-projection" pyramid_height: 2 - channels: 16 - img_size: 10 - img_size_y: 20 } } + height: 1 + width: 5 } input_layer_names: "data" output_layer_names: "__spp_0__" diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_bilinear_interp.py b/python/paddle/trainer_config_helpers/tests/configs/test_bilinear_interp.py index e15a55b412..be83f4f83c 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_bilinear_interp.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_bilinear_interp.py @@ -17,7 +17,7 @@ bilinear = bilinear_interp_layer(input=conv, out_size_x=64, out_size_y=64) pool = img_pool_layer( input=bilinear, - num_channels=4, + num_channels=16, pool_size=2, stride=2, pool_type=MaxPooling()) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_maxout.py b/python/paddle/trainer_config_helpers/tests/configs/test_maxout.py index 081430d716..eb14270baa 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_maxout.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_maxout.py @@ -2,7 +2,7 @@ from paddle.trainer_config_helpers import * settings(batch_size=1000, learning_rate=1e-5) -data = data_layer(name='data', size=2304) +data = data_layer(name='data', size=2304, height=48, width=48) conv = img_conv_layer( input=data, @@ -21,16 +21,21 @@ pool = img_pool_layer( conv2 = img_conv_layer( input=pool, filter_size=3, - num_channels=32, + num_channels=8, num_filters=128, padding=1, act=LinearActivation(), bias_attr=True) -maxout2 = maxout_layer(input=conv, num_channels=128, groups=4) +maxout2 = maxout_layer(input=conv2, num_channels=128, groups=4) block = block_expand_layer( - input=maxout, num_channels=32, stride_x=1, stride_y=1, block_x=1, block_y=6) + input=maxout2, + num_channels=32, + stride_x=1, + stride_y=1, + block_x=1, + block_y=6) fc = fc_layer(input=block, size=384, bias_attr=False) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_spp_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_spp_layer.py index e20ffb584e..e0b0d0d3be 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_spp_layer.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_spp_layer.py @@ -2,13 +2,9 @@ from paddle.trainer_config_helpers import * settings(batch_size=100, learning_rate=1e-5) -data = data_layer(name='data', size=3200) +data = data_layer(name='data', size=3200, height=20, width=10) spp = spp_layer( - input=data, - pyramid_height=2, - num_channels=16, - pool_type=MaxPooling(), - img_width=10) + input=data, pyramid_height=2, num_channels=16, pool_type=MaxPooling()) outputs(spp) -- GitLab From 2c84c1ecfbb90ed865a9cd82ac6c9a6d388014b3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 14:18:55 +0800 Subject: [PATCH 0044/1503] Add profiler object and update docs --- CMakeLists.txt | 5 + doc/optimization/gpu_profiling.rst | 175 ++++++++++++++++++++----- paddle/cuda/src/hl_cuda_device.cc | 2 + paddle/math/tests/test_GpuProfiler.cpp | 14 +- paddle/utils/Stat.cpp | 1 + paddle/utils/Stat.h | 20 +++ 6 files changed, 180 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1fdae3b5c..8b62f29787 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ option(WITH_RDMA "Compile PaddlePaddle with rdma support" OFF) option(WITH_GLOG "Compile PaddlePaddle use glog, otherwise use a log implement internally" ${LIBGLOG_FOUND}) option(WITH_GFLAGS "Compile PaddlePaddle use gflags, otherwise use a flag implement internally" ${GFLAGS_FOUND}) option(WITH_TIMER "Compile PaddlePaddle use timer" OFF) +option(WITH_PROFILER "Compile PaddlePaddle use gpu profiler" OFF) option(WITH_TESTING "Compile and run unittest for PaddlePaddle" ${GTEST_FOUND}) option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) option(WITH_SWIG_PY "Compile PaddlePaddle with py PaddlePaddle prediction api" ${SWIG_FOUND}) @@ -134,6 +135,10 @@ if(NOT WITH_TIMER) add_definitions(-DPADDLE_DISABLE_TIMER) endif(NOT WITH_TIMER) +if(NOT WITH_PROFILER) + add_definitions(-DPADDLE_DISABLE_PROFILER) +endif(NOT WITH_PROFILER) + if(WITH_AVX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AVX_FLAG}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AVX_FLAG}") diff --git a/doc/optimization/gpu_profiling.rst b/doc/optimization/gpu_profiling.rst index efdf5552c3..37dc7dfe3b 100644 --- a/doc/optimization/gpu_profiling.rst +++ b/doc/optimization/gpu_profiling.rst @@ -1,7 +1,7 @@ -GPU Profiling -============= +Profiling on PaddlePaddle +========================= -This tutorial will guide you step-by-step through how to conduct profiling and performance tuning using :code:`nvprof` and :code:`nvvp`. +This tutorial will guide you step-by-step through how to conduct profiling and performance tuning using built-in timer, **nvprof** and **nvvp**. - What is profiling? - Why we need profiling? @@ -45,73 +45,182 @@ Profiler Tools ============== For general GPU profiling, a bunch of tools are provided from both NVIDIA and third party. -:code:`nvprof` is Nvidia profiler and :code:`nvvp` is (GUI based) Nvidia visual profiler. +**nvprof** is Nvidia profiler and **nvvp** is (GUI based) Nvidia visual profiler. In this tutorial, we will focus on nvprof and nvvp. :code:`test_GpuProfiler` from :code:`paddle/math/tests` directory will be used to evaluate above profilers. -.. code-block:: c++ +.. literalinclude:: ../../paddle/math/tests/test_GpuProfiler.cpp + :language: c++ + :lines: 107-121 + :linenos: - TEST(Profiler, BilinearFwdBwd) { - hl_profiler_start(); - auto numSamples = 10; - auto channels = 16; - auto imgSize = 64; - testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); - hl_profiler_end(); - } +The above code snippet includes two methods, you can use any of them to profile the regions of interest. -:code:`hl_profiler_start` and :code:`hl_profiler_end` can be used to profile only regions of interest -in PaddlePaddle. They are wrapper functions of :code:`cudaProfilerStart` and :code:`cudaProfilerStop` -respectively to avoid program crashes when CPU version of PaddlePaddle invokes them. +1. :code:`REGISTER_TIMER_INFO` is a built-in timer wrapper which can calculate the time overhead of both cpu functions and cuda kernels. +2. :code:`REGISTER_GPU_PROFILER` is a general purpose wrapper object of :code:`cudaProfilerStart` and :code:`cudaProfilerStop` to avoid +program crashes when CPU version of PaddlePaddle invokes them. + +You can find all the gory details about how to use both of them in the next session. Hands-on Approach ================= -To use this command line profiler :code:`nvprof`, you can simply issue the command: +Built-in Timer +-------------- -.. code-block:: bash +To enable built-in timer in PaddlePaddle, first you have to add :code:`REGISTER_TIMER_INFO` into the regions of you interest. +Then, all information could be stamped in the console via :code:`printStatus` or :code:`printAllStatus` function. +As a simple example, consider the following: + +1. Add :code:`REGISTER_TIMER_INFO` and :code:`printStatus` functions (see the emphasize-lines). + + .. literalinclude:: ../../paddle/math/tests/test_GpuProfiler.cpp + :language: c++ + :lines: 107-121 + :emphasize-lines: 10-11,14 + :linenos: + +2. Configure cmake with **WITH_TIMER** and recompile PaddlePaddle. + + .. code-block:: bash + + cmake .. -DWITH_TIMER=ON + make + +3. Execute your code and observe the results (see the emphasize-lines). + + .. code-block:: bash + :emphasize-lines: 1,12-15 + + > ./paddle/math/tests/test_GpuProfiler + I1117 11:13:42.313065 2522362816 Util.cpp:155] commandline: ./paddle/math/tests/test_GpuProfiler + I1117 11:13:42.845065 2522362816 Util.cpp:130] Calling runInitFunctions + I1117 11:13:42.845208 2522362816 Util.cpp:143] Call runInitFunctions done. + [==========] Running 1 test from 1 test case. + [----------] Global test environment set-up. + [----------] 1 test from Profiler + [ RUN ] Profiler.BilinearFwdBwd + I1117 11:13:42.845310 2522362816 test_GpuProfiler.cpp:114] Enable GPU Profiler Stat: [testBilinearFwdBwd] "numSamples = 10, channels = 16, im + gSizeX = 64, imgSizeY = 64" + I1117 11:13:42.850154 2522362816 ThreadLocal.cpp:37] thread use undeterministic rand seed:20659751 + I1117 11:13:42.981501 2522362816 Stat.cpp:130] ======= StatSet: [GlobalStatInfo] status ====== + I1117 11:13:42.981539 2522362816 Stat.cpp:133] Stat=testBilinearFwdBwd total=136.141 avg=136.141 max=136.141 min=136.141 count=1 + I1117 11:13:42.981572 2522362816 Stat.cpp:141] ======= BarrierStatSet status ====== + I1117 11:13:42.981575 2522362816 Stat.cpp:154] -------------------------------------------------- + [ OK ] Profiler.BilinearFwdBwd (136 ms) + [----------] 1 test from Profiler (136 ms total) + + [----------] Global test environment tear-down + [==========] 1 test from 1 test case ran. (136 ms total) + [ PASSED ] 1 test. + +nvprof profiler +--------------- - nvprof ./paddle/math/tests/test_GpuProfiler +To use this command line profiler **nvprof**, you can simply issue the following command: + +1. Add :code:`REGISTER_GPU_PROFILER` function (see the emphasize-lines). + + .. literalinclude:: ../../paddle/math/tests/test_GpuProfiler.cpp + :language: c++ + :lines: 107-121 + :emphasize-lines: 7-8 + :linenos: + +2. Configure cmake with **WITH_PROFILER** and recompile PaddlePaddle. + + .. code-block:: bash + + cmake .. -DWITH_PROFILER=ON + make + +3. Use Nvidia profiler **nvprof** to profile the binary. + + .. code-block:: bash + + nvprof ./paddle/math/tests/test_GpuProfiler Then, you can get the following profiling result: -.. image:: nvprof.png - :align: center - :scale: 30% +.. code-block:: bash -For visual profiler :code:`nvvp`, you can either import the output of :code:`nvprof –o ...` or + ==78544== Profiling application: ./paddle/math/tests/test_GpuProfiler + ==78544== Profiling result: + Time(%) Time Calls Avg Min Max Name + 27.60% 9.6305ms 5 1.9261ms 3.4560us 6.4035ms [CUDA memcpy HtoD] + 26.07% 9.0957ms 1 9.0957ms 9.0957ms 9.0957ms KeBilinearInterpBw + 23.78% 8.2977ms 1 8.2977ms 8.2977ms 8.2977ms KeBilinearInterpFw + 22.55% 7.8661ms 2 3.9330ms 1.5798ms 6.2863ms [CUDA memcpy DtoH] + + ==78544== API calls: + Time(%) Time Calls Avg Min Max Name + 46.85% 682.28ms 8 85.285ms 12.639us 682.03ms cudaStreamCreateWithFlags + 39.83% 580.00ms 4 145.00ms 302ns 550.27ms cudaFree + 9.82% 143.03ms 9 15.892ms 8.7090us 142.78ms cudaStreamCreate + 1.23% 17.983ms 7 2.5690ms 23.210us 6.4563ms cudaMemcpy + 1.23% 17.849ms 2 8.9247ms 8.4726ms 9.3768ms cudaStreamSynchronize + 0.66% 9.5969ms 7 1.3710ms 288.43us 2.4279ms cudaHostAlloc + 0.13% 1.9530ms 11 177.54us 7.6810us 591.06us cudaMalloc + 0.07% 1.0424ms 8 130.30us 1.6970us 453.72us cudaGetDevice + 0.04% 527.90us 40 13.197us 525ns 253.99us cudaEventCreateWithFlags + 0.03% 435.73us 348 1.2520us 124ns 42.704us cuDeviceGetAttribute + 0.03% 419.36us 1 419.36us 419.36us 419.36us cudaGetDeviceCount + 0.02% 260.75us 2 130.38us 129.32us 131.43us cudaGetDeviceProperties + 0.02% 222.32us 2 111.16us 106.94us 115.39us cudaLaunch + 0.01% 214.06us 4 53.514us 28.586us 77.655us cuDeviceGetName + 0.01% 115.45us 4 28.861us 9.8250us 44.526us cuDeviceTotalMem + 0.01% 83.988us 4 20.997us 578ns 77.760us cudaSetDevice + 0.00% 38.918us 1 38.918us 38.918us 38.918us cudaEventCreate + 0.00% 34.573us 31 1.1150us 279ns 12.784us cudaDeviceGetAttribute + 0.00% 17.767us 1 17.767us 17.767us 17.767us cudaProfilerStart + 0.00% 15.228us 2 7.6140us 3.5460us 11.682us cudaConfigureCall + 0.00% 14.536us 2 7.2680us 1.1490us 13.387us cudaGetLastError + 0.00% 8.6080us 26 331ns 173ns 783ns cudaSetupArgument + 0.00% 5.5470us 6 924ns 215ns 2.6780us cuDeviceGet + 0.00% 5.4090us 6 901ns 328ns 3.3320us cuDeviceGetCount + 0.00% 4.1770us 3 1.3920us 1.0630us 1.8300us cuDriverGetVersion + 0.00% 3.4650us 3 1.1550us 1.0810us 1.2680us cuInit + 0.00% 830ns 1 830ns 830ns 830ns cudaRuntimeGetVersion + + +nvvp profiler +------------- + +For visual profiler **nvvp**, you can either import the output of :code:`nvprof –o ...` or run application through GUI. +**Note: nvvp also support CPU profiling** (Click the box in nvvp to enable profile execution on CPU). + .. image:: nvvp1.png :align: center - :scale: 30% + :scale: 33% -From the perspective of kernel functions, :code:`nvvp` can even illustrate why does an operation take a long time? +From the perspective of kernel functions, **nvvp** can even illustrate why does an operation take a long time? As shown in the following figure, kernel's block usage, register usage and shared memory usage from :code:`nvvp` -allow us to fully utilize all warps on the GPU. +allow us to fully utilize all warps on the GPU. .. image:: nvvp2.png :align: center - :scale: 30% + :scale: 33% -From the perspective of application, :code:`nvvp` can give you some suggestions to address performance bottleneck. +From the perspective of application, **nvvp** can give you some suggestions to address performance bottleneck. For instance, some advice in data movement and compute utilization from the below figure can guide you to tune performance. .. image:: nvvp3.png :align: center - :scale: 30% + :scale: 33% .. image:: nvvp4.png :align: center - :scale: 30% + :scale: 33% Profiling tips ============== -- The :code:`nvprof` and :code:`nvvp` output is a very good place to start -- The timeline is a good place to go next +- The **nvprof** and **nvvp** output is a very good place to start. +- The timeline is a good place to go next. - Only dig deep into a kernel if it’s taking a significant amount of your time. - Where possible, try to match profiler output with theory. 1) For example, if I know I’m moving 1GB, and my kernel takes 10ms, I expect the profiler to report 100GB/s. @@ -119,7 +228,7 @@ Profiling tips - Know your hardware: If your GPU can do 6 TFLOPs, and you’re already doing 5.5 TFLOPs, you won’t go much faster! -Profiling is a key step in optimisation. Sometimes quite simple changes can lead to big improvements in performance. +Profiling is a key step in optimization. Sometimes quite simple changes can lead to big improvements in performance. Your mileage may vary! Reference diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 0f45462adc..aa1d184a3e 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -762,6 +762,8 @@ bool hl_cuda_event_is_ready(hl_event_t event) { void hl_profiler_start() { CHECK_CUDA(dynload::cudaProfilerStart()); } + void hl_profiler_end() { CHECK_CUDA(dynload::cudaProfilerStop()); } + diff --git a/paddle/math/tests/test_GpuProfiler.cpp b/paddle/math/tests/test_GpuProfiler.cpp index 7645447789..ea1bd2481e 100644 --- a/paddle/math/tests/test_GpuProfiler.cpp +++ b/paddle/math/tests/test_GpuProfiler.cpp @@ -20,7 +20,6 @@ limitations under the License. */ #include #include "paddle/gserver/tests/TestUtil.h" #include "paddle/utils/Stat.h" -#include "hl_cuda.h" using namespace paddle; // NOLINT using namespace std; // NOLINT @@ -106,12 +105,19 @@ void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, } TEST(Profiler, BilinearFwdBwd) { - hl_profiler_start(); auto numSamples = 10; auto channels = 16; auto imgSize = 64; - testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); - hl_profiler_end(); + { + // nvprof: GPU Proflier + REGISTER_GPU_PROFILER("testBilinearFwdBwd", + "numSamples = 10, channels = 16, imgSizeX = 64, imgSizeY = 64"); + // Paddle built-in timer + REGISTER_TIMER_INFO("testBilinearFwdBwd", + "numSamples = 10, channels = 16, imgSizeX = 64, imgSizeY = 64"); + testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); + } + globalStat.printStatus("testBilinearFwdBwd"); } int main(int argc, char** argv) { diff --git a/paddle/utils/Stat.cpp b/paddle/utils/Stat.cpp index d7b20ca5eb..733fc7f1a7 100644 --- a/paddle/utils/Stat.cpp +++ b/paddle/utils/Stat.cpp @@ -65,6 +65,7 @@ std::ostream& operator<<(std::ostream& outPut, const Stat& stat) { auto showStat = [&](const StatInfo* info, pid_t tid, bool isFirst = true) { uint64_t average = 0; if (info->count_ > 0) { + outPut << std::setfill(' ') << std::left; if (!isFirst) { outPut << std::setw(42) << " "; } diff --git a/paddle/utils/Stat.h b/paddle/utils/Stat.h index 00e5aaec2b..8f65abb2d0 100644 --- a/paddle/utils/Stat.h +++ b/paddle/utils/Stat.h @@ -28,6 +28,7 @@ limitations under the License. */ #include "Locks.h" #include "ThreadLocal.h" #include "BarrierStat.h" +#include "hl_gpu.h" namespace paddle { @@ -280,4 +281,23 @@ inline StatSet& registerTimerArg2(uint64_t threshold = -1, #endif // DISABLE_TIMER +class GpuProfiler final { +public: + GpuProfiler() { hl_profiler_start(); } + ~GpuProfiler() { hl_profiler_end(); } +}; + +#ifdef PADDLE_DISABLE_PROFILER + +#define REGISTER_GPU_PROFILER(statName, ...) + +#else + +#define REGISTER_GPU_PROFILER(statName, ...) \ + LOG(INFO) << "Enable GPU Profiler Stat: [" \ + << statName << "] " << #__VA_ARGS__; \ + GpuProfiler __gpuProfiler; + +#endif // DISABLE_PROFILER + } // namespace paddle -- GitLab From 9670b9a1be2f2b1d668a0364549f8a240ec5009a Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 14:29:16 +0800 Subject: [PATCH 0045/1503] pre-commit hook check python style --- doc/optimization/gpu_profiling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/optimization/gpu_profiling.rst b/doc/optimization/gpu_profiling.rst index 37dc7dfe3b..013edb396e 100644 --- a/doc/optimization/gpu_profiling.rst +++ b/doc/optimization/gpu_profiling.rst @@ -62,7 +62,7 @@ The above code snippet includes two methods, you can use any of them to profile 2. :code:`REGISTER_GPU_PROFILER` is a general purpose wrapper object of :code:`cudaProfilerStart` and :code:`cudaProfilerStop` to avoid program crashes when CPU version of PaddlePaddle invokes them. -You can find all the gory details about how to use both of them in the next session. +You can find more details about how to use both of them in the next session. Hands-on Approach ================= -- GitLab From 8a50f79686839de58cf1ccde7c29da76b6c02224 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 17 Nov 2016 15:28:32 +0800 Subject: [PATCH 0046/1503] Convert markdown to rst for h-rnn --- doc_cn/algorithm/rnn/hierarchical-rnn.md | 403 -------------------- doc_cn/algorithm/rnn/hierarchical-rnn.rst | 440 ++++++++++++++++++++++ 2 files changed, 440 insertions(+), 403 deletions(-) delete mode 100644 doc_cn/algorithm/rnn/hierarchical-rnn.md create mode 100644 doc_cn/algorithm/rnn/hierarchical-rnn.rst diff --git a/doc_cn/algorithm/rnn/hierarchical-rnn.md b/doc_cn/algorithm/rnn/hierarchical-rnn.md deleted file mode 100644 index c184a34e85..0000000000 --- a/doc_cn/algorithm/rnn/hierarchical-rnn.md +++ /dev/null @@ -1,403 +0,0 @@ -# 双层RNN配置与示例 - -我们在`paddle/gserver/tests/test_RecurrentGradientMachine`单测中,通过多组语义相同的单双层RNN配置,讲解如何使用双层RNN。 - -## 示例1:双进双出,subseq间无memory - -配置:单层RNN(`sequence_layer_group`)和双层RNN(`sequence_nest_layer_group`),语义完全相同。 - -### 读取双层序列的方法 - -首先,我们看一下单双层序列的不同数据组织形式(您也可以采用别的组织形式): - -- 单层序列的数据(`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 - -```text -2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 -2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * -2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 -2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 -2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . -2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 -2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! -2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 -2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * -2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 -``` - -- 双层序列的数据(`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 - -```text -2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 -2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * - -2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 -2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 -2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . - -2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 -2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! - -2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 -2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * -2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 -``` - -其次,我们看一下单双层序列的不同dataprovider(见`sequenceGen.py`): - -- 单层序列的dataprovider如下: - - word_slot是integer_value_sequence类型,代表单层序列。 - - label是integer_value类型,代表一个向量。 - -```python -def hook(settings, dict_file, **kwargs): - settings.word_dict = dict_file - settings.input_types = [integer_value_sequence(len(settings.word_dict)), - integer_value(3)] - -@provider(init_hook=hook) -def process(settings, file_name): - with open(file_name, 'r') as fdata: - for line in fdata: - label, comment = line.strip().split('\t') - label = int(''.join(label.split())) - words = comment.split() - word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] - yield word_slot, label -``` - -- 双层序列的dataprovider如下: - - word_slot是integer_value_sub_sequence类型,代表双层序列。 - - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 - - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 - -```python -def hook2(settings, dict_file, **kwargs): - settings.word_dict = dict_file - settings.input_types = [integer_value_sub_sequence(len(settings.word_dict)), - integer_value_sequence(3)] - -@provider(init_hook=hook2) -def process2(settings, file_name): - with open(file_name) as fdata: - label_list = [] - word_slot_list = [] - for line in fdata: - if (len(line)) > 1: - label,comment = line.strip().split('\t') - label = int(''.join(label.split())) - words = comment.split() - word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] - label_list.append(label) - word_slot_list.append(word_slot) - else: - yield word_slot_list, label_list - label_list = [] - word_slot_list = [] -``` - -### 模型中的配置 - -首先,我们看一下单层序列的配置(见`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 - -```python -settings(batch_size=5) - -data = data_layer(name="word", size=dict_dim) - -emb = embedding_layer(input=data, size=word_dim) - -# (lstm_input + lstm) is equal to lstmemory -with mixed_layer(size=hidden_dim*4) as lstm_input: - lstm_input += full_matrix_projection(input=emb) - -lstm = lstmemory_group(input=lstm_input, - size=hidden_dim, - act=TanhActivation(), - gate_act=SigmoidActivation(), - state_act=TanhActivation(), - lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) - -lstm_last = last_seq(input=lstm) - -with mixed_layer(size=label_dim, - act=SoftmaxActivation(), - bias_attr=True) as output: - output += full_matrix_projection(input=lstm_last) - -outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) - -``` -其次,我们看一下语义相同的双层序列配置(见`sequence_nest_layer_group.conf`),并对其详细分析: - -- batchsize=2表示一次过2句双层序列。但从上面的数据格式可知,2句双层序列和5句单层序列的数据完全一样。 -- data_layer和embedding_layer不关心数据是否是序列格式,因此两个配置在这两层上的输出是一样的。 -- lstmemory: - - 单层序列过了一个mixed_layer和lstmemory_group。 - - 双层序列在同样的mixed_layer和lstmemory_group外,直接加了一层group。由于这个外层group里面没有memory,表示subseq间不存在联系,即起到的作用仅仅是把双层seq拆成单层,因此双层序列过完lstmemory的输出和单层的一样。 -- last_seq: - - 单层序列直接取了最后一个元素 - - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 - - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 - -```python -settings(batch_size=2) - -data = data_layer(name="word", size=dict_dim) - -emb_group = embedding_layer(input=data, size=word_dim) - -# (lstm_input + lstm) is equal to lstmemory -def lstm_group(lstm_group_input): - with mixed_layer(size=hidden_dim*4) as group_input: - group_input += full_matrix_projection(input=lstm_group_input) - - lstm_output = lstmemory_group(input=group_input, - name="lstm_group", - size=hidden_dim, - act=TanhActivation(), - gate_act=SigmoidActivation(), - state_act=TanhActivation(), - lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) - return lstm_output - -lstm_nest_group = recurrent_group(input=SubsequenceInput(emb_group), - step=lstm_group, - name="lstm_nest_group") -# hasSubseq ->(seqlastins) seq -lstm_last = last_seq(input=lstm_nest_group, agg_level=AggregateLevel.EACH_SEQUENCE) - -# seq ->(expand) hasSubseq -lstm_expand = expand_layer(input=lstm_last, expand_as=emb_group, expand_level=ExpandLevel.FROM_SEQUENCE) - -# hasSubseq ->(average) seq -lstm_average = pooling_layer(input=lstm_expand, - pooling_type=AvgPooling(), - agg_level=AggregateLevel.EACH_SEQUENCE) - -with mixed_layer(size=label_dim, - act=SoftmaxActivation(), - bias_attr=True) as output: - output += full_matrix_projection(input=lstm_average) - -outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) -``` -## 示例2:双进双出,subseq间有memory - -配置:单层RNN(`sequence_rnn.conf`),双层RNN(`sequence_nest_rnn.conf`和`sequence_nest_rnn_readonly_memory.conf`),语义完全相同。 - -### 读取双层序列的方法 - -我们看一下单双层序列的不同数据组织形式和dataprovider(见`rnn_data_provider.py`) -```python -data = [ - [[[1, 3, 2], [4, 5, 2]], 0], - [[[0, 2], [2, 5], [0, 1, 2]], 1], -] - -@provider(input_types=[integer_value_sub_sequence(10), - integer_value(3)]) -def process_subseq(settings, file_name): - for d in data: - yield d - -@provider(input_types=[integer_value_sequence(10), - integer_value(3)]) -def process_seq(settings, file_name): - for d in data: - seq = [] -``` -- 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 -- 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 -- 单双层序列的label都分别是0和1 - -### 模型中的配置 - -我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 - -- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 - -```python -def step(y): - mem = memory(name="rnn_state", size=hidden_dim) - return fc_layer(input=[y, mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name="rnn_state") - -out = recurrent_group(step=step, input=emb) -``` -- 双层序列,外层memory是一个元素: - - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 - - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 - -```python -def outer_step(x): - outer_mem = memory(name="outer_rnn_state", size=hidden_dim) - def inner_step(y): - inner_mem = memory(name="inner_rnn_state", - size=hidden_dim, - boot_layer=outer_mem) - return fc_layer(input=[y, inner_mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name="inner_rnn_state") - - inner_rnn_output = recurrent_group( - step=inner_step, - input=x) - last = last_seq(input=inner_rnn_output, name="outer_rnn_state") - - return inner_rnn_output - -out = recurrent_group(step=outer_step, input=SubsequenceInput(emb)) -``` -- 双层序列,外层memory是单层序列: - - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 - - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 - -## 示例3:双进双出,输入不等长 - -**输入不等长**是指recurrent_group的多个输入在各时刻的长度可以不相等, 但需要指定一个和输出长度一致的input,用targetInlink表示。参考配置:单层RNN(`sequence_rnn_multi_unequalength_inputs.conf`),双层RNN(`sequence_nest_rnn_multi_unequalength_inputs.conf`) - -### 读取双层序列的方法 - -我们看一下单双层序列的数据组织形式和dataprovider(见`rnn_data_provider.py`) -```python -data2 = [ - [[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]] ,0], - [[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]], 1], -] - -@provider(input_types=[integer_value_sub_sequence(10), - integer_value_sub_sequence(10), - integer_value(2)], - should_shuffle=False) -def process_unequalength_subseq(settings, file_name): #双层RNN的dataprovider - for d in data2: - yield d - - -@provider(input_types=[integer_value_sequence(10), - integer_value_sequence(10), - integer_value(2)], - should_shuffle=False) -def process_unequalength_seq(settings, file_name): #单层RNN的dataprovider - for d in data2: - words1=reduce(lambda x,y: x+y, d[0]) - words2=reduce(lambda x,y: x+y, d[1]) - yield words1, words2, d[2] -``` - -data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 - -- 单层序列:两个样本分别为[[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]] 和 [[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]] -- 双层序列:两个样本分别为 - - **样本1**:[[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]]]。fea1和fea2都分别有2个子句,fea1=[[1, 2], [4, 5, 2]], fea2=[[5, 4, 1], [3, 1]] - - **样本2**:[[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]]。fea1和fea2都分别有3个子句, fea1=[[0, 2], [2, 5], [0, 1, 2]], fea2=[[1, 5], [4], [2, 3, 6, 1]]。
    - - **注意**:每个样本中,各特征的子句数目需要相等。这里说的“双进双出,输入不等长”是指fea1在i时刻的输入的长度可以不等于fea2在i时刻的输入的长度。如对于第1个样本,时刻i=2, fea1[2]=[4, 5, 2],fea2[2]=[3, 1],3≠2。 -- 单双层序列中,两个样本的label都分别是0和1 - -### 模型中的配置 - -单层RNN(`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN(`sequence_nest_rnn_multi_unequalength_inputs.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 - -- 单层序列: - - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 - -```python -def step(x1, x2): - def calrnn(y): - mem = memory(name = 'rnn_state_' + y.name, size = hidden_dim) - out = fc_layer(input = [y, mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'rnn_state_' + y.name) - return out - - encoder1 = calrnn(x1) - encoder2 = calrnn(x2) - return [encoder1, encoder2] - -encoder1_rep, encoder2_rep = recurrent_group( - name="stepout", - step=step, - input=[emb1, emb2]) - -encoder1_last = last_seq(input = encoder1_rep) -encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) -context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) -``` -- 双层序列: - - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 - - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 - - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - -```python -def outer_step(x1, x2): - outer_mem1 = memory(name = "outer_rnn_state1", size = hidden_dim) - outer_mem2 = memory(name = "outer_rnn_state2", size = hidden_dim) - def inner_step1(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem1) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - def inner_step2(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem2) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - encoder1 = recurrent_group( - step = inner_step1, - name = 'inner1', - input = x1) - - encoder2 = recurrent_group( - step = inner_step2, - name = 'inner2', - input = x2) - - sentence_last_state1 = last_seq(input = encoder1, name = 'outer_rnn_state1') - sentence_last_state2_ = last_seq(input = encoder2, name = 'outer_rnn_state2') - - encoder1_expand = expand_layer(input = sentence_last_state1, - expand_as = encoder2) - - return [encoder1_expand, encoder2] - -encoder1_rep, encoder2_rep = recurrent_group( - name="outer", - step=outer_step, - input=[SubsequenceInput(emb1), SubsequenceInput(emb2)], - targetInlink=emb2) - -encoder1_last = last_seq(input = encoder1_rep) -encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) -context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) -``` - -## 示例4:beam_search的生成 - -TBD diff --git a/doc_cn/algorithm/rnn/hierarchical-rnn.rst b/doc_cn/algorithm/rnn/hierarchical-rnn.rst new file mode 100644 index 0000000000..7de54cc0b1 --- /dev/null +++ b/doc_cn/algorithm/rnn/hierarchical-rnn.rst @@ -0,0 +1,440 @@ +################# +双层RNN配置与示例 +################# + +我们在 :code:`paddle/gserver/tests/test_RecurrentGradientMachine` 单测中,通过多组语义相同的单双层RNN配置,讲解如何使用双层RNN。 + +示例1:双进双出,subseq间无memory +================================= + +配置:单层RNN(:code:`sequence_layer_group`)和双层RNN(:code:`sequence_nest_layer_group`),语义完全相同。 + +读取双层序列的方法 +------------------ + +首先,我们看一下单双层序列的不同数据组织形式(您也可以采用别的组织形式)\: + +- 单层序列的数据( :code:`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 + +.. code-block:: text + + 2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 + 2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * + 2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 + 2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 + 2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . + 2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 + 2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! + 2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 + 2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * + 2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 + + +- 双层序列的数据( :code:`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 + +.. code-block:: text + + 2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 + 2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * + + 2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 + 2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 + 2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . + + 2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 + 2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! + + 2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 + 2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * + 2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 + +其次,我们看一下单双层序列的不同dataprovider(见 :code:`sequenceGen.py` ): + +- 单层序列的dataprovider如下: + + - word_slot是integer_value_sequence类型,代表单层序列。 + - label是integer_value类型,代表一个向量。 + +.. code-block:: python + + def hook(settings, dict_file, **kwargs): + settings.word_dict = dict_file + settings.input_types = [integer_value_sequence(len(settings.word_dict)), + integer_value(3)] + + @provider(init_hook=hook) + def process(settings, file_name): + with open(file_name, 'r') as fdata: + for line in fdata: + label, comment = line.strip().split('\t') + label = int(''.join(label.split())) + words = comment.split() + word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] + yield word_slot, label + +- 双层序列的dataprovider如下: + + - word_slot是integer_value_sub_sequence类型,代表双层序列。 + - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 + - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 + +.. code-block:: python + + def hook2(settings, dict_file, **kwargs): + settings.word_dict = dict_file + settings.input_types = [integer_value_sub_sequence(len(settings.word_dict)), + integer_value_sequence(3)] + + @provider(init_hook=hook2) + def process2(settings, file_name): + with open(file_name) as fdata: + label_list = [] + word_slot_list = [] + for line in fdata: + if (len(line)) > 1: + label,comment = line.strip().split('\t') + label = int(''.join(label.split())) + words = comment.split() + word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] + label_list.append(label) + word_slot_list.append(word_slot) + else: + yield word_slot_list, label_list + label_list = [] + word_slot_list = [] + + +模型中的配置 +------------ + +首先,我们看一下单层序列的配置(见 :code:`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 + +.. code-block:: python + + settings(batch_size=5) + + data = data_layer(name="word", size=dict_dim) + + emb = embedding_layer(input=data, size=word_dim) + + # (lstm_input + lstm) is equal to lstmemory + with mixed_layer(size=hidden_dim*4) as lstm_input: + lstm_input += full_matrix_projection(input=emb) + + lstm = lstmemory_group(input=lstm_input, + size=hidden_dim, + act=TanhActivation(), + gate_act=SigmoidActivation(), + state_act=TanhActivation(), + lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) + + lstm_last = last_seq(input=lstm) + + with mixed_layer(size=label_dim, + act=SoftmaxActivation(), + bias_attr=True) as output: + output += full_matrix_projection(input=lstm_last) + + outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) + + +其次,我们看一下语义相同的双层序列配置(见 :code:`sequence_nest_layer_group.conf` ),并对其详细分析: + +- batchsize=2表示一次过2句双层序列。但从上面的数据格式可知,2句双层序列和5句单层序列的数据完全一样。 +- data_layer和embedding_layer不关心数据是否是序列格式,因此两个配置在这两层上的输出是一样的。 +- lstmemory\: + + - 单层序列过了一个mixed_layer和lstmemory_group。 + - 双层序列在同样的mixed_layer和lstmemory_group外,直接加了一层group。由于这个外层group里面没有memory,表示subseq间不存在联系,即起到的作用仅仅是把双层seq拆成单层,因此双层序列过完lstmemory的输出和单层的一样。 + +- last_seq\: + + - 单层序列直接取了最后一个元素 + - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 + - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 + +.. code-block:: python + + settings(batch_size=2) + + data = data_layer(name="word", size=dict_dim) + + emb_group = embedding_layer(input=data, size=word_dim) + + # (lstm_input + lstm) is equal to lstmemory + def lstm_group(lstm_group_input): + with mixed_layer(size=hidden_dim*4) as group_input: + group_input += full_matrix_projection(input=lstm_group_input) + + lstm_output = lstmemory_group(input=group_input, + name="lstm_group", + size=hidden_dim, + act=TanhActivation(), + gate_act=SigmoidActivation(), + state_act=TanhActivation(), + lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) + return lstm_output + + lstm_nest_group = recurrent_group(input=SubsequenceInput(emb_group), + step=lstm_group, + name="lstm_nest_group") + # hasSubseq ->(seqlastins) seq + lstm_last = last_seq(input=lstm_nest_group, agg_level=AggregateLevel.EACH_SEQUENCE) + + # seq ->(expand) hasSubseq + lstm_expand = expand_layer(input=lstm_last, expand_as=emb_group, expand_level=ExpandLevel.FROM_SEQUENCE) + + # hasSubseq ->(average) seq + lstm_average = pooling_layer(input=lstm_expand, + pooling_type=AvgPooling(), + agg_level=AggregateLevel.EACH_SEQUENCE) + + with mixed_layer(size=label_dim, + act=SoftmaxActivation(), + bias_attr=True) as output: + output += full_matrix_projection(input=lstm_average) + + outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) + +示例2:双进双出,subseq间有memory +================================= + +配置:单层RNN( :code:`sequence_rnn.conf` ),双层RNN( :code:`sequence_nest_rnn.conf` 和 :code:`sequence_nest_rnn_readonly_memory.conf` ),语义完全相同。 + +读取双层序列的方法 +------------------ + +我们看一下单双层序列的不同数据组织形式和dataprovider(见 :code:`rnn_data_provider.py`) + +.. code-block:: python + + data = [ + [[[1, 3, 2], [4, 5, 2]], 0], + [[[0, 2], [2, 5], [0, 1, 2]], 1], + ] + + @provider(input_types=[integer_value_sub_sequence(10), + integer_value(3)]) + def process_subseq(settings, file_name): + for d in data: + yield d + + @provider(input_types=[integer_value_sequence(10), + integer_value(3)]) + def process_seq(settings, file_name): + for d in data: + seq = [] + +- 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 +- 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 +- 单双层序列的label都分别是0和1 + +模型中的配置 +------------ + +我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 + +- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 + +.. code-block:: python + + def step(y): + mem = memory(name="rnn_state", size=hidden_dim) + return fc_layer(input=[y, mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name="rnn_state") + + out = recurrent_group(step=step, input=emb) + +- 双层序列,外层memory是一个元素: + - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 + - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 + +.. code-block:: + + def outer_step(x): + outer_mem = memory(name="outer_rnn_state", size=hidden_dim) + def inner_step(y): + inner_mem = memory(name="inner_rnn_state", + size=hidden_dim, + boot_layer=outer_mem) + return fc_layer(input=[y, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name="inner_rnn_state") + + inner_rnn_output = recurrent_group( + step=inner_step, + input=x) + last = last_seq(input=inner_rnn_output, name="outer_rnn_state") + + return inner_rnn_output + + out = recurrent_group(step=outer_step, input=SubsequenceInput(emb)) + +- 双层序列,外层memory是单层序列: + - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 + - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 + +示例3:双进双出,输入不等长 +=========================== + +.. role:: red + +.. raw:: html + + + +**输入不等长** 是指recurrent_group的多个输入在各时刻的长度可以不相等, 但需要指定一个和输出长度一致的input,用 :red:`targetInlink` 表示。参考配置:单层RNN(:code:`sequence_rnn_multi_unequalength_inputs.conf`),双层RNN(:code:`sequence_nest_rnn_multi_unequalength_inputs.conf`) + +读取双层序列的方法 +------------------ + +我们看一下单双层序列的数据组织形式和dataprovider(见`rnn_data_provider.py`) + +.. code-block:: python + + data2 = [ + [[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]] ,0], + [[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]], 1], + ] + + @provider(input_types=[integer_value_sub_sequence(10), + integer_value_sub_sequence(10), + integer_value(2)], + should_shuffle=False) + def process_unequalength_subseq(settings, file_name): #双层RNN的dataprovider + for d in data2: + yield d + + + @provider(input_types=[integer_value_sequence(10), + integer_value_sequence(10), + integer_value(2)], + should_shuffle=False) + def process_unequalength_seq(settings, file_name): #单层RNN的dataprovider + for d in data2: + words1=reduce(lambda x,y: x+y, d[0]) + words2=reduce(lambda x,y: x+y, d[1]) + yield words1, words2, d[2] + +data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 + +- 单层序列:两个样本分别为[[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]] 和 [[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]] +- 双层序列:两个样本分别为 + + - **样本1**\:[[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]]]。fea1和fea2都分别有2个子句,fea1=[[1, 2], [4, 5, 2]], fea2=[[5, 4, 1], [3, 1]] + - **样本2**\:[[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]]。fea1和fea2都分别有3个子句, fea1=[[0, 2], [2, 5], [0, 1, 2]], fea2=[[1, 5], [4], [2, 3, 6, 1]]。
    + - **注意**\:每个样本中,各特征的子句数目需要相等。这里说的“双进双出,输入不等长”是指fea1在i时刻的输入的长度可以不等于fea2在i时刻的输入的长度。如对于第1个样本,时刻i=2, fea1[2]=[4, 5, 2],fea2[2]=[3, 1],3≠2。 + +- 单双层序列中,两个样本的label都分别是0和1 + +模型中的配置 +------------ + +单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`sequence_nest_rnn_multi_unequalength_inputs.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 + +- 单层序列\: + + - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 + - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 + +.. code-block:: python + + def step(x1, x2): + def calrnn(y): + mem = memory(name = 'rnn_state_' + y.name, size = hidden_dim) + out = fc_layer(input = [y, mem], + size = hidden_dim, + act = TanhActivation(), + bias_attr = True, + name = 'rnn_state_' + y.name) + return out + + encoder1 = calrnn(x1) + encoder2 = calrnn(x2) + return [encoder1, encoder2] + + encoder1_rep, encoder2_rep = recurrent_group( + name="stepout", + step=step, + input=[emb1, emb2]) + + encoder1_last = last_seq(input = encoder1_rep) + encoder1_expandlast = expand_layer(input = encoder1_last, + expand_as = encoder2_rep) + context = mixed_layer(input = [identity_projection(encoder1_expandlast), + identity_projection(encoder2_rep)], + size = hidden_dim) + +- 双层序列\: + + - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 + - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 + - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 + +.. code-block:: python + + def outer_step(x1, x2): + outer_mem1 = memory(name = "outer_rnn_state1", size = hidden_dim) + outer_mem2 = memory(name = "outer_rnn_state2", size = hidden_dim) + def inner_step1(y): + inner_mem = memory(name = 'inner_rnn_state_' + y.name, + size = hidden_dim, + boot_layer = outer_mem1) + out = fc_layer(input = [y, inner_mem], + size = hidden_dim, + act = TanhActivation(), + bias_attr = True, + name = 'inner_rnn_state_' + y.name) + return out + + def inner_step2(y): + inner_mem = memory(name = 'inner_rnn_state_' + y.name, + size = hidden_dim, + boot_layer = outer_mem2) + out = fc_layer(input = [y, inner_mem], + size = hidden_dim, + act = TanhActivation(), + bias_attr = True, + name = 'inner_rnn_state_' + y.name) + return out + + encoder1 = recurrent_group( + step = inner_step1, + name = 'inner1', + input = x1) + + encoder2 = recurrent_group( + step = inner_step2, + name = 'inner2', + input = x2) + + sentence_last_state1 = last_seq(input = encoder1, name = 'outer_rnn_state1') + sentence_last_state2_ = last_seq(input = encoder2, name = 'outer_rnn_state2') + + encoder1_expand = expand_layer(input = sentence_last_state1, + expand_as = encoder2) + + return [encoder1_expand, encoder2] + + encoder1_rep, encoder2_rep = recurrent_group( + name="outer", + step=outer_step, + input=[SubsequenceInput(emb1), SubsequenceInput(emb2)], + targetInlink=emb2) + + encoder1_last = last_seq(input = encoder1_rep) + encoder1_expandlast = expand_layer(input = encoder1_last, + expand_as = encoder2_rep) + context = mixed_layer(input = [identity_projection(encoder1_expandlast), + identity_projection(encoder2_rep)], + size = hidden_dim) + +示例4:beam_search的生成 +======================== + +TBD -- GitLab From 285fdf58980fe7a85f6487a49eb39b2a8f5cf333 Mon Sep 17 00:00:00 2001 From: qingqing01 Date: Thu, 17 Nov 2016 15:22:00 +0800 Subject: [PATCH 0047/1503] Update sentiment_analysis.md --- demo/sentiment/trainer_config.py | 1 + doc/algorithm/rnn/rnn.rst | 2 +- .../sentiment_analysis/sentiment_analysis.md | 18 ++++++++++-------- doc/dev/new_layer/new_layer.rst | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/demo/sentiment/trainer_config.py b/demo/sentiment/trainer_config.py index 894070e7c9..114a9138eb 100644 --- a/demo/sentiment/trainer_config.py +++ b/demo/sentiment/trainer_config.py @@ -29,6 +29,7 @@ settings( batch_size=128, learning_rate=2e-3, learning_method=AdamOptimizer(), + average_window=0.5, regularization=L2Regularization(8e-4), gradient_clipping_threshold=25) diff --git a/doc/algorithm/rnn/rnn.rst b/doc/algorithm/rnn/rnn.rst index 399c5da5ff..01d2caefb5 100644 --- a/doc/algorithm/rnn/rnn.rst +++ b/doc/algorithm/rnn/rnn.rst @@ -17,7 +17,7 @@ PaddlePaddle does not need any preprocessing to sequence data, such as padding. .. code-block:: python - settings.slots = [ + settings.input_types = [ integer_value_sequence(len(settings.src_dict)), integer_value_sequence(len(settings.trg_dict)), integer_value_sequence(len(settings.trg_dict))] diff --git a/doc/demo/sentiment_analysis/sentiment_analysis.md b/doc/demo/sentiment_analysis/sentiment_analysis.md index 385f49891d..c53952c544 100644 --- a/doc/demo/sentiment_analysis/sentiment_analysis.md +++ b/doc/demo/sentiment_analysis/sentiment_analysis.md @@ -6,7 +6,7 @@ Sentiment analysis is also used to monitor social media based on large amount of On the other hand, grabbing the user comments of products and analyzing their sentiment are useful to understand user preferences for companies, products, even competing products. -This tutorial will guide you through the process of training a Long Short Term Memory (LSTM) Network to classify the sentiment of sentences from [Large Movie Review Dataset](http://ai.stanford.edu/~amaas/data/sentiment/), sometimes known as the [Internet Movie Database (IMDB)](http://ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf). This dataset contains movie reviews along with their associated binary sentiment polarity labels, namely positive and negative. So randomly guessing yields 50% accuracy. +This tutorial will guide you through the process of training a Long Short Term Memory (LSTM) Network to classify the sentiment of sentences from [Large Movie Review Dataset](http://ai.stanford.edu/~amaas/data/sentiment/), sometimes known as the Internet Movie Database (IMDB). This dataset contains movie reviews along with their associated binary sentiment polarity labels, namely positive and negative. So randomly guessing yields 50% accuracy. ## Data Preparation @@ -39,7 +39,7 @@ imdbEr.txt imdb.vocab README test train * imdbEr.txt: expected rating for each token in imdb.vocab. * README: data documentation. -Both train and test set directory contains: +The file in train set directory is as follows. The test set also contains them except `unsup` and `urls_unsup.txt`. ``` labeledBow.feat neg pos unsup unsupBow.feat urls_neg.txt urls_pos.txt urls_unsup.txt @@ -151,6 +151,7 @@ settings( batch_size=128, learning_rate=2e-3, learning_method=AdamOptimizer(), + average_window=0.5, regularization=L2Regularization(8e-4), gradient_clipping_threshold=25 ) @@ -163,17 +164,18 @@ stacked_lstm_net(dict_dim, class_dim=class_dim, * **Data Definition**: * get\_config\_arg(): get arguments setted by `--config_args=xx` in commandline argument. - * Define TrainData and TestData provider, here using Python interface (PyDataProviderWrapper) of PaddlePaddle to load data. For details, you can refer to the document of PyDataProvider. + * Define data provider, here using Python interface to load data. For details, you can refer to the document of PyDataProvider2. * **Algorithm Configuration**: - * use sgd algorithm. - * use adam optimization. * set batch size of 128. - * set average sgd window. * set global learning rate. + * use adam optimization. + * set average sgd window. + * set L2 regularization. + * set gradient clipping threshold. * **Network Configuration**: - * dict_dim: get dictionary dimension. - * class_dim: set category number, IMDB has two label, namely positive and negative label. + * dict_dim: dictionary dimension. + * class_dim: category number, IMDB has two label, namely positive and negative label. * `stacked_lstm_net`: predefined network as shown in Figure 3, use this network by default. * `bidirectional_lstm_net`: predefined network as shown in Figure 2. diff --git a/doc/dev/new_layer/new_layer.rst b/doc/dev/new_layer/new_layer.rst index 2fa0073048..af8b76a307 100644 --- a/doc/dev/new_layer/new_layer.rst +++ b/doc/dev/new_layer/new_layer.rst @@ -60,7 +60,7 @@ Implement C++ Class The C++ class of the layer implements the initialization, forward, and backward part of the layer. The fully connected layer is at :code:`paddle/gserver/layers/FullyConnectedLayer.h` and :code:`paddle/gserver/layers/FullyConnectedLayer.cpp`. We list simplified version of the code below. -It needs to derive the base class :code:`paddle::BaseLayer`, and it needs to override the following functions: +It needs to derive the base class :code:`paddle::Layer`, and it needs to override the following functions: - constructor and destructor. - :code:`init` function. It is used to initialize the parameters and settings. -- GitLab From f1955e2b2092d63f9acab07d8971ef2c5572170c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 17 Nov 2016 16:36:26 +0800 Subject: [PATCH 0048/1503] Remove copy & paste code. --- doc_cn/algorithm/rnn/hierarchical-rnn.rst | 337 +++------------------- 1 file changed, 38 insertions(+), 299 deletions(-) diff --git a/doc_cn/algorithm/rnn/hierarchical-rnn.rst b/doc_cn/algorithm/rnn/hierarchical-rnn.rst index 7de54cc0b1..7c81ce8c67 100644 --- a/doc_cn/algorithm/rnn/hierarchical-rnn.rst +++ b/doc_cn/algorithm/rnn/hierarchical-rnn.rst @@ -16,37 +16,14 @@ - 单层序列的数据( :code:`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 -.. code-block:: text - - 2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 - 2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * - 2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 - 2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 - 2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . - 2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 - 2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! - 2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 - 2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * - 2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 +.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg + :language: text - 双层序列的数据( :code:`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 -.. code-block:: text - - 2 酒店 有 很 舒适 的 床垫 子 , 床上用品 也 应该 是 一人 一 换 , 感觉 很 利落 对 卫生 很 放心 呀 。 - 2 很 温馨 , 也 挺 干净 的 * 地段 不错 , 出来 就 有 全家 , 离 地铁站 也 近 , 交通 很方便 * 就是 都 不 给 刷牙 的 杯子 啊 , 就 第一天 给 了 一次性杯子 * - - 2 位置 方便 , 强烈推荐 , 十一 出去玩 的 时候 选 的 , 对面 就是 华润万家 , 周围 吃饭 的 也 不少 。 - 2 交通便利 , 吃 很 便利 , 乾 浄 、 安静 , 商务 房 有 电脑 、 上网 快 , 价格 可以 , 就 早餐 不 好吃 。 整体 是 不错 的 。 適 合 出差 來 住 。 - 2 本来 准备 住 两 晚 , 第 2 天 一早 居然 停电 , 且 无 通知 , 只有 口头 道歉 。 总体来说 性价比 尚可 , 房间 较 新 , 还是 推荐 . - - 2 这个 酒店 去过 很多 次 了 , 选择 的 主要原因 是 离 客户 最 便宜 相对 又 近 的 酒店 - 2 挺好 的 汉庭 , 前台 服务 很 热情 , 卫生 很 整洁 , 房间 安静 , 水温 适中 , 挺好 ! - - 2 HowardJohnson 的 品质 , 服务 相当 好 的 一 家 五星级 。 房间 不错 、 泳池 不错 、 楼层 安排 很 合理 。 还有 就是 地理位置 , 简直 一 流 。 就 在 天一阁 、 月湖 旁边 , 离 天一广场 也 不远 。 下次 来 宁波 还会 住 。 - 2 酒店 很干净 , 很安静 , 很 温馨 , 服务员 服务 好 , 各方面 都 不错 * - 2 挺好 的 , 就是 没 窗户 , 不过 对 得 起 这 价格 +.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg.nest + :language: text 其次,我们看一下单双层序列的不同dataprovider(见 :code:`sequenceGen.py` ): @@ -55,22 +32,9 @@ - word_slot是integer_value_sequence类型,代表单层序列。 - label是integer_value类型,代表一个向量。 -.. code-block:: python - - def hook(settings, dict_file, **kwargs): - settings.word_dict = dict_file - settings.input_types = [integer_value_sequence(len(settings.word_dict)), - integer_value(3)] - - @provider(init_hook=hook) - def process(settings, file_name): - with open(file_name, 'r') as fdata: - for line in fdata: - label, comment = line.strip().split('\t') - label = int(''.join(label.split())) - words = comment.split() - word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] - yield word_slot, label +.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py + :language: python + :lines: 21-39 - 双层序列的dataprovider如下: @@ -78,64 +42,18 @@ - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 -.. code-block:: python - - def hook2(settings, dict_file, **kwargs): - settings.word_dict = dict_file - settings.input_types = [integer_value_sub_sequence(len(settings.word_dict)), - integer_value_sequence(3)] - - @provider(init_hook=hook2) - def process2(settings, file_name): - with open(file_name) as fdata: - label_list = [] - word_slot_list = [] - for line in fdata: - if (len(line)) > 1: - label,comment = line.strip().split('\t') - label = int(''.join(label.split())) - words = comment.split() - word_slot = [settings.word_dict[w] for w in words if w in settings.word_dict] - label_list.append(label) - word_slot_list.append(word_slot) - else: - yield word_slot_list, label_list - label_list = [] - word_slot_list = [] - +.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py + :language: python + :lines: 42-71 模型中的配置 ------------ 首先,我们看一下单层序列的配置(见 :code:`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 -.. code-block:: python - - settings(batch_size=5) - - data = data_layer(name="word", size=dict_dim) - - emb = embedding_layer(input=data, size=word_dim) - - # (lstm_input + lstm) is equal to lstmemory - with mixed_layer(size=hidden_dim*4) as lstm_input: - lstm_input += full_matrix_projection(input=emb) - - lstm = lstmemory_group(input=lstm_input, - size=hidden_dim, - act=TanhActivation(), - gate_act=SigmoidActivation(), - state_act=TanhActivation(), - lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) - - lstm_last = last_seq(input=lstm) - - with mixed_layer(size=label_dim, - act=SoftmaxActivation(), - bias_attr=True) as output: - output += full_matrix_projection(input=lstm_last) - - outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_layer_group.conf + :language: python + :lines: 38-63 其次,我们看一下语义相同的双层序列配置(见 :code:`sequence_nest_layer_group.conf` ),并对其详细分析: @@ -153,48 +71,9 @@ - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 -.. code-block:: python - - settings(batch_size=2) - - data = data_layer(name="word", size=dict_dim) - - emb_group = embedding_layer(input=data, size=word_dim) - - # (lstm_input + lstm) is equal to lstmemory - def lstm_group(lstm_group_input): - with mixed_layer(size=hidden_dim*4) as group_input: - group_input += full_matrix_projection(input=lstm_group_input) - - lstm_output = lstmemory_group(input=group_input, - name="lstm_group", - size=hidden_dim, - act=TanhActivation(), - gate_act=SigmoidActivation(), - state_act=TanhActivation(), - lstm_layer_attr=ExtraLayerAttribute(error_clipping_threshold=50)) - return lstm_output - - lstm_nest_group = recurrent_group(input=SubsequenceInput(emb_group), - step=lstm_group, - name="lstm_nest_group") - # hasSubseq ->(seqlastins) seq - lstm_last = last_seq(input=lstm_nest_group, agg_level=AggregateLevel.EACH_SEQUENCE) - - # seq ->(expand) hasSubseq - lstm_expand = expand_layer(input=lstm_last, expand_as=emb_group, expand_level=ExpandLevel.FROM_SEQUENCE) - - # hasSubseq ->(average) seq - lstm_average = pooling_layer(input=lstm_expand, - pooling_type=AvgPooling(), - agg_level=AggregateLevel.EACH_SEQUENCE) - - with mixed_layer(size=label_dim, - act=SoftmaxActivation(), - bias_attr=True) as output: - output += full_matrix_projection(input=lstm_average) - - outputs(classification_cost(input=output, label=data_layer(name="label", size=1))) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_layer_group.conf + :language: python + :lines: 38-84 示例2:双进双出,subseq间有memory ================================= @@ -206,24 +85,9 @@ 我们看一下单双层序列的不同数据组织形式和dataprovider(见 :code:`rnn_data_provider.py`) -.. code-block:: python - - data = [ - [[[1, 3, 2], [4, 5, 2]], 0], - [[[0, 2], [2, 5], [0, 1, 2]], 1], - ] - - @provider(input_types=[integer_value_sub_sequence(10), - integer_value(3)]) - def process_subseq(settings, file_name): - for d in data: - yield d - - @provider(input_types=[integer_value_sequence(10), - integer_value(3)]) - def process_seq(settings, file_name): - for d in data: - seq = [] +.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py + :language: python + :lines: 20-32 - 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 - 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 @@ -236,46 +100,21 @@ - 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 -.. code-block:: python - - def step(y): - mem = memory(name="rnn_state", size=hidden_dim) - return fc_layer(input=[y, mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name="rnn_state") - - out = recurrent_group(step=step, input=emb) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn.conf + :language: python + :lines: 36-48 - 双层序列,外层memory是一个元素: + - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 -.. code-block:: - - def outer_step(x): - outer_mem = memory(name="outer_rnn_state", size=hidden_dim) - def inner_step(y): - inner_mem = memory(name="inner_rnn_state", - size=hidden_dim, - boot_layer=outer_mem) - return fc_layer(input=[y, inner_mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name="inner_rnn_state") - - inner_rnn_output = recurrent_group( - step=inner_step, - input=x) - last = last_seq(input=inner_rnn_output, name="outer_rnn_state") - - return inner_rnn_output - - out = recurrent_group(step=outer_step, input=SubsequenceInput(emb)) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf + :language: python + :lines: 39-66 - 双层序列,外层memory是单层序列: + - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 @@ -293,33 +132,11 @@ 读取双层序列的方法 ------------------ -我们看一下单双层序列的数据组织形式和dataprovider(见`rnn_data_provider.py`) - -.. code-block:: python - - data2 = [ - [[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]] ,0], - [[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]], 1], - ] - - @provider(input_types=[integer_value_sub_sequence(10), - integer_value_sub_sequence(10), - integer_value(2)], - should_shuffle=False) - def process_unequalength_subseq(settings, file_name): #双层RNN的dataprovider - for d in data2: - yield d - +我们看一下单双层序列的数据组织形式和dataprovider(见 :code:`rnn_data_provider.py` ) - @provider(input_types=[integer_value_sequence(10), - integer_value_sequence(10), - integer_value(2)], - should_shuffle=False) - def process_unequalength_seq(settings, file_name): #单层RNN的dataprovider - for d in data2: - words1=reduce(lambda x,y: x+y, d[0]) - words2=reduce(lambda x,y: x+y, d[1]) - yield words1, words2, d[2] +.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py + :language: python + :lines: 69-97 data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 @@ -335,40 +152,16 @@ data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 模型中的配置 ------------ -单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`sequence_nest_rnn_multi_unequalength_inputs.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 +单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`v.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 - 单层序列\: - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 -.. code-block:: python - - def step(x1, x2): - def calrnn(y): - mem = memory(name = 'rnn_state_' + y.name, size = hidden_dim) - out = fc_layer(input = [y, mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'rnn_state_' + y.name) - return out - - encoder1 = calrnn(x1) - encoder2 = calrnn(x2) - return [encoder1, encoder2] - - encoder1_rep, encoder2_rep = recurrent_group( - name="stepout", - step=step, - input=[emb1, emb2]) - - encoder1_last = last_seq(input = encoder1_rep) - encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) - context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf + :language: python + :lines: 41-58 - 双层序列\: @@ -376,63 +169,9 @@ data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 -.. code-block:: python - - def outer_step(x1, x2): - outer_mem1 = memory(name = "outer_rnn_state1", size = hidden_dim) - outer_mem2 = memory(name = "outer_rnn_state2", size = hidden_dim) - def inner_step1(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem1) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - def inner_step2(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem2) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - encoder1 = recurrent_group( - step = inner_step1, - name = 'inner1', - input = x1) - - encoder2 = recurrent_group( - step = inner_step2, - name = 'inner2', - input = x2) - - sentence_last_state1 = last_seq(input = encoder1, name = 'outer_rnn_state1') - sentence_last_state2_ = last_seq(input = encoder2, name = 'outer_rnn_state2') - - encoder1_expand = expand_layer(input = sentence_last_state1, - expand_as = encoder2) - - return [encoder1_expand, encoder2] - - encoder1_rep, encoder2_rep = recurrent_group( - name="outer", - step=outer_step, - input=[SubsequenceInput(emb1), SubsequenceInput(emb2)], - targetInlink=emb2) - - encoder1_last = last_seq(input = encoder1_rep) - encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) - context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf + :language: python + :lines: 41-89 示例4:beam_search的生成 ======================== -- GitLab From 4351084181570c7082857018ea5ad676fb1d7105 Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Mon, 31 Oct 2016 19:00:48 +0800 Subject: [PATCH 0049/1503] Add PaddlePaddle QuickStart Demo on Kubernetes --- .../build_and_install/paddle_on_kubernetes.md | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 doc_cn/build_and_install/paddle_on_kubernetes.md diff --git a/doc_cn/build_and_install/paddle_on_kubernetes.md b/doc_cn/build_and_install/paddle_on_kubernetes.md new file mode 100644 index 0000000000..b7609c24c9 --- /dev/null +++ b/doc_cn/build_and_install/paddle_on_kubernetes.md @@ -0,0 +1,190 @@ +# Paddle On Kubernetes + +## 制作Docker镜像 + + 本文使用QucikStart的例子来作为镜像,详细的Paddle镜像请参考[Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html),使用paddledev/paddle:cpu-demo-latest作为基础镜像。 + +### 运行容器 + +``` +$ docker run --name quick_start_data -it paddledev/paddle:cpu-demo-latest +``` +进入容器`/root/paddle/demo/quick_start/data`目录,使用`get_data.sh`下载数据 + +``` +$ root@fbd1f2bb71f4:~/paddle/demo/quick_start/data# ./get_data.sh + +Downloading Amazon Electronics reviews data... +--2016-10-31 01:33:43-- http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +Resolving snap.stanford.edu (snap.stanford.edu)... 171.64.75.80 +Connecting to snap.stanford.edu (snap.stanford.edu)|171.64.75.80|:80... connected. +HTTP request sent, awaiting response... 200 OK +Length: 495854086 (473M) [application/x-gzip] +Saving to: 'reviews_Electronics_5.json.gz' + + 0% [ ] 874,279 64.7KB/s eta 2h 13m + +``` + +下载完数据后,修改`/root/paddle/demo/quick_start/train.sh`文件,内容如下(增加了一条cd命令) +``` +set -e +cd /root/paddle/demo/quick_start +cfg=trainer_config.lr.py +#cfg=trainer_config.emb.py +#cfg=trainer_config.cnn.py +#cfg=trainer_config.lstm.py +#cfg=trainer_config.bidi-lstm.py +#cfg=trainer_config.db-lstm.py +paddle train \ + --config=$cfg \ + --save_dir=./output \ + --trainer_count=4 \ + --log_period=20 \ + --num_passes=15 \ + --use_gpu=false \ + --show_parameter_stats_period=100 \ + --test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' +``` + +### 提交镜像 + +下载完数据后,退出容器,使用`docker commit`命令创建新镜像。 + +``` +$ docker commit quick_start_data mypaddle/paddle:quickstart +``` + +## 使用 Kubernetes 进行训练 + +### 编写yaml文件 + +在训练时,输出结果可能会随着容器的消耗而被删除,需要在创建Job时,挂载卷。使用前面构造的镜像,可以创建一个Kubernetes Job,简单的yaml文件如下: + +``` +apiVersion: batch/v1 +kind: Job +metadata: + name: quickstart +spec: + parallelism: 1 + completions: 1 + template: + metadata: + name: quickstart + spec: + volumes: + - name: output + hostPath: + path: /home/work/paddle_output + containers: + - name: pi + image: mypaddle/paddle:quickstart + command: ["bin/bash", "-c", "/root/paddle/demo/quick_start/train.sh"] + volumeMounts: + - name: output + mountPath: /root/paddle/demo/quick_start/output + restartPolicy: Never +``` + +### 创建Paddle Job + +使用上文创建的yaml文件创建kubernetes job,命令为: + +``` +$ kubectl create -f paddle.yaml +``` + +查看job的详细情况: + +``` +$ kubectl get job +NAME DESIRED SUCCESSFUL AGE +quickstart 1 0 58s + +$ kubectl describe job quickstart +Name: quickstart +Namespace: default +Image(s): registry.baidu.com/public/paddle:cpu-demo-latest +Selector: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84 +Parallelism: 1 +Completions: 1 +Start Time: Mon, 31 Oct 2016 11:20:16 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Pods Statuses: 0 Running / 1 Succeeded / 0 Failed +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +Events: + FirstSeen LastSeen Count From SubobjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: quickstart-fa0wx +``` + +### 查看训练结果 + +根据job对应的pod信息,可以查看此Pod运行的宿主机。 + +``` +kubectl describe pod quickstart-fa0wx +Name: quickstart-fa0wx +Namespace: default +Node: paddle-demo-let02/10.206.202.44 +Start Time: Mon, 31 Oct 2016 11:20:17 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Status: Succeeded +IP: 10.0.0.9 +Controllers: Job/quickstart +Containers: + quickstart: + Container ID: docker://b8561f5c79193550d64fa47418a9e67ebdd71546186e840f88de5026b8097465 + Image: registry.baidu.com/public/paddle:cpu-demo-latest + Image ID: docker://18e457ce3d362ff5f3febf8e7f85ffec852f70f3b629add10aed84f930a68750 + Port: + Command: + bin/bash + -c + /root/paddle/demo/quick_start/train.sh + QoS Tier: + cpu: BestEffort + memory: BestEffort + State: Terminated + Reason: Completed + Exit Code: 0 + Started: Mon, 31 Oct 2016 11:20:20 +0800 + Finished: Mon, 31 Oct 2016 11:21:46 +0800 + Ready: False + Restart Count: 0 + Environment Variables: +Conditions: + Type Status + Ready False +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +``` + +登录宿主机,查看结果。 + +``` +[root@paddle-demo-let02 paddle_output]# ll +total 60 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00000 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00001 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00002 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00003 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00004 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00005 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00006 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00007 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00008 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00009 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00010 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00011 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 +``` \ No newline at end of file -- GitLab From e2caa1f713a34960cad191d45e2a74a1ffefe4b7 Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Thu, 3 Nov 2016 14:04:03 +0800 Subject: [PATCH 0050/1503] Added some explanation to paddle_on_kubernetes.md --- .../build_and_install/paddle_on_kubernetes.md | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/doc_cn/build_and_install/paddle_on_kubernetes.md b/doc_cn/build_and_install/paddle_on_kubernetes.md index b7609c24c9..612b43dbaa 100644 --- a/doc_cn/build_and_install/paddle_on_kubernetes.md +++ b/doc_cn/build_and_install/paddle_on_kubernetes.md @@ -1,14 +1,25 @@ # Paddle On Kubernetes +>在这篇文档里,我们介绍如何在 Kubernetes 集群上启动一个单机使用CPU的Paddle训练作业。在下一篇中,我们将介绍如何启动分布式训练作业。 + ## 制作Docker镜像 - 本文使用QucikStart的例子来作为镜像,详细的Paddle镜像请参考[Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html),使用paddledev/paddle:cpu-demo-latest作为基础镜像。 +在一个功能齐全的Kubernetes机群里,通常我们会安装Ceph等分布式操作系统来存储训练数据。这样的话,一个分布式Paddle训练任务中的每个进程都可以从Ceph读取数据。在这个例子里,我们只演示一个单机作业,所以可以简化对环境的要求,把训练数据直接放在 +Paddle的Docker Image里。为此,我们需要制作一个包含训练数据的Paddle镜像。 +Paddle 的 [Quick Start Tutorial](http://www.paddlepaddle.org/doc/demo/quick_start/index_en.html) +里介绍了用Paddle源码中的脚本下载训练数据的过程。 +而 `paddledev/paddle:cpu-demo-latest` 镜像里有 Paddle 源码与Demo,( 请注意,默认的 +Paddle镜像 `paddledev/paddle:cpu-latest` 是不包括源码的, Paddle的各版本镜像可以参考 [Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html) ),所以我们使用这个镜像来下载训练数据到Docker Container中,然后把这个包含了训练数据的container保存为一个新的镜像。 + ### 运行容器 ``` $ docker run --name quick_start_data -it paddledev/paddle:cpu-demo-latest ``` + +### 下载数据 + 进入容器`/root/paddle/demo/quick_start/data`目录,使用`get_data.sh`下载数据 ``` @@ -22,10 +33,12 @@ HTTP request sent, awaiting response... 200 OK Length: 495854086 (473M) [application/x-gzip] Saving to: 'reviews_Electronics_5.json.gz' - 0% [ ] 874,279 64.7KB/s eta 2h 13m + 10% [=======> ] 874,279 64.7KB/s eta 2h 13m ``` +### 修改启动脚本 + 下载完数据后,修改`/root/paddle/demo/quick_start/train.sh`文件,内容如下(增加了一条cd命令) ``` set -e @@ -58,9 +71,11 @@ $ docker commit quick_start_data mypaddle/paddle:quickstart ## 使用 Kubernetes 进行训练 +>针对任务运行完成后容器自动退出的场景,Kubernetes有Job类型的资源来支持。下文就是用Job类型的资源来进行训练。 + ### 编写yaml文件 -在训练时,输出结果可能会随着容器的消耗而被删除,需要在创建Job时,挂载卷。使用前面构造的镜像,可以创建一个Kubernetes Job,简单的yaml文件如下: +在训练时,输出结果可能会随着容器的消耗而被删除,需要在创建容器前挂载卷以便我们保存训练结果。使用我们之前构造的镜像,可以创建一个 [Kubernetes Job](http://kubernetes.io/docs/user-guide/jobs/#what-is-a-job),简单的yaml文件如下: ``` apiVersion: batch/v1 @@ -90,7 +105,7 @@ spec: ### 创建Paddle Job -使用上文创建的yaml文件创建kubernetes job,命令为: +使用上文创建的yaml文件创建Kubernetes Job,命令为: ``` $ kubectl create -f paddle.yaml @@ -125,7 +140,7 @@ Events: ### 查看训练结果 -根据job对应的pod信息,可以查看此Pod运行的宿主机。 +根据Job对应的Pod信息,可以查看此Pod运行的宿主机。 ``` kubectl describe pod quickstart-fa0wx @@ -167,7 +182,7 @@ Volumes: Path: /home/work/paddle_output ``` -登录宿主机,查看结果。 +我们还可以登录到宿主机上查看训练结果。 ``` [root@paddle-demo-let02 paddle_output]# ll @@ -187,4 +202,4 @@ drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00011 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 -``` \ No newline at end of file +``` -- GitLab From 2aa8c6161275b8aea6905ff0c1666337d7fcfe70 Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Sat, 5 Nov 2016 23:07:51 +0800 Subject: [PATCH 0051/1503] add paddle cluster train on k8s --- .../cluster_train_on_kubernetes.md | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 doc_cn/build_and_install/cluster_train_on_kubernetes.md diff --git a/doc_cn/build_and_install/cluster_train_on_kubernetes.md b/doc_cn/build_and_install/cluster_train_on_kubernetes.md new file mode 100644 index 0000000000..162b087a13 --- /dev/null +++ b/doc_cn/build_and_install/cluster_train_on_kubernetes.md @@ -0,0 +1,290 @@ + +# 使用Kubernetes进行分布式训练 + +>前一篇文章介绍了如何使用Kubernetes Job进行一次单机的Paddle训练。在这篇文档里,我们介绍如何使用 Kubernetes 进行Paddle的集群训练作业。 +>关于Paddle的分布式集群训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文在此基础上,利用了Kubernetes快速构建Paddle集群,进行分布式训练任务。 + +## 制作镜像 + +Paddle的集群训练需要有一个Paddle集群来实现,在本文中,我们使用Kubernetes来快速创建一个Paddle集群。我们使用 `paddledev/paddle:cpu-demo-latest` 镜像作为Paddle集群节点的运行环境,里面包含了 Paddle 运行所需要的相关依赖,同时,为了能将训练任务及配置统一分发到各个节点,需要使用到`sshd`以便使用`fabric`来操作。镜像的 Dockerfile 如下: + +``` +FROM paddledev/paddle:cpu-demo-latest + +RUN apt-get update +RUN apt-get install -y openssh-server +RUN mkdir /var/run/sshd +RUN echo 'root:root' | chpasswd + +RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config +RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config + +EXPOSE 22 + +CMD ["/usr/sbin/sshd", "-D"] +``` + +使用 `docker build` 构建镜像: + +``` +docker build -t mypaddle:paddle_demo_ssh . +``` + +## 准备工作空间 + +工作空间 [Job Workspace](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md#prepare-job-workspace) , 即一个包含了依赖库,训练,测试数据,模型配置文件的目录。参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)中的例子,我们也是用`demo/recommendation`作为本文的训练任务。此demo可直接从[Github Paddle源码](https://github.com/baidu/Paddle/tree/develop/demo/recommendation)中获取。 + +### 准备训练数据 + +在Paddle源码中,找到`demo/recommendation`文件夹,即为我们的Workspace, 在本文的环境中,路径为`/home/work/paddle-demo/Paddle/demo/recommendation` + +``` +[root@paddle-k8s-node0 recommendation]# tree +. +├── common_utils.py +├── data +│   ├── config_generator.py +│   ├── config.json +│   ├── meta_config.json +│   ├── meta_generator.py +│   ├── ml_data.sh +│   └── split.py +├── dataprovider.py +├── evaluate.sh +├── prediction.py +├── preprocess.sh +├── requirements.txt +├── run.sh +└── trainer_config.py + +1 directory, 14 files +``` + +运行`data/ml_data.sh`脚本,下载数据,然后运行`preprocess.sh`脚本进行预处理。 + +``` +[root@paddle-k8s-node0 recommendation]# data/ml_data.sh +++ dirname data/ml_data.sh ++ cd data ++ wget http://files.grouplens.org/datasets/movielens/ml-1m.zip +--2016-11-04 10:14:49-- http://files.grouplens.org/datasets/movielens/ml-1m.zip +Resolving files.grouplens.org (files.grouplens.org)... 128.101.34.146 +Connecting to files.grouplens.org (files.grouplens.org)|128.101.34.146|:80... connected. +HTTP request sent, awaiting response... 200 OK +Length: 5917549 (5.6M) [application/zip] +Saving to: ‘ml-1m.zip’ + +100%[==========================>] 5,917,549 50.6KB/s in 2m 29s + +2016-11-04 10:17:20 (38.8 KB/s) - ‘ml-1m.zip’ saved [5917549/5917549] + ++ unzip ml-1m.zip +Archive: ml-1m.zip + creating: ml-1m/ + inflating: ml-1m/movies.dat + inflating: ml-1m/ratings.dat + inflating: ml-1m/README + inflating: ml-1m/users.dat ++ rm ml-1m.zip + +[root@paddle-k8s-node0 recommendation]# ./preprocess.sh +generate meta config file +generate meta file +split train/test file +shuffle train file +``` + +### 修改集群训练配置 + +参考[Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)中的介绍,我们使用`paddle/scripts/cluster_train/`中的文件来作为分布式训练任务的配置和启动脚本。在`run.sh`文件中,填入我们的workspace和训练配置文件路径。 + +``` +#!/bin/sh +python paddle.py \ + --job_dispatch_package="/home/work/paddle-demo/Paddle/demo/recommendation" \ + --dot_period=10 \ + --ports_num_for_sparse=2 \ + --log_period=50 \ + --num_passes=10 \ + --trainer_count=4 \ + --saving_period=1 \ + --local=0 \ + --config=/home/work/paddle-demo/Paddle/demo/recommendation/trainer_config.py \ + --save_dir=./output \ + --use_gpu=0 +``` + +## 创建Paddle集群 + +创建Paddle集训需要编写创建Kubernetes资源的yaml文件,首先,创建一个Service,便于我们通过此Service来查找其对应的Paddle节点。 + +``` +apiVersion: v1 +kind: Service +metadata: + name: cluster-demo +spec: + selector: + app: cluster-demo + ports: + - name: default + protocol: TCP + port: 7164 + targetPort: 7164 +``` + +为了创建多个Paddle节点,我们使用Kubernetes ReplicationController资源来控制Paddle集群中的节点数量,Paddle节点之间需要开放相关的端口来互相通信。下面的例子中,我们开放了每个Paddle节点的7164-7167端口,例如,一个包含4个节点的Paddle集群的yaml文件如下: + +``` +apiVersion: v1 +kind: ReplicationController +metadata: + name: cluster-demo +spec: + replicas: 4 + selector: + app: cluster-demo + template: + metadata: + name: cluster-demo + labels: + app: cluster-demo + spec: + containers: + - name: cluster-demo + image: mypaddle:paddle_demo_ssh + ports: + - containerPort: 7164 + - containerPort: 7165 + - containerPort: 7166 + - containerPort: 7167 +``` + +然后我们可以通过`kubectl`工具来查看所创建的资源信息。 + +首先查看我们创建的Paddle Service,然后根据Service,查看所创建的Paddle节点的IP地址。 + +``` +[root@paddle-k8s-node0 cluster_train]# kubectl get svc +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +cluster-demo 11.1.1.77 7164/TCP 6h + +[root@paddle-k8s-node0 cluster_train]# kubectl get -o json endpoints cluster-demo | grep ip + "ip": "192.168.129.79", + "ip": "192.168.129.80", + "ip": "192.168.223.157", + "ip": "192.168.223.158", +``` + +## 开始集群训练 + +我们需要在`paddle/scripts/cluster_train/conf.py`文件中指定各个节点的IP地址以及开放的端口。根据上文创建的信息,`conf.py`文件修改如下: + +``` +HOSTS = [ + "root@192.168.129.79", + "root@192.168.129.80", + "root@192.168.223.157", + "root@192.168.223.158" + ] + +''' +workspace configuration +''' +#root dir for workspace, can be set as any director with real user account +ROOT_DIR = "/home/paddle" + + +''' +network configuration +''' +#pserver nics +PADDLE_NIC = "eth0" +#pserver port +PADDLE_PORT = 7164 +#pserver ports num +PADDLE_PORTS_NUM = 2 +#pserver sparse ports num +PADDLE_PORTS_NUM_FOR_SPARSE = 2 + +#environments setting for all processes in cluster job +LD_LIBRARY_PATH="/usr/local/cuda/lib64:/usr/lib64" +``` +然后使用`run.sh`脚本开始训练,启动的打印如下: + +``` +[root@paddle-k8s-node0 cluster_train]# ./run.sh +[root@192.168.129.79] Executing task 'job_create_workspace' +...... +[root@192.168.129.80] Executing task 'job_create_workspace' +...... +[root@192.168.223.157] Executing task 'job_create_workspace' +...... +[root@192.168.223.158] Executing task 'job_create_workspace' +...... +[root@192.168.129.79] run: echo 0 > /home/paddle/JOB20161104171630/nodefile +[root@192.168.129.80] Executing task 'set_nodefile' +[root@192.168.129.80] run: echo 1 > /home/paddle/JOB20161104171630/nodefile +[root@192.168.223.157] Executing task 'set_nodefile' +[root@192.168.223.157] run: echo 2 > /home/paddle/JOB20161104171630/nodefile +[root@192.168.223.158] Executing task 'set_nodefile' +[root@192.168.223.158] run: echo 3 > /home/paddle/JOB20161104171630/nodefile +``` + +可以看到192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158分别为Paddle集群的Node 0-3. + +我们可以进入其中一个Paddle节点查看训练的日志。 + +``` +root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# less paddle_trainer.INFO +Log file created at: 2016/11/04 09:17:20 +Running on machine: cluster-demo-fwwi5 +Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg +I1104 09:17:20.346797 108 Util.cpp:155] commandline: /usr/local/bin/../opt/paddle/bin/paddle +_trainer --num_gradient_servers=4 --nics=eth0 --port=7164 --ports_num=2 --comment=paddle_proce +ss_by_paddle --pservers=192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158 --ports_ +num_for_sparse=2 --config=./trainer_config.py --trainer_count=4 --use_gpu=0 --num_passes=10 -- +save_dir=./output --log_period=50 --dot_period=10 --saving_period=1 --local=0 --trainer_id=1 + +root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# tailf paddle_trainer.INFO +...... +I1104 09:17:37.376471 150 ThreadLocal.cpp:37] thread use undeterministic rand seed:151 +I1104 09:18:54.159624 108 TrainerInternal.cpp:163] Batch=50 samples=80000 AvgCost=4.03478 CurrentCost=4.03478 Eval: CurrentEval: + +I1104 09:20:10.207902 108 TrainerInternal.cpp:163] Batch=100 samples=160000 AvgCost=3.75806 CurrentCost=3.48134 Eval: CurrentEval: +I1104 09:21:26.493571 108 TrainerInternal.cpp:163] Batch=150 samples=240000 AvgCost=3.64512 CurrentCost=3.41923 Eval: CurrentEval: + +``` + +最后,我们可以在Paddle集群的node0(192.168.129.79)上查看训练的输出结果。 + +``` +[root@paddle-k8s-node0 ~]# ssh root@192.168.129.79 +...... +root@cluster-demo-r65g0:/home/paddle/JOB20161104171700/output/pass-00000# ll +total 14876 +drwxr-xr-x. 2 root root 4096 Nov 4 09:40 ./ +drwxr-xr-x. 3 root root 23 Nov 4 09:40 ../ +-rw-r--r--. 1 root root 4046864 Nov 4 09:40 ___embedding_0__.w0 +-rw-r--r--. 1 root root 100368 Nov 4 09:40 ___embedding_1__.w0 +-rw-r--r--. 1 root root 6184976 Nov 4 09:40 ___embedding_2__.w0 +-rw-r--r--. 1 root root 2064 Nov 4 09:40 ___embedding_3__.w0 +-rw-r--r--. 1 root root 7184 Nov 4 09:40 ___embedding_4__.w0 +-rw-r--r--. 1 root root 21520 Nov 4 09:40 ___embedding_5__.w0 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 ___fc_layer_0__.w0 +-rw-r--r--. 1 root root 1040 Nov 4 09:40 ___fc_layer_0__.wbias +...... +...... +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w0 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w1 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w2 +-rw-r--r--. 1 root root 1040 Nov 4 09:40 _movie_fusion.wbias +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w0 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w1 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w2 +-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w3 +-rw-r--r--. 1 root root 1040 Nov 4 09:40 _user_fusion.wbias +-rw-r--r--. 1 root root 169 Nov 4 09:40 done +-rw-r--r--. 1 root root 17 Nov 4 09:40 path.txt +-rw-r--r--. 1 root root 3495 Nov 4 09:40 trainer_config.py +``` \ No newline at end of file -- GitLab From 23b6dfd07a594f7b73f1329c4d5eae40238ee36b Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Tue, 8 Nov 2016 19:10:25 +0800 Subject: [PATCH 0052/1503] modify some words in paddle_on_kubernetes docs --- ...es.md => distributed_training_on_kubernetes.md} | 5 ++--- doc_cn/build_and_install/paddle_on_kubernetes.md | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) rename doc_cn/build_and_install/{cluster_train_on_kubernetes.md => distributed_training_on_kubernetes.md} (95%) diff --git a/doc_cn/build_and_install/cluster_train_on_kubernetes.md b/doc_cn/build_and_install/distributed_training_on_kubernetes.md similarity index 95% rename from doc_cn/build_and_install/cluster_train_on_kubernetes.md rename to doc_cn/build_and_install/distributed_training_on_kubernetes.md index 162b087a13..d1769bb40a 100644 --- a/doc_cn/build_and_install/cluster_train_on_kubernetes.md +++ b/doc_cn/build_and_install/distributed_training_on_kubernetes.md @@ -1,8 +1,7 @@ -# 使用Kubernetes进行分布式训练 +# Paddle on Kubernetes:分布式训练 ->前一篇文章介绍了如何使用Kubernetes Job进行一次单机的Paddle训练。在这篇文档里,我们介绍如何使用 Kubernetes 进行Paddle的集群训练作业。 ->关于Paddle的分布式集群训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文在此基础上,利用了Kubernetes快速构建Paddle集群,进行分布式训练任务。 +前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上启动分布式Paddle训练作业。关于Paddle的分布式集群训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文在此基础上,利用Kubernetes快速构建Paddle集群,进行分布式训练任务。 ## 制作镜像 diff --git a/doc_cn/build_and_install/paddle_on_kubernetes.md b/doc_cn/build_and_install/paddle_on_kubernetes.md index 612b43dbaa..f8c9f19a9f 100644 --- a/doc_cn/build_and_install/paddle_on_kubernetes.md +++ b/doc_cn/build_and_install/paddle_on_kubernetes.md @@ -1,16 +1,16 @@ -# Paddle On Kubernetes +# Paddle On Kubernetes:单机训练 ->在这篇文档里,我们介绍如何在 Kubernetes 集群上启动一个单机使用CPU的Paddle训练作业。在下一篇中,我们将介绍如何启动分布式训练作业。 +在这篇文档里,我们介绍如何在 Kubernetes 集群上启动一个单机使用CPU的Paddle训练作业。在下一篇中,我们将介绍如何启动分布式训练作业。 ## 制作Docker镜像 -在一个功能齐全的Kubernetes机群里,通常我们会安装Ceph等分布式操作系统来存储训练数据。这样的话,一个分布式Paddle训练任务中的每个进程都可以从Ceph读取数据。在这个例子里,我们只演示一个单机作业,所以可以简化对环境的要求,把训练数据直接放在 -Paddle的Docker Image里。为此,我们需要制作一个包含训练数据的Paddle镜像。 +在一个功能齐全的Kubernetes机群里,通常我们会安装Ceph等分布式文件系统来存储训练数据。这样的话,一个分布式Paddle训练任务中的每个进程都可以从Ceph读取数据。在这个例子里,我们只演示一个单机作业,所以可以简化对环境的要求,把训练数据直接放在 +Paddle的Docker image里。为此,我们需要制作一个包含训练数据的Paddle镜像。 Paddle 的 [Quick Start Tutorial](http://www.paddlepaddle.org/doc/demo/quick_start/index_en.html) 里介绍了用Paddle源码中的脚本下载训练数据的过程。 -而 `paddledev/paddle:cpu-demo-latest` 镜像里有 Paddle 源码与Demo,( 请注意,默认的 -Paddle镜像 `paddledev/paddle:cpu-latest` 是不包括源码的, Paddle的各版本镜像可以参考 [Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html) ),所以我们使用这个镜像来下载训练数据到Docker Container中,然后把这个包含了训练数据的container保存为一个新的镜像。 +而 `paddledev/paddle:cpu-demo-latest` 镜像里有 Paddle 源码与demo,( 请注意,默认的 +Paddle镜像 `paddledev/paddle:cpu-latest` 是不包括源码的, Paddle的各版本镜像可以参考 [Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html) ),所以我们使用这个镜像来下载训练数据到Docker container中,然后把这个包含了训练数据的container保存为一个新的镜像。 ### 运行容器 @@ -63,7 +63,7 @@ paddle train \ ### 提交镜像 -下载完数据后,退出容器,使用`docker commit`命令创建新镜像。 +修改启动脚本后,退出容器,使用`docker commit`命令创建新镜像。 ``` $ docker commit quick_start_data mypaddle/paddle:quickstart -- GitLab From 212dcedc48a978c98b0bd6482cdc7e1f7773b48e Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Thu, 17 Nov 2016 17:16:04 +0800 Subject: [PATCH 0053/1503] rewrite distributed_training_on_k8s.md --- .../distributed_training_on_kubernetes.md | 289 ------------------ doc_cn/cluster/k8s/Dockerfile | 7 + .../k8s/distributed_training_on_kubernetes.md | 254 +++++++++++++++ doc_cn/cluster/k8s/job.yaml | 43 +++ doc_cn/cluster/k8s/start.sh | 19 ++ doc_cn/cluster/k8s/start_paddle.py | 159 ++++++++++ 6 files changed, 482 insertions(+), 289 deletions(-) delete mode 100644 doc_cn/build_and_install/distributed_training_on_kubernetes.md create mode 100644 doc_cn/cluster/k8s/Dockerfile create mode 100644 doc_cn/cluster/k8s/distributed_training_on_kubernetes.md create mode 100644 doc_cn/cluster/k8s/job.yaml create mode 100755 doc_cn/cluster/k8s/start.sh create mode 100755 doc_cn/cluster/k8s/start_paddle.py diff --git a/doc_cn/build_and_install/distributed_training_on_kubernetes.md b/doc_cn/build_and_install/distributed_training_on_kubernetes.md deleted file mode 100644 index d1769bb40a..0000000000 --- a/doc_cn/build_and_install/distributed_training_on_kubernetes.md +++ /dev/null @@ -1,289 +0,0 @@ - -# Paddle on Kubernetes:分布式训练 - -前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上启动分布式Paddle训练作业。关于Paddle的分布式集群训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文在此基础上,利用Kubernetes快速构建Paddle集群,进行分布式训练任务。 - -## 制作镜像 - -Paddle的集群训练需要有一个Paddle集群来实现,在本文中,我们使用Kubernetes来快速创建一个Paddle集群。我们使用 `paddledev/paddle:cpu-demo-latest` 镜像作为Paddle集群节点的运行环境,里面包含了 Paddle 运行所需要的相关依赖,同时,为了能将训练任务及配置统一分发到各个节点,需要使用到`sshd`以便使用`fabric`来操作。镜像的 Dockerfile 如下: - -``` -FROM paddledev/paddle:cpu-demo-latest - -RUN apt-get update -RUN apt-get install -y openssh-server -RUN mkdir /var/run/sshd -RUN echo 'root:root' | chpasswd - -RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config -RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config - -EXPOSE 22 - -CMD ["/usr/sbin/sshd", "-D"] -``` - -使用 `docker build` 构建镜像: - -``` -docker build -t mypaddle:paddle_demo_ssh . -``` - -## 准备工作空间 - -工作空间 [Job Workspace](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md#prepare-job-workspace) , 即一个包含了依赖库,训练,测试数据,模型配置文件的目录。参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)中的例子,我们也是用`demo/recommendation`作为本文的训练任务。此demo可直接从[Github Paddle源码](https://github.com/baidu/Paddle/tree/develop/demo/recommendation)中获取。 - -### 准备训练数据 - -在Paddle源码中,找到`demo/recommendation`文件夹,即为我们的Workspace, 在本文的环境中,路径为`/home/work/paddle-demo/Paddle/demo/recommendation` - -``` -[root@paddle-k8s-node0 recommendation]# tree -. -├── common_utils.py -├── data -│   ├── config_generator.py -│   ├── config.json -│   ├── meta_config.json -│   ├── meta_generator.py -│   ├── ml_data.sh -│   └── split.py -├── dataprovider.py -├── evaluate.sh -├── prediction.py -├── preprocess.sh -├── requirements.txt -├── run.sh -└── trainer_config.py - -1 directory, 14 files -``` - -运行`data/ml_data.sh`脚本,下载数据,然后运行`preprocess.sh`脚本进行预处理。 - -``` -[root@paddle-k8s-node0 recommendation]# data/ml_data.sh -++ dirname data/ml_data.sh -+ cd data -+ wget http://files.grouplens.org/datasets/movielens/ml-1m.zip ---2016-11-04 10:14:49-- http://files.grouplens.org/datasets/movielens/ml-1m.zip -Resolving files.grouplens.org (files.grouplens.org)... 128.101.34.146 -Connecting to files.grouplens.org (files.grouplens.org)|128.101.34.146|:80... connected. -HTTP request sent, awaiting response... 200 OK -Length: 5917549 (5.6M) [application/zip] -Saving to: ‘ml-1m.zip’ - -100%[==========================>] 5,917,549 50.6KB/s in 2m 29s - -2016-11-04 10:17:20 (38.8 KB/s) - ‘ml-1m.zip’ saved [5917549/5917549] - -+ unzip ml-1m.zip -Archive: ml-1m.zip - creating: ml-1m/ - inflating: ml-1m/movies.dat - inflating: ml-1m/ratings.dat - inflating: ml-1m/README - inflating: ml-1m/users.dat -+ rm ml-1m.zip - -[root@paddle-k8s-node0 recommendation]# ./preprocess.sh -generate meta config file -generate meta file -split train/test file -shuffle train file -``` - -### 修改集群训练配置 - -参考[Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)中的介绍,我们使用`paddle/scripts/cluster_train/`中的文件来作为分布式训练任务的配置和启动脚本。在`run.sh`文件中,填入我们的workspace和训练配置文件路径。 - -``` -#!/bin/sh -python paddle.py \ - --job_dispatch_package="/home/work/paddle-demo/Paddle/demo/recommendation" \ - --dot_period=10 \ - --ports_num_for_sparse=2 \ - --log_period=50 \ - --num_passes=10 \ - --trainer_count=4 \ - --saving_period=1 \ - --local=0 \ - --config=/home/work/paddle-demo/Paddle/demo/recommendation/trainer_config.py \ - --save_dir=./output \ - --use_gpu=0 -``` - -## 创建Paddle集群 - -创建Paddle集训需要编写创建Kubernetes资源的yaml文件,首先,创建一个Service,便于我们通过此Service来查找其对应的Paddle节点。 - -``` -apiVersion: v1 -kind: Service -metadata: - name: cluster-demo -spec: - selector: - app: cluster-demo - ports: - - name: default - protocol: TCP - port: 7164 - targetPort: 7164 -``` - -为了创建多个Paddle节点,我们使用Kubernetes ReplicationController资源来控制Paddle集群中的节点数量,Paddle节点之间需要开放相关的端口来互相通信。下面的例子中,我们开放了每个Paddle节点的7164-7167端口,例如,一个包含4个节点的Paddle集群的yaml文件如下: - -``` -apiVersion: v1 -kind: ReplicationController -metadata: - name: cluster-demo -spec: - replicas: 4 - selector: - app: cluster-demo - template: - metadata: - name: cluster-demo - labels: - app: cluster-demo - spec: - containers: - - name: cluster-demo - image: mypaddle:paddle_demo_ssh - ports: - - containerPort: 7164 - - containerPort: 7165 - - containerPort: 7166 - - containerPort: 7167 -``` - -然后我们可以通过`kubectl`工具来查看所创建的资源信息。 - -首先查看我们创建的Paddle Service,然后根据Service,查看所创建的Paddle节点的IP地址。 - -``` -[root@paddle-k8s-node0 cluster_train]# kubectl get svc -NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE -cluster-demo 11.1.1.77 7164/TCP 6h - -[root@paddle-k8s-node0 cluster_train]# kubectl get -o json endpoints cluster-demo | grep ip - "ip": "192.168.129.79", - "ip": "192.168.129.80", - "ip": "192.168.223.157", - "ip": "192.168.223.158", -``` - -## 开始集群训练 - -我们需要在`paddle/scripts/cluster_train/conf.py`文件中指定各个节点的IP地址以及开放的端口。根据上文创建的信息,`conf.py`文件修改如下: - -``` -HOSTS = [ - "root@192.168.129.79", - "root@192.168.129.80", - "root@192.168.223.157", - "root@192.168.223.158" - ] - -''' -workspace configuration -''' -#root dir for workspace, can be set as any director with real user account -ROOT_DIR = "/home/paddle" - - -''' -network configuration -''' -#pserver nics -PADDLE_NIC = "eth0" -#pserver port -PADDLE_PORT = 7164 -#pserver ports num -PADDLE_PORTS_NUM = 2 -#pserver sparse ports num -PADDLE_PORTS_NUM_FOR_SPARSE = 2 - -#environments setting for all processes in cluster job -LD_LIBRARY_PATH="/usr/local/cuda/lib64:/usr/lib64" -``` -然后使用`run.sh`脚本开始训练,启动的打印如下: - -``` -[root@paddle-k8s-node0 cluster_train]# ./run.sh -[root@192.168.129.79] Executing task 'job_create_workspace' -...... -[root@192.168.129.80] Executing task 'job_create_workspace' -...... -[root@192.168.223.157] Executing task 'job_create_workspace' -...... -[root@192.168.223.158] Executing task 'job_create_workspace' -...... -[root@192.168.129.79] run: echo 0 > /home/paddle/JOB20161104171630/nodefile -[root@192.168.129.80] Executing task 'set_nodefile' -[root@192.168.129.80] run: echo 1 > /home/paddle/JOB20161104171630/nodefile -[root@192.168.223.157] Executing task 'set_nodefile' -[root@192.168.223.157] run: echo 2 > /home/paddle/JOB20161104171630/nodefile -[root@192.168.223.158] Executing task 'set_nodefile' -[root@192.168.223.158] run: echo 3 > /home/paddle/JOB20161104171630/nodefile -``` - -可以看到192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158分别为Paddle集群的Node 0-3. - -我们可以进入其中一个Paddle节点查看训练的日志。 - -``` -root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# less paddle_trainer.INFO -Log file created at: 2016/11/04 09:17:20 -Running on machine: cluster-demo-fwwi5 -Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg -I1104 09:17:20.346797 108 Util.cpp:155] commandline: /usr/local/bin/../opt/paddle/bin/paddle -_trainer --num_gradient_servers=4 --nics=eth0 --port=7164 --ports_num=2 --comment=paddle_proce -ss_by_paddle --pservers=192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158 --ports_ -num_for_sparse=2 --config=./trainer_config.py --trainer_count=4 --use_gpu=0 --num_passes=10 -- -save_dir=./output --log_period=50 --dot_period=10 --saving_period=1 --local=0 --trainer_id=1 - -root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# tailf paddle_trainer.INFO -...... -I1104 09:17:37.376471 150 ThreadLocal.cpp:37] thread use undeterministic rand seed:151 -I1104 09:18:54.159624 108 TrainerInternal.cpp:163] Batch=50 samples=80000 AvgCost=4.03478 CurrentCost=4.03478 Eval: CurrentEval: - -I1104 09:20:10.207902 108 TrainerInternal.cpp:163] Batch=100 samples=160000 AvgCost=3.75806 CurrentCost=3.48134 Eval: CurrentEval: -I1104 09:21:26.493571 108 TrainerInternal.cpp:163] Batch=150 samples=240000 AvgCost=3.64512 CurrentCost=3.41923 Eval: CurrentEval: - -``` - -最后,我们可以在Paddle集群的node0(192.168.129.79)上查看训练的输出结果。 - -``` -[root@paddle-k8s-node0 ~]# ssh root@192.168.129.79 -...... -root@cluster-demo-r65g0:/home/paddle/JOB20161104171700/output/pass-00000# ll -total 14876 -drwxr-xr-x. 2 root root 4096 Nov 4 09:40 ./ -drwxr-xr-x. 3 root root 23 Nov 4 09:40 ../ --rw-r--r--. 1 root root 4046864 Nov 4 09:40 ___embedding_0__.w0 --rw-r--r--. 1 root root 100368 Nov 4 09:40 ___embedding_1__.w0 --rw-r--r--. 1 root root 6184976 Nov 4 09:40 ___embedding_2__.w0 --rw-r--r--. 1 root root 2064 Nov 4 09:40 ___embedding_3__.w0 --rw-r--r--. 1 root root 7184 Nov 4 09:40 ___embedding_4__.w0 --rw-r--r--. 1 root root 21520 Nov 4 09:40 ___embedding_5__.w0 --rw-r--r--. 1 root root 262160 Nov 4 09:40 ___fc_layer_0__.w0 --rw-r--r--. 1 root root 1040 Nov 4 09:40 ___fc_layer_0__.wbias -...... -...... --rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w0 --rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w1 --rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w2 --rw-r--r--. 1 root root 1040 Nov 4 09:40 _movie_fusion.wbias --rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w0 --rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w1 --rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w2 --rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w3 --rw-r--r--. 1 root root 1040 Nov 4 09:40 _user_fusion.wbias --rw-r--r--. 1 root root 169 Nov 4 09:40 done --rw-r--r--. 1 root root 17 Nov 4 09:40 path.txt --rw-r--r--. 1 root root 3495 Nov 4 09:40 trainer_config.py -``` \ No newline at end of file diff --git a/doc_cn/cluster/k8s/Dockerfile b/doc_cn/cluster/k8s/Dockerfile new file mode 100644 index 0000000000..3a73606c61 --- /dev/null +++ b/doc_cn/cluster/k8s/Dockerfile @@ -0,0 +1,7 @@ +FROM paddledev/paddle:cpu-latest + +MAINTAINER zjsxzong89@gmail.com + +COPY start.sh /root/ +COPY start_paddle.py /root/ +CMD ["bash"," -c","/root/start.sh"] \ No newline at end of file diff --git a/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md new file mode 100644 index 0000000000..8e947f8d56 --- /dev/null +++ b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md @@ -0,0 +1,254 @@ + +# Paddle on Kubernetes:分布式训练 + +前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式Paddle训练作业。关于Paddle的分布式训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文利用Kubernetes的调度功能与容器编排能力,快速构建Paddle容器集群,进行分布式训练任务。 + +## Kubernetes 基本概念 + +在介绍分布式训练之前,需要对Kubernetes(k8s)有一个基本的认识,下面先简要介绍一下本文用到的几个k8s概念。 + +### Node + +[`Node`](http://kubernetes.io/docs/admin/node/) 表示一个k8s集群中的一个工作节点,这个节点可以是物理机或者虚拟机,k8s集群就是由`node`节点与`master`节点组成的。每个node都安装有Docker,在本文的例子中,`Paadle`容器就在node上运行。 + +### Pod + +一个[`Pod`](http://kubernetes.io/docs/user-guide/pods/) 是一组(一个或多个)容器,pod是k8s的最小调度单元,一个pod中的所有容器会被调度到同一个node上。Pod中的容器共享NET,PID,IPC,UTS等Linux namespace,它们使用同一个IP地址,可以通过`localhost`互相通信。不同pod之间可以通过IP地址访问。 + +### Job + +[`Job`](http://kubernetes.io/docs/user-guide/jobs/) 可以翻译为作业,每个job可以设定pod成功完成的次数,一次作业会创建多个pod,当成功完成的pod个数达到预设值时,就表示job成功结束了。 + +### Volume + +[`Volume`](http://kubernetes.io/docs/user-guide/volumes/) 存储卷,是pod内的容器都可以访问的共享目录,也是容器与node之间共享文件的方式,因为容器内的文件都是暂时存在的,当容器因为各种原因被销毁时,其内部的文件也会随之消失。通过volume,就可以将这些文件持久化存储。k8s支持多种volume,例如`hostPath(宿主机目录)`,`gcePersistentDisk`,`awsElasticBlockStore`等。 + +### Namespace + +[`Namespaces`](http://kubernetes.io/docs/user-guide/volumes/) 命名空间,在k8s中创建的所有资源对象(例如上文的pod,job)等都属于一个命名空间,在同一个命名空间中,资源对象的名字是唯一的,不同空间的资源名可以重复,命名空间主要用来为不同的用户提供相对隔离的环境。本文只使用了`default`默认命名空间,读者可以不关心此概念。 + +## 整体方案 + +### 前提条件 + +首先,我们需要拥有一个k8s集群,在这个集群中所有node与pod都可以互相通信。关于k8s集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。在本文的环境中,k8s集群中所有node都挂载了一个`mfs`(分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。在训练之前,用户将配置与训练数据切分好放在mfs目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。 + +### 使用 `Job` + +我们使用k8s中的job这个概念来代表一次分布式训练。`Job`表示一次性作业,在作业完成后,k8s会销毁job产生的容器并且释放相关资源。 + +在k8s中,可以通过编写一个 `yaml` 文件,来描述这个job,在这个文件中,主要包含了一些配置信息,例如Paddle节点的个数,`paddle pserver`开放的端口个数与端口号,`paddle`使用的网卡设备等,这些信息通过环境变量的形式传递给容器内的程序使用。 + +在一次分布式训练中,用户确定好本次训练需要的Paddle节点个数,将切分好的训练数据与配置文件上传到`mfs`共享目录中。然后编写这次训练的`job yaml`文件,提交给k8s集群创建并开始作业。 + +### 创建`Paddle`节点 + +当k8s master收到`job yaml`文件后,会解析相关字段,创建出多个pod(个数为Paddle节点数),k8s会把这些pod调度到集群的node上运行。一个`pod`就代表一个`Paddle`节点,当pod被成功分配到一台物理/虚拟机上后,k8s会启动pod内的容器,这个容器会根据`job yaml`文件中的环境变量,启动`paddle pserver`与`paddle train`进程。 + +### 启动训练 + +在容器启动后,会通过脚本来启动这次分布式训练,我们知道`paddle train`进程启动时需要知道其他节点的IP地址以及本节点的`trainer_id`,由于`Paddle`本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据`job name`向`k8s apiserver`查询这个`job`对应的所有`pod`信息(k8s默认会在每个容器的环境变量中写入`apiserver`的地址)。 + +根据这些pod信息,就可以通过某种方式,为每个pod分配一个唯一的`trainer_id`。本文把所有pod的IP地址进行排序,将顺序作为每个`Paddle`节点的`trainer_id`。启动脚本的工作流程大致如下: + + 1. 查询`k8s apiserver`获取pod信息,根据IP分配`trainer_id` + 1. 从`mfs`共享目录中拷贝训练文件到容器内 + 1. 根据环境变量,解析出`paddle pserver`与`paddle train`的启动参数,启动进程 + 1. 训练时,`Paddle`会自动将结果保存在`trainer_id`为0的节点上,将输出路径设置为`mfs`目录,保存输出的文件 + + +## 搭建过程 + +根据前文的描述,要在已有的k8s集群上进行`Paddle`的分布式训练,主要分为以下几个步骤: + +1. 制作`Paddle`镜像 +1. 将训练文件与切分好的数据上传到共享存储 +1. 编写本次训练的`job yaml`文件,创建`k8s job` +1. 训练结束后查看输出结果 + +下面就根据这几个步骤分别介绍。 + + + +### 制作镜像 + +`Paddle`镜像需要提供`paddle pserver`与`paddle train`进程的运行环境,用这个镜像创建的容器需要有以下两个功能: + +- 拷贝训练文件到容器内 + +- 生成`paddle pserver`与`paddle train`进程的启动参数,并且启动训练 + +因为官方镜像 `paddledev/paddle:cpu-latest` 内已经包含`Paddle`的执行程序但是还没上述功能,所以我们可以在这个基础上,添加启动脚本,制作新镜像来完成以上的工作。镜像的`Dockerfile`如下: + +```Dockerfile +FROM paddledev/paddle:cpu-latest + +MAINTAINER zjsxzong89@gmail.com + +COPY start.sh /root/ +COPY start_paddle.py /root/ +CMD ["bash"," -c","/root/start.sh"] +``` + +[`start.sh`](start.sh)文件拷贝训练文件到容器内,然后执行[`start_paddle.py`](start_paddle.py)脚本启动训练,前文提到的获取其他节点IP地址,分配`trainer_id`等都在`start_paddle.py`脚本中完成。 + + +使用 `docker build` 构建镜像: + +```bash +docker build -t registry.baidu.com/public/paddle:mypaddle . +``` + +然后将构建成功的镜像上传到镜像仓库,注意本文中使用的`registry.baidu.com`是一个私有仓库,读者可以根据自己的情况部署私有仓库或者使用`Docker hub`。 + +```bash +docker push registry.baidu.com/public/paddle:mypaddle +``` + +### 上传训练文件 + +本文使用`Paddle`官方的`recommendation demo`作为这次训练的内容,我们将训练文件与数据放在一个`job name`命名的目录中,上传到`mfs`共享存储。完成后`mfs`上的文件内容大致如下: + +```bash +[root@paddle-k8s-node0 mfs]# tree -d +. +└── paddle-cluster-job + ├── data + │   ├── 0 + │   │ + │   ├── 1 + │   │ + │   └── 2 + ├── output + └── recommendation +``` + +目录中`paddle-cluster-job`是本次训练对应的`job name`,本次训练要求有3个`Paddle`节点,在`paddle-cluster-job/data`目录中存放切分好的数据,文件夹`0,1,2`分别代表3个节点的`trainer_id`。`recommendation`文件夹内存放训练文件,`output`文件夹存放训练结果与日志。 + +### 创建`job` + +`k8s`可以通过`yaml`文件来创建相关对象,然后可以使用命令行工具创建`job`。 + +`job yaml`文件描述了这次训练使用的Docker镜像,需要启动的节点个数以及 `paddle pserver`与 `paddle train`进程启动的必要参数,也描述了容器需要使用的存储卷挂载的情况。`yaml`文件中各个字段的具体含义,可以查看[`k8s官方文档`](http://kubernetes.io/docs/api-reference/batch/v1/definitions/#_v1_job)。例如,本次训练的`yaml`文件可以写成: + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: paddle-cluster-job +spec: + parallelism: 3 + completions: 3 + template: + metadata: + name: paddle-cluster-job + spec: + volumes: + - name: jobpath + hostPath: + path: /home/work/mfs + containers: + - name: trainer + image: registry.baidu.com/public/paddle:mypaddle + command: ["bin/bash", "-c", "/root/start.sh"] + env: + - name: JOB_NAME + value: paddle-cluster-job + - name: JOB_PATH + value: /home/jobpath + - name: JOB_NAMESPACE + value: default + - name: TRAIN_CONFIG_DIR + value: recommendation + - name: CONF_PADDLE_NIC + value: eth0 + - name: CONF_PADDLE_PORT + value: "7164" + - name: CONF_PADDLE_PORTS_NUM + value: "2" + - name: CONF_PADDLE_PORTS_NUM_SPARSE + value: "2" + - name: CONF_PADDLE_GRADIENT_NUM + value: "3" + volumeMounts: + - name: jobpath + mountPath: /home/jobpath + restartPolicy: Never +``` + +文件中,`metadata`下的`name`表示这个`job`的名字。`parallelism,completions`字段表示这个`job`会同时开启3个`Paddle`节点,成功训练且退出的`pod`数目为3时,这个`job`才算成功结束。然后申明一个存储卷`jobpath`,代表宿主机目录`/home/work/mfs`,在对容器的描述`containers`字段中,将此目录挂载为容器的`/home/jobpath`目录,这样容器的`/home/jobpath`目录就成为了共享存储,放在这个目录里的文件其实是保存到了`mfs`上。 + +`env`字段表示容器的环境变量,我们将`paddle`运行的一些参数通过这种方式传递到容器内。 + +`JOB_PATH`表示共享存储挂载的路径,`JOB_NAME`表示job名字,`TRAIN_CONFIG_DIR`表示本次训练文件所在目录,这三个变量组合就可以找到本次训练需要的文件路径。 + +`CONF_PADDLE_NIC`表示`paddle pserver`进程需要的`--nics`参数,即网卡名 + +`CONF_PADDLE_PORT`表示`paddle pserver`的`--port`参数,`CONF_PADDLE_PORTS_NUM`则表示稠密更新的端口数量,也就是`--ports_num`参数。 + +`CONF_PADDLE_PORTS_NUM_SPARSE`表示稀疏更新的端口数量,也就是`--ports_num_for_sparse`参数。 + +`CONF_PADDLE_GRADIENT_NUM`表示训练节点数量,即`--num_gradient_servers`参数 + +编写完`yaml`文件后,可以使用k8s的命令行工具创建`job`. + +```bash +kubectl create -f job.yaml +``` + +创建成功后,k8s就会创建3个`pod`作为`Paddle`节点然后拉取镜像,启动容器开始训练。 + + +### 查看输出 + +在训练过程中,可以在共享存储上查看输出的日志和模型,例如`output`目录下就存放了输出结果。注意`node_0`,`node_1`,`node_2`这几个目录表示`Paddle`节点与`trainer_id`,并不是k8s中的`node`概念。 + +```bash +[root@paddle-k8s-node0 output]# tree -d +. +├── node_0 +│   ├── server.log +│   └── train.log +├── node_1 +│   ├── server.log +│   └── train.log +├── node_2 +...... +├── pass-00002 +│   ├── done +│   ├── ___embedding_0__.w0 +│   ├── ___embedding_1__.w0 +...... +``` + +我们可以通过日志查看容器训练的情况,例如: + +```bash +[root@paddle-k8s-node0 node_0]# cat train.log +I1116 09:10:17.123121 50 Util.cpp:155] commandline: + /usr/local/bin/../opt/paddle/bin/paddle_trainer + --nics=eth0 --port=7164 + --ports_num=2 --comment=paddle_process_by_paddle + --pservers=192.168.129.66,192.168.223.143,192.168.129.71 + --ports_num_for_sparse=2 --config=./trainer_config.py + --trainer_count=4 --num_passes=10 --use_gpu=0 + --log_period=50 --dot_period=10 --saving_period=1 + --local=0 --trainer_id=0 + --save_dir=/home/jobpath/paddle-cluster-job/output +I1116 09:10:17.123440 50 Util.cpp:130] Calling runInitFunctions +I1116 09:10:17.123764 50 Util.cpp:143] Call runInitFunctions done. +[WARNING 2016-11-16 09:10:17,227 default_decorators.py:40] please use keyword arguments in paddle config. +[INFO 2016-11-16 09:10:17,239 networks.py:1282] The input order is [movie_id, title, genres, user_id, gender, age, occupation, rating] +[INFO 2016-11-16 09:10:17,239 networks.py:1289] The output order is [__regression_cost_0__] +I1116 09:10:17.392917 50 Trainer.cpp:170] trainer mode: Normal +I1116 09:10:17.613910 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process +I1116 09:10:17.680917 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process +I1116 09:10:17.681543 50 GradientMachine.cpp:134] Initing parameters.. +I1116 09:10:18.012390 50 GradientMachine.cpp:141] Init parameters done. +I1116 09:10:18.018641 50 ParameterClient2.cpp:122] pserver 0 192.168.129.66:7164 +I1116 09:10:18.018950 50 ParameterClient2.cpp:122] pserver 1 192.168.129.66:7165 +I1116 09:10:18.019069 50 ParameterClient2.cpp:122] pserver 2 192.168.223.143:7164 +I1116 09:10:18.019492 50 ParameterClient2.cpp:122] pserver 3 192.168.223.143:7165 +I1116 09:10:18.019716 50 ParameterClient2.cpp:122] pserver 4 192.168.129.71:7164 +I1116 09:10:18.019836 50 ParameterClient2.cpp:122] pserver 5 192.168.129.71:7165 +``` \ No newline at end of file diff --git a/doc_cn/cluster/k8s/job.yaml b/doc_cn/cluster/k8s/job.yaml new file mode 100644 index 0000000000..1e0ac464b2 --- /dev/null +++ b/doc_cn/cluster/k8s/job.yaml @@ -0,0 +1,43 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: paddle-cluster-job +spec: + parallelism: 3 + completions: 3 + template: + metadata: + name: paddle-cluster-job + spec: + volumes: + - name: jobpath + hostPath: + path: /home/work/paddle_output + containers: + - name: trainer + image: registry.baidu.com/public/paddle:mypaddle + command: ["bin/bash", "-c", "/root/start.sh"] + env: + - name: JOB_NAME + value: paddle-cluster-job + - name: JOB_PATH + value: /home/jobpath + - name: JOB_NAMESPACE + value: default + - name: TRAIN_CONFIG_DIR + value: recommendation + - name: CONF_PADDLE_NIC + value: eth0 + - name: CONF_PADDLE_PORT + value: "7164" + - name: CONF_PADDLE_PORTS_NUM + value: "2" + - name: CONF_PADDLE_PORTS_NUM_SPARSE + value: "2" + - name: CONF_PADDLE_GRADIENT_NUM + value: "3" + volumeMounts: + - name: jobpath + mountPath: /home/jobpath + restartPolicy: Never + \ No newline at end of file diff --git a/doc_cn/cluster/k8s/start.sh b/doc_cn/cluster/k8s/start.sh new file mode 100755 index 0000000000..b3a1334174 --- /dev/null +++ b/doc_cn/cluster/k8s/start.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu + +jobconfig=${JOB_PATH}"/"${JOB_NAME}"/"${TRAIN_CONFIG_DIR} +cd /root +cp -rf $jobconfig . +cd $TRAIN_CONFIG_DIR + + +python /root/start_paddle.py \ + --dot_period=10 \ + --ports_num_for_sparse=$CONF_PADDLE_PORTS_NUM \ + --log_period=50 \ + --num_passes=10 \ + --trainer_count=4 \ + --saving_period=1 \ + --local=0 \ + --config=./trainer_config.py \ + --use_gpu=0 diff --git a/doc_cn/cluster/k8s/start_paddle.py b/doc_cn/cluster/k8s/start_paddle.py new file mode 100755 index 0000000000..bc0112a77f --- /dev/null +++ b/doc_cn/cluster/k8s/start_paddle.py @@ -0,0 +1,159 @@ +#!/usr/bin/python +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import requests +import time +import socket +import os +import argparse + + +# configuration for cluster +API = "/api/v1/namespaces/" +JOBSELECTOR = "labelSelector=job-name=" +JOB_PATH = os.getenv("JOB_PATH") + "/" + os.getenv("JOB_NAME") +JOB_PATH_DATA = JOB_PATH + "/data" +JOB_PATH_OUTPUT = JOB_PATH + "/output" +JOBNAME = os.getenv("JOB_NAME") +NAMESPACE = os.getenv("JOB_NAMESPACE") +PADDLE_NIC = os.getenv("CONF_PADDLE_NIC") +PADDLE_PORT = os.getenv("CONF_PADDLE_PORT") +PADDLE_PORTS_NUM = os.getenv("CONF_PADDLE_PORTS_NUM") +PADDLE_PORTS_NUM_SPARSE = os.getenv("CONF_PADDLE_PORTS_NUM_SPARSE") +PADDLE_SERVER_NUM = os.getenv("CONF_PADDLE_GRADIENT_NUM") + + +def refine_unknown_args(cmd_args): + ''' + refine unknown parameters to handle some special parameters + ''' + new_args = [] + for arg in cmd_args: + if arg.startswith("--") and arg.find("=") != -1: + equal_pos = arg.find("=") # find first = pos + arglist = list(arg) + arglist[equal_pos] = " " + arg = "".join(arglist) + arg = arg.lstrip("-") + new_args += arg.split(" ") + elif arg.startswith("--") and arg.find("=") == -1: + arg = arg.lstrip("-") + new_args.append(arg) + else: + new_args.append(arg) + return new_args + + +def isPodAllRunning(podlist): + ''' + check all pod is running + ''' + require = len(podlist["items"]) + running = 0 + for pod in podlist["items"]: + if pod["status"]["phase"] == "Running": + running += 1 + if require == running: + return True + return False + + +def getPodList(): + ''' + get all container status of the job + ''' + apiserver = "https://" + \ + os.getenv("KUBERNETES_SERVICE_HOST") + ":" + \ + os.getenv("KUBERNETES_SERVICE_PORT_HTTPS") + + pod = API + NAMESPACE + "/pods?" + job = JOBNAME + return requests.get(apiserver + pod + JOBSELECTOR + job, + verify=False).json() + + +def getIdMap(podlist): + ''' + generate tainer_id by ip + ''' + ips = [] + for pod in podlist["items"]: + ips.append(pod["status"]["podIP"]) + ips.sort() + idMap = {} + for i in range(len(ips)): + idMap[ips[i]] = i + return idMap + + +def startPaddle(idMap={}, train_args_dict=None): + ''' + start paddle pserver and trainer + ''' + program = 'paddle train' + args = " --nics=" + PADDLE_NIC + args += " --port=" + str(PADDLE_PORT) + args += " --ports_num=" + str(PADDLE_PORTS_NUM) + args += " --comment=" + "paddle_process_by_paddle" + ip_string = "" + for ip in idMap.keys(): + ip_string += (ip + ",") + ip_string = ip_string.rstrip(",") + args += " --pservers=" + ip_string + args_ext = "" + for key, value in train_args_dict.items(): + args_ext += (' --' + key + '=' + value) + localIP = socket.gethostbyname(socket.gethostname()) + trainerId = idMap[localIP] + args += " " + args_ext + " --trainer_id=" + \ + str(trainerId) + " --save_dir=" + JOB_PATH_OUTPUT + logDir = JOB_PATH_OUTPUT + "/node_" + str(trainerId) + if not os.path.exists(JOB_PATH_OUTPUT): + os.makedirs(JOB_PATH_OUTPUT) + os.mkdir(logDir) + copyCommand = 'cp -rf ' + JOB_PATH_DATA + \ + "/" + str(trainerId) + " ./data" + os.system(copyCommand) + startPserver = 'nohup paddle pserver' + \ + " --port=" + str(PADDLE_PORT) + \ + " --ports_num=" + str(PADDLE_PORTS_NUM) + \ + " --ports_num_for_sparse=" + str(PADDLE_PORTS_NUM_SPARSE) + \ + " --nics=" + PADDLE_NIC + \ + " --comment=" + "paddle_process_by_paddle" + \ + " --num_gradient_servers=" + str(PADDLE_SERVER_NUM) +\ + " > " + logDir + "/server.log 2>&1 &" + print startPserver + os.system(startPserver) + # wait until pservers completely start + time.sleep(10) + startTrainer = program + args + " > " + \ + logDir + "/train.log 2>&1 < /dev/null" + print startTrainer + os.system(startTrainer) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog="start_paddle.py", + description='simple tool for k8s') + args, train_args_list = parser.parse_known_args() + train_args = refine_unknown_args(train_args_list) + train_args_dict = dict(zip(train_args[:-1:2], train_args[1::2])) + podlist = getPodList() + # need to wait until all pods are running + while not isPodAllRunning(podlist): + time.sleep(10) + podlist = getPodList() + idMap = getIdMap(podlist) + startPaddle(idMap, train_args_dict) -- GitLab From 5300869ceb72569b81478c9045ef208c9e567682 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 23:52:45 +0800 Subject: [PATCH 0054/1503] Add new badges for docs and stats --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81ff8c7122..1b0574c984 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,16 @@ [![Build Status](https://travis-ci.org/baidu/Paddle.svg?branch=master)](https://travis-ci.org/baidu/Paddle) +[![Downloads](https://img.shields.io/github/downloads/baidu/Paddle/total.svg)](https://github.com/baidu/Paddle/releases) [![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) -[![Join the chat at https://gitter.im/PaddlePaddle/Deep_Learning](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/PaddlePaddle/Deep_Learning?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE) + +[![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) +[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)]() +[![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) + +[![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) +[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) + Welcome to the PaddlePaddle GitHub. -- GitLab From 9a5f89398fabcfae49432b95db5aa982657220da Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 23:59:02 +0800 Subject: [PATCH 0055/1503] Add chinese docs link in badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b0574c984..a309ff3734 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) -[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)]() +[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) [![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) [![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) -- GitLab From 0cc7d6eaf6be674ba1da4f197eca422ea2394891 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Thu, 17 Nov 2016 15:18:03 -0800 Subject: [PATCH 0056/1503] Update install_deps.rst --- doc_cn/build_and_install/cmake/install_deps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_cn/build_and_install/cmake/install_deps.rst b/doc_cn/build_and_install/cmake/install_deps.rst index 7fa4665a95..6d8727e329 100644 --- a/doc_cn/build_and_install/cmake/install_deps.rst +++ b/doc_cn/build_and_install/cmake/install_deps.rst @@ -1,4 +1,4 @@ 安装编译PaddlePaddle需要的依赖 ============================== -参见 `安装编译依赖 <../../../doc/build/build_from_source.html#install-dependencies>`_ +参见 `安装编译依赖 `_ -- GitLab From d980c2642fa8283563be012ab15df6b32f049981 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Thu, 17 Nov 2016 15:51:03 -0800 Subject: [PATCH 0057/1503] Update make_and_install.rst --- doc_cn/build_and_install/cmake/make_and_install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_cn/build_and_install/cmake/make_and_install.rst b/doc_cn/build_and_install/cmake/make_and_install.rst index 212b9c9352..8a390ef581 100644 --- a/doc_cn/build_and_install/cmake/make_and_install.rst +++ b/doc_cn/build_and_install/cmake/make_and_install.rst @@ -1,4 +1,4 @@ make和make install ================== -参见 `make和make install <../../../doc/build/build_from_source.html#build-and-install>`_ +参见 `make和make install `_ -- GitLab From af335da056e931085037ff2631df4bad7a34bd03 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Thu, 17 Nov 2016 16:34:19 -0800 Subject: [PATCH 0058/1503] Update index.md --- doc_cn/introduction/index.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/doc_cn/introduction/index.md b/doc_cn/introduction/index.md index 164cb7d494..d6efab0cf1 100644 --- a/doc_cn/introduction/index.md +++ b/doc_cn/introduction/index.md @@ -1,17 +1,17 @@ # 简介 -PaddlePaddle 是起源于百度的开源深度学习平台。它是简单易用的:你可以通过简单的十数行配置搭建经典的神经网络模型;它也是高效强大的:PaddlePaddle可以支撑复杂集群环境下超大模型的训练,令你受益于深度学习的前沿成果。在百度内部,已经有大量产品线使用了基于PaddlePaddle的深度学习技术。 +PaddlePaddle 源于百度的开源深度学习平台,有如下几个特点。首先,简单易用的:用户可以通过简单的十几行配置脚本搭建经典的神经网络模型。其次,高效强大的:PaddlePaddle可以支撑复杂集群环境下超大模型的训练,令你受益于深度学习的前沿成果。最后,在百度内部,已经有大量产品线使用了基于PaddlePaddle的深度学习技术。 -这份简短的介绍将像你展示如何利用PaddlePaddle解决一个经典的学习问题。 +这份简短的介绍将像你展示如何利用PaddlePaddle来解决一个经典的机器学习问题。 ## 1. 一个经典的任务 -让我们从一个基础问题开始:
    单变量的线性回归。问题假定观测到了一批二维空间上的点`(x, y) `,并且已知 `x` 和 `y` 之间存在着某种线性关系,我们的目标是通过观测数据还原这个线性关系。作为一个简单基础的模型,线性回归却有着广泛的应用场景。比如可以想象一个资产定价的简化场景,其中 `x` 对应于房屋的大小,`y` 对应于房屋价格。我们可以通过观察市场上房屋的情况获得二者之间的关系,从而为新房屋的定价提供参考。 +让我们从一个基础问题开始:单变量的线性回归。问题假定观测到了一批二维空间上的点`(x, y) `,并且已知 `x` 和 `y` 之间存在着某种线性关系,我们的目标是通过观测数据来学习这个线性关系。作为一个简单基础的模型,线性回归有着广泛的应用场景。以一个资产定价的问题为例,`x` 对应于房屋的大小,`y` 对应于房屋价格。我们可以通过观察市场上房屋销售的情况拟合 `x` 和 `y` 之间的关系,从而为新房屋的定价提供预测和参考。 ## 2. 准备数据 -假设变量 `X` 和 `Y` 的真实关系为: `Y = 2X + 0.3`,这里展示如何使用观测数据还原这一线性关系。如下Python代码将随机产生2000个观测点,它们将被用作PaddlePaddle的输入。产生PaddlePaddle的输入数据和写一段普通的Python脚本几乎一样,你唯一需要增加的就是定义输入数据的类型。 +假设变量 `X` 和 `Y` 的真实关系为: `Y = 2X + 0.3`,这里展示如何使用观测数据来拟合这一线性关系。首先,Python代码将随机产生2000个观测点,作为PaddlePaddle的输入。产生PaddlePaddle的输入数据和写一段普通的Python脚本几乎一样,你唯一需要增加的就是定义输入数据的类型。 ```python # -*- coding:utf-8 -*- @@ -29,7 +29,7 @@ def process(settings, input_file): ## 3. 训练模型 -为了还原 `Y = 2X + 0.3`,我们先从一条随机的直线 `Y' = wX + b` 开始,然后利用观测数据调整 `w` 和 `b` 使得 `Y'` 和 `Y` 的差距不断减小,最终趋于相同。这个过程就是模型的训练过程,而 `w` 和 `b` 就是模型的参数,即我们的训练目标。 +为了还原 `Y = 2X + 0.3`,我们先从一条随机的直线 `Y' = wX + b` 开始,然后利用观测数据调整 `w` 和 `b` 使得 `Y'` 和 `Y` 的差距不断减小,最终趋于接近。这个过程就是模型的训练过程,而 `w` 和 `b` 就是模型的参数,即我们的训练目标。 在PaddlePaddle里,该模型的网络配置如下。 @@ -50,33 +50,33 @@ settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) # 3. 神经网络配置 x = data_layer(name='x', size=1) y = data_layer(name='y', size=1) -# 线性计算单元: y_predict = wx + b +# 线性计算网络层: y_predict = wx + b y_predict = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) -# 损失计算,度量 y_predict 和真实 y 之间的差距 +# 计算误差函数,即 y_predict 和真实 y 之间的距离 cost = regression_cost(input=y_predict, label=y) outputs(cost) ``` 这段简短的配置展示了PaddlePaddle的基本用法: -- 首先,第一部分定义了数据输入。一般情况下,PaddlePaddle先从一个文件列表里获得数据文件地址,然后交给用户自定义的函数(例如上面的`process`函数)进行读入和预处理从而得到真实输入。本文中由于输入数据是随机生成的不需要读输入文件,所以放一个空列表(`empty.list`)即可。 +- 第一部分定义了数据输入。一般情况下,PaddlePaddle先从一个文件列表里获得数据文件地址,然后交给用户自定义的函数(例如上面的`process`函数)进行读入和预处理从而得到真实输入。本文中由于输入数据是随机生成的不需要读输入文件,所以放一个空列表(`empty.list`)即可。 -- 第二部分主要是选择学习算法,它定义了模型参数如何改变。PaddlePaddle提供了很多优秀的学习算法,但这里使用一个简单的基于momentum的算法就足够了,它每次读取12个数据进行计算和模型更新。 +- 第二部分主要是选择学习算法,它定义了模型参数改变的规则。PaddlePaddle提供了很多优秀的学习算法,这里使用一个基于momentum的随机梯度下降(SGD)算法,该算法每批量(batch)读取12个采样数据进行随机梯度计算来更新更新。 -- 最后一部分是神经网络的配置。由于PaddlePaddle已经实现了丰富的网络单元(Layer),所以很多时候你需要做的只是声明正确的网络单元并把它们拼接起来。这里使用了三种网络单元: - - **数据层**:数据层 `data_layer` 是神经网络的入口,它读入数据并将它们传输到下游的其它单元。这里数据层有两个,分别对应于变量 `X` 和 `Y`。 - - **全连接层**:全连接层 `fc_layer` 是基础的计算单元,这里利用它建模变量之间的线性关系。计算单元是神经网络的核心,PaddlePaddle支持大量的计算单元和任意深度的网络连接,从而可以挖掘复杂的数据关系。 - - **回归损失层**:回归损失层 `regression_cost`是众多损失函数层的一种,它们在训练过程作为网络的出口,用来计算模型的表现,并指导模型参数的改变。 +- 最后一部分是神经网络的配置。由于PaddlePaddle已经实现了丰富的网络层,所以很多时候你需要做的只是定义正确的网络层并把它们连接起来。这里使用了三种网络单元: + - **数据层**:数据层 `data_layer` 是神经网络的入口,它读入数据并将它们传输到接下来的网络层。这里数据层有两个,分别对应于变量 `X` 和 `Y`。 + - **全连接层**:全连接层 `fc_layer` 是基础的计算单元,这里利用它建模变量之间的线性关系。计算单元是神经网络的核心,PaddlePaddle支持大量的计算单元和任意深度的网络连接,从而可以拟合任意的函数来学习复杂的数据关系。 + - **回归误差代价层**:回归误差代价层 `regression_cost`是众多误差代价函数层的一种,它们在训练过程作为网络的出口,用来计算模型的误差,是模型参数优化的目标函数。 -这样定义了网络结构并保存为`trainer_config.py`之后,运行训练命令即可: +定义了网络结构并保存为`trainer_config.py`之后,运行以下训练命令: ``` paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 ``` -PaddlePaddle将在观测数据集上迭代训练30轮,并将每轮的模型结果存放在 `./output` 路径下。从输出日志可以看到,随着轮数增加损失函数的输出在不断的减小,这意味着模型在不断的改进,直到逼近真实解:` Y = 2X + 0.3 ` +PaddlePaddle将在观测数据集上迭代训练30轮,并将每轮的模型结果存放在 `./output` 路径下。从输出日志可以看到,随着轮数增加误差代价函数的输出在不断的减小,这意味着模型在训练数据上不断的改进,直到逼近真实解:` Y = 2X + 0.3 ` ## 4. 模型检验 -训练完成后,我们希望能够检验模型的好坏。一种常用的做法是用模型对另外一组数据进行预测,然后评价预测的效果。但在这个例子中,由于已经知道了真实答案,我们可以直接观察模型的参数是否符合预期来进行检验。 +训练完成后,我们希望能够检验模型的好坏。一种常用的做法是用学习的模型对另外一组测试数据进行预测,评价预测的效果。在这个例子中,由于已经知道了真实答案,我们可以直接观察模型的参数是否符合预期来进行检验。 PaddlePaddle将每个模型参数作为一个numpy数组单独存为一个文件,所以可以利用如下方法读取模型的参数。 @@ -94,9 +94,9 @@ print 'w=%.6f, b=%.6f' % (load('output/pass-00029/w'), load('output/pass-00029/b ```
    ![](./parameters.png)
    -从图中可以看到,虽然 `w` 和 `b` 都使用随机值初始化,但在起初的几轮训练中它们都在快速逼近真实值,并且后续仍在不断改进,使得最终得到的模型几乎与真实模型重合。 +从图中可以看到,虽然 `w` 和 `b` 都使用随机值初始化,但在起初的几轮训练中它们都在快速逼近真实值,并且后续仍在不断改进,使得最终得到的模型几乎与真实模型一致。 -这样,我们就完成了对单变量线性回归问题的解决:将数据输入PaddlePaddle,训练模型,最后验证结果。 +这样,我们用PaddlePaddle解决了单变量线性回归问题, 包括数据输入,模型训练和最后的结果验证。 ## 5. 推荐后续阅读 -- GitLab From 66fba6704f37712372546a24cfaec5bd60ffad12 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 18 Nov 2016 13:03:25 +0800 Subject: [PATCH 0059/1503] modify directory structure in source code --- doc/source/{api => }/api.rst | 4 +- doc/source/cuda/{cuda => }/cuda.rst | 24 +++--- doc/source/cuda/cuda/index.rst | 7 -- doc/source/cuda/index.rst | 10 +++ doc/source/cuda/{matrix => }/matrix.rst | 56 +++++++------- doc/source/cuda/matrix/index.rst | 7 -- doc/source/cuda/{rnn/rnn.rst => nn.rst} | 27 +++---- doc/source/cuda/rnn/index.rst | 7 -- doc/source/cuda/{utils => }/utils.rst | 23 +++--- doc/source/cuda/utils/index.rst | 7 -- .../index.rst => activations.rst} | 2 +- doc/source/gserver/dataprovider/index.rst | 7 -- .../{dataprovider => }/dataproviders.rst | 18 +++-- .../gserver/{evaluators => }/evaluators.rst | 15 ++-- doc/source/gserver/evaluators/index.rst | 7 -- doc/source/gserver/gradientmachines.rst | 27 +++++++ .../gradientmachines/gradientmachines.rst | 40 ---------- doc/source/gserver/gradientmachines/index.rst | 7 -- doc/source/gserver/index.rst | 12 +++ .../gserver/{layers/layer.rst => layers.rst} | 28 ++++++- doc/source/gserver/layers/index.rst | 7 -- doc/source/gserver/neworks.rst | 12 +++ doc/source/index.md | 49 ------------ doc/source/index.rst | 14 ++++ doc/source/math/functions.rst | 10 +++ doc/source/math/index.rst | 10 +++ doc/source/math/matrix.rst | 76 +++++++++++++++++++ doc/source/math/matrix/index.rst | 7 -- doc/source/math/matrix/matrix.rst | 20 ----- doc/source/math/{utils => }/utils.rst | 15 +++- doc/source/math/utils/index.rst | 7 -- doc/source/math/vector.rst | 37 +++++++++ doc/source/parameter/index.rst | 9 +++ doc/source/parameter/optimizer.rst | 22 ++++++ doc/source/parameter/optimizer/index.rst | 7 -- doc/source/parameter/optimizer/optimizer.rst | 7 -- .../parameter/{parameter => }/parameter.rst | 16 ++-- doc/source/parameter/parameter/index.rst | 7 -- doc/source/parameter/update/index.rst | 7 -- .../{update/update.rst => updater.rst} | 13 +++- doc/source/pserver/client.rst | 12 +++ doc/source/pserver/client/client.rst | 14 ---- doc/source/pserver/client/index.rst | 7 -- doc/source/pserver/index.rst | 10 +++ doc/source/pserver/network.rst | 27 +++++++ doc/source/pserver/network/index.rst | 7 -- doc/source/pserver/network/network.rst | 42 ---------- doc/source/pserver/server.rst | 12 +++ doc/source/pserver/server/index.rst | 7 -- doc/source/pserver/server/server.rst | 14 ---- doc/source/{trainer => }/trainer.rst | 2 +- doc/source/utils/customStackTrace.rst | 5 -- doc/source/utils/enum.rst | 8 +- doc/source/utils/index.rst | 11 +++ doc/source/utils/lock.rst | 43 +++++------ doc/source/utils/queue.rst | 4 - doc/source/utils/thread.rst | 45 ++++------- 57 files changed, 484 insertions(+), 473 deletions(-) rename doc/source/{api => }/api.rst (86%) rename doc/source/cuda/{cuda => }/cuda.rst (69%) delete mode 100644 doc/source/cuda/cuda/index.rst create mode 100644 doc/source/cuda/index.rst rename doc/source/cuda/{matrix => }/matrix.rst (76%) delete mode 100644 doc/source/cuda/matrix/index.rst rename doc/source/cuda/{rnn/rnn.rst => nn.rst} (80%) delete mode 100644 doc/source/cuda/rnn/index.rst rename doc/source/cuda/{utils => }/utils.rst (56%) delete mode 100644 doc/source/cuda/utils/index.rst rename doc/source/gserver/{activations/index.rst => activations.rst} (83%) delete mode 100644 doc/source/gserver/dataprovider/index.rst rename doc/source/gserver/{dataprovider => }/dataproviders.rst (89%) rename doc/source/gserver/{evaluators => }/evaluators.rst (96%) delete mode 100644 doc/source/gserver/evaluators/index.rst create mode 100644 doc/source/gserver/gradientmachines.rst delete mode 100644 doc/source/gserver/gradientmachines/gradientmachines.rst delete mode 100644 doc/source/gserver/gradientmachines/index.rst create mode 100644 doc/source/gserver/index.rst rename doc/source/gserver/{layers/layer.rst => layers.rst} (95%) delete mode 100644 doc/source/gserver/layers/index.rst create mode 100644 doc/source/gserver/neworks.rst delete mode 100644 doc/source/index.md create mode 100644 doc/source/index.rst create mode 100644 doc/source/math/functions.rst create mode 100644 doc/source/math/index.rst create mode 100644 doc/source/math/matrix.rst delete mode 100644 doc/source/math/matrix/index.rst delete mode 100644 doc/source/math/matrix/matrix.rst rename doc/source/math/{utils => }/utils.rst (62%) delete mode 100644 doc/source/math/utils/index.rst create mode 100644 doc/source/math/vector.rst create mode 100644 doc/source/parameter/index.rst create mode 100644 doc/source/parameter/optimizer.rst delete mode 100644 doc/source/parameter/optimizer/index.rst delete mode 100644 doc/source/parameter/optimizer/optimizer.rst rename doc/source/parameter/{parameter => }/parameter.rst (66%) delete mode 100644 doc/source/parameter/parameter/index.rst delete mode 100644 doc/source/parameter/update/index.rst rename doc/source/parameter/{update/update.rst => updater.rst} (75%) create mode 100644 doc/source/pserver/client.rst delete mode 100644 doc/source/pserver/client/client.rst delete mode 100644 doc/source/pserver/client/index.rst create mode 100644 doc/source/pserver/index.rst create mode 100644 doc/source/pserver/network.rst delete mode 100644 doc/source/pserver/network/index.rst delete mode 100644 doc/source/pserver/network/network.rst create mode 100644 doc/source/pserver/server.rst delete mode 100644 doc/source/pserver/server/index.rst delete mode 100644 doc/source/pserver/server/server.rst rename doc/source/{trainer => }/trainer.rst (94%) create mode 100644 doc/source/utils/index.rst diff --git a/doc/source/api/api.rst b/doc/source/api.rst similarity index 86% rename from doc/source/api/api.rst rename to doc/source/api.rst index 6fc450202d..22fb5fb644 100644 --- a/doc/source/api/api.rst +++ b/doc/source/api.rst @@ -1,5 +1,5 @@ -API -======== +Api +=== .. doxygenfile:: paddle/api/PaddleAPI.h .. doxygenfile:: paddle/api/Internal.h diff --git a/doc/source/cuda/cuda/cuda.rst b/doc/source/cuda/cuda.rst similarity index 69% rename from doc/source/cuda/cuda/cuda.rst rename to doc/source/cuda/cuda.rst index 52f17c2b2e..77b6e4a4d2 100644 --- a/doc/source/cuda/cuda/cuda.rst +++ b/doc/source/cuda/cuda.rst @@ -1,39 +1,35 @@ Cuda -============= +==== Dynamic Link Libs --------------------------- +----------------- hl_dso_loader.h -`````````````````` +``````````````` .. doxygenfile:: paddle/cuda/include/hl_dso_loader.h GPU Resources ----------------- +------------- hl_cuda.ph -`````````````` +`````````` .. doxygenfile:: paddle/cuda/include/hl_cuda.ph hl_cuda.h -`````````````` +````````` .. doxygenfile:: paddle/cuda/include/hl_cuda.h CUDA Wrapper --------------- +------------ hl_cuda_cublas.h -`````````````````````` +```````````````` .. doxygenfile:: paddle/cuda/include/hl_cuda_cublas.h hl_cuda_cudnn.h -`````````````````````` +``````````````` .. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.h hl_cuda_cudnn.h -`````````````````````` +``````````````` .. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.ph - - - - diff --git a/doc/source/cuda/cuda/index.rst b/doc/source/cuda/cuda/index.rst deleted file mode 100644 index 5fa38ff0fc..0000000000 --- a/doc/source/cuda/cuda/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -CUDA -==================== - -.. toctree:: - :maxdepth: 3 - - cuda.rst diff --git a/doc/source/cuda/index.rst b/doc/source/cuda/index.rst new file mode 100644 index 0000000000..d453f658a7 --- /dev/null +++ b/doc/source/cuda/index.rst @@ -0,0 +1,10 @@ +Cuda +==== + +.. toctree:: + :maxdepth: 2 + + cuda.rst + matrix.rst + nn.rst + utils.rst diff --git a/doc/source/cuda/matrix/matrix.rst b/doc/source/cuda/matrix.rst similarity index 76% rename from doc/source/cuda/matrix/matrix.rst rename to doc/source/cuda/matrix.rst index dd4f06599c..b7699c83ed 100644 --- a/doc/source/cuda/matrix/matrix.rst +++ b/doc/source/cuda/matrix.rst @@ -1,61 +1,59 @@ Matrix -======= +====== -Base Matrix -------------- +Base +---- hl_matrix.h -`````````````````` +``````````` .. doxygenfile:: paddle/cuda/include/hl_matrix.h hl_matrix_base.h -`````````````````` +```````````````` .. doxygenfile:: paddle/cuda/include/hl_matrix_base.cuh hl_matrix_apply.cuh -`````````````````````` +``````````````````` .. doxygenfile:: paddle/cuda/include/hl_matrix_apply.cuh hl_matrix_ops.cuh -`````````````````````` +````````````````` .. doxygenfile:: paddle/cuda/include/hl_matrix_ops.cuh hl_matrix_type.cuh -`````````````````````` +`````````````````` .. doxygenfile:: paddle/cuda/include/hl_matrix_type.cuh hl_sse_matrix_kernel.cuh -`````````````````````````` +```````````````````````` .. doxygenfile:: paddle/cuda/include/hl_sse_matrix_kernel.cuh +Matrix Function +--------------- + hl_batch_transpose.h -`````````````````````````` +```````````````````` .. doxygenfile:: paddle/cuda/include/hl_batch_transpose.h -Sparse Matrix --------------- - -hl_sparse.h -`````````````````` -.. doxygenfile:: paddle/cuda/include/hl_sparse.h - -hl_sparse.ph -`````````````````````` -.. doxygenfile:: paddle/cuda/include/hl_sparse.ph - -Others ---------------- - hl_aggregate.h -`````````````````` +`````````````` .. doxygenfile:: paddle/cuda/include/hl_aggregate.h +hl_top_k.h +`````````` +.. doxygenfile:: paddle/cuda/include/hl_top_k.h + hl_table_apply.h -`````````````````` +```````````````` .. doxygenfile:: paddle/cuda/include/hl_table_apply.h -hl_top_k.h -`````````````````` -.. doxygenfile:: paddle/cuda/include/hl_top_k.h +Sparse Matrix +------------- +hl_sparse.h +``````````` +.. doxygenfile:: paddle/cuda/include/hl_sparse.h +hl_sparse.ph +```````````` +.. doxygenfile:: paddle/cuda/include/hl_sparse.ph diff --git a/doc/source/cuda/matrix/index.rst b/doc/source/cuda/matrix/index.rst deleted file mode 100644 index 63f95eb466..0000000000 --- a/doc/source/cuda/matrix/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Matrix -==================== - -.. toctree:: - :maxdepth: 3 - - matrix.rst diff --git a/doc/source/cuda/rnn/rnn.rst b/doc/source/cuda/nn.rst similarity index 80% rename from doc/source/cuda/rnn/rnn.rst rename to doc/source/cuda/nn.rst index ce8ed96692..5ebd863242 100644 --- a/doc/source/cuda/rnn/rnn.rst +++ b/doc/source/cuda/nn.rst @@ -1,36 +1,37 @@ -Neural Networks -================== +Neural Network +============== Base -------- +---- + .. doxygenfile:: paddle/cuda/include/hl_gpu.h -.. doxygenfile:: paddle/cuda/include/hl_cnn.h .. doxygenfile:: paddle/cuda/include/hl_functions.h .. doxygenfile:: paddle/cuda/include/hl_avx_functions.h -.. doxygenfile:: paddle/cuda/include/hl_device_functions.cuh .. doxygenfile:: paddle/cuda/include/hl_gpu_functions.cuh - -Activation Functions ------------------------ .. doxygenfile:: paddle/cuda/include/hl_activation_functions.h +CNN Related APIs +---------------- +.. doxygenfile:: paddle/cuda/include/hl_cnn.h +.. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.h + RNN Related APIs ------------------ +---------------- .. doxygenfile:: paddle/cuda/include/hl_recurrent_apply.cuh .. doxygenfile:: paddle/cuda/include/hl_sequence.h LSTM Model -`````````````` +`````````` + .. doxygenfile:: paddle/cuda/include/hl_lstm.h .. dpxygenfile:: paddle/cuda/include/hl_cpu_lstm.cuh .. doxygenfile:: paddle/cuda/include/hl_gpu_lstm.cuh .. doxygenfile:: paddle/cuda/include/hl_lstm_ops.cuh GRU Model -```````````````` +````````` + .. doxygenfile:: paddle/cuda/include/hl_gru_ops.cuh .. doxygenfile:: paddle/cuda/include/hl_cpu_gru.cuh .. doxygenfile:: paddle/cuda/include/hl_gpu_gru.cuh - - diff --git a/doc/source/cuda/rnn/index.rst b/doc/source/cuda/rnn/index.rst deleted file mode 100644 index 4913e47ba1..0000000000 --- a/doc/source/cuda/rnn/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -RNN -==================== - -.. toctree:: - :maxdepth: 3 - - rnn.rst diff --git a/doc/source/cuda/utils/utils.rst b/doc/source/cuda/utils.rst similarity index 56% rename from doc/source/cuda/utils/utils.rst rename to doc/source/cuda/utils.rst index 1ea3e5404a..c5d4a45a44 100644 --- a/doc/source/cuda/utils/utils.rst +++ b/doc/source/cuda/utils.rst @@ -1,23 +1,18 @@ -Utilities -=========== +Utils +===== HPPL Base ------------- - -hl_base.h -`````````````` +--------- .. doxygenfile:: paddle/cuda/include/hl_base.h Timer ------------ - -hl_time.h -`````````````` +----- .. doxygenfile:: paddle/cuda/include/hl_time.h Thread Resource ------------ - -hl_thread.ph -`````````````` +--------------- .. doxygenfile:: paddle/cuda/include/hl_thread.ph + +Device Function +--------------- +.. doxygenfile:: paddle/cuda/include/hl_device_functions.cuh diff --git a/doc/source/cuda/utils/index.rst b/doc/source/cuda/utils/index.rst deleted file mode 100644 index 7a84cbe27d..0000000000 --- a/doc/source/cuda/utils/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Utils -==================== - -.. toctree:: - :maxdepth: 3 - - utils.rst diff --git a/doc/source/gserver/activations/index.rst b/doc/source/gserver/activations.rst similarity index 83% rename from doc/source/gserver/activations/index.rst rename to doc/source/gserver/activations.rst index ccdae41128..55b9d3be38 100644 --- a/doc/source/gserver/activations/index.rst +++ b/doc/source/gserver/activations.rst @@ -1,5 +1,5 @@ Activations -============= +=========== .. doxygenclass:: paddle::ActivationFunction :members: diff --git a/doc/source/gserver/dataprovider/index.rst b/doc/source/gserver/dataprovider/index.rst deleted file mode 100644 index 4f6077f122..0000000000 --- a/doc/source/gserver/dataprovider/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Data Providers Documents -========================== - -.. toctree:: - :maxdepth: 3 - - dataproviders.rst diff --git a/doc/source/gserver/dataprovider/dataproviders.rst b/doc/source/gserver/dataproviders.rst similarity index 89% rename from doc/source/gserver/dataprovider/dataproviders.rst rename to doc/source/gserver/dataproviders.rst index e8aa4bc356..73e45f1f33 100644 --- a/doc/source/gserver/dataprovider/dataproviders.rst +++ b/doc/source/gserver/dataproviders.rst @@ -1,23 +1,27 @@ +============== Data Providers -================ +============== + +Data Providers +============== Base DataProvider ------------------- +----------------- .. doxygenclass:: paddle::DataProvider :members: DataProviderGroup -------------------- +----------------- .. doxygenclass:: paddle::DataProviderGroup :members: MultiDataProvider -------------------- +----------------- .. doxygenclass:: paddle::MultiDataProvider :members: PyDataProvider -=================== +============== IFieldScanner ------------- @@ -45,7 +49,7 @@ SparseValueScanner :members: SequenceScanner ------------------- +--------------- .. doxygenclass:: paddle::SparseValueScanner :members: @@ -78,6 +82,6 @@ ProtoDataProvider :members: ProtoSequenceDataProvider ----------------- +------------------------- .. doxygenclass:: paddle::ProtoSequenceDataProvider :members: diff --git a/doc/source/gserver/evaluators/evaluators.rst b/doc/source/gserver/evaluators.rst similarity index 96% rename from doc/source/gserver/evaluators/evaluators.rst rename to doc/source/gserver/evaluators.rst index 0c5cc85e7d..f5361f76cd 100644 --- a/doc/source/gserver/evaluators/evaluators.rst +++ b/doc/source/gserver/evaluators.rst @@ -1,14 +1,15 @@ -Base Evaluator -============== +========== +Evaluators +========== + +Base +==== -Evaluator ---------- .. doxygenclass:: paddle::Evaluator :members: - -Utils -===== +Sum +=== SumEvaluator ------------ diff --git a/doc/source/gserver/evaluators/index.rst b/doc/source/gserver/evaluators/index.rst deleted file mode 100644 index 298de3e1a3..0000000000 --- a/doc/source/gserver/evaluators/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Evaluators -========== - -.. toctree:: - :maxdepth: 3 - - evaluators.rst diff --git a/doc/source/gserver/gradientmachines.rst b/doc/source/gserver/gradientmachines.rst new file mode 100644 index 0000000000..d67c8dbcbd --- /dev/null +++ b/doc/source/gserver/gradientmachines.rst @@ -0,0 +1,27 @@ +Gradient Machines +================= + +Gradient Machine +---------------- +.. doxygenclass:: paddle::GradientMachine + :members: + +Gradient Machine Mode +--------------------- +.. doxygenclass:: paddle::IGradientMachineMode + :members: + +Multi Gradient Machine +---------------------- +.. doxygenclass:: paddle::MultiGradientMachine + :members: + +TrainerThread +````````````` +.. doxygenclass:: paddle::TrainerThread + :members: + +Recurrent Gradient Machine +-------------------------- +.. doxygenclass:: paddle::RecurrentGradientMachine + :members: diff --git a/doc/source/gserver/gradientmachines/gradientmachines.rst b/doc/source/gserver/gradientmachines/gradientmachines.rst deleted file mode 100644 index 3607664c85..0000000000 --- a/doc/source/gserver/gradientmachines/gradientmachines.rst +++ /dev/null @@ -1,40 +0,0 @@ -Gradient Machines -================ - -GradientMachine ---------------------- -.. doxygenclass:: paddle::GradientMachine - :members: - -GradientMachineModel --------------------- -.. doxygenclass:: paddle::IGradientMachineMode - :members: - -MultiGradientMachine ---------------------- -.. doxygenclass:: paddle::MultiGradientMachine - :members: - -TrainerThread -````````````` -.. doxygenclass:: paddle::TrainerThread - :members: - -Recurrent Gradient Machines ---------------------------- -.. doxygenclass:: paddle::RecurrentGradientMachine - :members: - -Networks -======== - -NeuralNetwork -------------- -.. doxygenclass:: paddle::NeuralNetwork - :members: - -ParallelNeuralNetwork ---------------------- -.. doxygenclass:: paddle::ParallelNeuralNetwork - :members: diff --git a/doc/source/gserver/gradientmachines/index.rst b/doc/source/gserver/gradientmachines/index.rst deleted file mode 100644 index 997c29a102..0000000000 --- a/doc/source/gserver/gradientmachines/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Gradient Machines Documents -============================= - -.. toctree:: - :maxdepth: 3 - - gradientmachines.rst diff --git a/doc/source/gserver/index.rst b/doc/source/gserver/index.rst new file mode 100644 index 0000000000..b0b41e3ad1 --- /dev/null +++ b/doc/source/gserver/index.rst @@ -0,0 +1,12 @@ +Gserver +======= + +.. toctree:: + :maxdepth: 2 + + activations.rst + dataproviders.rst + evaluators.rst + gradientmachines.rst + layers.rst + neworks.rst diff --git a/doc/source/gserver/layers/layer.rst b/doc/source/gserver/layers.rst similarity index 95% rename from doc/source/gserver/layers/layer.rst rename to doc/source/gserver/layers.rst index 4b8e149505..191b2bdff2 100644 --- a/doc/source/gserver/layers/layer.rst +++ b/doc/source/gserver/layers.rst @@ -1,6 +1,10 @@ -Base +====== +Layers ====== +Base +==== + Layer ----- .. doxygenclass:: paddle::Layer @@ -17,7 +21,7 @@ Operator :members: Data Layer -=========== +========== .. doxygenclass:: paddle::DataLayer :members: @@ -58,6 +62,11 @@ CudnnConvLayer .. doxygenclass:: paddle::CudnnConvLayer :members: +ExpandConvBaseLayer +------------------- +.. doxygenclass:: paddle::ExpandConvBaseLayer + :members: + ExpandConvLayer --------------- .. doxygenclass:: paddle::ExpandConvLayer @@ -86,6 +95,16 @@ CudnnPoolLayer .. doxygenclass:: paddle::CudnnPoolLayer :members: +SpatialPyramidPoolLayer +----------------------- +.. doxygenclass:: paddle::SpatialPyramidPoolLayer + :members: + +MaxOutLayer +----------- +.. doxygenclass:: paddle::MaxOutLayer + :members: + Norm Layers =========== @@ -402,6 +421,11 @@ TransLayer Sampling Layers =============== +BilinearInterpLayer +------------------- +.. doxygenclass:: paddle::BilinearInterpLayer + :members: + MultinomialSampler ------------------ .. doxygenclass:: paddle::MultinomialSampler diff --git a/doc/source/gserver/layers/index.rst b/doc/source/gserver/layers/index.rst deleted file mode 100644 index 559c5436b1..0000000000 --- a/doc/source/gserver/layers/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Layers Documents -==================== - -.. toctree:: - :maxdepth: 3 - - layer.rst diff --git a/doc/source/gserver/neworks.rst b/doc/source/gserver/neworks.rst new file mode 100644 index 0000000000..73fb60d549 --- /dev/null +++ b/doc/source/gserver/neworks.rst @@ -0,0 +1,12 @@ +Networks +======== + +NeuralNetwork +------------- +.. doxygenclass:: paddle::NeuralNetwork + :members: + +ParallelNeuralNetwork +--------------------- +.. doxygenclass:: paddle::ParallelNeuralNetwork + :members: diff --git a/doc/source/index.md b/doc/source/index.md deleted file mode 100644 index 55fcdeb3df..0000000000 --- a/doc/source/index.md +++ /dev/null @@ -1,49 +0,0 @@ -# Source Code Documents - -## cuda - -- [CUDA](cuda/cuda/index.rst) -- [Matrix](cuda/matrix/index.rst) -- [RNN](cuda/rnn/index.rst) -- [Utils](cuda/utils/index.rst) - -## gserver - -- [Activations](gserver/activations/index.rst) -- [Data Providers](gserver/dataprovider/index.rst) -- [Evaluators](gserver/evaluators/index.rst) -- [Gradient Machines](gserver/gradientmachines/index.rst) -- [Layers](gserver/layers/index.rst) - -## math - -- [Matrix](math/matrix/index.rst) -- [Utils](math/utils/index.rst) - -## parameter - -- [Parameter](parameter/parameter/index.rst) -- [Update](parameter/update/index.rst) -- [Optimizer](parameter/optimizer/index.rst) - -## pserver - -- [Client](pserver/client/index.rst) -- [Network](pserver/network/index.rst) -- [Server](pserver/server/index.rst) - -## trainer - -- [Trainer](trainer/trainer.rst) - -## api - -- [API](api/api.rst) - -## utils - -- [CustomStackTrace](utils/customStackTrace.rst) -- [Enumeration wrapper](utils/enum.rst) -- [Lock](utils/lock.rst) -- [Queue](utils/queue.rst) -- [Thread](utils/thread.rst) diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000000..7aebcadd75 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,14 @@ +Source Code Documents +===================== + +.. toctree:: + :maxdepth: 1 + + gserver/index.rst + trainer.rst + parameter/index.rst + pserver.rst + api.rst + cuda/index.rst + math/index.rst + utils/index.rst diff --git a/doc/source/math/functions.rst b/doc/source/math/functions.rst new file mode 100644 index 0000000000..aef12e0f00 --- /dev/null +++ b/doc/source/math/functions.rst @@ -0,0 +1,10 @@ +Functions +========= + +MathFunctions +------------- +.. doxygenfile:: paddle/math/MathFunctions.h + +SIMDFunctions +------------- +.. doxygenfile:: paddle/math/SIMDFunctions.h diff --git a/doc/source/math/index.rst b/doc/source/math/index.rst new file mode 100644 index 0000000000..2ec16f2b44 --- /dev/null +++ b/doc/source/math/index.rst @@ -0,0 +1,10 @@ +Math +==== + +.. toctree:: + :maxdepth: 2 + + vector.rst + matrix.rst + functions.rst + utils.rst diff --git a/doc/source/math/matrix.rst b/doc/source/math/matrix.rst new file mode 100644 index 0000000000..9bb20f618d --- /dev/null +++ b/doc/source/math/matrix.rst @@ -0,0 +1,76 @@ +Matrix +====== + +Base +---- + +BaseMatrix Template +``````````````````` +.. doxygenclass:: paddle::BaseMatrixT + :members: + +Matrix +`````` +.. doxygenclass:: paddle::Matrix + :members: + +MatrixOffset +```````````` +.. doxygenclass:: paddle::MatrixOffset + :members: + +CpuMatrix +--------- + +CpuMatrix +````````` +.. doxygenclass:: paddle::CpuMatrix + :members: + +SharedCpuMatrix +``````````````` +.. doxygenclass:: paddle::SharedCpuMatrix + :members: + +GpuMatrix +--------- +.. doxygenclass:: paddle::GpuMatrix + :members: + +CpuSparseMatrix +--------------- + +CpuSparseMatrix +``````````````` +.. doxygenclass:: paddle::CpuSparseMatrix + :members: + +SparseRowCpuMatrix +`````````````````` +.. doxygenclass:: paddle::SparseRowCpuMatrix + :members: + +SparseAutoGrowRowCpuMatrix +`````````````````````````` +.. doxygenclass:: paddle::SparseAutoGrowRowCpuMatrix + :members: + +SparsePrefetchRowCpuMatrix +`````````````````````````` +.. doxygenclass:: paddle::SparsePrefetchRowCpuMatrix + :members: + +SparseRowIdsCpuMatrix +````````````````````` +.. doxygenclass:: paddle::SparseRowIdsCpuMatrix + :members: + +CacheRowCpuMatrix +````````````````` +.. doxygenclass:: paddle::CacheRowCpuMatrix + :members: + +GpuSparseMatrix +--------------- +.. doxygenclass:: paddle::GpuSparseMatrix + :members: diff --git a/doc/source/math/matrix/index.rst b/doc/source/math/matrix/index.rst deleted file mode 100644 index 68410f2a27..0000000000 --- a/doc/source/math/matrix/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Matrix Documents -==================== - -.. toctree:: - :maxdepth: 3 - - matrix.rst diff --git a/doc/source/math/matrix/matrix.rst b/doc/source/math/matrix/matrix.rst deleted file mode 100644 index b12e3934f4..0000000000 --- a/doc/source/math/matrix/matrix.rst +++ /dev/null @@ -1,20 +0,0 @@ -Matrix -======= - -Base --------- -.. doxygenfile:: paddle/math/BaseMatrix.h - -Sparse Matrix ----------------- -.. doxygenfile:: paddle/math/Matrix.h -.. doxygenfile:: paddle/math/Vector.h -.. doxygenfile:: paddle/math/MathUtils.h -.. doxygenfile:: paddle/math/SparseMatrix.h -.. doxygenfile:: paddle/math/SparseRowMatrix.h -.. doxygenfile:: paddle/math/CpuSparseMatrix.h - -Others ----------- -.. doxygenfile:: paddle/math/MathFunctions.h -.. doxygenfile:: paddle/math/SIMDFunctions.h diff --git a/doc/source/math/utils/utils.rst b/doc/source/math/utils.rst similarity index 62% rename from doc/source/math/utils/utils.rst rename to doc/source/math/utils.rst index 3df721a47b..55d9961a39 100644 --- a/doc/source/math/utils/utils.rst +++ b/doc/source/math/utils.rst @@ -1,9 +1,18 @@ -Utils -======= +Memory Manager +============== Memory Handle --------------- +------------- .. doxygenfile:: paddle/math/MemoryHandle.h + +Allocator +--------- .. doxygenfile:: paddle/math/Allocator.h + +PoolAllocator +````````````` .. doxygenfile:: paddle/math/PoolAllocator.h + +Storage +------- .. doxygenfile:: paddle/math/Storage.h diff --git a/doc/source/math/utils/index.rst b/doc/source/math/utils/index.rst deleted file mode 100644 index e5fe335da2..0000000000 --- a/doc/source/math/utils/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Utils Documents -==================== - -.. toctree:: - :maxdepth: 3 - - utils.rst diff --git a/doc/source/math/vector.rst b/doc/source/math/vector.rst new file mode 100644 index 0000000000..579ed7ba55 --- /dev/null +++ b/doc/source/math/vector.rst @@ -0,0 +1,37 @@ +Vector +====== + +BaseVector +`````````` +.. doxygenclass:: paddle::BaseVector + :members: + +Vector Template +``````````````` +.. doxygenclass:: paddle::VectorT + :members: + +CpuVector Template +`````````````````` +.. doxygenclass:: paddle::CpuVectorT + :members: + +GpuVector Template +`````````````````` +.. doxygenclass:: paddle::GpuVectorT + :members: + +Parallel CpuVector Template +``````````````````````````` +.. doxygenclass:: paddle::ParallelCpuVectorT + :members: + +Parallel GpuVector Template +``````````````````````````` +.. doxygenclass:: paddle::ParallelGpuVectorT + :members: + +CpuGpuVector Template +````````````````````` +.. doxygenclass:: paddle::CpuGpuVectorT + :members: diff --git a/doc/source/parameter/index.rst b/doc/source/parameter/index.rst new file mode 100644 index 0000000000..3bf6948dc3 --- /dev/null +++ b/doc/source/parameter/index.rst @@ -0,0 +1,9 @@ +Parameter +========= + +.. toctree:: + :maxdepth: 2 + + parameter.rst + optimizer.rst + updater.rst diff --git a/doc/source/parameter/optimizer.rst b/doc/source/parameter/optimizer.rst new file mode 100644 index 0000000000..b5b8b850b3 --- /dev/null +++ b/doc/source/parameter/optimizer.rst @@ -0,0 +1,22 @@ +Optimizer +========= + +ParameterOptimizer +------------------ +.. doxygenfile:: paddle/parameter/ParameterOptimizer.h + +Regularizer +----------- +.. doxygenfile:: paddle/parameter/Regularizer.h + +FirstOrderOptimizer +------------------- +.. doxygenfile:: paddle/parameter/FirstOrderOptimizer.h + +AverageOptimizer +---------------- +.. doxygenfile:: paddle/parameter/AverageOptimizer.h + +OptimizerWithRegularizer +------------------------ +.. doxygenfile:: paddle/parameter/OptimizerWithRegularizer.h diff --git a/doc/source/parameter/optimizer/index.rst b/doc/source/parameter/optimizer/index.rst deleted file mode 100644 index 3338af5608..0000000000 --- a/doc/source/parameter/optimizer/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Parameter Documents -==================== - -.. toctree:: - :maxdepth: 3 - - optimizer.rst diff --git a/doc/source/parameter/optimizer/optimizer.rst b/doc/source/parameter/optimizer/optimizer.rst deleted file mode 100644 index 3d9e49217e..0000000000 --- a/doc/source/parameter/optimizer/optimizer.rst +++ /dev/null @@ -1,7 +0,0 @@ -Optimizer -============ - -.. doxygenfile:: paddle/parameter/FirstOrderOptimizer.h -.. doxygenfile:: paddle/parameter/AverageOptimizer.h -.. doxygenfile:: paddle/parameter/ParameterOptimizer.h -.. doxygenfile:: paddle/parameter/OptimizerWithRegularizer.h diff --git a/doc/source/parameter/parameter/parameter.rst b/doc/source/parameter/parameter.rst similarity index 66% rename from doc/source/parameter/parameter/parameter.rst rename to doc/source/parameter/parameter.rst index 2b7afdb409..2daa62d4e6 100644 --- a/doc/source/parameter/parameter/parameter.rst +++ b/doc/source/parameter/parameter.rst @@ -1,16 +1,12 @@ Parameter -============= - -Weight --------- -.. doxygenfile:: paddle/parameter/Weight.h - -Regularizer ------------- -.. doxygenfile:: paddle/parameter/Regularizer.h +========= Parameter -------------- +--------- .. doxygenfile:: paddle/parameter/Argument.h .. doxygenfile:: paddle/parameter/Parameter.h .. doxygenfile:: paddle/parameter/ParallelParameter.h + +Weight +------ +.. doxygenfile:: paddle/parameter/Weight.h diff --git a/doc/source/parameter/parameter/index.rst b/doc/source/parameter/parameter/index.rst deleted file mode 100644 index e7ed70ec4c..0000000000 --- a/doc/source/parameter/parameter/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Parameter Documents -==================== - -.. toctree:: - :maxdepth: 3 - - parameter.rst diff --git a/doc/source/parameter/update/index.rst b/doc/source/parameter/update/index.rst deleted file mode 100644 index 1bbd733193..0000000000 --- a/doc/source/parameter/update/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Parameter Documents -==================== - -.. toctree:: - :maxdepth: 3 - - update.rst diff --git a/doc/source/parameter/update/update.rst b/doc/source/parameter/updater.rst similarity index 75% rename from doc/source/parameter/update/update.rst rename to doc/source/parameter/updater.rst index c417602f03..dfa22e8e7d 100644 --- a/doc/source/parameter/update/update.rst +++ b/doc/source/parameter/updater.rst @@ -1,7 +1,14 @@ -Update -========== +Updater +======= +Base +---- .. doxygenfile:: paddle/parameter/ParameterUpdaterBase.h + +Hook +---- .. doxygenfile:: paddle/parameter/ParameterUpdaterHook.h -.. doxygenfile:: paddle/parameter/ParameterUpdateFunctions.h +Functions +--------- +.. doxygenfile:: paddle/parameter/ParameterUpdateFunctions.h diff --git a/doc/source/pserver/client.rst b/doc/source/pserver/client.rst new file mode 100644 index 0000000000..e5bba0706a --- /dev/null +++ b/doc/source/pserver/client.rst @@ -0,0 +1,12 @@ +Client +====== + +BaseClient +---------- +.. doxygenclass:: paddle::BaseClient + :members: + +ParameterClient2 +---------------- +.. doxygenclass:: paddle::ParameterClient2 + :members: diff --git a/doc/source/pserver/client/client.rst b/doc/source/pserver/client/client.rst deleted file mode 100644 index fc7ed90d3d..0000000000 --- a/doc/source/pserver/client/client.rst +++ /dev/null @@ -1,14 +0,0 @@ -Client -========= - -.. doxygenclass:: paddle::BaseClient - :members: - :protected-members: - :private-members: - :undoc-members: - -.. doxygenclass:: paddle::ParameterClient2 - :members: - :protected-members: - :private-members: - :undoc-members: diff --git a/doc/source/pserver/client/index.rst b/doc/source/pserver/client/index.rst deleted file mode 100644 index dc924c9ca8..0000000000 --- a/doc/source/pserver/client/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Client Documents -==================== - -.. toctree:: - :maxdepth: 3 - - client.rst diff --git a/doc/source/pserver/index.rst b/doc/source/pserver/index.rst new file mode 100644 index 0000000000..1cb3af32f8 --- /dev/null +++ b/doc/source/pserver/index.rst @@ -0,0 +1,10 @@ +Pserver +======= + +.. toctree:: + :maxdepth: 2 + + client.rst + network.rst + server.rst + utils.rst diff --git a/doc/source/pserver/network.rst b/doc/source/pserver/network.rst new file mode 100644 index 0000000000..cf6418bc1a --- /dev/null +++ b/doc/source/pserver/network.rst @@ -0,0 +1,27 @@ +Network +======= + +Socket Server +------------- +.. doxygenclass:: paddle::SocketServer + :members: + +Socket Worker +------------- +.. doxygenclass:: paddle::SocketWorker + :members: + +Socket Client +------------- +.. doxygenclass:: paddle::SocketClient + :members: + +Socket Channel +-------------- +.. doxygenclass:: paddle::SocketChannel + :members: + +Message Reader +-------------- +.. doxygenclass:: paddle::MsgReader + :members: diff --git a/doc/source/pserver/network/index.rst b/doc/source/pserver/network/index.rst deleted file mode 100644 index 2fdf95e17d..0000000000 --- a/doc/source/pserver/network/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Network Documents -==================== - -.. toctree:: - :maxdepth: 3 - - network.rst diff --git a/doc/source/pserver/network/network.rst b/doc/source/pserver/network/network.rst deleted file mode 100644 index e000ff8dbb..0000000000 --- a/doc/source/pserver/network/network.rst +++ /dev/null @@ -1,42 +0,0 @@ -Network -========== - -Socket Server ----------------- -.. doxygenclass:: paddle::SocketServer - :members: - :protected-members: - :private-members: - :undoc-members: - -Socket Worker ----------------- -.. doxygenclass:: paddle::SocketWorker - :members: - :protected-members: - :private-members: - :undoc-members: - -Socket Client ----------------- -.. doxygenclass:: paddle::SocketClient - :members: - :protected-members: - :private-members: - :undoc-members: - -Socket Channel ---------------- -.. doxygenclass:: paddle::SocketChannel - :members: - :protected-members: - :private-members: - :undoc-members: - -Message Reader ---------------- -.. doxygenclass:: paddle::MsgReader - :members: - :protected-members: - :private-members: - :undoc-members: diff --git a/doc/source/pserver/server.rst b/doc/source/pserver/server.rst new file mode 100644 index 0000000000..35301acf8f --- /dev/null +++ b/doc/source/pserver/server.rst @@ -0,0 +1,12 @@ +Server +====== + +ProtoServer +----------- +.. doxygenclass:: paddle::ProtoServer + :members: + +ParameterServer2 +---------------- +.. doxygenclass:: paddle::ParameterServer2 + :members: diff --git a/doc/source/pserver/server/index.rst b/doc/source/pserver/server/index.rst deleted file mode 100644 index 09e3530bfe..0000000000 --- a/doc/source/pserver/server/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Server Documents -==================== - -.. toctree:: - :maxdepth: 3 - - server.rst diff --git a/doc/source/pserver/server/server.rst b/doc/source/pserver/server/server.rst deleted file mode 100644 index f3110fdd73..0000000000 --- a/doc/source/pserver/server/server.rst +++ /dev/null @@ -1,14 +0,0 @@ -Server -========== - -.. doxygenclass:: paddle::ProtoServer - :members: - :protected-members: - :private-members: - :undoc-members: - -.. doxygenclass:: paddle::ParameterServer2 - :members: - :protected-members: - :private-members: - :undoc-members: diff --git a/doc/source/trainer/trainer.rst b/doc/source/trainer.rst similarity index 94% rename from doc/source/trainer/trainer.rst rename to doc/source/trainer.rst index 12c24597e7..85f1feb4fc 100644 --- a/doc/source/trainer/trainer.rst +++ b/doc/source/trainer.rst @@ -14,7 +14,7 @@ RemoteParameterUpdater :members: ConcurrentRemoteParameterUpdater ---------------------------------- +-------------------------------- .. doxygenclass:: paddle::ConcurrentRemoteParameterUpdater :members: diff --git a/doc/source/utils/customStackTrace.rst b/doc/source/utils/customStackTrace.rst index a4e6f05a40..cdc8930739 100644 --- a/doc/source/utils/customStackTrace.rst +++ b/doc/source/utils/customStackTrace.rst @@ -1,9 +1,4 @@ CustomStackTrace ================ - - -class CustomStackTrace ----------------------- - .. doxygenclass:: paddle::CustomStackTrace :members: diff --git a/doc/source/utils/enum.rst b/doc/source/utils/enum.rst index 17166d35f7..e0da75afe1 100644 --- a/doc/source/utils/enum.rst +++ b/doc/source/utils/enum.rst @@ -1,9 +1,3 @@ -enumeration_wrapper +Enumeration wrapper =================== - - -namespace paddle::enumeration_wrapper -------------------------------------- - .. doxygennamespace:: paddle::enumeration_wrapper - diff --git a/doc/source/utils/index.rst b/doc/source/utils/index.rst new file mode 100644 index 0000000000..7ddc47d172 --- /dev/null +++ b/doc/source/utils/index.rst @@ -0,0 +1,11 @@ +Utils +===== + +.. toctree:: + :maxdepth: 2 + + lock.rst + queue.rst + thread.rst + customStackTrace.rst + enum.rst diff --git a/doc/source/utils/lock.rst b/doc/source/utils/lock.rst index 0b027e403f..1dd3f4c051 100644 --- a/doc/source/utils/lock.rst +++ b/doc/source/utils/lock.rst @@ -1,37 +1,32 @@ -Thread -====== +Lock +==== - -class Thread +class RWLock ------------ - -.. doxygenclass:: paddle::Thread +.. doxygenclass:: paddle::RWLock :members: - -class ThreadWorker ------------------- - -.. doxygenclass:: paddle::ThreadWorker +class ReadLockGuard +------------------- +.. doxygenclass:: paddle::ReadLockGuard :members: - -class SyncThreadPool --------------------- - -.. doxygenclass:: paddle::SyncThreadPool +class SpinLock +-------------- +.. doxygenclass:: paddle::SpinLock :members: - -class MultiThreadWorker ------------------------ +class Semaphore +--------------- +.. doxygenclass:: paddle::Semaphore + :members: -.. doxygenclass:: paddle::MultiThreadWorker +class ThreadBarrier +------------------- +.. doxygenclass:: paddle::ThreadBarrier :members: - -class AsyncThreadPool +class LockedCondition --------------------- - -.. doxygenclass:: paddle::AsyncThreadPool +.. doxygenclass:: paddle::LockedCondition :members: diff --git a/doc/source/utils/queue.rst b/doc/source/utils/queue.rst index 72a464ca67..5829028ebd 100644 --- a/doc/source/utils/queue.rst +++ b/doc/source/utils/queue.rst @@ -1,16 +1,12 @@ Queue ===== - class Queue ------------ - .. doxygenclass:: paddle::Queue :members: - class BlockingQueue ------------------- - .. doxygenclass:: paddle::BlockingQueue :members: diff --git a/doc/source/utils/thread.rst b/doc/source/utils/thread.rst index 2eb67dde6a..09f826fa1d 100644 --- a/doc/source/utils/thread.rst +++ b/doc/source/utils/thread.rst @@ -1,40 +1,27 @@ -Lock -==== +Thread +====== - -class RWLock +class Thread ------------ - -.. doxygenclass:: paddle::RWLock +.. doxygenclass:: paddle::Thread :members: -class ReadLockGuard -------------------- - -.. doxygenclass:: paddle::ReadLockGuard +class ThreadWorker +------------------ +.. doxygenclass:: paddle::ThreadWorker :members: -class SpinLock --------------- - -.. doxygenclass:: paddle::SpinLock +class SyncThreadPool +-------------------- +.. doxygenclass:: paddle::SyncThreadPool :members: - -class Semaphore ---------------- - -.. doxygenclass:: paddle::Semaphore + +class MultiThreadWorker +----------------------- +.. doxygenclass:: paddle::MultiThreadWorker :members: -class ThreadBarrier -------------------- - -.. doxygenclass:: paddle::ThreadBarrier - :members: - -class LockedCondition +class AsyncThreadPool --------------------- - -.. doxygenclass:: paddle::LockedCondition +.. doxygenclass:: paddle::AsyncThreadPool :members: - -- GitLab From 876fa1935d6950b9cfe380985906d5e7251c053d Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 18 Nov 2016 14:33:49 +0800 Subject: [PATCH 0060/1503] Fix the config_parse.py if user does not set padding in the old config. --- python/paddle/trainer/config_parser.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index dbe2f3b292..adcd14569e 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -815,9 +815,9 @@ class Pool(Cfg): size_y=None, img_width=None, start=None, - stride=None, + stride=1, stride_y=None, - padding=None, + padding=0, padding_y=None): self.add_keys(locals()) @@ -1111,15 +1111,15 @@ def parse_pool(pool, input_layer_name, pool_conf): config_assert(not pool.start, "start is deprecated in pooling.") - if pool.padding is not None: - pool_conf.padding = pool.padding - pool_conf.padding_y = default(pool.padding_y, pool_conf.padding) - pool_conf.output_x = cnn_output_size( - pool_conf.img_size, pool_conf.size_x, pool_conf.padding, - pool_conf.stride, False) - pool_conf.output_y = cnn_output_size( - pool_conf.img_size_y, pool_conf.size_y, pool_conf.padding_y, - pool_conf.stride_y, False) + pool_conf.padding = pool.padding + pool_conf.padding_y = default(pool.padding_y, pool_conf.padding) + + pool_conf.output_x = cnn_output_size( + pool_conf.img_size, pool_conf.size_x, pool_conf.padding, + pool_conf.stride, False) + pool_conf.output_y = cnn_output_size( + pool_conf.img_size_y, pool_conf.size_y, pool_conf.padding_y, + pool_conf.stride_y, False) def parse_spp(spp, input_layer_name, spp_conf): -- GitLab From 7f07e830e22879c7ebfd2d9670d367e68abe0c7c Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 18 Nov 2016 15:19:55 +0800 Subject: [PATCH 0061/1503] proper nouns begin with a capital letter, and remove spaces in class name --- doc/source/api.rst | 2 +- doc/source/cuda/cuda.rst | 35 ------------------------- doc/source/cuda/index.rst | 3 +-- doc/source/cuda/nn.rst | 2 ++ doc/source/cuda/utils.rst | 19 ++++++++++++++ doc/source/gserver/dataproviders.rst | 12 ++++----- doc/source/gserver/gradientmachines.rst | 16 +++++------ doc/source/gserver/index.rst | 2 +- doc/source/index.rst | 2 +- doc/source/math/vector.rst | 8 +++--- doc/source/pserver/index.rst | 2 +- doc/source/pserver/network.rst | 20 +++++++------- doc/source/utils/lock.rst | 24 ++++++++--------- doc/source/utils/queue.rst | 8 +++--- doc/source/utils/thread.rst | 20 +++++++------- 15 files changed, 80 insertions(+), 95 deletions(-) delete mode 100644 doc/source/cuda/cuda.rst diff --git a/doc/source/api.rst b/doc/source/api.rst index 22fb5fb644..30396c26b6 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1,4 +1,4 @@ -Api +API === .. doxygenfile:: paddle/api/PaddleAPI.h diff --git a/doc/source/cuda/cuda.rst b/doc/source/cuda/cuda.rst deleted file mode 100644 index 77b6e4a4d2..0000000000 --- a/doc/source/cuda/cuda.rst +++ /dev/null @@ -1,35 +0,0 @@ -Cuda -==== - -Dynamic Link Libs ------------------ - -hl_dso_loader.h -``````````````` -.. doxygenfile:: paddle/cuda/include/hl_dso_loader.h - -GPU Resources -------------- - -hl_cuda.ph -`````````` -.. doxygenfile:: paddle/cuda/include/hl_cuda.ph - -hl_cuda.h -````````` -.. doxygenfile:: paddle/cuda/include/hl_cuda.h - -CUDA Wrapper ------------- - -hl_cuda_cublas.h -```````````````` -.. doxygenfile:: paddle/cuda/include/hl_cuda_cublas.h - -hl_cuda_cudnn.h -``````````````` -.. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.h - -hl_cuda_cudnn.h -``````````````` -.. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.ph diff --git a/doc/source/cuda/index.rst b/doc/source/cuda/index.rst index d453f658a7..b0fed2e7f7 100644 --- a/doc/source/cuda/index.rst +++ b/doc/source/cuda/index.rst @@ -1,10 +1,9 @@ -Cuda +CUDA ==== .. toctree:: :maxdepth: 2 - cuda.rst matrix.rst nn.rst utils.rst diff --git a/doc/source/cuda/nn.rst b/doc/source/cuda/nn.rst index 5ebd863242..5577d01e72 100644 --- a/doc/source/cuda/nn.rst +++ b/doc/source/cuda/nn.rst @@ -10,10 +10,12 @@ Base .. doxygenfile:: paddle/cuda/include/hl_gpu_functions.cuh .. doxygenfile:: paddle/cuda/include/hl_activation_functions.h + CNN Related APIs ---------------- .. doxygenfile:: paddle/cuda/include/hl_cnn.h .. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.h +.. doxygenfile:: paddle/cuda/include/hl_cuda_cudnn.ph RNN Related APIs ---------------- diff --git a/doc/source/cuda/utils.rst b/doc/source/cuda/utils.rst index c5d4a45a44..850e8bd1c6 100644 --- a/doc/source/cuda/utils.rst +++ b/doc/source/cuda/utils.rst @@ -1,10 +1,29 @@ Utils ===== +Dynamic Link Libs +----------------- +.. doxygenfile:: paddle/cuda/include/hl_dso_loader.h + +GPU Resources +------------- + +hl_cuda.ph +`````````` +.. doxygenfile:: paddle/cuda/include/hl_cuda.ph + +hl_cuda.h +````````` +.. doxygenfile:: paddle/cuda/include/hl_cuda.h + HPPL Base --------- .. doxygenfile:: paddle/cuda/include/hl_base.h +CUBLAS Wrapper +-------------- +.. doxygenfile:: paddle/cuda/include/hl_cuda_cublas.h + Timer ----- .. doxygenfile:: paddle/cuda/include/hl_time.h diff --git a/doc/source/gserver/dataproviders.rst b/doc/source/gserver/dataproviders.rst index 73e45f1f33..c30d9d6a36 100644 --- a/doc/source/gserver/dataproviders.rst +++ b/doc/source/gserver/dataproviders.rst @@ -2,11 +2,11 @@ Data Providers ============== -Data Providers -============== +DataProviders +============= -Base DataProvider ------------------ +Base +---- .. doxygenclass:: paddle::DataProvider :members: @@ -73,8 +73,8 @@ IPyDataProvider .. doxygenclass:: paddle::PyDataProvider2 :members: -Proto Data Provider -=================== +ProtoDataProvider +================= ProtoDataProvider ---------------- diff --git a/doc/source/gserver/gradientmachines.rst b/doc/source/gserver/gradientmachines.rst index d67c8dbcbd..04c8e91d03 100644 --- a/doc/source/gserver/gradientmachines.rst +++ b/doc/source/gserver/gradientmachines.rst @@ -1,18 +1,18 @@ Gradient Machines ================= -Gradient Machine ----------------- +GradientMachine +--------------- .. doxygenclass:: paddle::GradientMachine :members: -Gradient Machine Mode ---------------------- +GradientMachineMode +------------------- .. doxygenclass:: paddle::IGradientMachineMode :members: -Multi Gradient Machine ----------------------- +MultiGradientMachine +-------------------- .. doxygenclass:: paddle::MultiGradientMachine :members: @@ -21,7 +21,7 @@ TrainerThread .. doxygenclass:: paddle::TrainerThread :members: -Recurrent Gradient Machine --------------------------- +RecurrentGradientMachine +------------------------ .. doxygenclass:: paddle::RecurrentGradientMachine :members: diff --git a/doc/source/gserver/index.rst b/doc/source/gserver/index.rst index b0b41e3ad1..223b00b9a9 100644 --- a/doc/source/gserver/index.rst +++ b/doc/source/gserver/index.rst @@ -1,4 +1,4 @@ -Gserver +GServer ======= .. toctree:: diff --git a/doc/source/index.rst b/doc/source/index.rst index 7aebcadd75..36323c888e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -7,7 +7,7 @@ Source Code Documents gserver/index.rst trainer.rst parameter/index.rst - pserver.rst + pserver/index.rst api.rst cuda/index.rst math/index.rst diff --git a/doc/source/math/vector.rst b/doc/source/math/vector.rst index 579ed7ba55..07f7062aba 100644 --- a/doc/source/math/vector.rst +++ b/doc/source/math/vector.rst @@ -21,13 +21,13 @@ GpuVector Template .. doxygenclass:: paddle::GpuVectorT :members: -Parallel CpuVector Template -``````````````````````````` +ParallelCpuVector Template +`````````````````````````` .. doxygenclass:: paddle::ParallelCpuVectorT :members: -Parallel GpuVector Template -``````````````````````````` +ParallelGpuVector Template +`````````````````````````` .. doxygenclass:: paddle::ParallelGpuVectorT :members: diff --git a/doc/source/pserver/index.rst b/doc/source/pserver/index.rst index 1cb3af32f8..0031e9476b 100644 --- a/doc/source/pserver/index.rst +++ b/doc/source/pserver/index.rst @@ -1,4 +1,4 @@ -Pserver +PServer ======= .. toctree:: diff --git a/doc/source/pserver/network.rst b/doc/source/pserver/network.rst index cf6418bc1a..7004c9d91f 100644 --- a/doc/source/pserver/network.rst +++ b/doc/source/pserver/network.rst @@ -1,27 +1,27 @@ Network ======= -Socket Server -------------- +SocketServer +------------ .. doxygenclass:: paddle::SocketServer :members: -Socket Worker -------------- +SocketWorker +------------ .. doxygenclass:: paddle::SocketWorker :members: -Socket Client -------------- +SocketClient +------------ .. doxygenclass:: paddle::SocketClient :members: -Socket Channel --------------- +SocketChannel +------------- .. doxygenclass:: paddle::SocketChannel :members: -Message Reader --------------- +MessageReader +------------- .. doxygenclass:: paddle::MsgReader :members: diff --git a/doc/source/utils/lock.rst b/doc/source/utils/lock.rst index 1dd3f4c051..f011acb943 100644 --- a/doc/source/utils/lock.rst +++ b/doc/source/utils/lock.rst @@ -1,32 +1,32 @@ Lock ==== -class RWLock ------------- +RWLock +------ .. doxygenclass:: paddle::RWLock :members: -class ReadLockGuard -------------------- +ReadLockGuard +------------- .. doxygenclass:: paddle::ReadLockGuard :members: -class SpinLock --------------- +SpinLock +-------- .. doxygenclass:: paddle::SpinLock :members: -class Semaphore ---------------- +Semaphore +--------- .. doxygenclass:: paddle::Semaphore :members: -class ThreadBarrier -------------------- +ThreadBarrier +------------- .. doxygenclass:: paddle::ThreadBarrier :members: -class LockedCondition ---------------------- +LockedCondition +--------------- .. doxygenclass:: paddle::LockedCondition :members: diff --git a/doc/source/utils/queue.rst b/doc/source/utils/queue.rst index 5829028ebd..98192648e2 100644 --- a/doc/source/utils/queue.rst +++ b/doc/source/utils/queue.rst @@ -1,12 +1,12 @@ Queue ===== -class Queue ------------- +Queue +----- .. doxygenclass:: paddle::Queue :members: -class BlockingQueue -------------------- +BlockingQueue +------------- .. doxygenclass:: paddle::BlockingQueue :members: diff --git a/doc/source/utils/thread.rst b/doc/source/utils/thread.rst index 09f826fa1d..23d379a989 100644 --- a/doc/source/utils/thread.rst +++ b/doc/source/utils/thread.rst @@ -1,27 +1,27 @@ Thread ====== -class Thread ------------- +Thread +------ .. doxygenclass:: paddle::Thread :members: -class ThreadWorker ------------------- +ThreadWorker +------------ .. doxygenclass:: paddle::ThreadWorker :members: -class SyncThreadPool --------------------- +SyncThreadPool +-------------- .. doxygenclass:: paddle::SyncThreadPool :members: -class MultiThreadWorker ------------------------ +MultiThreadWorker +----------------- .. doxygenclass:: paddle::MultiThreadWorker :members: -class AsyncThreadPool ---------------------- +AsyncThreadPool +--------------- .. doxygenclass:: paddle::AsyncThreadPool :members: -- GitLab From 9a000a2609c60ee00e87d4494559489ab398a0bf Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 18 Nov 2016 16:17:39 +0800 Subject: [PATCH 0062/1503] Encourage users to file Github issues instead of emails. --- README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 81ff8c7122..e8679fb55f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ developed by Baidu scientists and engineers for the purpose of applying deep learning to many products at Baidu. Our vision is to enable deep learning for everyone via PaddlePaddle. -Please refer to our [release announcement](https://github.com/baidu/Paddle/releases) to track the latest feature of PaddlePaddle. +Please refer to our [release announcement](https://github.com/baidu/Paddle/releases) to track the latest feature of PaddlePaddle. ## Features @@ -26,15 +26,15 @@ Please refer to our [release announcement](https://github.com/baidu/Paddle/relea connection. - **Efficiency** - + In order to unleash the power of heterogeneous computing resource, optimization occurs at different levels of PaddlePaddle, including computing, memory, architecture and communication. The following are some examples: - Optimized math operations through SSE/AVX intrinsics, BLAS libraries - (e.g. MKL, ATLAS, cuBLAS) or customized CPU/GPU kernels. - - Highly optimized recurrent networks which can handle **variable-length** + (e.g. MKL, ATLAS, cuBLAS) or customized CPU/GPU kernels. + - Highly optimized recurrent networks which can handle **variable-length** sequence without padding. - Optimized local and distributed training for models with high dimensional sparse data. @@ -57,41 +57,39 @@ Please refer to our [release announcement](https://github.com/baidu/Paddle/relea ## Installation Check out the [Install Guide](http://paddlepaddle.org/doc/build/) to install from -pre-built packages (**docker image**, **deb package**) or +pre-built packages (**docker image**, **deb package**) or directly build on **Linux** and **Mac OS X** from the source code. - + ## Documentation Both [English Docs](http://paddlepaddle.org/doc/) and [Chinese Docs](http://paddlepaddle.org/doc_cn/) are provided for our users and developers. - [Quick Start](http://paddlepaddle.org/doc/demo/quick_start/index_en)
    You can follow the quick start tutorial to learn how use PaddlePaddle step-by-step. - + - [Example and Demo](http://paddlepaddle.org/doc/demo/)
    We provide five demos, including: image classification, sentiment analysis, - sequence to sequence model, recommendation, semantic role labeling. - + sequence to sequence model, recommendation, semantic role labeling. + - [Distributed Training](http://paddlepaddle.org/doc/cluster)
    This system supports training deep learning models on multiple machines with data parallelism. - + - [Python API](http://paddlepaddle.org/doc/ui/)
    PaddlePaddle supports using either Python interface or C++ to build your system. We also use SWIG to wrap C++ source code to create a user friendly interface for Python. You can also use SWIG to create interface for your favorite programming language. - + - [How to Contribute](http://paddlepaddle.org/doc/build/contribute_to_paddle.html)
    We sincerely appreciate your interest and contributions. If you would like to - contribute, please read the contribution guide. + contribute, please read the contribution guide. - [Source Code Documents](http://paddlepaddle.org/doc/source/)
    ## Ask Questions -Please join the [**gitter chat**](https://gitter.im/PaddlePaddle/Deep_Learning) or send email to -**paddle-dev@baidu.com** to ask questions and talk about methods and models. -Framework development discussions and -bug reports are collected on [Issues](https://github.com/baidu/paddle/issues). + +You are welcome to submit questions and bug reports as [Github Issues](https://github.com/baidu/paddle/issues). ## Copyright and License PaddlePaddle is provided under the [Apache-2.0 license](LICENSE). -- GitLab From 82273f60ca8b4f803792577e6cb2b2a5dc72fb60 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 18 Nov 2016 17:11:38 +0800 Subject: [PATCH 0063/1503] fix how_to_write_docs --- doc_cn/howto/how_to_write_docs/index.rst | 29 ++++++++---------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/doc_cn/howto/how_to_write_docs/index.rst b/doc_cn/howto/how_to_write_docs/index.rst index 869ef747f9..a1f983b340 100644 --- a/doc_cn/howto/how_to_write_docs/index.rst +++ b/doc_cn/howto/how_to_write_docs/index.rst @@ -2,32 +2,19 @@ 如何贡献/修改PaddlePaddle的文档 ############################### -PaddlePaddle的文档使用 `cmake`_ 驱动 `sphinx`_ 生成。公有两个文档,:code:`doc` 和 :code:`doc_cn` 。这两者会在 `cmake`_ 中进行编译,生成后的文档会存储在服务器的 :code:`doc` 和 :code:`doc_cn` 两个目录下。 +PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``doc`` 和 ``doc_cn`` 两个子目录下。 -下面分几个部分介绍一下PaddlePaddle文档的贡献方法。 - -如何书写PaddlePaddle的文档 -========================== - -TBD 如何构建PaddlePaddle的文档 ========================== -构建PaddlePaddle文档,需要使用构建Paddle的全部环境。准备这个环境相对来说比较复杂,所以本文档提供两种方式构建PaddlePaddle的文档,即 - -* 使用Docker构建PaddlePaddle的文档 -* 直接构建PaddlePaddle的文档。 - -并且,我们推荐使用Docker来构建PaddlePaddle的文档。 +PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。构建PaddlePaddle文档需要准备的环境相对较复杂,所以我们推荐使用基于Docker来构建PaddlePaddle的文档。 使用Docker构建PaddlePaddle的文档 -------------------------------- -使用Docker构建PaddlePaddle的文档,首先要求在系统里安装好Docker工具包。安装Docker请参考 `Docker的官网 `_ 。 - -安装好Docker之后可以使用源码目录下的脚本构建文档,即 +使用Docker构建PaddlePaddle的文档,需要在系统里先安装好Docker工具包。Docker安装请参考 `Docker的官网 `_ 。安装好Docker之后可以使用源码目录下的脚本构建文档,即 .. code-block:: bash @@ -35,10 +22,10 @@ TBD cd paddle/scripts/tools/build_docs bash build_docs.sh -执行完这个脚本后,该目录下会生成两个目录,分别是\: +编译完成后,该目录下会生成如下两个子目录\: -* doc 目录,英文文档地址 -* doc_cn 目录,中文文档地址 +* doc 英文文档目录 +* doc_cn 中文文档目录 打开浏览器访问对应目录下的index.html即可访问本地文档。 @@ -52,6 +39,10 @@ TBD TBD +如何书写PaddlePaddle的文档 +========================== + +TBD 如何更新www.paddlepaddle.org文档 ================================ -- GitLab From b3dd2d10b74b495aa525ec1052e5ed1b761aa935 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 18 Nov 2016 17:07:06 +0800 Subject: [PATCH 0064/1503] Add glossary for Paddle --- doc_cn/algorithm/rnn/hrnn_demo.rst | 7 + doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 183 ++++++++++++++++++ doc_cn/concepts/glossary.rst | 59 ++++++ doc_cn/concepts/glossary_rnn.dot | 42 ++++ doc_cn/concepts/glossary_rnn_with_memory.dot | 48 +++++ doc_cn/index.rst | 3 +- 6 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 doc_cn/algorithm/rnn/hrnn_demo.rst create mode 100644 doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst create mode 100644 doc_cn/concepts/glossary.rst create mode 100644 doc_cn/concepts/glossary_rnn.dot create mode 100644 doc_cn/concepts/glossary_rnn_with_memory.dot diff --git a/doc_cn/algorithm/rnn/hrnn_demo.rst b/doc_cn/algorithm/rnn/hrnn_demo.rst new file mode 100644 index 0000000000..cf38e416c0 --- /dev/null +++ b/doc_cn/algorithm/rnn/hrnn_demo.rst @@ -0,0 +1,7 @@ +.. algo_hrnn_demo: + +################# +双层RNN的使用示例 +################# + +TBD \ No newline at end of file diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst new file mode 100644 index 0000000000..cf18108019 --- /dev/null +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -0,0 +1,183 @@ +.. _algo_hrnn_rnn_api_compare: + +##################### +单双层RNN API对比介绍 +##################### + +这篇教程主要介绍了 :ref:`glossary_双层RNN` 的API接口。本文中的以 :ref:`glossary_paddle` 的 :ref:`glossary_双层RNN` 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用 :ref:`glossary_双层RNN` 。本文中所有的例子,都只是介绍 :ref:`glossary_双层RNN` 的API接口,并不是使用 :ref:`glossary_双层RNN` 解决实际的问题。如果想要了解 :ref:`glossary_双层RNN` 在具体问题中的使用,请参考 :ref:`algo_hrnn_demo` 。文章中示例所使用的单元测试文件是 `test_RecurrentGradientMachine.cpp `_ 。 + +示例1:双层RNN,子序列间无Memory +================================ + + + +配置:单层RNN(:code:`sequence_layer_group`)和双层RNN(:code:`sequence_nest_layer_group`),语义完全相同。 + +读取双层序列的方法 +------------------ + +首先,我们看一下单双层序列的不同数据组织形式(您也可以采用别的组织形式)\: + +- 单层序列的数据( :code:`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 + +.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg + :language: text + + +- 双层序列的数据( :code:`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 + +.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg.nest + :language: text + +其次,我们看一下单双层序列的不同dataprovider(见 :code:`sequenceGen.py` ): + +- 单层序列的dataprovider如下: + + - word_slot是integer_value_sequence类型,代表单层序列。 + - label是integer_value类型,代表一个向量。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py + :language: python + :lines: 21-39 + +- 双层序列的dataprovider如下: + + - word_slot是integer_value_sub_sequence类型,代表双层序列。 + - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 + - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py + :language: python + :lines: 42-71 + +模型中的配置 +------------ + +首先,我们看一下单层序列的配置(见 :code:`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_layer_group.conf + :language: python + :lines: 38-63 + + +其次,我们看一下语义相同的双层序列配置(见 :code:`sequence_nest_layer_group.conf` ),并对其详细分析: + +- batchsize=2表示一次过2句双层序列。但从上面的数据格式可知,2句双层序列和5句单层序列的数据完全一样。 +- data_layer和embedding_layer不关心数据是否是序列格式,因此两个配置在这两层上的输出是一样的。 +- lstmemory\: + + - 单层序列过了一个mixed_layer和lstmemory_group。 + - 双层序列在同样的mixed_layer和lstmemory_group外,直接加了一层group。由于这个外层group里面没有memory,表示subseq间不存在联系,即起到的作用仅仅是把双层seq拆成单层,因此双层序列过完lstmemory的输出和单层的一样。 + +- last_seq\: + + - 单层序列直接取了最后一个元素 + - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 + - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_layer_group.conf + :language: python + :lines: 38-84 + +示例2:双进双出,subseq间有memory +================================= + +配置:单层RNN( :code:`sequence_rnn.conf` ),双层RNN( :code:`sequence_nest_rnn.conf` 和 :code:`sequence_nest_rnn_readonly_memory.conf` ),语义完全相同。 + +读取双层序列的方法 +------------------ + +我们看一下单双层序列的不同数据组织形式和dataprovider(见 :code:`rnn_data_provider.py`) + +.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py + :language: python + :lines: 20-32 + +- 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 +- 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 +- 单双层序列的label都分别是0和1 + +模型中的配置 +------------ + +我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 + +- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn.conf + :language: python + :lines: 36-48 + +- 双层序列,外层memory是一个元素: + + - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 + - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf + :language: python + :lines: 39-66 + +- 双层序列,外层memory是单层序列: + + - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 + - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 + +示例3:双进双出,输入不等长 +=========================== + +.. role:: red + +.. raw:: html + + + +**输入不等长** 是指recurrent_group的多个输入在各时刻的长度可以不相等, 但需要指定一个和输出长度一致的input,用 :red:`targetInlink` 表示。参考配置:单层RNN(:code:`sequence_rnn_multi_unequalength_inputs.conf`),双层RNN(:code:`sequence_nest_rnn_multi_unequalength_inputs.conf`) + +读取双层序列的方法 +------------------ + +我们看一下单双层序列的数据组织形式和dataprovider(见 :code:`rnn_data_provider.py` ) + +.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py + :language: python + :lines: 69-97 + +data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 + +- 单层序列:两个样本分别为[[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]] 和 [[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]] +- 双层序列:两个样本分别为 + + - **样本1**\:[[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]]]。fea1和fea2都分别有2个子句,fea1=[[1, 2], [4, 5, 2]], fea2=[[5, 4, 1], [3, 1]] + - **样本2**\:[[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]]。fea1和fea2都分别有3个子句, fea1=[[0, 2], [2, 5], [0, 1, 2]], fea2=[[1, 5], [4], [2, 3, 6, 1]]。
    + - **注意**\:每个样本中,各特征的子句数目需要相等。这里说的“双进双出,输入不等长”是指fea1在i时刻的输入的长度可以不等于fea2在i时刻的输入的长度。如对于第1个样本,时刻i=2, fea1[2]=[4, 5, 2],fea2[2]=[3, 1],3≠2。 + +- 单双层序列中,两个样本的label都分别是0和1 + +模型中的配置 +------------ + +单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`v.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 + +- 单层序列\: + + - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 + - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf + :language: python + :lines: 41-58 + +- 双层序列\: + + - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 + - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 + - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 + +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf + :language: python + :lines: 41-89 + +示例4:beam_search的生成 +======================== + +TBD diff --git a/doc_cn/concepts/glossary.rst b/doc_cn/concepts/glossary.rst new file mode 100644 index 0000000000..a94aa73675 --- /dev/null +++ b/doc_cn/concepts/glossary.rst @@ -0,0 +1,59 @@ +.. _glossary: + +######################## +Paddle文档中使用的词汇表 +######################## + +.. _glossary_paddle: + +PaddlePaddle +------------ + +TBD + + +.. _glossary_memory: + +Memory +------ + +Memory是 :ref:`glossary_paddle` 实现 :ref:`glossary_RNN` 时候使用的一个概念。 :ref:`glossary_RNN` 即时间递归神经网络,通常要求时间步之间具有一些依赖性,即当前时间步下的神经网络依赖前一个时间步神经网络中某一个神经元输出。如下图所示。 + +.. graphviz:: glossary_rnn.dot + +上图中虚线的连接,即是跨越时间步的网络连接。:ref:`glossary_paddle` 在实现 :ref:`glossary_RNN` 的时候,将这种跨越时间步的连接用一个特殊的神经网络单元实现。这个神经网络单元就叫Memory。Memory可以缓存上一个时刻某一个神经元的输出,然后在下一个时间步输入给另一个神经元。使用Memory的 :ref:`glossary_RNN` 实现便如下图所示。 + +.. graphviz:: glossary_rnn_with_memory.dot + +使用这种方式,:ref:`glossary_paddle` 可以比较简单的判断哪些输出是应该跨越时间步的,哪些不是。 + +.. _glossary_Sequence: + +时间序列 +-------- + +时间序列(time series)是指一系列的特征数据。这些特征数据之间的顺序是有意义的。即特征的数组,而不是特征的集合。而这每一个数组元素,或者每一个系列里的特征数据,即为一个时间步(time step)。值得注意的是,时间序列、时间步的概念,并不真正的和『时间』有关。只要一系列特征数据中的『顺序』是有意义的,即为时间序列的输入。 + +举例说明,例如文本分类中,我们通常将一句话理解成一个时间序列。比如一句话中的每一个单词,会变成词表中的位置。而这一句话就可以表示成这些位置的数组。例如 :code:`[9, 2, 3, 5, 3]` 。 + +关于时间序列(time series)的更详细准确的定义,可以参考 `维基百科页面 Time series `_ 或者 `维基百科中文页面 时间序列 `_ 。 + +另外,Paddle中经常会将时间序列成为 :code:`Sequence` 。他们在Paddle的文档和API中是一个概念。 + +.. _glossary_RNN: + +RNN +--- + +RNN 在 :ref:`glossary_paddle` 的文档中,一般表示 :code:`Recurrent neural network`,即时间递归神经网络。详细介绍可以参考 `维基百科页面 Recurrent neural network `_ 或者 `中文维基百科页面 `_ 中关于时间递归神经网络的介绍。 + +RNN 一般在 :ref:`glossary_paddle` 中,指对于一个 :ref:`glossary_Sequence` 输入数据,每一个时间步之间的神经网络具有一定的相关性。例如,某一个神经元的一个输入为上一个时间步网络中某一个神经元的输出。或者,从每一个时间步来看,神经网络的网络结构中具有有向环结构。 + +.. _glossary_双层RNN: + +双层RNN +------- + +双层RNN顾名思义,即 :ref:`glossary_RNN` 之间有一次嵌套关系。输入数据整体上是一个时间序列,而对于每一个内层特征数据而言,也是一个时间序列。即二维数组,或者数组的数组这个概念。 而双层RNN是可以处理这种输入数据的网络结构。 + +例如,对于段落的文本分类,即将一段话进行分类。我们将一段话看成句子的数组,每个句子又是单词的数组。这便是一种双层RNN的输入数据。而将这个段落的每一句话用lstm编码成一个向量,再对每一句话的编码向量用lstm编码成一个段落的向量。再对这个段落向量进行分类,即为这个双层RNN的网络结构。 diff --git a/doc_cn/concepts/glossary_rnn.dot b/doc_cn/concepts/glossary_rnn.dot new file mode 100644 index 0000000000..2cd0fb1820 --- /dev/null +++ b/doc_cn/concepts/glossary_rnn.dot @@ -0,0 +1,42 @@ +digraph G{ + subgraph cluster_timestep0 { + label="recurrent timestep i-1" + bgcolor=lightgray + node [style=filled,color=white] + fc0_0 [label="fc 0"] + fc0_1 [label="fc 1"] + fc0_2 [label="fc 2"] + + fc0_0 -> fc0_1 + fc0_1 -> fc0_2 + } + + subgraph cluster_timestep1 { + label="recurrent timestep i" + node [style=filled]; + fc1_0 [label="fc 0"] + fc1_1 [label="fc 1"] + fc1_2 [label="fc 2"] + color=blue + + fc1_0 -> fc1_1 + fc1_1 -> fc1_2 + } + + subgraph cluster_timestep2 { + label="recurrent timestep i+1" + bgcolor=lightgray + node [style=filled,color=white] + fc2_0 [label="fc 0"] + fc2_1 [label="fc 1"] + fc2_2 [label="fc 2"] + + fc2_0 -> fc2_1 + fc2_1 -> fc2_2 + } + + + fc0_1 -> fc1_1 [style="dotted" constraint=false] + fc1_1 -> fc2_1 [style="dotted" constraint=false] + +} \ No newline at end of file diff --git a/doc_cn/concepts/glossary_rnn_with_memory.dot b/doc_cn/concepts/glossary_rnn_with_memory.dot new file mode 100644 index 0000000000..0f101ec2d8 --- /dev/null +++ b/doc_cn/concepts/glossary_rnn_with_memory.dot @@ -0,0 +1,48 @@ +digraph G{ + subgraph cluster_timestep0 { + label="recurrent timestep i-1" + bgcolor=lightgray + node [style=filled,color=white] + fc0_0 [label="fc 0"] + fc0_1 [label="fc 1"] + fc0_2 [label="fc 2"] + m0 [label="memory"] + fc0_0 -> fc0_1 + fc0_1 -> fc0_2 + fc0_1 -> m0 + m0 -> fc0_1 + } + + subgraph cluster_timestep1 { + label="recurrent timestep i" + node [style=filled]; + fc1_0 [label="fc 0"] + fc1_1 [label="fc 1"] + fc1_2 [label="fc 2"] + m1 [label="memory"] + color=blue + fc1_0 -> fc1_1 + fc1_1 -> fc1_2 + fc1_1 -> m1 + m1 -> fc1_1 + } + + subgraph cluster_timestep2 { + label="recurrent timestep i+1" + bgcolor=lightgray + node [style=filled,color=white] + fc2_0 [label="fc 0"] + fc2_1 [label="fc 1"] + fc2_2 [label="fc 2"] + m2 [label="memory"] + fc2_0 -> fc2_1 + fc2_1 -> fc2_2 + fc2_1 -> m2 + m2 -> fc2_1 + } + + + m0 -> m1 [style="dotted" constraint=false] + m1 -> m2 [style="dotted" constraint=false] + +} \ No newline at end of file diff --git a/doc_cn/index.rst b/doc_cn/index.rst index f1398206fd..fef39aa527 100644 --- a/doc_cn/index.rst +++ b/doc_cn/index.rst @@ -11,6 +11,7 @@ PaddlePaddle文档 * `使用示例 `_ * `模型配置 <../doc/ui/api/trainer_config_helpers/index.html>`_ * `集群训练 `_ +* :ref:`glossary` 开发指南 -------- @@ -22,7 +23,7 @@ PaddlePaddle文档 * `Recurrent Group教程 `_ * `单层RNN示例 <../doc/algorithm/rnn/rnn.html>`_ -* `双层RNN示例 `_ +* :ref:`algo_hrnn_rnn_api_compare` * `支持双层序列作为输入的Layer `_ 常见问题 -- GitLab From ac7aa3a172c237d897780a1a9f8c372850a9b135 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 18 Nov 2016 18:19:16 +0800 Subject: [PATCH 0065/1503] refine compile_options.rst --- .../cmake/cblas_settings.csv | 9 +- .../cmake/compile_options.csv | 23 ++-- .../cmake/compile_options.rst | 115 ++++++++---------- 3 files changed, 69 insertions(+), 78 deletions(-) diff --git a/doc_cn/build_and_install/cmake/cblas_settings.csv b/doc_cn/build_and_install/cmake/cblas_settings.csv index d804c0a662..a6356baf16 100644 --- a/doc_cn/build_and_install/cmake/cblas_settings.csv +++ b/doc_cn/build_and_install/cmake/cblas_settings.csv @@ -1,4 +1,5 @@ -MKL_ROOT,mkl的路径,在${MKL_ROOT}/include下需要包含mkl.h,在${MKL_ROOT}/lib目录下需要包含 mkl_core,mkl_sequential和mkl_intel_lp64三个库 -ATLAS_ROOT,ATLAS库的路径,在${ATLAS_ROOT}/include下需要包含cblas.h,而在${ATLAS_ROOT}/lib下需要包含cblas和atlas两个库 -OPENBLAS_ROOT,在${OPENBLAS_ROOT}/include下需要包含cblas.h,而在${OPENBLAS_ROOT}/lib下需要包含openblas库 -REFERENCE_CBLAS_ROOT,在${REFERENCE_CBLAS_ROOT}/include下需要包含cblas.h,在${REFERENCE_CBLAS_ROOT}/lib下需要包含cblas库 \ No newline at end of file +编译选项,描述,注意 +MKL_ROOT,MKL的路径,${MKL_ROOT}/include下需要包含mkl.h,${MKL_ROOT}/lib目录下需要包含mkl_core,mkl_sequential和mkl_intel_lp64三个库。 +ATLAS_ROOT,ATLAS的路径,${ATLAS_ROOT}/include下需要包含cblas.h,${ATLAS_ROOT}/lib下需要包含cblas和atlas两个库。 +OPENBLAS_ROOT,OpenBLAS的路径,${OPENBLAS_ROOT}/include下需要包含cblas.h,${OPENBLAS_ROOT}/lib下需要包含openblas库。 +REFERENCE_CBLAS_ROOT,REFERENCE BLAS的路径,${REFERENCE_CBLAS_ROOT}/include下需要包含cblas.h,${REFERENCE_CBLAS_ROOT}/lib下需要包含cblas库。 \ No newline at end of file diff --git a/doc_cn/build_and_install/cmake/compile_options.csv b/doc_cn/build_and_install/cmake/compile_options.csv index 0b8015aaee..36a78e8227 100644 --- a/doc_cn/build_and_install/cmake/compile_options.csv +++ b/doc_cn/build_and_install/cmake/compile_options.csv @@ -1,15 +1,14 @@ 选项,说明,默认值 -WITH_GPU,是否编译GPU支持。,是否寻找到cuda工具链 +WITH_GPU,是否支持GPU。,取决于是否寻找到CUDA工具链 WITH_DOUBLE,是否使用双精度浮点数。,否 -WITH_DSO,是否使用运行时动态加载cuda动态库,而非静态加载cuda动态库。,是 -WITH_AVX,是否编译含有AVX指令集的PaddlePaddle二进制,是 -WITH_PYTHON,是否内嵌python解释器。可以方便嵌入式工作。,是 +WITH_DSO,是否运行时动态加载CUDA动态库,而非静态加载。,是 +WITH_AVX,是否编译含有AVX指令集的PaddlePaddle二进制文件,是 +WITH_PYTHON,是否内嵌PYTHON解释器。该选项方便今后PaddlePaddle移植到没有PYTHON的嵌入式设备上。,是 WITH_STYLE_CHECK,是否编译时进行代码风格检查,是 -WITH_RDMA,是否开启RDMA支持,否 -WITH_GLOG,是否使用GLOG,如果不使用则会使用一个简化版的日志实现。可以方便嵌入式工作。,取决于是否寻找到GLOG -WITH_GFLAGS,是否使用GFLAGS,如果不使用则会使用一个简化版的命令行参数解析。可以方便嵌入式工作。,取决于是否寻找到GFLAGS -WITH_TIMER,是否开启计时功能开启计时功能会导致运行略慢,打印的日志变多。但是方便调试和benchmark,否 -WITH_TESTING,是否开启单元测试,取决于是否寻找到gtest -WITH_DOC,是否编译英文文档,否 -WITH_DOC_CN,是否编译中文文档,否 -WITH_SWIG_PY,是否编译python的swig接口,python的swig接口可以方便进行预测和定制化训练,取决于是否找到swig +WITH_RDMA,是否开启RDMA,否 +WITH_GLOG,是否开启GLOG。如果不开启,则会使用一个简化版的日志。该选项方便今后PaddlePaddle移植到没有GLOG的嵌入式设备上。,取决于是否寻找到GLOG +WITH_GFLAGS,是否使用GFLAGS。如果不开启,则会使用一个简化版的命令行参数解析器。该选项方便今后PaddlePaddle移植到没有GFLAGS的嵌入式设备上。,取决于是否寻找到GFLAGS +WITH_TIMER,是否开启计时功能。如果开启会导致运行略慢,打印的日志变多,但是方便调试和测Benchmark,否 +WITH_TESTING,是否开启单元测试,取决于是否寻找到GTEST +WITH_DOC,是否编译中英文文档,否 +WITH_SWIG_PY,是否编译PYTHON的SWIG接口,该接口可用于预测和定制化训练,取决于是否寻找到SWIG \ No newline at end of file diff --git a/doc_cn/build_and_install/cmake/compile_options.rst b/doc_cn/build_and_install/cmake/compile_options.rst index bb5b18a073..abd7c56288 100644 --- a/doc_cn/build_and_install/cmake/compile_options.rst +++ b/doc_cn/build_and_install/cmake/compile_options.rst @@ -1,62 +1,53 @@ -设置PaddlePaddle的编译选项 -========================== - -PaddlePaddle的编译选项可以在调用cmake的时候设置。cmake是一个跨平台的编译脚本,调用 -cmake可以将cmake项目文件,生成各个平台的makefile。详细的cmake使用方法可以参考 -`cmake的官方文档 `_ 。 - -PaddlePaddle的编译选项是可以控制PaddlePaddle生成CPU/GPU版本二进制,链接何种blas等等。所有的 -编译选项列表如下 - -PaddlePaddle的编译选项 ----------------------- - -bool型的编译选项 -++++++++++++++++ -设置下列编译选项时,可以在cmake的命令行设置。使用 -D命令即可。例如 -:code:`cmake -D WITH_GPU=OFF` - -.. csv-table:: PaddlePaddle的bool型编译选项 - :widths: 1, 7, 2 - :file: compile_options.csv - -blas相关的编译选项 -++++++++++++++++++ - -PaddlePaddle可以使用 `MKL `_ , -`Atlas `_ , -`OpenBlas `_ 和 -`refference Blas `_ ,任意一种cblas实现。 -通过编译时指定路径来实现引用各种blas。 - -cmake编译时会首先在系统路径(/usr/lib\:/usr/local/lib)中寻找这些blas的实现。同时 -也会读取相关路径变量来进行搜索。路径变量为\: - - -.. csv-table:: PaddlePaddle的cblas编译选项 - :widths: 1, 9 - :header: "编译选项", "描述" - :file: cblas_settings.csv - -这些变量均可以使用 -D命令指定。例如 :code:`cmake -D MKL_ROOT=/opt/mkl/`。这些变 -量也可以通过调用cmake命令前通过环境变量指定。例如 - -.. code-block:: bash - - export MKL_ROOT=/opt/mkl - cmake - -需要注意的是,这些变量只在第一次cmake的时候有效。如果在第一次cmake之后想要重新设 -置这些变量,推荐清理( :code:`rm -rf` )掉编译目录后,再指定。 - -cuda/cudnn相关的编译选项 -++++++++++++++++++++++++ - -PaddlePaddle可以使用 cudnn v2之后的任何一个cudnn版本来编译运行。但需要注意的是编译和 -运行使用的cudnn尽量是同一个版本。推荐使用最新版本的cudnn v5.1。 - -在cmake配置时可以使用 :code:`CUDNN_ROOT` 来配置CUDNN的安装路径。使用的命令也是 --D,例如 :code:`cmake -D CUDNN_ROOT=/opt/cudnnv5` 。 - -需要注意的是,这些变量只在第一次cmake的时候有效。如果在第一次cmake之后想要重新设 -置这些变量,推荐清理( :code:`rm -rf` )掉编译目录后,再指定。 +PaddlePaddle的编译选项 +===================== + +PaddlePaddle的编译选项,包括生成CPU/GPU二进制文件、链接何种BLAS库等。用户可在调用cmake的时候设置它们,详细的cmake使用方法可以参考 `官方文档 `_ 。 + +Bool型的编译选项 +-------------------- +用户可在cmake的命令行中,通过使用 ``-D`` 命令设置该类编译选项,例如 + +.. code-block:: bash + + cmake .. -DWITH_GPU=OFF + +.. csv-table:: Bool型的编译选项 + :widths: 1, 7, 2 + :file: compile_options.csv + +路径相关的编译选项 +-------------------- +BLAS路径相关 ++++++++++++++ + +PaddlePaddle支持以下任意一种BLAS库:`MKL `_ ,`ATLAS `_ ,`OpenBlAS `_ 和 `REFERENCE BLAS `_ 。 + +.. csv-table:: BLAS路径相关的编译选项 + :widths: 1, 2, 7 + :file: cblas_settings.csv + +CUDA/Cudnn路径相关 +++++++++++++++++++++ + +PaddlePaddle可以使用cudnn v2之后的任何一个版本来编译运行,但尽量请保持编译和运行使用的cudnn是同一个版本。 我们推荐使用最新版本的cudnn v5.1。 + +编译选项的设置 ++++++++++++++ + +cmake编译时,首先在系统路径(/usr/lib\:/usr/local/lib)中搜索上述库,其次也会根据相关路径的编译选项来进行搜索。 有两种方式可以设置: + +1. 使用 ``-D`` 命令指定,例如 + +.. code-block:: bash + + cmake .. -DMKL_ROOT=/opt/mkl/ -DCUDNN_ROOT=/opt/cudnnv5 + +2. 在cmake命令前,通过环境变量指定,例如 + +.. code-block:: bash + + export MKL_ROOT=/opt/mkl + export CUDNN_ROOT=/opt/cudnnv5 + cmake + +注意:该类编译选项的设置,只在第一次cmake的时候有效。如果之后想要重新设置,推荐清理整个编译目录(``rm -rf``)后,再指定。 -- GitLab From 4ca9c3955ebaf239053ab0e1da011518e92fabf8 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 18 Nov 2016 18:31:09 +0800 Subject: [PATCH 0066/1503] minor changes --- proto/ModelConfig.proto.m4 | 12 ++++++------ python/paddle/trainer/config_parser.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/proto/ModelConfig.proto.m4 b/proto/ModelConfig.proto.m4 index aea77248cb..c835cfd522 100644 --- a/proto/ModelConfig.proto.m4 +++ b/proto/ModelConfig.proto.m4 @@ -92,7 +92,7 @@ message PoolConfig { optional uint32 start = 4; // Defines the stride size between successive pooling squares. - required uint32 stride = 5; + required uint32 stride = 5 [default = 1]; // The size of output feature map. required uint32 output_x = 6; @@ -105,19 +105,19 @@ message PoolConfig { optional uint32 padding = 8 [default = 0]; // if not set, use size_x - optional uint32 size_y = 9 [default = 0]; + optional uint32 size_y = 9; // if not set, use stride - optional uint32 stride_y = 10 [default = 0]; + optional uint32 stride_y = 10; // if not set, use output_x - optional uint32 output_y = 11 [default = 0]; + optional uint32 output_y = 11; // if not set, use img_size - optional uint32 img_size_y = 12 [default = 0]; + optional uint32 img_size_y = 12; // if not set, use padding - optional uint32 padding_y = 13 [default = 0]; + optional uint32 padding_y = 13; } message SppConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index adcd14569e..427d690786 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -815,9 +815,9 @@ class Pool(Cfg): size_y=None, img_width=None, start=None, - stride=1, + stride=None, # 1 by defalut in protobuf stride_y=None, - padding=0, + padding=None, # 0 by defalut in protobuf padding_y=None): self.add_keys(locals()) @@ -1111,9 +1111,9 @@ def parse_pool(pool, input_layer_name, pool_conf): config_assert(not pool.start, "start is deprecated in pooling.") - pool_conf.padding = pool.padding + if pool.padding is not None + pool_conf.padding = pool.padding pool_conf.padding_y = default(pool.padding_y, pool_conf.padding) - pool_conf.output_x = cnn_output_size( pool_conf.img_size, pool_conf.size_x, pool_conf.padding, pool_conf.stride, False) -- GitLab From d8cca855e1f1b69fcd9ca53ae8bd9bc6260dcfe1 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 18 Nov 2016 18:52:42 +0800 Subject: [PATCH 0067/1503] Refine use_concepts.rst --- doc_cn/concepts/use_concepts.rst | 97 ++++++++++++++------------------ 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/doc_cn/concepts/use_concepts.rst b/doc_cn/concepts/use_concepts.rst index 67e98edabc..d3da9cc16b 100644 --- a/doc_cn/concepts/use_concepts.rst +++ b/doc_cn/concepts/use_concepts.rst @@ -2,16 +2,16 @@ PaddlePaddle 基本使用概念 ######################### -PaddlePaddle是一个神经网络学习框架。其单机进程为 :code:`paddle train`。 单机的所有设备使用,均在单机进程内调度完成。 而多机辅助进程 :code:`paddle pserver` 负责联合多个单机进程进行通信,进而充分利用集群的计算资源。 PaddlePaddle同时以 :code:`swig api` 的形式,提供训练结果模型预测的方法和自定义训练流程。 +PaddlePaddle是一个深度学习框架,同时支持单机和多机模式的系统。命令 ``paddle train`` 可启动单机模式的进程,我们称之为 ``trainer`` 进程。单机所有设备使用均在单机进程内调度完成。多机模式除了需要启动trainer进程外,还需要通过命令 ``paddle pserver`` 启动多机参数服务器进程, 我们称之为   ``pserver`` 进程。该进程负责多个单机进程间的通信,进而充分利用集群的计算资源。 PaddlePaddle同时以 ``swig api`` 的形式,提供训练结果模型预测的方法和自定义训练流程。 -下面我们会分别介绍主要进程 :code:`paddle train` 中的一些概念。这些概念会对如何使用PaddlePaddle有一定的帮助。 了解这些概念的前提是,读者已经了解 `基本的神经网络/机器学习原理和概念 `_ 。同时,如果想要了解PaddlePaddle实现中的一些概念,请参考 `PaddlePaddle 编程中的基本概念 `_ 。 +下面我们会介绍trainer进程中的一些概念,这些概念会对如何使用PaddlePaddle有一定的帮助。 了解这些概念的前提是,读者已经了解 `基本的神经网络/机器学习原理和概念 `_ 。同时,如果想要了解PaddlePaddle实现中的一些概念,请参考 `PaddlePaddle 编程中的基本概念 `_ 。 .. contents:: -PaddlePaddle 的进程模型 -======================= +系统模块 +======== -PaddlePaddle进程内嵌了一个 :code:`python` 解释器。 这个 :code:`python` 解释器负责解析用户定义的神经网络配置,和解析用户数据,并将用户数据传入给 PaddlePaddle。 +``trainer`` 进程内嵌了一个 ``python`` 解释器, 这个 ``python`` 解释器负责解析用户定义的神经网络配置;解析输入数据流,并将数据传入给 ``trainer`` 系统。 .. graphviz:: @@ -30,95 +30,84 @@ PaddlePaddle进程内嵌了一个 :code:`python` 解释器。 这个 :code:`pyth py -> data_provider [dir="back"]; } -所以,PaddlePaddle单机训练进程,:code:`paddle train` , 对于用户的主要接口语言为 python。 主要需要用户配置的两个文件为 :code:`DataProvider` 和训练文件 :code:`TrainerConfig` 。 +所以,单机训练 ``trainer`` 进程对用户的主要接口语言为Python。用户需要配置文件主要有两个:数据流提供器 ``DataProvider`` 和模型配置 ``TrainerConfig`` 。 DataProvider ============ -DataProvider是 :code:`paddle train` 的数据提供器。 它负责将用户的原始数据转换成 PaddlePaddle 可以识别的数据类型。每当 PaddlePaddle 需要新的数据训练时,都会调用 DataProvider 返回数据。 当所有数据读取完一轮后,DataProvider 便返回空数据通知 PaddlePaddle。PaddlePaddle负责在下一轮训练开始前,将DataProvider重置。 +DataProvider是 ``trainer`` 进程的数据提供器。主要负责将用户的原始数据转换成 ``trainer`` 系统可以识别的数据类型。当系统需要新的数据训练时,会调用DataProvider获取数据接口。当所有数据读取完一轮后,DataProvider返回空数据通知系统一轮数据读取结束。 ``trainer`` 在每一轮训练开始时会重置DataProvider。 -需要注意的是,DataProvider在PaddlePaddle中是被训练逻辑调用的关系, 而不是新的数据驱动训练。并且所有的 :code:`shuffle` , 和一些随机化的噪声添加,都应该在 DataProvider 阶段完成。 +需要注意的是,DataProvider是被 ``trainer`` 系统调用,而不是新数据驱动系统;数据 ``shuffle`` 和一些随机化噪声添加都应该在DataProvider中完成。 -为了方便用户使用自己的数据格式, PaddlePaddle 提供了 `PyDataProvider`_ 来处理数据。 并且在这个Provider中,PaddlePaddle的 C++ 部分接管了如何shuffle,处理 batch,GPU/CPU通信,双缓冲,异步读取等问题。 用户可以参考 `PyDataProvider`_ 的相关文档,继续深入了解 DataProvider 的使用。 +为了用户能够灵活的处理数据,PaddlePaddle提供了处理数据的Python接口(称为 `PyDataProvider`_ )。 在 ``PyDataProvider`` 中,系统C++模块接管了shuffle、处理batch、GPU和CPU通信、双缓冲、异步读取等问题,需要说明的是,一些情况下需要Python接口里处理shuffle,可以参考 `PyDataProvider`_ 的相关文档继续深入了解。 -训练文件 -======== +TrainerConfig +============= -训练文件是PaddlePaddle中配置神经网络结构、学习优化算法、数据传入方式的地方。 训练文件是一个python文件,使用命令行参数 :code:`--config` 传给 paddle 的主程序。 例如\: +模型配置是一个Python文件,主要包括神经网络结构、优化算法、数据传入方式,使用命令行参数 ``--config`` 传给``trainer``主程序。 例如\: .. code-block:: bash paddle train --config=trainer_config.py -一个典型简单的训练文件可能为 +一个简单的模型配置文件为: .. literalinclude:: trainer_config.py :linenos: -下面我们详细的介绍一下训练文件中各个模块的概念。 +下面我们详细的介绍一下模型配置中各个模块的概念。 trainer_config_helpers ---------------------- -PaddlePaddle的配置文件与PaddlePaddle C++端通信的最基础协议是 :code:`protobuf` 。而为了避免用户直接写比较难写的 protobuf string,我们书写了一个helpers来生成这个protobuf包。所以在文件的开始,import这些helpers函数。 +PaddlePaddle配置文件与C++模块通信的最基础协议是 ``protobuf`` 。为了避免用户直接写比较难写的protobuf string,我们通过Python代码来生成protobuf包,这就是helpers的作用。所以在文件的开始,需要import这些helpers函数。 -需要注意的是,这个 :code:`paddle.trainer_config_helpers` 包是标准的python包,这意味着用户可以选择自己喜欢的 :code:`ide` 或者编辑器来编写Paddle的配置文件,这个python包注释文档比较完善,并且考虑了IDE的代码提示与类型注释。 +需要注意的是,这个 ``paddle.trainer_config_helpers`` 包是标准的python包,这意味着用户可以选择自己喜欢的 ``IDE`` 或者编辑器来编写Paddle的配置文件,这个Python包注释文档比较完善,并提供了IDE的代码提示与类型注释。 data_sources ------------ -data_sources是配置神经网络的数据源。这里使用的函数是 :code:`define_py_data_sources2` ,这个函数是定义了使用 `PyDataProvider`_ 作为数据源。 而后缀 :code:`2` 是Paddle历史遗留问题,因为Paddle之前使用的 PyDataProvider 性能较差,所以完全重构了一个新的 `PyDataProvider`_ 。 - -data_sources里面的 train_list 和 test_list 指定的是训练文件列表和测试文件列表。 如果传入一个字符串的话,是指一个训练列表文件。这个训练列表文件中包含的是每一个训练或者测试文件的路径。如果传入一个list的话,则会默认生成一个 list 文件,再传入给 train.list 或者 test.list 。 +data_sources配置神经网络的数据源,使用的函数是 ``define_py_data_sources2`` ,这个函数是定义了使用 `PyDataProvider`_ 提供数据源。后缀 ``2`` 是Paddle历史遗留问题,因为Paddle之前使用的PyDataProvider性能问题,重构了一个新的 `PyDataProvider`_ 。 -而 :code:`module` 和 :code:`obj` 指定了 DataProvider 的模块名和函数名。 +data_sources里通过train_list和test_list指定是训练文件列表和测试文件列表。 如果传入字符串的话,是指一个数据列表文件。这个数据列表文件中包含的是每一个训练或者测试文件的路径。如果传入一个list的话,则会默认生成一个list文件,再传入给train.list或者test.list。 -更具体的使用,请参考 `PyDataProvider`_ 。 +其中``module`` 和``obj``指定了DataProvider的文件名和返回数据的函数名。更详细的使用,请参考 `PyDataProvider`_ 。 settings -------- -`settings`_ 是神经网络训练算法相关的设置项。包括学习率,batch_size,优化算法,正则方法等等。具体的使用方法请参考 `settings`_ 文档。 +`settings`_ 设置训练神经网络所使用的算法。包括学习率、batch_size、优化算法、正则方法等,具体的使用方法请参考 `settings`_ 文档。 网络配置 -------- -上述网络配置中余下的部分均是神经网络配置。第一行是定义一个名字叫 "pixel" 的 :code:`data_layer` 。每一个layer返回的都是一个 :code:`LayerOutput` 对象。 这里第一层的输出对象是 :code:`img` 。然后这个对象传输给了另一个 layer 函数, -:code:`simple_img_conv_pool` 。:code:`simple_img_conv_pool` 是一个组合层, -包括了图像的卷积 (convolution) 和池化(pooling), -并继续接了一个全连接层( :code:`fc_layer` ),然后再接了一个Softmax的全连接层。 +上述配置中余下的部分是神经网络配置,主要包括网络连接、 ``cost`` 层、评估器。 -最终,网络配置输出了 :code:`classification_cost` 。标记网络输出的函数为 -:code:`outputs` 。网络的输出是神经网络的优化目标,神经网络训练的时候,实际上就是 -要最小化这个输出。 +- 首先,定义了一个名字叫"pixel"的 ``data_layer`` ,每个layer返回的都是一个 ``LayerOutput`` 对象,比如第一层的输出对象称作 ``img`` 。 +- 然后,这个对象作为另一个layer( ``simple_img_conv_pool`` )的输入, ``simple_img_conv_pool`` 是一个组合层,包括了图像的卷积 (convolution) 和池化(pooling), +- 其次,连接到全连接层(``fc_layer``),再连接到一个含Softmax激活的全连接层。 +- 最终,连接到cost层( ``classification_cost`` ), ``classification_cost`` 默认使用多类交叉熵损失函数和分类错误率统计评估器。标记网络输出的函数为 ``outputs`` ,网络的输出是神经网络的优化目标,神经网络训练的时候,实际上就是要最小化这个输出。 -在神经网络进行预测的时候,实际上网络的输出也是通过 :code:`outputs` 标记。 +用该模型配置进行预测时,网络的输出也是通过 ``outputs`` 标记。 Layer、Projection、Operator =========================== -PaddlePaddle的网络基本上是基于Layer来配置的。所谓的Layer即是神经网络的某一层, -而神经网络的某一层,一般是封装了许多复杂操作的操作集合。比如最简单的 -:code:`fc_layer` ,也包括矩阵乘法,多输入的求和,和activation。 +PaddlePaddle的网络是基于Layer来配置的。所谓的Layer即是神经网络的某一层,一般是封装了许多复杂操作的操作集合。比如最简单的 ``fc_layer`` ,包括矩阵乘法、多输入的求和、加Bias操作、激活( ``activation`` )函数操作。 .. code-block:: python data = data_layer(name='data', size=200) out = fc_layer(input=data, size=200, act=TanhActivation()) -而对于更灵活配置需求,可能这样基于Layer的配置是不灵活的。于是 PaddlePaddle 提供 -了基于 Projection 或者 Operator 的配置。使用Projection和Operator需要与 -:code:`mixed_layer` 配合使用。 :code:`mixed_layer` 是将layer中的元素累加求和, -并且做一个 :code:`activation` , 而这个layer具体如何计算,是交由内部的Projection -和 Operator 定义。Projection是指含有可学习参数的操作,而Operator不含有可学习的 -参数,输入全是其他Layer的输出。 +对于更灵活配置需求,PaddlePaddle提供了基于 ``Projection`` 或者 ``Operator`` 的配置,这些需要与 ``mixed_layer`` 配合使用。 ``mixed_layer`` 是将多个输入累加求和,然后加Bias和 ``activation`` 操作。 ``mixed_layer`` 具体计算是通过内部的Projection和Operator完成。Projection含有可学习参数;而Operator不含可学习的参数,输入全是其他Layer的输出。 -例如,和 :code:`fc_layer` 同样功能的 :code:`mixed_layer` 。 +例如,和 ``fc_layer`` 同样功能的 ``mixed_layer`` 是: .. code-block:: python @@ -126,14 +115,12 @@ PaddlePaddle的网络基本上是基于Layer来配置的。所谓的Layer即是 with mixed_layer(size=200) as out: out += full_matrix_projection(input=data) -PaddlePaddle可以使用的mixed layer 配置出非常复杂的网络,甚至可以直接配置一个完整的LSTM。 -用户可以参考 `mixed_layer`_ 的相关文档进行配置。 +PaddlePaddle可以使用 ``mixed layer`` 配置出非常复杂的网络,甚至可以直接配置一个完整的LSTM。用户可以参考 `mixed_layer`_ 的相关文档进行配置。 如何利用单机的所有GPU或所有CPU核心 -================================== +=============================== -PaddlePaddle的单机进程 :code:`paddle train` 可以充分利用一台计算机上所有的GPU资 -源或者CPU。 +PaddlePaddle的单机 ``trainer`` 进程可以充分利用一台计算机上所有的GPU资源或者CPU。 如果要使用机器上多块GPU,使用如下命令即可\: @@ -145,41 +132,41 @@ PaddlePaddle的单机进程 :code:`paddle train` 可以充分利用一台计算 .. code-block:: bash - paddle train --trainer_config=4 # use 4 cpu cores. + paddle train --trainer_count=4 # use 4 cpu cores. -对于其他设置GPU的选择情况,例如选择第0、2号GPU显卡,则可以使用 :code:`CUDA_VISIBLE_DEVICES` 环境变量来选择部分的显卡。 具体可以参考连接`masking-gpus`_ 。 可以使用的命令为 +如果要指定GPU编号,例如选择第0、2号GPU,则可以设置 ``CUDA_VISIBLE_DEVICES`` 环境变量来指定特定的GPU。具体可以参考连接`masking-gpu`_ ,命令为: .. code-block:: bash - env CUDA_VISIBLE_DEVICES=0,2 paddle train --use_gpu=true --trainer_config=2 + env CUDA_VISIBLE_DEVICES=0,2 paddle train --use_gpu=true --trainer_count=2 如何利用多台机器的计算资源训练神经网络 -====================================== +=================================== -PaddlePaddle多机使用的经典方法是通过 :code:`Parameter Server` 来对多机的 :code:`paddle train` 进行同步。 而多机训练神经网络,首先要讲数据切分到不同的机器上。 切分数据文件的方式在PaddlePaddle的开源实现中并没有提供工具包。 但是切分数据并不是一件非常复杂的事情,也不是神经网络实现的重点。 +PaddlePaddle多机采用经典的 ``Parameter Server`` 架构对多个节点的 ``trainer`` 进行同步。多机训练神经网络,要讲数据切分到不同的机器上,切分数据相对简单,所以在PaddlePaddle的开源实现中并没有提供相关工具包。 -多机训练过程中,经典的拓扑结构如下\: +多机训练的经典拓扑结构如下\: .. graphviz:: pserver_topology.dot -图中每个灰色方块是一台机器,在每个机器中,先去启动一个 :code:`paddle pserver` 进程,并确定整体的端口号。可能的参数是\: +图中每个灰色方块是一台机器,在每个机器中,先启动一个 ``paddle pserver`` 进程,并指定端口号,可能的参数是\: .. code-block:: bash paddle pserver --port=5000 --num_gradient_servers=4 --nics='eth0' -这里说明系统的 :code:`paddle pserver` 的起始端口是 :code:`5000` ,并且有四个训练进程(:code:`gradient_servers`,Paddle同时将 :code:`paddle train` 进程称作 :code:`GradientServer` 。因为其为负责提供Gradient的进程)。 而对于训练进程的话,则需要在 :code:`paddle pserver` 启动之后,再在各个节点上运行如下命令\: +这里说明系统的 ``pserver`` 进程端口是 ``5000`` ,有四个训练进程(即 ``--gradient_servers=4`` ,PaddlePaddle同时将 ``trainer`` 称作 ``GradientServer`` 。因为其为负责提供Gradient)。 启动之后 ``pserver`` 进程之后,需要 ``trainer`` 训练进程,再在各个机器上运行如下命令\: .. code-block:: bash paddle train --port=5000 --pservers=192.168.100.101,192.168.100.102,192.168.100.103,192.168.100.104 --config=... -对于简单的多机协同使用上述方式即可。同时,pserver/train 通常在高级情况下,还有两个参数需要设置,他们是 +对于简单的多机协同训练使用上述方式即可。另外,pserver/train 通常在高级情况下,还需要设置下面两个参数\: * --ports_num\: 一个 pserver进程共绑定多少个端口用来做稠密更新。默认是1 * --ports_num_for_sparse\: 一个pserver进程共绑定多少端口用来做稀疏更新,默认是0 -使用手工指定端口数量,是因为Paddle的网络通信中,使用了 :code:`int32` 作为消息长度,比较容易在大模型下溢出。所以,在 :code:`paddle pserver` 进程中可以启动多个子线程去接受 trainer 的数据,这样单个子线程的长度就不会溢出了。但是这个值不可以调的过大,因为增加这个值,还是对性能,尤其是内存占用有一定的开销的,另外稀疏更新的端口如果太大的话,很容易某一个参数服务器没有分配到任何参数。 +使用手工指定端口数量,是因为Paddle的网络通信中,使用了 ``int32`` 作为消息长度,比较容易在大模型下溢出。所以,在 ``pserver`` 进程中可以启动多个子线程去接受trainer的数据,这样单个子线程的长度就不会溢出了。但是这个值不可以调的过大,因为增加这个值,对性能尤其是内存占用有一定的开销,另外稀疏更新的端口如果太大的话,很容易导致某一个参数服务器没有分配到任何参数。 详细的说明可以参考,使用 `集群训练Paddle`_ 。 -- GitLab From 07dcf7bfc69166fcf889a82533cd9eca15596246 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Fri, 18 Nov 2016 13:32:56 -0800 Subject: [PATCH 0068/1503] Update docker_install.rst --- doc_cn/build_and_install/install/docker_install.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc_cn/build_and_install/install/docker_install.rst b/doc_cn/build_and_install/install/docker_install.rst index a5f5fb117e..21a2d8bb94 100644 --- a/doc_cn/build_and_install/install/docker_install.rst +++ b/doc_cn/build_and_install/install/docker_install.rst @@ -1,8 +1,7 @@ 安装PaddlePaddle的Docker镜像 ============================ -PaddlePaddle提供了Docker的使用镜像。PaddlePaddle推荐使用Docker进行PaddlePaddle的部署和 -运行。Docker是一个基于容器的轻量级虚拟环境。具有和宿主机相近的运行效率,并提供 +PaddlePaddle提供了Docker的使用镜像。PaddlePaddle推荐使用Docker进行PaddlePaddle的部署和运行。Docker是一个基于容器的轻量级虚拟环境。具有和宿主机相近的运行效率,并提供 了非常方便的二进制分发手段。 下述内容将分为如下几个类别描述。 @@ -41,7 +40,7 @@ PaddlePaddle提供的Docker镜像版本 * CPU WITHOUT AVX: CPU版本,不支持AVX指令集的CPU也可以运行 * GPU WITHOUT AVX: GPU版本,不需要AVX指令集的CPU也可以运行。 -用户可以选择对应版本的docker image。使用如下脚本可以确定本机的CPU知否支持 :code:`AVX` 指令集\: +用户可以选择对应版本的docker image。使用如下脚本可以确定本机的CPU是否支持 :code:`AVX` 指令集\: .. code-block:: bash -- GitLab From faa32c34182ab2c2383166bcd6bb5dee1b830a10 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Fri, 18 Nov 2016 17:51:50 -0800 Subject: [PATCH 0069/1503] Update docker_install.rst --- doc_cn/build_and_install/install/docker_install.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc_cn/build_and_install/install/docker_install.rst b/doc_cn/build_and_install/install/docker_install.rst index 21a2d8bb94..0872fd0b7a 100644 --- a/doc_cn/build_and_install/install/docker_install.rst +++ b/doc_cn/build_and_install/install/docker_install.rst @@ -66,7 +66,7 @@ mac osx或者是windows机器,请参考 .. code-block:: bash - $ docker run -it paddledev/paddlepaddle:cpu-latest + $ docker run -it paddledev/paddle:cpu-latest 即可启动和进入PaddlePaddle的container。如果运行GPU版本的PaddlePaddle,则需要先将 cuda相关的Driver和设备映射进container中,脚本类似于 @@ -75,7 +75,7 @@ cuda相关的Driver和设备映射进container中,脚本类似于 $ export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu + $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:gpu-latest 进入Docker container后,运行 :code:`paddle version` 即可打印出PaddlePaddle的版本和构建 信息。安装完成的PaddlePaddle主体包括三个部分, :code:`paddle` 脚本, python的 -- GitLab From eeca7be7ad0ca24d909b2139acb021f08f75dfb2 Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Sun, 20 Nov 2016 10:04:43 +0800 Subject: [PATCH 0070/1503] fix some md syntax error. --- .../k8s/distributed_training_on_kubernetes.md | 157 ++++++++++++------ doc_cn/cluster/k8s/k8s-paddle-arch.png | Bin 0 -> 22084 bytes 2 files changed, 105 insertions(+), 52 deletions(-) create mode 100644 doc_cn/cluster/k8s/k8s-paddle-arch.png diff --git a/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md index 8e947f8d56..e07cf6182f 100644 --- a/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md +++ b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md @@ -1,84 +1,75 @@ # Paddle on Kubernetes:分布式训练 -前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式Paddle训练作业。关于Paddle的分布式训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md), 本文利用Kubernetes的调度功能与容器编排能力,快速构建Paddle容器集群,进行分布式训练任务。 +前一篇文章介绍了如何在Kubernetes集群上启动一个单机PaddlePaddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式PaddlePaddle训练作业。关于PaddlePaddle的分布式训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md),本文利用Kubernetes的调度功能与容器编排能力,快速构建PaddlePaddle容器集群,进行分布式训练任务。 ## Kubernetes 基本概念 -在介绍分布式训练之前,需要对Kubernetes(k8s)有一个基本的认识,下面先简要介绍一下本文用到的几个k8s概念。 +[*Kubernetes*](http://kubernetes.io/)是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用。在介绍分布式训练之前,需要对[Kubernetes](http://kubernetes.io/)有一个基本的认识,下面先简要介绍一下本文用到的几个Kubernetes概念。 -### Node +- [*Node*](http://kubernetes.io/docs/admin/node/) 表示一个Kubernetes集群中的一个工作节点,这个节点可以是物理机或者虚拟机,Kubernetes集群就是由node节点与master节点组成的。 -[`Node`](http://kubernetes.io/docs/admin/node/) 表示一个k8s集群中的一个工作节点,这个节点可以是物理机或者虚拟机,k8s集群就是由`node`节点与`master`节点组成的。每个node都安装有Docker,在本文的例子中,`Paadle`容器就在node上运行。 +- [*Pod*](http://kubernetes.io/docs/user-guide/pods/) 是一组(一个或多个)容器,pod是Kubernetes的最小调度单元,一个pod中的所有容器会被调度到同一个node上。Pod中的容器共享NET,PID,IPC,UTS等Linux namespace。由于容器之间共享NET namespace,所以它们使用同一个IP地址,可以通过*localhost*互相通信。不同pod之间可以通过IP地址访问。 -### Pod +- [*Job*](http://kubernetes.io/docs/user-guide/jobs/) 是Kubernetes上运行的作业,一次作业称为一个job,通常每个job包括一个或者多个pods。 -一个[`Pod`](http://kubernetes.io/docs/user-guide/pods/) 是一组(一个或多个)容器,pod是k8s的最小调度单元,一个pod中的所有容器会被调度到同一个node上。Pod中的容器共享NET,PID,IPC,UTS等Linux namespace,它们使用同一个IP地址,可以通过`localhost`互相通信。不同pod之间可以通过IP地址访问。 +- [*Volume*](http://kubernetes.io/docs/user-guide/volumes/) 存储卷,是pod内的容器都可以访问的共享目录,也是容器与node之间共享文件的方式,因为容器内的文件都是暂时存在的,当容器因为各种原因被销毁时,其内部的文件也会随之消失。通过volume,就可以将这些文件持久化存储。Kubernetes支持多种volume,例如hostPath(宿主机目录),gcePersistentDisk,awsElasticBlockStore等。 -### Job - -[`Job`](http://kubernetes.io/docs/user-guide/jobs/) 可以翻译为作业,每个job可以设定pod成功完成的次数,一次作业会创建多个pod,当成功完成的pod个数达到预设值时,就表示job成功结束了。 - -### Volume - -[`Volume`](http://kubernetes.io/docs/user-guide/volumes/) 存储卷,是pod内的容器都可以访问的共享目录,也是容器与node之间共享文件的方式,因为容器内的文件都是暂时存在的,当容器因为各种原因被销毁时,其内部的文件也会随之消失。通过volume,就可以将这些文件持久化存储。k8s支持多种volume,例如`hostPath(宿主机目录)`,`gcePersistentDisk`,`awsElasticBlockStore`等。 - -### Namespace - -[`Namespaces`](http://kubernetes.io/docs/user-guide/volumes/) 命名空间,在k8s中创建的所有资源对象(例如上文的pod,job)等都属于一个命名空间,在同一个命名空间中,资源对象的名字是唯一的,不同空间的资源名可以重复,命名空间主要用来为不同的用户提供相对隔离的环境。本文只使用了`default`默认命名空间,读者可以不关心此概念。 +- [*Namespaces*](http://kubernetes.io/docs/user-guide/volumes/) 命名空间,在kubernetes中创建的所有资源对象(例如上文的pod,job)等都属于一个命名空间,在同一个命名空间中,资源对象的名字是唯一的,不同空间的资源名可以重复,命名空间主要为了对象进行逻辑上的分组便于管理。本文只使用了默认命名空间。 ## 整体方案 -### 前提条件 +### 部署Kubernetes集群 + +首先,我们需要拥有一个Kubernetes集群,在这个集群中所有node与pod都可以互相通信。关于Kubernetes集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。本文假设大家能找到几台物理机,并且可以按照官方文档在上面部署Kubernetes。在本文的环境中,Kubernetes集群中所有node都挂载了一个*mfs*(分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。在训练之前,用户将配置与训练数据切分好放在mfs目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。整体的结果图如下: -首先,我们需要拥有一个k8s集群,在这个集群中所有node与pod都可以互相通信。关于k8s集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。在本文的环境中,k8s集群中所有node都挂载了一个`mfs`(分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。在训练之前,用户将配置与训练数据切分好放在mfs目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。 +![paddle on kubernetes结构图](k8s-paddle-arch.png) -### 使用 `Job` +### 使用 Job -我们使用k8s中的job这个概念来代表一次分布式训练。`Job`表示一次性作业,在作业完成后,k8s会销毁job产生的容器并且释放相关资源。 +我们使用Kubernetes中的job这个概念来代表一次分布式训练。Job表示一次性作业,在作业完成后,Kubernetes会销毁job产生的容器并且释放相关资源。 -在k8s中,可以通过编写一个 `yaml` 文件,来描述这个job,在这个文件中,主要包含了一些配置信息,例如Paddle节点的个数,`paddle pserver`开放的端口个数与端口号,`paddle`使用的网卡设备等,这些信息通过环境变量的形式传递给容器内的程序使用。 +在Kubernetes中,可以通过编写一个YAML文件,来描述这个job,在这个文件中,主要包含了一些配置信息,例如PaddlePaddle的节点个数,`paddle pserver`开放的端口个数与端口号,使用的网卡设备等,这些信息通过环境变量的形式传递给容器内的程序使用。 -在一次分布式训练中,用户确定好本次训练需要的Paddle节点个数,将切分好的训练数据与配置文件上传到`mfs`共享目录中。然后编写这次训练的`job yaml`文件,提交给k8s集群创建并开始作业。 +在一次分布式训练中,用户确定好本次训练需要的PaddlePaddle节点个数,将切分好的训练数据与配置文件上传到mfs共享目录中。然后编写这次训练的job YAML文件,提交给Kubernetes集群创建并开始作业。 -### 创建`Paddle`节点 +### 创建PaddlePaddle节点 -当k8s master收到`job yaml`文件后,会解析相关字段,创建出多个pod(个数为Paddle节点数),k8s会把这些pod调度到集群的node上运行。一个`pod`就代表一个`Paddle`节点,当pod被成功分配到一台物理/虚拟机上后,k8s会启动pod内的容器,这个容器会根据`job yaml`文件中的环境变量,启动`paddle pserver`与`paddle train`进程。 +当Kubernetes master收到请求,解析完YAML文件后,会创建出多个pod(个数为PaddlePaddle节点数),Kubernetes会把这些pod调度到集群的node上运行。一个pod就代表一个PaddlePaddle节点,当pod被成功分配到一台物理/虚拟机上后,Kubernetes会启动pod内的容器,这个容器会根据YAML文件中的环境变量,启动`paddle pserver`与`paddle train`进程。 ### 启动训练 -在容器启动后,会通过脚本来启动这次分布式训练,我们知道`paddle train`进程启动时需要知道其他节点的IP地址以及本节点的`trainer_id`,由于`Paddle`本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据`job name`向`k8s apiserver`查询这个`job`对应的所有`pod`信息(k8s默认会在每个容器的环境变量中写入`apiserver`的地址)。 +在容器启动后,会通过脚本来启动这次分布式训练,我们知道`paddle train`进程启动时需要知道其他节点的IP地址以及本节点的trainer_id,由于Paddle本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据job name向Kubernetes apiserver查询这个job对应的所有pod信息(Kubernetes默认会在每个容器的环境变量中写入apiserver的地址)。 -根据这些pod信息,就可以通过某种方式,为每个pod分配一个唯一的`trainer_id`。本文把所有pod的IP地址进行排序,将顺序作为每个`Paddle`节点的`trainer_id`。启动脚本的工作流程大致如下: +根据这些pod信息,就可以通过某种方式,为每个pod分配一个唯一的trainer_id。本文把所有pod的IP地址进行排序,将顺序作为每个PaddlePaddle节点的trainer_id。启动脚本的工作流程大致如下: - 1. 查询`k8s apiserver`获取pod信息,根据IP分配`trainer_id` - 1. 从`mfs`共享目录中拷贝训练文件到容器内 + 1. 查询Kubernetes apiserver获取pod信息,根据IP分配trainer_id + 1. 从mfs共享目录中拷贝训练文件到容器内 1. 根据环境变量,解析出`paddle pserver`与`paddle train`的启动参数,启动进程 - 1. 训练时,`Paddle`会自动将结果保存在`trainer_id`为0的节点上,将输出路径设置为`mfs`目录,保存输出的文件 + 1. 训练时,PaddlePaddle会自动将结果保存在trainer_id为0的节点上,将输出路径设置为mfs目录,保存输出的文件 ## 搭建过程 -根据前文的描述,要在已有的k8s集群上进行`Paddle`的分布式训练,主要分为以下几个步骤: +根据前文的描述,要在已有的Kubernetes集群上进行PaddlePaddle的分布式训练,主要分为以下几个步骤: -1. 制作`Paddle`镜像 +1. 制作PaddlePaddle镜像 1. 将训练文件与切分好的数据上传到共享存储 -1. 编写本次训练的`job yaml`文件,创建`k8s job` +1. 编写本次训练的YAML文件,创建一个Kubernetes job 1. 训练结束后查看输出结果 下面就根据这几个步骤分别介绍。 - ### 制作镜像 -`Paddle`镜像需要提供`paddle pserver`与`paddle train`进程的运行环境,用这个镜像创建的容器需要有以下两个功能: +PaddlePaddle镜像需要提供`paddle pserver`与`paddle train`进程的运行环境,用这个镜像创建的容器需要有以下两个功能: - 拷贝训练文件到容器内 - 生成`paddle pserver`与`paddle train`进程的启动参数,并且启动训练 -因为官方镜像 `paddledev/paddle:cpu-latest` 内已经包含`Paddle`的执行程序但是还没上述功能,所以我们可以在这个基础上,添加启动脚本,制作新镜像来完成以上的工作。镜像的`Dockerfile`如下: +因为官方镜像 `paddledev/paddle:cpu-latest` 内已经包含PaddlePaddle的执行程序但是还没上述功能,所以我们可以在这个基础上,添加启动脚本,制作新镜像来完成以上的工作。镜像的*Dockerfile*如下: ```Dockerfile FROM paddledev/paddle:cpu-latest @@ -92,25 +83,87 @@ CMD ["bash"," -c","/root/start.sh"] [`start.sh`](start.sh)文件拷贝训练文件到容器内,然后执行[`start_paddle.py`](start_paddle.py)脚本启动训练,前文提到的获取其他节点IP地址,分配`trainer_id`等都在`start_paddle.py`脚本中完成。 +`start_paddle.py`脚本开始时,会先进行参数的初始化与解析。 + +```python +parser = argparse.ArgumentParser(prog="start_paddle.py", + description='simple tool for k8s') + args, train_args_list = parser.parse_known_args() + train_args = refine_unknown_args(train_args_list) + train_args_dict = dict(zip(train_args[:-1:2], train_args[1::2])) + podlist = getPodList() +``` + +然后通过函数`getPodList()`访问Kubernetes的接口来查询此job对应的所有pod信息。当所有pod都处于running状态(容器运行都运行)时,再通过函数`getIdMap(podlist)`获取trainer_id。 + +```python + podlist = getPodList() + # need to wait until all pods are running + while not isPodAllRunning(podlist): + time.sleep(10) + podlist = getPodList() + idMap = getIdMap(podlist) +``` + +在函数`getIdMap(podlist)`内部,我们通过读取`podlist`中每个pod的IP地址,将IP排序生成的序号作为trainer_id。 + +```python +def getIdMap(podlist): + ''' + generate tainer_id by ip + ''' + ips = [] + for pod in podlist["items"]: + ips.append(pod["status"]["podIP"]) + ips.sort() + idMap = {} + for i in range(len(ips)): + idMap[ips[i]] = i + return idMap +``` + +在得到`idMap`后,通过函数`startPaddle(idMap, train_args_dict)`构造`paddle pserver`与`paddle train`的启动参数并执行进程。 + +在函数`startPaddle`中,最主要的工作就是解析出`paddle pserver`与`paddle train`的启动参数。例如`paddle train`参数的解析,解析环境变量得到`PADDLE_NIC`,`PADDLE_PORT`,`PADDLE_PORTS_NUM`等参数,然后通过自身的IP地址在`idMap`中获取`trainerId`。 + +```python + program = 'paddle train' + args = " --nics=" + PADDLE_NIC + args += " --port=" + str(PADDLE_PORT) + args += " --ports_num=" + str(PADDLE_PORTS_NUM) + args += " --comment=" + "paddle_process_by_paddle" + ip_string = "" + for ip in idMap.keys(): + ip_string += (ip + ",") + ip_string = ip_string.rstrip(",") + args += " --pservers=" + ip_string + args_ext = "" + for key, value in train_args_dict.items(): + args_ext += (' --' + key + '=' + value) + localIP = socket.gethostbyname(socket.gethostname()) + trainerId = idMap[localIP] + args += " " + args_ext + " --trainer_id=" + \ + str(trainerId) + " --save_dir=" + JOB_PATH_OUTPUT +``` 使用 `docker build` 构建镜像: ```bash -docker build -t registry.baidu.com/public/paddle:mypaddle . +docker build -t your_repo/paddle:mypaddle . ``` -然后将构建成功的镜像上传到镜像仓库,注意本文中使用的`registry.baidu.com`是一个私有仓库,读者可以根据自己的情况部署私有仓库或者使用`Docker hub`。 +然后将构建成功的镜像上传到镜像仓库。 ```bash -docker push registry.baidu.com/public/paddle:mypaddle +docker push your_repo/paddle:mypaddle ``` ### 上传训练文件 -本文使用`Paddle`官方的`recommendation demo`作为这次训练的内容,我们将训练文件与数据放在一个`job name`命名的目录中,上传到`mfs`共享存储。完成后`mfs`上的文件内容大致如下: +本文使用Paddle官方的[recommendation demo](http://www.paddlepaddle.org/doc/demo/index.html#recommendation)作为这次训练的内容,我们将训练文件与数据放在一个job name命名的目录中,上传到mfs共享存储。完成后mfs上的文件内容大致如下: ```bash -[root@paddle-k8s-node0 mfs]# tree -d +[root@paddle-kubernetes-node0 mfs]# tree -d . └── paddle-cluster-job ├── data @@ -123,13 +176,13 @@ docker push registry.baidu.com/public/paddle:mypaddle └── recommendation ``` -目录中`paddle-cluster-job`是本次训练对应的`job name`,本次训练要求有3个`Paddle`节点,在`paddle-cluster-job/data`目录中存放切分好的数据,文件夹`0,1,2`分别代表3个节点的`trainer_id`。`recommendation`文件夹内存放训练文件,`output`文件夹存放训练结果与日志。 +目录中paddle-cluster-job是本次训练对应的job name,本次训练要求有3个Paddle节点,在paddle-cluster-job/data目录中存放切分好的数据,文件夹0,1,2分别代表3个节点的trainer_id。recommendation文件夹内存放训练文件,output文件夹存放训练结果与日志。 -### 创建`job` +### 创建Job -`k8s`可以通过`yaml`文件来创建相关对象,然后可以使用命令行工具创建`job`。 +Kubernetes可以通过YAML文件来创建相关对象,然后可以使用命令行工具创建job。 -`job yaml`文件描述了这次训练使用的Docker镜像,需要启动的节点个数以及 `paddle pserver`与 `paddle train`进程启动的必要参数,也描述了容器需要使用的存储卷挂载的情况。`yaml`文件中各个字段的具体含义,可以查看[`k8s官方文档`](http://kubernetes.io/docs/api-reference/batch/v1/definitions/#_v1_job)。例如,本次训练的`yaml`文件可以写成: +Job YAML文件描述了这次训练使用的Docker镜像,需要启动的节点个数以及 `paddle pserver`与 `paddle train`进程启动的必要参数,也描述了容器需要使用的存储卷挂载的情况。YAML文件中各个字段的具体含义,可以查看[Kubernetes Job API](http://kubernetes.io/docs/api-reference/batch/v1/definitions/#_v1_job)。例如,本次训练的YAML文件可以写成: ```yaml apiVersion: batch/v1 @@ -149,7 +202,7 @@ spec: path: /home/work/mfs containers: - name: trainer - image: registry.baidu.com/public/paddle:mypaddle + image: your_repo/paddle:mypaddle command: ["bin/bash", "-c", "/root/start.sh"] env: - name: JOB_NAME @@ -176,7 +229,7 @@ spec: restartPolicy: Never ``` -文件中,`metadata`下的`name`表示这个`job`的名字。`parallelism,completions`字段表示这个`job`会同时开启3个`Paddle`节点,成功训练且退出的`pod`数目为3时,这个`job`才算成功结束。然后申明一个存储卷`jobpath`,代表宿主机目录`/home/work/mfs`,在对容器的描述`containers`字段中,将此目录挂载为容器的`/home/jobpath`目录,这样容器的`/home/jobpath`目录就成为了共享存储,放在这个目录里的文件其实是保存到了`mfs`上。 +文件中,`metadata`下的`name`表示这个job的名字。`parallelism,completions`字段表示这个job会同时开启3个Paddle节点,成功训练且退出的pod数目为3时,这个job才算成功结束。然后申明一个存储卷`jobpath`,代表宿主机目录`/home/work/mfs`,在对容器的描述`containers`字段中,将此目录挂载为容器的`/home/jobpath`目录,这样容器的`/home/jobpath`目录就成为了共享存储,放在这个目录里的文件其实是保存到了mfs上。 `env`字段表示容器的环境变量,我们将`paddle`运行的一些参数通过这种方式传递到容器内。 @@ -190,21 +243,21 @@ spec: `CONF_PADDLE_GRADIENT_NUM`表示训练节点数量,即`--num_gradient_servers`参数 -编写完`yaml`文件后,可以使用k8s的命令行工具创建`job`. +编写完YAML文件后,可以使用Kubernetes的命令行工具创建job。 ```bash kubectl create -f job.yaml ``` -创建成功后,k8s就会创建3个`pod`作为`Paddle`节点然后拉取镜像,启动容器开始训练。 +创建成功后,Kubernetes就会创建3个pod作为PaddlePaddle节点然后拉取镜像,启动容器开始训练。 ### 查看输出 -在训练过程中,可以在共享存储上查看输出的日志和模型,例如`output`目录下就存放了输出结果。注意`node_0`,`node_1`,`node_2`这几个目录表示`Paddle`节点与`trainer_id`,并不是k8s中的`node`概念。 +在训练过程中,可以在共享存储上查看输出的日志和模型,例如output目录下就存放了输出结果。注意node_0,node_1,node_2这几个目录表示Paddle节点与trainer_id,并不是Kubernetes中的node概念。 ```bash -[root@paddle-k8s-node0 output]# tree -d +[root@paddle-kubernetes-node0 output]# tree -d . ├── node_0 │   ├── server.log @@ -224,7 +277,7 @@ kubectl create -f job.yaml 我们可以通过日志查看容器训练的情况,例如: ```bash -[root@paddle-k8s-node0 node_0]# cat train.log +[root@paddle-kubernetes-node0 node_0]# cat train.log I1116 09:10:17.123121 50 Util.cpp:155] commandline: /usr/local/bin/../opt/paddle/bin/paddle_trainer --nics=eth0 --port=7164 diff --git a/doc_cn/cluster/k8s/k8s-paddle-arch.png b/doc_cn/cluster/k8s/k8s-paddle-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c64550b1fa7f41de1eaa9a037c65cddc0cd30e GIT binary patch literal 22084 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�gYl^6l-ltkTlmdD|aM=)lE&m0c9Ljcj^BL7lC-z2L>3r2HR0o`S5yv$fIU z(Ljy{fR*V8?ml`NzCU4E3@Tu^MghIeYibgs5OPxjuhw}@!ls?k06$%=1#iA_y#)3t z;IW^h?|p21<;s;|L6cuUK7hPkewHGl2+j8iMUGPF%rDN4C(2Ail6@}Rg~2{g-rJ{z{~1EjGA5=6N?hjSsKMb2|Q zAc<=Mt|OpqlmbNH1hrKR76GKj0R~k^bIV_IbHfdAPht|1Rq#@tNHzFzCz|8ed!kl> zUjR9H4SV~~X!({vqeYCa!-r3t==~(%x>>N$8MD@Kx+!Q5+@~8k+KJj}Z`SQ8fT^vt zklPLc*5Pz{j$&bClu4Hg0==4zY%!ggWjzAW5pJy2(c7yDh@$Y_9^3DdbUI#Vh7y^zS=ItIwCM6Z3 z32<}s%HgDK%=e#t?+D023RQMtVIkL`(Ho>9(`^r z+P|SrTvqOq$}LH{U=7uY6>DnJ zDNPtd7?);Qsiw2JG%*borLhPl7P%z$`Px5Vf8P0Newg?BzTfwAd7jVndFKs#*L`P1 zdi_3g6ad6k12Uy_vL-X5+YC66HSDvoUlGi3W7%6cL~#3+;Zo3gG69f?b;&}--a~(yFaIpy38yE@gjx`daI;5xsY9|tg5+f`*s$*xqK!=FC!y^XTIgxZu-xy`6j$4 zbUIyx^txs9=FrK(HWd|>4tEcB5yZv_KoMag|cPB=Px_k+{{P`xDn2YQolmS^R0@O&P#&ZO9>?ShWvT$VJS;*%lj-1Ooy_e?w^+cbwv9Tc_@^En%Fs`N20K!=@Y%j@IkIDgmfFsK1i+-=HWI@>xjn%GEQ!8{Zs=s;fvrKy^ zT_^eWq-yd>Vg2BS&yzSYWpMaqjgw!}gk71*m#ej<@19o;9IcmCg}AhFX)an?T3dVL z1BrZtE|uJGY2Je!_dJOADdY=f<**6G!+DUj+f`4j4wN^}mtY%kj*lPSe#MM);lh5r zAxc+)6L%+$CJIZcK1@x;;3;ES&s0czj^Qu=BvM6c%RmQ)m%^G?A;}Wr-C3Ffa;&sD zRdpIy>gZFcBKb+`&OETY!|P6&Q1W6>V=eXdh1ez9vI=&E65xu8p|( z0qmi;xR}t2k;0|#UbTm-4|RbMr{5BQc^ptvM#3wm^6BY~(EQ&PH~we{=u zfvv9>Khl8A0T9AQEzCc5b#!)GUR(P6k`ORo=VSCFf{-6GIGpY3Tb*}+aR@NI$?))S zFt{%-P$H2O;0F(~Hc%fU>hC+|8{f0Fx96s+t}B37nLn~hUZ`U_IGBQ|)z(AM#0^jx zL!z`sooUOCSY)N(+3`2QT|)LPfuL1;cU-HsRZ{mi+PkEtfTs?}tLD#ojdz?c87 zC&$0FX0nbSukVd*a*-_vKq@bH`>2o#m34vVP1Z@eOvF`%c=hb*jt-WF286YHz{BK< z<$3K;B&3|`{+>x`n9Am4X1*6H8+szip%HR0>ypW!rFe7&sXFpC;AC&!&rls`Rkw*9LgUHl}%%4bpBhmBr8Tr%g3VS zULlr;vodnlM#fu)-g&&aj4D}}5wgXNwc_>I2nMmBr6cFk)6<#eU1K)VO3%x&xFV0T nXk=*E78GxYJ^4R=n!@Hzow=b{nG)e=My#J{=WHvq@jvqqm+3b2 literal 0 HcmV?d00001 -- GitLab From 0e7d5cdea2de0e5f2d2b051083f6a92956f52ca4 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 20 Nov 2016 14:37:50 +0800 Subject: [PATCH 0071/1503] Refine quick start index.rst chinese docs --- doc_cn/demo/quick_start/index.md | 545 ------------------------------ doc_cn/demo/quick_start/index.rst | 474 ++++++++++++++++++++++++++ 2 files changed, 474 insertions(+), 545 deletions(-) delete mode 100644 doc_cn/demo/quick_start/index.md create mode 100644 doc_cn/demo/quick_start/index.rst diff --git a/doc_cn/demo/quick_start/index.md b/doc_cn/demo/quick_start/index.md deleted file mode 100644 index 4d9b24ba85..0000000000 --- a/doc_cn/demo/quick_start/index.md +++ /dev/null @@ -1,545 +0,0 @@ -# PaddlePaddle快速入门教程 - -我们以文本分类问题作为背景,介绍PaddlePaddle使用流程和常用的网络基础单元的配置方法。 - -## 安装(Install) - -首先请参考安装教程安装PaddlePaddle。 - -## 使用概述(Overview) - -**文本分类问题**:对于给定的一条文本, 我们从提前给定的类别集合中选择其所属类 -别。比如通过用户对电子商务网站评论,评估产品的质量: - -- 这个显示器很棒! (好评) -- 用了两个月之后这个显示器屏幕碎了。(差评) - -每一个任务流程都可以分为如下5个基础部分。 -
    ![](./Pipeline.jpg)
    - -1. 数据格式准备 - - 每行保存一条样本,类别Id 和文本信息用Tab间隔, 文本中的单词用空格分隔(如果不切词,则字与字之间用空格分隔),例如:```类别Id ‘\t’ 这 个 显 示 器 很 棒 !``` -2. 数据向模型传送 - - PaddlePaddle可以读取Python写的传输数据脚本,所有字符都将转换为连续整数表示的Id传给模型 -3. 网络结构(由易到难展示4种不同的网络配置) - - 逻辑回归模型 - - 词向量模型 - - 卷积模型 - - 时序模型 - - 优化算法 -4. 训练模型 -5. 预测 - -## 数据格式准备(Data Preparation) -在本问题中,我们使用[Amazon电子产品评论数据](http://jmcauley.ucsd.edu/data/amazon/), -将评论分为好评(正样本)和差评(负样本)两类。[源码](https://github.com/baidu/Paddle)的`demo/quick_start`里提供了数据下载脚本 -和预处理脚本。 - -```bash -cd demo/quick_start -./data/get_data.sh -./preprocess.sh -``` - -## 数据向模型传送(Transfer Data to Model) - -### Python数据加载脚本(Data Provider Script) - -下面dataprovider_bow.py文件给出了完整例子,主要包括两部分: - -* initalizer: 定义文本信息、类别Id的数据类型。 -* process: yield文本信息和类别Id,和initalizer里定义顺序一致。 - -```python -from paddle.trainer.PyDataProvider2 import * - -# id of the word not in dictionary -UNK_IDX = 0 - -# initializer is called by the framework during initialization. -# It allows the user to describe the data types and setup the -# necessary data structure for later use. -# `settings` is an object. initializer need to properly fill settings.input_types. -# initializer can also store other data structures needed to be used at process(). -# In this example, dictionary is stored in settings. -# `dictionay` and `kwargs` are arguments passed from trainer_config.lr.py -def initializer(settings, dictionary, **kwargs): - # Put the word dictionary into settings - settings.word_dict = dictionary - - # setting.input_types specifies what the data types the data provider - # generates. - settings.input_types = [ - # The first input is a sparse_binary_vector, - # which means each dimension of the vector is either 0 or 1. It is the - # bag-of-words (BOW) representation of the texts. - sparse_binary_vector(len(dictionary)), - # The second input is an integer. It represents the category id of the - # sample. 2 means there are two labels in the dataset. - # (1 for positive and 0 for negative) - integer_value(2)] - -# Delaring a data provider. It has an initializer 'data_initialzer'. -# It will cache the generated data of the first pass in memory, so that -# during later pass, no on-the-fly data generation will be needed. -# `setting` is the same object used by initializer() -# `file_name` is the name of a file listed train_list or test_list file given -# to define_py_data_sources2(). See trainer_config.lr.py. -@provider(init_hook=initializer, cache=CacheType.CACHE_PASS_IN_MEM) -def process(settings, file_name): - # Open the input data file. - with open(file_name, 'r') as f: - # Read each line. - for line in f: - # Each line contains the label and text of the comment, separated by \t. - label, comment = line.strip().split('\t') - - # Split the words into a list. - words = comment.split() - - # convert the words into a list of ids by looking them up in word_dict. - word_vector = [settings.word_dict.get(w, UNK_IDX) for w in words] - - # Return the features for the current comment. The first is a list - # of ids representing a 0-1 binary sparse vector of the text, - # the second is the integer id of the label. - yield word_vector, int(label) -``` - -### 配置中的数据加载定义(Data Provider in Configure) - -在模型配置中利用`define_py_data_sources2`加载数据: - -```python -from paddle.trainer_config_helpers import * - -file = "data/dict.txt" -word_dict = dict() -with open(dict_file, 'r') as f: - for i, line in enumerate(f): - w = line.strip().split()[0] - word_dict[w] = i -# define the data sources for the model. -# We need to use different process for training and prediction. -# For training, the input data includes both word IDs and labels. -# For prediction, the input data only includs word Ids. -define_py_data_sources2(train_list='data/train.list', - test_list='data/test.list', - module="dataprovider_bow", - obj="process", - args={"dictionary": word_dict}) -``` -* data/train.list,data/test.list: 指定训练、测试数据 -* module="dataprovider": 数据处理Python文件名 -* obj="process": 指定生成数据的函数 -* args={"dictionary": word_dict}: 额外的参数,这里指定词典 - -更详细数据格式和用例请参考 -PyDataProvider2。 - -## 网络结构(Network Architecture) -本节我们将专注于网络结构的介绍。 -
    ![](./PipelineNetwork.jpg)
    - -我们将以基本的逻辑回归网络作为起点,并逐渐展示更加深入的功能。更详细的网络配置 -连接请参考Layer文档。 -所有配置在[源码](https://github.com/baidu/Paddle)`demo/quick_start`目录,首先列举逻辑回归网络。 - -### 逻辑回归模型(Logistic Regression) - -流程如下: -
    ![](./NetLR.jpg)
    - -- 获取利用one-hot vector表示的每个单词,维度是词典大小 - -```python -word = data_layer(name="word", size=word_dim) -``` - -- 获取该条样本类别Id,维度是类别个数。 - -```python -label = data_layer(name="label", size=label_dim) -``` - -- 利用逻辑回归模型对该向量进行分类,同时会计算分类准确率 - -```python -# Define a fully connected layer with logistic activation (also called softmax activation). -output = fc_layer(input=word, - size=label_dim, - act_type=SoftmaxActivation()) -# Define cross-entropy classification loss and error. -classification_cost(input=output, label=label) -``` - - - input: 除过data层,每个层都有一个或多个input,多个input以list方式输入 - - size: 该层神经元个数 - - act_type: 激活函数类型 - -效果总结:我们将在后面介绍训练和预测的流程的脚本。在此为方便对比不同网络结构, -我们随时总结了各个网络的复杂度和效果。 - - -
    - - - - - - - - - - - - - - - - - -
    网络名称参数数量错误率
    逻辑回归252 KB8.652%
    - -
    - -### 词向量模型(Word Vector) - -embedding模型需要稍微改变数据提供的脚本,即`dataprovider_emb.py`,词向量模型、 -卷积模型、时序模型均使用该脚本。其中文本输入类型定义为整数时序类型integer_value_sequence。 - -``` -def initializer(settings, dictionary, **kwargs): - settings.word_dict = dictionary - settings.input_types = [ - # Define the type of the first input as sequence of integer. - # The value of the integers range from 0 to len(dictrionary)-1 - integer_value_sequence(len(dictionary)), - # Define the second input for label id - integer_value(2)] - -@provider(init_hook=initializer) -def process(settings, file_name): - ... - # omitted, it is same as the data provider for LR model -``` - -该模型依然是使用逻辑回归分类网络的框架, 只是将句子利用连续向量表示替换稀疏 -向量表示, 即对第3步进行替换。句子表示的计算更新为2步: -
    ![](./NetContinuous.jpg)
    - -- 利用单词Id查找对应的该单词的连续表示向量(维度为word_dim), 输入N个单词,输出为N个word_dim维度向量 - -```python -emb = embedding_layer(input=word, size=word_dim) -``` - -- 将该句话包含的所有单词向量求平均得到句子的表示 - -```python -avg = pooling_layer(input=emb, pooling_type=AvgPooling()) -``` - -其它部分和逻辑回归网络结构一致。 -效果总结: - - -
    - - - - - - - - - - - - - - - - - -
    网络名称参数数量错误率
    词向量模型15 MB8.484%
    -
    -
    - -### 卷积模型(Convolution) -卷积网络是一种特殊的从词向量表示到句子表示的方法, 也就是将词向量模型额步 -骤3-2进行进一步演化, 变为3个新的子步骤。 -
    ![](./NetConv.jpg)
    - -文本卷积分为三个步骤: -1. 获取每个单词左右各k个近邻, 拼接成一个新的向量表示; -2. 对该表示进行非线性变换 (例如Sigmoid变换), 成为维度为hidden_dim的新的向量; -3. 在每个维度上取出在该句话新的向量集合上该维度的最大值作为最后的句子表示向量。 这3个子步骤可配置为: - -```python -text_conv = sequence_conv_pool(input=emb, - context_start=k, - context_len=2 * k + 1) -``` - -效果总结: - - -
    - - - - - - - - - - - - - - - - - -
    网络名称参数数量错误率
    卷积模型16 MB5.628%
    -
    - -### 时序模型(Time Sequence) -
    ![](./NetRNN.jpg)
    - -时序模型即为RNN模型, 包括简单的RNN模型、GRU模型、LSTM模型等。 - -- GRU模型配置: - -```python -gru = simple_gru(input=emb, size=gru_size) -``` - -- LSTM模型配置: - -```python -lstm = simple_lstm(input=emb, size=lstm_size) -``` - -针对本问题,我们采用单层LSTM模型,并使用了Dropout,效果总结: - - -
    - - - - - - - - - - - - - - - - - -
    网络名称参数数量错误率
    时序模型16 MB4.812%
    - -
    - -## 优化算法(Optimization Algorithm) -优化算法包括 -Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优化方法,加了L2正则和梯度截断。 - -```python -settings(batch_size=128, - learning_rate=2e-3, - learning_method=AdamOptimizer(), - regularization=L2Regularization(8e-4), - gradient_clipping_threshold=25) -``` - -## 训练模型(Training Model) -在完成了数据和网络结构搭建之后, 我们进入到训练部分。 -
    ![](./PipelineTrain.jpg)
    - -训练脚本:我们将训练的命令行保存在了 `train.sh`文件中。训练时所需设置的主要参数如下: - -```bash -paddle train \ ---config=trainer_config.py \ ---log_period=20 \ ---save_dir=./output \ ---num_passes=15 \ ---use_gpu=false -``` -这里没有介绍多机分布式训练,可以参考分布式训练的demo学习如何进行多机训练。 - -## 预测(Prediction) -可以使用训练好的模型评估带有label的验证集,也可以预测没有label的测试集。 -
    ![](./PipelineTest.jpg)
    - -测试脚本如下,将会测试配置文件中test.list指定的数据。 - -```bash -paddle train \ ---use_gpu=false \ ---job=test \ ---init_model_path=./output/pass-0000x -``` - -可以参考Python API预测 -教程,或其他demo的Python预测过程。也可以通过如下方式预测。 - -预测脚本(`predict.sh`): - -```bash -model="output/pass-00003" -paddle train \ - --config=trainer_config.lstm.py \ - --use_gpu=false \ - --job=test \ - --init_model_path=$model \ - --config_args=is_predict=1 \ - --predict_output_dir=. \ - -mv rank-00000 result.txt -``` -这里以`output/pass-00003`为例进行预测,用户可以根据训练log选择test结果最好的模型来预测。与训练网络配置不同的是:无需label相关的层,指定outputs输出概率层(softmax输出), -指定batch_size=1,数据传输无需label数据,预测数据指定test_list的位置。 - -预测结果以文本的形式保存在`result.txt`中,一行为一个样本,格式如下: - -``` -预测ID;ID为0的概率 ID为1的概率 -预测ID;ID为0的概率 ID为1的概率 -``` - -``` -is_predict = get_config_arg('is_predict', bool, False) -trn = 'data/train.list' if not is_predict else None -tst = 'data/test.list' if not is_predict else 'data/pred.list' -obj = 'process' if not is_predict else 'process_pre' -batch_size = 128 if not is_predict else 1 -if is_predict: - maxid = maxid_layer(output) - outputs([maxid,output]) -else: - label = data_layer(name="label", size=2) - cls = classification_cost(input=output, label=label) - outputs(cls) -``` - -## 总体效果总结(Summary) -这些流程中的数据下载、网络配置、训练脚本在`/demo/quick_start`目录,我们在此总 -结上述网络结构在Amazon-Elec测试集(25k)上的效果: - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    网络名称参数数量错误率配置文件
    逻辑回归模型 252KB 8.652%trainer_config.lr.py
    词向量模型 15MB 8.484%trainer_config.emb.py
    卷积模型 16MB 5.628%trainer_config.cnn.py
    时序模型 16MB 4.812%trainer_config.lstm.py
    -
    -
    - -## 附录(Appendix) -### 命令行参数(Command Line Argument) - -* \--config:网络配置 -* \--save_dir:模型存储路径 -* \--log_period:每隔多少batch打印一次日志 -* \--num_passes:训练轮次,一个pass表示过一遍所有训练样本 -* \--config_args:命令指定的参数会传入网络配置中。 -* \--init_model_path:指定初始化模型路径,可用在测试或训练时指定初始化模型。 - -默认一个pass保存一次模型,也可以通过saving_period_by_batches设置每隔多少batch保存一次模型。 -可以通过show_parameter_stats_period设置打印参数信息等。 -其他参数请参考令行参数文档。 - -### 输出日志(Log) - -``` -TrainerInternal.cpp:160] Batch=20 samples=2560 AvgCost=0.628761 CurrentCost=0.628761 Eval: classification_error_evaluator=0.304297 CurrentEval: classification_error_evaluator=0.304297 -``` -模型训练会看到这样的日志,详细的参数解释如下面表格: -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    名称解释
    Batch=20 表示过了20个batch
    samples=2560 表示过了2560个样本
    AvgCost 每个pass的第0个batch到当前batch所有样本的平均cost
    CurrentCost 当前log_period个batch所有样本的平均cost
    Eval: classification_error_evaluator 每个pass的第0个batch到当前batch所有样本的平均分类错误率
    CurrentEval: classification_error_evaluator 当前log_period个batch所有样本的平均分类错误率
    -
    -
    diff --git a/doc_cn/demo/quick_start/index.rst b/doc_cn/demo/quick_start/index.rst new file mode 100644 index 0000000000..9dabf1f661 --- /dev/null +++ b/doc_cn/demo/quick_start/index.rst @@ -0,0 +1,474 @@ +PaddlePaddle快速入门教程 +======================== + +我们将以 `文本分类问题 `_ 为例, +介绍PaddlePaddle的基本使用方法。 + +安装 +==== + +请参考 `安装教程 <../../build_and_install/index.html>`_ 安装PaddlePaddle。 + +使用概述 +======== + +**文本分类问题**:对于给定的一条文本,我们从提前给定的类别集合中选择其所属类别。 + +比如, 在购物网站上,通过查看买家对某个产品的评价反馈, 评估该产品的质量。 + +- 这个显示器很棒! (好评) +- 用了两个月之后这个显示器屏幕碎了。(差评) + +使用PaddlePaddle, 每一个任务流程都可以被划分为如下五个步骤。 + + .. image:: Pipeline.jpg + :align: center + :scale: 80% + +1. 数据格式准备 + - 本例每行保存一条样本,类别Id和文本信息用 ``Tab`` 间隔,文本中的单词用空格分隔(如果不切词,则字与字之间用空格分隔),例如:``类别Id '\t' 这 个 显 示 器 很 棒 !`` +2. 向系统传送数据 + - PaddlePaddle可以执行用户的python脚本程序来读取各种格式的数据文件。 + - 本例的所有字符都将转换为连续整数表示的Id传给模型。 +3. 描述网络结构和优化算法 + - 本例由易到难展示4种不同的文本分类网络配置:逻辑回归模型,词向量模型,卷积模型,时序模型。 + - 常用优化算法包括Momentum, RMSProp,AdaDelta,AdaGrad,Adam,Adamax等,本例采用Adam优化方法,加了L2正则和梯度截断。 +4. 训练模型 +5. 应用模型 + +数据格式准备 +------------ + +接下来我们将展示如何用PaddlePaddle训练一个文本分类模型,将 `Amazon电子产品评论数据 `_ 分为好评(正样本)和差评(负样本)两种类别。 +`源代码 `_ 的 ``demo/quick_start`` 目录里提供了该数据的下载脚本和预处理脚本。 + +.. code-block:: bash + + cd demo/quick_start + ./data/get_data.sh + ./preprocess.sh + +向系统传送数据 +============== + +Python数据读取脚本 +------------------ + +`DataProvider <../../ui/data_provider/index.html>`_ 是PaddlePaddle负责提供数据的模块。``DataProvider`` 主要职责在于将训练数据传入内存或者显存,让模型能够得到训练更新,其包括两个函数: + +* initializer:PaddlePaddle会在调用读取数据的Python脚本之前,先调用initializer函数。在下面例子里,我们在initialzier函数里初始化词表,并且在随后的读取数据过程中填充词表。 +* process:PaddlePaddle调用process函数来读取数据。每次读取一条数据后,process函数会用yield语句输出这条数据,从而能够被PaddlePaddle 捕获 (harvest)。 + +``dataprovider_bow.py`` 文件给出了完整例子: + +.. code-block:: python + + from paddle.trainer.PyDataProvider2 import * + + # id of the word not in dictionary + UNK_IDX = 0 + + # initializer is called by the framework during initialization. + # It allows the user to describe the data types and setup the + # necessary data structure for later use. + # `settings` is an object. initializer need to properly fill settings.input_types. + # initializer can also store other data structures needed to be used at process(). + # In this example, dictionary is stored in settings. + # `dictionay` and `kwargs` are arguments passed from trainer_config.lr.py + def initializer(settings, dictionary, **kwargs): + # Put the word dictionary into settings + settings.word_dict = dictionary + + # setting.input_types specifies what the data types the data provider + # generates. + settings.input_types = [ + # The first input is a sparse_binary_vector, + # which means each dimension of the vector is either 0 or 1. It is the + # bag-of-words (BOW) representation of the texts. + sparse_binary_vector(len(dictionary)), + # The second input is an integer. It represents the category id of the + # sample. 2 means there are two labels in the dataset. + # (1 for positive and 0 for negative) + integer_value(2)] + + # Delaring a data provider. It has an initializer 'data_initialzer'. + # It will cache the generated data of the first pass in memory, so that + # during later pass, no on-the-fly data generation will be needed. + # `setting` is the same object used by initializer() + # `file_name` is the name of a file listed train_list or test_list file given + # to define_py_data_sources2(). See trainer_config.lr.py. + @provider(init_hook=initializer, cache=CacheType.CACHE_PASS_IN_MEM) + def process(settings, file_name): + # Open the input data file. + with open(file_name, 'r') as f: + # Read each line. + for line in f: + # Each line contains the label and text of the comment, separated by \t. + label, comment = line.strip().split('\t') + + # Split the words into a list. + words = comment.split() + + # convert the words into a list of ids by looking them up in word_dict. + word_vector = [settings.word_dict.get(w, UNK_IDX) for w in words] + + # Return the features for the current comment. The first is a list + # of ids representing a 0-1 binary sparse vector of the text, + # the second is the integer id of the label. + yield word_vector, int(label) + +配置中的数据加载定义 +-------------------- + +在模型配置中通过 ``define_py_data_sources2`` 接口来加载数据: + +.. code-block:: python + + from paddle.trainer_config_helpers import * + + file = "data/dict.txt" + word_dict = dict() + with open(dict_file, 'r') as f: + for i, line in enumerate(f): + w = line.strip().split()[0] + word_dict[w] = i + # define the data sources for the model. + # We need to use different process for training and prediction. + # For training, the input data includes both word IDs and labels. + # For prediction, the input data only includs word Ids. + define_py_data_sources2(train_list='data/train.list', + test_list='data/test.list', + module="dataprovider_bow", + obj="process", + args={"dictionary": word_dict}) + + +以下是对上述数据加载的解释: + +- data/train.list,data/test.list: 指定训练数据和测试数据 +- module="dataprovider_bow": 数据处理的Python脚本文件名 +- obj="process": 指定生成数据的函数 +- args={"dictionary": word_dict}: 额外的参数,这里指定词典 + +更详细数据格式和用例请参考 `PyDataProvider2 <../../ui/data_provider/pydataprovider2.html>`_ 。 + +模型网络结构 +============ + +本小节我们将介绍模型网络结构。 + + .. image:: PipelineNetwork.jpg + :align: center + :scale: 80% + + +我们将以基本的逻辑回归网络作为起点,并逐渐展示更加深入的功能。更详细的网络配置连接请参考 `Layer文档 <../../../doc/layer.html>`_ 。 +所有配置都在 `源代码 `_ 的 ``demo/quick_start`` 目录下。 + +逻辑回归模型 +------------ + +具体流程如下: + + .. image:: NetLR.jpg + :align: center + :scale: 80% + +- 获取利用one-hot vector表示的每个单词,维度是词典大小 + + .. code-block:: python + + word = data_layer(name="word", size=word_dim) + +- 获取该条样本类别Id,维度是类别个数。 + + .. code-block:: python + + label = data_layer(name="label", size=label_dim) + +- 利用逻辑回归模型对该向量进行分类,同时会计算分类准确率 + + .. code-block:: python + + # Define a fully connected layer with logistic activation (also called softmax activation). + output = fc_layer(input=word, + size=label_dim, + act_type=SoftmaxActivation()) + # Define cross-entropy classification loss and error. + classification_cost(input=output, label=label) + + + - input: 除过data层,每个层都有一个或多个input,多个input以list方式输入 + - size: 该层神经元个数 + - act_type: 激活函数类型 + +**效果总结**:我们将在后面介绍训练和预测流程的脚本。在此为方便对比不同网络结构,我们总结了各个网络的复杂度和效果。 + + ===================== =============================== ================= + 网络名称 参数数量 错误率 + ===================== =============================== ================= + 逻辑回归 252 KB 8.652 % + ===================== =============================== ================= + +词向量模型 +---------- + +embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb.py``,词向量模型、 +卷积模型、时序模型均使用该脚本。其中文本输入类型定义为整数时序类型integer_value_sequence。 + +.. code-block:: python + + def initializer(settings, dictionary, **kwargs): + settings.word_dict = dictionary + settings.input_types = [ + # Define the type of the first input as sequence of integer. + # The value of the integers range from 0 to len(dictrionary)-1 + integer_value_sequence(len(dictionary)), + # Define the second input for label id + integer_value(2)] + + @provider(init_hook=initializer) + def process(settings, file_name): + ... + # omitted, it is same as the data provider for LR model + +该模型依然是使用逻辑回归分类网络的框架, 只是将句子利用连续向量表示替换稀疏 +向量表示, 即对第3步进行替换。句子表示的计算更新为2步: + +.. image:: NetContinuous.jpg + :align: center + :scale: 80% + +- 利用单词Id查找对应的该单词的连续表示向量(维度为word_dim), 输入N个单词,输出为N个word_dim维度向量 + + .. code-block:: python + + emb = embedding_layer(input=word, size=word_dim) + +- 将该句话包含的所有单词向量求平均得到句子的表示 + + .. code-block:: python + + avg = pooling_layer(input=emb, pooling_type=AvgPooling()) + +其它部分和逻辑回归网络结构一致。 + +**效果总结:** + + ===================== =============================== ================== + 网络名称 参数数量 错误率 + ===================== =============================== ================== + 词向量模型 15 MB 8.484 % + ===================== =============================== ================== + +卷积模型 +----------- + +卷积网络是一种特殊的从词向量表示到句子表示的方法, 也就是将词向量模型额步 +骤3-2进行进一步演化, 变为3个新的子步骤。 + +.. image:: NetConv.jpg + :align: center + :scale: 80% + +文本卷积分为三个步骤: + +1. 获取每个单词左右各k个近邻, 拼接成一个新的向量表示; + +2. 对该表示进行非线性变换 (例如Sigmoid变换), 成为维度为hidden_dim的新的向量; + +3. 在每个维度上取出在该句话新的向量集合上该维度的最大值作为最后的句子表示向量。 这3个子步骤可配置为: + +.. code-block:: python + + text_conv = sequence_conv_pool(input=emb, + context_start=k, + context_len=2 * k + 1) + +**效果总结:** + + ===================== =============================== ======================== + 网络名称 参数数量 错误率 + ===================== =============================== ======================== + 卷积模型 16 MB 5.628 % + ===================== =============================== ======================== + +时序模型 +---------- + +.. image:: NetRNN.jpg + :align: center + :scale: 80% + +时序模型即为RNN模型, 包括简单的RNN模型、GRU模型、LSTM模型等。 + +- GRU模型配置: + + .. code-block:: python + + gru = simple_gru(input=emb, size=gru_size) + + +- LSTM模型配置: + + .. code-block:: python + + lstm = simple_lstm(input=emb, size=lstm_size) + +针对本问题,我们采用单层LSTM模型,并使用了Dropout,**效果总结:** + + ===================== =============================== ========================= + 网络名称 参数数量 错误率 + ===================== =============================== ========================= + 时序模型 16 MB 4.812 % + ===================== =============================== ========================= + +优化算法 +========= + +`优化算法 <../../../doc/ui/trainer_config_helpers_api.html#module-paddle.trainer_config_helpers.optimizers>`_ 包括 +Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优化方法,加了L2正则和梯度截断。 + +.. code-block:: python + + settings(batch_size=128, + learning_rate=2e-3, + learning_method=AdamOptimizer(), + regularization=L2Regularization(8e-4), + gradient_clipping_threshold=25) + +训练模型 +========= + +在完成了数据和网络结构搭建之后, 我们进入到训练部分。 + +.. image:: PipelineTrain.jpg + :align: center + :scale: 80% + +训练脚本:我们将训练的命令行保存在了 ``train.sh`` 文件中。训练时所需设置的主要参数如下: + + .. code-block:: bash + + paddle train \ + --config=trainer_config.py \ + --log_period=20 \ + --save_dir=./output \ + --num_passes=15 \ + --use_gpu=false + +这里没有介绍多机分布式训练,可以参考 `分布式训练 <../../cluster/index.html>`_ 的demo学习如何进行多机训练。 + +预测 +===== + +可以使用训练好的模型评估带有label的验证集,也可以预测没有label的测试集。 + +.. image:: PipelineTest.jpg + :align: center + :scale: 80% + +测试脚本如下,将会测试配置文件中test.list指定的数据。 + + .. code-block:: bash + + paddle train \ + --use_gpu=false \ + --job=test \ + --init_model_path=./output/pass-0000x + +可以参考 `Python API预测 <../../ui/predict/swig_py_paddle.html>`_ +教程,或其他 `demo <../../demo/index.html>`_ 的Python预测过程。也可以通过如下方式预测。 + +预测脚本(``predict.sh``): + + .. code-block:: bash + + model="output/pass-00003" + paddle train \ + --config=trainer_config.lstm.py \ + --use_gpu=false \ + --job=test \ + --init_model_path=$model \ + --config_args=is_predict=1 \ + --predict_output_dir=. \ + + mv rank-00000 result.txt + +这里以 ``output/pass-00003`` 为例进行预测,用户可以根据训练log选择test结果最好的模型来预测。与训练网络配置不同的是:无需label相关的层,指定outputs输出概率层(softmax输出), +指定batch_size=1,数据传输无需label数据,预测数据指定test_list的位置。 + +预测结果以文本的形式保存在 ``result.txt`` 中,一行为一个样本,格式如下: + + .. code-block:: bash + + 预测ID;ID为0的概率 ID为1的概率 + 预测ID;ID为0的概率 ID为1的概率 + + .. code-block:: python + + is_predict = get_config_arg('is_predict', bool, False) + trn = 'data/train.list' if not is_predict else None + tst = 'data/test.list' if not is_predict else 'data/pred.list' + obj = 'process' if not is_predict else 'process_pre' + batch_size = 128 if not is_predict else 1 + if is_predict: + maxid = maxid_layer(output) + outputs([maxid,output]) + else: + label = data_layer(name="label", size=2) + cls = classification_cost(input=output, label=label) + outputs(cls) + +总体效果总结 +============== + +这些流程中的数据下载、网络配置、训练脚本在 ``/demo/quick_start`` 目录,我们在此总 +结上述网络结构在Amazon-Elec测试集(25k)上的效果: + + ===================== =============================== ============= ================================== + 网络名称 参数数量 错误率 配置文件 + ===================== =============================== ============= ================================== + 逻辑回归模型 252 KB 8.652% trainer_config.lr.py + 词向量模型 15 MB 8.484% trainer_config.emb.py + 卷积模型 16 MB 5.628% trainer_config.cnn.py + 时序模型 16 MB 4.812% trainer_config.lstm.py + ===================== =============================== ============= ================================== + + +附录 +===== + +命令行参数 +---------- + +* \--config:网络配置 +* \--save_dir:模型存储路径 +* \--log_period:每隔多少batch打印一次日志 +* \--num_passes:训练轮次,一个pass表示过一遍所有训练样本 +* \--config_args:命令指定的参数会传入网络配置中。 +* \--init_model_path:指定初始化模型路径,可用在测试或训练时指定初始化模型。 + +默认一个pass保存一次模型,也可以通过saving_period_by_batches设置每隔多少batch保存一次模型。 +可以通过show_parameter_stats_period设置打印参数信息等。 +其他参数请参考 `命令行参数文档 <../../ui/index.html#command-line-argument>`_ 。 + +输出日志 +--------- + +.. code-block:: bash + + TrainerInternal.cpp:160] Batch=20 samples=2560 AvgCost=0.628761 CurrentCost=0.628761 Eval: classification_error_evaluator=0.304297 CurrentEval: classification_error_evaluator=0.304297 + +模型训练会看到这样的日志,详细的参数解释如下面表格: + + =========================================== ========================================================== + 名称 解释 + =========================================== ========================================================== + Batch=20 表示过了20个batch + samples=2560 表示过了2560个样本 + AvgCost 每个pass的第0个batch到当前batch所有样本的平均cost + CurrentCost 当前log_period个batch所有样本的平均cost + Eval: classification_error_evaluator 每个pass的第0个batch到当前batch所有样本的平均分类错误率 + CurrentEval: classification_error_evaluator 当前log_period个batch所有样本的平均分类错误率 + =========================================== ========================================================== -- GitLab From 20aac5bba15d209fdf6ac32dc270e8c83180ebb9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 20 Nov 2016 15:35:40 +0800 Subject: [PATCH 0072/1503] Add style check for *.cc files in cuda directory --- paddle/cuda/CMakeLists.txt | 7 +- paddle/cuda/src/hl_cuda_cublas.cc | 15 ++-- paddle/cuda/src/hl_cuda_cudnn.cc | 143 +++++++++++++----------------- paddle/cuda/src/hl_cuda_device.cc | 12 +-- paddle/cuda/src/hl_cudart_wrap.cc | 26 ++---- paddle/cuda/src/hl_dso_loader.cc | 41 ++++----- 6 files changed, 112 insertions(+), 132 deletions(-) diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index cdb730bb3c..11dbfb54b2 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -81,5 +81,8 @@ else() add_library(paddle_cuda ${CUDA_SOURCES}) endif() -add_style_check_target(paddle_cuda ${CUDA_SOURCES}) -add_style_check_target(paddle_cuda ${CUDA_HEADERS}) +add_style_check_target(paddle_cuda + ${CUDA_SOURCES} + ${CUDA_HEADERS} + ${CUDA_DSO_SOURCES} + ${CUDA_CXX_WITH_GPU_SOURCES}) diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index f16376ec93..abf6afadc2 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -104,7 +104,7 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) #endif const char* hl_cublas_get_error_string(cublasStatus_t status) { - switch(status) { + switch (status) { case CUBLAS_STATUS_NOT_INITIALIZED: return "[cublas status]: not initialized"; case CUBLAS_STATUS_ALLOC_FAILED: @@ -181,7 +181,7 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { real **inout_d = (real **)hl_malloc_device(sizeof(real *)); hl_memcpy(inout_d, inout_h, sizeof(real *)); - int *pivot_d = (int *)hl_malloc_device(dimN*sizeof(int)); + int *pivot_d = (int *)hl_malloc_device(dimN * sizeof(int)); int *info_d = (int *)t_resource.gpu_mem; /* Note: cublasSgetrfBatched is used to calculate a number of @@ -189,10 +189,9 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { the API for better performance. */ CHECK_CUBLAS(CUBLAS_GETRF(t_resource.handle, - dimN, inout_d, lda, pivot_d, - info_d, 1)); + dimN, inout_d, lda, pivot_d, info_d, 1)); - int info_h; + int info_h; hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { LOG(FATAL) << "Factorization of matrix failed: matrix may be singular.\n"; @@ -204,8 +203,8 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { hl_memcpy(out_d, out_h, sizeof(real *)); CHECK_CUBLAS(CUBLAS_GETRI(t_resource.handle, - dimN, (const real **)inout_d, lda, pivot_d, - out_d, ldc, info_d, 1)); + dimN, (const real **)inout_d, lda, pivot_d, + out_d, ldc, info_d, 1)); hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { @@ -215,7 +214,7 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { hl_free_mem_device(inout_d); hl_free_mem_device(pivot_d); hl_free_mem_device(out_d); - + CHECK_SYNC("hl_matrix_inverse failed"); } diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index 92b28e4345..1829fe23ac 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -159,13 +159,11 @@ CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) bool g_is_libcudnn_init = false; int g_cudnn_lib_version = 0; -void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) -{ +void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) { CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(cudnn_desc)); } -void hl_cudnn_init(cudnnHandle_t *cudnn_handle, cudaStream_t stream) -{ +void hl_cudnn_init(cudnnHandle_t *cudnn_handle, cudaStream_t stream) { size_t cudnn_dso_ver = dynload::cudnnGetVersion(); size_t cudnn_dso_major = cudnn_dso_ver / 1000; size_t cudnn_cuh_major = CUDNN_VERSION / 1000; @@ -212,13 +210,18 @@ void hl_conv_workspace(hl_tensor_descriptor input, CHECK_NOTNULL(conv); // Specify workspace limit directly - size_t memoryLimitBytes = (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; + size_t memoryLimitBytes = + (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; // cudnn convolution forward configuration - cudnnTensorDescriptor_t fwd_src_desc = GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t fwd_dest_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnFilterDescriptor_t fwd_filter_desc = GET_FILTER_DESCRIPTOR(filter); - cudnnConvolutionDescriptor_t fwd_conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + cudnnTensorDescriptor_t fwd_src_desc = + GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t fwd_dest_desc = + GET_TENSOR_DESCRIPTOR(output); + cudnnFilterDescriptor_t fwd_filter_desc = + GET_FILTER_DESCRIPTOR(filter); + cudnnConvolutionDescriptor_t fwd_conv_desc = + GET_CONVOLUTION_DESCRIPTOR(conv); CHECK_CUDNN(dynload::cudnnGetConvolutionForwardAlgorithm( t_resource.cudnn_handle, @@ -250,23 +253,23 @@ void hl_conv_workspace(hl_tensor_descriptor input, GET_CONVOLUTION_DESCRIPTOR(conv); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataAlgorithm( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdDataAlgo))); + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdDataAlgo))); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataWorkspaceSize( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - static_cast(*convBwdDataAlgo), - bwdDataLimitBytes)); + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + static_cast(*convBwdDataAlgo), + bwdDataLimitBytes)); // cudnn convolution backward filter configuration cudnnTensorDescriptor_t bwd_filter_src_desc = @@ -279,21 +282,21 @@ void hl_conv_workspace(hl_tensor_descriptor input, GET_FILTER_DESCRIPTOR(filter); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterAlgorithm( - t_resource.cudnn_handle, - bwd_filter_src_desc, - bwd_filter_diff_desc, - bwd_filter_conv_desc, - bwd_filter_grad_desc, - CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdFilterAlgo))); + t_resource.cudnn_handle, + bwd_filter_src_desc, + bwd_filter_diff_desc, + bwd_filter_conv_desc, + bwd_filter_grad_desc, + CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdFilterAlgo))); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterWorkspaceSize( - t_resource.cudnn_handle, bwd_filter_src_desc, - bwd_filter_diff_desc, bwd_filter_conv_desc, - bwd_filter_grad_desc, - static_cast(*convBwdFilterAlgo), - bwdFilterLimitBytes)); + t_resource.cudnn_handle, bwd_filter_src_desc, + bwd_filter_diff_desc, bwd_filter_conv_desc, + bwd_filter_grad_desc, + static_cast(*convBwdFilterAlgo), + bwdFilterLimitBytes)); #endif } @@ -302,8 +305,7 @@ void hl_create_tensor_descriptor(hl_tensor_descriptor* image_desc, int batch_size, int feature_maps, int height, - int width) -{ + int width) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = @@ -359,8 +361,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int batch_size, int feature_maps, int height, - int width) -{ + int width) { const int stride_w = 1; const int stride_h = width * stride_w; const int stride_c = height * stride_h; @@ -384,8 +385,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int nStride, int cStride, int hStride, - int wStride) -{ + int wStride) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; @@ -408,8 +408,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, hl_desc->width = width; } -void hl_destroy_tensor_descriptor(hl_tensor_descriptor image_desc) -{ +void hl_destroy_tensor_descriptor(hl_tensor_descriptor image_desc) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; @@ -430,11 +429,9 @@ void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, int height_padding, int width_padding, int stride_height, - int stride_width) -{ + int stride_width) { cudnnPoolingMode_t cudnn_mode; - switch (mode) - { + switch (mode) { case HL_POOLING_MAX: cudnn_mode = CUDNN_POOLING_MAX; break; @@ -478,13 +475,13 @@ void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, *pooling_desc = (hl_pooling_descriptor)hl_pooling_desc; } -void hl_destroy_pooling_descriptor(hl_pooling_descriptor pooling_desc) -{ +void hl_destroy_pooling_descriptor(hl_pooling_descriptor pooling_desc) { CHECK_NOTNULL(pooling_desc); - cudnn_pooling_descriptor hl_pooling = (cudnn_pooling_descriptor)pooling_desc; - CHECK_NOTNULL(hl_pooling->desc); + cudnn_pooling_descriptor hl_pooling = + (cudnn_pooling_descriptor)pooling_desc; + CHECK_NOTNULL(hl_pooling->desc); CHECK_CUDNN(dynload::cudnnDestroyPoolingDescriptor(hl_pooling->desc)); hl_pooling->desc = NULL; @@ -496,8 +493,7 @@ void hl_pooling_forward(hl_tensor_descriptor input, real* input_image, hl_tensor_descriptor output, real* output_image, - hl_pooling_descriptor pooling) -{ + hl_pooling_descriptor pooling) { cudnnPoolingDescriptor_t pooling_desc; cudnnTensorDescriptor_t input_desc; cudnnTensorDescriptor_t output_desc; @@ -531,8 +527,7 @@ void hl_pooling_backward(hl_tensor_descriptor input, hl_tensor_descriptor output, real* output_image, real* output_image_grad, - hl_pooling_descriptor pooling) -{ + hl_pooling_descriptor pooling) { cudnnPoolingDescriptor_t pooling_desc; cudnnTensorDescriptor_t input_desc; cudnnTensorDescriptor_t output_desc; @@ -571,8 +566,7 @@ void hl_create_filter_descriptor(hl_filter_descriptor* filter, int input_feature_maps, int output_feature_maps, int height, - int width) -{ + int width) { CHECK_NOTNULL(filter); cudnn_filter_descriptor hl_filter = @@ -607,8 +601,7 @@ void hl_create_filter_descriptor(hl_filter_descriptor* filter, } -void hl_destroy_filter_descriptor(hl_filter_descriptor filter) -{ +void hl_destroy_filter_descriptor(hl_filter_descriptor filter) { CHECK_NOTNULL(filter); cudnn_filter_descriptor hl_filter = (cudnn_filter_descriptor)filter; @@ -627,14 +620,13 @@ void hl_create_convolution_descriptor(hl_convolution_descriptor* conv, int padding_height, int padding_width, int stride_height, - int stride_width) -{ + int stride_width) { CHECK_NOTNULL(conv); - cudnn_convolution_descriptor hl_conv = - (cudnn_convolution_descriptor)malloc(sizeof(_cudnn_convolution_descriptor)); - CHECK_NOTNULL(hl_conv); + cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor) + malloc(sizeof(_cudnn_convolution_descriptor)); + CHECK_NOTNULL(hl_conv); CHECK_CUDNN(dynload::cudnnCreateConvolutionDescriptor(&hl_conv->desc)); cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; @@ -667,8 +659,7 @@ void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, int padding_height, int padding_width, int stride_height, - int stride_width) -{ + int stride_width) { CHECK_NOTNULL(conv); CHECK_NOTNULL(image); CHECK_NOTNULL(filter); @@ -697,8 +688,7 @@ void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, hl_conv->mode = mode; } -void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) -{ +void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) { CHECK_NOTNULL(conv); cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; @@ -753,8 +743,7 @@ void hl_convolution_forward(hl_tensor_descriptor input, void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, real* bias_data, hl_tensor_descriptor output, - real* output_data) -{ + real* output_data) { CHECK_NOTNULL(bias); CHECK_NOTNULL(output); CHECK_NOTNULL(bias_data); @@ -782,8 +771,7 @@ void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, void hl_convolution_backward_bias(hl_tensor_descriptor bias, real* bias_grad_data, hl_tensor_descriptor output, - real* output_grad_data) -{ + real* output_grad_data) { CHECK_NOTNULL(bias); CHECK_NOTNULL(output); CHECK_NOTNULL(bias_grad_data); @@ -814,7 +802,6 @@ void hl_convolution_backward_filter(hl_tensor_descriptor input, void* gpuWorkSpace, size_t sizeInBytes, int convBwdFilterAlgo) { - CHECK_NOTNULL(input); CHECK_NOTNULL(output); CHECK_NOTNULL(filter); @@ -889,8 +876,7 @@ void hl_convolution_backward_data(hl_tensor_descriptor input, void hl_softmax_forward(real *input, real *output, int height, - int width) -{ + int width) { #ifndef PADDLE_TYPE_DOUBLE cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else @@ -923,8 +909,7 @@ void hl_softmax_forward(real *input, void hl_softmax_backward(real *output_value, real *output_grad, int height, - int width) -{ + int width) { #ifndef PADDLE_TYPE_DOUBLE cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 3ea2c91bd5..ca19f210c5 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -203,8 +203,8 @@ inline pid_t gettid() { #endif pid_t tid = syscall(__NR_gettid); #endif - CHECK_NE(tid, -1); - return tid; + CHECK_NE((int)tid, -1); + return tid; } void hl_init(int device) { @@ -355,7 +355,8 @@ void* hl_malloc_host(size_t size) { void *dest_h; CHECK(size) << __func__ << ": the size for device memory is 0, please check."; - CHECK_CUDA(dynload::cudaHostAlloc((void**)&dest_h, size, cudaHostAllocDefault)); + CHECK_CUDA(dynload::cudaHostAlloc( + (void**)&dest_h, size, cudaHostAllocDefault)); return dest_h; } @@ -364,7 +365,7 @@ void hl_free_mem_host(void *dest_h) { CHECK_NOTNULL(dest_h); cudaError_t err = dynload::cudaFreeHost(dest_h); - CHECK (cudaSuccess == err || cudaErrorCudartUnloading == err) + CHECK(cudaSuccess == err || cudaErrorCudartUnloading == err) << hl_get_device_error_string(); } @@ -502,7 +503,8 @@ int hl_get_cuda_version() { return g_cuda_lib_version; } -void hl_create_thread_resources(int device, thread_device_resources device_res) { +void hl_create_thread_resources(int device, + thread_device_resources device_res) { CHECK_CUDA(dynload::cudaSetDevice(device)); /* create thread stream */ diff --git a/paddle/cuda/src/hl_cudart_wrap.cc b/paddle/cuda/src/hl_cudart_wrap.cc index 27bbd03bc3..fe755b8c26 100644 --- a/paddle/cuda/src/hl_cudart_wrap.cc +++ b/paddle/cuda/src/hl_cudart_wrap.cc @@ -78,48 +78,38 @@ __host__ cudaError_t CUDARTAPI cudaLaunchKernel(const void *func, dim3 blockDim, void **args, size_t sharedMem, - cudaStream_t stream) -{ - return dynload::cudaLaunchKernel(func, gridDim, blockDim, args, sharedMem, stream); + cudaStream_t stream) { + return dynload::cudaLaunchKernel(func, gridDim, blockDim, + args, sharedMem, stream); } #endif /* CUDART_VERSION >= 7000 */ -__host__ cudaError_t CUDARTAPI cudaLaunch(const void *func) -{ +__host__ cudaError_t CUDARTAPI cudaLaunch(const void *func) { return dynload::cudaLaunch(func); } __host__ cudaError_t CUDARTAPI cudaSetupArgument(const void *arg, size_t size, - size_t offset) -{ + size_t offset) { return dynload::cudaSetupArgument(arg, size, offset); } __host__ cudaError_t CUDARTAPI cudaConfigureCall(dim3 gridDim, dim3 blockDim, size_t sharedMem, - cudaStream_t stream) -{ + cudaStream_t stream) { return dynload::cudaConfigureCall(gridDim, blockDim, sharedMem, stream); } extern "C" { -void** CUDARTAPI __cudaRegisterFatBinary( - void *fatCubin -) -{ +void** CUDARTAPI __cudaRegisterFatBinary(void *fatCubin) { return dynload::__cudaRegisterFatBinary(fatCubin); - } -void CUDARTAPI __cudaUnregisterFatBinary( - void **fatCubinHandle -) -{ +void CUDARTAPI __cudaUnregisterFatBinary(void **fatCubinHandle) { return dynload::__cudaUnregisterFatBinary(fatCubinHandle); } diff --git a/paddle/cuda/src/hl_dso_loader.cc b/paddle/cuda/src/hl_dso_loader.cc index b564b96903..5cb16cfbb3 100644 --- a/paddle/cuda/src/hl_dso_loader.cc +++ b/paddle/cuda/src/hl_dso_loader.cc @@ -19,17 +19,18 @@ limitations under the License. */ P_DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " - "/usr/local/cudnn/lib64. If empty [default], dlopen will search " - "cudnn from LD_LIBRARY_PATH"); + "/usr/local/cudnn/lib64. If empty [default], dlopen " + "will search cudnn from LD_LIBRARY_PATH"); P_DEFINE_string(cuda_dir, "", "Specify path for loading cuda library, such as libcublas, " - "libcurand. For instance, /usr/local/cuda/lib64. " - "(Note: libcudart can not be specified by cuda_dir, since some " + "libcurand. For instance, /usr/local/cuda/lib64. (Note: " + "libcudart can not be specified by cuda_dir, since some " "build-in function in cudart already ran before main entry). " - "If empty [default], dlopen will search cuda from LD_LIBRARY_PATH"); + "If default, dlopen will search cuda from LD_LIBRARY_PATH"); -static inline std::string join(const std::string& part1, const std::string& part2) { +static inline std::string join(const std::string& part1, + const std::string& part2) { // directory separator const char sep = '/'; @@ -49,10 +50,10 @@ static inline std::string join(const std::string& part1, const std::string& part static inline void GetDsoHandleFromDefaultPath( std::string& dso_path, void** dso_handle, int dynload_flags) { VLOG(3) << "Try to find cuda library: " << dso_path - << " from default system path."; - // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH + << " from default system path."; + // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH *dso_handle = dlopen(dso_path.c_str(), dynload_flags); - + // DYLD_LIBRARY_PATH is disabled after Mac OS 10.11 to // bring System Integrity Projection (SIP), if dso_handle // is null, search from default package path in Mac OS. @@ -62,13 +63,13 @@ static inline void GetDsoHandleFromDefaultPath( *dso_handle = dlopen(dso_path.c_str(), dynload_flags); if (nullptr == *dso_handle) { if (dso_path == "libcudnn.dylib") { - LOG(FATAL) << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" - << "For instance, sudo tar -xzf cudnn-7.5-osx-x64-v5.0-ga.tgz -C " - << "/usr/local \n sudo chmod a+r /usr/local/cuda/include/cudnn.h " + LOG(FATAL) << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" // NOLINT + << "For instance, sudo tar -xzf cudnn-7.5-osx-x64-v5.0-ga.tgz -C " // NOLINT + << "/usr/local \n sudo chmod a+r /usr/local/cuda/include/cudnn.h " // NOLINT << "/usr/local/cuda/lib/libcudnn*"; } - } - } + } + } #endif } @@ -96,19 +97,19 @@ static inline void GetDsoHandleFromSearchPath( CHECK(nullptr != *dso_handle) << "Failed to find cuda library: " << dlPath << std::endl - << "Please specify its path correctly using one of the following ideas: \n" + << "Please specify its path correctly using one of the following ways: \n" // NOLINT - << "Idea 1. set cuda and cudnn lib path at runtime. " - << "http://www.paddlepaddle.org/doc/ui/cmd_argument/argument_outline.html \n" + << "Method 1. set cuda and cudnn lib path at runtime. " + << "http://www.paddlepaddle.org/doc/ui/cmd_argument/argument_outline.html \n" // NOLINT << "For instance, issue command: paddle train --use_gpu=1 " - << "--cuda_dir=/usr/local/cudnn/lib --cudnn_dir=/usr/local/cudnn/lib ...\n" + << "--cuda_dir=/usr/local/cuda/lib64 --cudnn_dir=/usr/local/cudnn/lib ...\n" // NOLINT - << "Idea 2. set environment variable LD_LIBRARY_PATH on Linux or " + << "Method 2. set environment variable LD_LIBRARY_PATH on Linux or " << "DYLD_LIBRARY_PATH on Mac OS. \n" << "For instance, issue command: export LD_LIBRARY_PATH=... \n" << "Note: After Mac OS 10.11, using the DYLD_LIBRARY_PATH is impossible " - << "unless System Integrity Protection (SIP) is disabled. However, @Idea 1" + << "unless System Integrity Protection (SIP) is disabled. However, method 1 " // NOLINT << "always work well."; } -- GitLab From a49d1d9529e14638f84c22289b3eff8caf45da80 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sat, 19 Nov 2016 21:37:58 +0800 Subject: [PATCH 0073/1503] Refine the original hrnn documentation. --- doc_cn/algorithm/rnn/hierarchical-rnn.rst | 179 ------------------ doc_cn/algorithm/rnn/hrnn_demo.rst | 2 +- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 98 +++++----- doc_cn/concepts/glossary.rst | 34 ++++ doc_cn/concepts/use_concepts.rst | 2 + doc_cn/conf.py.in | 2 +- paddle/gserver/tests/sequenceGen.py | 20 +- paddle/gserver/tests/sequence_nest_rnn.conf | 5 +- 8 files changed, 98 insertions(+), 244 deletions(-) delete mode 100644 doc_cn/algorithm/rnn/hierarchical-rnn.rst diff --git a/doc_cn/algorithm/rnn/hierarchical-rnn.rst b/doc_cn/algorithm/rnn/hierarchical-rnn.rst deleted file mode 100644 index 7c81ce8c67..0000000000 --- a/doc_cn/algorithm/rnn/hierarchical-rnn.rst +++ /dev/null @@ -1,179 +0,0 @@ -################# -双层RNN配置与示例 -################# - -我们在 :code:`paddle/gserver/tests/test_RecurrentGradientMachine` 单测中,通过多组语义相同的单双层RNN配置,讲解如何使用双层RNN。 - -示例1:双进双出,subseq间无memory -================================= - -配置:单层RNN(:code:`sequence_layer_group`)和双层RNN(:code:`sequence_nest_layer_group`),语义完全相同。 - -读取双层序列的方法 ------------------- - -首先,我们看一下单双层序列的不同数据组织形式(您也可以采用别的组织形式)\: - -- 单层序列的数据( :code:`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 - -.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg - :language: text - - -- 双层序列的数据( :code:`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 - -.. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg.nest - :language: text - -其次,我们看一下单双层序列的不同dataprovider(见 :code:`sequenceGen.py` ): - -- 单层序列的dataprovider如下: - - - word_slot是integer_value_sequence类型,代表单层序列。 - - label是integer_value类型,代表一个向量。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py - :language: python - :lines: 21-39 - -- 双层序列的dataprovider如下: - - - word_slot是integer_value_sub_sequence类型,代表双层序列。 - - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 - - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py - :language: python - :lines: 42-71 - -模型中的配置 ------------- - -首先,我们看一下单层序列的配置(见 :code:`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_layer_group.conf - :language: python - :lines: 38-63 - - -其次,我们看一下语义相同的双层序列配置(见 :code:`sequence_nest_layer_group.conf` ),并对其详细分析: - -- batchsize=2表示一次过2句双层序列。但从上面的数据格式可知,2句双层序列和5句单层序列的数据完全一样。 -- data_layer和embedding_layer不关心数据是否是序列格式,因此两个配置在这两层上的输出是一样的。 -- lstmemory\: - - - 单层序列过了一个mixed_layer和lstmemory_group。 - - 双层序列在同样的mixed_layer和lstmemory_group外,直接加了一层group。由于这个外层group里面没有memory,表示subseq间不存在联系,即起到的作用仅仅是把双层seq拆成单层,因此双层序列过完lstmemory的输出和单层的一样。 - -- last_seq\: - - - 单层序列直接取了最后一个元素 - - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 - - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_layer_group.conf - :language: python - :lines: 38-84 - -示例2:双进双出,subseq间有memory -================================= - -配置:单层RNN( :code:`sequence_rnn.conf` ),双层RNN( :code:`sequence_nest_rnn.conf` 和 :code:`sequence_nest_rnn_readonly_memory.conf` ),语义完全相同。 - -读取双层序列的方法 ------------------- - -我们看一下单双层序列的不同数据组织形式和dataprovider(见 :code:`rnn_data_provider.py`) - -.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py - :language: python - :lines: 20-32 - -- 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 -- 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 -- 单双层序列的label都分别是0和1 - -模型中的配置 ------------- - -我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 - -- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn.conf - :language: python - :lines: 36-48 - -- 双层序列,外层memory是一个元素: - - - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 - - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf - :language: python - :lines: 39-66 - -- 双层序列,外层memory是单层序列: - - - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 - - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 - -示例3:双进双出,输入不等长 -=========================== - -.. role:: red - -.. raw:: html - - - -**输入不等长** 是指recurrent_group的多个输入在各时刻的长度可以不相等, 但需要指定一个和输出长度一致的input,用 :red:`targetInlink` 表示。参考配置:单层RNN(:code:`sequence_rnn_multi_unequalength_inputs.conf`),双层RNN(:code:`sequence_nest_rnn_multi_unequalength_inputs.conf`) - -读取双层序列的方法 ------------------- - -我们看一下单双层序列的数据组织形式和dataprovider(见 :code:`rnn_data_provider.py` ) - -.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py - :language: python - :lines: 69-97 - -data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 - -- 单层序列:两个样本分别为[[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]] 和 [[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]] -- 双层序列:两个样本分别为 - - - **样本1**\:[[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]]]。fea1和fea2都分别有2个子句,fea1=[[1, 2], [4, 5, 2]], fea2=[[5, 4, 1], [3, 1]] - - **样本2**\:[[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]]。fea1和fea2都分别有3个子句, fea1=[[0, 2], [2, 5], [0, 1, 2]], fea2=[[1, 5], [4], [2, 3, 6, 1]]。
    - - **注意**\:每个样本中,各特征的子句数目需要相等。这里说的“双进双出,输入不等长”是指fea1在i时刻的输入的长度可以不等于fea2在i时刻的输入的长度。如对于第1个样本,时刻i=2, fea1[2]=[4, 5, 2],fea2[2]=[3, 1],3≠2。 - -- 单双层序列中,两个样本的label都分别是0和1 - -模型中的配置 ------------- - -单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`v.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 - -- 单层序列\: - - - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf - :language: python - :lines: 41-58 - -- 双层序列\: - - - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 - - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 - - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - -.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf - :language: python - :lines: 41-89 - -示例4:beam_search的生成 -======================== - -TBD diff --git a/doc_cn/algorithm/rnn/hrnn_demo.rst b/doc_cn/algorithm/rnn/hrnn_demo.rst index cf38e416c0..96396ff105 100644 --- a/doc_cn/algorithm/rnn/hrnn_demo.rst +++ b/doc_cn/algorithm/rnn/hrnn_demo.rst @@ -1,4 +1,4 @@ -.. algo_hrnn_demo: +.. _algo_hrnn_demo: ################# 双层RNN的使用示例 diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index cf18108019..8ae0f85b29 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -4,101 +4,99 @@ 单双层RNN API对比介绍 ##################### -这篇教程主要介绍了 :ref:`glossary_双层RNN` 的API接口。本文中的以 :ref:`glossary_paddle` 的 :ref:`glossary_双层RNN` 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用 :ref:`glossary_双层RNN` 。本文中所有的例子,都只是介绍 :ref:`glossary_双层RNN` 的API接口,并不是使用 :ref:`glossary_双层RNN` 解决实际的问题。如果想要了解 :ref:`glossary_双层RNN` 在具体问题中的使用,请参考 :ref:`algo_hrnn_demo` 。文章中示例所使用的单元测试文件是 `test_RecurrentGradientMachine.cpp `_ 。 +这篇教程主要介绍了\ :ref:`glossary_双层RNN`\ 的API接口。本文中的以\ :ref:`glossary_paddle`\ 的\ :ref:`glossary_双层RNN`\ 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用\ :ref:`glossary_双层RNN`\ 。本文中所有的例子,都只是介绍\ :ref:`glossary_双层RNN`\ 的API接口,并不是使用\ :ref:`glossary_双层RNN`\ 解决实际的问题。如果想要了解\ :ref:`glossary_双层RNN`\ 在具体问题中的使用,请参考\ :ref:`algo_hrnn_demo`\ 。文章中示例所使用的单元测试文件是\ `test_RecurrentGradientMachine.cpp `_\ 。 示例1:双层RNN,子序列间无Memory ================================ +在\ :ref:`glossary_双层RNN`\ 中的经典情况是将内层的每一个\ :ref:`glossary_Sequence`\ 数据,分别进行序列操作。并且内层的序列操作之间是独立没有依赖的,即不需要使用\ :ref:`glossary_Memory`\ 的。 +在本问题中,单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 的网络配置,都是将每一句分好词后的句子,使用\ :ref:`glossary_lstm`\ 作为\ :ref:`glossary_encoder`\ ,压缩成一个向量。区别是\ :ref:`glossary_RNN`\ 使用两层序列模型,将多句话看成一个整体,同时使用\ :ref:`glossary_encoder`\ 压缩,二者语意上完全一致。这组语意相同的示例配置如下 -配置:单层RNN(:code:`sequence_layer_group`)和双层RNN(:code:`sequence_nest_layer_group`),语义完全相同。 +* 单层 \:ref:`glossary_RNN`\: `sequence_layer_group.conf `_ +* :ref:`glossary_双层RNN`\: `sequence_nest_layer_group.conf `_ -读取双层序列的方法 ------------------- -首先,我们看一下单双层序列的不同数据组织形式(您也可以采用别的组织形式)\: +读取双层序列数据 +---------------- + +首先,本示例中使用的原始数据如下\: -- 单层序列的数据( :code:`Sequence/tour_train_wdseg`)如下,一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。 +- 本里中的原始数据一共有10个\ :ref:`glossary_sample`\ 。每个\ :ref:`glossary_sample`\ 由两部分组成,一个label(此处都为2)和一个已经分词后的句子。这个数据也被单层\ :ref:`glossary_RNN`\ 网络直接使用。 .. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg :language: text -- 双层序列的数据( :code:`Sequence/tour_train_wdseg.nest`)如下,一共有4个样本。样本间用空行分开,代表不同的双层序列,序列数据和上面的完全一样。每个样本的子句数分别为2,3,2,3。 +- 双层序列数据一共有4个\ :ref:`glossary_sample`\ 。 每个样本间用空行分开,整体数据和原始数据完全一样。而对于双层序列的\ :ref:`glossary_lstm`\ 来说,第一条数据同时\ :ref:`glossary_encode` 两条数据成两个向量。这四条数据同时处理的句子为\ :code:`[2, 3, 2, 3]`\ 。 .. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg.nest :language: text -其次,我们看一下单双层序列的不同dataprovider(见 :code:`sequenceGen.py` ): - -- 单层序列的dataprovider如下: - - - word_slot是integer_value_sequence类型,代表单层序列。 - - label是integer_value类型,代表一个向量。 +其次,对于两种不同的输入数据类型,不同\ :ref:`glossary_DataProvider`\ 对比如下(`sequenceGen.py `_)\: .. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py :language: python :lines: 21-39 + :linenos: -- 双层序列的dataprovider如下: - - - word_slot是integer_value_sub_sequence类型,代表双层序列。 - - label是integer_value_sequence类型,代表单层序列,即一个子句一个label。注意:也可以为integer_value类型,代表一个向量,即一个句子一个label。通常根据任务需求进行不同设置。 - - 关于dataprovider中input_types的详细用法,参见PyDataProvider2。 +- 这是普通的单层\ :ref:`glossary_Sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: + + * :ref:`glossary_DataProvider`\ 共返回两个数据,分别是words和label。即上述代码中的第19行。 + - words是原始数据中的每一句话,所对应的词表index数组。它是integer_value_sequence类型的,即整数数组。words即为这个数据中的单层\ :ref:`glossary_Sequence`\ 。 + - label是原始数据中对于每一句话的分类标签,它是integer_value类型的。 .. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py :language: python :lines: 42-71 + :linenos: -模型中的配置 ------------- +- 这是对于同样的数据,本示例中双层\ :ref:`glossary_Sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: + + - :ref:`glossary_DataProvider`\ 共返回两组数据,分别是sentences和labels。即在双层序列的原始数据中,每一组内的所有句子和labels + - sentences是双层\ :ref:`glossary_Sequence`\ 的数据。他内部包括了每组数据中的所有句子,又使用句子中每一个单词的词表index表示每一个句子,故为双层\ :ref:`glossary_Sequence`\ 。类型为 integer_value_sub_sequence 。 + - labels是每组内每一个句子的标签,故而是一个单层\ :ref:`glossary_Sequence`\ 。 + + +:ref:`glossary_trainer_config`\ 的模型配置 +------------------------------------------ -首先,我们看一下单层序列的配置(见 :code:`sequence_layer_group.conf`)。注意:batchsize=5表示一次过5句单层序列,因此2个batch就可以完成1个pass。 +首先,我们看一下单层\ :ref:`glossary_RNN`\ 的配置。代码中9-15行即为单层RNN序列的使用代码。这里使用了\ :ref:`glossary_paddle`\ 预定义好的\ :ref:`glossary_RNN`\ 处理函数。在这个函数中,\ :ref:`glossary_RNN`\ 对于每一个\ :ref:`glossary_timestep`\ 通过了一个\ :ref:`glossary_lstm`\ 网络。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_layer_group.conf :language: python :lines: 38-63 + :linenos: + :emphasize-lines: 9-15 -其次,我们看一下语义相同的双层序列配置(见 :code:`sequence_nest_layer_group.conf` ),并对其详细分析: +其次,我们看一下语义相同的\ :ref:`glossary_双层RNN`\ 的网络配置。 -- batchsize=2表示一次过2句双层序列。但从上面的数据格式可知,2句双层序列和5句单层序列的数据完全一样。 -- data_layer和embedding_layer不关心数据是否是序列格式,因此两个配置在这两层上的输出是一样的。 -- lstmemory\: +* :ref:`glossary_paddle`\ 中的许多layer并不在意输入是否是\ :ref:`glossary_Sequence`\ ,例如\ :code:`embedding_layer`\ 。在这些layer中,所有的操作都是针对每一个\ :ref:`glossary_timestep`\ 来进行的。 - - 单层序列过了一个mixed_layer和lstmemory_group。 - - 双层序列在同样的mixed_layer和lstmemory_group外,直接加了一层group。由于这个外层group里面没有memory,表示subseq间不存在联系,即起到的作用仅仅是把双层seq拆成单层,因此双层序列过完lstmemory的输出和单层的一样。 +* 在该配置中,7-26行将双层\ :ref:`glossary_Sequence`\ 数据,先变换成单层\ :ref:`glossary_Sequence`\ 数据,在对每一个单层\ :ref:`glossary_Sequence`\ 进行处理。 -- last_seq\: + * 使用\ :code:`recurrent_group`\ 这个函数进行变换,在变换时需要将输入序列传入。由于我们想要的变换是双层\ :ref:`glossary_Sequence`\ => 单层\ :ref:`glossary_Sequence`\ ,所以我们需要将输入数据标记成\ :code:`SubsequenceInput`\ 。 + + * 在本例中,我们将原始数据的每一组,通过\ :code:`recurrent_group`\ 进行拆解,拆解成的每一句话再通过一个\ :ref:`glossary_lstm`\ 网络。这和单层\ :ref:`glossary_RNN`\ 的配置是等价的。 + +* 与单层\ :ref:`glossary_RNN`\ 的配置类似,我们只需要知道使用\ :ref:`glossary_lstm` :ref:`glossary_encode`\ 成的最后一个向量。所以对\ :code:`recurrent_group`\ 进行了\ :code:`last_seq`\ 操作。但是,和单层\ :ref:`glossary_RNN`\ 有区别的地方是,我们是对每一个子序列取最后一个元素。于是我们设置\ :code:`agg_level=AggregateLevel.EACH_SEQUENCE`\ 。 - - 单层序列直接取了最后一个元素 - - 双层序列首先(last_seq层)取了每个subseq的最后一个元素,将其拼接成一个新的单层序列;接着(expand_layer层)将其扩展成一个新的双层序列,其中第i个subseq中的所有向量均为输入的单层序列中的第i个向量;最后(average_layer层)取了每个subseq的平均值。 - - 分析得出:第一个last_seq后,每个subseq的最后一个元素就等于单层序列的最后一个元素,而expand_layer和average_layer后,依然保持每个subseq最后一个元素的值不变(这两层仅是为了展示它们的用法,实际中并不需要)。因此单双层序列的输出是一样旳。 +* 至此,\ :code:`lstm_last`\ 便和单层\ :ref:`glossary_RNN`\ 的配置中的\ :code:`lstm_last`\ 具有相同的结果了。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_layer_group.conf :language: python - :lines: 38-84 - -示例2:双进双出,subseq间有memory -================================= + :lines: 38-64 + :linenos: + :emphasize-lines: 7-26 -配置:单层RNN( :code:`sequence_rnn.conf` ),双层RNN( :code:`sequence_nest_rnn.conf` 和 :code:`sequence_nest_rnn_readonly_memory.conf` ),语义完全相同。 - -读取双层序列的方法 ------------------- +示例2::ref:`glossary_双层RNN`,子序列间有\ :ref:`glossary_Memory` +================================================================== -我们看一下单双层序列的不同数据组织形式和dataprovider(见 :code:`rnn_data_provider.py`) +本示例中,意图使用单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 同时实现一个完全等价的全连接\ :ref:`glossary_RNN`\ 。对于单层\ :ref:`glossary_RNN`\ ,输入数据为一个完整的\ :ref:`glossary_Sequence`\ ,例如\ :code:`[4, 5, 2, 0, 9, 8, 1, 4]`\ 。而对于\ :ref:`glossary_双层RNN`\ ,输入数据为在单层\ :ref:`glossary_RNN`\ 数据里面,任意将一些数据组合成双层\ :ref:`glossary_Sequence`\ ,例如\ :code:`[ [4, 5, 2], [0, 9], [8, 1, 4]]`。 -.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py - :language: python - :lines: 20-32 - -- 单层序列:有两句,分别为[1,3,2,4,5,2]和[0,2,2,5,0,1,2]。 -- 双层序列:有两句,分别为[[1,3,2],[4,5,2]](2个子句)和[[0,2],[2,5],[0,1,2]](3个子句)。 -- 单双层序列的label都分别是0和1 - -模型中的配置 ------------- +:ref:`glossary_trainer_config`\ 的模型配置 +------------------------------------------ 我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 diff --git a/doc_cn/concepts/glossary.rst b/doc_cn/concepts/glossary.rst index a94aa73675..518712d1fe 100644 --- a/doc_cn/concepts/glossary.rst +++ b/doc_cn/concepts/glossary.rst @@ -11,6 +11,33 @@ PaddlePaddle TBD +.. _glossary_encode: + +encode +------ + +参考\ :ref:`glossary_encoder`\ 。 + +.. _glossary_encoder: + +encoder +------- + +TBD + +.. _glossary_sample: + +样本 +---- + +TBD Sample的概念 + +.. _glossary_lstm: + +LSTM +---- + +TBD .. _glossary_memory: @@ -27,6 +54,13 @@ Memory是 :ref:`glossary_paddle` 实现 :ref:`glossary_RNN` 时候使用的一 使用这种方式,:ref:`glossary_paddle` 可以比较简单的判断哪些输出是应该跨越时间步的,哪些不是。 +.. _glossary_timestep: + +时间步 +------ + +参考 :ref:`_glossary_Sequence` 。 + .. _glossary_Sequence: 时间序列 diff --git a/doc_cn/concepts/use_concepts.rst b/doc_cn/concepts/use_concepts.rst index 67e98edabc..73fa78455f 100644 --- a/doc_cn/concepts/use_concepts.rst +++ b/doc_cn/concepts/use_concepts.rst @@ -32,6 +32,7 @@ PaddlePaddle进程内嵌了一个 :code:`python` 解释器。 这个 :code:`pyth 所以,PaddlePaddle单机训练进程,:code:`paddle train` , 对于用户的主要接口语言为 python。 主要需要用户配置的两个文件为 :code:`DataProvider` 和训练文件 :code:`TrainerConfig` 。 +.. _glossary_DataProvider: DataProvider ============ @@ -42,6 +43,7 @@ DataProvider是 :code:`paddle train` 的数据提供器。 它负责将用户的 为了方便用户使用自己的数据格式, PaddlePaddle 提供了 `PyDataProvider`_ 来处理数据。 并且在这个Provider中,PaddlePaddle的 C++ 部分接管了如何shuffle,处理 batch,GPU/CPU通信,双缓冲,异步读取等问题。 用户可以参考 `PyDataProvider`_ 的相关文档,继续深入了解 DataProvider 的使用。 +.. _glossary_trainer_config: 训练文件 ======== diff --git a/doc_cn/conf.py.in b/doc_cn/conf.py.in index 93242ace40..80e5291815 100644 --- a/doc_cn/conf.py.in +++ b/doc_cn/conf.py.in @@ -69,7 +69,7 @@ master_doc = 'index' # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'zh_CN' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/paddle/gserver/tests/sequenceGen.py b/paddle/gserver/tests/sequenceGen.py index fab876fd30..99440ada53 100644 --- a/paddle/gserver/tests/sequenceGen.py +++ b/paddle/gserver/tests/sequenceGen.py @@ -33,10 +33,10 @@ def process(settings, file_name): label, comment = line.strip().split('\t') label = int(''.join(label.split())) words = comment.split() - word_slot = [ + words = [ settings.word_dict[w] for w in words if w in settings.word_dict ] - yield word_slot, label + yield words, label ## for hierarchical sequence network @@ -52,20 +52,20 @@ def hook2(settings, dict_file, **kwargs): @provider(init_hook=hook2, should_shuffle=False) def process2(settings, file_name): with open(file_name) as fdata: - label_list = [] - word_slot_list = [] + labels = [] + sentences = [] for line in fdata: if (len(line)) > 1: label, comment = line.strip().split('\t') label = int(''.join(label.split())) words = comment.split() - word_slot = [ + words = [ settings.word_dict[w] for w in words if w in settings.word_dict ] - label_list.append(label) - word_slot_list.append(word_slot) + labels.append(label) + sentences.append(words) else: - yield word_slot_list, label_list - label_list = [] - word_slot_list = [] + yield sentences, labels + labels = [] + sentences = [] diff --git a/paddle/gserver/tests/sequence_nest_rnn.conf b/paddle/gserver/tests/sequence_nest_rnn.conf index 93b08eb2f8..524760be76 100644 --- a/paddle/gserver/tests/sequence_nest_rnn.conf +++ b/paddle/gserver/tests/sequence_nest_rnn.conf @@ -55,9 +55,8 @@ def outer_step(x): input=x) last = last_seq(input=inner_rnn_output, name="outer_rnn_state") - # "return last" should also work. But currently RecurrentGradientMachine - # does not handle it, and will report error: In hierachical RNN, all out - # links should be from sequences now. + # "return last" won't work, because recurrent_group only support the input + # sequence type is same as return sequence type. return inner_rnn_output out = recurrent_group( -- GitLab From a146fcf8ad37c111cd0fc44378dd6ca6804b3dfe Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 20 Nov 2016 21:25:25 +0800 Subject: [PATCH 0074/1503] stash --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index 8ae0f85b29..eea220c043 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -98,23 +98,28 @@ :ref:`glossary_trainer_config`\ 的模型配置 ------------------------------------------ -我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 - -- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 +本例配置了两个完全等价的全连接\ :ref:`glossary_RNN`\ 。对于单层序列模型的配置如下: .. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn.conf :language: python :lines: 36-48 + :linenos: -- 双层序列,外层memory是一个元素: +在该配置中,名称为\ :code:`rnn_state`\ 的全连接层暂存到了\ :ref:`glossary_Memory`\ 中。这个\ :ref:`glossary_Memory`\ 变量\ :code:`mem`\ 中可以保存到上一个\ :ref:`glossary_timestep`\ 中的全连接层的输出。从而实现一个全连接的\ :ref:`glossary_RNN`\ 。 - - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 - - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 +而对于\ :ref:`glossary_双层RNN`\ 来说,等价的网络配置如下\: .. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf :language: python :lines: 39-66 +- 双层序列,外层memory是一个元素: + + - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 + - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 + + + - 双层序列,外层memory是单层序列: - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 -- GitLab From 20600e70363b0cfa2568b6adc0dae3f3d349b40f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 20 Nov 2016 22:12:32 +0800 Subject: [PATCH 0075/1503] Fix several cpp issues * Different Type compare. * ostream << should pass a const object. * remove always true checks. --- .../gserver/evaluators/CTCErrorEvaluator.cpp | 2 +- paddle/gserver/evaluators/ChunkEvaluator.cpp | 2 +- paddle/gserver/evaluators/Evaluator.cpp | 6 +++--- paddle/gserver/evaluators/Evaluator.h | 16 +++++++-------- .../gserver/gradientmachines/MultiNetwork.cpp | 2 +- .../gradientmachines/NeuralNetwork.cpp | 2 +- paddle/math/BaseMatrix.cu | 20 +++++++++---------- paddle/math/Vector.cpp | 6 +++--- paddle/pserver/ParameterServer2.cpp | 2 -- paddle/utils/BarrierStat.cpp | 10 +++++----- paddle/utils/BarrierStat.h | 11 +++++----- paddle/utils/CompilerMacros.h | 17 ++++++++++++++++ paddle/utils/Logging.cpp | 4 ++-- paddle/utils/Logging.h | 3 ++- 14 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 paddle/utils/CompilerMacros.h diff --git a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp index e397c71c87..c2625bce9a 100644 --- a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp +++ b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp @@ -240,7 +240,7 @@ public: seqClassficationError_ = 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << (numSequences_ ? totalScore_ / numSequences_ : 0); os << " deletions error" diff --git a/paddle/gserver/evaluators/ChunkEvaluator.cpp b/paddle/gserver/evaluators/ChunkEvaluator.cpp index 22579891f3..6f5d2b47c3 100644 --- a/paddle/gserver/evaluators/ChunkEvaluator.cpp +++ b/paddle/gserver/evaluators/ChunkEvaluator.cpp @@ -114,7 +114,7 @@ public: numCorrect_ = 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { double precision = (double)numCorrect_ / numOutputSegments_; double recall = (double)numCorrect_ / numLabelSegments_; double f1 = diff --git a/paddle/gserver/evaluators/Evaluator.cpp b/paddle/gserver/evaluators/Evaluator.cpp index 7bdcdaae53..d43dceea74 100644 --- a/paddle/gserver/evaluators/Evaluator.cpp +++ b/paddle/gserver/evaluators/Evaluator.cpp @@ -315,7 +315,7 @@ public: return 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { CHECK(colIdx_ + (int32_t)colNum_ >= 0 && colIdx_ - (int32_t)colNum_ < 0) << "column index [" << colIdx_ << "] out of range [-" << colNum_ << ", " << colNum_ << ")"; @@ -421,7 +421,7 @@ void AucEvaluator::distributeEval(ParameterClient2* client) { client->reduce(statNeg_, statNeg_, kBinNum_ + 1, FLAGS_trainer_id, 0); } -double AucEvaluator::calcAuc() { +double AucEvaluator::calcAuc() const { double totPos = 0.0; double totNeg = 0.0; double totPosPrev = 0.0; @@ -584,7 +584,7 @@ real PrecisionRecallEvaluator::evalImp(std::vector& arguments) { return 0; } -void PrecisionRecallEvaluator::printStats(std::ostream& os) { +void PrecisionRecallEvaluator::printStats(std::ostream& os) const { int label = config_.positive_label(); if (label != -1) { CHECK(label >= 0 && label < (int)statsInfo_.size()) diff --git a/paddle/gserver/evaluators/Evaluator.h b/paddle/gserver/evaluators/Evaluator.h index b79a539384..e9957a5ce2 100644 --- a/paddle/gserver/evaluators/Evaluator.h +++ b/paddle/gserver/evaluators/Evaluator.h @@ -99,19 +99,19 @@ public: * @brief print the statistics of evaluate result * @note finish() should be called before printStats */ - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << (numSamples_ ? totalScore_ / numSamples_ : 0); } friend std::ostream& operator<<(std::ostream& os, - Evaluator& evaluator) { + const Evaluator& evaluator) { evaluator.printStats(os); return os; } friend std::ostream&& operator<<(std::ostream&& os, // NOLINT - Evaluator& evaluator) { + const Evaluator& evaluator) { evaluator.printStats(os); return std::move(os); } @@ -135,7 +135,7 @@ public: return -1; } virtual void finish() {} - virtual void printStats(std::ostream&) {} + virtual void printStats(std::ostream&) const {} }; /** * @brief evaluate AUC using colIdx-th column as prediction. @@ -165,7 +165,7 @@ public: virtual real evalImp(std::vector& arguments); - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << calcAuc(); } @@ -189,7 +189,7 @@ private: return (X1 > X2 ? (X1 - X2) : (X2 - X1)) * (Y1 + Y2) / 2.0; } - double calcAuc(); + double calcAuc() const; }; /** @@ -244,7 +244,7 @@ public: virtual real evalImp(std::vector& arguments); - virtual void printStats(std::ostream& os); + virtual void printStats(std::ostream& os) const; virtual void distributeEval(ParameterClient2* client); @@ -339,7 +339,7 @@ public: virtual void finish() { calc(predictArray_); } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << " pos/neg" << "=" << pairArray_[0] / ((pairArray_[1] <= 0) ? 1.0 : pairArray_[1]); } diff --git a/paddle/gserver/gradientmachines/MultiNetwork.cpp b/paddle/gserver/gradientmachines/MultiNetwork.cpp index d30ca6f28e..b85d2e0c99 100644 --- a/paddle/gserver/gradientmachines/MultiNetwork.cpp +++ b/paddle/gserver/gradientmachines/MultiNetwork.cpp @@ -154,7 +154,7 @@ public: return -1; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { for (auto& evaluator : evaluators_) { evaluator->printStats(os); os << ' '; diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index 3127b4dd9a..c77b00eb06 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -325,7 +325,7 @@ public: (void)arguments; return -1; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { for (auto& evaluator : evaluators_) { evaluator->printStats(os); os << ' '; diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 54448bdb5a..2afb216db5 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1449,8 +1449,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1463,8 +1463,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, @@ -1493,8 +1493,8 @@ template int BaseMatrixT::applyRow(Agg agg, Op op, Saver sv, BaseMatrixT& b, BaseMatrixT& c) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); CHECK_EQ(c.height_, numRows); @@ -1524,8 +1524,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1538,8 +1538,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index 23c9caccea..9ef7f2b4b5 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -82,8 +82,8 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { template <> MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { - int height = getSize(); - int width = idRange; + auto height = getSize(); + auto width = idRange; MatrixPtr mat = Matrix::createSparseMatrix( height, idRange, height, NO_VALUE, SPARSE_CSR, false, useGpu); @@ -91,7 +91,7 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { cpuIds.copyFrom(*this); int *idData = cpuIds.getData(); - for (int i = 0; i < height; i ++) { + for (decltype(height) i = 0; i < height; i ++) { const unsigned int id = idData[i]; CHECK_LT(id, width); mat->setRow(i, 1, &id, nullptr); diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index c8f37d0bf4..960fca2853 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -1469,7 +1469,6 @@ void ParameterServer2::waitPassFinish(const WaitPassFinishRequest& request, void ParameterServer2::synchronize(const SynchronizeRequest& request, ProtoResponseCallback callback) { - CHECK_LT(request.sync_object_id(), SyncObject_ARRAYSIZE); synchronizeBarriers_[request.sync_object_id()]->wait(); dataSize_ = 0; callback(SynchronizeResponse()); @@ -1477,7 +1476,6 @@ void ParameterServer2::synchronize(const SynchronizeRequest& request, void ParameterServer2::asyncFinishPass(const SynchronizeRequest& request, ProtoResponseCallback callback) { - CHECK_LT(request.sync_object_id(), SyncObject_ARRAYSIZE); synchronizeBarriers_[request.sync_object_id()]->wait(); callback(SynchronizeResponse()); diff --git a/paddle/utils/BarrierStat.cpp b/paddle/utils/BarrierStat.cpp index cbc738a839..f083ef3982 100644 --- a/paddle/utils/BarrierStat.cpp +++ b/paddle/utils/BarrierStat.cpp @@ -29,10 +29,10 @@ P_DEFINE_bool(log_barrier_show_log, false, // for performance tuning insight namespace paddle { -std::ostream &operator<<(std::ostream &output, BarrierStatBase &stat) { +std::ostream &operator<<(std::ostream &output, + const BarrierStatBase &stat) { if (FLAGS_log_barrier_abstract) { - std::lock_guard guard( - const_cast(stat).lock_); + std::lock_guard guard(stat.lock_); stat.showAbstract(output); } return output; @@ -136,7 +136,7 @@ void BarrierEndStat::reset(bool clearRawData) { totAbstract_.minDelta = UINT64_MAX; } -void BarrierEndStat::showAbstract(std::ostream &output) { +void BarrierEndStat::showAbstract(std::ostream &output) const { // do not support the case "<=2 pserver" if (numConnThreads_ <= 2 || !totSamples_) { return; @@ -272,7 +272,7 @@ void BarrierDeltaStat::reset(bool clearRawData) { totAbstract_.minDelta = UINT64_MAX; } -void BarrierDeltaStat::showAbstract(std::ostream &output) { +void BarrierDeltaStat::showAbstract(std::ostream &output) const { // do not support the case "<=2 pserver" if (numConnThreads_ <= 2 || !totSamples_) { return; diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h index 22d6cc9bce..add1093758 100644 --- a/paddle/utils/BarrierStat.h +++ b/paddle/utils/BarrierStat.h @@ -218,11 +218,12 @@ public: } protected: - virtual void showAbstract(std::ostream &output) {} - friend std::ostream &operator<<(std::ostream &output, BarrierStatBase &stat); + virtual void showAbstract(std::ostream &output) const {} + friend std::ostream &operator<<(std::ostream &output, + const BarrierStatBase &stat); protected: - std::mutex lock_; + mutable std::mutex lock_; std::mutex abstractLock_; // see note on updaterStat // each freqency for each barrier trainer std::vector abstract_; @@ -262,7 +263,7 @@ protected: * log_barrier_abstract, log_barrier_lowest_nodes, log_barrier_threshold * control details. */ - virtual void showAbstract(std::ostream &output); + virtual void showAbstract(std::ostream &output) const; private: std::unique_ptr timeVector_; @@ -286,7 +287,7 @@ public: virtual bool checkPassBarrier() { return timeVector_->empty(); } protected: - virtual void showAbstract(std::ostream &outPut); + virtual void showAbstract(std::ostream &outPut) const; private: // store delta time in uint64_t, eg BP time of all trainers diff --git a/paddle/utils/CompilerMacros.h b/paddle/utils/CompilerMacros.h new file mode 100644 index 0000000000..4236d750c4 --- /dev/null +++ b/paddle/utils/CompilerMacros.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 + +#define ATTR_NORETURN __attribute__((noreturn)) diff --git a/paddle/utils/Logging.cpp b/paddle/utils/Logging.cpp index a0644940b5..9a6b1f2d83 100644 --- a/paddle/utils/Logging.cpp +++ b/paddle/utils/Logging.cpp @@ -134,7 +134,7 @@ static void initializeLogFds(char* argv0) { gLogInited = true; } -static void (*gFailureFunctionPtr)() __attribute__((noreturn)) = abort; +static void (*gFailureFunctionPtr)() ATTR_NORETURN = abort; LogMessage::LogMessage(const char* fname, int line, int severity) : fname_(fname), line_(line), severity_(severity) {} @@ -171,7 +171,7 @@ void setMinLogLevel(int level) { paddle::internal::gMinLogLevel = level; } -void installFailureFunction(void (*callback)()) { +void installFailureFunction(void (*callback)() ATTR_NORETURN) { paddle::internal::gFailureFunctionPtr = callback; } diff --git a/paddle/utils/Logging.h b/paddle/utils/Logging.h index 7fdfa3240c..46b6a7feeb 100644 --- a/paddle/utils/Logging.h +++ b/paddle/utils/Logging.h @@ -23,6 +23,7 @@ limitations under the License. */ #include #ifndef PADDLE_USE_GLOG +#include "CompilerMacros.h" //! TODO(yuyang18): Move this utility macro into some global header. #define PP_CAT(a, b) PP_CAT_I(a, b) @@ -168,7 +169,7 @@ void setMinLogLevel(int level); * @brief Install Log(Fatal) failure function. Default is abort(); * @param callback: The failure function. */ -void installFailureFunction(void (*callback)()); +void installFailureFunction(void (*callback)() ATTR_NORETURN); /** * @brief installFailureWriter -- GitLab From 954723ef775519827176a243fe922354dc41f355 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Mon, 21 Nov 2016 10:19:59 +0800 Subject: [PATCH 0076/1503] add test config --- demo/semantic_role_labeling/train.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/semantic_role_labeling/train.sh b/demo/semantic_role_labeling/train.sh index a0fc372a0c..420768bb2b 100644 --- a/demo/semantic_role_labeling/train.sh +++ b/demo/semantic_role_labeling/train.sh @@ -25,4 +25,5 @@ paddle train \ --average_test_period=10000000 \ --init_model_path=./data \ --load_missing_parameter_strategy=rand \ -2>&1 | tee 'train.log' + --test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' -- GitLab From 5f97bc751f1b56a8a0620e53d96400732017e324 Mon Sep 17 00:00:00 2001 From: zhangjcqq <664122220@qq.com> Date: Mon, 21 Nov 2016 10:22:14 +0800 Subject: [PATCH 0077/1503] revise test config --- doc/demo/semantic_role_labeling/semantic_role_labeling.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/demo/semantic_role_labeling/semantic_role_labeling.md index add2e54fad..e2793b2b34 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/demo/semantic_role_labeling/semantic_role_labeling.md @@ -129,6 +129,7 @@ paddle train \ --average_test_period=10000000 \ --init_model_path=./data \ --load_missing_parameter_strategy=rand \ + --test_all_data_in_one_period=1 \ 2>&1 | tee 'train.log' ``` @@ -142,6 +143,7 @@ paddle train \ - \--average_test_period=10000000: do test on average parameter every average_test_period batches - \--init_model_path=./data: parameter initialization path - \--load_missing_parameter_strategy=rand: random initialization unexisted parameters +- \--test_all_data_in_one_period=1: test all data in one period After training, the models will be saved in directory `output`. Our training curve is as following: -- GitLab From 8393c19ccf19559493a38f14842d187855b0d0e9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 21 Nov 2016 13:44:41 +0800 Subject: [PATCH 0078/1503] Add recursive mutex and counter for gpu profiler --- doc/optimization/gpu_profiling.rst | 3 ++- doc/optimization/nvprof.png | Bin 487751 -> 0 bytes paddle/math/tests/test_GpuProfiler.cpp | 24 ++++++++++++++++-------- paddle/utils/Stat.cpp | 18 ++++++++++++++++++ paddle/utils/Stat.h | 12 ++++++------ 5 files changed, 42 insertions(+), 15 deletions(-) delete mode 100644 doc/optimization/nvprof.png diff --git a/doc/optimization/gpu_profiling.rst b/doc/optimization/gpu_profiling.rst index 013edb396e..44ecb34885 100644 --- a/doc/optimization/gpu_profiling.rst +++ b/doc/optimization/gpu_profiling.rst @@ -24,7 +24,7 @@ Why we need profiling? ====================== Since training deep neural network typically take a very long time to get over, performance is gradually becoming the most important thing in deep learning field. The first step to improve performance is to understand what parts -are slow. No point in improving performance of a region which doesn’t take much time! +are slow. There is no point in improving performance of a region which doesn’t take much time! How to do profiling? @@ -59,6 +59,7 @@ above profilers. The above code snippet includes two methods, you can use any of them to profile the regions of interest. 1. :code:`REGISTER_TIMER_INFO` is a built-in timer wrapper which can calculate the time overhead of both cpu functions and cuda kernels. + 2. :code:`REGISTER_GPU_PROFILER` is a general purpose wrapper object of :code:`cudaProfilerStart` and :code:`cudaProfilerStop` to avoid program crashes when CPU version of PaddlePaddle invokes them. diff --git a/doc/optimization/nvprof.png b/doc/optimization/nvprof.png deleted file mode 100644 index 5931a9b7dc43e6438c9c2105020f59eb3367f0d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 487751 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 pzq<3cmM^og8YTH*iGmRmF%P>qB{r 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�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 inputGpu->copyFrom(*input); - target->bilinearForward(*input, imgSizeH, imgSizeW, - 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); - targetGpu->bilinearForward(*inputGpu, imgSizeH, imgSizeW, - 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + { + // nvprof: GPU Proflier + REGISTER_GPU_PROFILER("testBilinearFwdBwd"); + target->bilinearForward(*input, imgSizeH, imgSizeW, + 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + targetGpu->bilinearForward(*inputGpu, imgSizeH, imgSizeW, + 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + } // check targetCheck->copyFrom(*targetGpu); @@ -104,25 +108,29 @@ void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, MatrixCheckErr(*inputGrad, *targetCheckGrad); } -TEST(Profiler, BilinearFwdBwd) { +TEST(Profiler, testBilinearFwdBwd) { auto numSamples = 10; auto channels = 16; auto imgSize = 64; { // nvprof: GPU Proflier - REGISTER_GPU_PROFILER("testBilinearFwdBwd", - "numSamples = 10, channels = 16, imgSizeX = 64, imgSizeY = 64"); + REGISTER_GPU_PROFILER("testBilinearFwdBwd"); // Paddle built-in timer REGISTER_TIMER_INFO("testBilinearFwdBwd", "numSamples = 10, channels = 16, imgSizeX = 64, imgSizeY = 64"); testBilinearFwdBwd(numSamples, imgSize, imgSize, channels); } - globalStat.printStatus("testBilinearFwdBwd"); + globalStat.printAllStatus(); } int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); + + // nvprof: GPU Proflier + REGISTER_GPU_PROFILER("RecursiveProfilingTest", + "numSamples = 10, channels = 16, imgSizeX = 64, imgSizeY = 64"); + return RUN_ALL_TESTS(); } diff --git a/paddle/utils/Stat.cpp b/paddle/utils/Stat.cpp index 733fc7f1a7..ab140c3350 100644 --- a/paddle/utils/Stat.cpp +++ b/paddle/utils/Stat.cpp @@ -203,4 +203,22 @@ StatInfo::~StatInfo() { } } +static unsigned g_profileCount = 0; +static std::recursive_mutex g_profileMutex; + +GpuProfiler::GpuProfiler(std::string statName, std::string info) + : guard_(g_profileMutex) { + if (++g_profileCount == 1) { + LOG(INFO) << "Enable GPU Profiler Stat: [" + << statName << "] " << info; + hl_profiler_start(); + } +} + +GpuProfiler::~GpuProfiler() { + if (--g_profileCount == 0) { + hl_profiler_end(); + } +} + } // namespace paddle diff --git a/paddle/utils/Stat.h b/paddle/utils/Stat.h index 8f65abb2d0..8bfe42a694 100644 --- a/paddle/utils/Stat.h +++ b/paddle/utils/Stat.h @@ -283,8 +283,10 @@ inline StatSet& registerTimerArg2(uint64_t threshold = -1, class GpuProfiler final { public: - GpuProfiler() { hl_profiler_start(); } - ~GpuProfiler() { hl_profiler_end(); } + GpuProfiler(std::string statName, std::string info); + ~GpuProfiler(); +private: + std::lock_guard guard_; }; #ifdef PADDLE_DISABLE_PROFILER @@ -293,10 +295,8 @@ public: #else -#define REGISTER_GPU_PROFILER(statName, ...) \ - LOG(INFO) << "Enable GPU Profiler Stat: [" \ - << statName << "] " << #__VA_ARGS__; \ - GpuProfiler __gpuProfiler; +#define REGISTER_GPU_PROFILER(statName, ...) \ + GpuProfiler __gpuProfiler(statName, #__VA_ARGS__); #endif // DISABLE_PROFILER -- GitLab From 600d65d85ace12dc33583133d8814f00a9f16653 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 21 Nov 2016 14:51:18 +0800 Subject: [PATCH 0079/1503] Revise badge status * because baidu/paddle transfered to paddlepaddle/paddle --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a309ff3734..48b9eb1162 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,15 @@ # PaddlePaddle -[![Build Status](https://travis-ci.org/baidu/Paddle.svg?branch=master)](https://travis-ci.org/baidu/Paddle) -[![Downloads](https://img.shields.io/github/downloads/baidu/Paddle/total.svg)](https://github.com/baidu/Paddle/releases) -[![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) - +[![Build Status](https://travis-ci.org/PaddlePaddle/Paddle.svg?branch=develop)](https://travis-ci.org/baidu/Paddle) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) [![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) -[![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) - -[![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) +[![Coverage Status](https://coveralls.io/repos/github/PaddlePaddle/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) +[![Release](https://img.shields.io/github/release/baidu/Paddle.svg?colorB=fedcba)](https://github.com/baidu/Paddle/releases) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) Welcome to the PaddlePaddle GitHub. - PaddlePaddle (PArallel Distributed Deep LEarning) is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep -- GitLab From 026da4a937def2820675d039be47ee1b0aca7023 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 21 Nov 2016 14:54:24 +0800 Subject: [PATCH 0080/1503] Revise Readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 48b9eb1162..38e7fcc506 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Welcome to the PaddlePaddle GitHub. + PaddlePaddle (PArallel Distributed Deep LEarning) is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep -- GitLab From 4fcf01a849743965b8fb49db9173217e4aed2dfc Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 21 Nov 2016 16:36:07 +0800 Subject: [PATCH 0081/1503] Refine code, found a bad design --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 20 ++++++++----- .../simple_full_hierarchical_recurrent.dot | 30 +++++++++++++++++++ .../algorithm/rnn/simple_full_recurrent.dot | 19 ++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 doc_cn/algorithm/rnn/simple_full_hierarchical_recurrent.dot create mode 100644 doc_cn/algorithm/rnn/simple_full_recurrent.dot diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index eea220c043..09172c53f7 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -107,23 +107,29 @@ 在该配置中,名称为\ :code:`rnn_state`\ 的全连接层暂存到了\ :ref:`glossary_Memory`\ 中。这个\ :ref:`glossary_Memory`\ 变量\ :code:`mem`\ 中可以保存到上一个\ :ref:`glossary_timestep`\ 中的全连接层的输出。从而实现一个全连接的\ :ref:`glossary_RNN`\ 。 +以数据\ :code:`[4, 5, 2, 0, 9, 8, 1, 4]`\ 举例,单层\ :ref:`glossary_RNN`\ 的网络图如下\: + +.. graphviz:: simple_full_recurrent.dot + 而对于\ :ref:`glossary_双层RNN`\ 来说,等价的网络配置如下\: .. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf :language: python :lines: 39-66 + :linenos: + :emphasize-lines: 4-6 -- 双层序列,外层memory是一个元素: - - - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 - - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 +- 在该配置中,外层的\ :code:`outer_mem`\ 和内层的\ :code:`inner_mem`\ 两个变量配合,实现了和单层\ :ref:`glossary_RNN`\ 等价的全连接\ :ref:`glossary_RNN`\ 。 + - 外层\ :code:`outer_step`\ 中的\ :code:`outer_mem`\ 会将神经网络中每个子序列的最后一个结果记录下来。即将第18行的\ :code:`last`\ 变量记录下来。 + - 内层\ :code:`inner_step`\ 中的\ :code:`inner_mem`\ 会将神经网络中子序列中的每一个元素的结果记录下来。即将第7行的\ :code:`out`\ 变量记录下来。 + - 内层的\ :code:`inner_mem`\ 初始值是\ :code:`outer_mem`(:code:`boot_layer`)。于是前一个子序列的最后结果,是新的子序列的初试结果。即完成了简单的全连接\ :code:`glossary_RNN`\ 。 +本例中的\ :ref:`glossary_双层RNN`\ ,以数据\ :code:`[ [4, 5, 2], [0, 9], [8, 1, 4]]`\ 举例,配置图如下\: -- 双层序列,外层memory是单层序列: +.. graphviz:: simple_full_hierarchical_recurrent.dot - - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 - - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 +这里有一点注意事项,Paddle目前实现的\ :ref:`glossary_双层RNN`\ 不完全支持内层\ :ref:`glossary_RNN`\ 的\ :ref:`glossary_Memory`\ 引用外层\ :ref:`glossary_RNN`\ 的某一层序列输入。即\ :code:`inner_mem`的\ :code:`boot_layer`\ 需要是非序列类型的,或者可以是序列类型,但是每个时间步下,序列长度是一致的。从序列类型转换为非序列类型,可以使用\ :code:`pooling_layer`, :code:`last_seq`, :code:`first_seq`\ 等操作进行转换。 示例3:双进双出,输入不等长 =========================== diff --git a/doc_cn/algorithm/rnn/simple_full_hierarchical_recurrent.dot b/doc_cn/algorithm/rnn/simple_full_hierarchical_recurrent.dot new file mode 100644 index 0000000000..ff278a0323 --- /dev/null +++ b/doc_cn/algorithm/rnn/simple_full_hierarchical_recurrent.dot @@ -0,0 +1,30 @@ +digraph G { + rankdir=LR; + + subgraph cluster_t0 { + a [label="4"] + b [label="5"] + c [label="2"] + } + + subgraph cluster_t1 { + d [label="0"] + e [label="9"] + } + + subgraph cluster_t2 { + f [label="8"] + g [label="1"] + h [label="4"] + } + + a -> b; + b -> c; + c -> d [constraint=false]; + + d -> e; + e -> f [constraint=false]; + + f -> g; + g -> h; +} \ No newline at end of file diff --git a/doc_cn/algorithm/rnn/simple_full_recurrent.dot b/doc_cn/algorithm/rnn/simple_full_recurrent.dot new file mode 100644 index 0000000000..cee281fbac --- /dev/null +++ b/doc_cn/algorithm/rnn/simple_full_recurrent.dot @@ -0,0 +1,19 @@ +digraph G { + rankdir=LR; + a [label="4"] + b [label="5"] + c [label="2"] + d [label="0"] + e [label="9"] + f [label="8"] + g [label="1"] + h [label="4"] + + a -> b; + b -> c; + c -> d; + d -> e; + e -> f; + f -> g; + g -> h; +} \ No newline at end of file -- GitLab From 5e738ca33315da274676cdfb53bd9fa4aef9bab3 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 21 Nov 2016 16:37:36 +0800 Subject: [PATCH 0082/1503] Change auto => size_t in BaseMatrix.cu * Because it is a cuda source file, and we need to support c++ 03 in cuda. --- paddle/math/BaseMatrix.cu | 20 ++++++++++---------- paddle/math/Vector.cpp | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 2afb216db5..2f32b3fdd1 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1449,8 +1449,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1463,8 +1463,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, @@ -1493,8 +1493,8 @@ template int BaseMatrixT::applyRow(Agg agg, Op op, Saver sv, BaseMatrixT& b, BaseMatrixT& c) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); CHECK_EQ(c.height_, numRows); @@ -1524,8 +1524,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1538,8 +1538,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index 9ef7f2b4b5..68a1518d67 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -82,8 +82,8 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { template <> MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { - auto height = getSize(); - auto width = idRange; + size_t height = getSize(); + size_t width = idRange; MatrixPtr mat = Matrix::createSparseMatrix( height, idRange, height, NO_VALUE, SPARSE_CSR, false, useGpu); -- GitLab From 8c2e5b2cadf450de6f5b44ffffa42da93651c3c4 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 21 Nov 2016 20:56:00 +0800 Subject: [PATCH 0083/1503] Update use_concepts --- doc_cn/concepts/use_concepts.rst | 156 ++++++++++++++----------------- doc_cn/faq/index.rst | 19 +++- 2 files changed, 88 insertions(+), 87 deletions(-) diff --git a/doc_cn/concepts/use_concepts.rst b/doc_cn/concepts/use_concepts.rst index d3da9cc16b..49c45ff779 100644 --- a/doc_cn/concepts/use_concepts.rst +++ b/doc_cn/concepts/use_concepts.rst @@ -2,16 +2,20 @@ PaddlePaddle 基本使用概念 ######################### -PaddlePaddle是一个深度学习框架,同时支持单机和多机模式的系统。命令 ``paddle train`` 可启动单机模式的进程,我们称之为 ``trainer`` 进程。单机所有设备使用均在单机进程内调度完成。多机模式除了需要启动trainer进程外,还需要通过命令 ``paddle pserver`` 启动多机参数服务器进程, 我们称之为   ``pserver`` 进程。该进程负责多个单机进程间的通信,进而充分利用集群的计算资源。 PaddlePaddle同时以 ``swig api`` 的形式,提供训练结果模型预测的方法和自定义训练流程。 +PaddlePaddle是一个深度学习框架,支持单机模式和多机模式。 -下面我们会介绍trainer进程中的一些概念,这些概念会对如何使用PaddlePaddle有一定的帮助。 了解这些概念的前提是,读者已经了解 `基本的神经网络/机器学习原理和概念 `_ 。同时,如果想要了解PaddlePaddle实现中的一些概念,请参考 `PaddlePaddle 编程中的基本概念 `_ 。 +单节模式用命令 ``paddle train`` 可以启动一个trainer进程,一个单机训练作业只包括一个trainer进程,单机的所有设备使用,均在单机进程内调度完成。 + +如果数据规模比较大,希望加速训练,可以启动分布式作业。一个分布式作业里包括若干trainer进程和若干Parameter Server(或称pserver)进程。用命令 ``paddle pserver`` 可以启动 pserver 进程,pserver进程用于协调多个trainer进程之间的通信。 + +本文首先介绍trainer进程中的一些使用概念,然后介绍pserver进程中概念。 .. contents:: -系统模块 +系统框图 ======== -``trainer`` 进程内嵌了一个 ``python`` 解释器, 这个 ``python`` 解释器负责解析用户定义的神经网络配置;解析输入数据流,并将数据传入给 ``trainer`` 系统。 +下图描述了用户使用框图,PaddlePaddle里链接了Python解释器,trainer进程可以利用这个解释器执行Python脚本,Python脚本里定义了模型配置、训练算法、以及数据读取函数。其中,数据读取程序往往定义在一个单独Python脚本文件里,被称为DataProvider,通常是一个Python函数。模型配置、训练算法通常定义在另一单独Python文件中。下面将分别介绍这两部分。 .. graphviz:: @@ -30,132 +34,105 @@ PaddlePaddle是一个深度学习框架,同时支持单机和多机模式的 py -> data_provider [dir="back"]; } -所以,单机训练 ``trainer`` 进程对用户的主要接口语言为Python。用户需要配置文件主要有两个:数据流提供器 ``DataProvider`` 和模型配置 ``TrainerConfig`` 。 - - DataProvider ============ -DataProvider是 ``trainer`` 进程的数据提供器。主要负责将用户的原始数据转换成 ``trainer`` 系统可以识别的数据类型。当系统需要新的数据训练时,会调用DataProvider获取数据接口。当所有数据读取完一轮后,DataProvider返回空数据通知系统一轮数据读取结束。 ``trainer`` 在每一轮训练开始时会重置DataProvider。 +在不同的应用里,训练数据的格式往往各不相同。因此,为了用户能够灵活的处理数据,我们提供了Python处理数据的接口,称为 `PyDataProvider`_ 。 -需要注意的是,DataProvider是被 ``trainer`` 系统调用,而不是新数据驱动系统;数据 ``shuffle`` 和一些随机化噪声添加都应该在DataProvider中完成。 +trainer进程会调用DataProvider函数,将用户的原始数据转换成系统可以识别的数据类型。当所有数据读取完一轮后,DataProvider返回空数据,通知系统一轮数据读取结束,系统每一轮训练开始时会重置DataProvider。需要注意的是,DataProvider是被系统调用,而不是新数据驱动系统,一些随机化噪声添加都应该在DataProvider中完成。 -为了用户能够灵活的处理数据,PaddlePaddle提供了处理数据的Python接口(称为 `PyDataProvider`_ )。 在 ``PyDataProvider`` 中,系统C++模块接管了shuffle、处理batch、GPU和CPU通信、双缓冲、异步读取等问题,需要说明的是,一些情况下需要Python接口里处理shuffle,可以参考 `PyDataProvider`_ 的相关文档继续深入了解。 +在 ``PyDataProvider`` 中,系统C++模块接管了shuffle、处理batch、GPU和CPU通信、双缓冲、异步读取等问题,一些情况下(如:``min_pool_size=0``)需要Python接口里处理shuffle,可以参考 `PyDataProvider`_ 的相关文档继续深入了解。 -TrainerConfig -============= - -模型配置是一个Python文件,主要包括神经网络结构、优化算法、数据传入方式,使用命令行参数 ``--config`` 传给``trainer``主程序。 例如\: - -.. code-block:: bash +模型配置文件 +============ - paddle train --config=trainer_config.py +模型配置主要包括数据传入接口定义(DataConfig)、优化算法(OptimizationConfig)、网络结构(ModelConfig)。 其中数据传入接口定义与DataProvider的关系是:DataProvider里定义数据读取函数,配置文件的DataConfig里指定DataProvider文件名字、生成数据函数接口,请不要混淆。 一个简单的模型配置文件为: .. literalinclude:: trainer_config.py :linenos: -下面我们详细的介绍一下模型配置中各个模块的概念。 - +文件开头 ``from paddle.trainer_config_helpers import *`` ,是因为PaddlePaddle配置文件与C++模块通信的最基础协议是protobuf。为了避免用户直接写复杂的protobuf string,我们为用户定以Python接口来配置网络,该Python代码可以生成protobuf包,这就是的作用`trainer_config_helpers`_的作用。因此,在文件的开始,需要import这些函数。 这个包里面包含了模型配置需要的各个模块。 -trainer_config_helpers ----------------------- +下面分别介绍DataConfig、OptimizationConfig、ModelConfig这三部分该概念。 -PaddlePaddle配置文件与C++模块通信的最基础协议是 ``protobuf`` 。为了避免用户直接写比较难写的protobuf string,我们通过Python代码来生成protobuf包,这就是helpers的作用。所以在文件的开始,需要import这些helpers函数。 +DataConfig +---------- -需要注意的是,这个 ``paddle.trainer_config_helpers`` 包是标准的python包,这意味着用户可以选择自己喜欢的 ``IDE`` 或者编辑器来编写Paddle的配置文件,这个Python包注释文档比较完善,并提供了IDE的代码提示与类型注释。 +使用函数 ``define_py_data_sources2`` 配置数据源,后缀 2 是Paddle历史遗留问题,因为Paddle之前使用的PyDataProvider性能问题,重构了一个新的 `PyDataProvider`_ 。 -data_sources ------------- +``define_py_data_sources2`` 里通过train_list和test_list指定是训练文件列表和测试文件列表。 如果传入字符串的话,是指一个数据列表文件。这个数据列表文件中包含的是每一个训练或者测试文件的路径。如果传入一个list的话,则会默认生成一个list文件,再传入给train.list或者test.list。 -data_sources配置神经网络的数据源,使用的函数是 ``define_py_data_sources2`` ,这个函数是定义了使用 `PyDataProvider`_ 提供数据源。后缀 ``2`` 是Paddle历史遗留问题,因为Paddle之前使用的PyDataProvider性能问题,重构了一个新的 `PyDataProvider`_ 。 +``module`` 和 ``obj`` 指定了DataProvider的文件名和返回数据的函数名。更详细的使用,请参考 `PyDataProvider`_ 。 -data_sources里通过train_list和test_list指定是训练文件列表和测试文件列表。 如果传入字符串的话,是指一个数据列表文件。这个数据列表文件中包含的是每一个训练或者测试文件的路径。如果传入一个list的话,则会默认生成一个list文件,再传入给train.list或者test.list。 +OptimizationConfig +------------------ -其中``module`` 和``obj``指定了DataProvider的文件名和返回数据的函数名。更详细的使用,请参考 `PyDataProvider`_ 。 +通过`settings`_ 接口设置神经网络所使用的训练参数和优化算法,包括学习率、batch_size、优化算法、正则方法等,具体的使用方法请参考 `settings`_ 文档。 -settings --------- +ModelConfig +----------- -`settings`_ 设置训练神经网络所使用的算法。包括学习率、batch_size、优化算法、正则方法等,具体的使用方法请参考 `settings`_ 文档。 +神经网络配置主要包括网络连接、激活函数、损失函数、评估器。 -网络配置 --------- +- 网络连接: 主要由Layer组成,每个Layer返回的都是一个 ``LayerOutput`` 对象,Layer里面可以定义参数属性、激活类型等。 -上述配置中余下的部分是神经网络配置,主要包括网络连接、 ``cost`` 层、评估器。 + 为了更灵活的配置,PaddlePaddle提供了基于 Projection 或者 Operator 的配置,这两个需要与 ``mixed_layer`` 配合使用。这里简单介绍Layer、Projection、Operator的概念: -- 首先,定义了一个名字叫"pixel"的 ``data_layer`` ,每个layer返回的都是一个 ``LayerOutput`` 对象,比如第一层的输出对象称作 ``img`` 。 -- 然后,这个对象作为另一个layer( ``simple_img_conv_pool`` )的输入, ``simple_img_conv_pool`` 是一个组合层,包括了图像的卷积 (convolution) 和池化(pooling), -- 其次,连接到全连接层(``fc_layer``),再连接到一个含Softmax激活的全连接层。 -- 最终,连接到cost层( ``classification_cost`` ), ``classification_cost`` 默认使用多类交叉熵损失函数和分类错误率统计评估器。标记网络输出的函数为 ``outputs`` ,网络的输出是神经网络的优化目标,神经网络训练的时候,实际上就是要最小化这个输出。 + - Layer: 神经网络的某一层,可以有可学习的参数,一般是封装了许多复杂操作的集合。 + - Projection:需要与 ``mixed_layer`` 配合使用,含可学习参数。 + - Operator: 需要与 ``mixed_layer`` 配合使用,不含可学习参数,输入全是其他Layer的输出。 -用该模型配置进行预测时,网络的输出也是通过 ``outputs`` 标记。 + + 这个配置文件网络由 ``data_layer`` 、 ``simple_img_conv_pool`` 、 ``fc_layer`` 组成。 + - `data_layer`_ : 通常每个配置文件都会包括 ``data_layer`` ,定义输入数据大小。 + - `simple_img_conv_pool`_ :是一个组合层,包括了图像的卷积 (convolution)和池化(pooling)。 + - `fc_layer`_ :全连接层,激活函数为Softmax,这里也可叫分类层。 -Layer、Projection、Operator -=========================== + +- 损失函数和评估器:损失函数即为网络的优化目标,评估器可以评价模型结果。 -PaddlePaddle的网络是基于Layer来配置的。所谓的Layer即是神经网络的某一层,一般是封装了许多复杂操作的操作集合。比如最简单的 ``fc_layer`` ,包括矩阵乘法、多输入的求和、加Bias操作、激活( ``activation`` )函数操作。 - -.. code-block:: python + PaddlePaddle包括很多损失函数和评估起,详细可以参考 `损失函数层`_ 和 `评估器`_ 。这里 ``classification_cost`` 默认使用多类交叉熵损失函数和分类错误率统计评估器。 + +- ``outputs``: 标记网络输出的函数为 ``outputs`` 。 - data = data_layer(name='data', size=200) - out = fc_layer(input=data, size=200, act=TanhActivation()) + 训练阶段,网络的输出为神经网络的优化目标;预测阶段,网络的输出也可通过 ``outputs`` 标记。 -对于更灵活配置需求,PaddlePaddle提供了基于 ``Projection`` 或者 ``Operator`` 的配置,这些需要与 ``mixed_layer`` 配合使用。 ``mixed_layer`` 是将多个输入累加求和,然后加Bias和 ``activation`` 操作。 ``mixed_layer`` 具体计算是通过内部的Projection和Operator完成。Projection含有可学习参数;而Operator不含可学习的参数,输入全是其他Layer的输出。 +这里对 ``mixed_layer`` 稍做详细说明, 该Layer将多个输入(Projection 或 Operator)累加求和,具体计算是通过内部的 Projection 和 Operator 完成,然后加 Bias 和 activation 操作, 例如,和 ``fc_layer`` 同样功能的 ``mixed_layer`` 是: .. code-block:: python + + data = data_layer(name='data', size=200) + with mixed_layer(size=200) as out: + out += full_matrix_projection(input=data) - data = data_layer(name='data', size=200) - with mixed_layer(size=200) as out: - out += full_matrix_projection(input=data) - -PaddlePaddle可以使用 ``mixed layer`` 配置出非常复杂的网络,甚至可以直接配置一个完整的LSTM。用户可以参考 `mixed_layer`_ 的相关文档进行配置。 - -如何利用单机的所有GPU或所有CPU核心 -=============================== - -PaddlePaddle的单机 ``trainer`` 进程可以充分利用一台计算机上所有的GPU资源或者CPU。 +PaddlePaddle 可以使用 ``mixed layer`` 配置出非常复杂的网络,甚至可以直接配置一个完整的LSTM。用户可以参考 `mixed_layer`_ 的相关文档进行配置。 -如果要使用机器上多块GPU,使用如下命令即可\: -.. code-block:: bash - - paddle train --use_gpu=true --trainer_count=4 # use 4 gpu card, 0, 1, 2, 3 - -如果要使用机器上多块CPU, 使用如下命令即可\: - -.. code-block:: bash - - paddle train --trainer_count=4 # use 4 cpu cores. - -如果要指定GPU编号,例如选择第0、2号GPU,则可以设置 ``CUDA_VISIBLE_DEVICES`` 环境变量来指定特定的GPU。具体可以参考连接`masking-gpu`_ ,命令为: +分布式训练 +========== -.. code-block:: bash - - env CUDA_VISIBLE_DEVICES=0,2 paddle train --use_gpu=true --trainer_count=2 - -如何利用多台机器的计算资源训练神经网络 -=================================== - -PaddlePaddle多机采用经典的 ``Parameter Server`` 架构对多个节点的 ``trainer`` 进行同步。多机训练神经网络,要讲数据切分到不同的机器上,切分数据相对简单,所以在PaddlePaddle的开源实现中并没有提供相关工具包。 - -多机训练的经典拓扑结构如下\: +PaddlePaddle多机采用经典的 Parameter Server 架构对多个节点的 trainer 进行同步。多机训练的经典拓扑结构如下\: .. graphviz:: pserver_topology.dot -图中每个灰色方块是一台机器,在每个机器中,先启动一个 ``paddle pserver`` 进程,并指定端口号,可能的参数是\: +图中每个灰色方块是一台机器,在每个机器中,先使用命令 ``paddle pserver`` 启动一个pserver进程,并指定端口号,可能的参数是\: .. code-block:: bash paddle pserver --port=5000 --num_gradient_servers=4 --nics='eth0' -这里说明系统的 ``pserver`` 进程端口是 ``5000`` ,有四个训练进程(即 ``--gradient_servers=4`` ,PaddlePaddle同时将 ``trainer`` 称作 ``GradientServer`` 。因为其为负责提供Gradient)。 启动之后 ``pserver`` 进程之后,需要 ``trainer`` 训练进程,再在各个机器上运行如下命令\: +* 指定 pserver 进程端口是 5000 。 +* 有四个训练进程(即 ``--gradient_servers=4`` ,PaddlePaddle同时将 trainer 称作 GradientServer 。因为其为负责提供Gradient) 。 +* 指定以太网类型为TCP网络。 + +启动之后 pserver 进程之后,需要启动 trainer 训练进程,在各个机器上运行如下命令\: .. code-block:: bash @@ -163,16 +140,23 @@ PaddlePaddle多机采用经典的 ``Parameter Server`` 架构对多个节点的 对于简单的多机协同训练使用上述方式即可。另外,pserver/train 通常在高级情况下,还需要设置下面两个参数\: -* --ports_num\: 一个 pserver进程共绑定多少个端口用来做稠密更新。默认是1 +* --ports_num\: 一个 pserver 进程共绑定多少个端口用来做稠密更新。默认是1 * --ports_num_for_sparse\: 一个pserver进程共绑定多少端口用来做稀疏更新,默认是0 -使用手工指定端口数量,是因为Paddle的网络通信中,使用了 ``int32`` 作为消息长度,比较容易在大模型下溢出。所以,在 ``pserver`` 进程中可以启动多个子线程去接受trainer的数据,这样单个子线程的长度就不会溢出了。但是这个值不可以调的过大,因为增加这个值,对性能尤其是内存占用有一定的开销,另外稀疏更新的端口如果太大的话,很容易导致某一个参数服务器没有分配到任何参数。 +使用手工指定端口数量,是因为Paddle的网络通信中,使用了 int32 作为消息长度,比较容易在大模型下溢出。所以,在 pserver 进程中可以启动多个子线程去接受 trainer 的数据,这样单个子线程的长度就不会溢出了。但是这个值不可以调的过大,因为增加这个值,对性能尤其是内存占用有一定的开销,另外稀疏更新的端口如果太大的话,很容易导致某一个参数服务器没有分配到任何参数。 详细的说明可以参考,使用 `集群训练Paddle`_ 。 .. _PyDataProvider: ../ui/data_provider/pydataprovider2.html -.. _settings: ../../doc/ui/api/trainer_config_helpers/optimizers.html#settings -.. _mixed_layer: ../../doc/ui/api/trainer_config_helpers/layers.html#mixed-layer -.. _masking-gpu: http://www.acceleware.com/blog/cudavisibledevices-masking-gpus +.. _settings: ../../doc/ui/api/trainer_config_helpers/optimizers.html#settings +.. _trainer_config_helper: ../../doc/ui/api/trainer_config_helpers/index.html +.. _data_layer: ../../doc/ui/api/trainer_config_helpers/layers.html#data-layer +.. _simple_img_conv_pool: ../../doc/ui/api/trainer_config_helpers/networks.html#simple-img-conv-pool +.. _fc_layer: ../../doc/ui/api/trainer_config_helpers/layers.html#fc-layer +.. _损失函数层: ../../doc/ui/api/trainer_config_helpers/layers.html#cost-layers +.. _评估器: ../../doc/ui/api/trainer_config_helpers/evaluators.html +.. _mixed_layer: ../../doc/ui/api/trainer_config_helpers/layers.html#mixed-layer +.. _masking-gpu: http://www.acceleware.com/blog/cudavisibledevices-masking-gpus + .. _集群训练Paddle: ../cluster/index.html diff --git a/doc_cn/faq/index.rst b/doc_cn/faq/index.rst index 3eb0e10ae2..8da21e5b8b 100644 --- a/doc_cn/faq/index.rst +++ b/doc_cn/faq/index.rst @@ -213,4 +213,21 @@ PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字 原因是:单元测试使用了一个旧版本的python包,而没有测试到代码中实际修改的python包。即单元测试需要一个干净的环境: * 如果paddle包已经在python的site-packages里面了,那么单元测试时使用的paddle包,就是site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。 -* 即便设置了 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 \ No newline at end of file +* 即便设置了 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 + +9. 如何指定GPU设备 +----------------- + +例如机器上有4块GPU,编号从0开始,指定使用2、3号GPU: + +* 方式1:通过 ``CUDA_VISIBLE_DEVICES`` 环境变量来指定特定的GPU。 + +.. code-block:: bash + + env CUDA_VISIBLE_DEVICES=2,3 paddle train --use_gpu=true --trainer_count=2 + +* 方式2:通过命令行参数 ``--gpu_id`` 指定。 + +.. code-block:: bash + + paddle train --use_gpu=true --trainer_count=2 --gpu_id=2 -- GitLab From 049f9d3a1cba8b9add3d04e3f9324d48ab177252 Mon Sep 17 00:00:00 2001 From: gangliao Date: Mon, 21 Nov 2016 23:30:30 +0800 Subject: [PATCH 0084/1503] Fix a pointer comparison bug in hl_dso_loader.cc --- paddle/cuda/src/hl_dso_loader.cc | 39 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/paddle/cuda/src/hl_dso_loader.cc b/paddle/cuda/src/hl_dso_loader.cc index 5cb16cfbb3..ccc307f9a9 100644 --- a/paddle/cuda/src/hl_dso_loader.cc +++ b/paddle/cuda/src/hl_dso_loader.cc @@ -19,7 +19,7 @@ limitations under the License. */ P_DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " - "/usr/local/cudnn/lib64. If empty [default], dlopen " + "/usr/local/cudnn/lib. If empty [default], dlopen " "will search cudnn from LD_LIBRARY_PATH"); P_DEFINE_string(cuda_dir, "", @@ -31,24 +31,23 @@ P_DEFINE_string(cuda_dir, "", static inline std::string join(const std::string& part1, const std::string& part2) { - // directory separator - const char sep = '/'; - - if (!part2.empty() && part2.front() == sep) { - return part2; - } - std::string ret; - ret.reserve(part1.size() + part2.size() + 1); - ret = part1; - if (!ret.empty() && ret.back() != sep) { - ret += sep; - } - ret += part2; - return ret; + // directory separator + const char sep = '/'; + if (!part2.empty() && part2.front() == sep) { + return part2; + } + std::string ret; + ret.reserve(part1.size() + part2.size() + 1); + ret = part1; + if (!ret.empty() && ret.back() != sep) { + ret += sep; + } + ret += part2; + return ret; } static inline void GetDsoHandleFromDefaultPath( - std::string& dso_path, void** dso_handle, int dynload_flags) { + std::string& dso_path, void** dso_handle, int dynload_flags) { VLOG(3) << "Try to find cuda library: " << dso_path << " from default system path."; // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH @@ -74,9 +73,9 @@ static inline void GetDsoHandleFromDefaultPath( } static inline void GetDsoHandleFromSearchPath( - const std::string& search_root, - const std::string& dso_name, - void** dso_handle) { + const std::string& search_root, + const std::string& dso_name, + void** dso_handle) { int dynload_flags = RTLD_LAZY | RTLD_LOCAL; *dso_handle = nullptr; @@ -88,7 +87,7 @@ static inline void GetDsoHandleFromSearchPath( dlPath = join(search_root, dso_name); *dso_handle = dlopen(dlPath.c_str(), dynload_flags); // if not found, search from default path - if (nullptr == dso_handle) { + if (nullptr == *dso_handle) { LOG(WARNING) << "Failed to find cuda library: " << dlPath; dlPath = dso_name; GetDsoHandleFromDefaultPath(dlPath, dso_handle, dynload_flags); -- GitLab From b34ab48b5c397245972335925e2095dd60cd330d Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Mon, 21 Nov 2016 14:20:25 -0800 Subject: [PATCH 0085/1503] Update docker_install.rst address @wangkuiyi comments. --- doc_cn/build_and_install/install/docker_install.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc_cn/build_and_install/install/docker_install.rst b/doc_cn/build_and_install/install/docker_install.rst index 0872fd0b7a..40339659be 100644 --- a/doc_cn/build_and_install/install/docker_install.rst +++ b/doc_cn/build_and_install/install/docker_install.rst @@ -1,8 +1,7 @@ 安装PaddlePaddle的Docker镜像 ============================ -PaddlePaddle提供了Docker的使用镜像。PaddlePaddle推荐使用Docker进行PaddlePaddle的部署和运行。Docker是一个基于容器的轻量级虚拟环境。具有和宿主机相近的运行效率,并提供 -了非常方便的二进制分发手段。 +PaddlePaddle项目提供官方 `Docker `_ 镜像。Docker镜像是我们目前唯一官方支持的部署和运行方式。 下述内容将分为如下几个类别描述。 -- GitLab From 926f10b8d1beb24c3b8ce2f2890e0c95cc13dff8 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Mon, 21 Nov 2016 15:12:19 -0800 Subject: [PATCH 0086/1503] Update index.md Thanks for @wangkuiyi comments. --- doc_cn/introduction/index.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/doc_cn/introduction/index.md b/doc_cn/introduction/index.md index d6efab0cf1..f6eb5456c0 100644 --- a/doc_cn/introduction/index.md +++ b/doc_cn/introduction/index.md @@ -1,20 +1,18 @@ # 简介 -PaddlePaddle 源于百度的开源深度学习平台,有如下几个特点。首先,简单易用的:用户可以通过简单的十几行配置脚本搭建经典的神经网络模型。其次,高效强大的:PaddlePaddle可以支撑复杂集群环境下超大模型的训练,令你受益于深度学习的前沿成果。最后,在百度内部,已经有大量产品线使用了基于PaddlePaddle的深度学习技术。 - -这份简短的介绍将像你展示如何利用PaddlePaddle来解决一个经典的机器学习问题。 +PaddlePaddle是源于百度的一个深度学习平台。这份简短的介绍将向你展示如何利用PaddlePaddle来解决一个经典的线性回归问题。 ## 1. 一个经典的任务 -让我们从一个基础问题开始:单变量的线性回归。问题假定观测到了一批二维空间上的点`(x, y) `,并且已知 `x` 和 `y` 之间存在着某种线性关系,我们的目标是通过观测数据来学习这个线性关系。作为一个简单基础的模型,线性回归有着广泛的应用场景。以一个资产定价的问题为例,`x` 对应于房屋的大小,`y` 对应于房屋价格。我们可以通过观察市场上房屋销售的情况拟合 `x` 和 `y` 之间的关系,从而为新房屋的定价提供预测和参考。 +我们展示如何用PaddlePaddle解决单变量的线性回归问题。线性回归的输入是一批点`(x, y) `,其中 `y = wx + b + ε`, 而 ε 是一个符合高斯分布的随机变量。线性回归的输出是从这批点估计出来的参数 w 和 b。 +一个例子是房产估值。我们假设房产的价格(y)是其大小(x)的一个线性函数,那么我们可以通过收集市场上房子的大小和价格,用来估计线性函数的参数w 和 b。 ## 2. 准备数据 -假设变量 `X` 和 `Y` 的真实关系为: `Y = 2X + 0.3`,这里展示如何使用观测数据来拟合这一线性关系。首先,Python代码将随机产生2000个观测点,作为PaddlePaddle的输入。产生PaddlePaddle的输入数据和写一段普通的Python脚本几乎一样,你唯一需要增加的就是定义输入数据的类型。 +假设变量 `x` 和 `y` 的真实关系为: `y = 2x + 0.3 + ε`,这里展示如何使用观测数据来拟合这一线性关系。首先,Python代码将随机产生2000个观测点,作为线性回归的输入。下面脚本符合PaddlePaddle期待的读取数据的Python程序的模式。 ```python -# -*- coding:utf-8 -*- # dataprovider.py from paddle.trainer.PyDataProvider2 import * import random @@ -29,12 +27,11 @@ def process(settings, input_file): ## 3. 训练模型 -为了还原 `Y = 2X + 0.3`,我们先从一条随机的直线 `Y' = wX + b` 开始,然后利用观测数据调整 `w` 和 `b` 使得 `Y'` 和 `Y` 的差距不断减小,最终趋于接近。这个过程就是模型的训练过程,而 `w` 和 `b` 就是模型的参数,即我们的训练目标。 +为了还原 `y = 2x + 0.3`,我们先从一条随机的直线 `y' = wx + b` 开始,然后利用观测数据调整 `w` 和 `b` 使得 `y'` 和 `y` 的差距不断减小,最终趋于接近。这个过程就是模型的训练过程,而 `w` 和 `b` 就是模型的参数,即我们的训练目标。 在PaddlePaddle里,该模型的网络配置如下。 ```python -# -*- coding:utf-8 -*- # trainer_config.py from paddle.trainer_config_helpers import * @@ -50,10 +47,10 @@ settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) # 3. 神经网络配置 x = data_layer(name='x', size=1) y = data_layer(name='y', size=1) -# 线性计算网络层: y_predict = wx + b -y_predict = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) -# 计算误差函数,即 y_predict 和真实 y 之间的距离 -cost = regression_cost(input=y_predict, label=y) +# 线性计算网络层: ȳ = wx + b +ȳ = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) +# 计算误差函数,即 ȳ 和真实 y 之间的距离 +cost = regression_cost(input= ȳ, label=y) outputs(cost) ``` 这段简短的配置展示了PaddlePaddle的基本用法: @@ -63,7 +60,7 @@ outputs(cost) - 第二部分主要是选择学习算法,它定义了模型参数改变的规则。PaddlePaddle提供了很多优秀的学习算法,这里使用一个基于momentum的随机梯度下降(SGD)算法,该算法每批量(batch)读取12个采样数据进行随机梯度计算来更新更新。 - 最后一部分是神经网络的配置。由于PaddlePaddle已经实现了丰富的网络层,所以很多时候你需要做的只是定义正确的网络层并把它们连接起来。这里使用了三种网络单元: - - **数据层**:数据层 `data_layer` 是神经网络的入口,它读入数据并将它们传输到接下来的网络层。这里数据层有两个,分别对应于变量 `X` 和 `Y`。 + - **数据层**:数据层 `data_layer` 是神经网络的入口,它读入数据并将它们传输到接下来的网络层。这里数据层有两个,分别对应于变量 `x` 和 `y`。 - **全连接层**:全连接层 `fc_layer` 是基础的计算单元,这里利用它建模变量之间的线性关系。计算单元是神经网络的核心,PaddlePaddle支持大量的计算单元和任意深度的网络连接,从而可以拟合任意的函数来学习复杂的数据关系。 - **回归误差代价层**:回归误差代价层 `regression_cost`是众多误差代价函数层的一种,它们在训练过程作为网络的出口,用来计算模型的误差,是模型参数优化的目标函数。 @@ -72,7 +69,7 @@ outputs(cost) paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 ``` -PaddlePaddle将在观测数据集上迭代训练30轮,并将每轮的模型结果存放在 `./output` 路径下。从输出日志可以看到,随着轮数增加误差代价函数的输出在不断的减小,这意味着模型在训练数据上不断的改进,直到逼近真实解:` Y = 2X + 0.3 ` +PaddlePaddle将在观测数据集上迭代训练30轮,并将每轮的模型结果存放在 `./output` 路径下。从输出日志可以看到,随着轮数增加误差代价函数的输出在不断的减小,这意味着模型在训练数据上不断的改进,直到逼近真实解:` y = 2x + 0.3 ` ## 4. 模型检验 -- GitLab From 7ac9c18d5b5dcfcee41bb35121c4bcffb91815bd Mon Sep 17 00:00:00 2001 From: gangliao Date: Tue, 22 Nov 2016 10:26:03 +0800 Subject: [PATCH 0087/1503] Fix badge's wrong link in Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd47ed44bc..cf6fce86fc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PaddlePaddle -[![Build Status](https://travis-ci.org/PaddlePaddle/Paddle.svg?branch=develop)](https://travis-ci.org/baidu/Paddle) +[![Build Status](https://travis-ci.org/PaddlePaddle/Paddle.svg?branch=develop)](https://travis-ci.org/PaddlePaddle/Paddle) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) [![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) [![Coverage Status](https://coveralls.io/repos/github/PaddlePaddle/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) -- GitLab From 6bde55ab659e58c35812ee6aa54a7faefa81ff87 Mon Sep 17 00:00:00 2001 From: gangliao Date: Tue, 22 Nov 2016 10:47:24 +0800 Subject: [PATCH 0088/1503] Fix wrong links in Readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cf6fce86fc..8a8e158415 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![Build Status](https://travis-ci.org/PaddlePaddle/Paddle.svg?branch=develop)](https://travis-ci.org/PaddlePaddle/Paddle) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) [![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) -[![Coverage Status](https://coveralls.io/repos/github/PaddlePaddle/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) -[![Release](https://img.shields.io/github/release/baidu/Paddle.svg?colorB=fedcba)](https://github.com/baidu/Paddle/releases) +[![Coverage Status](https://coveralls.io/repos/github/PaddlePaddle/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/PaddlePaddle/Paddle?branch=develop) +[![Release](https://img.shields.io/github/release/PaddlePaddle/Paddle.svg)](https://github.com/PaddlePaddle/Paddle/releases) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) @@ -17,7 +17,7 @@ developed by Baidu scientists and engineers for the purpose of applying deep learning to many products at Baidu. Our vision is to enable deep learning for everyone via PaddlePaddle. -Please refer to our [release announcement](https://github.com/baidu/Paddle/releases) to track the latest feature of PaddlePaddle. +Please refer to our [release announcement](https://github.com/PaddlePaddle/Paddle/releases) to track the latest feature of PaddlePaddle. ## Features @@ -92,7 +92,7 @@ Both [English Docs](http://paddlepaddle.org/doc/) and [Chinese Docs](http://padd ## Ask Questions -You are welcome to submit questions and bug reports as [Github Issues](https://github.com/baidu/paddle/issues). +You are welcome to submit questions and bug reports as [Github Issues](https://github.com/PaddlePaddle/Paddle/issues). ## Copyright and License PaddlePaddle is provided under the [Apache-2.0 license](LICENSE). -- GitLab From 102dfc217e6f64896abb4e1d80496799448e07cf Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Mon, 21 Nov 2016 19:13:45 -0800 Subject: [PATCH 0089/1503] Update make_and_install.rst Per @luotao1's comments. Thanks. --- doc_cn/build_and_install/cmake/make_and_install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_cn/build_and_install/cmake/make_and_install.rst b/doc_cn/build_and_install/cmake/make_and_install.rst index 8a390ef581..212b9c9352 100644 --- a/doc_cn/build_and_install/cmake/make_and_install.rst +++ b/doc_cn/build_and_install/cmake/make_and_install.rst @@ -1,4 +1,4 @@ make和make install ================== -参见 `make和make install `_ +参见 `make和make install <../../../doc/build/build_from_source.html#build-and-install>`_ -- GitLab From 2eb79c197d4893632ee0db5d28dc213b13d259ef Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Mon, 21 Nov 2016 19:15:39 -0800 Subject: [PATCH 0090/1503] Update install_deps.rst Thanks for @luotao1's comments. --- doc_cn/build_and_install/cmake/install_deps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_cn/build_and_install/cmake/install_deps.rst b/doc_cn/build_and_install/cmake/install_deps.rst index 6d8727e329..7fa4665a95 100644 --- a/doc_cn/build_and_install/cmake/install_deps.rst +++ b/doc_cn/build_and_install/cmake/install_deps.rst @@ -1,4 +1,4 @@ 安装编译PaddlePaddle需要的依赖 ============================== -参见 `安装编译依赖 `_ +参见 `安装编译依赖 <../../../doc/build/build_from_source.html#install-dependencies>`_ -- GitLab From 0de93a437ae0220fe9548302fbf4a41990119806 Mon Sep 17 00:00:00 2001 From: tianbingsz Date: Mon, 21 Nov 2016 19:21:14 -0800 Subject: [PATCH 0091/1503] Rename index.md to index.rst Thanks for @luotao's comments. --- doc_cn/introduction/{index.md => index.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc_cn/introduction/{index.md => index.rst} (100%) diff --git a/doc_cn/introduction/index.md b/doc_cn/introduction/index.rst similarity index 100% rename from doc_cn/introduction/index.md rename to doc_cn/introduction/index.rst -- GitLab From ccea3b026ed1741ab352574eb99a26386452e239 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 20 Nov 2016 15:35:40 +0800 Subject: [PATCH 0092/1503] Add style check for *.cc files in cuda directory --- paddle/cuda/CMakeLists.txt | 7 +- paddle/cuda/src/hl_cuda_cublas.cc | 15 ++-- paddle/cuda/src/hl_cuda_cudnn.cc | 143 +++++++++++++----------------- paddle/cuda/src/hl_cuda_device.cc | 12 +-- paddle/cuda/src/hl_cudart_wrap.cc | 26 ++---- paddle/cuda/src/hl_dso_loader.cc | 41 ++++----- 6 files changed, 112 insertions(+), 132 deletions(-) diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index cdb730bb3c..11dbfb54b2 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -81,5 +81,8 @@ else() add_library(paddle_cuda ${CUDA_SOURCES}) endif() -add_style_check_target(paddle_cuda ${CUDA_SOURCES}) -add_style_check_target(paddle_cuda ${CUDA_HEADERS}) +add_style_check_target(paddle_cuda + ${CUDA_SOURCES} + ${CUDA_HEADERS} + ${CUDA_DSO_SOURCES} + ${CUDA_CXX_WITH_GPU_SOURCES}) diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index f16376ec93..abf6afadc2 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -104,7 +104,7 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) #endif const char* hl_cublas_get_error_string(cublasStatus_t status) { - switch(status) { + switch (status) { case CUBLAS_STATUS_NOT_INITIALIZED: return "[cublas status]: not initialized"; case CUBLAS_STATUS_ALLOC_FAILED: @@ -181,7 +181,7 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { real **inout_d = (real **)hl_malloc_device(sizeof(real *)); hl_memcpy(inout_d, inout_h, sizeof(real *)); - int *pivot_d = (int *)hl_malloc_device(dimN*sizeof(int)); + int *pivot_d = (int *)hl_malloc_device(dimN * sizeof(int)); int *info_d = (int *)t_resource.gpu_mem; /* Note: cublasSgetrfBatched is used to calculate a number of @@ -189,10 +189,9 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { the API for better performance. */ CHECK_CUBLAS(CUBLAS_GETRF(t_resource.handle, - dimN, inout_d, lda, pivot_d, - info_d, 1)); + dimN, inout_d, lda, pivot_d, info_d, 1)); - int info_h; + int info_h; hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { LOG(FATAL) << "Factorization of matrix failed: matrix may be singular.\n"; @@ -204,8 +203,8 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { hl_memcpy(out_d, out_h, sizeof(real *)); CHECK_CUBLAS(CUBLAS_GETRI(t_resource.handle, - dimN, (const real **)inout_d, lda, pivot_d, - out_d, ldc, info_d, 1)); + dimN, (const real **)inout_d, lda, pivot_d, + out_d, ldc, info_d, 1)); hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { @@ -215,7 +214,7 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { hl_free_mem_device(inout_d); hl_free_mem_device(pivot_d); hl_free_mem_device(out_d); - + CHECK_SYNC("hl_matrix_inverse failed"); } diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index 92b28e4345..1829fe23ac 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -159,13 +159,11 @@ CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) bool g_is_libcudnn_init = false; int g_cudnn_lib_version = 0; -void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) -{ +void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) { CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(cudnn_desc)); } -void hl_cudnn_init(cudnnHandle_t *cudnn_handle, cudaStream_t stream) -{ +void hl_cudnn_init(cudnnHandle_t *cudnn_handle, cudaStream_t stream) { size_t cudnn_dso_ver = dynload::cudnnGetVersion(); size_t cudnn_dso_major = cudnn_dso_ver / 1000; size_t cudnn_cuh_major = CUDNN_VERSION / 1000; @@ -212,13 +210,18 @@ void hl_conv_workspace(hl_tensor_descriptor input, CHECK_NOTNULL(conv); // Specify workspace limit directly - size_t memoryLimitBytes = (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; + size_t memoryLimitBytes = + (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; // cudnn convolution forward configuration - cudnnTensorDescriptor_t fwd_src_desc = GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t fwd_dest_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnFilterDescriptor_t fwd_filter_desc = GET_FILTER_DESCRIPTOR(filter); - cudnnConvolutionDescriptor_t fwd_conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + cudnnTensorDescriptor_t fwd_src_desc = + GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t fwd_dest_desc = + GET_TENSOR_DESCRIPTOR(output); + cudnnFilterDescriptor_t fwd_filter_desc = + GET_FILTER_DESCRIPTOR(filter); + cudnnConvolutionDescriptor_t fwd_conv_desc = + GET_CONVOLUTION_DESCRIPTOR(conv); CHECK_CUDNN(dynload::cudnnGetConvolutionForwardAlgorithm( t_resource.cudnn_handle, @@ -250,23 +253,23 @@ void hl_conv_workspace(hl_tensor_descriptor input, GET_CONVOLUTION_DESCRIPTOR(conv); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataAlgorithm( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdDataAlgo))); + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdDataAlgo))); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataWorkspaceSize( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - static_cast(*convBwdDataAlgo), - bwdDataLimitBytes)); + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + static_cast(*convBwdDataAlgo), + bwdDataLimitBytes)); // cudnn convolution backward filter configuration cudnnTensorDescriptor_t bwd_filter_src_desc = @@ -279,21 +282,21 @@ void hl_conv_workspace(hl_tensor_descriptor input, GET_FILTER_DESCRIPTOR(filter); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterAlgorithm( - t_resource.cudnn_handle, - bwd_filter_src_desc, - bwd_filter_diff_desc, - bwd_filter_conv_desc, - bwd_filter_grad_desc, - CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdFilterAlgo))); + t_resource.cudnn_handle, + bwd_filter_src_desc, + bwd_filter_diff_desc, + bwd_filter_conv_desc, + bwd_filter_grad_desc, + CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdFilterAlgo))); CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterWorkspaceSize( - t_resource.cudnn_handle, bwd_filter_src_desc, - bwd_filter_diff_desc, bwd_filter_conv_desc, - bwd_filter_grad_desc, - static_cast(*convBwdFilterAlgo), - bwdFilterLimitBytes)); + t_resource.cudnn_handle, bwd_filter_src_desc, + bwd_filter_diff_desc, bwd_filter_conv_desc, + bwd_filter_grad_desc, + static_cast(*convBwdFilterAlgo), + bwdFilterLimitBytes)); #endif } @@ -302,8 +305,7 @@ void hl_create_tensor_descriptor(hl_tensor_descriptor* image_desc, int batch_size, int feature_maps, int height, - int width) -{ + int width) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = @@ -359,8 +361,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int batch_size, int feature_maps, int height, - int width) -{ + int width) { const int stride_w = 1; const int stride_h = width * stride_w; const int stride_c = height * stride_h; @@ -384,8 +385,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int nStride, int cStride, int hStride, - int wStride) -{ + int wStride) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; @@ -408,8 +408,7 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, hl_desc->width = width; } -void hl_destroy_tensor_descriptor(hl_tensor_descriptor image_desc) -{ +void hl_destroy_tensor_descriptor(hl_tensor_descriptor image_desc) { CHECK_NOTNULL(image_desc); cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; @@ -430,11 +429,9 @@ void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, int height_padding, int width_padding, int stride_height, - int stride_width) -{ + int stride_width) { cudnnPoolingMode_t cudnn_mode; - switch (mode) - { + switch (mode) { case HL_POOLING_MAX: cudnn_mode = CUDNN_POOLING_MAX; break; @@ -478,13 +475,13 @@ void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, *pooling_desc = (hl_pooling_descriptor)hl_pooling_desc; } -void hl_destroy_pooling_descriptor(hl_pooling_descriptor pooling_desc) -{ +void hl_destroy_pooling_descriptor(hl_pooling_descriptor pooling_desc) { CHECK_NOTNULL(pooling_desc); - cudnn_pooling_descriptor hl_pooling = (cudnn_pooling_descriptor)pooling_desc; - CHECK_NOTNULL(hl_pooling->desc); + cudnn_pooling_descriptor hl_pooling = + (cudnn_pooling_descriptor)pooling_desc; + CHECK_NOTNULL(hl_pooling->desc); CHECK_CUDNN(dynload::cudnnDestroyPoolingDescriptor(hl_pooling->desc)); hl_pooling->desc = NULL; @@ -496,8 +493,7 @@ void hl_pooling_forward(hl_tensor_descriptor input, real* input_image, hl_tensor_descriptor output, real* output_image, - hl_pooling_descriptor pooling) -{ + hl_pooling_descriptor pooling) { cudnnPoolingDescriptor_t pooling_desc; cudnnTensorDescriptor_t input_desc; cudnnTensorDescriptor_t output_desc; @@ -531,8 +527,7 @@ void hl_pooling_backward(hl_tensor_descriptor input, hl_tensor_descriptor output, real* output_image, real* output_image_grad, - hl_pooling_descriptor pooling) -{ + hl_pooling_descriptor pooling) { cudnnPoolingDescriptor_t pooling_desc; cudnnTensorDescriptor_t input_desc; cudnnTensorDescriptor_t output_desc; @@ -571,8 +566,7 @@ void hl_create_filter_descriptor(hl_filter_descriptor* filter, int input_feature_maps, int output_feature_maps, int height, - int width) -{ + int width) { CHECK_NOTNULL(filter); cudnn_filter_descriptor hl_filter = @@ -607,8 +601,7 @@ void hl_create_filter_descriptor(hl_filter_descriptor* filter, } -void hl_destroy_filter_descriptor(hl_filter_descriptor filter) -{ +void hl_destroy_filter_descriptor(hl_filter_descriptor filter) { CHECK_NOTNULL(filter); cudnn_filter_descriptor hl_filter = (cudnn_filter_descriptor)filter; @@ -627,14 +620,13 @@ void hl_create_convolution_descriptor(hl_convolution_descriptor* conv, int padding_height, int padding_width, int stride_height, - int stride_width) -{ + int stride_width) { CHECK_NOTNULL(conv); - cudnn_convolution_descriptor hl_conv = - (cudnn_convolution_descriptor)malloc(sizeof(_cudnn_convolution_descriptor)); - CHECK_NOTNULL(hl_conv); + cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor) + malloc(sizeof(_cudnn_convolution_descriptor)); + CHECK_NOTNULL(hl_conv); CHECK_CUDNN(dynload::cudnnCreateConvolutionDescriptor(&hl_conv->desc)); cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; @@ -667,8 +659,7 @@ void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, int padding_height, int padding_width, int stride_height, - int stride_width) -{ + int stride_width) { CHECK_NOTNULL(conv); CHECK_NOTNULL(image); CHECK_NOTNULL(filter); @@ -697,8 +688,7 @@ void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, hl_conv->mode = mode; } -void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) -{ +void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) { CHECK_NOTNULL(conv); cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; @@ -753,8 +743,7 @@ void hl_convolution_forward(hl_tensor_descriptor input, void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, real* bias_data, hl_tensor_descriptor output, - real* output_data) -{ + real* output_data) { CHECK_NOTNULL(bias); CHECK_NOTNULL(output); CHECK_NOTNULL(bias_data); @@ -782,8 +771,7 @@ void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, void hl_convolution_backward_bias(hl_tensor_descriptor bias, real* bias_grad_data, hl_tensor_descriptor output, - real* output_grad_data) -{ + real* output_grad_data) { CHECK_NOTNULL(bias); CHECK_NOTNULL(output); CHECK_NOTNULL(bias_grad_data); @@ -814,7 +802,6 @@ void hl_convolution_backward_filter(hl_tensor_descriptor input, void* gpuWorkSpace, size_t sizeInBytes, int convBwdFilterAlgo) { - CHECK_NOTNULL(input); CHECK_NOTNULL(output); CHECK_NOTNULL(filter); @@ -889,8 +876,7 @@ void hl_convolution_backward_data(hl_tensor_descriptor input, void hl_softmax_forward(real *input, real *output, int height, - int width) -{ + int width) { #ifndef PADDLE_TYPE_DOUBLE cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else @@ -923,8 +909,7 @@ void hl_softmax_forward(real *input, void hl_softmax_backward(real *output_value, real *output_grad, int height, - int width) -{ + int width) { #ifndef PADDLE_TYPE_DOUBLE cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 3ea2c91bd5..ca19f210c5 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -203,8 +203,8 @@ inline pid_t gettid() { #endif pid_t tid = syscall(__NR_gettid); #endif - CHECK_NE(tid, -1); - return tid; + CHECK_NE((int)tid, -1); + return tid; } void hl_init(int device) { @@ -355,7 +355,8 @@ void* hl_malloc_host(size_t size) { void *dest_h; CHECK(size) << __func__ << ": the size for device memory is 0, please check."; - CHECK_CUDA(dynload::cudaHostAlloc((void**)&dest_h, size, cudaHostAllocDefault)); + CHECK_CUDA(dynload::cudaHostAlloc( + (void**)&dest_h, size, cudaHostAllocDefault)); return dest_h; } @@ -364,7 +365,7 @@ void hl_free_mem_host(void *dest_h) { CHECK_NOTNULL(dest_h); cudaError_t err = dynload::cudaFreeHost(dest_h); - CHECK (cudaSuccess == err || cudaErrorCudartUnloading == err) + CHECK(cudaSuccess == err || cudaErrorCudartUnloading == err) << hl_get_device_error_string(); } @@ -502,7 +503,8 @@ int hl_get_cuda_version() { return g_cuda_lib_version; } -void hl_create_thread_resources(int device, thread_device_resources device_res) { +void hl_create_thread_resources(int device, + thread_device_resources device_res) { CHECK_CUDA(dynload::cudaSetDevice(device)); /* create thread stream */ diff --git a/paddle/cuda/src/hl_cudart_wrap.cc b/paddle/cuda/src/hl_cudart_wrap.cc index 27bbd03bc3..fe755b8c26 100644 --- a/paddle/cuda/src/hl_cudart_wrap.cc +++ b/paddle/cuda/src/hl_cudart_wrap.cc @@ -78,48 +78,38 @@ __host__ cudaError_t CUDARTAPI cudaLaunchKernel(const void *func, dim3 blockDim, void **args, size_t sharedMem, - cudaStream_t stream) -{ - return dynload::cudaLaunchKernel(func, gridDim, blockDim, args, sharedMem, stream); + cudaStream_t stream) { + return dynload::cudaLaunchKernel(func, gridDim, blockDim, + args, sharedMem, stream); } #endif /* CUDART_VERSION >= 7000 */ -__host__ cudaError_t CUDARTAPI cudaLaunch(const void *func) -{ +__host__ cudaError_t CUDARTAPI cudaLaunch(const void *func) { return dynload::cudaLaunch(func); } __host__ cudaError_t CUDARTAPI cudaSetupArgument(const void *arg, size_t size, - size_t offset) -{ + size_t offset) { return dynload::cudaSetupArgument(arg, size, offset); } __host__ cudaError_t CUDARTAPI cudaConfigureCall(dim3 gridDim, dim3 blockDim, size_t sharedMem, - cudaStream_t stream) -{ + cudaStream_t stream) { return dynload::cudaConfigureCall(gridDim, blockDim, sharedMem, stream); } extern "C" { -void** CUDARTAPI __cudaRegisterFatBinary( - void *fatCubin -) -{ +void** CUDARTAPI __cudaRegisterFatBinary(void *fatCubin) { return dynload::__cudaRegisterFatBinary(fatCubin); - } -void CUDARTAPI __cudaUnregisterFatBinary( - void **fatCubinHandle -) -{ +void CUDARTAPI __cudaUnregisterFatBinary(void **fatCubinHandle) { return dynload::__cudaUnregisterFatBinary(fatCubinHandle); } diff --git a/paddle/cuda/src/hl_dso_loader.cc b/paddle/cuda/src/hl_dso_loader.cc index b564b96903..5cb16cfbb3 100644 --- a/paddle/cuda/src/hl_dso_loader.cc +++ b/paddle/cuda/src/hl_dso_loader.cc @@ -19,17 +19,18 @@ limitations under the License. */ P_DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " - "/usr/local/cudnn/lib64. If empty [default], dlopen will search " - "cudnn from LD_LIBRARY_PATH"); + "/usr/local/cudnn/lib64. If empty [default], dlopen " + "will search cudnn from LD_LIBRARY_PATH"); P_DEFINE_string(cuda_dir, "", "Specify path for loading cuda library, such as libcublas, " - "libcurand. For instance, /usr/local/cuda/lib64. " - "(Note: libcudart can not be specified by cuda_dir, since some " + "libcurand. For instance, /usr/local/cuda/lib64. (Note: " + "libcudart can not be specified by cuda_dir, since some " "build-in function in cudart already ran before main entry). " - "If empty [default], dlopen will search cuda from LD_LIBRARY_PATH"); + "If default, dlopen will search cuda from LD_LIBRARY_PATH"); -static inline std::string join(const std::string& part1, const std::string& part2) { +static inline std::string join(const std::string& part1, + const std::string& part2) { // directory separator const char sep = '/'; @@ -49,10 +50,10 @@ static inline std::string join(const std::string& part1, const std::string& part static inline void GetDsoHandleFromDefaultPath( std::string& dso_path, void** dso_handle, int dynload_flags) { VLOG(3) << "Try to find cuda library: " << dso_path - << " from default system path."; - // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH + << " from default system path."; + // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH *dso_handle = dlopen(dso_path.c_str(), dynload_flags); - + // DYLD_LIBRARY_PATH is disabled after Mac OS 10.11 to // bring System Integrity Projection (SIP), if dso_handle // is null, search from default package path in Mac OS. @@ -62,13 +63,13 @@ static inline void GetDsoHandleFromDefaultPath( *dso_handle = dlopen(dso_path.c_str(), dynload_flags); if (nullptr == *dso_handle) { if (dso_path == "libcudnn.dylib") { - LOG(FATAL) << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" - << "For instance, sudo tar -xzf cudnn-7.5-osx-x64-v5.0-ga.tgz -C " - << "/usr/local \n sudo chmod a+r /usr/local/cuda/include/cudnn.h " + LOG(FATAL) << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" // NOLINT + << "For instance, sudo tar -xzf cudnn-7.5-osx-x64-v5.0-ga.tgz -C " // NOLINT + << "/usr/local \n sudo chmod a+r /usr/local/cuda/include/cudnn.h " // NOLINT << "/usr/local/cuda/lib/libcudnn*"; } - } - } + } + } #endif } @@ -96,19 +97,19 @@ static inline void GetDsoHandleFromSearchPath( CHECK(nullptr != *dso_handle) << "Failed to find cuda library: " << dlPath << std::endl - << "Please specify its path correctly using one of the following ideas: \n" + << "Please specify its path correctly using one of the following ways: \n" // NOLINT - << "Idea 1. set cuda and cudnn lib path at runtime. " - << "http://www.paddlepaddle.org/doc/ui/cmd_argument/argument_outline.html \n" + << "Method 1. set cuda and cudnn lib path at runtime. " + << "http://www.paddlepaddle.org/doc/ui/cmd_argument/argument_outline.html \n" // NOLINT << "For instance, issue command: paddle train --use_gpu=1 " - << "--cuda_dir=/usr/local/cudnn/lib --cudnn_dir=/usr/local/cudnn/lib ...\n" + << "--cuda_dir=/usr/local/cuda/lib64 --cudnn_dir=/usr/local/cudnn/lib ...\n" // NOLINT - << "Idea 2. set environment variable LD_LIBRARY_PATH on Linux or " + << "Method 2. set environment variable LD_LIBRARY_PATH on Linux or " << "DYLD_LIBRARY_PATH on Mac OS. \n" << "For instance, issue command: export LD_LIBRARY_PATH=... \n" << "Note: After Mac OS 10.11, using the DYLD_LIBRARY_PATH is impossible " - << "unless System Integrity Protection (SIP) is disabled. However, @Idea 1" + << "unless System Integrity Protection (SIP) is disabled. However, method 1 " // NOLINT << "always work well."; } -- GitLab From 201c0ed8287643a106d63fef60d1ab3fb4e80e45 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 23:52:45 +0800 Subject: [PATCH 0093/1503] Add new badges for docs and stats --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8679fb55f..962b08853b 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,16 @@ [![Build Status](https://travis-ci.org/baidu/Paddle.svg?branch=master)](https://travis-ci.org/baidu/Paddle) +[![Downloads](https://img.shields.io/github/downloads/baidu/Paddle/total.svg)](https://github.com/baidu/Paddle/releases) [![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) -[![Join the chat at https://gitter.im/PaddlePaddle/Deep_Learning](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/PaddlePaddle/Deep_Learning?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE) + +[![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) +[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)]() +[![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) + +[![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) +[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) + Welcome to the PaddlePaddle GitHub. -- GitLab From c9b7e0efd04c474f5ca8178647d26ca29b98a59b Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 17 Nov 2016 23:59:02 +0800 Subject: [PATCH 0094/1503] Add chinese docs link in badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 962b08853b..a14e1b82d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) -[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)]() +[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) [![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) [![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) -- GitLab From dab2ddbb610f862b487b95642c442d3d9bd169d4 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 21 Nov 2016 14:51:18 +0800 Subject: [PATCH 0095/1503] Revise badge status * because baidu/paddle transfered to paddlepaddle/paddle --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a14e1b82d1..4060096559 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,15 @@ # PaddlePaddle -[![Build Status](https://travis-ci.org/baidu/Paddle.svg?branch=master)](https://travis-ci.org/baidu/Paddle) -[![Downloads](https://img.shields.io/github/downloads/baidu/Paddle/total.svg)](https://github.com/baidu/Paddle/releases) -[![Coverage Status](https://coveralls.io/repos/github/baidu/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) - +[![Build Status](https://travis-ci.org/PaddlePaddle/Paddle.svg?branch=develop)](https://travis-ci.org/baidu/Paddle) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://www.paddlepaddle.org/) [![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](http://www.paddlepaddle.org/cn/index.html) -[![Join the chat at](https://img.shields.io/gitter/room/PaddlePaddle/Deep_Learning.svg)](https://gitter.im/PaddlePaddle/Deep_Learning) - -[![Release](https://img.shields.io/github/release/baidu/Paddle.svg)](https://github.com/baidu/Paddle/releases) +[![Coverage Status](https://coveralls.io/repos/github/PaddlePaddle/Paddle/badge.svg?branch=develop)](https://coveralls.io/github/baidu/Paddle?branch=develop) +[![Release](https://img.shields.io/github/release/baidu/Paddle.svg?colorB=fedcba)](https://github.com/baidu/Paddle/releases) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) Welcome to the PaddlePaddle GitHub. - PaddlePaddle (PArallel Distributed Deep LEarning) is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep -- GitLab From a4d18146680fe9cd95955fe9e85e3f61fdcbaf9e Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 21 Nov 2016 14:54:24 +0800 Subject: [PATCH 0096/1503] Revise Readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4060096559..bd47ed44bc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Welcome to the PaddlePaddle GitHub. + PaddlePaddle (PArallel Distributed Deep LEarning) is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep -- GitLab From d42fbed02daf63d29be3b6383f017a6f4e7c7c9e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 20 Nov 2016 22:12:32 +0800 Subject: [PATCH 0097/1503] Fix several cpp issues * Different Type compare. * ostream << should pass a const object. * remove always true checks. --- .../gserver/evaluators/CTCErrorEvaluator.cpp | 2 +- paddle/gserver/evaluators/ChunkEvaluator.cpp | 2 +- paddle/gserver/evaluators/Evaluator.cpp | 6 +++--- paddle/gserver/evaluators/Evaluator.h | 16 +++++++-------- .../gserver/gradientmachines/MultiNetwork.cpp | 2 +- .../gradientmachines/NeuralNetwork.cpp | 2 +- paddle/math/BaseMatrix.cu | 20 +++++++++---------- paddle/math/Vector.cpp | 6 +++--- paddle/pserver/ParameterServer2.cpp | 2 -- paddle/utils/BarrierStat.cpp | 10 +++++----- paddle/utils/BarrierStat.h | 11 +++++----- paddle/utils/CompilerMacros.h | 17 ++++++++++++++++ paddle/utils/Logging.cpp | 4 ++-- paddle/utils/Logging.h | 3 ++- 14 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 paddle/utils/CompilerMacros.h diff --git a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp index e397c71c87..c2625bce9a 100644 --- a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp +++ b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp @@ -240,7 +240,7 @@ public: seqClassficationError_ = 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << (numSequences_ ? totalScore_ / numSequences_ : 0); os << " deletions error" diff --git a/paddle/gserver/evaluators/ChunkEvaluator.cpp b/paddle/gserver/evaluators/ChunkEvaluator.cpp index 22579891f3..6f5d2b47c3 100644 --- a/paddle/gserver/evaluators/ChunkEvaluator.cpp +++ b/paddle/gserver/evaluators/ChunkEvaluator.cpp @@ -114,7 +114,7 @@ public: numCorrect_ = 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { double precision = (double)numCorrect_ / numOutputSegments_; double recall = (double)numCorrect_ / numLabelSegments_; double f1 = diff --git a/paddle/gserver/evaluators/Evaluator.cpp b/paddle/gserver/evaluators/Evaluator.cpp index 7bdcdaae53..d43dceea74 100644 --- a/paddle/gserver/evaluators/Evaluator.cpp +++ b/paddle/gserver/evaluators/Evaluator.cpp @@ -315,7 +315,7 @@ public: return 0; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { CHECK(colIdx_ + (int32_t)colNum_ >= 0 && colIdx_ - (int32_t)colNum_ < 0) << "column index [" << colIdx_ << "] out of range [-" << colNum_ << ", " << colNum_ << ")"; @@ -421,7 +421,7 @@ void AucEvaluator::distributeEval(ParameterClient2* client) { client->reduce(statNeg_, statNeg_, kBinNum_ + 1, FLAGS_trainer_id, 0); } -double AucEvaluator::calcAuc() { +double AucEvaluator::calcAuc() const { double totPos = 0.0; double totNeg = 0.0; double totPosPrev = 0.0; @@ -584,7 +584,7 @@ real PrecisionRecallEvaluator::evalImp(std::vector& arguments) { return 0; } -void PrecisionRecallEvaluator::printStats(std::ostream& os) { +void PrecisionRecallEvaluator::printStats(std::ostream& os) const { int label = config_.positive_label(); if (label != -1) { CHECK(label >= 0 && label < (int)statsInfo_.size()) diff --git a/paddle/gserver/evaluators/Evaluator.h b/paddle/gserver/evaluators/Evaluator.h index b79a539384..e9957a5ce2 100644 --- a/paddle/gserver/evaluators/Evaluator.h +++ b/paddle/gserver/evaluators/Evaluator.h @@ -99,19 +99,19 @@ public: * @brief print the statistics of evaluate result * @note finish() should be called before printStats */ - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << (numSamples_ ? totalScore_ / numSamples_ : 0); } friend std::ostream& operator<<(std::ostream& os, - Evaluator& evaluator) { + const Evaluator& evaluator) { evaluator.printStats(os); return os; } friend std::ostream&& operator<<(std::ostream&& os, // NOLINT - Evaluator& evaluator) { + const Evaluator& evaluator) { evaluator.printStats(os); return std::move(os); } @@ -135,7 +135,7 @@ public: return -1; } virtual void finish() {} - virtual void printStats(std::ostream&) {} + virtual void printStats(std::ostream&) const {} }; /** * @brief evaluate AUC using colIdx-th column as prediction. @@ -165,7 +165,7 @@ public: virtual real evalImp(std::vector& arguments); - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << config_.name() << "=" << calcAuc(); } @@ -189,7 +189,7 @@ private: return (X1 > X2 ? (X1 - X2) : (X2 - X1)) * (Y1 + Y2) / 2.0; } - double calcAuc(); + double calcAuc() const; }; /** @@ -244,7 +244,7 @@ public: virtual real evalImp(std::vector& arguments); - virtual void printStats(std::ostream& os); + virtual void printStats(std::ostream& os) const; virtual void distributeEval(ParameterClient2* client); @@ -339,7 +339,7 @@ public: virtual void finish() { calc(predictArray_); } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { os << " pos/neg" << "=" << pairArray_[0] / ((pairArray_[1] <= 0) ? 1.0 : pairArray_[1]); } diff --git a/paddle/gserver/gradientmachines/MultiNetwork.cpp b/paddle/gserver/gradientmachines/MultiNetwork.cpp index d30ca6f28e..b85d2e0c99 100644 --- a/paddle/gserver/gradientmachines/MultiNetwork.cpp +++ b/paddle/gserver/gradientmachines/MultiNetwork.cpp @@ -154,7 +154,7 @@ public: return -1; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { for (auto& evaluator : evaluators_) { evaluator->printStats(os); os << ' '; diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index 3127b4dd9a..c77b00eb06 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -325,7 +325,7 @@ public: (void)arguments; return -1; } - virtual void printStats(std::ostream& os) { + virtual void printStats(std::ostream& os) const { for (auto& evaluator : evaluators_) { evaluator->printStats(os); os << ' '; diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 54448bdb5a..2afb216db5 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1449,8 +1449,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1463,8 +1463,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, @@ -1493,8 +1493,8 @@ template int BaseMatrixT::applyRow(Agg agg, Op op, Saver sv, BaseMatrixT& b, BaseMatrixT& c) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); CHECK_EQ(c.height_, numRows); @@ -1524,8 +1524,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1538,8 +1538,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - int numRows = b.height_; - int numCols = b.width_; + auto numRows = b.height_; + auto numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index 23c9caccea..9ef7f2b4b5 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -82,8 +82,8 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { template <> MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { - int height = getSize(); - int width = idRange; + auto height = getSize(); + auto width = idRange; MatrixPtr mat = Matrix::createSparseMatrix( height, idRange, height, NO_VALUE, SPARSE_CSR, false, useGpu); @@ -91,7 +91,7 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { cpuIds.copyFrom(*this); int *idData = cpuIds.getData(); - for (int i = 0; i < height; i ++) { + for (decltype(height) i = 0; i < height; i ++) { const unsigned int id = idData[i]; CHECK_LT(id, width); mat->setRow(i, 1, &id, nullptr); diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index c8f37d0bf4..960fca2853 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -1469,7 +1469,6 @@ void ParameterServer2::waitPassFinish(const WaitPassFinishRequest& request, void ParameterServer2::synchronize(const SynchronizeRequest& request, ProtoResponseCallback callback) { - CHECK_LT(request.sync_object_id(), SyncObject_ARRAYSIZE); synchronizeBarriers_[request.sync_object_id()]->wait(); dataSize_ = 0; callback(SynchronizeResponse()); @@ -1477,7 +1476,6 @@ void ParameterServer2::synchronize(const SynchronizeRequest& request, void ParameterServer2::asyncFinishPass(const SynchronizeRequest& request, ProtoResponseCallback callback) { - CHECK_LT(request.sync_object_id(), SyncObject_ARRAYSIZE); synchronizeBarriers_[request.sync_object_id()]->wait(); callback(SynchronizeResponse()); diff --git a/paddle/utils/BarrierStat.cpp b/paddle/utils/BarrierStat.cpp index cbc738a839..f083ef3982 100644 --- a/paddle/utils/BarrierStat.cpp +++ b/paddle/utils/BarrierStat.cpp @@ -29,10 +29,10 @@ P_DEFINE_bool(log_barrier_show_log, false, // for performance tuning insight namespace paddle { -std::ostream &operator<<(std::ostream &output, BarrierStatBase &stat) { +std::ostream &operator<<(std::ostream &output, + const BarrierStatBase &stat) { if (FLAGS_log_barrier_abstract) { - std::lock_guard guard( - const_cast(stat).lock_); + std::lock_guard guard(stat.lock_); stat.showAbstract(output); } return output; @@ -136,7 +136,7 @@ void BarrierEndStat::reset(bool clearRawData) { totAbstract_.minDelta = UINT64_MAX; } -void BarrierEndStat::showAbstract(std::ostream &output) { +void BarrierEndStat::showAbstract(std::ostream &output) const { // do not support the case "<=2 pserver" if (numConnThreads_ <= 2 || !totSamples_) { return; @@ -272,7 +272,7 @@ void BarrierDeltaStat::reset(bool clearRawData) { totAbstract_.minDelta = UINT64_MAX; } -void BarrierDeltaStat::showAbstract(std::ostream &output) { +void BarrierDeltaStat::showAbstract(std::ostream &output) const { // do not support the case "<=2 pserver" if (numConnThreads_ <= 2 || !totSamples_) { return; diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h index 22d6cc9bce..add1093758 100644 --- a/paddle/utils/BarrierStat.h +++ b/paddle/utils/BarrierStat.h @@ -218,11 +218,12 @@ public: } protected: - virtual void showAbstract(std::ostream &output) {} - friend std::ostream &operator<<(std::ostream &output, BarrierStatBase &stat); + virtual void showAbstract(std::ostream &output) const {} + friend std::ostream &operator<<(std::ostream &output, + const BarrierStatBase &stat); protected: - std::mutex lock_; + mutable std::mutex lock_; std::mutex abstractLock_; // see note on updaterStat // each freqency for each barrier trainer std::vector abstract_; @@ -262,7 +263,7 @@ protected: * log_barrier_abstract, log_barrier_lowest_nodes, log_barrier_threshold * control details. */ - virtual void showAbstract(std::ostream &output); + virtual void showAbstract(std::ostream &output) const; private: std::unique_ptr timeVector_; @@ -286,7 +287,7 @@ public: virtual bool checkPassBarrier() { return timeVector_->empty(); } protected: - virtual void showAbstract(std::ostream &outPut); + virtual void showAbstract(std::ostream &outPut) const; private: // store delta time in uint64_t, eg BP time of all trainers diff --git a/paddle/utils/CompilerMacros.h b/paddle/utils/CompilerMacros.h new file mode 100644 index 0000000000..4236d750c4 --- /dev/null +++ b/paddle/utils/CompilerMacros.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 + +#define ATTR_NORETURN __attribute__((noreturn)) diff --git a/paddle/utils/Logging.cpp b/paddle/utils/Logging.cpp index a0644940b5..9a6b1f2d83 100644 --- a/paddle/utils/Logging.cpp +++ b/paddle/utils/Logging.cpp @@ -134,7 +134,7 @@ static void initializeLogFds(char* argv0) { gLogInited = true; } -static void (*gFailureFunctionPtr)() __attribute__((noreturn)) = abort; +static void (*gFailureFunctionPtr)() ATTR_NORETURN = abort; LogMessage::LogMessage(const char* fname, int line, int severity) : fname_(fname), line_(line), severity_(severity) {} @@ -171,7 +171,7 @@ void setMinLogLevel(int level) { paddle::internal::gMinLogLevel = level; } -void installFailureFunction(void (*callback)()) { +void installFailureFunction(void (*callback)() ATTR_NORETURN) { paddle::internal::gFailureFunctionPtr = callback; } diff --git a/paddle/utils/Logging.h b/paddle/utils/Logging.h index 7fdfa3240c..46b6a7feeb 100644 --- a/paddle/utils/Logging.h +++ b/paddle/utils/Logging.h @@ -23,6 +23,7 @@ limitations under the License. */ #include #ifndef PADDLE_USE_GLOG +#include "CompilerMacros.h" //! TODO(yuyang18): Move this utility macro into some global header. #define PP_CAT(a, b) PP_CAT_I(a, b) @@ -168,7 +169,7 @@ void setMinLogLevel(int level); * @brief Install Log(Fatal) failure function. Default is abort(); * @param callback: The failure function. */ -void installFailureFunction(void (*callback)()); +void installFailureFunction(void (*callback)() ATTR_NORETURN); /** * @brief installFailureWriter -- GitLab From 731fe950c4714d5ab3374128cfbbbb24b1aefe78 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 21 Nov 2016 16:37:36 +0800 Subject: [PATCH 0098/1503] Change auto => size_t in BaseMatrix.cu * Because it is a cuda source file, and we need to support c++ 03 in cuda. --- paddle/math/BaseMatrix.cu | 20 ++++++++++---------- paddle/math/Vector.cpp | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 2afb216db5..2f32b3fdd1 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1449,8 +1449,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1463,8 +1463,8 @@ template<> template int BaseMatrixT::applyRow(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, @@ -1493,8 +1493,8 @@ template int BaseMatrixT::applyRow(Agg agg, Op op, Saver sv, BaseMatrixT& b, BaseMatrixT& c) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(height_, numRows); CHECK_EQ(width_, 1UL); CHECK_EQ(c.height_, numRows); @@ -1524,8 +1524,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), base::binary::second(), b, numRows, @@ -1538,8 +1538,8 @@ template<> template int BaseMatrixT::applyCol(Agg agg, Saver sv, BaseMatrixT& b) { MatrixOffset offset(0, 0, 0, 0, 0, 0); - auto numRows = b.height_; - auto numCols = b.width_; + size_t numRows = b.height_; + size_t numCols = b.width_; CHECK_EQ(width_, numCols); CHECK_EQ(height_, 1UL); aggregate(agg, base::unary::identity(), sv, b, numRows, numCols, offset, diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index 9ef7f2b4b5..68a1518d67 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -82,8 +82,8 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { template <> MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { - auto height = getSize(); - auto width = idRange; + size_t height = getSize(); + size_t width = idRange; MatrixPtr mat = Matrix::createSparseMatrix( height, idRange, height, NO_VALUE, SPARSE_CSR, false, useGpu); -- GitLab From c6eeb650af3ed249dfa92b13ba8b1f0ebc9b0b13 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 22 Nov 2016 10:56:45 +0800 Subject: [PATCH 0099/1503] Add clang-format hooks --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9385943da9..c1ae1058c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,7 @@ # not all of our python code is runnable. Some are used for # documenation # - id: debug-statements +- repo: https://github.com/PaddlePaddle/clang-format-pre-commit-hook.git + sha: 38b232ea0fd2be46c26a691b9f32dd94d1ee3333 + hooks: + - id: clang-formater -- GitLab From 5e3dd38143769d79e93009dfcb5b819af564689f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 22 Nov 2016 11:06:28 +0800 Subject: [PATCH 0100/1503] auto-update pre-commit --- .pre-commit-config.yaml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1ae1058c2..90c25e4350 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,22 +7,14 @@ hooks: - id: yapf - repo: https://github.com/pre-commit/pre-commit-hooks - sha: 4ef03c4223ad322c7adaa6c6c0efb26b57df3b71 + sha: 7539d8bd1a00a3c1bfd34cdb606d3a6372e83469 hooks: - id: check-added-large-files - id: check-merge-conflict - id: check-symlinks - id: detect-private-key - id: end-of-file-fixer -# TODO(yuyang): trailing whitespace has some bugs on markdown -# files now, please not add it to pre-commit hook now -# - id: trailing-whitespace -# -# TODO(yuyang): debug-statements not fit for Paddle, because -# not all of our python code is runnable. Some are used for -# documenation -# - id: debug-statements - repo: https://github.com/PaddlePaddle/clang-format-pre-commit-hook.git - sha: 38b232ea0fd2be46c26a691b9f32dd94d1ee3333 + sha: 28c0ea8a67a3e2dbbf4822ef44e85b63a0080a29 hooks: - id: clang-formater -- GitLab From 80c68d38ff3c59e12f48b4e4e88c24c89568fc0a Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 22 Nov 2016 17:58:15 +0800 Subject: [PATCH 0101/1503] clang format .cc .h .cpp .c and .hpp file --- paddle/api/Arguments.cpp | 3 +- paddle/api/ConfigParser.cpp | 4 +- paddle/api/GradientMachine.cpp | 25 +- paddle/api/Internal.h | 8 +- paddle/api/Matrix.cpp | 51 +- paddle/api/PaddleAPI.h | 88 +- paddle/api/Parameter.cpp | 1 - paddle/api/ParameterOptimizer.cpp | 36 +- paddle/api/SequenceGenerator.cpp | 10 +- paddle/api/Trainer.cpp | 16 +- paddle/api/Util.cpp | 8 +- paddle/api/Vector.cpp | 21 +- paddle/cuda/include/hl_activation_functions.h | 14 +- paddle/cuda/include/hl_aggregate.h | 1 - paddle/cuda/include/hl_avx_functions.h | 19 +- paddle/cuda/include/hl_base.h | 101 +- paddle/cuda/include/hl_batch_transpose.h | 8 +- paddle/cuda/include/hl_cnn.h | 242 ++-- paddle/cuda/include/hl_cuda.h | 23 +- paddle/cuda/include/hl_cuda_cublas.h | 78 +- paddle/cuda/include/hl_cuda_cudnn.h | 100 +- paddle/cuda/include/hl_dso_loader.h | 1 - paddle/cuda/include/hl_functions.h | 35 +- paddle/cuda/include/hl_gpu.h | 1 - paddle/cuda/include/hl_lstm.h | 1 - paddle/cuda/include/hl_matrix.h | 66 +- paddle/cuda/include/hl_sequence.h | 28 +- paddle/cuda/include/hl_sparse.h | 78 +- paddle/cuda/include/hl_table_apply.h | 20 +- paddle/cuda/include/hl_time.h | 1 - paddle/cuda/include/hl_top_k.h | 14 +- paddle/cuda/include/stub/hl_aggregate_stub.h | 19 +- paddle/cuda/include/stub/hl_cnn_stub.h | 244 ++-- .../cuda/include/stub/hl_cuda_cublas_stub.h | 59 +- paddle/cuda/include/stub/hl_cuda_cudnn_stub.h | 171 ++- paddle/cuda/include/stub/hl_cuda_stub.h | 27 +- paddle/cuda/include/stub/hl_lstm_stub.h | 1 - paddle/cuda/include/stub/hl_matrix_stub.h | 60 +- paddle/cuda/include/stub/hl_sequence_stub.h | 22 +- paddle/cuda/include/stub/hl_sparse_stub.h | 80 +- paddle/cuda/src/avx_mathfun.h | 506 +++---- paddle/cuda/src/hl_avx_functions.cc | 84 +- paddle/cuda/src/hl_cpu_functions.cc | 61 +- paddle/cuda/src/hl_cuda_cublas.cc | 316 +++-- paddle/cuda/src/hl_cuda_cudnn.cc | 1246 +++++++++-------- paddle/cuda/src/hl_cuda_device.cc | 405 +++--- paddle/cuda/src/hl_cudart_wrap.cc | 196 ++- paddle/cuda/src/hl_math.cc | 17 +- paddle/cuda/src/hl_time.cc | 8 +- .../activations/ActivationFunction.cpp | 70 +- .../gserver/activations/ActivationFunction.h | 1 - paddle/gserver/dataproviders/DataProvider.cpp | 21 +- paddle/gserver/dataproviders/DataProvider.h | 40 +- .../gserver/dataproviders/DataProviderGroup.h | 10 +- .../dataproviders/MultiDataProvider.cpp | 7 +- .../gserver/dataproviders/MultiDataProvider.h | 1 - .../dataproviders/ProtoDataProvider.cpp | 129 +- .../gserver/dataproviders/ProtoDataProvider.h | 10 +- paddle/gserver/dataproviders/ProtoReader.h | 4 +- .../gserver/dataproviders/PyDataProvider.cpp | 133 +- paddle/gserver/dataproviders/PyDataProvider.h | 22 +- .../gserver/dataproviders/PyDataProvider2.cpp | 312 ++--- .../gserver/evaluators/CTCErrorEvaluator.cpp | 18 +- paddle/gserver/evaluators/ChunkEvaluator.cpp | 3 +- paddle/gserver/evaluators/Evaluator.cpp | 69 +- paddle/gserver/evaluators/Evaluator.h | 25 +- .../gradientmachines/GradientMachine.cpp | 13 +- .../gradientmachines/GradientMachine.h | 14 +- .../gradientmachines/GradientMachineMode.h | 43 +- .../gradientmachines/MultiGradientMachine.cpp | 180 +-- .../gradientmachines/MultiGradientMachine.h | 179 +-- .../gserver/gradientmachines/MultiNetwork.cpp | 11 +- .../gserver/gradientmachines/MultiNetwork.h | 13 +- .../gradientmachines/NeuralNetwork.cpp | 81 +- .../gserver/gradientmachines/NeuralNetwork.h | 22 +- .../ParallelNeuralNetwork.cpp | 18 +- .../gradientmachines/ParallelNeuralNetwork.h | 22 +- .../RecurrentGradientMachine.cpp | 145 +- .../RecurrentGradientMachine.h | 41 +- paddle/gserver/layers/AddtoLayer.cpp | 1 - paddle/gserver/layers/AddtoLayer.h | 24 +- paddle/gserver/layers/AgentLayer.cpp | 36 +- paddle/gserver/layers/AgentLayer.h | 24 +- paddle/gserver/layers/AverageLayer.cpp | 4 +- paddle/gserver/layers/BatchNormBaseLayer.cpp | 1 - paddle/gserver/layers/BatchNormBaseLayer.h | 13 +- .../layers/BatchNormalizationLayer.cpp | 77 +- .../gserver/layers/BatchNormalizationLayer.h | 1 - paddle/gserver/layers/BilinearInterpLayer.cpp | 30 +- paddle/gserver/layers/BlockExpandLayer.cpp | 67 +- paddle/gserver/layers/BlockExpandLayer.h | 1 - paddle/gserver/layers/CRFDecodingLayer.cpp | 4 +- paddle/gserver/layers/CRFDecodingLayer.h | 1 - paddle/gserver/layers/CRFLayer.cpp | 16 +- paddle/gserver/layers/CRFLayer.h | 3 +- paddle/gserver/layers/CTCLayer.cpp | 25 +- paddle/gserver/layers/CTCLayer.h | 4 +- paddle/gserver/layers/ConcatenateLayer.cpp | 7 +- paddle/gserver/layers/ContextProjection.cpp | 46 +- paddle/gserver/layers/ContextProjection.h | 4 +- paddle/gserver/layers/ConvBaseLayer.cpp | 49 +- paddle/gserver/layers/ConvBaseLayer.h | 1 - paddle/gserver/layers/ConvOperator.cpp | 101 +- paddle/gserver/layers/ConvProjection.cpp | 128 +- paddle/gserver/layers/ConvProjection.h | 13 +- paddle/gserver/layers/ConvShiftLayer.cpp | 1 - .../gserver/layers/ConvexCombinationLayer.cpp | 22 +- paddle/gserver/layers/CosSimLayer.cpp | 10 +- paddle/gserver/layers/CosSimLayer.h | 4 +- paddle/gserver/layers/CosSimVecMatLayer.cpp | 53 +- paddle/gserver/layers/CostLayer.cpp | 119 +- paddle/gserver/layers/CostLayer.h | 11 +- paddle/gserver/layers/CudnnBatchNormLayer.cpp | 47 +- paddle/gserver/layers/CudnnBatchNormLayer.h | 4 +- paddle/gserver/layers/CudnnConvLayer.cpp | 24 +- paddle/gserver/layers/CudnnConvLayer.h | 1 - paddle/gserver/layers/CudnnPoolLayer.cpp | 23 +- paddle/gserver/layers/CudnnPoolLayer.h | 13 +- paddle/gserver/layers/DataLayer.cpp | 13 +- paddle/gserver/layers/DataLayer.h | 11 +- paddle/gserver/layers/DataNormLayer.cpp | 33 +- paddle/gserver/layers/DataNormLayer.h | 1 - paddle/gserver/layers/DotMulOperator.cpp | 5 +- paddle/gserver/layers/DotMulProjection.cpp | 7 +- paddle/gserver/layers/EosIdCheckLayer.cpp | 3 +- paddle/gserver/layers/ExpandConvBaseLayer.cpp | 105 +- paddle/gserver/layers/ExpandConvBaseLayer.h | 3 +- paddle/gserver/layers/ExpandConvLayer.cpp | 2 - paddle/gserver/layers/ExpandConvLayer.h | 5 +- .../gserver/layers/ExpandConvTransLayer.cpp | 4 +- paddle/gserver/layers/ExpandConvTransLayer.h | 5 +- .../gserver/layers/FeatureMapExpandLayer.cpp | 19 +- .../gserver/layers/FullMatrixProjection.cpp | 1 - paddle/gserver/layers/FullMatrixProjection.h | 3 +- paddle/gserver/layers/FullyConnectedLayer.cpp | 1 - paddle/gserver/layers/FullyConnectedLayer.h | 8 +- paddle/gserver/layers/GatedRecurrentLayer.cpp | 102 +- paddle/gserver/layers/GatedRecurrentLayer.h | 20 +- paddle/gserver/layers/GetOutputLayer.cpp | 1 - paddle/gserver/layers/GruCompute.cpp | 31 +- paddle/gserver/layers/GruCompute.h | 5 +- paddle/gserver/layers/GruStepLayer.cpp | 31 +- .../layers/HierarchicalSigmoidLayer.cpp | 31 +- .../gserver/layers/HierarchicalSigmoidLayer.h | 15 +- paddle/gserver/layers/IdentityProjection.cpp | 7 +- paddle/gserver/layers/InterpolationLayer.cpp | 5 +- paddle/gserver/layers/Layer.cpp | 54 +- paddle/gserver/layers/Layer.h | 39 +- paddle/gserver/layers/LinearChainCRF.cpp | 1 - paddle/gserver/layers/LinearChainCRF.h | 4 +- paddle/gserver/layers/LinearChainCTC.cpp | 15 +- paddle/gserver/layers/LinearChainCTC.h | 9 +- paddle/gserver/layers/LstmCompute.cpp | 32 +- paddle/gserver/layers/LstmCompute.h | 7 +- paddle/gserver/layers/LstmLayer.cpp | 261 ++-- paddle/gserver/layers/LstmLayer.h | 26 +- paddle/gserver/layers/LstmStepLayer.cpp | 74 +- paddle/gserver/layers/MDLstmLayer.cpp | 255 ++-- paddle/gserver/layers/MaxIdLayer.cpp | 5 +- paddle/gserver/layers/MaxLayer.cpp | 4 +- paddle/gserver/layers/MaxLayer.h | 1 - paddle/gserver/layers/MixedLayer.cpp | 8 +- paddle/gserver/layers/MixedLayer.h | 7 +- paddle/gserver/layers/MultinomialSampler.cpp | 1 - paddle/gserver/layers/MultinomialSampler.h | 1 - paddle/gserver/layers/MultiplexLayer.cpp | 1 - paddle/gserver/layers/NCELayer.cpp | 28 +- paddle/gserver/layers/NormLayer.cpp | 1 - paddle/gserver/layers/NormLayer.h | 5 +- paddle/gserver/layers/NormProjectionLayer.cpp | 16 +- paddle/gserver/layers/NormProjectionLayer.h | 1 - paddle/gserver/layers/Operator.cpp | 1 - paddle/gserver/layers/Operator.h | 7 +- paddle/gserver/layers/OuterProdLayer.cpp | 14 +- paddle/gserver/layers/ParameterReluLayer.cpp | 5 +- paddle/gserver/layers/ParameterReluLayer.h | 1 - paddle/gserver/layers/PoolLayer.cpp | 1 - paddle/gserver/layers/PoolLayer.h | 1 - paddle/gserver/layers/PoolProjection.cpp | 76 +- paddle/gserver/layers/PoolProjection.h | 12 +- paddle/gserver/layers/PoolProjectionLayer.cpp | 11 +- paddle/gserver/layers/PowerLayer.cpp | 3 +- paddle/gserver/layers/PrintLayer.cpp | 3 +- paddle/gserver/layers/Projection.cpp | 4 +- paddle/gserver/layers/Projection.h | 6 +- paddle/gserver/layers/RecurrentLayer.cpp | 63 +- paddle/gserver/layers/RecurrentLayerGroup.cpp | 12 +- paddle/gserver/layers/ResizeLayer.cpp | 9 +- paddle/gserver/layers/ScalingLayer.cpp | 3 +- paddle/gserver/layers/ScalingProjection.cpp | 12 +- .../layers/SelectiveFullyConnectedLayer.cpp | 113 +- .../layers/SelectiveFullyConnectedLayer.h | 5 +- paddle/gserver/layers/SequenceConcatLayer.cpp | 17 +- .../layers/SequenceLastInstanceLayer.cpp | 1 - paddle/gserver/layers/SequencePoolLayer.cpp | 2 +- .../gserver/layers/SequenceReshapeLayer.cpp | 15 +- paddle/gserver/layers/SequenceToBatch.cpp | 56 +- paddle/gserver/layers/SequenceToBatch.h | 16 +- paddle/gserver/layers/SlopeInterceptLayer.cpp | 7 +- .../layers/SpatialPyramidPoolLayer.cpp | 3 +- .../gserver/layers/SpatialPyramidPoolLayer.h | 9 +- paddle/gserver/layers/SubSequenceLayer.cpp | 17 +- paddle/gserver/layers/SumToOneNormLayer.cpp | 3 +- paddle/gserver/layers/TableProjection.cpp | 4 +- paddle/gserver/layers/TableProjection.h | 4 +- paddle/gserver/layers/TensorLayer.cpp | 9 +- paddle/gserver/layers/TensorLayer.h | 1 - paddle/gserver/layers/TransLayer.cpp | 1 - paddle/gserver/layers/TransLayer.h | 1 - .../layers/TransposedFullMatrixProjection.cpp | 4 +- paddle/gserver/layers/ValidationLayer.cpp | 8 +- paddle/gserver/tests/LayerGradUtil.cpp | 192 ++- paddle/gserver/tests/LayerGradUtil.h | 103 +- paddle/gserver/tests/TestUtil.cpp | 26 +- paddle/gserver/tests/TestUtil.h | 27 +- paddle/gserver/tests/test_ActivationGrad.cpp | 4 +- paddle/gserver/tests/test_ConvTrans.cpp | 386 ++--- paddle/gserver/tests/test_Evaluator.cpp | 26 +- paddle/gserver/tests/test_LayerGrad.cpp | 252 +++- paddle/gserver/tests/test_LinearChainCRF.cpp | 1 - .../gserver/tests/test_MultinomialSampler.cpp | 4 +- paddle/gserver/tests/test_NetworkCompare.cpp | 31 +- .../gserver/tests/test_ProtoDataProvider.cpp | 85 +- paddle/gserver/tests/test_PyDataProvider.cpp | 8 +- paddle/gserver/tests/test_PyDataProvider2.cpp | 96 +- .../tests/test_RecurrentGradientMachine.cpp | 32 +- paddle/gserver/tests/test_RecurrentLayer.cpp | 87 +- .../gserver/tests/test_SelectiveFCLayer.cpp | 158 ++- paddle/math/Allocator.h | 27 +- paddle/math/BaseMatrix.h | 145 +- paddle/math/CpuSparseMatrix.cpp | 149 +- paddle/math/CpuSparseMatrix.h | 48 +- paddle/math/ExecViaCpu.h | 16 +- paddle/math/MathFunctions.cpp | 197 ++- paddle/math/MathFunctions.h | 60 +- paddle/math/MathUtils.cpp | 21 +- paddle/math/MathUtils.h | 12 +- paddle/math/Matrix.cpp | 1143 ++++++++++----- paddle/math/Matrix.h | 799 +++++++---- paddle/math/MatrixBitCode.cpp | 76 +- paddle/math/MemoryHandle.cpp | 11 +- paddle/math/MemoryHandle.h | 7 +- paddle/math/PoolAllocator.cpp | 8 +- paddle/math/PoolAllocator.h | 1 - paddle/math/SIMDFunctions.cpp | 18 +- paddle/math/SIMDFunctions.h | 10 +- paddle/math/SparseMatrix.cpp | 263 ++-- paddle/math/SparseMatrix.h | 69 +- paddle/math/SparseRowMatrix.cpp | 73 +- paddle/math/SparseRowMatrix.h | 47 +- paddle/math/Storage.cpp | 23 +- paddle/math/Vector.cpp | 132 +- paddle/math/Vector.h | 45 +- paddle/math/tests/test_Allocator.cpp | 8 +- paddle/math/tests/test_ExecViaCpu.cpp | 22 +- paddle/math/tests/test_FPException.cpp | 11 +- paddle/math/tests/test_SIMDFunctions.cpp | 10 +- paddle/math/tests/test_batchTranspose.cpp | 5 +- paddle/math/tests/test_matrix.cpp | 136 +- paddle/math/tests/test_matrixCompare.cpp | 466 +++--- paddle/math/tests/test_matrixUtil.h | 6 +- paddle/math/tests/test_perturbation.cpp | 174 ++- .../math/tests/test_sparseMatrixCompare.cpp | 2 +- paddle/parameter/Argument.cpp | 219 +-- paddle/parameter/Argument.h | 39 +- paddle/parameter/AverageOptimizer.cpp | 40 +- paddle/parameter/AverageOptimizer.h | 16 +- paddle/parameter/FirstOrderOptimizer.cpp | 81 +- paddle/parameter/FirstOrderOptimizer.h | 48 +- paddle/parameter/LearningRateScheduler.cpp | 1 - paddle/parameter/LearningRateScheduler.h | 8 +- paddle/parameter/OptimizerFunctions.cpp | 16 +- paddle/parameter/OptimizerFunctions.h | 4 +- paddle/parameter/OptimizerWithRegularizer.cpp | 59 +- paddle/parameter/OptimizerWithRegularizer.h | 19 +- paddle/parameter/ParallelParameter.cpp | 4 +- paddle/parameter/ParallelParameter.h | 20 +- paddle/parameter/Parameter.cpp | 139 +- paddle/parameter/Parameter.h | 18 +- paddle/parameter/ParameterOptimizer.cpp | 1 - paddle/parameter/ParameterOptimizer.h | 139 +- paddle/parameter/ParameterUpdateFunctions.cpp | 56 +- paddle/parameter/ParameterUpdateFunctions.h | 30 +- paddle/parameter/ParameterUpdaterBase.cpp | 1 - paddle/parameter/ParameterUpdaterBase.h | 1 - paddle/parameter/ParameterUpdaterHook.cpp | 4 +- paddle/parameter/ParameterUpdaterHook.h | 1 - paddle/parameter/Regularizer.cpp | 6 +- paddle/parameter/Regularizer.h | 46 +- paddle/parameter/Weight.cpp | 14 +- paddle/parameter/tests/test_common.cpp | 72 +- paddle/pserver/BaseClient.cpp | 1 - paddle/pserver/BaseClient.h | 43 +- paddle/pserver/LightNetwork.cpp | 43 +- paddle/pserver/LightNetwork.h | 11 +- paddle/pserver/ParameterClient2.cpp | 115 +- paddle/pserver/ParameterClient2.h | 97 +- paddle/pserver/ParameterServer2.cpp | 157 ++- paddle/pserver/ParameterServer2.h | 50 +- paddle/pserver/ProtoServer.cpp | 5 +- paddle/pserver/ProtoServer.h | 149 +- paddle/pserver/RDMANetwork.h | 4 +- paddle/pserver/SocketChannel.cpp | 47 +- paddle/pserver/SocketChannel.h | 1 - .../pserver/SparseParameterDistribution.cpp | 16 +- paddle/pserver/test/SocketTest.cpp | 4 +- paddle/pserver/test/test_ParameterServer2.cpp | 14 +- paddle/pserver/test/test_ProtoServer.cpp | 8 +- paddle/trainer/ParamUtil.cpp | 21 +- paddle/trainer/ParamUtil.h | 25 +- paddle/trainer/ParameterUpdater.cpp | 4 +- paddle/trainer/ParameterUpdater.h | 13 +- paddle/trainer/RemoteParameterUpdater.cpp | 44 +- paddle/trainer/RemoteParameterUpdater.h | 26 +- paddle/trainer/Tester.cpp | 97 +- paddle/trainer/Tester.h | 11 +- paddle/trainer/TesterConfig.h | 1 - paddle/trainer/ThreadParameterUpdater.cpp | 49 +- paddle/trainer/ThreadParameterUpdater.h | 10 +- paddle/trainer/Trainer.cpp | 155 +- paddle/trainer/Trainer.h | 19 +- paddle/trainer/TrainerConfigHelper.cpp | 37 +- paddle/trainer/TrainerConfigHelper.h | 19 +- paddle/trainer/TrainerInternal.cpp | 110 +- paddle/trainer/TrainerInternal.h | 26 +- paddle/trainer/TrainerInternalConfig.cpp | 3 +- paddle/trainer/TrainerInternalConfig.h | 13 +- paddle/trainer/TrainerMain.cpp | 3 +- paddle/trainer/tests/picojson.h | 20 +- paddle/trainer/tests/test_Compare.cpp | 4 +- paddle/trainer/tests/test_CompareSparse.cpp | 38 +- paddle/trainer/tests/test_CompareTwoNets.cpp | 40 +- paddle/trainer/tests/test_CompareTwoOpts.cpp | 34 +- paddle/trainer/tests/test_Prediction.cpp | 13 +- .../tests/test_PyDataProviderWrapper.cpp | 1 - paddle/trainer/tests/test_Trainer.cpp | 6 +- paddle/trainer/tests/test_TrainerOnePass.cpp | 35 +- .../test_recurrent_machine_generation.cpp | 20 +- paddle/utils/BarrierStat.cpp | 18 +- paddle/utils/BarrierStat.h | 109 +- paddle/utils/ClassRegistrar.h | 11 +- paddle/utils/CommandLineParser.cpp | 44 +- paddle/utils/CommandLineParser.h | 7 +- paddle/utils/CustomStackTrace.cpp | 36 +- paddle/utils/CustomStackTrace.h | 27 +- paddle/utils/DisableCopy.h | 7 +- paddle/utils/Excepts.cpp | 12 +- paddle/utils/Flags.cpp | 31 +- paddle/utils/Flags.h | 1 - paddle/utils/GlobalConstants.cpp | 1 - paddle/utils/GlobalConstants.h | 9 +- paddle/utils/Locks.h | 49 +- paddle/utils/Logging.cpp | 26 +- paddle/utils/Logging.h | 8 +- paddle/utils/PythonUtil.cpp | 39 +- paddle/utils/PythonUtil.h | 51 +- paddle/utils/Queue.h | 17 +- paddle/utils/Stat.h | 29 +- paddle/utils/StringUtil.h | 3 - paddle/utils/Thread.h | 54 +- paddle/utils/ThreadLocal.cpp | 3 +- paddle/utils/ThreadLocal.h | 5 +- paddle/utils/TypeDefs.h | 1 - paddle/utils/Util.cpp | 70 +- paddle/utils/Util.h | 54 +- paddle/utils/Version.cpp | 25 +- paddle/utils/Version.h | 16 +- paddle/utils/arch/linux/Locks.cpp | 32 +- paddle/utils/arch/osx/Locks.cpp | 28 +- paddle/utils/tests/test_CommandLineParser.cpp | 17 +- paddle/utils/tests/test_CustomStackTrace.cpp | 43 +- .../tests/test_CustomStackTracePrint.cpp | 2 +- paddle/utils/tests/test_Logging.cpp | 6 +- paddle/utils/tests/test_SpinLock.cpp | 24 +- paddle/utils/tests/test_StringUtils.cpp | 1 - paddle/utils/tests/test_Thread.cpp | 31 +- paddle/utils/tests/test_ThreadBarrier.cpp | 60 +- 377 files changed, 10846 insertions(+), 7850 deletions(-) diff --git a/paddle/api/Arguments.cpp b/paddle/api/Arguments.cpp index 6f51d55120..b539374cd4 100644 --- a/paddle/api/Arguments.cpp +++ b/paddle/api/Arguments.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "PaddleAPIPrivate.h" @@ -112,7 +111,7 @@ void Arguments::setSlotSequenceStartPositions(size_t idx, } void Arguments::setSlotSubSequenceStartPositions( - size_t idx, IVector *vec) throw(RangeError) { + size_t idx, IVector* vec) throw(RangeError) { auto& a = m->getArg(idx); auto& v = m->cast(vec->getSharedPtr()); a.subSequenceStartPositions = std::make_shared(v); diff --git a/paddle/api/ConfigParser.cpp b/paddle/api/ConfigParser.cpp index 25d94f5a6a..bc40d871d1 100644 --- a/paddle/api/ConfigParser.cpp +++ b/paddle/api/ConfigParser.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "PaddleAPIPrivate.h" #include "paddle/trainer/Trainer.h" @@ -44,8 +43,7 @@ TrainerConfig* TrainerConfig::createFromTrainerConfigFile( return retv; } -TrainerConfig* TrainerConfig::createFromProtoString( - const std::string& str) { +TrainerConfig* TrainerConfig::createFromProtoString(const std::string& str) { auto retv = new TrainerConfig(); paddle::TrainerConfig trainerConfigProto; auto conf = std::make_shared(trainerConfigProto); diff --git a/paddle/api/GradientMachine.cpp b/paddle/api/GradientMachine.cpp index bef499c678..9a4846d809 100644 --- a/paddle/api/GradientMachine.cpp +++ b/paddle/api/GradientMachine.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "PaddleAPIPrivate.h" @@ -27,7 +26,8 @@ GradientMachine::GradientMachine() : m(new GradientMachinePrivate()) {} GradientMachine::~GradientMachine() { delete m; } GradientMachine* GradientMachine::createFromPaddleModelPtr( - const void* confPtr, GradientMatchineCreateMode mode, + const void* confPtr, + GradientMatchineCreateMode mode, const std::vector& types) { auto& conf = *(const paddle::ModelConfig*)(confPtr); std::vector realTypes; @@ -44,7 +44,8 @@ GradientMachine* GradientMachine::createFromPaddleModelPtr( } GradientMachine* GradientMachine::createByConfigProtoStr( - const std::string& protoStr, GradientMatchineCreateMode mode, + const std::string& protoStr, + GradientMatchineCreateMode mode, const std::vector& types) { paddle::ModelConfig conf; conf.ParseFromString(protoStr); @@ -56,13 +57,15 @@ GradientMachine* GradientMachine::createByConfigProtoStr( } GradientMachine* GradientMachine::createByModelConfig( - ModelConfig* conf, GradientMatchineCreateMode mode, + ModelConfig* conf, + GradientMatchineCreateMode mode, const std::vector& types) { auto confPtr = &conf->m->conf->getModelConfig(); return GradientMachine::createFromPaddleModelPtr(confPtr, mode, types); } -void GradientMachine::forward(const Arguments& inArgs, Arguments* outArgs, +void GradientMachine::forward(const Arguments& inArgs, + Arguments* outArgs, PassType passType) { auto& in = m->cast>(inArgs.getInternalArgumentsPtr()); @@ -99,7 +102,8 @@ void GradientMachine::backward(const UpdateCallback& callback) { } void GradientMachine::forwardBackward(const Arguments& inArgs, - Arguments* outArgs, PassType passType, + Arguments* outArgs, + PassType passType, const UpdateCallback& callback) { auto& in = m->cast>(inArgs.getInternalArgumentsPtr()); @@ -129,7 +133,7 @@ Parameter* GradientMachine::getParameter(size_t i) throw(RangeError) { void GradientMachine::randParameters() { m->machine->randParameters(); } Matrix* GradientMachine::getLayerOutput(const std::string& layerName) const - throw(UnsupportError) { + throw(UnsupportError) { auto nn = std::dynamic_pointer_cast(m->machine); if (nn) { auto mat = nn->getLayerOutput(layerName); @@ -140,8 +144,11 @@ Matrix* GradientMachine::getLayerOutput(const std::string& layerName) const } SequenceGenerator* GradientMachine::asSequenceGenerator( - const std::vector& dict, size_t begin_id, size_t end_id, - size_t max_length, size_t beam_size) { + const std::vector& dict, + size_t begin_id, + size_t end_id, + size_t max_length, + size_t beam_size) { SequenceGenerator* r = SequenceGenerator::createByGradientMachineSharedPtr(&m->machine); r->setDict(dict); diff --git a/paddle/api/Internal.h b/paddle/api/Internal.h index b990f650be..66a13bc603 100644 --- a/paddle/api/Internal.h +++ b/paddle/api/Internal.h @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" @@ -23,7 +22,8 @@ limitations under the License. */ template void staticCastVector(std::vector* dest, const std::vector& src) { dest->resize(src.size()); - std::transform(src.begin(), src.end(), dest->begin(), [](T1 t){ - return static_cast(t); - }); + std::transform(src.begin(), + src.end(), + dest->begin(), + [](T1 t) { return static_cast(t); }); } diff --git a/paddle/api/Matrix.cpp b/paddle/api/Matrix.cpp index e5493a381a..f257ee65aa 100644 --- a/paddle/api/Matrix.cpp +++ b/paddle/api/Matrix.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "paddle/math/Matrix.h" #include "paddle/math/SparseMatrix.h" @@ -44,17 +43,21 @@ Matrix* Matrix::createZero(size_t height, size_t width, bool useGpu) { return m; } -Matrix* Matrix::createDense(const std::vector& data, size_t height, - size_t width, bool useGpu) { +Matrix* Matrix::createDense(const std::vector& data, + size_t height, + size_t width, + bool useGpu) { auto m = new Matrix(); m->m->mat = paddle::Matrix::create(height, width, useGpu); m->m->mat->copyFrom(data.data(), data.size()); return m; } -Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, - bool copy, bool useGpu) - throw (UnsupportError) { +Matrix* Matrix::createDenseFromNumpy(float* data, + int dim1, + int dim2, + bool copy, + bool useGpu) throw(UnsupportError) { if (useGpu) { /// Gpu mode only supports copy=True if (!copy) { @@ -66,7 +69,9 @@ Matrix* Matrix::createDenseFromNumpy(float* data, int dim1, int dim2, } } -Matrix* Matrix::createCpuDenseFromNumpy(float* data, int dim1, int dim2, +Matrix* Matrix::createCpuDenseFromNumpy(float* data, + int dim1, + int dim2, bool copy) { auto m = new Matrix(); if (copy) { @@ -85,12 +90,20 @@ Matrix* Matrix::createGpuDenseFromNumpy(float* data, int dim1, int dim2) { return m; } -Matrix* Matrix::createSparse(size_t height, size_t width, size_t nnz, - bool isNonVal, bool isTrans, bool useGpu) { +Matrix* Matrix::createSparse(size_t height, + size_t width, + size_t nnz, + bool isNonVal, + bool isTrans, + bool useGpu) { auto m = new Matrix(); m->m->mat = paddle::Matrix::createSparseMatrix( - height, width, nnz, isNonVal ? paddle::NO_VALUE : paddle::FLOAT_VALUE, - isTrans, useGpu); + height, + width, + nnz, + isNonVal ? paddle::NO_VALUE : paddle::FLOAT_VALUE, + isTrans, + useGpu); return m; } @@ -221,7 +234,8 @@ FloatArray Matrix::getData() const { } void Matrix::sparseCopyFrom( - const std::vector& rows, const std::vector& cols, + const std::vector& rows, + const std::vector& cols, const std::vector& vals) throw(UnsupportError) { auto cpuSparseMat = std::dynamic_pointer_cast(m->mat); @@ -240,7 +254,8 @@ void Matrix::sparseCopyFrom( void* Matrix::getSharedPtr() const { return &m->mat; } -void Matrix::toNumpyMatInplace(float** view_data, int* dim1, +void Matrix::toNumpyMatInplace(float** view_data, + int* dim1, int* dim2) throw(UnsupportError) { auto cpuMat = std::dynamic_pointer_cast(m->mat); if (cpuMat) { @@ -251,7 +266,8 @@ void Matrix::toNumpyMatInplace(float** view_data, int* dim1, throw UnsupportError(); } } -void Matrix::copyToNumpyMat(float** view_m_data, int* dim1, +void Matrix::copyToNumpyMat(float** view_m_data, + int* dim1, int* dim2) throw(UnsupportError) { static_assert(sizeof(paddle::real) == sizeof(float), "Currently PaddleAPI only support for single " @@ -269,8 +285,8 @@ void Matrix::copyToNumpyMat(float** view_m_data, int* dim1, } else if (auto gpuMat = dynamic_cast(m->mat.get())) { auto src = gpuMat->getData(); auto dest = *view_m_data; - hl_memcpy_device2host(dest, src, - sizeof(paddle::real) * (*dim1) * (*dim2)); + hl_memcpy_device2host( + dest, src, sizeof(paddle::real) * (*dim1) * (*dim2)); } else { LOG(WARNING) << "Unexpected Situation"; throw UnsupportError(); @@ -278,7 +294,8 @@ void Matrix::copyToNumpyMat(float** view_m_data, int* dim1, } } -void Matrix::copyFromNumpyMat(float* data, int dim1, +void Matrix::copyFromNumpyMat(float* data, + int dim1, int dim2) throw(UnsupportError, RangeError) { if (isSparse()) { throw UnsupportError(); diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 5688ece44d..c07facdb12 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -61,8 +60,8 @@ class RangeError {}; /// Not support Error, such as access GPU memory directly, etc. class UnsupportError : public std::runtime_error { public: - UnsupportError() : std::runtime_error(" ") {}; - UnsupportError(const std::string& message) : std::runtime_error(message) {}; + UnsupportError() : std::runtime_error(" "){}; + UnsupportError(const std::string& message) : std::runtime_error(message){}; }; /// This type will map to python's list of float. @@ -112,7 +111,8 @@ public: /** * Create A Matrix with height,width, which is filled by zero. */ - static Matrix* createZero(size_t height, size_t width, + static Matrix* createZero(size_t height, + size_t width, bool useGpu = isUsingGpu()); /** @@ -124,8 +124,11 @@ public: * * @note the default sparse type is SPARSE_CSR. */ - static Matrix* createSparse(size_t height, size_t width, size_t nnz, - bool isNonVal = true, bool trans = false, + static Matrix* createSparse(size_t height, + size_t width, + size_t nnz, + bool isNonVal = true, + bool trans = false, bool useGpu = isUsingGpu()); /** @@ -134,13 +137,17 @@ public: * @param data list of float should be passed in python. * @note the value will be copy into a new matrix. */ - static Matrix* createDense(const std::vector& data, size_t height, - size_t width, bool useGpu = isUsingGpu()); - - static Matrix* createDenseFromNumpy(float* data, int dim1, int dim2, - bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError); + static Matrix* createDense(const std::vector& data, + size_t height, + size_t width, + bool useGpu = isUsingGpu()); + + static Matrix* createDenseFromNumpy( + float* data, + int dim1, + int dim2, + bool copy = true, + bool useGpu = isUsingGpu()) throw(UnsupportError); /** * Create Cpu Dense Matrix from numpy matrix, dtype=float32 @@ -151,7 +158,9 @@ public: * @param copy true if copy into a new matrix, false will create * matrix inplace. */ - static Matrix* createCpuDenseFromNumpy(float* data, int dim1, int dim2, + static Matrix* createCpuDenseFromNumpy(float* data, + int dim1, + int dim2, bool copy = false); /// Create Gpu Dense Matrix from numpy matrix, dtype=float32 @@ -171,11 +180,13 @@ public: * numpy_mat = m.toNumpyMat() * @endcode */ - void toNumpyMatInplace(float** view_data, int* dim1, + void toNumpyMatInplace(float** view_data, + int* dim1, int* dim2) throw(UnsupportError); /// Copy To numpy mat. - void copyToNumpyMat(float** view_m_data, int* dim1, + void copyToNumpyMat(float** view_m_data, + int* dim1, int* dim2) throw(UnsupportError); /// Copy From Numpy Mat @@ -248,15 +259,18 @@ public: static Vector* create(const std::vector& data, bool useGpu = isUsingGpu()); - static Vector* createVectorFromNumpy(float* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError); + static Vector* createVectorFromNumpy( + float* data, + int dim, + bool copy = true, + bool useGpu = isUsingGpu()) throw(UnsupportError); /** * Create Cpu Vector from numpy array, which dtype=float32 * * If copy is false, it will create vector inplace. */ - static Vector* createCpuVectorFromNumpy(float* data, int dim, + static Vector* createCpuVectorFromNumpy(float* data, + int dim, bool copy = false); /// Create Gpu Vector from numpy array, which dtype=float32 @@ -312,16 +326,19 @@ public: static IVector* create(const std::vector& data, bool useGpu = isUsingGpu()); - static IVector* createVectorFromNumpy(int* data, int dim, bool copy = true, - bool useGpu = isUsingGpu()) - throw (UnsupportError); + static IVector* createVectorFromNumpy( + int* data, + int dim, + bool copy = true, + bool useGpu = isUsingGpu()) throw(UnsupportError); /** * Create Cpu IVector from numpy array, which dtype=int32 * * If copy is false, it will create vector inplace */ - static IVector* createCpuVectorFromNumpy(int* data, int dim, + static IVector* createCpuVectorFromNumpy(int* data, + int dim, bool copy = false); /** * Create Gpu IVector from numpy array, which dtype=int32 @@ -605,7 +622,8 @@ class ParameterTraverseCallback { public: ~ParameterTraverseCallback(); - void apply(const std::vector& vecs, const ParameterConfig& config, + void apply(const std::vector& vecs, + const ParameterConfig& config, size_t sparseId); private: @@ -638,7 +656,8 @@ public: void finishBatch(); - void update(const std::vector& vecs, const ParameterConfig& conf, + void update(const std::vector& vecs, + const ParameterConfig& conf, size_t sparseId = NO_SPARSE_ID); std::vector getParameterTypes() const; @@ -678,7 +697,8 @@ public: * model config by TrainerConfig */ static GradientMachine* createByModelConfig( - ModelConfig* conf, GradientMatchineCreateMode mode = CREATE_MODE_NORMAL, + ModelConfig* conf, + GradientMatchineCreateMode mode = CREATE_MODE_NORMAL, const std::vector& parameterTypes = defaultParamTypes); /** @@ -701,7 +721,8 @@ public: /** * Combine forward/backward */ - void forwardBackward(const Arguments& inArgs, Arguments* outArgs, + void forwardBackward(const Arguments& inArgs, + Arguments* outArgs, PassType passType, const UpdateCallback& callback = UpdateCallback()); @@ -722,14 +743,17 @@ public: */ SequenceGenerator* asSequenceGenerator( const std::vector& dict = std::vector(), - size_t begin_id = 0UL, size_t end_id = 0UL, size_t max_length = 100UL, + size_t begin_id = 0UL, + size_t end_id = 0UL, + size_t max_length = 100UL, size_t beam_size = -1UL); private: GradientMachinePrivate* m; static GradientMachine* createFromPaddleModelPtr( - const void* confPtr, GradientMatchineCreateMode mode, + const void* confPtr, + GradientMatchineCreateMode mode, const std::vector& types); // Not to use c++ 11 init-list, so we use static var as function default arg. @@ -751,8 +775,8 @@ public: /// Create A Trainer By TrainerConfig. using paddle command line. static Trainer* createByCommandLine() throw(IOError); - static Trainer* create(TrainerConfig* optConfig, GradientMachine* gm) - throw(IOError); + static Trainer* create(TrainerConfig* optConfig, + GradientMachine* gm) throw(IOError); /// Start training void startTrain(); diff --git a/paddle/api/Parameter.cpp b/paddle/api/Parameter.cpp index 8b56adc97c..c5876bb1c7 100644 --- a/paddle/api/Parameter.cpp +++ b/paddle/api/Parameter.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "paddle/parameter/Parameter.h" diff --git a/paddle/api/ParameterOptimizer.cpp b/paddle/api/ParameterOptimizer.cpp index b13761ab09..21d031e4bc 100644 --- a/paddle/api/ParameterOptimizer.cpp +++ b/paddle/api/ParameterOptimizer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "PaddleAPIPrivate.h" #include "paddle/parameter/ParameterOptimizer.h" @@ -32,17 +31,21 @@ struct ParameterTraverseCallbackPrivate { const paddle::ParameterOptimizer::TraverseCallback& callback) : callback(callback) {} - void apply(const std::vector& vecs, const ParameterConfig& conf, + void apply(const std::vector& vecs, + const ParameterConfig& conf, size_t sparseId) { std::vector real_vecs; real_vecs.resize(vecs.size()); - std::transform(vecs.begin(), vecs.end(), real_vecs.begin(), [](Vector* v) { - if (v) { - return *(paddle::VectorPtr*)(v->getSharedPtr()); - } else { - return paddle::VectorPtr(); - } - }); + std::transform(vecs.begin(), + vecs.end(), + real_vecs.begin(), + [](Vector* v) { + if (v) { + return *(paddle::VectorPtr*)(v->getSharedPtr()); + } else { + return paddle::VectorPtr(); + } + }); paddle::ParameterConfig& real_conf = *(paddle::ParameterConfig*)(const_cast(conf) @@ -86,10 +89,12 @@ void ParameterOptimizer::startBatch(size_t numSamplesProcessed) { void ParameterOptimizer::finishBatch() { m->optimizer->finishBatch(); } void ParameterOptimizer::update(const std::vector& vecs, - const ParameterConfig& conf, size_t sparseId) { - ParameterTraverseCallbackPrivate invoker([&]( - const paddle::VectorPtr _vecs[], const paddle::ParameterConfig& config, - size_t sid = -1UL) { m->optimizer->update(_vecs, config, sid); }); + const ParameterConfig& conf, + size_t sparseId) { + ParameterTraverseCallbackPrivate invoker( + [&](const paddle::VectorPtr _vecs[], + const paddle::ParameterConfig& config, + size_t sid = -1UL) { m->optimizer->update(_vecs, config, sid); }); invoker.apply(vecs, conf, sparseId); } @@ -116,8 +121,9 @@ void ParameterTraverseCallback::apply(const std::vector& vecs, ParameterTraverseCallback* ParameterOptimizer::needSpecialTraversal( const ParameterConfig& config) const { - auto& param_config = *(paddle::ParameterConfig*)const_cast( - config).getRawPtr(); + auto& param_config = + *(paddle::ParameterConfig*)const_cast(config) + .getRawPtr(); auto callback = m->optimizer->needSpecialTraversal(param_config); if (callback) { auto retCallback = new ParameterTraverseCallback(); diff --git a/paddle/api/SequenceGenerator.cpp b/paddle/api/SequenceGenerator.cpp index 9d353ccc8e..d51be78d45 100644 --- a/paddle/api/SequenceGenerator.cpp +++ b/paddle/api/SequenceGenerator.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/parameter/Argument.h" @@ -42,8 +41,10 @@ struct Path { // position static void findNBest(paddle::GradientMachine* gradMachine, std::vector& inArgs, - std::vector& finalPaths, size_t bos_id, - size_t eos_id, size_t max_length) { + std::vector& finalPaths, + size_t bos_id, + size_t eos_id, + size_t max_length) { std::vector paths; Path emptyPath; paths.push_back(emptyPath); @@ -166,7 +167,8 @@ public: if (id < getSize()) { Path& p = (*path_)[id]; std::ostringstream sout; - std::transform(p.ids.begin(), p.ids.end(), + std::transform(p.ids.begin(), + p.ids.end(), std::ostream_iterator(sout, split ? " " : ""), [&](int id) { return (*dict_)[id]; }); return sout.str(); diff --git a/paddle/api/Trainer.cpp b/paddle/api/Trainer.cpp index b61f36f740..7a6aa69fb6 100644 --- a/paddle/api/Trainer.cpp +++ b/paddle/api/Trainer.cpp @@ -64,12 +64,11 @@ Trainer* Trainer::createByCommandLine() throw(IOError) { Trainer::Trainer(TrainerConfig* config, GradientMachine* gm) : m(new TrainerPrivate()) { - m->init(config->m->conf, /* testing= */false, gm ? gm->m->machine : nullptr); + m->init(config->m->conf, /* testing= */ false, gm ? gm->m->machine : nullptr); } -Trainer* Trainer::create(TrainerConfig* config, GradientMachine* gm) - throw(IOError) -{ +Trainer* Trainer::create(TrainerConfig* config, + GradientMachine* gm) throw(IOError) { auto retv = new Trainer(config, gm); if (retv->m->getConfig().IsInitialized()) { return retv; @@ -134,15 +133,17 @@ void Trainer::finishTestPeriod() { m->finishTestPeriod(); } Matrix* Trainer::getLayerOutput(const std::string& layerName) { auto nn = std::dynamic_pointer_cast( - this->m->getGradientMachine()); + this->m->getGradientMachine()); CHECK(nn) << "trainerInternal_.getGradientMachine() is not NeuralNetwork"; auto m = nn->getLayerOutput(layerName); return Matrix::createByPaddleMatrixPtr(&m); } -void Trainer::forwardOneBatch(size_t batchSize) { m->forwardOneBatch(batchSize); } +void Trainer::forwardOneBatch(size_t batchSize) { + m->forwardOneBatch(batchSize); +} -bool TrainerPrivate::forwardOneBatch(size_t batchSize) { +bool TrainerPrivate::forwardOneBatch(size_t batchSize) { CHECK(dataProvider_) << "data_provider is not specified"; paddle::DataBatch dataBatch; int num = dataProvider_->getNextBatch(batchSize, &dataBatch); @@ -156,7 +157,6 @@ bool TrainerPrivate::forwardOneBatch(size_t batchSize) { void TrainerPrivate::forwardOneDataBatch( const std::vector& inArgs) { - std::vector& outArgs = forwardOutput_; if (config_->getOptConfig().use_sparse_remote_updater()) { diff --git a/paddle/api/Util.cpp b/paddle/api/Util.cpp index a8932351a6..1bba1df2e1 100644 --- a/paddle/api/Util.cpp +++ b/paddle/api/Util.cpp @@ -37,13 +37,15 @@ FloatArray::FloatArray(const float* b, const size_t l) IntArray::IntArray(const int* b, const size_t l, bool f) : buf(b), length(l), needFree(f) {} -IntWithFloatArray::IntWithFloatArray(const float* v, const int* i, size_t l, +IntWithFloatArray::IntWithFloatArray(const float* v, + const int* i, + size_t l, bool f) : valBuf(v), idxBuf(i), length(l), needFree(f) {} -bool isUsingGpu() {return FLAGS_use_gpu;} +bool isUsingGpu() { return FLAGS_use_gpu; } -void setUseGpu(bool useGpu) {FLAGS_use_gpu = useGpu;} +void setUseGpu(bool useGpu) { FLAGS_use_gpu = useGpu; } bool isGpuVersion() { #ifdef PADDLE_ONLY_CPU diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index d44cdefc35..cc1c098223 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PaddleAPI.h" #include "paddle/math/Vector.h" @@ -39,8 +38,10 @@ IVector* IVector::create(const std::vector& data, bool useGpu) { return v; } -IVector* IVector::createVectorFromNumpy(int* data, int dim, bool copy, - bool useGpu) throw (UnsupportError){ +IVector* IVector::createVectorFromNumpy(int* data, + int dim, + bool copy, + bool useGpu) throw(UnsupportError) { if (useGpu) { /// if use gpu only copy=true is supported if (!copy) { @@ -137,8 +138,8 @@ void IVector::copyToNumpyArray(int** view_m_data, int* dim1) { if (auto cpuVec = dynamic_cast(m->vec.get())) { std::memcpy(*view_m_data, cpuVec->getData(), sizeof(int) * (*dim1)); } else if (auto gpuVec = dynamic_cast(m->vec.get())) { - hl_memcpy_device2host(*view_m_data, gpuVec->getData(), - sizeof(int) * (*dim1)); + hl_memcpy_device2host( + *view_m_data, gpuVec->getData(), sizeof(int) * (*dim1)); } else { LOG(INFO) << "Unexpected situation"; } @@ -201,8 +202,10 @@ Vector* Vector::createByPaddleVectorPtr(void* ptr) { } } -Vector* Vector::createVectorFromNumpy(float* data, int dim, bool copy, - bool useGpu) throw (UnsupportError){ +Vector* Vector::createVectorFromNumpy(float* data, + int dim, + bool copy, + bool useGpu) throw(UnsupportError) { if (useGpu) { /// if use gpu only copy=True is supported if (!copy) { @@ -251,8 +254,8 @@ void Vector::copyToNumpyArray(float** view_m_data, int* dim1) { if (auto cpuVec = dynamic_cast(m->vec.get())) { std::memcpy(*view_m_data, cpuVec->getData(), sizeof(float) * (*dim1)); } else if (auto gpuVec = dynamic_cast(m->vec.get())) { - hl_memcpy_device2host(*view_m_data, gpuVec->getData(), - sizeof(float) * (*dim1)); + hl_memcpy_device2host( + *view_m_data, gpuVec->getData(), sizeof(float) * (*dim1)); } else { LOG(INFO) << "Unexpected situation"; } diff --git a/paddle/cuda/include/hl_activation_functions.h b/paddle/cuda/include/hl_activation_functions.h index c8aabc7844..03e15b2223 100644 --- a/paddle/cuda/include/hl_activation_functions.h +++ b/paddle/cuda/include/hl_activation_functions.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_ACTIVATION_FUNCTIONS_H_ #define HL_ACTIVATION_FUNCTIONS_H_ @@ -21,11 +20,8 @@ limitations under the License. */ /** * Active functions: sigmoid, relu, tanh and linear. */ -#define HPPL_ACTIVE_FUNCTION {hppl::sigmoid, \ - hppl::relu, \ - hppl::tanh, \ - hppl::linear \ - } +#define HPPL_ACTIVE_FUNCTION \ + { hppl::sigmoid, hppl::relu, hppl::tanh, hppl::linear } namespace hppl { @@ -42,18 +38,18 @@ public: #ifdef __NVCC__ namespace gpu { -static __device__ Active::forward forward[] = HPPL_ACTIVE_FUNCTION; +static __device__ Active::forward forward[] = HPPL_ACTIVE_FUNCTION; static __device__ Active::backward backward[] = HPPL_ACTIVE_FUNCTION; } #else namespace cpu { -static Active::forward forward[] = HPPL_ACTIVE_FUNCTION; +static Active::forward forward[] = HPPL_ACTIVE_FUNCTION; static Active::backward backward[] = HPPL_ACTIVE_FUNCTION; } #ifdef __AVX__ namespace avx { -static Active<__m256>::forward forward[] = HPPL_ACTIVE_FUNCTION; +static Active<__m256>::forward forward[] = HPPL_ACTIVE_FUNCTION; static Active<__m256>::backward backward[] = HPPL_ACTIVE_FUNCTION; } #endif diff --git a/paddle/cuda/include/hl_aggregate.h b/paddle/cuda/include/hl_aggregate.h index db75809f5d..a6d9ff8483 100644 --- a/paddle/cuda/include/hl_aggregate.h +++ b/paddle/cuda/include/hl_aggregate.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_AGGREGATE_H_ #define HL_AGGREGATE_H_ diff --git a/paddle/cuda/include/hl_avx_functions.h b/paddle/cuda/include/hl_avx_functions.h index cf062dd969..ed339e312a 100644 --- a/paddle/cuda/include/hl_avx_functions.h +++ b/paddle/cuda/include/hl_avx_functions.h @@ -12,22 +12,21 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_AVX_FUNCTIONS_H_ #define HL_AVX_FUNCTIONS_H_ #include namespace hppl { - __m256 relu(const __m256 a); - __m256 sigmoid(const __m256 a); - __m256 tanh(const __m256 a); - __m256 linear(const __m256 a); - - __m256 relu(const __m256 a, const __m256 b); - __m256 sigmoid(const __m256 a, const __m256 b); - __m256 tanh(const __m256 a, const __m256 b); - __m256 linear(const __m256 a, const __m256 b); +__m256 relu(const __m256 a); +__m256 sigmoid(const __m256 a); +__m256 tanh(const __m256 a); +__m256 linear(const __m256 a); + +__m256 relu(const __m256 a, const __m256 b); +__m256 sigmoid(const __m256 a, const __m256 b); +__m256 tanh(const __m256 a, const __m256 b); +__m256 linear(const __m256 a, const __m256 b); } // namespace hppl #endif // HL_AVX_FUNCTIONS_H_ diff --git a/paddle/cuda/include/hl_base.h b/paddle/cuda/include/hl_base.h index 9f80898a1f..a076952467 100644 --- a/paddle/cuda/include/hl_base.h +++ b/paddle/cuda/include/hl_base.h @@ -12,8 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - - #ifndef HL_BASE_H_ #define HL_BASE_H_ @@ -33,36 +31,36 @@ limitations under the License. */ * HPPL_STREAM_DEFAULT is HPPL default stream. */ typedef enum { - HPPL_STREAM_DEFAULT = 0, /* Thread Default Stream*/ - HPPL_STREAM_1 = 1, - HPPL_STREAM_2 = 2, - HPPL_STREAM_3 = 3, - HPPL_STREAM_4 = 4, - HPPL_THREAD_STREAM_1 = 5, - HPPL_THREAD_STREAM_2 = 6, - HPPL_THREAD_STREAM_3 = 7, - HPPL_THREAD_STREAM_4 = 8, - HPPL_STREAM_END + HPPL_STREAM_DEFAULT = 0, /* Thread Default Stream*/ + HPPL_STREAM_1 = 1, + HPPL_STREAM_2 = 2, + HPPL_STREAM_3 = 3, + HPPL_STREAM_4 = 4, + HPPL_THREAD_STREAM_1 = 5, + HPPL_THREAD_STREAM_2 = 6, + HPPL_THREAD_STREAM_3 = 7, + HPPL_THREAD_STREAM_4 = 8, + HPPL_STREAM_END } hl_stream_t; /** * @brief HPPL activation mode. */ typedef enum { - HL_ACTIVATION_SIGMOID = 0, - HL_ACTIVATION_RELU = 1, - HL_ACTIVATION_TANH = 2, - HL_ACTIVATION_LINEAR = 3, - HL_ACTIVATION_END + HL_ACTIVATION_SIGMOID = 0, + HL_ACTIVATION_RELU = 1, + HL_ACTIVATION_TANH = 2, + HL_ACTIVATION_LINEAR = 3, + HL_ACTIVATION_END } hl_activation_mode_t; /** * @brief Transpose type. */ typedef enum { - HPPL_OP_N = 0, /* transpose */ - HPPL_OP_T = 1, /* non transpose */ - HPPL_OP_END + HPPL_OP_N = 0, /* transpose */ + HPPL_OP_T = 1, /* non transpose */ + HPPL_OP_END } hl_trans_op_t; /** @@ -148,23 +146,21 @@ typedef struct { * @brief Sparse matrix value type. */ typedef enum { - HL_NO_VALUE = 0, /* matrix values only 0 or 1 */ - HL_FLOAT_VALUE = 1, - HL_VALUE_END + HL_NO_VALUE = 0, /* matrix values only 0 or 1 */ + HL_FLOAT_VALUE = 1, + HL_VALUE_END } hl_matrix_value_t; - /** * @brief HPPL matrix format. */ typedef enum { - HL_SPARSE_CSR = 0, - HL_SPARSE_CSC = 1, - HL_SPARSE_END + HL_SPARSE_CSR = 0, + HL_SPARSE_CSC = 1, + HL_SPARSE_END } hl_matrix_format_t; - -typedef struct _hl_matrix_s * hl_matrix_s; +typedef struct _hl_matrix_s *hl_matrix_s; /** * @brief HPPL sparse matrix. @@ -177,12 +173,12 @@ typedef struct _hl_matrix_s * hl_matrix_s; * @param nnz nonzero values of sparse matrix. */ typedef struct { - hl_matrix_s matrix; - hl_matrix_format_t format; - hl_matrix_value_t type; - int rows; - int cols; - size_t nnz; + hl_matrix_s matrix; + hl_matrix_format_t format; + hl_matrix_value_t type; + int rows; + int cols; + size_t nnz; } _hl_sparse_matrix_s, *hl_sparse_matrix_s; #ifndef PADDLE_TYPE_DOUBLE @@ -195,7 +191,7 @@ typedef struct { * * HL_FLOAT_MIN: 1.17549435e-38F */ -#define HL_FLOAT_MAX 3.40282347e+38F +#define HL_FLOAT_MAX 3.40282347e+38F /** * if real == double * @@ -203,20 +199,18 @@ typedef struct { * * HL_FLOAT_MIN: 2.2250738585072014e-308 */ -#define HL_FLOAT_MIN 1.17549435e-38F +#define HL_FLOAT_MIN 1.17549435e-38F #else -#define HL_FLOAT_MAX 1.7976931348623157e+308 -#define HL_FLOAT_MIN 2.2250738585072014e-308 +#define HL_FLOAT_MAX 1.7976931348623157e+308 +#define HL_FLOAT_MIN 2.2250738585072014e-308 #endif - /** * The maximum input value for exp, used to avoid overflow problem. * * Currently only used for tanh function. */ -#define EXP_MAX_INPUT 40.0 - +#define EXP_MAX_INPUT 40.0 /** * @brief DIVUP(x, y) is similar to ceil(x / y). @@ -224,7 +218,7 @@ typedef struct { * the size of blockDim. */ #ifndef DIVUP -#define DIVUP(x, y) (((x) + (y) - 1) / (y)) +#define DIVUP(x, y) (((x) + (y)-1) / (y)) #endif #ifdef __NVCC__ @@ -233,7 +227,7 @@ typedef struct { #include "hl_cuda.h" #include "cuda_runtime.h" -extern __thread bool g_sync_flag; +extern __thread bool g_sync_flag; extern __thread cudaStream_t default_stream; #define STREAM_DEFAULT default_stream @@ -241,16 +235,15 @@ extern __thread cudaStream_t default_stream; * @brief Check cuda kernel execution. * @param msg error string */ -#define CHECK_SYNC(msg) \ - if (true == g_sync_flag) { \ - hl_stream_synchronize(HPPL_STREAM_DEFAULT); \ - cudaError_t err \ - = (cudaError_t)hl_get_device_last_error(); \ - CHECK_EQ(cudaSuccess, err) << "[" << msg << "] " \ - << "CUDA error: " \ - << hl_get_device_error_string((size_t)err); \ +#define CHECK_SYNC(msg) \ + if (true == g_sync_flag) { \ + hl_stream_synchronize(HPPL_STREAM_DEFAULT); \ + cudaError_t err = (cudaError_t)hl_get_device_last_error(); \ + CHECK_EQ(cudaSuccess, err) \ + << "[" << msg << "] " \ + << "CUDA error: " << hl_get_device_error_string((size_t)err); \ } -#endif /* __NVCC__ */ +#endif /* __NVCC__ */ -#endif /* HL_BASE_H_ */ +#endif /* HL_BASE_H_ */ diff --git a/paddle/cuda/include/hl_batch_transpose.h b/paddle/cuda/include/hl_batch_transpose.h index 414c7996ac..f3630e9762 100644 --- a/paddle/cuda/include/hl_batch_transpose.h +++ b/paddle/cuda/include/hl_batch_transpose.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_BATCH_TRANSPOSE_H_ #define HL_BATCH_TRANSPOSE_H_ @@ -31,10 +30,7 @@ limitations under the License. */ * order. Each batch has height * width data, which are * arranged in height-first (or row-first) manner. */ -extern void batchTranspose(const real* input, - real* output, - int width, - int height, - int batchSize); +extern void batchTranspose( + const real* input, real* output, int width, int height, int batchSize); #endif // HL_BATCH_TRANSPOSE_H_ diff --git a/paddle/cuda/include/hl_cnn.h b/paddle/cuda/include/hl_cnn.h index 70b5be6fda..cffaac634f 100644 --- a/paddle/cuda/include/hl_cnn.h +++ b/paddle/cuda/include/hl_cnn.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CNN_H_ #define HL_CNN_H_ @@ -37,15 +36,21 @@ limitations under the License. */ * @param[in] alpha * @param[in] beta */ -extern void hl_shrink_col2feature( - const real * dataCol, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataIm, - real alpha = 1.0f, real beta = 0.0f); +extern void hl_shrink_col2feature(const real* dataCol, + size_t channels, + size_t height, + size_t width, + size_t blockH, + size_t blockW, + size_t strideH, + size_t strideW, + size_t paddingH, + size_t paddingW, + size_t outputH, + size_t outputW, + real* dataIm, + real alpha = 1.0f, + real beta = 0.0f); /** * @brief Expand feature to column. @@ -65,14 +70,19 @@ extern void hl_shrink_col2feature( * @param[out] dataCol expand data. * */ -extern void hl_expand_feature2col( - const real* dataIm, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataCol); +extern void hl_expand_feature2col(const real* dataIm, + size_t channels, + size_t height, + size_t width, + size_t blockH, + size_t blockW, + size_t strideH, + size_t strideW, + size_t paddingH, + size_t paddingW, + size_t outputH, + size_t outputW, + real* dataCol); /** * @brief Maximum pool forward. @@ -94,15 +104,21 @@ extern void hl_expand_feature2col( * @param[in] tgtStride stride between output data samples. * */ -extern void hl_maxpool_forward( - const int frameCnt, const real* inputData, - const int channels, - const int height, const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real* tgtData, const int tgtStride); +extern void hl_maxpool_forward(const int frameCnt, + const real* inputData, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real* tgtData, + const int tgtStride); /** * @brief Maximum pool backward. @@ -125,20 +141,28 @@ extern void hl_maxpool_forward( * @param[in] paddingH padding height. * @param[in] paddingW padding width. * @param[out] targetGrad output grad. - * @param[in] outStride stride between output data samples. + * @param[in] outStride stride between output data samples. * */ -extern void hl_maxpool_backward( - const int frameCnt, const real* inputData, - const real* outData, const real* outGrad, - const int channels, const int height, - const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real scaleA, real scaleB, - real* targetGrad, const int outStride); +extern void hl_maxpool_backward(const int frameCnt, + const real* inputData, + const real* outData, + const real* outGrad, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real scaleA, + real scaleB, + real* targetGrad, + const int outStride); /** * @brief Averge pool forward. @@ -160,15 +184,21 @@ extern void hl_maxpool_backward( * @param[in] tgtStride stride between output data samples. * */ -extern void hl_avgpool_forward( - const int frameCnt, const real* inputData, - const int channels, - const int height, const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real* tgtData, const int tgtStride); +extern void hl_avgpool_forward(const int frameCnt, + const real* inputData, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real* tgtData, + const int tgtStride); /** * @brief Maximum pool backward. @@ -189,19 +219,26 @@ extern void hl_avgpool_forward( * @param[in] scaleA scale. * @param[in] scaleB scale. * @param[out] backGrad output grad. - * @param[in] outStride stride between output data samples. + * @param[in] outStride stride between output data samples. * */ -extern void hl_avgpool_backward( - const int frameCnt, const real* outGrad, - const int channels, const int height, - const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - int paddingH, int paddingW, - real scaleA, real scaleB, - real* backGrad, const int outStride); +extern void hl_avgpool_backward(const int frameCnt, + const real* outGrad, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + int paddingH, + int paddingW, + real scaleA, + real scaleB, + real* backGrad, + const int outStride); /** * @brief Cross-map-respose normalize forward. @@ -218,10 +255,16 @@ extern void hl_avgpool_backward( * @param[in] beta scale. * */ -extern void hl_CMRNorm_forward( - size_t frameCnt, const real* in, real* scale, real* out, - size_t channels, size_t height, size_t width, size_t sizeX, - real alpha, real beta); +extern void hl_CMRNorm_forward(size_t frameCnt, + const real* in, + real* scale, + real* out, + size_t channels, + size_t height, + size_t width, + size_t sizeX, + real alpha, + real beta); /** * @brief Cross-map-respose normalize backward. @@ -240,11 +283,18 @@ extern void hl_CMRNorm_forward( * @param[in] beta scale. * */ -extern void hl_CMRNorm_backward( - size_t frameCnt, const real* inV, const real* scale, - const real* outV, const real* outDiff, real *inDiff, - size_t channels, size_t height, size_t width, size_t sizeX, - real alpha, real beta); +extern void hl_CMRNorm_backward(size_t frameCnt, + const real* inV, + const real* scale, + const real* outV, + const real* outDiff, + real* inDiff, + size_t channels, + size_t height, + size_t width, + size_t sizeX, + real alpha, + real beta); /** * @brief Bilinear interpolation forward. @@ -278,24 +328,24 @@ extern void hl_bilinear_forward(const real* inData, const real ratioH, const real ratioW); - /** - * @brief Bilinear interpolation backward. - * - * @param[out] inGrad input gradient. - * @param[in] inImgH input image height. - * @param[in] inImgW input image width. - * @param[in] inputH input batchSize. - * @param[in] inputW input image data dim. - * @param[in] outGrad output gradient. - * @param[in] outImgH output image height. - * @param[in] outImgW output image width. - * @param[in] outputH output batchSize. - * @param[in] outputW output image data dim. - * @param[in] numChannels number of channels. - * @param[in] ratioH inImgH / outImgH. - * @param[in] ratioW inImgW / outImgW. - * - */ +/** +* @brief Bilinear interpolation backward. +* +* @param[out] inGrad input gradient. +* @param[in] inImgH input image height. +* @param[in] inImgW input image width. +* @param[in] inputH input batchSize. +* @param[in] inputW input image data dim. +* @param[in] outGrad output gradient. +* @param[in] outImgH output image height. +* @param[in] outImgW output image width. +* @param[in] outputH output batchSize. +* @param[in] outputW output image data dim. +* @param[in] numChannels number of channels. +* @param[in] ratioH inImgH / outImgH. +* @param[in] ratioW inImgW / outImgW. +* +*/ extern void hl_bilinear_backward(real* inGrad, const size_t inImgH, const size_t inImgW, @@ -321,9 +371,13 @@ extern void hl_bilinear_backward(real* inGrad, * @param[in] featLen feature length = image height * image width. * @param[in] groups number of groups. */ -extern void hl_maxout_forward( - const real* inData, real* outData, int* idData, - size_t batchSize, size_t size, size_t featLen, size_t groups); +extern void hl_maxout_forward(const real* inData, + real* outData, + int* idData, + size_t batchSize, + size_t size, + size_t featLen, + size_t groups); /** * @brief MaxOut backward. @@ -336,8 +390,12 @@ extern void hl_maxout_forward( * @param[in] featLen feature length = image height * image width. * @param[in] groups number of groups. */ -extern void hl_maxout_backward( - real* inGrad, const real* outGrad, const int* idData, - size_t batchSize, size_t size, size_t featLen, size_t groups); +extern void hl_maxout_backward(real* inGrad, + const real* outGrad, + const int* idData, + size_t batchSize, + size_t size, + size_t featLen, + size_t groups); #endif /* HL_CNN_H_ */ diff --git a/paddle/cuda/include/hl_cuda.h b/paddle/cuda/include/hl_cuda.h index 3196db67f6..357286e318 100644 --- a/paddle/cuda/include/hl_cuda.h +++ b/paddle/cuda/include/hl_cuda.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_H_ #define HL_CUDA_H_ @@ -22,8 +21,7 @@ limitations under the License. */ /** * @brief HPPL event. */ -typedef struct _hl_event_st * hl_event_t; - +typedef struct _hl_event_st *hl_event_t; /** * @brief return cuda runtime api version. @@ -42,7 +40,7 @@ extern void hl_start(); * if device is NULL, will start all GPU. * @param[in] number number of devices. */ -extern void hl_specify_devices_start(int* device, int number); +extern void hl_specify_devices_start(int *device, int number); /** * @brief Queries if a device may directly access a peer device's memory. @@ -126,7 +124,7 @@ extern int hl_get_device(); * * @return dest_d pointer to device memory. */ -extern void* hl_malloc_device(size_t size); +extern void *hl_malloc_device(size_t size); /** * @brief Free device memory. @@ -143,7 +141,7 @@ extern void hl_free_mem_device(void *dest_d); * * @return dest_h pointer to host memory. */ -extern void* hl_malloc_host(size_t size); +extern void *hl_malloc_host(size_t size); /** * @brief Free host page-lock memory. @@ -228,9 +226,9 @@ extern void hl_srand(unsigned int seed); * @param[in] stream stream id. */ extern void hl_memcpy_async(void *dst, - void *src, - size_t size, - hl_stream_t stream); + void *src, + size_t size, + hl_stream_t stream); /** * @brief Waits for stream tasks to complete. @@ -261,8 +259,7 @@ extern void hl_destroy_event(hl_event_t event); * * @return time Time between start and end in ms. */ -extern float hl_event_elapsed_time(hl_event_t start, - hl_event_t end); +extern float hl_event_elapsed_time(hl_event_t start, hl_event_t end); /** * @brief Records an event. @@ -300,7 +297,7 @@ extern void hl_set_device_flags_block(); /** * @brief Returns the last error string from a cuda runtime call. */ -extern const char* hl_get_device_error_string(); +extern const char *hl_get_device_error_string(); /** * @brief Returns the last error string from a cuda runtime call. @@ -309,7 +306,7 @@ extern const char* hl_get_device_error_string(); * * @see hl_get_device_last_error() */ -extern const char* hl_get_device_error_string(size_t err); +extern const char *hl_get_device_error_string(size_t err); /** * @brief Returns the last error number. diff --git a/paddle/cuda/include/hl_cuda_cublas.h b/paddle/cuda/include/hl_cuda_cublas.h index d757317eb4..db8c03c2c0 100644 --- a/paddle/cuda/include/hl_cuda_cublas.h +++ b/paddle/cuda/include/hl_cuda_cublas.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_CUBLAS_H_ #define HL_CUDA_CUBLAS_H_ @@ -29,12 +28,8 @@ limitations under the License. */ * @param[in] ldc the first dimension of C_d. * */ -extern void hl_matrix_transpose(real *A_d, - real *C_d, - int dimM, - int dimN, - int lda, - int ldc); +extern void hl_matrix_transpose( + real *A_d, real *C_d, int dimM, int dimN, int lda, int ldc); /* * @brief Matrix transpose, while lda = dimN, ldc = dimM. @@ -45,10 +40,7 @@ extern void hl_matrix_transpose(real *A_d, * @param[in] dimN matrix width. * */ -extern void hl_matrix_transpose(real *A_d, - real *C_d, - int dimM, - int dimN); +extern void hl_matrix_transpose(real *A_d, real *C_d, int dimM, int dimN); /* * @brief Matrix inverse @@ -60,11 +52,7 @@ extern void hl_matrix_transpose(real *A_d, * @param[in] ldc the first dimension of C_d * */ -extern void hl_matrix_inverse(real *A_d, - real *C_d, - int dimN, - int lda, - int ldc); +extern void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc); /** * @brief C_d = alpha*(op(A_d) * op(B_d)) + beta*C_d @@ -84,12 +72,19 @@ extern void hl_matrix_inverse(real *A_d, * @param[in] ldc the first dimension of C_d. * */ -extern void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +extern void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta, - int lda, int ldb, int ldc); + int dimM, + int dimN, + int dimK, + real alpha, + real beta, + int lda, + int ldb, + int ldc); /** * @brief C_d = alpha*(op(A_d) * op(B_d)) + beta*C_d @@ -106,11 +101,16 @@ extern void hl_matrix_mul(real *A_d, hl_trans_op_t transa, * @param[in] beta scalar used for multiplication. * */ -extern void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +extern void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta); + int dimM, + int dimN, + int dimK, + real alpha, + real beta); /** * @brief This function performs the matrix-vector multiplication. @@ -132,11 +132,17 @@ extern void hl_matrix_mul(real *A_d, hl_trans_op_t transa, * */ -extern void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, - real *B_d, real *C_d, - int dimM, int dimN, - real alpha, real beta, - int lda, int incb, int incc); +extern void hl_matrix_mul_vector(real *A_d, + hl_trans_op_t trans, + real *B_d, + real *C_d, + int dimM, + int dimN, + real alpha, + real beta, + int lda, + int incb, + int incc); /** * @brief This function performs the matrix-vector multiplication. @@ -154,9 +160,13 @@ extern void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, * @param[in] beta scalar used for multiplication. * */ -extern void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, - real *B_d, real *C_d, - int dimM, int dimN, - real alpha, real beta); +extern void hl_matrix_mul_vector(real *A_d, + hl_trans_op_t trans, + real *B_d, + real *C_d, + int dimM, + int dimN, + real alpha, + real beta); #endif /* HL_CUDA_CUBLAS_H_ */ diff --git a/paddle/cuda/include/hl_cuda_cudnn.h b/paddle/cuda/include/hl_cuda_cudnn.h index f256cb54df..3a2f916210 100644 --- a/paddle/cuda/include/hl_cuda_cudnn.h +++ b/paddle/cuda/include/hl_cuda_cudnn.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_CUDNN_H_ #define HL_CUDA_CUDNN_H_ @@ -22,7 +21,7 @@ limitations under the License. */ * hppl pooling mode */ typedef enum { - HL_POOLING_MAX = 0, + HL_POOLING_MAX = 0, // average includes padded values HL_POOLING_AVERAGE = 1, // average does not include padded values @@ -324,17 +323,16 @@ extern void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, * @param[in] sizeInBytes gpu workspace size (bytes). * @param[in] convBwdFilterAlgo backward filter algorithm. */ -extern void hl_convolution_backward_filter( - hl_tensor_descriptor input, - real* input_data, - hl_tensor_descriptor output, - real* output_grad_data, - hl_filter_descriptor filter, - real* filter_grad_data, - hl_convolution_descriptor conv, - void* gpuWorkSpace, - size_t sizeInBytes, - int convBwdFilterAlgo); +extern void hl_convolution_backward_filter(hl_tensor_descriptor input, + real* input_data, + hl_tensor_descriptor output, + real* output_grad_data, + hl_filter_descriptor filter, + real* filter_grad_data, + hl_convolution_descriptor conv, + void* gpuWorkSpace, + size_t sizeInBytes, + int convBwdFilterAlgo); /** * @brief convolution backward data(calculate input image grad data). @@ -350,17 +348,16 @@ extern void hl_convolution_backward_filter( * @param[in] sizeInBytes gpu workspace size (bytes). * @param[in] convBwdDataAlgo backward data algorithm. */ -extern void hl_convolution_backward_data( - hl_tensor_descriptor input, - real* input_data_grad, - hl_tensor_descriptor output, - real* output_grad_data, - hl_filter_descriptor filter, - real* filter_data, - hl_convolution_descriptor conv, - void* gpuWorkSpace, - size_t sizeInBytes, - int convBwdDataAlgo); +extern void hl_convolution_backward_data(hl_tensor_descriptor input, + real* input_data_grad, + hl_tensor_descriptor output, + real* output_grad_data, + hl_filter_descriptor filter, + real* filter_data, + hl_convolution_descriptor conv, + void* gpuWorkSpace, + size_t sizeInBytes, + int convBwdDataAlgo); /** * @brief convolution backward bias(calculate bias grad data). @@ -383,8 +380,8 @@ extern void hl_convolution_backward_bias(hl_tensor_descriptor bias, * @param[in] height matrix height. * @param[in] width matrix width. */ -extern void hl_softmax_forward(real *input, - real *output, +extern void hl_softmax_forward(real* input, + real* output, int height, int width); @@ -396,8 +393,8 @@ extern void hl_softmax_forward(real *input, * @param[in] height matrix height. * @param[in] width matrix width. */ -extern void hl_softmax_backward(real *output_value, - real *output_grad, +extern void hl_softmax_backward(real* output_value, + real* output_grad, int height, int width); @@ -426,18 +423,18 @@ extern void hl_softmax_backward(real *output_value, * */ extern void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outputDesc, - real *output, + real* output, hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, + real* scale, + real* bias, double factor, - real *runningMean, - real *runningInvVar, + real* runningMean, + real* runningInvVar, double epsilon, - real *savedMean, - real *savedVar); + real* savedMean, + real* savedVar); /** * @brief cudnn batch norm forward. @@ -463,14 +460,14 @@ extern void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, * */ extern void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outputDesc, - real *output, + real* output, hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, - real *estimatedMean, - real *estimatedVar, + real* scale, + real* bias, + real* estimatedMean, + real* estimatedVar, double epsilon); /** @@ -483,7 +480,8 @@ extern void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, * @param[in] inGradDesc input tensor descriptor desc. * @param[in] inGrad input data. * @param[in] dBnParamDesc tensor descriptor desc. - * bnScale, bnBias, running mean/var, save_mean/var. + * bnScale, bnBias, running mean/var, + * save_mean/var. * @param[in] scale batch normalization scale parameter (in original * paper scale is referred to as gamma). * @param[in] scaleGrad batch normalization scale parameter (in original @@ -497,17 +495,17 @@ extern void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, * */ extern void hl_batch_norm_backward(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outGradDesc, - real *outGrad, + real* outGrad, hl_tensor_descriptor inGradDesc, - real *inGrad, + real* inGrad, hl_tensor_descriptor dBnParamDesc, - real *scale, - real *scaleGrad, - real *biasGrad, + real* scale, + real* scaleGrad, + real* biasGrad, double epsilon, - real *savedMean, - real *savedInvVar); + real* savedMean, + real* savedInvVar); #endif // HL_CUDA_CUDNN_H_ diff --git a/paddle/cuda/include/hl_dso_loader.h b/paddle/cuda/include/hl_dso_loader.h index f36c724e2d..1eb9f9ca88 100644 --- a/paddle/cuda/include/hl_dso_loader.h +++ b/paddle/cuda/include/hl_dso_loader.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_DSO_LOADER_H_ #define HL_DSO_LOADER_H_ diff --git a/paddle/cuda/include/hl_functions.h b/paddle/cuda/include/hl_functions.h index 65f366461c..91ce9a0678 100644 --- a/paddle/cuda/include/hl_functions.h +++ b/paddle/cuda/include/hl_functions.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_FUNCTIONS_H_ #define HL_FUNCTIONS_H_ @@ -21,30 +20,30 @@ limitations under the License. */ /** * sigmoid threshold maximum */ -#define SIGMOID_THRESHOLD_MIN -40.0 +#define SIGMOID_THRESHOLD_MIN -40.0 /** * sigmoid threshold minimum */ -#define SIGMOID_THRESHOLD_MAX 13.0 +#define SIGMOID_THRESHOLD_MAX 13.0 #ifndef __NVCC__ namespace hppl { - /* - * forward activation - */ - real relu(const real a); - real sigmoid(const real a); - real tanh(const real a); - real linear(const real a); - - /* - * backward activation - */ - real relu(const real a, const real b); - real sigmoid(const real a, const real b); - real tanh(const real a, const real b); - real linear(const real a, const real b); +/* + * forward activation + */ +real relu(const real a); +real sigmoid(const real a); +real tanh(const real a); +real linear(const real a); + +/* + * backward activation + */ +real relu(const real a, const real b); +real sigmoid(const real a, const real b); +real tanh(const real a, const real b); +real linear(const real a, const real b); } // namespace hppl #ifdef __AVX__ diff --git a/paddle/cuda/include/hl_gpu.h b/paddle/cuda/include/hl_gpu.h index 05039663b6..3be0df3b93 100644 --- a/paddle/cuda/include/hl_gpu.h +++ b/paddle/cuda/include/hl_gpu.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_GPU_H_ #define HL_GPU_H_ diff --git a/paddle/cuda/include/hl_lstm.h b/paddle/cuda/include/hl_lstm.h index 1f95e318a1..7e527a7902 100644 --- a/paddle/cuda/include/hl_lstm.h +++ b/paddle/cuda/include/hl_lstm.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_LSTM_H_ #define HL_LSTM_H_ diff --git a/paddle/cuda/include/hl_matrix.h b/paddle/cuda/include/hl_matrix.h index 6195e30b99..96648661e3 100644 --- a/paddle/cuda/include/hl_matrix.h +++ b/paddle/cuda/include/hl_matrix.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_MATRIX_H_ #define HL_MATRIX_H_ @@ -30,13 +29,8 @@ limitations under the License. */ * @param[in] beta scalar used for addition. * */ -extern void hl_matrix_add(real* A_d, - real* B_d, - real* C_d, - int dimM, - int dimN, - real alpha, - real beta); +extern void hl_matrix_add( + real* A_d, real* B_d, real* C_d, int dimM, int dimN, real alpha, real beta); /** * @brief Matrix Softmax. * @@ -46,7 +40,7 @@ extern void hl_matrix_add(real* A_d, * @param[in] dimN matrix width. * */ -extern void hl_matrix_softmax(real *A_d, real *C_d, int dimM, int dimN); +extern void hl_matrix_softmax(real* A_d, real* C_d, int dimM, int dimN); /** * @brief Matrix softmax derivative. @@ -58,11 +52,8 @@ extern void hl_matrix_softmax(real *A_d, real *C_d, int dimM, int dimN); * @param[in] dimN matrix width. * */ -extern void hl_matrix_softmax_derivative(real* grad_d, - real* output_d, - real* sftmaxSum_d, - int dimM, - int dimN); +extern void hl_matrix_softmax_derivative( + real* grad_d, real* output_d, real* sftmaxSum_d, int dimM, int dimN); /** * @brief Sequence softmax. @@ -73,8 +64,8 @@ extern void hl_matrix_softmax_derivative(real* grad_d, * @param[in] numSequence sequence number. * */ -extern void hl_sequence_softmax_forward(real *A_d, - real *C_d, +extern void hl_sequence_softmax_forward(real* A_d, + real* C_d, const int* index, int numSequence); @@ -88,11 +79,8 @@ extern void hl_sequence_softmax_forward(real *A_d, * @param[in] dimN matrix width. * */ -extern void hl_matrix_classification_error(real* A_d, - int* B_d, - real* C_d, - int dimM, - int dimN); +extern void hl_matrix_classification_error( + real* A_d, int* B_d, real* C_d, int dimM, int dimN); /** * @brief Matrix cross entropy. @@ -104,11 +92,8 @@ extern void hl_matrix_classification_error(real* A_d, * @param[in] dimN matrix width. * */ -extern void hl_matrix_cross_entropy(real* A_d, - real* C_d, - int* label_d, - int dimM, - int dimN); +extern void hl_matrix_cross_entropy( + real* A_d, real* C_d, int* label_d, int dimM, int dimN); /** * @brief Matrix cross entropy back propagation. @@ -120,11 +105,8 @@ extern void hl_matrix_cross_entropy(real* A_d, * @param[in] dimN matrix width. * */ -extern void hl_matrix_cross_entropy_bp(real* grad_d, - real* output_d, - int* label_d, - int dimM, - int dimN); +extern void hl_matrix_cross_entropy_bp( + real* grad_d, real* output_d, int* label_d, int dimM, int dimN); /** * @brief Matrix multi-binary label cross entropy @@ -135,11 +117,8 @@ extern void hl_matrix_cross_entropy_bp(real* grad_d, * @param[in] dimM matrix height. * @param[in] dimN matrix width. */ -extern void hl_matrix_multi_binary_cross_entropy(real* output, - real* entropy, - hl_sparse_matrix_s mat, - int dimM, - int dimN); +extern void hl_matrix_multi_binary_cross_entropy( + real* output, real* entropy, hl_sparse_matrix_s mat, int dimM, int dimN); /** * @brief Matrix multi-binary label cross entropy backprop @@ -150,11 +129,8 @@ extern void hl_matrix_multi_binary_cross_entropy(real* output, * @param[in] dimM matrix height. * @param[in] dimN matrix width. */ -extern void hl_matrix_multi_binary_cross_entropy_bp(real* output, - real* grad, - hl_sparse_matrix_s mat, - int dimM, - int dimN); +extern void hl_matrix_multi_binary_cross_entropy_bp( + real* output, real* grad, hl_sparse_matrix_s mat, int dimM, int dimN); /** * @brief Matrix zero memory. @@ -176,12 +152,8 @@ extern void hl_matrix_zero_mem(real* data, int num); * @param[in] partial_sum */ -extern void hl_param_relu_forward(real* output, - real* input, - real* w, - int width, - int height, - int partial_sum); +extern void hl_param_relu_forward( + real* output, real* input, real* w, int width, int height, int partial_sum); /** * @brief parameter relu backward w * diff --git a/paddle/cuda/include/hl_sequence.h b/paddle/cuda/include/hl_sequence.h index 46d86b2982..bb5124df44 100644 --- a/paddle/cuda/include/hl_sequence.h +++ b/paddle/cuda/include/hl_sequence.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_SEQUENCE_H_ #define HL_SEQUENCE_H_ @@ -32,7 +31,7 @@ limitations under the License. */ extern void hl_max_sequence_forward(real* input, const int* sequence, real* output, - int *index, + int* index, int numSequences, int dim); @@ -46,11 +45,8 @@ extern void hl_max_sequence_forward(real* input, * @param[in] dim input dimension. * */ -extern void hl_max_sequence_backward(real* outputGrad, - int *index, - real* inputGrad, - int numSequences, - int dim); +extern void hl_max_sequence_backward( + real* outputGrad, int* index, real* inputGrad, int numSequences, int dim); /** * @brief Context projection forward. @@ -63,7 +59,8 @@ extern void hl_max_sequence_backward(real* outputGrad, * @param[in] inputDim input sequence dimension. * @param[in] contextLength context length. * @param[in] contextStart context start. - * @param[in] beginPad number of extra timesteps added at the beginning. + * @param[in] beginPad number of extra timesteps added at the + * beginning. * @param[in] isPadding trainable padding. * */ @@ -109,7 +106,8 @@ extern void hl_context_projection_backward_data(real* outputGrad, * @param[in] totalPad number of extra timesteps. * @param[in] contextLength context length. * @param[in] contextStart context start. - * @param[in] beginPad number of extra timesteps added at the beginning. + * @param[in] beginPad number of extra timesteps added at the + * beginning. * */ extern void hl_context_projection_backward_weight(real* outputGrad, @@ -141,9 +139,9 @@ extern void hl_context_projection_backward_weight(real* outputGrad, * @param[in] seq2batch copy direction. * */ -extern void hl_sequence2batch_copy(real *batch, - real *sequence, - const int *batchIndex, +extern void hl_sequence2batch_copy(real* batch, + real* sequence, + const int* batchIndex, int seqWidth, int batchCount, bool seq2batch); @@ -167,9 +165,9 @@ extern void hl_sequence2batch_copy(real *batch, * @param[in] seq2batch copy direction. * */ -extern void hl_sequence2batch_add(real *batch, - real *sequence, - int *batchIndex, +extern void hl_sequence2batch_add(real* batch, + real* sequence, + int* batchIndex, int seqWidth, int batchCount, bool seq2batch); diff --git a/paddle/cuda/include/hl_sparse.h b/paddle/cuda/include/hl_sparse.h index 9acdebdebf..c4e0be23e2 100644 --- a/paddle/cuda/include/hl_sparse.h +++ b/paddle/cuda/include/hl_sparse.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_SPARSE_H_ #define HL_SPARSE_H_ @@ -31,7 +30,7 @@ limitations under the License. */ */ extern void hl_malloc_sparse_matrix(hl_sparse_matrix_s *A_d, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz); @@ -60,10 +59,10 @@ extern void hl_free_sparse_matrix(hl_sparse_matrix_s A_d); * */ extern void hl_construct_sparse_matrix(hl_sparse_matrix_s *A_d, - void * dest_d, + void *dest_d, size_t size, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz); @@ -94,11 +93,11 @@ extern void hl_construct_sparse_matrix(hl_sparse_matrix_s *A_d, * */ extern void hl_construct_sparse_matrix(hl_sparse_matrix_s *A_d, - real* value_d, - int* rows_d, - int* cols_d, + real *value_d, + int *rows_d, + int *cols_d, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz); @@ -259,10 +258,14 @@ extern void hl_matrix_csr_mul_dense(hl_sparse_matrix_s A_d, */ extern void hl_matrix_csc_mul_dense(hl_sparse_matrix_s A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta); + int dimM, + int dimN, + int dimK, + real alpha, + real beta); /** * @brief C_d = alpha*(op(A_d) * op(B_d)) + beta*C_d. @@ -311,11 +314,16 @@ extern void hl_matrix_dense_mul_csc(real *A_d, * @note transb is not support HPPL_OP_T. * */ -extern void hl_sparse_matrix_mul(real* A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +extern void hl_sparse_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, hl_sparse_matrix_s C_d, - int dimM, int dimN, int dimK, - real alpha, real beta); + int dimM, + int dimN, + int dimK, + real alpha, + real beta); /** * @brief C_d = alpha*(op(A_d) * op(B_d)) + beta*C_d @@ -336,12 +344,16 @@ extern void hl_sparse_matrix_mul(real* A_d, hl_trans_op_t transa, * @note transa is not support HPPL_OP_T. * */ -extern void hl_matrix_dense_mul_csr(real *A_d, hl_trans_op_t transa, +extern void hl_matrix_dense_mul_csr(real *A_d, + hl_trans_op_t transa, hl_sparse_matrix_s B_d, hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta); + int dimM, + int dimN, + int dimK, + real alpha, + real beta); /** * @brief Memcpy csc_matrix to host. @@ -412,7 +424,6 @@ extern void hl_memcpy_from_csr_matrix(real *csr_val, hl_sparse_matrix_s csr_matrix, hl_stream_t stream); - /** * @brief A_d[j] += B_d[i,j] for i in range(height) * @@ -423,19 +434,13 @@ extern void hl_memcpy_from_csr_matrix(real *csr_val, * @param[in] scale scale of B_d * */ -extern void hl_sparse_matrix_column_sum(real* A_d, - hl_sparse_matrix_s B_d, - int dimM, - int dimN, - real scale); +extern void hl_sparse_matrix_column_sum( + real *A_d, hl_sparse_matrix_s B_d, int dimM, int dimN, real scale); /** * @brief implementation of csr sparse matrix in hl_sparse_matirx_column_sum */ -extern void hl_matrix_csr_column_sum(real* A_d, - hl_sparse_matrix_s B_d, - int dimM, - int dimN, - real scale); +extern void hl_matrix_csr_column_sum( + real *A_d, hl_sparse_matrix_s B_d, int dimM, int dimN, real scale); /** * @brief A_d[i,j] += B_d[j] @@ -446,13 +451,13 @@ extern void hl_matrix_csr_column_sum(real* A_d, * */ extern void hl_sparse_matrix_add_bias(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, real scale); /** * @brief implementation of csr sparse matrix in hl_sparse_matrix_add_bias */ extern void hl_matrix_csr_add_bias(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, real scale); /** @@ -470,7 +475,7 @@ extern void hl_matrix_csr_add_bias(hl_sparse_matrix_s A_d, * */ extern void hl_sparse_matrix_add_dense(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, int dimM, int dimN, real alpha, @@ -479,7 +484,7 @@ extern void hl_sparse_matrix_add_dense(hl_sparse_matrix_s A_d, * @brief implementation of csr sparse matrix in hl_sparse_matrix_add_dense */ extern void hl_matrix_csr_add_dense(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, int dimM, int dimN, real alpha, @@ -493,7 +498,7 @@ extern void hl_matrix_csr_add_dense(hl_sparse_matrix_s A_d, * @return return rows pointer, which is gpu address * */ -extern int* hl_sparse_matrix_get_rows(hl_sparse_matrix_s sMat); +extern int *hl_sparse_matrix_get_rows(hl_sparse_matrix_s sMat); /** * @brief get cols pionter of GpuSparseMatrix @@ -503,7 +508,7 @@ extern int* hl_sparse_matrix_get_rows(hl_sparse_matrix_s sMat); * @return return cols pointer, which is gpu address * */ -extern int* hl_sparse_matrix_get_cols(hl_sparse_matrix_s sMat); +extern int *hl_sparse_matrix_get_cols(hl_sparse_matrix_s sMat); /** * @brief get value pionter of GpuSparseMatrix @@ -513,7 +518,6 @@ extern int* hl_sparse_matrix_get_cols(hl_sparse_matrix_s sMat); * @return return value pointer, which is gpu address * */ -extern real* hl_sparse_matrix_get_value(hl_sparse_matrix_s sMat); - +extern real *hl_sparse_matrix_get_value(hl_sparse_matrix_s sMat); #endif /* HL_SPARSE_H_ */ diff --git a/paddle/cuda/include/hl_table_apply.h b/paddle/cuda/include/hl_table_apply.h index 3c9428e925..b4ac83a66a 100644 --- a/paddle/cuda/include/hl_table_apply.h +++ b/paddle/cuda/include/hl_table_apply.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_TABLE_APPLY_H_ #define HL_TABLE_APPLY_H_ @@ -31,8 +30,10 @@ limitations under the License. */ * @param[in] dim width of table. * */ -extern void hl_matrix_select_rows(real* output, int ldo, - real* table, int ldt, +extern void hl_matrix_select_rows(real* output, + int ldo, + real* table, + int ldt, int* ids, int numSamples, int tableSize, @@ -53,8 +54,10 @@ extern void hl_matrix_select_rows(real* output, int ldo, * @param[in] dim width of table. * */ -extern void hl_matrix_add_to_rows(real* table, int ldt, - real* input, int ldi, +extern void hl_matrix_add_to_rows(real* table, + int ldt, + real* input, + int ldi, int* ids, int numSamples, int tableSize, @@ -72,8 +75,7 @@ extern void hl_matrix_add_to_rows(real* table, int ldt, * */ template -extern void hl_vector_select_from(T* dst, int sized, - const T* src, int sizes, - const int* ids, int sizei); +extern void hl_vector_select_from( + T* dst, int sized, const T* src, int sizes, const int* ids, int sizei); -#endif /* HL_TABLE_APPLY_H_ */ +#endif /* HL_TABLE_APPLY_H_ */ diff --git a/paddle/cuda/include/hl_time.h b/paddle/cuda/include/hl_time.h index 4414b0b2d2..b0a88c66a1 100644 --- a/paddle/cuda/include/hl_time.h +++ b/paddle/cuda/include/hl_time.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_TIME_H_ #define HL_TIME_H_ diff --git a/paddle/cuda/include/hl_top_k.h b/paddle/cuda/include/hl_top_k.h index a38d4cf862..e8cfebbf6a 100644 --- a/paddle/cuda/include/hl_top_k.h +++ b/paddle/cuda/include/hl_top_k.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_TOP_K_H_ #define HL_TOP_K_H_ @@ -31,9 +30,11 @@ limitations under the License. */ * @param[in] numSamples height of input value. * */ -extern void hl_matrix_top_k(real* topVal, int ldv, - int * topIds, - real* src, int lds, +extern void hl_matrix_top_k(real* topVal, + int ldv, + int* topIds, + real* src, + int lds, int dim, int beamSize, int numSamples); @@ -50,8 +51,9 @@ extern void hl_matrix_top_k(real* topVal, int ldv, * * @note Only support HL_SPARSE_CSR format. */ -extern void hl_sparse_matrix_top_k(real* topVal, int ldv, - int * topIds, +extern void hl_sparse_matrix_top_k(real* topVal, + int ldv, + int* topIds, hl_sparse_matrix_s src, int beamSize, int numSamples); diff --git a/paddle/cuda/include/stub/hl_aggregate_stub.h b/paddle/cuda/include/stub/hl_aggregate_stub.h index 4c0c68f3c9..bb53fc581e 100644 --- a/paddle/cuda/include/stub/hl_aggregate_stub.h +++ b/paddle/cuda/include/stub/hl_aggregate_stub.h @@ -12,29 +12,22 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_AGGREGATE_STUB_H_ #define HL_AGGREGATE_STUB_H_ #include "hl_aggregate.h" -inline void hl_matrix_row_sum(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_row_sum(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_row_max(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_row_max(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_row_min(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_row_min(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_column_sum(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_column_sum(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_column_max(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_column_max(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_column_min(real *A_d, real *C_d, - int dimM, int dimN) {} +inline void hl_matrix_column_min(real *A_d, real *C_d, int dimM, int dimN) {} inline void hl_vector_sum(real *A_d, real *C_h, int dimM) {} diff --git a/paddle/cuda/include/stub/hl_cnn_stub.h b/paddle/cuda/include/stub/hl_cnn_stub.h index c6f32ad337..2f73b9671e 100644 --- a/paddle/cuda/include/stub/hl_cnn_stub.h +++ b/paddle/cuda/include/stub/hl_cnn_stub.h @@ -12,84 +12,134 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CNN_STUB_H_ #define HL_CNN_STUB_H_ #include "hl_cnn.h" -inline void hl_shrink_col2feature( - const real * dataCol, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataIm, - real alpha, real beta) {} - -inline void hl_expand_feature2col( - const real* dataIm, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataCol) {} - -inline void hl_maxpool_forward( - const int frameCnt, const real* inputData, - const int channels, - const int height, const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real* tgtData, const int tgtStride) {} - -inline void hl_maxpool_backward( - const int frameCnt, const real* inputData, - const real* outData, const real* outGrad, - const int channels, const int height, - const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real scaleA, real scaleB, - real* targetGrad, const int outStride) {} - -inline void hl_avgpool_forward( - const int frameCnt, const real* inputData, - const int channels, - const int height, const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - const int paddingH, const int paddingW, - real* tgtData, const int tgtStride) {} - -inline void hl_avgpool_backward( - const int frameCnt, const real* outGrad, - const int channels, const int height, - const int width, - const int pooledH, const int pooledW, - const int sizeX, const int sizeY, - const int strideH, const int strideW, - int paddingH, int paddingW, - real scaleA, real scaleB, - real* backGrad, const int outStride) {} - -inline void hl_CMRNorm_forward( - size_t frameCnt, const real* in, real* scale, real* out, - size_t channels, size_t height, size_t width, size_t sizeX, - real alpha, real beta) {} - -inline void hl_CMRNorm_backward( - size_t frameCnt, const real* inV, const real* scale, - const real* outV, const real* outDiff, real *inDiff, - size_t channels, size_t height, size_t width, size_t sizeX, - real alpha, real beta) {} +inline void hl_shrink_col2feature(const real* dataCol, + size_t channels, + size_t height, + size_t width, + size_t blockH, + size_t blockW, + size_t strideH, + size_t strideW, + size_t paddingH, + size_t paddingW, + size_t outputH, + size_t outputW, + real* dataIm, + real alpha, + real beta) {} + +inline void hl_expand_feature2col(const real* dataIm, + size_t channels, + size_t height, + size_t width, + size_t blockH, + size_t blockW, + size_t strideH, + size_t strideW, + size_t paddingH, + size_t paddingW, + size_t outputH, + size_t outputW, + real* dataCol) {} + +inline void hl_maxpool_forward(const int frameCnt, + const real* inputData, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real* tgtData, + const int tgtStride) {} + +inline void hl_maxpool_backward(const int frameCnt, + const real* inputData, + const real* outData, + const real* outGrad, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real scaleA, + real scaleB, + real* targetGrad, + const int outStride) {} + +inline void hl_avgpool_forward(const int frameCnt, + const real* inputData, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + const int paddingH, + const int paddingW, + real* tgtData, + const int tgtStride) {} + +inline void hl_avgpool_backward(const int frameCnt, + const real* outGrad, + const int channels, + const int height, + const int width, + const int pooledH, + const int pooledW, + const int sizeX, + const int sizeY, + const int strideH, + const int strideW, + int paddingH, + int paddingW, + real scaleA, + real scaleB, + real* backGrad, + const int outStride) {} + +inline void hl_CMRNorm_forward(size_t frameCnt, + const real* in, + real* scale, + real* out, + size_t channels, + size_t height, + size_t width, + size_t sizeX, + real alpha, + real beta) {} + +inline void hl_CMRNorm_backward(size_t frameCnt, + const real* inV, + const real* scale, + const real* outV, + const real* outDiff, + real* inDiff, + size_t channels, + size_t height, + size_t width, + size_t sizeX, + real alpha, + real beta) {} inline void hl_bilinear_forward(const real* inData, const size_t inImgH, @@ -106,25 +156,33 @@ inline void hl_bilinear_forward(const real* inData, const real ratioW) {} inline void hl_bilinear_backward(real* inGrad, - const size_t inImgH, - const size_t inImgW, - const size_t inputH, - const size_t inputW, - const real* outGrad, - const size_t outImgH, - const size_t outImgW, - const size_t outputH, - const size_t outputW, - const size_t numChannels, - const real ratioH, - const real ratioW) {} - -inline void hl_maxout_forward( - const real* inData, real* outData, int* idData, - size_t batchSize, size_t size, size_t featLen, size_t group) {} - -inline void hl_maxout_backward( - real* inGrad, const real* outGrad, const int* idData, - size_t batchSize, size_t size, size_t featLen, size_t group) {} + const size_t inImgH, + const size_t inImgW, + const size_t inputH, + const size_t inputW, + const real* outGrad, + const size_t outImgH, + const size_t outImgW, + const size_t outputH, + const size_t outputW, + const size_t numChannels, + const real ratioH, + const real ratioW) {} + +inline void hl_maxout_forward(const real* inData, + real* outData, + int* idData, + size_t batchSize, + size_t size, + size_t featLen, + size_t group) {} + +inline void hl_maxout_backward(real* inGrad, + const real* outGrad, + const int* idData, + size_t batchSize, + size_t size, + size_t featLen, + size_t group) {} #endif // HL_CNN_STUB_H_ diff --git a/paddle/cuda/include/stub/hl_cuda_cublas_stub.h b/paddle/cuda/include/stub/hl_cuda_cublas_stub.h index 903dcbe835..85f7c390c4 100644 --- a/paddle/cuda/include/stub/hl_cuda_cublas_stub.h +++ b/paddle/cuda/include/stub/hl_cuda_cublas_stub.h @@ -12,41 +12,42 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_CUBLAS_STUB_H_ #define HL_CUDA_CUBLAS_STUB_H_ #include "hl_cuda_cublas.h" -inline void hl_matrix_transpose(real *A_d, - real *C_d, - int dimM, - int dimN, - int lda, - int ldc) {} - -inline void hl_matrix_transpose(real *A_d, - real *C_d, - int dimM, - int dimN) {} - -inline void hl_matrix_inverse(real *A_d, - real *C_d, - int dimN, - int lda, - int ldc) {} - -inline void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, - real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta, - int lda, int ldb, int ldc) {} +inline void hl_matrix_transpose( + real *A_d, real *C_d, int dimM, int dimN, int lda, int ldc) {} + +inline void hl_matrix_transpose(real *A_d, real *C_d, int dimM, int dimN) {} -inline void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +inline void hl_matrix_inverse( + real *A_d, real *C_d, int dimN, int lda, int ldc) {} + +inline void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, + real *C_d, + int dimM, + int dimN, + int dimK, + real alpha, + real beta, + int lda, + int ldb, + int ldc) {} + +inline void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta) {} + int dimM, + int dimN, + int dimK, + real alpha, + real beta) {} #endif // HL_CUDA_CUBLAS_STUB_H_ diff --git a/paddle/cuda/include/stub/hl_cuda_cudnn_stub.h b/paddle/cuda/include/stub/hl_cuda_cudnn_stub.h index b96804afd8..3beb0e5b51 100644 --- a/paddle/cuda/include/stub/hl_cuda_cudnn_stub.h +++ b/paddle/cuda/include/stub/hl_cuda_cudnn_stub.h @@ -12,15 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_CUDNN_STUB_H_ #define HL_CUDA_CUDNN_STUB_H_ #include "hl_cuda_cudnn.h" -inline int hl_get_cudnn_lib_version() { - return 0; -} +inline int hl_get_cudnn_lib_version() { return 0; } inline void hl_create_tensor_descriptor(hl_tensor_descriptor* image_desc) {} @@ -68,41 +65,41 @@ inline void hl_pooling_backward(hl_tensor_descriptor input, hl_pooling_descriptor pooling) {} inline void hl_create_filter_descriptor(hl_filter_descriptor* filter, - int input_feature_maps, - int output_feature_maps, - int height, - int width) {} + int input_feature_maps, + int output_feature_maps, + int height, + int width) {} inline void hl_destroy_filter_descriptor(hl_filter_descriptor filter) {} inline void hl_create_convolution_descriptor(hl_convolution_descriptor* conv, - hl_tensor_descriptor image, - hl_filter_descriptor filter, - int padding_height, - int padding_width, - int stride_height, - int stride_width) {} + hl_tensor_descriptor image, + hl_filter_descriptor filter, + int padding_height, + int padding_width, + int stride_height, + int stride_width) {} inline void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, - hl_tensor_descriptor image, - hl_filter_descriptor filter, - int padding_height, - int padding_width, - int stride_height, - int stride_width) {} + hl_tensor_descriptor image, + hl_filter_descriptor filter, + int padding_height, + int padding_width, + int stride_height, + int stride_width) {} inline void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) {} inline void hl_conv_workspace(hl_tensor_descriptor input, - hl_tensor_descriptor output, - hl_filter_descriptor filter, - hl_convolution_descriptor conv, - int* convFwdAlgo, - size_t* fwdLimitBytes, - int* convBwdDataAlgo, - size_t* bwdDataLimitBytes, - int* convBwdFilterAlgo, - size_t* bwdFilterLimitBytes) {} + hl_tensor_descriptor output, + hl_filter_descriptor filter, + hl_convolution_descriptor conv, + int* convFwdAlgo, + size_t* fwdLimitBytes, + int* convBwdDataAlgo, + size_t* bwdDataLimitBytes, + int* convBwdFilterAlgo, + size_t* bwdFilterLimitBytes) {} inline void hl_convolution_forward(hl_tensor_descriptor input, real* input_data, @@ -116,86 +113,84 @@ inline void hl_convolution_forward(hl_tensor_descriptor input, int convFwdAlgo) {} inline void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, - real* bias_data, - hl_tensor_descriptor output, - real* output_data) {} - -inline void hl_convolution_backward_filter( - hl_tensor_descriptor input, - real* input_data, - hl_tensor_descriptor output, - real* output_grad_data, - hl_filter_descriptor filter, - real* filter_grad_data, - hl_convolution_descriptor conv, - void* gpuWorkSpace, - size_t sizeInBytes, - int convBwdFilterAlgo) {} - -inline void hl_convolution_backward_data( - hl_tensor_descriptor input, - real* input_data_grad, - hl_tensor_descriptor output, - real* output_grad_data, - hl_filter_descriptor filter, - real* filter_data, - hl_convolution_descriptor conv, - void* gpuWorkSpace, - size_t sizeInBytes, - int convBwdDataAlgo) {} + real* bias_data, + hl_tensor_descriptor output, + real* output_data) {} + +inline void hl_convolution_backward_filter(hl_tensor_descriptor input, + real* input_data, + hl_tensor_descriptor output, + real* output_grad_data, + hl_filter_descriptor filter, + real* filter_grad_data, + hl_convolution_descriptor conv, + void* gpuWorkSpace, + size_t sizeInBytes, + int convBwdFilterAlgo) {} + +inline void hl_convolution_backward_data(hl_tensor_descriptor input, + real* input_data_grad, + hl_tensor_descriptor output, + real* output_grad_data, + hl_filter_descriptor filter, + real* filter_data, + hl_convolution_descriptor conv, + void* gpuWorkSpace, + size_t sizeInBytes, + int convBwdDataAlgo) {} inline void hl_convolution_backward_bias(hl_tensor_descriptor bias, - real* bias_grad_data, - hl_tensor_descriptor output, - real* output_grad_data) {} + real* bias_grad_data, + hl_tensor_descriptor output, + real* output_grad_data) {} -inline void hl_softmax_forward(real *input, - real *output, - int height, - int width) {} - -inline void hl_softmax_backward(real *output_value, - real *output_grad, +inline void hl_softmax_forward(real* input, + real* output, int height, int width) {} +inline void hl_softmax_backward(real* output_value, + real* output_grad, + int height, + int width) {} + inline void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outputDesc, - real *output, + real* output, hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, + real* scale, + real* bias, double factor, - real *runningMean, - real *runningInvVar, + real* runningMean, + real* runningInvVar, double epsilon, - real *savedMean, - real *savedVar) {} + real* savedMean, + real* savedVar) {} inline void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outputDesc, - real *output, + real* output, hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, - real *estimatedMean, - real *estimatedVar, + real* scale, + real* bias, + real* estimatedMean, + real* estimatedVar, double epsilon) {} inline void hl_batch_norm_backward(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outGradDesc, - real *outGrad, + real* outGrad, hl_tensor_descriptor inGradDesc, - real *inGrad, + real* inGrad, hl_tensor_descriptor dBnParamDesc, - real *scale, - real *scaleGrad, - real *biasGrad, + real* scale, + real* scaleGrad, + real* biasGrad, double epsilon, - real *savedMean, - real *savedInvVar) {} + real* savedMean, + real* savedInvVar) {} #endif // HL_CUDA_CUDNN_STUB_H_ diff --git a/paddle/cuda/include/stub/hl_cuda_stub.h b/paddle/cuda/include/stub/hl_cuda_stub.h index 675ac03b0e..1f91068cdf 100644 --- a/paddle/cuda/include/stub/hl_cuda_stub.h +++ b/paddle/cuda/include/stub/hl_cuda_stub.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_CUDA_STUB_H_ #define HL_CUDA_STUB_H_ @@ -24,29 +23,25 @@ inline void hl_specify_devices_start(int *device, int number) {} inline void hl_init(int device) {} -inline int hl_get_cuda_lib_version(int device) { - return 0; -} +inline int hl_get_cuda_lib_version(int device) { return 0; } inline void hl_fini() {} inline void hl_set_sync_flag(bool flag) {} -inline bool hl_get_sync_flag() { - return false; -} +inline bool hl_get_sync_flag() { return false; } -inline int hl_get_device_count() { return 0; } +inline int hl_get_device_count() { return 0; } inline void hl_set_device(int device) {} -inline int hl_get_device() { return 0; } +inline int hl_get_device() { return 0; } -inline void* hl_malloc_device(size_t size) { return NULL; } +inline void *hl_malloc_device(size_t size) { return NULL; } inline void hl_free_mem_device(void *dest_d) {} -inline void* hl_malloc_host(size_t size) { return NULL; } +inline void *hl_malloc_host(size_t size) { return NULL; } inline void hl_free_mem_host(void *dest_h) {} @@ -64,7 +59,9 @@ inline void hl_rand(real *dest_d, size_t num) {} inline void hl_srand(unsigned int seed) {} -inline void hl_memcpy_async(void *dst, void *src, size_t size, +inline void hl_memcpy_async(void *dst, + void *src, + size_t size, hl_stream_t stream) {} inline void hl_stream_synchronize(hl_stream_t stream) {} @@ -83,11 +80,11 @@ inline void hl_stream_wait_event(hl_stream_t stream, hl_event_t event) {} inline void hl_event_synchronize(hl_event_t event) {} -inline int hl_get_device_last_error() { return 0; } +inline int hl_get_device_last_error() { return 0; } -inline const char* hl_get_device_error_string() { return NULL; } +inline const char *hl_get_device_error_string() { return NULL; } -inline const char* hl_get_device_error_string(size_t err) { return NULL; } +inline const char *hl_get_device_error_string(size_t err) { return NULL; } inline bool hl_cuda_event_is_ready(hl_event_t event) { return true; } diff --git a/paddle/cuda/include/stub/hl_lstm_stub.h b/paddle/cuda/include/stub/hl_lstm_stub.h index 2700bef02a..7ccda032d2 100644 --- a/paddle/cuda/include/stub/hl_lstm_stub.h +++ b/paddle/cuda/include/stub/hl_lstm_stub.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_LSTM_STUB_H_ #define HL_LSTM_STUB_H_ diff --git a/paddle/cuda/include/stub/hl_matrix_stub.h b/paddle/cuda/include/stub/hl_matrix_stub.h index 76cac2e577..1bd78d23fb 100644 --- a/paddle/cuda/include/stub/hl_matrix_stub.h +++ b/paddle/cuda/include/stub/hl_matrix_stub.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_MATRIX_STUB_H_ #define HL_MATRIX_STUB_H_ @@ -26,48 +25,30 @@ inline void hl_matrix_add(real* A_d, real alpha, real beta) {} -inline void hl_matrix_softmax(real *A_d, real *C_d, int dimM, int dimN) {} +inline void hl_matrix_softmax(real* A_d, real* C_d, int dimM, int dimN) {} -inline void hl_sequence_softmax_forward(real *A_d, - real *C_d, +inline void hl_sequence_softmax_forward(real* A_d, + real* C_d, const int* index, int numSequence) {} -inline void hl_matrix_softmax_derivative(real* grad_d, - real* output_d, - real* sftmaxSum_d, - int dimM, - int dimN) {} - -inline void hl_matrix_classification_error(real* A_d, - int* B_d, - real* C_d, - int dimM, - int dimN) {} - -inline void hl_matrix_cross_entropy(real* A_d, - real* C_d, - int* label_d, - int dimM, - int dimN) {} - -inline void hl_matrix_cross_entropy_bp(real* grad_d, - real* output_d, - int* label_d, - int dimM, - int dimN) {} - -inline void hl_matrix_multi_binary_cross_entropy(real* output, - real* entropy, - hl_sparse_matrix_s mat, - int dimM, - int dimN) {} - -inline void hl_matrix_multi_binary_cross_entropy_bp(real* output, - real* grad, - hl_sparse_matrix_s mat, - int dimM, - int dimN) {} +inline void hl_matrix_softmax_derivative( + real* grad_d, real* output_d, real* sftmaxSum_d, int dimM, int dimN) {} + +inline void hl_matrix_classification_error( + real* A_d, int* B_d, real* C_d, int dimM, int dimN) {} + +inline void hl_matrix_cross_entropy( + real* A_d, real* C_d, int* label_d, int dimM, int dimN) {} + +inline void hl_matrix_cross_entropy_bp( + real* grad_d, real* output_d, int* label_d, int dimM, int dimN) {} + +inline void hl_matrix_multi_binary_cross_entropy( + real* output, real* entropy, hl_sparse_matrix_s mat, int dimM, int dimN) {} + +inline void hl_matrix_multi_binary_cross_entropy_bp( + real* output, real* grad, hl_sparse_matrix_s mat, int dimM, int dimN) {} inline void hl_matrix_zero_mem(real* data, int num) {} @@ -101,7 +82,6 @@ inline void hl_cossim(real* output, int input2_height, real scale) {} - inline void hl_cossim_derivative(real* grad, real* output, real* prevOutX, diff --git a/paddle/cuda/include/stub/hl_sequence_stub.h b/paddle/cuda/include/stub/hl_sequence_stub.h index aabd956c37..381f0a6f26 100644 --- a/paddle/cuda/include/stub/hl_sequence_stub.h +++ b/paddle/cuda/include/stub/hl_sequence_stub.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_SEQUENCE_STUB_H_ #define HL_SEQUENCE_STUB_H_ @@ -21,15 +20,12 @@ limitations under the License. */ inline void hl_max_sequence_forward(real* input, const int* sequence, real* output, - int *index, + int* index, int numSequences, int dim) {} -inline void hl_max_sequence_backward(real* outputGrad, - int *index, - real* inputGrad, - int numSequences, - int dim) {} +inline void hl_max_sequence_backward( + real* outputGrad, int* index, real* inputGrad, int numSequences, int dim) {} inline void hl_context_projection_forward(real* input, const int* sequence, @@ -60,16 +56,16 @@ inline void hl_context_projection_backward_weight(real* outputGrad, int contextStart, int beginPad) {} -inline void hl_sequence2batch_copy(real *batch, - real *sequence, - const int *batchIndex, +inline void hl_sequence2batch_copy(real* batch, + real* sequence, + const int* batchIndex, int seqWidth, int batchCount, bool seq2batch) {} -inline void hl_sequence2batch_add(real *batch, - real *sequence, - int *batchIndex, +inline void hl_sequence2batch_add(real* batch, + real* sequence, + int* batchIndex, int seqWidth, int batchCount, bool seq2batch) {} diff --git a/paddle/cuda/include/stub/hl_sparse_stub.h b/paddle/cuda/include/stub/hl_sparse_stub.h index 346a1900dd..d47bdd2c47 100644 --- a/paddle/cuda/include/stub/hl_sparse_stub.h +++ b/paddle/cuda/include/stub/hl_sparse_stub.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef HL_SPARSE_STUB_H_ #define HL_SPARSE_STUB_H_ @@ -20,7 +19,7 @@ limitations under the License. */ inline void hl_malloc_sparse_matrix(hl_sparse_matrix_s *A_d, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz) {} @@ -28,20 +27,20 @@ inline void hl_malloc_sparse_matrix(hl_sparse_matrix_s *A_d, inline void hl_free_sparse_matrix(hl_sparse_matrix_s A_d) {} inline void hl_construct_sparse_matrix(hl_sparse_matrix_s *A_d, - void * dest_d, + void *dest_d, size_t size, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz) {} inline void hl_construct_sparse_matrix(hl_sparse_matrix_s *A_d, - real* value_d, - int* rows_d, - int* cols_d, + real *value_d, + int *rows_d, + int *cols_d, hl_matrix_format_t format, - hl_matrix_value_t value_type, + hl_matrix_value_t value_type, int dimM, int dimN, int nnz) {} @@ -87,10 +86,14 @@ inline void hl_matrix_csr_mul_dense(hl_sparse_matrix_s A_d, inline void hl_matrix_csc_mul_dense(hl_sparse_matrix_s A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta) {} + int dimM, + int dimN, + int dimK, + real alpha, + real beta) {} inline void hl_matrix_dense_mul_csc(real *A_d, hl_trans_op_t transa, @@ -103,18 +106,27 @@ inline void hl_matrix_dense_mul_csc(real *A_d, real alpha, real beta) {} -inline void hl_sparse_matrix_mul(real* A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +inline void hl_sparse_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, hl_sparse_matrix_s C_d, - int dimM, int dimN, int dimK, - real alpha, real beta) {} + int dimM, + int dimN, + int dimK, + real alpha, + real beta) {} -inline void hl_matrix_dense_mul_csr(real *A_d, hl_trans_op_t transa, +inline void hl_matrix_dense_mul_csr(real *A_d, + hl_trans_op_t transa, hl_sparse_matrix_s B_d, hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta) {} + int dimM, + int dimN, + int dimK, + real alpha, + real beta) {} inline void hl_memcpy_from_csc_matrix(real *csc_val, size_t val_size, @@ -134,49 +146,39 @@ inline void hl_memcpy_from_csr_matrix(real *csr_val, hl_sparse_matrix_s csr_matrix, hl_stream_t stream) {} -inline void hl_sparse_matrix_column_sum(real* A_d, - hl_sparse_matrix_s B_d, - int dimM, - int dimN, - real scale) {} +inline void hl_sparse_matrix_column_sum( + real *A_d, hl_sparse_matrix_s B_d, int dimM, int dimN, real scale) {} -inline void hl_matrix_csr_column_sum(real* A_d, - hl_sparse_matrix_s B_d, - int dimM, - int dimN, - real scale) {} +inline void hl_matrix_csr_column_sum( + real *A_d, hl_sparse_matrix_s B_d, int dimM, int dimN, real scale) {} inline void hl_sparse_matrix_add_bias(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, real scale) {} inline void hl_matrix_csr_add_bias(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, real scale) {} inline void hl_sparse_matrix_add_dense(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, int dimM, int dimN, real alpha, real beta) {} inline void hl_matrix_csr_add_dense(hl_sparse_matrix_s A_d, - real* B_d, + real *B_d, int dimM, int dimN, real alpha, real beta) {} -inline int* hl_sparse_matrix_get_rows(hl_sparse_matrix_s sMat) { - return NULL; -} +inline int *hl_sparse_matrix_get_rows(hl_sparse_matrix_s sMat) { return NULL; } -inline int* hl_sparse_matrix_get_cols(hl_sparse_matrix_s sMat) { - return NULL; -} +inline int *hl_sparse_matrix_get_cols(hl_sparse_matrix_s sMat) { return NULL; } -inline real* hl_sparse_matrix_get_value(hl_sparse_matrix_s sMat) { +inline real *hl_sparse_matrix_get_value(hl_sparse_matrix_s sMat) { return NULL; } diff --git a/paddle/cuda/src/avx_mathfun.h b/paddle/cuda/src/avx_mathfun.h index 2922d4dc29..2412ed5abc 100644 --- a/paddle/cuda/src/avx_mathfun.h +++ b/paddle/cuda/src/avx_mathfun.h @@ -32,32 +32,35 @@ #include /* yes I know, the top of this file is quite ugly */ -# define ALIGN32_BEG -# define ALIGN32_END __attribute__((aligned(32))) +#define ALIGN32_BEG +#define ALIGN32_END __attribute__((aligned(32))) /* __m128 is ugly to write */ -typedef __m256 v8sf; // vector of 8 float (avx) -typedef __m256i v8si; // vector of 8 int (avx) -typedef __m128i v4si; // vector of 8 int (avx) +typedef __m256 v8sf; // vector of 8 float (avx) +typedef __m256i v8si; // vector of 8 int (avx) +typedef __m128i v4si; // vector of 8 int (avx) -#define _PI32AVX_CONST(Name, Val) \ - static const ALIGN32_BEG int _pi32avx_##Name[4] ALIGN32_END = { Val, Val, Val, Val } +#define _PI32AVX_CONST(Name, Val) \ + static const ALIGN32_BEG int _pi32avx_##Name[4] ALIGN32_END = { \ + Val, Val, Val, Val} _PI32AVX_CONST(1, 1); _PI32AVX_CONST(inv1, ~1); _PI32AVX_CONST(2, 2); _PI32AVX_CONST(4, 4); - /* declare some AVX constants -- why can't I figure a better way to do that? */ -#define _PS256_CONST(Name, Val) \ - static const ALIGN32_BEG float _ps256_##Name[8] ALIGN32_END = { Val, Val, Val, Val, Val, Val, Val, Val } -#define _PI32_CONST256(Name, Val) \ - static const ALIGN32_BEG int _pi32_256_##Name[8] ALIGN32_END = { Val, Val, Val, Val, Val, Val, Val, Val } -#define _PS256_CONST_TYPE(Name, Type, Val) \ - static const ALIGN32_BEG Type _ps256_##Name[8] ALIGN32_END = { Val, Val, Val, Val, Val, Val, Val, Val } - -_PS256_CONST(1 , 1.0f); +#define _PS256_CONST(Name, Val) \ + static const ALIGN32_BEG float _ps256_##Name[8] ALIGN32_END = { \ + Val, Val, Val, Val, Val, Val, Val, Val} +#define _PI32_CONST256(Name, Val) \ + static const ALIGN32_BEG int _pi32_256_##Name[8] ALIGN32_END = { \ + Val, Val, Val, Val, Val, Val, Val, Val} +#define _PS256_CONST_TYPE(Name, Type, Val) \ + static const ALIGN32_BEG Type _ps256_##Name[8] ALIGN32_END = { \ + Val, Val, Val, Val, Val, Val, Val, Val} + +_PS256_CONST(1, 1.0f); _PS256_CONST(0p5, 0.5f); /* the smallest non denormalized float number */ _PS256_CONST_TYPE(min_norm_pos, int, 0x00800000); @@ -76,14 +79,14 @@ _PI32_CONST256(0x7f, 0x7f); _PS256_CONST(cephes_SQRTHF, 0.707106781186547524); _PS256_CONST(cephes_log_p0, 7.0376836292E-2); -_PS256_CONST(cephes_log_p1, - 1.1514610310E-1); +_PS256_CONST(cephes_log_p1, -1.1514610310E-1); _PS256_CONST(cephes_log_p2, 1.1676998740E-1); -_PS256_CONST(cephes_log_p3, - 1.2420140846E-1); -_PS256_CONST(cephes_log_p4, + 1.4249322787E-1); -_PS256_CONST(cephes_log_p5, - 1.6668057665E-1); -_PS256_CONST(cephes_log_p6, + 2.0000714765E-1); -_PS256_CONST(cephes_log_p7, - 2.4999993993E-1); -_PS256_CONST(cephes_log_p8, + 3.3333331174E-1); +_PS256_CONST(cephes_log_p3, -1.2420140846E-1); +_PS256_CONST(cephes_log_p4, +1.4249322787E-1); +_PS256_CONST(cephes_log_p5, -1.6668057665E-1); +_PS256_CONST(cephes_log_p6, +2.0000714765E-1); +_PS256_CONST(cephes_log_p7, -2.4999993993E-1); +_PS256_CONST(cephes_log_p8, +3.3333331174E-1); _PS256_CONST(cephes_log_q1, -2.12194440e-4); _PS256_CONST(cephes_log_q2, 0.693359375); @@ -94,50 +97,51 @@ typedef union imm_xmm_union { v4si xmm[2]; } imm_xmm_union; -#define COPY_IMM_TO_XMM(imm_, xmm0_, xmm1_) { \ - imm_xmm_union u __attribute__((aligned(32))); \ - u.imm = imm_; \ - xmm0_ = u.xmm[0]; \ - xmm1_ = u.xmm[1]; \ -} - -#define COPY_XMM_TO_IMM(xmm0_, xmm1_, imm_) { \ +#define COPY_IMM_TO_XMM(imm_, xmm0_, xmm1_) \ + { \ imm_xmm_union u __attribute__((aligned(32))); \ - u.xmm[0]=xmm0_; u.xmm[1]=xmm1_; imm_ = u.imm; \ + u.imm = imm_; \ + xmm0_ = u.xmm[0]; \ + xmm1_ = u.xmm[1]; \ } +#define COPY_XMM_TO_IMM(xmm0_, xmm1_, imm_) \ + { \ + imm_xmm_union u __attribute__((aligned(32))); \ + u.xmm[0] = xmm0_; \ + u.xmm[1] = xmm1_; \ + imm_ = u.imm; \ + } -#define AVX2_BITOP_USING_SSE2(fn) \ -static inline v8si avx2_mm256_##fn(v8si x, int a) \ -{ \ - /* use SSE2 instruction to perform the bitop AVX2 */ \ - v4si x1, x2; \ - v8si ret; \ - COPY_IMM_TO_XMM(x, x1, x2); \ - x1 = _mm_##fn(x1,a); \ - x2 = _mm_##fn(x2,a); \ - COPY_XMM_TO_IMM(x1, x2, ret); \ - return(ret); \ -} +#define AVX2_BITOP_USING_SSE2(fn) \ + static inline v8si avx2_mm256_##fn(v8si x, int a) { \ + /* use SSE2 instruction to perform the bitop AVX2 */ \ + v4si x1, x2; \ + v8si ret; \ + COPY_IMM_TO_XMM(x, x1, x2); \ + x1 = _mm_##fn(x1, a); \ + x2 = _mm_##fn(x2, a); \ + COPY_XMM_TO_IMM(x1, x2, ret); \ + return (ret); \ + } //#warning "Using SSE2 to perform AVX2 bitshift ops" AVX2_BITOP_USING_SSE2(slli_epi32) AVX2_BITOP_USING_SSE2(srli_epi32) -#define AVX2_INTOP_USING_SSE2(fn) \ -static inline v8si avx2_mm256_##fn(v8si x, v8si y) \ -{ \ - /* use SSE2 instructions to perform the AVX2 integer operation */ \ - v4si x1, x2; \ - v4si y1, y2; \ - v8si ret; \ - COPY_IMM_TO_XMM(x, x1, x2); \ - COPY_IMM_TO_XMM(y, y1, y2); \ - x1 = _mm_##fn(x1,y1); \ - x2 = _mm_##fn(x2,y2); \ - COPY_XMM_TO_IMM(x1, x2, ret); \ - return(ret); \ -} +#define AVX2_INTOP_USING_SSE2(fn) \ + static inline v8si avx2_mm256_##fn(v8si x, v8si y) { \ + /* use SSE2 instructions to perform the AVX2 integer operation */ \ + v4si x1, x2; \ + v4si y1, y2; \ + v8si ret; \ + COPY_IMM_TO_XMM(x, x1, x2); \ + COPY_IMM_TO_XMM(y, y1, y2); \ + x1 = _mm_##fn(x1, y1); \ + x2 = _mm_##fn(x2, y2); \ + COPY_XMM_TO_IMM(x1, x2, ret); \ + return (ret); \ + } //#warning "Using SSE2 to perform AVX2 integer ops" AVX2_INTOP_USING_SSE2(and_si128) @@ -157,84 +161,83 @@ AVX2_INTOP_USING_SSE2(add_epi32) #define avx2_mm256_add_epi32 _mm256_add_epi32 #endif /* __AVX2__ */ - -/* natural logarithm computed for 8 simultaneous float +/* natural logarithm computed for 8 simultaneous float return NaN for x <= 0 */ v8sf log256_ps(v8sf x) { v8si imm0; - v8sf one = *(v8sf*)_ps256_1; + v8sf one = *(v8sf *)_ps256_1; - //v8sf invalid_mask = _mm256_cmple_ps(x, _mm256_setzero_ps()); + // v8sf invalid_mask = _mm256_cmple_ps(x, _mm256_setzero_ps()); v8sf invalid_mask = _mm256_cmp_ps(x, _mm256_setzero_ps(), _CMP_LE_OS); - x = _mm256_max_ps(x, *(v8sf*)_ps256_min_norm_pos); /* cut off denormalized stuff */ + x = _mm256_max_ps( + x, *(v8sf *)_ps256_min_norm_pos); /* cut off denormalized stuff */ // can be done with AVX2 imm0 = avx2_mm256_srli_epi32(_mm256_castps_si256(x), 23); /* keep only the fractional part */ - x = _mm256_and_ps(x, *(v8sf*)_ps256_inv_mant_mask); - x = _mm256_or_ps(x, *(v8sf*)_ps256_0p5); + x = _mm256_and_ps(x, *(v8sf *)_ps256_inv_mant_mask); + x = _mm256_or_ps(x, *(v8sf *)_ps256_0p5); // this is again another AVX2 instruction - imm0 = avx2_mm256_sub_epi32(imm0, *(v8si*)_pi32_256_0x7f); + imm0 = avx2_mm256_sub_epi32(imm0, *(v8si *)_pi32_256_0x7f); v8sf e = _mm256_cvtepi32_ps(imm0); e = _mm256_add_ps(e, one); - /* part2: + /* part2: if( x < SQRTHF ) { e -= 1; x = x + x - 1.0; } else { x = x - 1.0; } */ - //v8sf mask = _mm256_cmplt_ps(x, *(v8sf*)_ps256_cephes_SQRTHF); - v8sf mask = _mm256_cmp_ps(x, *(v8sf*)_ps256_cephes_SQRTHF, _CMP_LT_OS); + // v8sf mask = _mm256_cmplt_ps(x, *(v8sf*)_ps256_cephes_SQRTHF); + v8sf mask = _mm256_cmp_ps(x, *(v8sf *)_ps256_cephes_SQRTHF, _CMP_LT_OS); v8sf tmp = _mm256_and_ps(x, mask); x = _mm256_sub_ps(x, one); e = _mm256_sub_ps(e, _mm256_and_ps(one, mask)); x = _mm256_add_ps(x, tmp); - v8sf z = _mm256_mul_ps(x,x); + v8sf z = _mm256_mul_ps(x, x); - v8sf y = *(v8sf*)_ps256_cephes_log_p0; + v8sf y = *(v8sf *)_ps256_cephes_log_p0; y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p1); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p1); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p2); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p2); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p3); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p3); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p4); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p4); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p5); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p5); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p6); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p6); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p7); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p7); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_log_p8); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_log_p8); y = _mm256_mul_ps(y, x); y = _mm256_mul_ps(y, z); - - tmp = _mm256_mul_ps(e, *(v8sf*)_ps256_cephes_log_q1); - y = _mm256_add_ps(y, tmp); + tmp = _mm256_mul_ps(e, *(v8sf *)_ps256_cephes_log_q1); + y = _mm256_add_ps(y, tmp); - tmp = _mm256_mul_ps(z, *(v8sf*)_ps256_0p5); + tmp = _mm256_mul_ps(z, *(v8sf *)_ps256_0p5); y = _mm256_sub_ps(y, tmp); - tmp = _mm256_mul_ps(e, *(v8sf*)_ps256_cephes_log_q2); + tmp = _mm256_mul_ps(e, *(v8sf *)_ps256_cephes_log_q2); x = _mm256_add_ps(x, y); x = _mm256_add_ps(x, tmp); - x = _mm256_or_ps(x, invalid_mask); // negative arg will be NAN + x = _mm256_or_ps(x, invalid_mask); // negative arg will be NAN return x; } -_PS256_CONST(exp_hi, 88.3762626647949f); -_PS256_CONST(exp_lo, -88.3762626647949f); +_PS256_CONST(exp_hi, 88.3762626647949f); +_PS256_CONST(exp_lo, -88.3762626647949f); _PS256_CONST(cephes_LOG2EF, 1.44269504088896341); _PS256_CONST(cephes_exp_C1, 0.693359375); @@ -250,45 +253,45 @@ _PS256_CONST(cephes_exp_p5, 5.0000001201E-1); v8sf exp256_ps(v8sf x) { v8sf tmp = _mm256_setzero_ps(), fx; v8si imm0; - v8sf one = *(v8sf*)_ps256_1; + v8sf one = *(v8sf *)_ps256_1; - x = _mm256_min_ps(x, *(v8sf*)_ps256_exp_hi); - x = _mm256_max_ps(x, *(v8sf*)_ps256_exp_lo); + x = _mm256_min_ps(x, *(v8sf *)_ps256_exp_hi); + x = _mm256_max_ps(x, *(v8sf *)_ps256_exp_lo); /* express exp(x) as exp(g + n*log(2)) */ - fx = _mm256_mul_ps(x, *(v8sf*)_ps256_cephes_LOG2EF); - fx = _mm256_add_ps(fx, *(v8sf*)_ps256_0p5); + fx = _mm256_mul_ps(x, *(v8sf *)_ps256_cephes_LOG2EF); + fx = _mm256_add_ps(fx, *(v8sf *)_ps256_0p5); /* how to perform a floorf with SSE: just below */ - //imm0 = _mm256_cvttps_epi32(fx); - //tmp = _mm256_cvtepi32_ps(imm0); - + // imm0 = _mm256_cvttps_epi32(fx); + // tmp = _mm256_cvtepi32_ps(imm0); + tmp = _mm256_floor_ps(fx); /* if greater, substract 1 */ - //v8sf mask = _mm256_cmpgt_ps(tmp, fx); - v8sf mask = _mm256_cmp_ps(tmp, fx, _CMP_GT_OS); + // v8sf mask = _mm256_cmpgt_ps(tmp, fx); + v8sf mask = _mm256_cmp_ps(tmp, fx, _CMP_GT_OS); mask = _mm256_and_ps(mask, one); fx = _mm256_sub_ps(tmp, mask); - tmp = _mm256_mul_ps(fx, *(v8sf*)_ps256_cephes_exp_C1); - v8sf z = _mm256_mul_ps(fx, *(v8sf*)_ps256_cephes_exp_C2); + tmp = _mm256_mul_ps(fx, *(v8sf *)_ps256_cephes_exp_C1); + v8sf z = _mm256_mul_ps(fx, *(v8sf *)_ps256_cephes_exp_C2); x = _mm256_sub_ps(x, tmp); x = _mm256_sub_ps(x, z); - z = _mm256_mul_ps(x,x); - - v8sf y = *(v8sf*)_ps256_cephes_exp_p0; + z = _mm256_mul_ps(x, x); + + v8sf y = *(v8sf *)_ps256_cephes_exp_p0; y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_exp_p1); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_exp_p1); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_exp_p2); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_exp_p2); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_exp_p3); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_exp_p3); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_exp_p4); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_exp_p4); y = _mm256_mul_ps(y, x); - y = _mm256_add_ps(y, *(v8sf*)_ps256_cephes_exp_p5); + y = _mm256_add_ps(y, *(v8sf *)_ps256_cephes_exp_p5); y = _mm256_mul_ps(y, z); y = _mm256_add_ps(y, x); y = _mm256_add_ps(y, one); @@ -296,7 +299,7 @@ v8sf exp256_ps(v8sf x) { /* build 2^n */ imm0 = _mm256_cvttps_epi32(fx); // another two AVX2 instructions - imm0 = avx2_mm256_add_epi32(imm0, *(v8si*)_pi32_256_0x7f); + imm0 = avx2_mm256_add_epi32(imm0, *(v8si *)_pi32_256_0x7f); imm0 = avx2_mm256_slli_epi32(imm0, 23); v8sf pow2n = _mm256_castsi256_ps(imm0); y = _mm256_mul_ps(y, pow2n); @@ -307,13 +310,12 @@ _PS256_CONST(minus_cephes_DP1, -0.78515625); _PS256_CONST(minus_cephes_DP2, -2.4187564849853515625e-4); _PS256_CONST(minus_cephes_DP3, -3.77489497744594108e-8); _PS256_CONST(sincof_p0, -1.9515295891E-4); -_PS256_CONST(sincof_p1, 8.3321608736E-3); +_PS256_CONST(sincof_p1, 8.3321608736E-3); _PS256_CONST(sincof_p2, -1.6666654611E-1); -_PS256_CONST(coscof_p0, 2.443315711809948E-005); +_PS256_CONST(coscof_p0, 2.443315711809948E-005); _PS256_CONST(coscof_p1, -1.388731625493765E-003); -_PS256_CONST(coscof_p2, 4.166664568298827E-002); -_PS256_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI - +_PS256_CONST(coscof_p2, 4.166664568298827E-002); +_PS256_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI /* evaluation of 8 sines at onces using AVX intrisics @@ -327,7 +329,7 @@ _PS256_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI surprising but correct result. */ -v8sf sin256_ps(v8sf x) { // any x +v8sf sin256_ps(v8sf x) { // any x v8sf xmm1, xmm2 = _mm256_setzero_ps(), xmm3, sign_bit, y; v8si imm0, imm2; @@ -338,78 +340,78 @@ v8sf sin256_ps(v8sf x) { // any x sign_bit = x; /* take the absolute value */ - x = _mm256_and_ps(x, *(v8sf*)_ps256_inv_sign_mask); + x = _mm256_and_ps(x, *(v8sf *)_ps256_inv_sign_mask); /* extract the sign bit (upper one) */ - sign_bit = _mm256_and_ps(sign_bit, *(v8sf*)_ps256_sign_mask); - + sign_bit = _mm256_and_ps(sign_bit, *(v8sf *)_ps256_sign_mask); + /* scale by 4/Pi */ - y = _mm256_mul_ps(x, *(v8sf*)_ps256_cephes_FOPI); + y = _mm256_mul_ps(x, *(v8sf *)_ps256_cephes_FOPI); - /* - Here we start a series of integer operations, which are in the - realm of AVX2. - If we don't have AVX, let's perform them using SSE2 directives - */ +/* + Here we start a series of integer operations, which are in the + realm of AVX2. + If we don't have AVX, let's perform them using SSE2 directives +*/ #ifdef __AVX2__ /* store the integer part of y in mm0 */ imm2 = _mm256_cvttps_epi32(y); /* j=(j+1) & (~1) (see the cephes sources) */ // another two AVX2 instruction - imm2 = avx2_mm256_add_epi32(imm2, *(v8si*)_pi32_256_1); - imm2 = avx2_mm256_and_si256(imm2, *(v8si*)_pi32_256_inv1); + imm2 = avx2_mm256_add_epi32(imm2, *(v8si *)_pi32_256_1); + imm2 = avx2_mm256_and_si256(imm2, *(v8si *)_pi32_256_inv1); y = _mm256_cvtepi32_ps(imm2); /* get the swap sign flag */ - imm0 = avx2_mm256_and_si256(imm2, *(v8si*)_pi32_256_4); + imm0 = avx2_mm256_and_si256(imm2, *(v8si *)_pi32_256_4); imm0 = avx2_mm256_slli_epi32(imm0, 29); - /* get the polynom selection mask + /* get the polynom selection mask there is one polynom for 0 <= x <= Pi/4 and another one for Pi/4 #include "hl_functions.h" namespace hppl { - extern __m256 exp(__m256 a); +extern __m256 exp(__m256 a); - __m256 relu(const __m256 a) { - __m256 tmp = _mm256_set1_ps(0.0f); - return _mm256_max_ps(a, tmp); - } +__m256 relu(const __m256 a) { + __m256 tmp = _mm256_set1_ps(0.0f); + return _mm256_max_ps(a, tmp); +} - __m256 sigmoid(const __m256 a) { - __m256 max = _mm256_set1_ps(SIGMOID_THRESHOLD_MAX); - __m256 min = _mm256_set1_ps(SIGMOID_THRESHOLD_MIN); - __m256 tmp = _mm256_max_ps(a, min); - tmp = _mm256_min_ps(tmp, max); - tmp = _mm256_sub_ps(_mm256_set1_ps(0.0f), tmp); - tmp = exp(tmp); - tmp = _mm256_add_ps(_mm256_set1_ps(1.0f), tmp); - tmp = _mm256_div_ps(_mm256_set1_ps(1.0f), tmp); - return tmp; - } +__m256 sigmoid(const __m256 a) { + __m256 max = _mm256_set1_ps(SIGMOID_THRESHOLD_MAX); + __m256 min = _mm256_set1_ps(SIGMOID_THRESHOLD_MIN); + __m256 tmp = _mm256_max_ps(a, min); + tmp = _mm256_min_ps(tmp, max); + tmp = _mm256_sub_ps(_mm256_set1_ps(0.0f), tmp); + tmp = exp(tmp); + tmp = _mm256_add_ps(_mm256_set1_ps(1.0f), tmp); + tmp = _mm256_div_ps(_mm256_set1_ps(1.0f), tmp); + return tmp; +} - __m256 tanh(const __m256 a) { - __m256 max = _mm256_set1_ps(EXP_MAX_INPUT); - __m256 tmp = _mm256_mul_ps(_mm256_set1_ps(-2.0f), a); - tmp = _mm256_min_ps(tmp, max); - tmp = exp(tmp); - return _mm256_sub_ps( - _mm256_div_ps(_mm256_set1_ps(2.0f), - _mm256_add_ps(_mm256_set1_ps(1.0f), tmp)), _mm256_set1_ps(1.0f)); - } +__m256 tanh(const __m256 a) { + __m256 max = _mm256_set1_ps(EXP_MAX_INPUT); + __m256 tmp = _mm256_mul_ps(_mm256_set1_ps(-2.0f), a); + tmp = _mm256_min_ps(tmp, max); + tmp = exp(tmp); + return _mm256_sub_ps(_mm256_div_ps(_mm256_set1_ps(2.0f), + _mm256_add_ps(_mm256_set1_ps(1.0f), tmp)), + _mm256_set1_ps(1.0f)); +} - __m256 linear(const __m256 a) { - return a; - } +__m256 linear(const __m256 a) { return a; } - __m256 relu(const __m256 a, const __m256 b) { - return _mm256_mul_ps(a, +__m256 relu(const __m256 a, const __m256 b) { + return _mm256_mul_ps( + a, _mm256_and_ps(_mm256_cmp_ps(b, _mm256_set1_ps(0.0f), _CMP_GT_OS), - _mm256_set1_ps(1.0f))); - } + _mm256_set1_ps(1.0f))); +} - __m256 sigmoid(const __m256 a, const __m256 b) { - return _mm256_mul_ps(_mm256_mul_ps(a, b), - _mm256_sub_ps(_mm256_set1_ps(1.0f), b)); - } +__m256 sigmoid(const __m256 a, const __m256 b) { + return _mm256_mul_ps(_mm256_mul_ps(a, b), + _mm256_sub_ps(_mm256_set1_ps(1.0f), b)); +} - __m256 tanh(const __m256 a, const __m256 b) { - return _mm256_mul_ps(a, - _mm256_sub_ps(_mm256_set1_ps(1.0f), _mm256_mul_ps(b, b))); - } +__m256 tanh(const __m256 a, const __m256 b) { + return _mm256_mul_ps( + a, _mm256_sub_ps(_mm256_set1_ps(1.0f), _mm256_mul_ps(b, b))); +} - __m256 linear(const __m256 a, const __m256 b) { - return a; - } +__m256 linear(const __m256 a, const __m256 b) { return a; } } // namespace hppl diff --git a/paddle/cuda/src/hl_cpu_functions.cc b/paddle/cuda/src/hl_cpu_functions.cc index b8352c2d53..af00f352e5 100644 --- a/paddle/cuda/src/hl_cpu_functions.cc +++ b/paddle/cuda/src/hl_cpu_functions.cc @@ -12,46 +12,33 @@ WITHOUT 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 "hl_functions.h" namespace hppl { - real relu(const real a) { - return a > 0.0f ? a : 0.0f; - } - - real sigmoid(const real a) { - const real min = SIGMOID_THRESHOLD_MIN; - const real max = SIGMOID_THRESHOLD_MAX; - real tmp = (a < min) ? min : ((a > max) ? max : a); - return 1.0 / (1.0 + exp(-tmp)); - } - - real tanh(const real a) { - real tmp = -2.0 * a; - tmp = (tmp > EXP_MAX_INPUT) ? EXP_MAX_INPUT : tmp; - return (2.0 / (1.0 + exp(tmp))) - 1.0; - } - - real linear(const real a) { - return a; - } - - real relu(const real a, const real b) { - return a * (b > 0.0f ? 1.0f : 0.0f); - } - - real sigmoid(const real a, const real b) { - return a * b * (1 - b); - } - - real tanh(const real a, const real b) { - return a * (1.0f - b * b); - } - - real linear(const real a, const real b) { - return a; - } +real relu(const real a) { return a > 0.0f ? a : 0.0f; } + +real sigmoid(const real a) { + const real min = SIGMOID_THRESHOLD_MIN; + const real max = SIGMOID_THRESHOLD_MAX; + real tmp = (a < min) ? min : ((a > max) ? max : a); + return 1.0 / (1.0 + exp(-tmp)); +} + +real tanh(const real a) { + real tmp = -2.0 * a; + tmp = (tmp > EXP_MAX_INPUT) ? EXP_MAX_INPUT : tmp; + return (2.0 / (1.0 + exp(tmp))) - 1.0; +} + +real linear(const real a) { return a; } + +real relu(const real a, const real b) { return a * (b > 0.0f ? 1.0f : 0.0f); } + +real sigmoid(const real a, const real b) { return a * b * (1 - b); } + +real tanh(const real a, const real b) { return a * (1.0f - b * b); } + +real linear(const real a, const real b) { return a; } } // namespace hppl diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index abf6afadc2..f82d6c9402 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "hl_cuda.h" @@ -24,7 +23,7 @@ limitations under the License. */ namespace dynload { std::once_flag cublas_dso_flag; -void* cublas_dso_handle = nullptr; +void *cublas_dso_handle = nullptr; /** * The following macro definition can generate structs @@ -34,38 +33,32 @@ void* cublas_dso_handle = nullptr; * note: default dynamic linked libs */ #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - cublasStatus_t operator()(Args... args) { \ - typedef cublasStatus_t (*cublasFunc)(Args...); \ - std::call_once(cublas_dso_flag, GetCublasDsoHandle, \ - &cublas_dso_handle); \ - void* p_##__name = dlsym(cublas_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + typedef cublasStatus_t (*cublasFunc)(Args...); \ + std::call_once(cublas_dso_flag, GetCublasDsoHandle, &cublas_dso_handle); \ + void *p_##__name = dlsym(cublas_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; // struct DynLoad__##__name #else -#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - cublasStatus_t operator()(Args... args) { \ - return __name(args...); \ - } \ +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + return __name(args...); \ + } \ } __name; // struct DynLoad__##__name #endif -#define DYNAMIC_LOAD_CUBLAS_V2_WRAP(__name) \ - DYNAMIC_LOAD_CUBLAS_WRAP(__name) +#define DYNAMIC_LOAD_CUBLAS_V2_WRAP(__name) DYNAMIC_LOAD_CUBLAS_WRAP(__name) // include all needed cublas functions in HPPL -#define CUBLAS_BLAS_ROUTINE_EACH(__macro) \ - __macro(cublasSgemv) \ - __macro(cublasDgemv) \ - __macro(cublasSgemm) \ - __macro(cublasDgemm) \ - __macro(cublasSgeam) \ - __macro(cublasDgeam) \ +#define CUBLAS_BLAS_ROUTINE_EACH(__macro) \ + __macro(cublasSgemv) __macro(cublasDgemv) __macro(cublasSgemm) \ + __macro(cublasDgemm) __macro(cublasSgeam) __macro(cublasDgeam) DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasCreate) DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasDestroy) @@ -88,41 +81,40 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) } /* namespace dynload */ - #ifndef PADDLE_TYPE_DOUBLE -#define CUBLAS_GEAM dynload::cublasSgeam -#define CUBLAS_GEMV dynload::cublasSgemv -#define CUBLAS_GEMM dynload::cublasSgemm -#define CUBLAS_GETRF dynload::cublasSgetrfBatched -#define CUBLAS_GETRI dynload::cublasSgetriBatched +#define CUBLAS_GEAM dynload::cublasSgeam +#define CUBLAS_GEMV dynload::cublasSgemv +#define CUBLAS_GEMM dynload::cublasSgemm +#define CUBLAS_GETRF dynload::cublasSgetrfBatched +#define CUBLAS_GETRI dynload::cublasSgetriBatched #else -#define CUBLAS_GEAM dynload::cublasDgeam -#define CUBLAS_GEMV dynload::cublasDgemv -#define CUBLAS_GEMM dynload::cublasDgemm -#define CUBLAS_GETRF dynload::cublasDgetrfBatched -#define CUBLAS_GETRI dynload::cublasDgetriBatched +#define CUBLAS_GEAM dynload::cublasDgeam +#define CUBLAS_GEMV dynload::cublasDgemv +#define CUBLAS_GEMM dynload::cublasDgemm +#define CUBLAS_GETRF dynload::cublasDgetrfBatched +#define CUBLAS_GETRI dynload::cublasDgetriBatched #endif -const char* hl_cublas_get_error_string(cublasStatus_t status) { +const char *hl_cublas_get_error_string(cublasStatus_t status) { switch (status) { - case CUBLAS_STATUS_NOT_INITIALIZED: - return "[cublas status]: not initialized"; - case CUBLAS_STATUS_ALLOC_FAILED: - return "[cublas status]: allocate failed"; - case CUBLAS_STATUS_INVALID_VALUE: - return "[cublas status]: invalid value"; - case CUBLAS_STATUS_ARCH_MISMATCH: - return "[cublas status]: arch mismatch"; - case CUBLAS_STATUS_MAPPING_ERROR: - return "[cublas status]: mapping error"; - case CUBLAS_STATUS_EXECUTION_FAILED: - return "[cublas status]: execution failed"; - case CUBLAS_STATUS_INTERNAL_ERROR: - return "[cublas status]: internal error"; - case CUBLAS_STATUS_SUCCESS: - return "[cublas status]: success"; - default: - return "[cublas status]: unknown error"; + case CUBLAS_STATUS_NOT_INITIALIZED: + return "[cublas status]: not initialized"; + case CUBLAS_STATUS_ALLOC_FAILED: + return "[cublas status]: allocate failed"; + case CUBLAS_STATUS_INVALID_VALUE: + return "[cublas status]: invalid value"; + case CUBLAS_STATUS_ARCH_MISMATCH: + return "[cublas status]: arch mismatch"; + case CUBLAS_STATUS_MAPPING_ERROR: + return "[cublas status]: mapping error"; + case CUBLAS_STATUS_EXECUTION_FAILED: + return "[cublas status]: execution failed"; + case CUBLAS_STATUS_INTERNAL_ERROR: + return "[cublas status]: internal error"; + case CUBLAS_STATUS_SUCCESS: + return "[cublas status]: success"; + default: + return "[cublas status]: unknown error"; } } @@ -131,27 +123,21 @@ const char* hl_cublas_get_error_string(cublasStatus_t status) { * support << operator for more details error info. */ cublasStatus_t g_cublasStat; -#define CHECK_CUBLAS(cublas_func) \ - g_cublasStat = cublas_func; \ - CHECK_EQ(CUBLAS_STATUS_SUCCESS, g_cublasStat) \ - << "Cublas Error: " \ - << hl_cublas_get_error_string(g_cublasStat) \ - << " " +#define CHECK_CUBLAS(cublas_func) \ + g_cublasStat = cublas_func; \ + CHECK_EQ(CUBLAS_STATUS_SUCCESS, g_cublasStat) \ + << "Cublas Error: " << hl_cublas_get_error_string(g_cublasStat) << " " void hl_cublas_init(cublasHandle_t *cublas_handle, cudaStream_t stream) { CHECK_CUBLAS(dynload::cublasCreate(cublas_handle)) - << "[cublas init] Cublas create handle faild!"; + << "[cublas init] Cublas create handle faild!"; CHECK_CUBLAS(dynload::cublasSetStream(*cublas_handle, stream)) - << "[cublas init] Cublas set stream faild!"; + << "[cublas init] Cublas set stream faild!"; } -void hl_matrix_transpose(real *A_d, - real *C_d, - int dimM, - int dimN, - int lda, - int ldc) { +void hl_matrix_transpose( + real *A_d, real *C_d, int dimM, int dimN, int lda, int ldc) { real alpha = 1.0; real beta = 0.0; @@ -159,11 +145,18 @@ void hl_matrix_transpose(real *A_d, CHECK_NOTNULL(C_d); CHECK_CUBLAS(CUBLAS_GEAM(t_resource.handle, - CUBLAS_OP_T, CUBLAS_OP_N, - dimM, dimN, - &alpha, A_d, lda, - &beta, nullptr, dimM, - C_d, ldc)); + CUBLAS_OP_T, + CUBLAS_OP_N, + dimM, + dimN, + &alpha, + A_d, + lda, + &beta, + nullptr, + dimM, + C_d, + ldc)); CHECK_SYNC("hl_matrix_transpose failed"); } @@ -188,13 +181,13 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { small-sized matrices. There may be a better way to reconstruct the API for better performance. */ - CHECK_CUBLAS(CUBLAS_GETRF(t_resource.handle, - dimN, inout_d, lda, pivot_d, info_d, 1)); + CHECK_CUBLAS( + CUBLAS_GETRF(t_resource.handle, dimN, inout_d, lda, pivot_d, info_d, 1)); int info_h; hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { - LOG(FATAL) << "Factorization of matrix failed: matrix may be singular.\n"; + LOG(FATAL) << "Factorization of matrix failed: matrix may be singular.\n"; } /* Step 2: Compute the inverse of the matrix given its LU decomposition */ @@ -203,12 +196,18 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { hl_memcpy(out_d, out_h, sizeof(real *)); CHECK_CUBLAS(CUBLAS_GETRI(t_resource.handle, - dimN, (const real **)inout_d, lda, pivot_d, - out_d, ldc, info_d, 1)); + dimN, + (const real **)inout_d, + lda, + pivot_d, + out_d, + ldc, + info_d, + 1)); hl_memcpy(&info_h, info_d, sizeof(int)); if (info_h != 0) { - LOG(FATAL) << "Inversion of matrix failed: matrix may be singular.\n"; + LOG(FATAL) << "Inversion of matrix failed: matrix may be singular.\n"; } hl_free_mem_device(inout_d); @@ -218,12 +217,19 @@ void hl_matrix_inverse(real *A_d, real *C_d, int dimN, int lda, int ldc) { CHECK_SYNC("hl_matrix_inverse failed"); } -void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta, - int lda, int ldb, int ldc) { + int dimM, + int dimN, + int dimK, + real alpha, + real beta, + int lda, + int ldb, + int ldc) { CHECK_NOTNULL(A_d); CHECK_NOTNULL(B_d); CHECK_NOTNULL(C_d); @@ -231,8 +237,8 @@ void hl_matrix_mul(real *A_d, hl_trans_op_t transa, if (dimN == 1 && dimM != 1 && dimK != 1 && transb == HPPL_OP_N) { int m = (transa == HPPL_OP_N) ? dimM : dimK; int n = (transa == HPPL_OP_N) ? dimK : dimM; - hl_matrix_mul_vector(A_d, transa, B_d, C_d, m, n, - alpha, beta, lda, ldb, ldc); + hl_matrix_mul_vector( + A_d, transa, B_d, C_d, m, n, alpha, beta, lda, ldb, ldc); return; } @@ -240,8 +246,7 @@ void hl_matrix_mul(real *A_d, hl_trans_op_t transa, int m = (transb == HPPL_OP_N) ? dimK : dimN; int n = (transb == HPPL_OP_N) ? dimN : dimK; hl_trans_op_t trans = (transb == HPPL_OP_N) ? HPPL_OP_T : HPPL_OP_N; - hl_matrix_mul_vector(B_d, trans, A_d, C_d, m, n, - alpha, beta, ldb, 1, 1); + hl_matrix_mul_vector(B_d, trans, A_d, C_d, m, n, alpha, beta, ldb, 1, 1); return; } @@ -250,26 +255,47 @@ void hl_matrix_mul(real *A_d, hl_trans_op_t transa, stat = CUBLAS_GEMM(t_resource.handle, CUBLAS_OP_N, CUBLAS_OP_N, - dimN, dimM, dimK, - &alpha, B_d, ldb, - A_d, lda, - &beta, C_d, ldc); + dimN, + dimM, + dimK, + &alpha, + B_d, + ldb, + A_d, + lda, + &beta, + C_d, + ldc); } else if ((HPPL_OP_T == transa) && (HPPL_OP_N == transb)) { stat = CUBLAS_GEMM(t_resource.handle, CUBLAS_OP_N, CUBLAS_OP_T, - dimN, dimM, dimK, - &alpha, B_d, ldb, - A_d, lda, - &beta, C_d, ldc); + dimN, + dimM, + dimK, + &alpha, + B_d, + ldb, + A_d, + lda, + &beta, + C_d, + ldc); } else if ((HPPL_OP_N == transa) && (HPPL_OP_T == transb)) { stat = CUBLAS_GEMM(t_resource.handle, CUBLAS_OP_T, CUBLAS_OP_N, - dimN, dimM, dimK, - &alpha, B_d, ldb, - A_d, lda, - &beta, C_d, ldc); + dimN, + dimM, + dimK, + &alpha, + B_d, + ldb, + A_d, + lda, + &beta, + C_d, + ldc); } else { LOG(FATAL) << "parameter transa error!"; } @@ -277,24 +303,46 @@ void hl_matrix_mul(real *A_d, hl_trans_op_t transa, CHECK_SYNC("hl_matrix_mul failed"); } -void hl_matrix_mul(real *A_d, hl_trans_op_t transa, - real *B_d, hl_trans_op_t transb, +void hl_matrix_mul(real *A_d, + hl_trans_op_t transa, + real *B_d, + hl_trans_op_t transb, real *C_d, - int dimM, int dimN, int dimK, - real alpha, real beta) { + int dimM, + int dimN, + int dimK, + real alpha, + real beta) { int lda = (HPPL_OP_N == transa) ? dimK : dimM; int ldb = (HPPL_OP_N == transb) ? dimN : dimK; int ldc = dimN; - hl_matrix_mul(A_d, transa, B_d, transb, C_d, dimM, dimN, - dimK, alpha, beta, lda, ldb, ldc); + hl_matrix_mul(A_d, + transa, + B_d, + transb, + C_d, + dimM, + dimN, + dimK, + alpha, + beta, + lda, + ldb, + ldc); } -void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, - real *B_d, real *C_d, - int dimM, int dimN, - real alpha, real beta, - int lda, int incb, int incc) { +void hl_matrix_mul_vector(real *A_d, + hl_trans_op_t trans, + real *B_d, + real *C_d, + int dimM, + int dimN, + real alpha, + real beta, + int lda, + int incb, + int incc) { CHECK_NOTNULL(A_d); CHECK_NOTNULL(B_d); CHECK_NOTNULL(C_d); @@ -303,21 +351,29 @@ void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, if (HPPL_OP_N == trans) { stat = CUBLAS_GEMV(t_resource.handle, CUBLAS_OP_T, - dimN, dimM, + dimN, + dimM, &alpha, - A_d, lda, - B_d, incb, + A_d, + lda, + B_d, + incb, &beta, - C_d, incc); + C_d, + incc); } else if (HPPL_OP_T == trans) { stat = CUBLAS_GEMV(t_resource.handle, CUBLAS_OP_N, - dimN, dimM, + dimN, + dimM, &alpha, - A_d, lda, - B_d, incb, + A_d, + lda, + B_d, + incb, &beta, - C_d, incc); + C_d, + incc); } else { LOG(FATAL) << "parameter transa error!"; } @@ -326,10 +382,14 @@ void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, CHECK_SYNC("hl_matrix_mul_vector"); } -void hl_matrix_mul_vector(real *A_d, hl_trans_op_t trans, - real *B_d, real *C_d, - int dimM, int dimN, - real alpha, real beta) { - hl_matrix_mul_vector(A_d, trans, B_d, C_d, dimM, dimN, - alpha, beta, dimN, 1, 1); +void hl_matrix_mul_vector(real *A_d, + hl_trans_op_t trans, + real *B_d, + real *C_d, + int dimM, + int dimN, + real alpha, + real beta) { + hl_matrix_mul_vector( + A_d, trans, B_d, C_d, dimM, dimN, alpha, beta, dimN, 1, 1); } diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index 1829fe23ac..9d4ff08a78 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "hl_cuda_cudnn.h" @@ -22,9 +21,10 @@ limitations under the License. */ #include "paddle/utils/Logging.h" #include "paddle/utils/CommandLineParser.h" -P_DEFINE_int32(cudnn_conv_workspace_limit_in_mb, 4096, - "Specify cuDNN max workspace limit, in units MB, " - "4096MB=4GB by default."); +P_DEFINE_int32(cudnn_conv_workspace_limit_in_mb, + 4096, + "Specify cuDNN max workspace limit, in units MB, " + "4096MB=4GB by default."); namespace dynload { @@ -41,16 +41,15 @@ void* cudnn_dso_handle = nullptr; #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - using cudnn_func = decltype(__name(args...))(*)(Args...); \ - std::call_once(cudnn_dso_flag, GetCudnnDsoHandle, \ - &cudnn_dso_handle); \ - void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + using cudnn_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(cudnn_dso_flag, GetCudnnDsoHandle, &cudnn_dso_handle); \ + void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; /* struct DynLoad__##__name */ #else @@ -69,6 +68,7 @@ void* cudnn_dso_handle = nullptr; * include all needed cudnn functions in HPPL * different cudnn version has different interfaces **/ +// clang-format off #define CUDNN_DNN_ROUTINE_EACH(__macro) \ __macro(cudnnSetTensor4dDescriptor) \ __macro(cudnnSetTensor4dDescriptorEx) \ @@ -141,56 +141,53 @@ CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) #endif #undef CUDNN_DNN_ROUTINE_EACH - +// clang-format on } /* namespace dynload */ /** * Check build-in cudnn function using glog and it **does not** * support << operator for more details error info. */ -#define CHECK_CUDNN(cudnnFunc) \ - do { \ - cudnnStatus_t cudnnStat = cudnnFunc; \ - CHECK_EQ(CUDNN_STATUS_SUCCESS, cudnnStat) \ - << "Cudnn Error: " \ - << dynload::cudnnGetErrorString(cudnnStat); \ +#define CHECK_CUDNN(cudnnFunc) \ + do { \ + cudnnStatus_t cudnnStat = cudnnFunc; \ + CHECK_EQ(CUDNN_STATUS_SUCCESS, cudnnStat) \ + << "Cudnn Error: " << dynload::cudnnGetErrorString(cudnnStat); \ } while (0) bool g_is_libcudnn_init = false; int g_cudnn_lib_version = 0; -void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) { - CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(cudnn_desc)); +void hl_cudnn_desc_init(cudnnTensorDescriptor_t* cudnn_desc) { + CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(cudnn_desc)); } -void hl_cudnn_init(cudnnHandle_t *cudnn_handle, cudaStream_t stream) { - size_t cudnn_dso_ver = dynload::cudnnGetVersion(); - size_t cudnn_dso_major = cudnn_dso_ver / 1000; - size_t cudnn_cuh_major = CUDNN_VERSION / 1000; - - // Compare cudnn header version with that of cudnn.so. - CHECK((cudnn_cuh_major < 4 && cudnn_dso_major < 4) || - (cudnn_cuh_major == cudnn_dso_major)) - << "[cudnn init] libcudnn v" << cudnn_dso_major << - " with header v" << cudnn_cuh_major << " unmatched!\n" - << "PaddlePaddle Requirement: " - << "(header v[2-3] with libcudnn v[2-3]) Or " - << "(header v4 with libcudnn v4) Or " - << "(header v5 with libcudnn v5)."; - - CHECK(!(CUDNN_VERSION >= 5000 && CUDA_VERSION < 7050)) - << "cudnn v5 requires cuda version >= 7.5"; - - CHECK_CUDNN(dynload::cudnnCreate(cudnn_handle)); - CHECK_CUDNN(dynload::cudnnSetStream(*cudnn_handle, stream)); - - g_is_libcudnn_init = true; - g_cudnn_lib_version = cudnn_dso_ver; +void hl_cudnn_init(cudnnHandle_t* cudnn_handle, cudaStream_t stream) { + size_t cudnn_dso_ver = dynload::cudnnGetVersion(); + size_t cudnn_dso_major = cudnn_dso_ver / 1000; + size_t cudnn_cuh_major = CUDNN_VERSION / 1000; + + // Compare cudnn header version with that of cudnn.so. + CHECK((cudnn_cuh_major < 4 && cudnn_dso_major < 4) || + (cudnn_cuh_major == cudnn_dso_major)) + << "[cudnn init] libcudnn v" << cudnn_dso_major << " with header v" + << cudnn_cuh_major << " unmatched!\n" + << "PaddlePaddle Requirement: " + << "(header v[2-3] with libcudnn v[2-3]) Or " + << "(header v4 with libcudnn v4) Or " + << "(header v5 with libcudnn v5)."; + + CHECK(!(CUDNN_VERSION >= 5000 && CUDA_VERSION < 7050)) + << "cudnn v5 requires cuda version >= 7.5"; + + CHECK_CUDNN(dynload::cudnnCreate(cudnn_handle)); + CHECK_CUDNN(dynload::cudnnSetStream(*cudnn_handle, stream)); + + g_is_libcudnn_init = true; + g_cudnn_lib_version = cudnn_dso_ver; } -int hl_get_cudnn_lib_version() { - return g_cudnn_lib_version; -} +int hl_get_cudnn_lib_version() { return g_cudnn_lib_version; } void hl_conv_workspace(hl_tensor_descriptor input, hl_tensor_descriptor output, @@ -204,99 +201,91 @@ void hl_conv_workspace(hl_tensor_descriptor input, size_t* bwdFilterLimitBytes) { #if CUDNN_VERSION >= 4000 - CHECK_NOTNULL(input); - CHECK_NOTNULL(output); - CHECK_NOTNULL(filter); - CHECK_NOTNULL(conv); - - // Specify workspace limit directly - size_t memoryLimitBytes = - (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; - - // cudnn convolution forward configuration - cudnnTensorDescriptor_t fwd_src_desc = - GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t fwd_dest_desc = - GET_TENSOR_DESCRIPTOR(output); - cudnnFilterDescriptor_t fwd_filter_desc = - GET_FILTER_DESCRIPTOR(filter); - cudnnConvolutionDescriptor_t fwd_conv_desc = - GET_CONVOLUTION_DESCRIPTOR(conv); - - CHECK_CUDNN(dynload::cudnnGetConvolutionForwardAlgorithm( - t_resource.cudnn_handle, - fwd_src_desc, - fwd_filter_desc, - fwd_conv_desc, - fwd_dest_desc, - CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convFwdAlgo))); - - CHECK_CUDNN(dynload::cudnnGetConvolutionForwardWorkspaceSize( - t_resource.cudnn_handle, - fwd_src_desc, - fwd_filter_desc, - fwd_conv_desc, - fwd_dest_desc, - static_cast(*convFwdAlgo), - fwdLimitBytes)); - - // cudnn convolution backward data configuration - cudnnFilterDescriptor_t bwd_data_filter_desc = - GET_FILTER_DESCRIPTOR(filter); - cudnnTensorDescriptor_t bwd_data_diff_desc = - GET_TENSOR_DESCRIPTOR(output); - cudnnTensorDescriptor_t bwd_data_grad_desc = - GET_TENSOR_DESCRIPTOR(input); - cudnnConvolutionDescriptor_t bwd_data_conv_desc = - GET_CONVOLUTION_DESCRIPTOR(conv); - - CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataAlgorithm( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdDataAlgo))); - - CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataWorkspaceSize( - t_resource.cudnn_handle, - bwd_data_filter_desc, - bwd_data_diff_desc, - bwd_data_conv_desc, - bwd_data_grad_desc, - static_cast(*convBwdDataAlgo), - bwdDataLimitBytes)); - - // cudnn convolution backward filter configuration - cudnnTensorDescriptor_t bwd_filter_src_desc = - GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t bwd_filter_diff_desc = - GET_TENSOR_DESCRIPTOR(output); - cudnnConvolutionDescriptor_t bwd_filter_conv_desc = - GET_CONVOLUTION_DESCRIPTOR(conv); - cudnnFilterDescriptor_t bwd_filter_grad_desc = - GET_FILTER_DESCRIPTOR(filter); - - CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterAlgorithm( - t_resource.cudnn_handle, - bwd_filter_src_desc, - bwd_filter_diff_desc, - bwd_filter_conv_desc, - bwd_filter_grad_desc, - CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, - memoryLimitBytes, - reinterpret_cast(convBwdFilterAlgo))); - - CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterWorkspaceSize( - t_resource.cudnn_handle, bwd_filter_src_desc, - bwd_filter_diff_desc, bwd_filter_conv_desc, - bwd_filter_grad_desc, - static_cast(*convBwdFilterAlgo), - bwdFilterLimitBytes)); + CHECK_NOTNULL(input); + CHECK_NOTNULL(output); + CHECK_NOTNULL(filter); + CHECK_NOTNULL(conv); + + // Specify workspace limit directly + size_t memoryLimitBytes = + (1LL << 20) * FLAGS_cudnn_conv_workspace_limit_in_mb; + + // cudnn convolution forward configuration + cudnnTensorDescriptor_t fwd_src_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t fwd_dest_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnFilterDescriptor_t fwd_filter_desc = GET_FILTER_DESCRIPTOR(filter); + cudnnConvolutionDescriptor_t fwd_conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + + CHECK_CUDNN(dynload::cudnnGetConvolutionForwardAlgorithm( + t_resource.cudnn_handle, + fwd_src_desc, + fwd_filter_desc, + fwd_conv_desc, + fwd_dest_desc, + CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convFwdAlgo))); + + CHECK_CUDNN(dynload::cudnnGetConvolutionForwardWorkspaceSize( + t_resource.cudnn_handle, + fwd_src_desc, + fwd_filter_desc, + fwd_conv_desc, + fwd_dest_desc, + static_cast(*convFwdAlgo), + fwdLimitBytes)); + + // cudnn convolution backward data configuration + cudnnFilterDescriptor_t bwd_data_filter_desc = GET_FILTER_DESCRIPTOR(filter); + cudnnTensorDescriptor_t bwd_data_diff_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnTensorDescriptor_t bwd_data_grad_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnConvolutionDescriptor_t bwd_data_conv_desc = + GET_CONVOLUTION_DESCRIPTOR(conv); + + CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataAlgorithm( + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdDataAlgo))); + + CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardDataWorkspaceSize( + t_resource.cudnn_handle, + bwd_data_filter_desc, + bwd_data_diff_desc, + bwd_data_conv_desc, + bwd_data_grad_desc, + static_cast(*convBwdDataAlgo), + bwdDataLimitBytes)); + + // cudnn convolution backward filter configuration + cudnnTensorDescriptor_t bwd_filter_src_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t bwd_filter_diff_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnConvolutionDescriptor_t bwd_filter_conv_desc = + GET_CONVOLUTION_DESCRIPTOR(conv); + cudnnFilterDescriptor_t bwd_filter_grad_desc = GET_FILTER_DESCRIPTOR(filter); + + CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterAlgorithm( + t_resource.cudnn_handle, + bwd_filter_src_desc, + bwd_filter_diff_desc, + bwd_filter_conv_desc, + bwd_filter_grad_desc, + CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT, + memoryLimitBytes, + reinterpret_cast(convBwdFilterAlgo))); + + CHECK_CUDNN(dynload::cudnnGetConvolutionBackwardFilterWorkspaceSize( + t_resource.cudnn_handle, + bwd_filter_src_desc, + bwd_filter_diff_desc, + bwd_filter_conv_desc, + bwd_filter_grad_desc, + static_cast(*convBwdFilterAlgo), + bwdFilterLimitBytes)); #endif } @@ -306,55 +295,54 @@ void hl_create_tensor_descriptor(hl_tensor_descriptor* image_desc, int feature_maps, int height, int width) { - CHECK_NOTNULL(image_desc); + CHECK_NOTNULL(image_desc); - cudnn_tensor_descriptor hl_desc = - (cudnn_tensor_descriptor)malloc(sizeof(_cudnn_tensor_descriptor)); - CHECK_NOTNULL(hl_desc); + cudnn_tensor_descriptor hl_desc = + (cudnn_tensor_descriptor)malloc(sizeof(_cudnn_tensor_descriptor)); + CHECK_NOTNULL(hl_desc); #ifndef PADDLE_TYPE_DOUBLE - cudnnDataType_t data_type = CUDNN_DATA_FLOAT; + cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else - cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; + cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; #endif - CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(&hl_desc->desc)); - - CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor( - hl_desc->desc, - CUDNN_TENSOR_NCHW, - data_type, - batch_size, - feature_maps, - height, - width)); - - hl_desc->format = CUDNN_TENSOR_NCHW; - hl_desc->data_type = data_type; - hl_desc->batch_size = batch_size; - hl_desc->feature_maps = feature_maps; - hl_desc->height = height; - hl_desc->width = width; - - *image_desc = (hl_tensor_descriptor)hl_desc; + CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(&hl_desc->desc)); + + CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor(hl_desc->desc, + CUDNN_TENSOR_NCHW, + data_type, + batch_size, + feature_maps, + height, + width)); + + hl_desc->format = CUDNN_TENSOR_NCHW; + hl_desc->data_type = data_type; + hl_desc->batch_size = batch_size; + hl_desc->feature_maps = feature_maps; + hl_desc->height = height; + hl_desc->width = width; + + *image_desc = (hl_tensor_descriptor)hl_desc; } void hl_create_tensor_descriptor(hl_tensor_descriptor* image_desc) { - CHECK_NOTNULL(image_desc); + CHECK_NOTNULL(image_desc); - cudnn_tensor_descriptor hl_desc = - (cudnn_tensor_descriptor)malloc(sizeof(_cudnn_tensor_descriptor)); - CHECK_NOTNULL(hl_desc); + cudnn_tensor_descriptor hl_desc = + (cudnn_tensor_descriptor)malloc(sizeof(_cudnn_tensor_descriptor)); + CHECK_NOTNULL(hl_desc); #ifndef PADDLE_TYPE_DOUBLE - cudnnDataType_t data_type = CUDNN_DATA_FLOAT; + cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else - cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; + cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; #endif - CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(&hl_desc->desc)); + CHECK_CUDNN(dynload::cudnnCreateTensorDescriptor(&hl_desc->desc)); - hl_desc->data_type = data_type; + hl_desc->data_type = data_type; - *image_desc = (hl_tensor_descriptor)hl_desc; + *image_desc = (hl_tensor_descriptor)hl_desc; } void hl_tensor_reshape(hl_tensor_descriptor image_desc, @@ -362,19 +350,19 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int feature_maps, int height, int width) { - const int stride_w = 1; - const int stride_h = width * stride_w; - const int stride_c = height * stride_h; - const int stride_n = feature_maps * stride_c; - return hl_tensor_reshape(image_desc, - batch_size, - feature_maps, - height, - width, - stride_n, - stride_c, - stride_h, - stride_w); + const int stride_w = 1; + const int stride_h = width * stride_w; + const int stride_c = height * stride_h; + const int stride_n = feature_maps * stride_c; + return hl_tensor_reshape(image_desc, + batch_size, + feature_maps, + height, + width, + stride_n, + stride_c, + stride_h, + stride_w); } void hl_tensor_reshape(hl_tensor_descriptor image_desc, @@ -386,42 +374,41 @@ void hl_tensor_reshape(hl_tensor_descriptor image_desc, int cStride, int hStride, int wStride) { - CHECK_NOTNULL(image_desc); - - cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; - CHECK_NOTNULL(hl_desc->desc); - - CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptorEx(hl_desc->desc, - hl_desc->data_type, - batch_size, - feature_maps, - height, - width, - nStride, - cStride, - hStride, - wStride)); - - hl_desc->batch_size = batch_size; - hl_desc->feature_maps = feature_maps; - hl_desc->height = height; - hl_desc->width = width; + CHECK_NOTNULL(image_desc); + + cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; + CHECK_NOTNULL(hl_desc->desc); + + CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptorEx(hl_desc->desc, + hl_desc->data_type, + batch_size, + feature_maps, + height, + width, + nStride, + cStride, + hStride, + wStride)); + + hl_desc->batch_size = batch_size; + hl_desc->feature_maps = feature_maps; + hl_desc->height = height; + hl_desc->width = width; } void hl_destroy_tensor_descriptor(hl_tensor_descriptor image_desc) { - CHECK_NOTNULL(image_desc); + CHECK_NOTNULL(image_desc); - cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; - CHECK_NOTNULL(hl_desc->desc); + cudnn_tensor_descriptor hl_desc = (cudnn_tensor_descriptor)image_desc; + CHECK_NOTNULL(hl_desc->desc); - CHECK_CUDNN(dynload::cudnnDestroyTensorDescriptor(hl_desc->desc)); + CHECK_CUDNN(dynload::cudnnDestroyTensorDescriptor(hl_desc->desc)); - hl_desc->desc = NULL; + hl_desc->desc = NULL; - free(image_desc); + free(image_desc); } - void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, hl_pooling_mode_t mode, int height, @@ -430,63 +417,61 @@ void hl_create_pooling_descriptor(hl_pooling_descriptor* pooling_desc, int width_padding, int stride_height, int stride_width) { - cudnnPoolingMode_t cudnn_mode; - switch (mode) { - case HL_POOLING_MAX: - cudnn_mode = CUDNN_POOLING_MAX; - break; - case HL_POOLING_AVERAGE: - cudnn_mode = CUDNN_POOLING_AVERAGE_COUNT_INCLUDE_PADDING; - break; - case HL_POOLING_AVERAGE_EXCLUDE_PADDING: - cudnn_mode = CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING; - break; - default: - LOG(FATAL) << "parameter mode error"; - } - - CHECK_NOTNULL(pooling_desc); - - cudnn_pooling_descriptor hl_pooling_desc = - (cudnn_pooling_descriptor)malloc(sizeof(_cudnn_pooling_descriptor)); - CHECK_NOTNULL(hl_pooling_desc); - - CHECK_CUDNN(dynload::cudnnCreatePoolingDescriptor(&hl_pooling_desc->desc)); - - CHECK_CUDNN(dynload::cudnnSetPooling2dDescriptor( - hl_pooling_desc->desc, - cudnn_mode, + cudnnPoolingMode_t cudnn_mode; + switch (mode) { + case HL_POOLING_MAX: + cudnn_mode = CUDNN_POOLING_MAX; + break; + case HL_POOLING_AVERAGE: + cudnn_mode = CUDNN_POOLING_AVERAGE_COUNT_INCLUDE_PADDING; + break; + case HL_POOLING_AVERAGE_EXCLUDE_PADDING: + cudnn_mode = CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING; + break; + default: + LOG(FATAL) << "parameter mode error"; + } + + CHECK_NOTNULL(pooling_desc); + + cudnn_pooling_descriptor hl_pooling_desc = + (cudnn_pooling_descriptor)malloc(sizeof(_cudnn_pooling_descriptor)); + CHECK_NOTNULL(hl_pooling_desc); + + CHECK_CUDNN(dynload::cudnnCreatePoolingDescriptor(&hl_pooling_desc->desc)); + + CHECK_CUDNN(dynload::cudnnSetPooling2dDescriptor(hl_pooling_desc->desc, + cudnn_mode, #if CUDNN_VERSION >= 5000 - CUDNN_PROPAGATE_NAN, + CUDNN_PROPAGATE_NAN, #endif - height, - width, - height_padding, - width_padding, - stride_height, - stride_width)); - - hl_pooling_desc->mode = cudnn_mode; - hl_pooling_desc->window_height = height; - hl_pooling_desc->window_width = width; - hl_pooling_desc->stride_height = stride_height; - hl_pooling_desc->stride_width = stride_width; - - *pooling_desc = (hl_pooling_descriptor)hl_pooling_desc; + height, + width, + height_padding, + width_padding, + stride_height, + stride_width)); + + hl_pooling_desc->mode = cudnn_mode; + hl_pooling_desc->window_height = height; + hl_pooling_desc->window_width = width; + hl_pooling_desc->stride_height = stride_height; + hl_pooling_desc->stride_width = stride_width; + + *pooling_desc = (hl_pooling_descriptor)hl_pooling_desc; } void hl_destroy_pooling_descriptor(hl_pooling_descriptor pooling_desc) { - CHECK_NOTNULL(pooling_desc); + CHECK_NOTNULL(pooling_desc); - cudnn_pooling_descriptor hl_pooling = - (cudnn_pooling_descriptor)pooling_desc; + cudnn_pooling_descriptor hl_pooling = (cudnn_pooling_descriptor)pooling_desc; - CHECK_NOTNULL(hl_pooling->desc); - CHECK_CUDNN(dynload::cudnnDestroyPoolingDescriptor(hl_pooling->desc)); + CHECK_NOTNULL(hl_pooling->desc); + CHECK_CUDNN(dynload::cudnnDestroyPoolingDescriptor(hl_pooling->desc)); - hl_pooling->desc = NULL; + hl_pooling->desc = NULL; - free(pooling_desc); + free(pooling_desc); } void hl_pooling_forward(hl_tensor_descriptor input, @@ -494,31 +479,30 @@ void hl_pooling_forward(hl_tensor_descriptor input, hl_tensor_descriptor output, real* output_image, hl_pooling_descriptor pooling) { - cudnnPoolingDescriptor_t pooling_desc; - cudnnTensorDescriptor_t input_desc; - cudnnTensorDescriptor_t output_desc; - - CHECK_NOTNULL(input); - CHECK_NOTNULL(output); - CHECK_NOTNULL(pooling); - CHECK_NOTNULL(input_image); - CHECK_NOTNULL(output_image); - - real alpha = 1.0f; - real beta = 1.0f; - input_desc = ((cudnn_tensor_descriptor)input)->desc; - output_desc = ((cudnn_tensor_descriptor)output)->desc; - pooling_desc = ((cudnn_pooling_descriptor)pooling)->desc; - CHECK_CUDNN(dynload::cudnnPoolingForward( - t_resource.cudnn_handle, - pooling_desc, - &alpha, - input_desc, - input_image, - &beta, - output_desc, - output_image)); - CHECK_SYNC("hl_pooling_forward failed"); + cudnnPoolingDescriptor_t pooling_desc; + cudnnTensorDescriptor_t input_desc; + cudnnTensorDescriptor_t output_desc; + + CHECK_NOTNULL(input); + CHECK_NOTNULL(output); + CHECK_NOTNULL(pooling); + CHECK_NOTNULL(input_image); + CHECK_NOTNULL(output_image); + + real alpha = 1.0f; + real beta = 1.0f; + input_desc = ((cudnn_tensor_descriptor)input)->desc; + output_desc = ((cudnn_tensor_descriptor)output)->desc; + pooling_desc = ((cudnn_pooling_descriptor)pooling)->desc; + CHECK_CUDNN(dynload::cudnnPoolingForward(t_resource.cudnn_handle, + pooling_desc, + &alpha, + input_desc, + input_image, + &beta, + output_desc, + output_image)); + CHECK_SYNC("hl_pooling_forward failed"); } void hl_pooling_backward(hl_tensor_descriptor input, @@ -528,90 +512,86 @@ void hl_pooling_backward(hl_tensor_descriptor input, real* output_image, real* output_image_grad, hl_pooling_descriptor pooling) { - cudnnPoolingDescriptor_t pooling_desc; - cudnnTensorDescriptor_t input_desc; - cudnnTensorDescriptor_t output_desc; - - CHECK_NOTNULL(input); - CHECK_NOTNULL(output); - CHECK_NOTNULL(pooling); - CHECK_NOTNULL(input_image); - CHECK_NOTNULL(input_image_grad); - CHECK_NOTNULL(output_image); - CHECK_NOTNULL(output_image_grad); - - real alpha = 1.0f; - real beta = 1.0f; - input_desc = ((cudnn_tensor_descriptor)input)->desc; - output_desc = ((cudnn_tensor_descriptor)output)->desc; - pooling_desc = ((cudnn_pooling_descriptor)pooling)->desc; - CHECK_CUDNN(dynload::cudnnPoolingBackward( - t_resource.cudnn_handle, - pooling_desc, - &alpha, - output_desc, - output_image, - output_desc, - output_image_grad, - input_desc, - input_image, - &beta, - input_desc, - input_image_grad)); + cudnnPoolingDescriptor_t pooling_desc; + cudnnTensorDescriptor_t input_desc; + cudnnTensorDescriptor_t output_desc; + + CHECK_NOTNULL(input); + CHECK_NOTNULL(output); + CHECK_NOTNULL(pooling); + CHECK_NOTNULL(input_image); + CHECK_NOTNULL(input_image_grad); + CHECK_NOTNULL(output_image); + CHECK_NOTNULL(output_image_grad); + + real alpha = 1.0f; + real beta = 1.0f; + input_desc = ((cudnn_tensor_descriptor)input)->desc; + output_desc = ((cudnn_tensor_descriptor)output)->desc; + pooling_desc = ((cudnn_pooling_descriptor)pooling)->desc; + CHECK_CUDNN(dynload::cudnnPoolingBackward(t_resource.cudnn_handle, + pooling_desc, + &alpha, + output_desc, + output_image, + output_desc, + output_image_grad, + input_desc, + input_image, + &beta, + input_desc, + input_image_grad)); CHECK_SYNC("hl_pooling_backward failed"); } - void hl_create_filter_descriptor(hl_filter_descriptor* filter, int input_feature_maps, int output_feature_maps, int height, int width) { - CHECK_NOTNULL(filter); + CHECK_NOTNULL(filter); - cudnn_filter_descriptor hl_filter = - (cudnn_filter_descriptor)malloc(sizeof(_cudnn_filter_descriptor)); - CHECK_NOTNULL(hl_filter); + cudnn_filter_descriptor hl_filter = + (cudnn_filter_descriptor)malloc(sizeof(_cudnn_filter_descriptor)); + CHECK_NOTNULL(hl_filter); - CHECK_CUDNN(dynload::cudnnCreateFilterDescriptor(&hl_filter->desc)); + CHECK_CUDNN(dynload::cudnnCreateFilterDescriptor(&hl_filter->desc)); #ifndef PADDLE_TYPE_DOUBLE - cudnnDataType_t data_type = CUDNN_DATA_FLOAT; + cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else - cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; + cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; #endif - CHECK_CUDNN(dynload::cudnnSetFilter4dDescriptor( - hl_filter->desc, - data_type, + CHECK_CUDNN(dynload::cudnnSetFilter4dDescriptor(hl_filter->desc, + data_type, #if CUDNN_VERSION >= 5000 - CUDNN_TENSOR_NCHW, + CUDNN_TENSOR_NCHW, #endif - output_feature_maps, - input_feature_maps, - height, - width)); - - hl_filter->data_type = data_type; - hl_filter->output_feature_maps = output_feature_maps; - hl_filter->input_feature_maps = input_feature_maps; - hl_filter->filter_height = height; - hl_filter->filter_width = width; - - *filter = (hl_filter_descriptor)hl_filter; + output_feature_maps, + input_feature_maps, + height, + width)); + + hl_filter->data_type = data_type; + hl_filter->output_feature_maps = output_feature_maps; + hl_filter->input_feature_maps = input_feature_maps; + hl_filter->filter_height = height; + hl_filter->filter_width = width; + + *filter = (hl_filter_descriptor)hl_filter; } - void hl_destroy_filter_descriptor(hl_filter_descriptor filter) { - CHECK_NOTNULL(filter); + CHECK_NOTNULL(filter); - cudnn_filter_descriptor hl_filter = (cudnn_filter_descriptor)filter; - CHECK_NOTNULL(hl_filter->desc); + cudnn_filter_descriptor hl_filter = (cudnn_filter_descriptor)filter; + CHECK_NOTNULL(hl_filter->desc); - CHECK_CUDNN(dynload::cudnnDestroyFilterDescriptor(hl_filter->desc)); + CHECK_CUDNN(dynload::cudnnDestroyFilterDescriptor(hl_filter->desc)); - hl_filter->desc = NULL; + hl_filter->desc = NULL; - free(filter); + free(filter); } void hl_create_convolution_descriptor(hl_convolution_descriptor* conv, @@ -621,36 +601,35 @@ void hl_create_convolution_descriptor(hl_convolution_descriptor* conv, int padding_width, int stride_height, int stride_width) { - CHECK_NOTNULL(conv); - - cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor) - malloc(sizeof(_cudnn_convolution_descriptor)); - - CHECK_NOTNULL(hl_conv); - CHECK_CUDNN(dynload::cudnnCreateConvolutionDescriptor(&hl_conv->desc)); - - cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; - CHECK_CUDNN(dynload::cudnnSetConvolution2dDescriptor( - hl_conv->desc, - padding_height, - padding_width, - stride_height, - stride_width, - 1, - 1, - mode)); - - hl_conv->input_image = image; - hl_conv->filter = filter; - hl_conv->padding_height = padding_height; - hl_conv->padding_width = padding_width; - hl_conv->stride_height = stride_height; - hl_conv->stride_width = stride_width; - hl_conv->upscalex = 1; - hl_conv->upscaley = 1; - hl_conv->mode = mode; - - *conv = (hl_convolution_descriptor)hl_conv; + CHECK_NOTNULL(conv); + + cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)malloc( + sizeof(_cudnn_convolution_descriptor)); + + CHECK_NOTNULL(hl_conv); + CHECK_CUDNN(dynload::cudnnCreateConvolutionDescriptor(&hl_conv->desc)); + + cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; + CHECK_CUDNN(dynload::cudnnSetConvolution2dDescriptor(hl_conv->desc, + padding_height, + padding_width, + stride_height, + stride_width, + 1, + 1, + mode)); + + hl_conv->input_image = image; + hl_conv->filter = filter; + hl_conv->padding_height = padding_height; + hl_conv->padding_width = padding_width; + hl_conv->stride_height = stride_height; + hl_conv->stride_width = stride_width; + hl_conv->upscalex = 1; + hl_conv->upscaley = 1; + hl_conv->mode = mode; + + *conv = (hl_convolution_descriptor)hl_conv; } void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, @@ -660,44 +639,43 @@ void hl_reset_convolution_descriptor(hl_convolution_descriptor conv, int padding_width, int stride_height, int stride_width) { - CHECK_NOTNULL(conv); - CHECK_NOTNULL(image); - CHECK_NOTNULL(filter); - - cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); - cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; - CHECK_CUDNN(dynload::cudnnSetConvolution2dDescriptor( - conv_desc, - padding_height, - padding_width, - stride_height, - stride_width, - 1, - 1, - mode)); - - cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; - hl_conv->input_image = image; - hl_conv->filter = filter; - hl_conv->padding_height = padding_height; - hl_conv->padding_width = padding_width; - hl_conv->stride_height = stride_height; - hl_conv->stride_width = stride_width; - hl_conv->upscalex = 1; - hl_conv->upscaley = 1; - hl_conv->mode = mode; + CHECK_NOTNULL(conv); + CHECK_NOTNULL(image); + CHECK_NOTNULL(filter); + + cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + cudnnConvolutionMode_t mode = CUDNN_CROSS_CORRELATION; + CHECK_CUDNN(dynload::cudnnSetConvolution2dDescriptor(conv_desc, + padding_height, + padding_width, + stride_height, + stride_width, + 1, + 1, + mode)); + + cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; + hl_conv->input_image = image; + hl_conv->filter = filter; + hl_conv->padding_height = padding_height; + hl_conv->padding_width = padding_width; + hl_conv->stride_height = stride_height; + hl_conv->stride_width = stride_width; + hl_conv->upscalex = 1; + hl_conv->upscaley = 1; + hl_conv->mode = mode; } void hl_destroy_convolution_descriptor(hl_convolution_descriptor conv) { - CHECK_NOTNULL(conv); + CHECK_NOTNULL(conv); - cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; - CHECK_NOTNULL(hl_conv->desc); + cudnn_convolution_descriptor hl_conv = (cudnn_convolution_descriptor)conv; + CHECK_NOTNULL(hl_conv->desc); - CHECK_CUDNN(dynload::cudnnDestroyConvolutionDescriptor(hl_conv->desc)); - hl_conv->desc = NULL; + CHECK_CUDNN(dynload::cudnnDestroyConvolutionDescriptor(hl_conv->desc)); + hl_conv->desc = NULL; - free(conv); + free(conv); } void hl_convolution_forward(hl_tensor_descriptor input, @@ -710,33 +688,33 @@ void hl_convolution_forward(hl_tensor_descriptor input, void* gpuWorkSpace, size_t sizeInBytes, int convFwdAlgo) { - CHECK_NOTNULL(input); - CHECK_NOTNULL(output); - CHECK_NOTNULL(filter); - CHECK_NOTNULL(conv); - CHECK_NOTNULL(input_data); - CHECK_NOTNULL(output_data); - CHECK_NOTNULL(filter_data); - cudnnTensorDescriptor_t src_desc = GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t dest_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnFilterDescriptor_t filter_desc = GET_FILTER_DESCRIPTOR(filter); - cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); - real alpha = 1.0f; - real beta = 1.0f; - CHECK_CUDNN(dynload::cudnnConvolutionForward( - t_resource.cudnn_handle, - &alpha, - src_desc, - input_data, - filter_desc, - filter_data, - conv_desc, - static_cast(convFwdAlgo), - gpuWorkSpace, - sizeInBytes, - &beta, - dest_desc, - output_data)); + CHECK_NOTNULL(input); + CHECK_NOTNULL(output); + CHECK_NOTNULL(filter); + CHECK_NOTNULL(conv); + CHECK_NOTNULL(input_data); + CHECK_NOTNULL(output_data); + CHECK_NOTNULL(filter_data); + cudnnTensorDescriptor_t src_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t dest_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnFilterDescriptor_t filter_desc = GET_FILTER_DESCRIPTOR(filter); + cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + real alpha = 1.0f; + real beta = 1.0f; + CHECK_CUDNN(dynload::cudnnConvolutionForward( + t_resource.cudnn_handle, + &alpha, + src_desc, + input_data, + filter_desc, + filter_data, + conv_desc, + static_cast(convFwdAlgo), + gpuWorkSpace, + sizeInBytes, + &beta, + dest_desc, + output_data)); CHECK_SYNC("hl_convolution_forward failed"); } @@ -744,27 +722,26 @@ void hl_convolution_forward_add_bias(hl_tensor_descriptor bias, real* bias_data, hl_tensor_descriptor output, real* output_data) { - CHECK_NOTNULL(bias); - CHECK_NOTNULL(output); - CHECK_NOTNULL(bias_data); - CHECK_NOTNULL(output_data); - - cudnnTensorDescriptor_t output_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnTensorDescriptor_t bias_desc = GET_TENSOR_DESCRIPTOR(bias); - real alpha = 1.0f; - real beta = 1.0f; - - CHECK_CUDNN(dynload::cudnnAddTensor( - t_resource.cudnn_handle, + CHECK_NOTNULL(bias); + CHECK_NOTNULL(output); + CHECK_NOTNULL(bias_data); + CHECK_NOTNULL(output_data); + + cudnnTensorDescriptor_t output_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnTensorDescriptor_t bias_desc = GET_TENSOR_DESCRIPTOR(bias); + real alpha = 1.0f; + real beta = 1.0f; + + CHECK_CUDNN(dynload::cudnnAddTensor(t_resource.cudnn_handle, #if CUDNN_VERSION < 4000 - CUDNN_ADD_SAME_C, + CUDNN_ADD_SAME_C, #endif - &alpha, - bias_desc, - bias_data, - &beta, - output_desc, - output_data)); + &alpha, + bias_desc, + bias_data, + &beta, + output_desc, + output_data)); CHECK_SYNC("hl_convolution_forward_add_bias failed"); } @@ -772,23 +749,22 @@ void hl_convolution_backward_bias(hl_tensor_descriptor bias, real* bias_grad_data, hl_tensor_descriptor output, real* output_grad_data) { - CHECK_NOTNULL(bias); - CHECK_NOTNULL(output); - CHECK_NOTNULL(bias_grad_data); - CHECK_NOTNULL(output_grad_data); - - real alpha = 1.0f; - real beta = 1.0f; - cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnTensorDescriptor_t bias_desc = GET_TENSOR_DESCRIPTOR(bias); - CHECK_CUDNN(dynload::cudnnConvolutionBackwardBias( - t_resource.cudnn_handle, - &alpha, - diff_desc, - output_grad_data, - &beta, - bias_desc, - bias_grad_data)); + CHECK_NOTNULL(bias); + CHECK_NOTNULL(output); + CHECK_NOTNULL(bias_grad_data); + CHECK_NOTNULL(output_grad_data); + + real alpha = 1.0f; + real beta = 1.0f; + cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnTensorDescriptor_t bias_desc = GET_TENSOR_DESCRIPTOR(bias); + CHECK_CUDNN(dynload::cudnnConvolutionBackwardBias(t_resource.cudnn_handle, + &alpha, + diff_desc, + output_grad_data, + &beta, + bias_desc, + bias_grad_data)); CHECK_SYNC("hl_convolution_backward_bias failed"); } @@ -802,37 +778,37 @@ void hl_convolution_backward_filter(hl_tensor_descriptor input, void* gpuWorkSpace, size_t sizeInBytes, int convBwdFilterAlgo) { - CHECK_NOTNULL(input); - CHECK_NOTNULL(output); - CHECK_NOTNULL(filter); - CHECK_NOTNULL(conv); - CHECK_NOTNULL(input_data); - CHECK_NOTNULL(output_grad_data); - CHECK_NOTNULL(filter_grad_data); - - real alpha = 1.0f; - real beta = 1.0f; - cudnnTensorDescriptor_t src_desc = GET_TENSOR_DESCRIPTOR(input); - cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); - cudnnFilterDescriptor_t grad_desc = GET_FILTER_DESCRIPTOR(filter); - - CHECK_CUDNN(dynload::cudnnConvolutionBackwardFilter( - t_resource.cudnn_handle, - &alpha, - src_desc, - input_data, - diff_desc, - output_grad_data, - conv_desc, + CHECK_NOTNULL(input); + CHECK_NOTNULL(output); + CHECK_NOTNULL(filter); + CHECK_NOTNULL(conv); + CHECK_NOTNULL(input_data); + CHECK_NOTNULL(output_grad_data); + CHECK_NOTNULL(filter_grad_data); + + real alpha = 1.0f; + real beta = 1.0f; + cudnnTensorDescriptor_t src_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + cudnnFilterDescriptor_t grad_desc = GET_FILTER_DESCRIPTOR(filter); + + CHECK_CUDNN(dynload::cudnnConvolutionBackwardFilter( + t_resource.cudnn_handle, + &alpha, + src_desc, + input_data, + diff_desc, + output_grad_data, + conv_desc, #if CUDNN_VERSION >= 4000 - static_cast(convBwdFilterAlgo), - gpuWorkSpace, - sizeInBytes, + static_cast(convBwdFilterAlgo), + gpuWorkSpace, + sizeInBytes, #endif - &beta, - grad_desc, - filter_grad_data)); + &beta, + grad_desc, + filter_grad_data)); CHECK_SYNC("hl_convolution_backward_filter failed"); } @@ -846,119 +822,111 @@ void hl_convolution_backward_data(hl_tensor_descriptor input, void* gpuWorkSpace, size_t sizeInBytes, int convBwdDataAlgo) { - real alpha = 1.0f; - real beta = 1.0f; - cudnnFilterDescriptor_t filter_desc = GET_FILTER_DESCRIPTOR(filter); - cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); - cudnnTensorDescriptor_t grad_desc = GET_TENSOR_DESCRIPTOR(input); - cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); - - CHECK_CUDNN(dynload::cudnnConvolutionBackwardData( - t_resource.cudnn_handle, - &alpha, - filter_desc, - filter_data, - diff_desc, - output_grad_data, - conv_desc, + real alpha = 1.0f; + real beta = 1.0f; + cudnnFilterDescriptor_t filter_desc = GET_FILTER_DESCRIPTOR(filter); + cudnnTensorDescriptor_t diff_desc = GET_TENSOR_DESCRIPTOR(output); + cudnnTensorDescriptor_t grad_desc = GET_TENSOR_DESCRIPTOR(input); + cudnnConvolutionDescriptor_t conv_desc = GET_CONVOLUTION_DESCRIPTOR(conv); + + CHECK_CUDNN(dynload::cudnnConvolutionBackwardData( + t_resource.cudnn_handle, + &alpha, + filter_desc, + filter_data, + diff_desc, + output_grad_data, + conv_desc, #if CUDNN_VERSION >= 4000 - static_cast(convBwdDataAlgo), - gpuWorkSpace, - sizeInBytes, + static_cast(convBwdDataAlgo), + gpuWorkSpace, + sizeInBytes, #endif - &beta, - grad_desc, - input_data_grad)); + &beta, + grad_desc, + input_data_grad)); CHECK_SYNC("hl_convolution_backward_data failed"); } - -void hl_softmax_forward(real *input, - real *output, - int height, - int width) { +void hl_softmax_forward(real* input, real* output, int height, int width) { #ifndef PADDLE_TYPE_DOUBLE - cudnnDataType_t data_type = CUDNN_DATA_FLOAT; + cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else - cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; + cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; #endif - CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor( - t_resource.cudnn_desc, - CUDNN_TENSOR_NCHW, - data_type, - height, - width, - 1, - 1)); - - real alpha = 1.0f; - real beta = 0.0f; - CHECK_CUDNN(dynload::cudnnSoftmaxForward( - t_resource.cudnn_handle, - CUDNN_SOFTMAX_ACCURATE, - CUDNN_SOFTMAX_MODE_CHANNEL, - &alpha, - t_resource.cudnn_desc, - input, - &beta, - t_resource.cudnn_desc, - output)); + CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor(t_resource.cudnn_desc, + CUDNN_TENSOR_NCHW, + data_type, + height, + width, + 1, + 1)); + + real alpha = 1.0f; + real beta = 0.0f; + CHECK_CUDNN(dynload::cudnnSoftmaxForward(t_resource.cudnn_handle, + CUDNN_SOFTMAX_ACCURATE, + CUDNN_SOFTMAX_MODE_CHANNEL, + &alpha, + t_resource.cudnn_desc, + input, + &beta, + t_resource.cudnn_desc, + output)); CHECK_SYNC("hl_softmax_forward failed"); } -void hl_softmax_backward(real *output_value, - real *output_grad, +void hl_softmax_backward(real* output_value, + real* output_grad, int height, int width) { #ifndef PADDLE_TYPE_DOUBLE - cudnnDataType_t data_type = CUDNN_DATA_FLOAT; + cudnnDataType_t data_type = CUDNN_DATA_FLOAT; #else - cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; + cudnnDataType_t data_type = CUDNN_DATA_DOUBLE; #endif - CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor( - t_resource.cudnn_desc, - CUDNN_TENSOR_NCHW, - data_type, - height, - width, - 1, - 1)); - - real alpha = 1.0f; - real beta = 0.0f; - CHECK_CUDNN(dynload::cudnnSoftmaxBackward( - t_resource.cudnn_handle, - CUDNN_SOFTMAX_ACCURATE, - CUDNN_SOFTMAX_MODE_CHANNEL, - &alpha, - t_resource.cudnn_desc, - output_value, - t_resource.cudnn_desc, - output_grad, - &beta, - t_resource.cudnn_desc, - output_grad)); + CHECK_CUDNN(dynload::cudnnSetTensor4dDescriptor(t_resource.cudnn_desc, + CUDNN_TENSOR_NCHW, + data_type, + height, + width, + 1, + 1)); + + real alpha = 1.0f; + real beta = 0.0f; + CHECK_CUDNN(dynload::cudnnSoftmaxBackward(t_resource.cudnn_handle, + CUDNN_SOFTMAX_ACCURATE, + CUDNN_SOFTMAX_MODE_CHANNEL, + &alpha, + t_resource.cudnn_desc, + output_value, + t_resource.cudnn_desc, + output_grad, + &beta, + t_resource.cudnn_desc, + output_grad)); CHECK_SYNC("hl_softmax_backward failed"); } void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outputDesc, - real *output, + real* output, hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, + real* scale, + real* bias, double factor, - real *runningMean, - real *runningInvVar, + real* runningMean, + real* runningInvVar, double epsilon, - real *savedMean, - real *savedVar) { + real* savedMean, + real* savedVar) { #if CUDNN_VERSION >= 4007 if ((NULL != runningMean && NULL == runningInvVar) || (NULL == runningMean && NULL != runningInvVar)) { LOG(FATAL) << "runningMean and runningInvVar can be NULL " - << "but only at the same time."; + << "but only at the same time."; } if ((NULL != savedMean && NULL == savedVar) || (NULL == savedMean && NULL != savedVar)) { @@ -972,10 +940,24 @@ void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, real alpha = 1.0f; real beta = 1.0f; cudnnBatchNormMode_t mode = CUDNN_BATCHNORM_SPATIAL; - CHECK_CUDNN(dynload::cudnnBatchNormalizationForwardTraining( - t_resource.cudnn_handle, mode, &alpha, &beta, xDesc, - input, yDesc, output, bnDesc, scale, bias, factor, - runningMean, runningInvVar, epsilon, savedMean, savedVar)); + CHECK_CUDNN( + dynload::cudnnBatchNormalizationForwardTraining(t_resource.cudnn_handle, + mode, + &alpha, + &beta, + xDesc, + input, + yDesc, + output, + bnDesc, + scale, + bias, + factor, + runningMean, + runningInvVar, + epsilon, + savedMean, + savedVar)); CHECK_SYNC("hl_batch_norm_forward_training failed"); #else @@ -985,15 +967,15 @@ void hl_batch_norm_forward_training(hl_tensor_descriptor inputDesc, } void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, - real *input, - hl_tensor_descriptor outputDesc, - real *output, - hl_tensor_descriptor bnParamDesc, - real *scale, - real *bias, - real *estimatedMean, - real *estimatedInvVar, - double epsilon) { + real* input, + hl_tensor_descriptor outputDesc, + real* output, + hl_tensor_descriptor bnParamDesc, + real* scale, + real* bias, + real* estimatedMean, + real* estimatedInvVar, + double epsilon) { #if CUDNN_VERSION >= 4007 cudnnTensorDescriptor_t xDesc = GET_TENSOR_DESCRIPTOR(inputDesc); cudnnTensorDescriptor_t yDesc = GET_TENSOR_DESCRIPTOR(outputDesc); @@ -1001,10 +983,21 @@ void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, real alpha = 1.0f; real beta = 1.0f; cudnnBatchNormMode_t mode = CUDNN_BATCHNORM_SPATIAL; - CHECK_CUDNN(dynload::cudnnBatchNormalizationForwardInference( - t_resource.cudnn_handle, mode, &alpha, &beta, xDesc, - input, yDesc, output, bnDesc, scale, bias, - estimatedMean, estimatedInvVar, epsilon)); + CHECK_CUDNN( + dynload::cudnnBatchNormalizationForwardInference(t_resource.cudnn_handle, + mode, + &alpha, + &beta, + xDesc, + input, + yDesc, + output, + bnDesc, + scale, + bias, + estimatedMean, + estimatedInvVar, + epsilon)); CHECK_SYNC("hl_batch_norm_forward_inference failed"); #else @@ -1014,18 +1007,18 @@ void hl_batch_norm_forward_inference(hl_tensor_descriptor inputDesc, } void hl_batch_norm_backward(hl_tensor_descriptor inputDesc, - real *input, + real* input, hl_tensor_descriptor outGradDesc, - real *outGrad, + real* outGrad, hl_tensor_descriptor inGradDesc, - real *inGrad, + real* inGrad, hl_tensor_descriptor dBnParamDesc, - real *scale, - real *scaleGrad, - real *biasGrad, + real* scale, + real* scaleGrad, + real* biasGrad, double epsilon, - real *savedMean, - real *savedInvVar) { + real* savedMean, + real* savedInvVar) { #if CUDNN_VERSION >= 4007 if ((NULL != savedMean && NULL == savedInvVar) || (NULL == savedMean && NULL != savedInvVar)) { @@ -1040,12 +1033,25 @@ void hl_batch_norm_backward(hl_tensor_descriptor inputDesc, real alpha = 1.0f; real beta = 1.0f; cudnnBatchNormMode_t mode = CUDNN_BATCHNORM_SPATIAL; - CHECK_CUDNN(dynload::cudnnBatchNormalizationBackward( - t_resource.cudnn_handle, mode, &alpha, &beta, - &alpha, &beta, - xDesc, input, dyDesc, outGrad, dxDesc, inGrad, - bnDesc, scale, scaleGrad, biasGrad, epsilon, - savedMean, savedInvVar)); + CHECK_CUDNN(dynload::cudnnBatchNormalizationBackward(t_resource.cudnn_handle, + mode, + &alpha, + &beta, + &alpha, + &beta, + xDesc, + input, + dyDesc, + outGrad, + dxDesc, + inGrad, + bnDesc, + scale, + scaleGrad, + biasGrad, + epsilon, + savedMean, + savedInvVar)); CHECK_SYNC("hl_batch_norm_backward failed"); #else diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index ca19f210c5..85d4860b5b 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include @@ -27,7 +26,7 @@ limitations under the License. */ namespace dynload { std::once_flag curand_dso_flag; -void* curand_dso_handle = nullptr; +void *curand_dso_handle = nullptr; /** * The following macro definition can generate structs @@ -37,34 +36,31 @@ void* curand_dso_handle = nullptr; * note: default dynamic linked libs */ #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - curandStatus_t operator()(Args... args) { \ - typedef curandStatus_t (*curandFunc)(Args...); \ - std::call_once(curand_dso_flag, GetCurandDsoHandle, \ - &curand_dso_handle); \ - void* p_##__name = dlsym(curand_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ - } __name; /* struct DynLoad__##__name */ +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + typedef curandStatus_t (*curandFunc)(Args...); \ + std::call_once(curand_dso_flag, GetCurandDsoHandle, &curand_dso_handle); \ + void *p_##__name = dlsym(curand_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ #else -#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - curandStatus_t operator()(Args... args) { \ - return __name(args...); \ - } \ - } __name; /* struct DynLoad__##__name */ +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + return __name(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ #endif /* include all needed curand functions in HPPL */ -#define CURAND_RAND_ROUTINE_EACH(__macro) \ - __macro(curandCreateGenerator) \ - __macro(curandSetStream) \ - __macro(curandSetPseudoRandomGeneratorSeed)\ - __macro(curandGenerateUniform) \ - __macro(curandGenerateUniformDouble) +#define CURAND_RAND_ROUTINE_EACH(__macro) \ + __macro(curandCreateGenerator) __macro(curandSetStream) \ + __macro(curandSetPseudoRandomGeneratorSeed) \ + __macro(curandGenerateUniform) __macro(curandGenerateUniformDouble) CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) @@ -72,7 +68,7 @@ CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) #undef DYNAMIC_LOAD_CURAND_WRAP std::once_flag cudart_dso_flag; -void* cudart_dso_handle = nullptr; +void *cudart_dso_handle = nullptr; /** * The following macro definition can generate structs @@ -82,109 +78,96 @@ void* cudart_dso_handle = nullptr; * note: default dynamic linked libs */ #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUDART_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - using cudart_func = decltype(__name(args...))(*)(Args...); \ - std::call_once(cudart_dso_flag, GetCudartDsoHandle, \ - &cudart_dso_handle); \ - void* p_##__name = dlsym(cudart_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ - } __name; /* struct DynLoad__##__name */ +#define DYNAMIC_LOAD_CUDART_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + using cudart_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(cudart_dso_flag, GetCudartDsoHandle, &cudart_dso_handle); \ + void *p_##__name = dlsym(cudart_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ #else -#define DYNAMIC_LOAD_CUDART_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - return __name(args...); \ - } \ - } __name; /* struct DynLoad__##__name */ +#define DYNAMIC_LOAD_CUDART_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + return __name(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ #endif /* include all needed cuda functions in HPPL */ -#define CUDA_ROUTINE_EACH(__macro) \ - __macro(cudaMalloc) \ - __macro(cudaHostAlloc) \ - __macro(cudaFree) \ - __macro(cudaFreeHost) \ - __macro(cudaMemcpy) \ - __macro(cudaMemset) \ - __macro(cudaMemcpyAsync) \ - __macro(cudaSetDevice) \ - __macro(cudaGetDevice) \ - __macro(cudaGetDeviceCount) \ - __macro(cudaGetDeviceProperties) \ - __macro(cudaDeviceSynchronize) \ - __macro(cudaDeviceCanAccessPeer) \ - __macro(cudaDeviceEnablePeerAccess) \ - __macro(cudaStreamCreate) \ - __macro(cudaStreamDestroy) \ - __macro(cudaStreamSynchronize) \ - __macro(cudaStreamWaitEvent) \ - __macro(cudaEventCreate) \ - __macro(cudaEventRecord) \ - __macro(cudaEventQuery) \ - __macro(cudaEventDestroy) \ - __macro(cudaEventSynchronize) \ - __macro(cudaEventElapsedTime) \ - __macro(cudaSetDeviceFlags) \ - __macro(cudaGetLastError) \ - __macro(cudaFuncSetCacheConfig) \ - __macro(cudaRuntimeGetVersion) \ - __macro(cudaGetErrorString) +#define CUDA_ROUTINE_EACH(__macro) \ + __macro(cudaMalloc) __macro(cudaHostAlloc) __macro(cudaFree) \ + __macro(cudaFreeHost) __macro(cudaMemcpy) __macro(cudaMemset) __macro( \ + cudaMemcpyAsync) __macro(cudaSetDevice) __macro(cudaGetDevice) \ + __macro(cudaGetDeviceCount) __macro(cudaGetDeviceProperties) \ + __macro(cudaDeviceSynchronize) __macro(cudaDeviceCanAccessPeer) \ + __macro(cudaDeviceEnablePeerAccess) \ + __macro(cudaStreamCreate) __macro(cudaStreamDestroy) \ + __macro(cudaStreamSynchronize) __macro( \ + cudaStreamWaitEvent) __macro(cudaEventCreate) \ + __macro(cudaEventRecord) __macro(cudaEventQuery) \ + __macro(cudaEventDestroy) __macro( \ + cudaEventSynchronize) \ + __macro(cudaEventElapsedTime) __macro( \ + cudaSetDeviceFlags) \ + __macro(cudaGetLastError) __macro( \ + cudaFuncSetCacheConfig) \ + __macro(cudaRuntimeGetVersion) \ + __macro(cudaGetErrorString) CUDA_ROUTINE_EACH(DYNAMIC_LOAD_CUDART_WRAP) #undef CUDA_ROUNTINE_EACH #undef DYNAMIC_LOAD_CUDART_WRAP -} /* namespace dynload */ +} /* namespace dynload */ /** * @brief global resource. */ -int g_system_device_num = 0; /* system device number */ -int device_num = 0; /* use device number */ -hl_device_prop *g_device; /* device info table */ -__thread thread_device_resources *t_device; /* device resources table */ +int g_system_device_num = 0; /* system device number */ +int device_num = 0; /* use device number */ +hl_device_prop *g_device; /* device info table */ +__thread thread_device_resources *t_device; /* device resources table */ int g_cuda_lib_version = 0; /* number of global stream */ -#define NUMBER_OF_GLOBAL_STREAM (HPPL_THREAD_STREAM_1) +#define NUMBER_OF_GLOBAL_STREAM (HPPL_THREAD_STREAM_1) /* number of thread stream */ -#define NUMBER_OF_THREAD_STREAM (HPPL_STREAM_END - HPPL_THREAD_STREAM_1) +#define NUMBER_OF_THREAD_STREAM (HPPL_STREAM_END - HPPL_THREAD_STREAM_1) /* sizeof of device memory */ -#define HPPL_GPU_MEMORY_SIZE (256*4) +#define HPPL_GPU_MEMORY_SIZE (256 * 4) /** * Check build-in cuda function using glog and it **does not** * support << operator for more details error info. */ -#define CHECK_CUDA(cudaFunc) \ - do { \ - cudaError_t cudaStat = cudaFunc; \ - CHECK_EQ(cudaSuccess, cudaStat) << "Cuda Error: " \ - << dynload::cudaGetErrorString(cudaStat); \ +#define CHECK_CUDA(cudaFunc) \ + do { \ + cudaError_t cudaStat = cudaFunc; \ + CHECK_EQ(cudaSuccess, cudaStat) << "Cuda Error: " \ + << dynload::cudaGetErrorString(cudaStat); \ } while (0) /** * @brief thread resource. */ -__thread _hl_thread_resource t_resource = { - {0}, /* stream */ - 0, /* handle */ - 0, /* gen */ - 0, /* cudnn_handle */ - 0, /* cudnn_desc */ - NULL, /* gen_mutex */ - NULL, /* gpu_mem */ - NULL, /* cpu_mem */ - 0, /* event */ - -1, /* device */ - 0, /* major */ - false}; /* is_init */ +__thread _hl_thread_resource t_resource = {{0}, /* stream */ + 0, /* handle */ + 0, /* gen */ + 0, /* cudnn_handle */ + 0, /* cudnn_desc */ + NULL, /* gen_mutex */ + NULL, /* gpu_mem */ + NULL, /* cpu_mem */ + 0, /* event */ + -1, /* device */ + 0, /* major */ + false}; /* is_init */ __thread cudaStream_t default_stream = 0; __thread bool g_sync_flag = true; @@ -198,9 +181,9 @@ inline pid_t gettid() { uint64_t tid; pthread_threadid_np(NULL, &tid); #else - #ifndef __NR_gettid - #define __NR_gettid 224 - #endif +#ifndef __NR_gettid +#define __NR_gettid 224 +#endif pid_t tid = syscall(__NR_gettid); #endif CHECK_NE((int)tid, -1); @@ -208,8 +191,7 @@ inline pid_t gettid() { } void hl_init(int device) { - CHECK(hl_start_flag) - << "[Init failed] hl_start() did not succeed."; + CHECK(hl_start_flag) << "[Init failed] hl_start() did not succeed."; /* thread has been initialized */ if (true == t_resource.is_init) { @@ -220,16 +202,16 @@ void hl_init(int device) { /* create thread devcie resources */ char *tmp; thread_device_resources device_res; - tmp = (char *)malloc(g_system_device_num*sizeof(thread_device_resources*) + - device_num*sizeof(_thread_device_resources)); + tmp = (char *)malloc(g_system_device_num * sizeof(thread_device_resources *) + + device_num * sizeof(_thread_device_resources)); CHECK_NOTNULL(tmp); - t_device = (thread_device_resources*)tmp; - device_res = (thread_device_resources)((char*)tmp + - g_system_device_num*sizeof(thread_device_resources*)); - memset(t_device, 0, g_system_device_num*sizeof(thread_device_resources*)); + t_device = (thread_device_resources *)tmp; + device_res = (thread_device_resources)( + (char *)tmp + g_system_device_num * sizeof(thread_device_resources *)); + memset(t_device, 0, g_system_device_num * sizeof(thread_device_resources *)); - char *tmp_stream = (char *) - malloc(device_num*NUMBER_OF_THREAD_STREAM*sizeof(cudaStream_t)); + char *tmp_stream = (char *)malloc(device_num * NUMBER_OF_THREAD_STREAM * + sizeof(cudaStream_t)); CHECK_NOTNULL(tmp_stream); int num = 0; @@ -239,8 +221,9 @@ void hl_init(int device) { } t_device[dev] = &device_res[num]; - t_device[dev]->stream = (cudaStream_t*)(tmp_stream + - num*NUMBER_OF_THREAD_STREAM*sizeof(cudaStream_t)); + t_device[dev]->stream = + (cudaStream_t *)(tmp_stream + + num * NUMBER_OF_THREAD_STREAM * sizeof(cudaStream_t)); hl_create_thread_resources(dev, t_device[dev]); num++; @@ -266,14 +249,14 @@ void hl_fini() { t_resource.stream[i] = 0; } - char* tmp = (char*)t_device; - char* tmp_stream = NULL; + char *tmp = (char *)t_device; + char *tmp_stream = NULL; for (int dev = 0; dev < g_system_device_num; dev++) { if (!t_device[dev]) { continue; } if (!tmp_stream) { - tmp_stream = (char*)t_device[dev]->stream; + tmp_stream = (char *)t_device[dev]->stream; } for (int j = 0; j < NUMBER_OF_THREAD_STREAM; j++) { CHECK_CUDA(dynload::cudaStreamDestroy(t_device[dev]->stream[j])); @@ -290,9 +273,7 @@ void hl_fini() { t_resource.is_init = false; } -int hl_get_device_count() { - return device_num; -} +int hl_get_device_count() { return device_num; } void hl_set_device(int device) { if (device == t_resource.device) { @@ -300,7 +281,7 @@ void hl_set_device(int device) { } CHECK(device >= 0 && device < g_system_device_num && g_device[device]) - << "Device: " << device << " is not specified in startup."; + << "Device: " << device << " is not specified in startup."; CHECK_CUDA(dynload::cudaSetDevice(device)); @@ -312,11 +293,11 @@ void hl_set_device(int device) { if (true == t_resource.is_init) { for (int i = NUMBER_OF_GLOBAL_STREAM; i < HPPL_STREAM_END; i++) { t_resource.stream[i] = - t_device[device]->stream[i - NUMBER_OF_GLOBAL_STREAM]; + t_device[device]->stream[i - NUMBER_OF_GLOBAL_STREAM]; } t_resource.gpu_mem = t_device[device]->gpu_mem; t_resource.cpu_mem = t_device[device]->cpu_mem; - t_resource.event = t_device[device]->mem_event; + t_resource.event = t_device[device]->mem_event; } t_resource.handle = g_device[device]->device_resources->handle; @@ -334,11 +315,11 @@ int hl_get_device() { return device; } -void* hl_malloc_device(size_t size) { +void *hl_malloc_device(size_t size) { void *dest_d; CHECK(size) << __func__ << ": the size for device memory is 0, please check."; - CHECK_CUDA(dynload::cudaMalloc((void**)&dest_d, size)); + CHECK_CUDA(dynload::cudaMalloc((void **)&dest_d, size)); return dest_d; } @@ -348,15 +329,15 @@ void hl_free_mem_device(void *dest_d) { cudaError_t err = dynload::cudaFree(dest_d); CHECK(cudaSuccess == err || cudaErrorCudartUnloading == err) - << hl_get_device_error_string(); + << hl_get_device_error_string(); } -void* hl_malloc_host(size_t size) { +void *hl_malloc_host(size_t size) { void *dest_h; CHECK(size) << __func__ << ": the size for device memory is 0, please check."; - CHECK_CUDA(dynload::cudaHostAlloc( - (void**)&dest_h, size, cudaHostAllocDefault)); + CHECK_CUDA( + dynload::cudaHostAlloc((void **)&dest_h, size, cudaHostAllocDefault)); return dest_h; } @@ -366,7 +347,7 @@ void hl_free_mem_host(void *dest_h) { cudaError_t err = dynload::cudaFreeHost(dest_h); CHECK(cudaSuccess == err || cudaErrorCudartUnloading == err) - << hl_get_device_error_string(); + << hl_get_device_error_string(); } void hl_memcpy(void *dst, void *src, size_t size) { @@ -388,8 +369,7 @@ void hl_memcpy_host2device(void *dest_d, void *src_h, size_t size) { } CHECK_NOTNULL(src_h); CHECK_NOTNULL(dest_d); - CHECK_CUDA(dynload::cudaMemcpy(dest_d, src_h, size, - cudaMemcpyHostToDevice)); + CHECK_CUDA(dynload::cudaMemcpy(dest_d, src_h, size, cudaMemcpyHostToDevice)); } void hl_memcpy_device2host(void *dest_h, void *src_d, size_t size) { @@ -398,8 +378,7 @@ void hl_memcpy_device2host(void *dest_h, void *src_d, size_t size) { } CHECK_NOTNULL(dest_h); CHECK_NOTNULL(src_d); - CHECK_CUDA(dynload::cudaMemcpy(dest_h, src_d, size, - cudaMemcpyDeviceToHost)); + CHECK_CUDA(dynload::cudaMemcpy(dest_h, src_d, size, cudaMemcpyDeviceToHost)); } void hl_memcpy_device2device(void *dest_d, void *src_d, size_t size) { @@ -408,8 +387,8 @@ void hl_memcpy_device2device(void *dest_d, void *src_d, size_t size) { } CHECK_NOTNULL(dest_d); CHECK_NOTNULL(src_d); - CHECK_CUDA(dynload::cudaMemcpy(dest_d, src_d, size, - cudaMemcpyDeviceToDevice)); + CHECK_CUDA( + dynload::cudaMemcpy(dest_d, src_d, size, cudaMemcpyDeviceToDevice)); } void hl_memcpy_async(void *dst, void *src, size_t size, hl_stream_t stream) { @@ -423,8 +402,8 @@ void hl_memcpy_async(void *dst, void *src, size_t size, hl_stream_t stream) { CHECK_LT(stream, HPPL_STREAM_END); cu_stream = t_resource.stream[stream]; - CHECK_CUDA(dynload::cudaMemcpyAsync(dst, src, size, cudaMemcpyDefault, - cu_stream)); + CHECK_CUDA( + dynload::cudaMemcpyAsync(dst, src, size, cudaMemcpyDefault, cu_stream)); } void hl_start() { @@ -435,8 +414,8 @@ void hl_start() { bool hl_device_can_access_peer(int device, int peerDevice) { int canAccessPeer; - CHECK_CUDA(dynload::cudaDeviceCanAccessPeer(&canAccessPeer, device, - peerDevice)); + CHECK_CUDA( + dynload::cudaDeviceCanAccessPeer(&canAccessPeer, device, peerDevice)); if (canAccessPeer == 1) { return true; @@ -478,33 +457,32 @@ void hl_create_global_resources(hl_device_prop device_prop) { /* create curand gen */ CHECK_EQ(dynload::curandCreateGenerator(&device_res->gen, - CURAND_RNG_PSEUDO_DEFAULT), CURAND_STATUS_SUCCESS) - << "[Start failed] Curand init failed."; + CURAND_RNG_PSEUDO_DEFAULT), + CURAND_STATUS_SUCCESS) + << "[Start failed] Curand init failed."; - CHECK_EQ(dynload::curandSetStream(device_res->gen, - device_res->stream[0]), CURAND_STATUS_SUCCESS) - << "[Start failed] Curand set stream failed!"; + CHECK_EQ(dynload::curandSetStream(device_res->gen, device_res->stream[0]), + CURAND_STATUS_SUCCESS) + << "[Start failed] Curand set stream failed!"; /* create cudnn handle */ hl_cudnn_init(&device_res->cudnn_handle, device_res->stream[0]); int seed = gettid(); - CHECK_EQ(dynload::curandSetPseudoRandomGeneratorSeed( - device_res->gen, seed+device), CURAND_STATUS_SUCCESS); + CHECK_EQ(dynload::curandSetPseudoRandomGeneratorSeed(device_res->gen, + seed + device), + CURAND_STATUS_SUCCESS); - device_res->gen_mutex = - (pthread_mutex_t*)(malloc(sizeof (pthread_mutex_t))); + device_res->gen_mutex = (pthread_mutex_t *)(malloc(sizeof(pthread_mutex_t))); pthread_mutex_init(device_res->gen_mutex, NULL); CHECK_CUDA(dynload::cudaRuntimeGetVersion(&g_cuda_lib_version)); } -int hl_get_cuda_version() { - return g_cuda_lib_version; -} +int hl_get_cuda_version() { return g_cuda_lib_version; } void hl_create_thread_resources(int device, - thread_device_resources device_res) { + thread_device_resources device_res) { CHECK_CUDA(dynload::cudaSetDevice(device)); /* create thread stream */ @@ -513,15 +491,15 @@ void hl_create_thread_resources(int device, } /* allocation device memory */ - device_res->gpu_mem = (real*)hl_malloc_device(HPPL_GPU_MEMORY_SIZE); + device_res->gpu_mem = (real *)hl_malloc_device(HPPL_GPU_MEMORY_SIZE); /* allocation host memory */ - device_res->cpu_mem = (real*)hl_malloc_host(HPPL_GPU_MEMORY_SIZE); + device_res->cpu_mem = (real *)hl_malloc_host(HPPL_GPU_MEMORY_SIZE); CHECK_CUDA(dynload::cudaEventCreate(&device_res->mem_event)); } -void hl_specify_devices_start(int* device, int number) { +void hl_specify_devices_start(int *device, int number) { if (hl_start_flag) return; /* 1. get the number of devices */ @@ -533,20 +511,19 @@ void hl_specify_devices_start(int* device, int number) { /* 2. check device & create device property table */ CHECK_LE(number, g_system_device_num) - << "[Start failed] System does not have enough device. " - << "Device number: " << g_system_device_num - << "Input number: " << number; + << "[Start failed] System does not have enough device. " + << "Device number: " << g_system_device_num << "Input number: " << number; char *tmp; hl_device_prop device_prop; - tmp = (char *)malloc(g_system_device_num*sizeof(hl_device_prop*) + - number*sizeof(_hl_device_prop)); + tmp = (char *)malloc(g_system_device_num * sizeof(hl_device_prop *) + + number * sizeof(_hl_device_prop)); CHECK(tmp) << "[Start failed] System memory is not enough."; - g_device = (hl_device_prop*)tmp; - device_prop = (hl_device_prop)((char*)tmp + - g_system_device_num*sizeof(hl_device_prop*)); - memset(g_device, 0, g_system_device_num*sizeof(hl_device_prop*)); + g_device = (hl_device_prop *)tmp; + device_prop = (hl_device_prop)( + (char *)tmp + g_system_device_num * sizeof(hl_device_prop *)); + memset(g_device, 0, g_system_device_num * sizeof(hl_device_prop *)); int num = 0; for (int i = 0; i < number; i++) { int dev; @@ -557,13 +534,13 @@ void hl_specify_devices_start(int* device, int number) { } CHECK_LT(dev, g_system_device_num) - << "[Start failed] The specified device number is " - << "out of range. Max device number: " << g_system_device_num - 1 - << " Specified devcie number: "<< dev; + << "[Start failed] The specified device number is " + << "out of range. Max device number: " << g_system_device_num - 1 + << " Specified devcie number: " << dev; if (g_device[dev]) { /* Warning */ - LOG(WARNING) <<"[Warning] Repeat specify device: " << dev; + LOG(WARNING) << "[Warning] Repeat specify device: " << dev; continue; } @@ -574,11 +551,11 @@ void hl_specify_devices_start(int* device, int number) { device_num = num; /* 3. create global device resources */ - char *tmp_res = (char *)malloc(device_num*sizeof(_global_device_resources)); + char *tmp_res = (char *)malloc(device_num * sizeof(_global_device_resources)); CHECK_NOTNULL(tmp_res); - char *tmp_stream = - (char *)malloc(device_num*NUMBER_OF_GLOBAL_STREAM*sizeof(cudaStream_t)); + char *tmp_stream = (char *)malloc(device_num * NUMBER_OF_GLOBAL_STREAM * + sizeof(cudaStream_t)); CHECK_NOTNULL(tmp_stream); num = 0; @@ -587,10 +564,11 @@ void hl_specify_devices_start(int* device, int number) { continue; } - g_device[i]->device_resources = (global_device_resources)(tmp_res + - num*sizeof(_global_device_resources)); - g_device[i]->device_resources->stream = (cudaStream_t*)(tmp_stream + - num*NUMBER_OF_GLOBAL_STREAM*sizeof(cudaStream_t)); + g_device[i]->device_resources = (global_device_resources)( + tmp_res + num * sizeof(_global_device_resources)); + g_device[i]->device_resources->stream = + (cudaStream_t *)(tmp_stream + + num * NUMBER_OF_GLOBAL_STREAM * sizeof(cudaStream_t)); hl_create_global_resources(g_device[i]); num++; @@ -600,9 +578,9 @@ void hl_specify_devices_start(int* device, int number) { hl_start_flag = true; /* set default device */ if (device == NULL) { - hl_set_device(0); + hl_set_device(0); } else { - hl_set_device(device[0]); + hl_set_device(device[0]); } } @@ -610,35 +588,31 @@ void hl_rand(real *dest_d, size_t num) { pthread_mutex_lock(t_resource.gen_mutex); CHECK_EQ( #ifndef PADDLE_TYPE_DOUBLE - dynload::curandGenerateUniform(t_resource.gen, dest_d, num), + dynload::curandGenerateUniform(t_resource.gen, dest_d, num), #else - dynload::curandGenerateUniformDouble(t_resource.gen, dest_d, num), + dynload::curandGenerateUniformDouble(t_resource.gen, dest_d, num), #endif - CURAND_STATUS_SUCCESS); + CURAND_STATUS_SUCCESS); pthread_mutex_unlock(t_resource.gen_mutex); CHECK_SYNC("hl_rand failed"); } void hl_srand(unsigned int seed) { pthread_mutex_lock(t_resource.gen_mutex); - CHECK_EQ(dynload::curandSetPseudoRandomGeneratorSeed( - t_resource.gen, seed), CURAND_STATUS_SUCCESS); + CHECK_EQ(dynload::curandSetPseudoRandomGeneratorSeed(t_resource.gen, seed), + CURAND_STATUS_SUCCESS); pthread_mutex_unlock(t_resource.gen_mutex); } -void hl_set_sync_flag(bool flag) { - g_sync_flag = flag; -} +void hl_set_sync_flag(bool flag) { g_sync_flag = flag; } -bool hl_get_sync_flag() { - return g_sync_flag; -} +bool hl_get_sync_flag() { return g_sync_flag; } void hl_stream_synchronize(hl_stream_t stream) { cudaStream_t cu_stream; - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ <<": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(dynload::cudaStreamSynchronize(cu_stream)); @@ -647,8 +621,8 @@ void hl_stream_synchronize(hl_stream_t stream) { void hl_create_event(hl_event_t *event) { CHECK_NOTNULL(event); - struct _hl_event_st* st_event = - (struct _hl_event_st*)malloc(sizeof(struct _hl_event_st)); + struct _hl_event_st *st_event = + (struct _hl_event_st *)malloc(sizeof(struct _hl_event_st)); CHECK_CUDA(dynload::cudaEventCreate(&st_event->cu_event)); @@ -660,8 +634,8 @@ float hl_event_elapsed_time(hl_event_t start, hl_event_t end) { CHECK_NOTNULL(start); CHECK_NOTNULL(end); - CHECK_CUDA(dynload::cudaEventElapsedTime(&time, - start->cu_event, end->cu_event)); + CHECK_CUDA( + dynload::cudaEventElapsedTime(&time, start->cu_event, end->cu_event)); return time; } @@ -669,24 +643,22 @@ void hl_stream_record_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ <<": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; - CHECK_CUDA(dynload::cudaEventRecord( - event->cu_event, cu_stream)); + CHECK_CUDA(dynload::cudaEventRecord(event->cu_event, cu_stream)); } void hl_stream_wait_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ <<": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; - CHECK_CUDA(dynload::cudaStreamWaitEvent( - cu_stream, event->cu_event, 0)); + CHECK_CUDA(dynload::cudaStreamWaitEvent(cu_stream, event->cu_event, 0)); } void hl_destroy_event(hl_event_t event) { @@ -705,15 +677,15 @@ void hl_event_synchronize(hl_event_t event) { void hl_get_device_name(char *name, int len, int device) { CHECK_NOTNULL(name); CHECK(device >= 0 && device < g_system_device_num && g_device[device]) - << "Device("<< device <<") is not specified in startup."; + << "Device(" << device << ") is not specified in startup."; - strncpy(name, g_device[device]->device_name , len); + strncpy(name, g_device[device]->device_name, len); } void hl_get_device_memory(size_t *mem_size, int device) { CHECK_NOTNULL(mem_size); CHECK(device >= 0 && device < g_system_device_num && g_device[device]) - << "Device("<< device <<") is not specified in startup."; + << "Device(" << device << ") is not specified in startup."; *mem_size = g_device[device]->device_mem; } @@ -722,31 +694,26 @@ void hl_get_device_compute_capability(int *major, int *minor, int device) { CHECK_NOTNULL(major); CHECK_NOTNULL(minor); CHECK(device >= 0 && device < g_system_device_num && g_device[device]) - << "Device("<< device << ") is not specified in startup."; + << "Device(" << device << ") is not specified in startup."; *major = g_device[device]->major; *minor = g_device[device]->minor; } -int hl_get_device_last_error() { - return (int)dynload::cudaGetLastError(); -} +int hl_get_device_last_error() { return (int)dynload::cudaGetLastError(); } -const char* hl_get_device_error_string() { +const char *hl_get_device_error_string() { cudaError_t err = dynload::cudaGetLastError(); return dynload::cudaGetErrorString(err); } -const char* hl_get_device_error_string(size_t err) { +const char *hl_get_device_error_string(size_t err) { return dynload::cudaGetErrorString((cudaError_t)err); } -void hl_device_synchronize() { - CHECK_CUDA(dynload::cudaDeviceSynchronize()); -} +void hl_device_synchronize() { CHECK_CUDA(dynload::cudaDeviceSynchronize()); } void hl_set_device_flags_block() { - CHECK_CUDA(dynload::cudaSetDeviceFlags( - cudaDeviceScheduleBlockingSync)); + CHECK_CUDA(dynload::cudaSetDeviceFlags(cudaDeviceScheduleBlockingSync)); } bool hl_cuda_event_is_ready(hl_event_t event) { diff --git a/paddle/cuda/src/hl_cudart_wrap.cc b/paddle/cuda/src/hl_cudart_wrap.cc index fe755b8c26..610b47581c 100644 --- a/paddle/cuda/src/hl_cudart_wrap.cc +++ b/paddle/cuda/src/hl_cudart_wrap.cc @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifdef PADDLE_USE_DSO #include @@ -29,48 +28,46 @@ limitations under the License. */ namespace dynload { extern std::once_flag cudart_dso_flag; -extern void* cudart_dso_handle; +extern void *cudart_dso_handle; /** * The following macro definition can generate structs * (for each function) to dynamic load cuda routine * via operator overloading. **/ -#define DYNAMIC_LOAD_CUDART_WRAP(__name, __type) \ - struct DynLoad__##__name { \ - template \ - __type operator()(Args... args) { \ - typedef __type (*cudartFunc)(Args...); \ - std::call_once(cudart_dso_flag, GetCudartDsoHandle, \ - &cudart_dso_handle); \ - void* p_##__name = dlsym(cudart_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ - } __name; /* struct DynLoad__##__name */ +#define DYNAMIC_LOAD_CUDART_WRAP(__name, __type) \ + struct DynLoad__##__name { \ + template \ + __type operator()(Args... args) { \ + typedef __type (*cudartFunc)(Args...); \ + std::call_once(cudart_dso_flag, GetCudartDsoHandle, &cudart_dso_handle); \ + void *p_##__name = dlsym(cudart_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ /* include all needed cuda functions in HPPL */ -#define CUDA_ROUTINE_EACH(__macro) \ - __macro(cudaLaunch, cudaError_t) \ - __macro(cudaSetupArgument, cudaError_t) \ - __macro(cudaConfigureCall, cudaError_t) \ - __macro(__cudaRegisterFatBinary, void**) \ - __macro(__cudaUnregisterFatBinary, void) \ - __macro(__cudaRegisterFunction, void) \ - __macro(__cudaRegisterVar, void) \ - __macro(__cudaRegisterManagedVar, void) \ - __macro(__cudaInitModule, char) \ - __macro(__cudaRegisterTexture, void) \ - __macro(__cudaRegisterSurface, void) +#define CUDA_ROUTINE_EACH(__macro) \ + __macro(cudaLaunch, cudaError_t) __macro(cudaSetupArgument, cudaError_t) \ + __macro(cudaConfigureCall, cudaError_t) \ + __macro(__cudaRegisterFatBinary, void **) \ + __macro(__cudaUnregisterFatBinary, void) \ + __macro(__cudaRegisterFunction, void) \ + __macro(__cudaRegisterVar, void) \ + __macro(__cudaRegisterManagedVar, void) \ + __macro(__cudaInitModule, char) \ + __macro(__cudaRegisterTexture, void) \ + __macro(__cudaRegisterSurface, void) CUDA_ROUTINE_EACH(DYNAMIC_LOAD_CUDART_WRAP) #if CUDART_VERSION >= 7000 - DYNAMIC_LOAD_CUDART_WRAP(cudaLaunchKernel, cudaError_t) +DYNAMIC_LOAD_CUDART_WRAP(cudaLaunchKernel, cudaError_t) #endif #undef CUDA_ROUNTINE_EACH -} /* namespace dynload */ +} /* namespace dynload */ #if CUDART_VERSION >= 7000 __host__ cudaError_t CUDARTAPI cudaLaunchKernel(const void *func, @@ -79,12 +76,11 @@ __host__ cudaError_t CUDARTAPI cudaLaunchKernel(const void *func, void **args, size_t sharedMem, cudaStream_t stream) { - return dynload::cudaLaunchKernel(func, gridDim, blockDim, - args, sharedMem, stream); + return dynload::cudaLaunchKernel( + func, gridDim, blockDim, args, sharedMem, stream); } #endif /* CUDART_VERSION >= 7000 */ - __host__ cudaError_t CUDARTAPI cudaLaunch(const void *func) { return dynload::cudaLaunch(func); } @@ -99,13 +95,12 @@ __host__ cudaError_t CUDARTAPI cudaConfigureCall(dim3 gridDim, dim3 blockDim, size_t sharedMem, cudaStream_t stream) { - return dynload::cudaConfigureCall(gridDim, blockDim, - sharedMem, stream); + return dynload::cudaConfigureCall(gridDim, blockDim, sharedMem, stream); } extern "C" { -void** CUDARTAPI __cudaRegisterFatBinary(void *fatCubin) { +void **CUDARTAPI __cudaRegisterFatBinary(void *fatCubin) { return dynload::__cudaRegisterFatBinary(fatCubin); } @@ -113,86 +108,87 @@ void CUDARTAPI __cudaUnregisterFatBinary(void **fatCubinHandle) { return dynload::__cudaUnregisterFatBinary(fatCubinHandle); } -void CUDARTAPI __cudaRegisterFunction( - void **fatCubinHandle, - const char *hostFun, - char *deviceFun, - const char *deviceName, - int thread_limit, - uint3 *tid, - uint3 *bid, - dim3 *bDim, - dim3 *gDim, - int *wSize -) { - return dynload::__cudaRegisterFunction( - fatCubinHandle, hostFun, deviceFun, deviceName, - thread_limit, tid, bid, bDim, gDim, wSize); +void CUDARTAPI __cudaRegisterFunction(void **fatCubinHandle, + const char *hostFun, + char *deviceFun, + const char *deviceName, + int thread_limit, + uint3 *tid, + uint3 *bid, + dim3 *bDim, + dim3 *gDim, + int *wSize) { + return dynload::__cudaRegisterFunction(fatCubinHandle, + hostFun, + deviceFun, + deviceName, + thread_limit, + tid, + bid, + bDim, + gDim, + wSize); } -void CUDARTAPI __cudaRegisterVar( - void **fatCubinHandle, - char *hostVar, - char *deviceAddress, - const char *deviceName, - int ext, - int size, - int constant, - int global -) { - return dynload::__cudaRegisterVar( - fatCubinHandle, hostVar, deviceAddress, - deviceName, ext, size, constant, global); +void CUDARTAPI __cudaRegisterVar(void **fatCubinHandle, + char *hostVar, + char *deviceAddress, + const char *deviceName, + int ext, + int size, + int constant, + int global) { + return dynload::__cudaRegisterVar(fatCubinHandle, + hostVar, + deviceAddress, + deviceName, + ext, + size, + constant, + global); } - - -extern void CUDARTAPI __cudaRegisterManagedVar( - void **fatCubinHandle, - void **hostVarPtrAddress, - char *deviceAddress, - const char *deviceName, - int ext, - int size, - int constant, - int global -) { - return dynload::__cudaRegisterManagedVar( - fatCubinHandle, hostVarPtrAddress, deviceAddress, - deviceName, ext, size, constant, global); +extern void CUDARTAPI __cudaRegisterManagedVar(void **fatCubinHandle, + void **hostVarPtrAddress, + char *deviceAddress, + const char *deviceName, + int ext, + int size, + int constant, + int global) { + return dynload::__cudaRegisterManagedVar(fatCubinHandle, + hostVarPtrAddress, + deviceAddress, + deviceName, + ext, + size, + constant, + global); } -char CUDARTAPI __cudaInitModule( - void **fatCubinHandle -) { +char CUDARTAPI __cudaInitModule(void **fatCubinHandle) { return dynload::__cudaInitModule(fatCubinHandle); } -void CUDARTAPI __cudaRegisterTexture( - void **fatCubinHandle, - const struct textureReference *hostVar, - const void **deviceAddress, - const char *deviceName, - int dim, - int norm, - int ext -) { +void CUDARTAPI __cudaRegisterTexture(void **fatCubinHandle, + const struct textureReference *hostVar, + const void **deviceAddress, + const char *deviceName, + int dim, + int norm, + int ext) { return dynload::__cudaRegisterTexture( - fatCubinHandle, hostVar, deviceAddress, - deviceName, dim, norm, ext); + fatCubinHandle, hostVar, deviceAddress, deviceName, dim, norm, ext); } -void CUDARTAPI __cudaRegisterSurface( - void **fatCubinHandle, - const struct surfaceReference *hostVar, - const void **deviceAddress, - const char *deviceName, - int dim, - int ext -) { +void CUDARTAPI __cudaRegisterSurface(void **fatCubinHandle, + const struct surfaceReference *hostVar, + const void **deviceAddress, + const char *deviceName, + int dim, + int ext) { return dynload::__cudaRegisterSurface( - fatCubinHandle, hostVar, deviceAddress, - deviceName, dim, ext); + fatCubinHandle, hostVar, deviceAddress, deviceName, dim, ext); } } /* extern "C" */ diff --git a/paddle/cuda/src/hl_math.cc b/paddle/cuda/src/hl_math.cc index 76d48c4a9b..f4bf888bab 100644 --- a/paddle/cuda/src/hl_math.cc +++ b/paddle/cuda/src/hl_math.cc @@ -12,24 +12,15 @@ WITHOUT 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 "avx_mathfun.h" namespace hppl { -__m256 exp(__m256 a) { - return exp256_ps(a); -} +__m256 exp(__m256 a) { return exp256_ps(a); } -__m256 log(__m256 a) { - return log256_ps(a); -} +__m256 log(__m256 a) { return log256_ps(a); } -__m256 sin(__m256 a) { - return sin256_ps(a); -} +__m256 sin(__m256 a) { return sin256_ps(a); } -__m256 cos(__m256 a) { - return cos256_ps(a); -} +__m256 cos(__m256 a) { return cos256_ps(a); } } // namespace hppl diff --git a/paddle/cuda/src/hl_time.cc b/paddle/cuda/src/hl_time.cc index adc88d60dd..d52b2a1df0 100644 --- a/paddle/cuda/src/hl_time.cc +++ b/paddle/cuda/src/hl_time.cc @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include @@ -21,8 +20,7 @@ limitations under the License. */ using std::chrono::high_resolution_clock; int64_t getCurrentTimeStick() { - high_resolution_clock::time_point tp = high_resolution_clock::now(); - high_resolution_clock::duration dtn = tp.time_since_epoch(); - return dtn.count(); + high_resolution_clock::time_point tp = high_resolution_clock::now(); + high_resolution_clock::duration dtn = tp.time_since_epoch(); + return dtn.count(); } - diff --git a/paddle/gserver/activations/ActivationFunction.cpp b/paddle/gserver/activations/ActivationFunction.cpp index 27eed75d4d..f1bb94216c 100644 --- a/paddle/gserver/activations/ActivationFunction.cpp +++ b/paddle/gserver/activations/ActivationFunction.cpp @@ -51,12 +51,14 @@ static ClassRegistrar gActivationRegistrar; * @brief Macro for registering a derived activation class */ #define END_DEFINE_ACTIVATION(ACTIVATION_NAME) \ - }; \ + } \ + ; \ const std::string ACTIVATION_CLASS_NAME(ACTIVATION_NAME)::name = \ #ACTIVATION_NAME; \ static InitFunction __reg_activation__##ACTIVATION_NAME([] { \ - gActivationRegistrar.registerClass< \ - ACTIVATION_CLASS_NAME(ACTIVATION_NAME)>(#ACTIVATION_NAME); \ + gActivationRegistrar \ + .registerClass( \ + #ACTIVATION_NAME); \ }); /** @@ -111,14 +113,22 @@ void backward(Argument& act) { outputG->softmaxBackward(*outputV); } else { SetDevice device(act.deviceId); - Matrix::resizeOrCreate(sftMaxDot_, outputG->getHeight(), + Matrix::resizeOrCreate(sftMaxDot_, + outputG->getHeight(), outputG->getWidth(), - /* trans */ false, useGpu(act.deviceId)); - Matrix::resizeOrCreate(sftMaxSum_, outputG->getHeight(), 1, - /* trans */ false, useGpu(act.deviceId)); + /* trans */ false, + useGpu(act.deviceId)); + Matrix::resizeOrCreate(sftMaxSum_, + outputG->getHeight(), + 1, + /* trans */ false, + useGpu(act.deviceId)); if (!one_ || one_->getWidth() != outputG->getWidth()) { - Matrix::resizeOrCreate(one_, 1, outputG->getWidth(), - /* trans */ false, useGpu(act.deviceId)); + Matrix::resizeOrCreate(one_, + 1, + outputG->getWidth(), + /* trans */ false, + useGpu(act.deviceId)); one_->one(); } @@ -130,7 +140,6 @@ void backward(Argument& act) { } END_DEFINE_ACTIVATION(softmax) - /** * @brief Sequence_softmax Activation * @note Softmax on all frames of one sequence. @@ -146,10 +155,16 @@ void forward(Argument& act) { CHECK_EQ(act.value->getWidth(), 1UL); if (!argument_.value) { - argument_.value = Matrix::create(nullptr, /* height= */ 1, 1, - /* trans= */ false, useGpu(act.deviceId)); - argument_.grad = Matrix::create(nullptr, /* height= */ 1, 1, - /* trans= */ false, useGpu(act.deviceId)); + argument_.value = Matrix::create(nullptr, + /* height= */ 1, + 1, + /* trans= */ false, + useGpu(act.deviceId)); + argument_.grad = Matrix::create(nullptr, + /* height= */ 1, + 1, + /* trans= */ false, + useGpu(act.deviceId)); } auto starts = act.sequenceStartPositions->getVector(useGpu(act.deviceId)); @@ -267,8 +282,11 @@ END_DEFINE_ACTIVATION(softrelu) BEGIN_DEFINE_ACTIVATION(abs) void forward(Argument& act) { SetDevice device(act.deviceId); - Matrix::resizeOrCreate(act.in, act.value->getHeight(), act.value->getWidth(), - /* trans */ false, useGpu(act.deviceId)); + Matrix::resizeOrCreate(act.in, + act.value->getHeight(), + act.value->getWidth(), + /* trans */ false, + useGpu(act.deviceId)); act.in->copyFrom(*act.value); act.value->abs(*act.value); @@ -286,8 +304,11 @@ END_DEFINE_ACTIVATION(abs) BEGIN_DEFINE_ACTIVATION(square) void forward(Argument& act) { SetDevice device(act.deviceId); - Matrix::resizeOrCreate(act.in, act.value->getHeight(), act.value->getWidth(), - /* trans */ false, useGpu(act.deviceId)); + Matrix::resizeOrCreate(act.in, + act.value->getHeight(), + act.value->getWidth(), + /* trans */ false, + useGpu(act.deviceId)); act.in->copyFrom(*act.value); act.value->square(*act.value); @@ -317,8 +338,11 @@ END_DEFINE_ACTIVATION(exponential) BEGIN_DEFINE_ACTIVATION(log) void forward(Argument& act) { SetDevice device(act.deviceId); - Matrix::resizeOrCreate(act.in, act.value->getHeight(), act.value->getWidth(), - /* trans */ false, useGpu(act.deviceId)); + Matrix::resizeOrCreate(act.in, + act.value->getHeight(), + act.value->getWidth(), + /* trans */ false, + useGpu(act.deviceId)); act.in->copyFrom(*act.value); act.value->log(*act.value); @@ -333,11 +357,9 @@ ActivationFunction* ActivationFunction::create(const std::string& type) { std::vector ActivationFunction::getAllRegisteredTypes() { std::vector types; - gActivationRegistrar.forEachType([&](const std::string& type) { - types.push_back(type); - }); + gActivationRegistrar.forEachType( + [&](const std::string& type) { types.push_back(type); }); return types; } - } // namespace paddle diff --git a/paddle/gserver/activations/ActivationFunction.h b/paddle/gserver/activations/ActivationFunction.h index c483372256..e9ed5c619a 100644 --- a/paddle/gserver/activations/ActivationFunction.h +++ b/paddle/gserver/activations/ActivationFunction.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include diff --git a/paddle/gserver/dataproviders/DataProvider.cpp b/paddle/gserver/dataproviders/DataProvider.cpp index 2cfb5a3a18..e6cc4a246a 100644 --- a/paddle/gserver/dataproviders/DataProvider.cpp +++ b/paddle/gserver/dataproviders/DataProvider.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "DataProvider.h" #include "paddle/utils/Util.h" @@ -57,7 +56,7 @@ void BufferBatch::clone(DataBatch* srcBatch, bool useGpu) { } } -DoubleBuffer::DoubleBuffer(DataProvider *dataPool, +DoubleBuffer::DoubleBuffer(DataProvider* dataPool, bool useGpu, int64_t batchSize) { batchSize_ = batchSize; @@ -155,7 +154,7 @@ void DoubleBuffer::startAsyncLoad() { } ClassRegistrar -DataProvider::registrar_; + DataProvider::registrar_; DataProvider* DataProvider::create(const DataConfig& config, const ModelConfig& modelConfig, @@ -182,7 +181,8 @@ int64_t DataProvider::getNextBatch(int64_t size, DataBatch* batch) { for (int i = 0; i < config_.constant_slots_size(); ++i) { MemoryHandlePtr handle = constantSlots[i] ? constantSlots[i]->getMemoryHandle() : nullptr; - Matrix::resizeOrCreate(constantSlots[i], batchSize, + Matrix::resizeOrCreate(constantSlots[i], + batchSize, 1, // = width false, // = trans useGpu_); // = useGpu @@ -216,7 +216,8 @@ void DataProvider::initAsyncLoader() { } SimpleDataProviderBase::SimpleDataProviderBase(const DataConfig& config, - bool useGpu, bool withInfo) + bool useGpu, + bool withInfo) : DataProvider(config, useGpu) { /* initialize the size of a sample, and the buffer */ sampleDim_ = config_.feat_dim() * (2 * config_.context_len() + 1); @@ -337,7 +338,8 @@ int64_t SimpleDataProviderBase::fillBuffer() { sampleNumInBuf_ = n + fillBufferImp(hInputDataBuf_->getData() + n * sampleDim_, hInputLabelBuf_->getData() + n, - hInputInfoBuf_->getData() + n, bufferCapacity_ - n); + hInputInfoBuf_->getData() + n, + bufferCapacity_ - n); /* for stachastic gradient training */ if (!skipShuffle_) { @@ -357,11 +359,14 @@ SimpleDataProvider::SimpleDataProvider(const DataConfig& config, bool useGpu) SimpleDataProvider::~SimpleDataProvider() {} -int64_t SimpleDataProvider::fillBufferImp(real* data, int* label, int* info, +int64_t SimpleDataProvider::fillBufferImp(real* data, + int* label, + int* info, int64_t size) { (void)info; int64_t n = std::min(labels_.size() - currentSampleIndex_, size); - memcpy(data, &data_[currentSampleIndex_ * sampleDim_], + memcpy(data, + &data_[currentSampleIndex_ * sampleDim_], n * sampleDim_ * sizeof(real)); memcpy(label, &labels_[currentSampleIndex_], sizeof(int) * n); currentSampleIndex_ += n; diff --git a/paddle/gserver/dataproviders/DataProvider.h b/paddle/gserver/dataproviders/DataProvider.h index 112e45de1c..8b7fb27f82 100644 --- a/paddle/gserver/dataproviders/DataProvider.h +++ b/paddle/gserver/dataproviders/DataProvider.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -44,15 +43,15 @@ namespace paddle { * @brief Macro for registering a data provider. The class type should contain * a consturctor with parameter (DataConfig, bool). */ -#define REGISTER_DATA_PROVIDER(__type_name, __class_name)\ - static InitFunction __reg_type_##__type_name([]() {\ - DataProvider::registrar_.registerClass(\ - #__type_name, \ - [](DataConfig conf, ModelConfig, bool useGpu) -> DataProvider* { \ - DataProvider* dp = new __class_name (conf, useGpu);\ - return dp;\ - });\ -}) +#define REGISTER_DATA_PROVIDER(__type_name, __class_name) \ + static InitFunction __reg_type_##__type_name([]() { \ + DataProvider::registrar_.registerClass( \ + #__type_name, \ + [](DataConfig conf, ModelConfig, bool useGpu) -> DataProvider* { \ + DataProvider* dp = new __class_name(conf, useGpu); \ + return dp; \ + }); \ + }) /** * @def REGISTER_DATA_PROVIDER_EX @@ -61,8 +60,8 @@ namespace paddle { */ #define REGISTER_DATA_PROVIDER_EX(__type_name, __class_name) \ static InitFunction __reg_type_##__type_name([] { \ - DataProvider::registrar_.registerClass<__class_name>(#__type_name); \ -}) + DataProvider::registrar_.registerClass<__class_name>(#__type_name); \ + }) class DataBatch; class BufferBatch; @@ -181,7 +180,8 @@ public: * @param[in] size DataBatch.getSize() * @param[in] dataId sub dataprovider id (in MultiDataProvider) */ - void appendArguments(const std::vector& argus, int size, + void appendArguments(const std::vector& argus, + int size, int dataId) { size_ += size; for (const auto& argu : argus) { @@ -259,9 +259,7 @@ typedef Queue BufferBatchQueue; class DoubleBuffer { public: - DoubleBuffer(DataProvider* dataPool, - bool useGpu, - int64_t batchSize = 0); + DoubleBuffer(DataProvider* dataPool, bool useGpu, int64_t batchSize = 0); virtual ~DoubleBuffer(); void removeOneBatch(DataBatch* dataBatch); @@ -310,7 +308,7 @@ public: /** * @brief create only used for unittest. */ - inline static DataProvider* create(const DataConfig &config, + inline static DataProvider* create(const DataConfig& config, bool useGpu = FLAGS_use_gpu) { return create(config, ModelConfig(), useGpu); } @@ -462,7 +460,9 @@ protected: * * label[n] is the label for the n-th sample. */ - virtual int64_t fillBufferImp(real* data, int* label, int* info, + virtual int64_t fillBufferImp(real* data, + int* label, + int* info, int64_t size) = 0; }; @@ -475,7 +475,9 @@ public: protected: void loadData(const std::string& fileName); void loadDataFile(const std::string& fileName); - virtual int64_t fillBufferImp(real* data, int* label, int* info, + virtual int64_t fillBufferImp(real* data, + int* label, + int* info, int64_t size); protected: diff --git a/paddle/gserver/dataproviders/DataProviderGroup.h b/paddle/gserver/dataproviders/DataProviderGroup.h index 0689f90f3e..6c178e29ee 100644 --- a/paddle/gserver/dataproviders/DataProviderGroup.h +++ b/paddle/gserver/dataproviders/DataProviderGroup.h @@ -12,7 +12,6 @@ WITHOUT 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 "DataProvider.h" @@ -65,8 +64,8 @@ void DataProviderGroup::reset() { provider_ = nullptr; // shuffle file list - std::shuffle(fileList_.begin(), fileList_.end(), - ThreadLocalRandomEngine::get()); + std::shuffle( + fileList_.begin(), fileList_.end(), ThreadLocalRandomEngine::get()); startLoader(); DataProvider::reset(); @@ -113,8 +112,9 @@ void DataProviderGroup::startLoader() { size_t endPos = std::min(fileList_.size(), startPos + loadFileCount); std::vector fileVec(fileList_.begin() + startPos, fileList_.begin() + endPos); - loader_->addJob([this, fileVec]() - -> ProviderPtrType { return this->loadFile(fileVec); }); + loader_->addJob([this, fileVec]() -> ProviderPtrType { + return this->loadFile(fileVec); + }); } loader_->stopAddJob(); } diff --git a/paddle/gserver/dataproviders/MultiDataProvider.cpp b/paddle/gserver/dataproviders/MultiDataProvider.cpp index 8e4f53978a..51fb1f2666 100644 --- a/paddle/gserver/dataproviders/MultiDataProvider.cpp +++ b/paddle/gserver/dataproviders/MultiDataProvider.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "MultiDataProvider.h" #include "paddle/utils/Logging.h" @@ -59,10 +58,8 @@ MultiDataProvider::MultiDataProvider(const DataConfig& config, "MultiDataProvider"; subConfig.set_async_load_data(false); } - subDataProviders_[i] = - std::unique_ptr(DataProvider::create(subConfig, - modelConfig, - useGpu_)); + subDataProviders_[i] = std::unique_ptr( + DataProvider::create(subConfig, modelConfig, useGpu_)); } } diff --git a/paddle/gserver/dataproviders/MultiDataProvider.h b/paddle/gserver/dataproviders/MultiDataProvider.h index b498ba6516..876467c04f 100644 --- a/paddle/gserver/dataproviders/MultiDataProvider.h +++ b/paddle/gserver/dataproviders/MultiDataProvider.h @@ -12,7 +12,6 @@ WITHOUT 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 "DataProvider.h" diff --git a/paddle/gserver/dataproviders/ProtoDataProvider.cpp b/paddle/gserver/dataproviders/ProtoDataProvider.cpp index 344644755f..0a7ff80246 100644 --- a/paddle/gserver/dataproviders/ProtoDataProvider.cpp +++ b/paddle/gserver/dataproviders/ProtoDataProvider.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ProtoDataProvider.h" #include "paddle/utils/Util.h" #include "paddle/utils/StringUtil.h" @@ -23,7 +22,8 @@ limitations under the License. */ #include "paddle/utils/Logging.h" #include "DataProviderGroup.h" -P_DEFINE_double(memory_threshold_on_load_data, 1.0, +P_DEFINE_double(memory_threshold_on_load_data, + 1.0, "stop loading data when memory is not sufficient"); namespace paddle { @@ -32,7 +32,8 @@ REGISTER_DATA_PROVIDER(proto_group, DataProviderGroup); REGISTER_DATA_PROVIDER(proto_sequence_group, DataProviderGroup); -ProtoDataProvider::ProtoDataProvider(const DataConfig& config, bool useGpu, +ProtoDataProvider::ProtoDataProvider(const DataConfig& config, + bool useGpu, bool loadDataAll) : DataProvider(config, useGpu), sampleNums_(0), currentSequenceIndex_(0) { if (loadDataAll) { @@ -279,7 +280,8 @@ void ProtoDataProvider::fillSlots(const DataSample& sample) { } slot.sparseNonValueData.resize(slot.indices.back() + slotSize); const unsigned int* ids = sample.vector_slots(i).ids().data(); - memcpy(slot.sparseNonValueData.data() + slot.indices.back(), ids, + memcpy(slot.sparseNonValueData.data() + slot.indices.back(), + ids, sizeof(*ids) * slotSize); slot.indices.push_back(slot.indices.back() + slotSize); if (subSlotSize) { @@ -318,10 +320,11 @@ void ProtoDataProvider::fillSlots(const DataSample& sample) { slot.varDenseData[oldSize].data.resize(varDim); const float* values = sample.vector_slots(i).values().data(); #ifdef PADDLE_TYPE_DOUBLE - std::copy(values, values + varDim, - slot.varDenseData[oldSize].data.data()); + std::copy( + values, values + varDim, slot.varDenseData[oldSize].data.data()); #else - memcpy(slot.varDenseData[oldSize].data.data(), values, + memcpy(slot.varDenseData[oldSize].data.data(), + values, sizeof(real) * varDim); #endif slot.varDenseData[oldSize].dims.resize( @@ -374,8 +377,9 @@ void ProtoDataProvider::reset() { } void ProtoDataProvider::shuffle() { - std::shuffle(shuffledSequenceIds_.begin(), shuffledSequenceIds_.end(), - ThreadLocalRandomEngine::get()); + std::shuffle(shuffledSequenceIds_.begin(), + shuffledSequenceIds_.end(), + ThreadLocalRandomEngine::get()); } /* @@ -502,7 +506,8 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, if (!iidData()) { ICpuGpuVector::resizeOrCreate(cpuArguments[0].sequenceStartPositions, - numSequences + 1, /* useGpu= */ false); + numSequences + 1, + /* useGpu= */ false); int* buf = cpuArguments[0].sequenceStartPositions->getMutableData(false); int pos = 0; int i = 0; @@ -530,7 +535,9 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, switch (slotType) { case SlotDef::VECTOR_DENSE: { - Matrix::resizeOrCreate(cpuArguments[slot].value, size, dim, + Matrix::resizeOrCreate(cpuArguments[slot].value, + size, + dim, false, // trans = false false); // useGpu = false real* buf = cpuArguments[slot].value->getData(); @@ -543,19 +550,27 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, } case SlotDef::VECTOR_SPARSE_NON_VALUE: { if (!(cpuArguments[slot].value)) { - cpuArguments[slot].value = Matrix::createSparseMatrix( - size, dim, size /*DEFAULT_AVG_WIDTH = 1*/, NO_VALUE, SPARSE_CSR, - false, useGpu_); + cpuArguments[slot].value = + Matrix::createSparseMatrix(size, + dim, + size /*DEFAULT_AVG_WIDTH = 1*/, + NO_VALUE, + SPARSE_CSR, + false, + useGpu_); } auto mat = cpuArguments[slot].value; mat->resize(size, dim); if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(dataPos.data(), slots_[slot].indices.data(), - slots_[slot].sparseNonValueData.data(), HPPL_STREAM_1); + ->copyFrom(dataPos.data(), + slots_[slot].indices.data(), + slots_[slot].sparseNonValueData.data(), + HPPL_STREAM_1); } else if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(dataPos.data(), slots_[slot].indices.data(), + ->copyFrom(dataPos.data(), + slots_[slot].indices.data(), slots_[slot].sparseNonValueData.data()); } else { LOG(FATAL) << "Not Supported"; @@ -571,19 +586,27 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, } case SlotDef::VECTOR_SPARSE_VALUE: { if (!(cpuArguments[slot].value)) { - cpuArguments[slot].value = Matrix::createSparseMatrix( - size, dim, size /*DEFAULT_AVG_WIDTH = 1*/, FLOAT_VALUE, - SPARSE_CSR, false, useGpu_); + cpuArguments[slot].value = + Matrix::createSparseMatrix(size, + dim, + size /*DEFAULT_AVG_WIDTH = 1*/, + FLOAT_VALUE, + SPARSE_CSR, + false, + useGpu_); } auto mat = cpuArguments[slot].value; mat->resize(size, dim); if (std::dynamic_pointer_cast(mat)) { - std::dynamic_pointer_cast(mat)->copyFrom( - dataPos.data(), slots_[slot].indices.data(), - slots_[slot].sparseFloatValueData.data(), HPPL_STREAM_1); + std::dynamic_pointer_cast(mat) + ->copyFrom(dataPos.data(), + slots_[slot].indices.data(), + slots_[slot].sparseFloatValueData.data(), + HPPL_STREAM_1); } else if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(dataPos.data(), slots_[slot].indices.data(), + ->copyFrom(dataPos.data(), + slots_[slot].indices.data(), slots_[slot].sparseFloatValueData.data()); } else { LOG(FATAL) << "Not Supported"; @@ -591,7 +614,8 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, break; } case SlotDef::INDEX: { - IVector::resizeOrCreate(cpuArguments[slot].ids, size, + IVector::resizeOrCreate(cpuArguments[slot].ids, + size, /* useGpu= */ false); int* buf = cpuArguments[slot].ids->getData(); for (int i = 0; i < size; ++i) { @@ -621,7 +645,9 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, if (oldWidth < height) { totalDim = width * height * depth; } - Matrix::resizeOrCreate(cpuArguments[slot].value, size, totalDim, + Matrix::resizeOrCreate(cpuArguments[slot].value, + size, + totalDim, false, // trans = false false); // useGpu = false real* buf = cpuArguments[slot].value->getData(); @@ -637,13 +663,13 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, } } } else { - memcpy(buf, slots_[slot].varDenseData[dataPos[0]].data.data(), + memcpy(buf, + slots_[slot].varDenseData[dataPos[0]].data.data(), sizeof(real) * totalDim); } - ICpuGpuVector::resizeOrCreate( - cpuArguments[slot].sequenceStartPositions, - size + 1, /* size == 1 currently */ - /* useGpu= */ false); + ICpuGpuVector::resizeOrCreate(cpuArguments[slot].sequenceStartPositions, + size + 1, /* size == 1 currently */ + /* useGpu= */ false); int* bufStarts = cpuArguments[slot].sequenceStartPositions->getMutableData(false); bufStarts[0] = 0; @@ -653,16 +679,17 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, case SlotDef::VAR_MDIM_INDEX: { CHECK_EQ(size, 1); size_t totalDim = slots_[slot].varIndices[dataPos[0]].size(); - IVector::resizeOrCreate(cpuArguments[slot].ids, totalDim, + IVector::resizeOrCreate(cpuArguments[slot].ids, + totalDim, /* useGpu= */ false); int* buf = cpuArguments[slot].ids->getData(); - memcpy(buf, slots_[slot].varIndices[dataPos[0]].data(), + memcpy(buf, + slots_[slot].varIndices[dataPos[0]].data(), sizeof(int) * totalDim); - ICpuGpuVector::resizeOrCreate( - cpuArguments[slot].sequenceStartPositions, - size + 1, /* size == 1 currently */ - /* useGpu= */ false); + ICpuGpuVector::resizeOrCreate(cpuArguments[slot].sequenceStartPositions, + size + 1, /* size == 1 currently */ + /* useGpu= */ false); int* bufStarts = cpuArguments[slot].sequenceStartPositions->getMutableData(false); bufStarts[0] = 0; @@ -700,8 +727,8 @@ int64_t ProtoDataProvider::getNextBatchInternal(int64_t size, gpuArguments[i].sequenceStartPositions = cpuArguments[i].sequenceStartPositions; } else { - gpuArguments[i].resizeAndCopyFrom(cpuArguments[i], useGpu_, - HPPL_STREAM_1); + gpuArguments[i].resizeAndCopyFrom( + cpuArguments[i], useGpu_, HPPL_STREAM_1); } } hl_stream_synchronize(HPPL_STREAM_1); @@ -746,10 +773,9 @@ int64_t ProtoSequenceDataProvider::getNextBatchInternal(int64_t size, sampleLoop(op, size); // current slot: sequenceStartPositions - ICpuGpuVector::resizeOrCreate( - cpuArguments[slot].sequenceStartPositions, - size + 1, - /* useGpu= */ false); + ICpuGpuVector::resizeOrCreate(cpuArguments[slot].sequenceStartPositions, + size + 1, + /* useGpu= */ false); switch (slotType) { case SlotDef::VECTOR_SPARSE_VALUE: @@ -821,10 +847,10 @@ int64_t ProtoSequenceDataProvider::getNextBatchInternal(int64_t size, }; int subSize = subSampleLoop(op, size, slot); ICpuGpuVector::resizeOrCreate( - cpuArguments[slot].subSequenceStartPositions, subSize + 1, - false); + cpuArguments[slot].subSequenceStartPositions, subSize + 1, false); int* currPosOfArgumentSubSeqStart = - cpuArguments[slot].subSequenceStartPositions->getMutableData(false); + cpuArguments[slot].subSequenceStartPositions->getMutableData( + false); int64_t* subSeqs = dataSubPos.data(); int64_t* subIndexs = slots_[slot].subIndices.data(); int allSubSequenceLength = 0; @@ -849,7 +875,8 @@ int64_t ProtoSequenceDataProvider::getNextBatchInternal(int64_t size, } case SlotDef::INDEX: { // label slot - IVector::resizeOrCreate(cpuArguments[slot].ids, size, + IVector::resizeOrCreate(cpuArguments[slot].ids, + size, /* useGpu= */ false); // fill labels int* buf = cpuArguments[slot].ids->getData(); @@ -863,7 +890,9 @@ int64_t ProtoSequenceDataProvider::getNextBatchInternal(int64_t size, case SlotDef::VECTOR_DENSE: { // copy values size_t dim = header_.slot_defs(slot).dim(); - Matrix::resizeOrCreate(cpuArguments[slot].value, size, dim, + Matrix::resizeOrCreate(cpuArguments[slot].value, + size, + dim, false, // trans = false false); // useGpu = false real* buf = cpuArguments[slot].value->getData(); @@ -887,8 +916,8 @@ int64_t ProtoSequenceDataProvider::getNextBatchInternal(int64_t size, gpuArguments.resize(cpuArguments.size()); gpuBatch.setSize(size); for (size_t i = 0; i < cpuArguments.size(); ++i) { - gpuArguments[i].resizeAndCopyFrom(cpuArguments[i], useGpu_, - HPPL_STREAM_1); + gpuArguments[i].resizeAndCopyFrom( + cpuArguments[i], useGpu_, HPPL_STREAM_1); } hl_stream_synchronize(HPPL_STREAM_1); *batch = gpuBatch; diff --git a/paddle/gserver/dataproviders/ProtoDataProvider.h b/paddle/gserver/dataproviders/ProtoDataProvider.h index 846dd7673a..ffdcc8fdc9 100644 --- a/paddle/gserver/dataproviders/ProtoDataProvider.h +++ b/paddle/gserver/dataproviders/ProtoDataProvider.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -48,7 +47,8 @@ namespace paddle { */ class ProtoDataProvider : public DataProvider { public: - ProtoDataProvider(const DataConfig& config, bool useGpu, + ProtoDataProvider(const DataConfig& config, + bool useGpu, bool loadDataAll = true); virtual void reset(); @@ -161,14 +161,16 @@ protected: }; /** - * @brief Special use for Proto data: instances should contain sparse-non-value slots + * @brief Special use for Proto data: instances should contain sparse-non-value + * slots * and label. * * @note ProtoSequenceDataProvider treats each SPARSE SLOT as a SEQUENCE */ class ProtoSequenceDataProvider : public ProtoDataProvider { public: - ProtoSequenceDataProvider(const DataConfig& config, bool useGpu, + ProtoSequenceDataProvider(const DataConfig& config, + bool useGpu, bool loadDataAll = true); ~ProtoSequenceDataProvider() {} virtual int64_t getNextBatchInternal(int64_t size, DataBatch* batch); diff --git a/paddle/gserver/dataproviders/ProtoReader.h b/paddle/gserver/dataproviders/ProtoReader.h index 3b1eb7e9ef..b8fca3cd7f 100644 --- a/paddle/gserver/dataproviders/ProtoReader.h +++ b/paddle/gserver/dataproviders/ProtoReader.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -138,7 +137,8 @@ protected: * * @note this code depends on protobuf 2.4.0. There is nothing like * CodedInputStream::CurrentPosition() in protobuf 2.5.0 to tell us how many - * bytes has the object readed so far. Therefore, we calculated bytes ourselves. + * bytes has the object readed so far. Therefore, we calculated bytes + * ourselves. */ int approximateReadedBytes_; }; diff --git a/paddle/gserver/dataproviders/PyDataProvider.cpp b/paddle/gserver/dataproviders/PyDataProvider.cpp index 1332c0ab63..bee6ca14a2 100644 --- a/paddle/gserver/dataproviders/PyDataProvider.cpp +++ b/paddle/gserver/dataproviders/PyDataProvider.cpp @@ -12,21 +12,20 @@ WITHOUT 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 "PyDataProvider.h" #include "paddle/utils/PythonUtil.h" #include #include "paddle/utils/Util.h" #include "paddle/utils/Excepts.h" - namespace paddle { #ifndef PADDLE_NO_PYTHON REGISTER_DATA_PROVIDER(py, PyDataProvider); #endif -PyDataProvider::PyDataProvider(const DataConfig& config, bool useGpu, +PyDataProvider::PyDataProvider(const DataConfig& config, + bool useGpu, bool loadDataAll) : DataProvider(config, useGpu), batchSize_(0) { PyGuard guard; @@ -50,8 +49,8 @@ void PyDataProvider::loadData(const std::vector& fileList) { classInstance_ = createPythonClass(pyModuleName_, pyClassName_, fileList, pyUserArgs_); CHECK(classInstance_) << "Create class instance failed."; - PyObjectPtr obj(PyObject_CallMethod(classInstance_.get(), - const_cast("getHeader"), NULL)); + PyObjectPtr obj(PyObject_CallMethod( + classInstance_.get(), const_cast("getHeader"), NULL)); CHECK_PY(obj) << "Call function getHeader failed."; std::string headerInfo = std::string(PyString_AsString(obj.get()), PyString_Size(obj.get())); @@ -90,7 +89,8 @@ void PyDataProvider::resetSlots() { } } -void PyDataProvider::fillDenseSlot(ProtoSlot& slot, char*& data, +void PyDataProvider::fillDenseSlot(ProtoSlot& slot, + char*& data, const char* dataEnd) { unsigned int dim = slot.dim; slot.sampleNum = readT(data, dataEnd); @@ -102,14 +102,17 @@ void PyDataProvider::fillDenseSlot(ProtoSlot& slot, char*& data, float* dat = reinterpret_cast(data); std::copy(dat, dat + slot.sampleNum * dim, slot.denseData.begin()); #else - memcpyWithCheck(slot.denseData.data(), data, - sizeof(real) * dim * slot.sampleNum, dataEnd); + memcpyWithCheck(slot.denseData.data(), + data, + sizeof(real) * dim * slot.sampleNum, + dataEnd); #endif // PyDataProvider always provide data in float data += sizeof(float) * dim * slot.sampleNum; } -void PyDataProvider::fillSparseNonValueSlot(ProtoSlot& slot, char*& data, +void PyDataProvider::fillSparseNonValueSlot(ProtoSlot& slot, + char*& data, const char* dataEnd) { slot.sampleNum = readT(data, dataEnd); unsigned int* indexPtr = (unsigned int*)data; @@ -121,12 +124,15 @@ void PyDataProvider::fillSparseNonValueSlot(ProtoSlot& slot, char*& data, length = readT(data, dataEnd); slot.indices.push_back(length); slot.sparseNonValueData.resize(length); - memcpyWithCheck(slot.sparseNonValueData.data(), data, - sizeof(unsigned int) * length, dataEnd); + memcpyWithCheck(slot.sparseNonValueData.data(), + data, + sizeof(unsigned int) * length, + dataEnd); data += sizeof(unsigned int) * length; } -void PyDataProvider::fillSparseValueSlot(ProtoSlot& slot, char*& data, +void PyDataProvider::fillSparseValueSlot(ProtoSlot& slot, + char*& data, const char* dataEnd) { slot.sampleNum = readT(data, dataEnd); unsigned int* indexPtr = (unsigned int*)data; @@ -153,7 +159,8 @@ void PyDataProvider::fillSparseValueSlot(ProtoSlot& slot, char*& data, } } -void PyDataProvider::fillIndexSlot(ProtoSlot& slot, char*& data, +void PyDataProvider::fillIndexSlot(ProtoSlot& slot, + char*& data, const char* dataEnd) { slot.sampleNum = readT(data, dataEnd); CHECK_LE(data + sizeof(unsigned int) * slot.sampleNum, dataEnd) @@ -163,7 +170,8 @@ void PyDataProvider::fillIndexSlot(ProtoSlot& slot, char*& data, data += sizeof(unsigned int) * slot.sampleNum; } -void PyDataProvider::fillStringSlot(ProtoSlot& slot, char*& data, +void PyDataProvider::fillStringSlot(ProtoSlot& slot, + char*& data, const char* dataEnd) { slot.sampleNum = readT(data, dataEnd); for (unsigned int i = 0; i < slot.sampleNum; ++i) { @@ -225,9 +233,8 @@ void PyDataProvider::fillSlotsByStr(const std::string& samples) { } for (size_t i = 0; i < sequenceNum; ++i) { size_t begin = slot.sequenceStartPositions[i]; - size_t end = (i < sequenceNum - 1) - ? slot.sequenceStartPositions[i + 1] - : slot.sampleNum; + size_t end = (i < sequenceNum - 1) ? slot.sequenceStartPositions[i + 1] + : slot.sampleNum; for (size_t ii = begin; ii < end; ++ii) { slot.sampleSequenceIdVec.push_back(ii); } @@ -255,8 +262,8 @@ void PyDataProvider::fillSlotsByStr(const std::string& samples) { void PyDataProvider::reset() { { // Invoke PyDataProvider Reset PyGuard guard; - PyObjectPtr obj(PyObject_CallMethod(classInstance_.get(), - const_cast("reset"), NULL)); + PyObjectPtr obj(PyObject_CallMethod( + classInstance_.get(), const_cast("reset"), NULL)); CHECK_PY(obj) << "Call function reset failed."; } @@ -270,15 +277,18 @@ void PyDataProvider::reset() { void PyDataProvider::shuffle() { // py shuffle PyGuard guard; - PyObjectPtr obj(PyObject_CallMethod(classInstance_.get(), - const_cast("shuffle"), NULL)); + PyObjectPtr obj(PyObject_CallMethod( + classInstance_.get(), const_cast("shuffle"), NULL)); CHECK_PY(obj) << "Call function shuffle failed."; } -void PyDataProvider::handleDenseSlot(ProtoSlot& slot, size_t slotIndex, +void PyDataProvider::handleDenseSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments) { unsigned int dim = slot.dim; - Matrix::resizeOrCreate(cpuArguments[slotIndex].value, slot.sampleNum, dim, + Matrix::resizeOrCreate(cpuArguments[slotIndex].value, + slot.sampleNum, + dim, false, // trans = false false); // useGpu = false real* buf = cpuArguments[slotIndex].value->getData(); @@ -294,19 +304,27 @@ void PyDataProvider::handleSparseNonValueSlot( ProtoSlot& slot, size_t slotIndex, std::vector& cpuArguments) { unsigned int dim = slot.dim; if (!(cpuArguments[slotIndex].value)) { - cpuArguments[slotIndex].value = Matrix::createSparseMatrix( - slot.sampleNum, dim, slot.sampleNum /*DEFAULT_AVG_WIDTH = 1*/, NO_VALUE, - SPARSE_CSR, false, useGpu_); + cpuArguments[slotIndex].value = + Matrix::createSparseMatrix(slot.sampleNum, + dim, + slot.sampleNum /*DEFAULT_AVG_WIDTH = 1*/, + NO_VALUE, + SPARSE_CSR, + false, + useGpu_); } auto mat = cpuArguments[slotIndex].value; mat->resize(slot.sampleNum, dim, slot.sampleNum, NO_VALUE, SPARSE_CSR); if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(slot.sampleSequenceIdVec.data(), slot.indices.data(), - slot.sparseNonValueData.data(), HPPL_STREAM_1); + ->copyFrom(slot.sampleSequenceIdVec.data(), + slot.indices.data(), + slot.sparseNonValueData.data(), + HPPL_STREAM_1); } else if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(slot.sampleSequenceIdVec.data(), slot.indices.data(), + ->copyFrom(slot.sampleSequenceIdVec.data(), + slot.indices.data(), slot.sparseNonValueData.data()); } else { LOG(FATAL) << "Not Supported"; @@ -317,28 +335,38 @@ void PyDataProvider::handleSparseValueSlot( ProtoSlot& slot, size_t slotIndex, std::vector& cpuArguments) { unsigned int dim = slot.dim; if (!(cpuArguments[slotIndex].value)) { - cpuArguments[slotIndex].value = Matrix::createSparseMatrix( - slot.sampleNum, dim, slot.sampleNum /*DEFAULT_AVG_WIDTH = 1*/, - FLOAT_VALUE, SPARSE_CSR, false, useGpu_); + cpuArguments[slotIndex].value = + Matrix::createSparseMatrix(slot.sampleNum, + dim, + slot.sampleNum /*DEFAULT_AVG_WIDTH = 1*/, + FLOAT_VALUE, + SPARSE_CSR, + false, + useGpu_); } auto mat = cpuArguments[slotIndex].value; mat->resize(slot.sampleNum, dim, slot.sampleNum, FLOAT_VALUE, SPARSE_CSR); if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(slot.sampleSequenceIdVec.data(), slot.indices.data(), - slot.sparseFloatValueData.data(), HPPL_STREAM_DEFAULT); + ->copyFrom(slot.sampleSequenceIdVec.data(), + slot.indices.data(), + slot.sparseFloatValueData.data(), + HPPL_STREAM_DEFAULT); } else if (std::dynamic_pointer_cast(mat)) { std::dynamic_pointer_cast(mat) - ->copyFrom(slot.sampleSequenceIdVec.data(), slot.indices.data(), + ->copyFrom(slot.sampleSequenceIdVec.data(), + slot.indices.data(), slot.sparseFloatValueData.data()); } else { LOG(FATAL) << "Not Supported"; } } -void PyDataProvider::handleIndexSlot(ProtoSlot& slot, size_t slotIndex, +void PyDataProvider::handleIndexSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments) { - IVector::resizeOrCreate(cpuArguments[slotIndex].ids, slot.sampleNum, + IVector::resizeOrCreate(cpuArguments[slotIndex].ids, + slot.sampleNum, /*useGpu_*/ false); int* buf = cpuArguments[slotIndex].ids->getData(); for (size_t i = 0; i < slot.sampleNum; ++i) { @@ -346,7 +374,8 @@ void PyDataProvider::handleIndexSlot(ProtoSlot& slot, size_t slotIndex, } } -void PyDataProvider::handleStringSlot(ProtoSlot& slot, size_t slotIndex, +void PyDataProvider::handleStringSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments) { if (cpuArguments[slotIndex].strs) { cpuArguments[slotIndex].strs->resize(slot.sampleNum); @@ -364,7 +393,8 @@ int64_t PyDataProvider::getNextBatchInternal(int64_t size, DataBatch* batch) { PyGuard guard; PyObjectPtr obj(PyObject_CallMethod(classInstance_.get(), const_cast("getNextBatch"), - const_cast("i"), size)); + const_cast("i"), + size)); CHECK_PY(obj) << "Call function getNextBatch failed."; const std::string& samples = std::string(PyString_AsString(obj.get()), PyString_Size(obj.get())); @@ -381,23 +411,24 @@ int64_t PyDataProvider::getNextBatchInternal(int64_t size, DataBatch* batch) { if (!iidData()) { for (size_t j = 0; j < slotNum_; ++j) { auto& slot = slots_[j]; - ICpuGpuVector::resizeOrCreate( - cpuArguments[j].sequenceStartPositions, - slot.sequenceNum + 1, /* useGpu= */ false); + ICpuGpuVector::resizeOrCreate(cpuArguments[j].sequenceStartPositions, + slot.sequenceNum + 1, + /* useGpu= */ false); int* buf = cpuArguments[j].sequenceStartPositions->getMutableData(false); std::copy(slot.sequenceStartPositions.begin(), - slot.sequenceStartPositions.end(), buf); + slot.sequenceStartPositions.end(), + buf); buf[slot.sequenceStartPositions.size()] = slot.sampleNum; if (slot.subSequenceStartPositions.size()) { - ICpuGpuVector::resizeOrCreate( - cpuArguments[j].subSequenceStartPositions, - slot.subSequenceNum + 1, - /* useGpu= */ false); + ICpuGpuVector::resizeOrCreate(cpuArguments[j].subSequenceStartPositions, + slot.subSequenceNum + 1, + /* useGpu= */ false); int* buf = - cpuArguments[j].subSequenceStartPositions->getMutableData(false); + cpuArguments[j].subSequenceStartPositions->getMutableData(false); std::copy(slot.subSequenceStartPositions.begin(), - slot.subSequenceStartPositions.end(), buf); + slot.subSequenceStartPositions.end(), + buf); buf[slot.subSequenceNum] = slot.sampleNum; // check subSequenceStartPositions and sequenceStartPositions cpuArguments[j].checkSubset(); @@ -452,8 +483,8 @@ int64_t PyDataProvider::getNextBatchInternal(int64_t size, DataBatch* batch) { cpuArguments[i].subSequenceStartPositions; } } else { - gpuArguments[i].resizeAndCopyFrom(cpuArguments[i], useGpu_, - HPPL_STREAM_1); + gpuArguments[i].resizeAndCopyFrom( + cpuArguments[i], useGpu_, HPPL_STREAM_1); } } hl_stream_synchronize(HPPL_STREAM_1); diff --git a/paddle/gserver/dataproviders/PyDataProvider.h b/paddle/gserver/dataproviders/PyDataProvider.h index 939d9cf725..6bb7c831fd 100644 --- a/paddle/gserver/dataproviders/PyDataProvider.h +++ b/paddle/gserver/dataproviders/PyDataProvider.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -25,7 +24,8 @@ namespace paddle { class PyDataProvider : public DataProvider { public: - PyDataProvider(const DataConfig& config, bool useGpu, + PyDataProvider(const DataConfig& config, + bool useGpu, bool loadDataAll = true); virtual void reset(); @@ -48,21 +48,27 @@ protected: void parseHeaderData(const std::string& headerData); void fillDenseSlot(ProtoSlot& slot, char*& data, const char* dataEnd); - void fillSparseNonValueSlot(ProtoSlot& slot, char*& data, + void fillSparseNonValueSlot(ProtoSlot& slot, + char*& data, const char* dataEnd); void fillSparseValueSlot(ProtoSlot& slot, char*& data, const char* dataEnd); void fillIndexSlot(ProtoSlot& slot, char*& data, const char* dataEnd); void fillStringSlot(ProtoSlot& slot, char*& data, const char* dataEnd); void fillSlotsByStr(const std::string& samples); - void handleDenseSlot(ProtoSlot& slot, size_t slotIndex, + void handleDenseSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments); - void handleSparseNonValueSlot(ProtoSlot& slot, size_t slotIndex, + void handleSparseNonValueSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments); - void handleSparseValueSlot(ProtoSlot& slot, size_t slotIndex, + void handleSparseValueSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments); - void handleIndexSlot(ProtoSlot& slot, size_t slotIndex, + void handleIndexSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments); - void handleStringSlot(ProtoSlot& slot, size_t slotIndex, + void handleStringSlot(ProtoSlot& slot, + size_t slotIndex, std::vector& cpuArguments); void resetSlots(); void loadData(const std::vector& fileList); diff --git a/paddle/gserver/dataproviders/PyDataProvider2.cpp b/paddle/gserver/dataproviders/PyDataProvider2.cpp index 90391a7c30..967fc9026a 100644 --- a/paddle/gserver/dataproviders/PyDataProvider2.cpp +++ b/paddle/gserver/dataproviders/PyDataProvider2.cpp @@ -34,7 +34,7 @@ namespace paddle { namespace unittest { static std::unique_ptr> - OnPoolFilled; + OnPoolFilled; namespace pydp2 { @@ -43,15 +43,11 @@ void setOnPoolFilledHook(const std::function& callback) { *OnPoolFilled = callback; } -void clearOnPoolFilledHook() { - OnPoolFilled.reset(); -} +void clearOnPoolFilledHook() { OnPoolFilled.reset(); } } // namespace pydp2 } // namespace unittest - - /** * Slot type */ @@ -65,17 +61,13 @@ enum SlotType { /** * Sequence type */ -enum SeqType { - SQT_NONE = 0, - SQT_SEQ, - SQT_SUBSEQ -}; +enum SeqType { SQT_NONE = 0, SQT_SEQ, SQT_SUBSEQ }; /** * Cache Type. */ enum CacheType { - NO_CACHE = 0, // Each pass will load data from PyDataProvider2. + NO_CACHE = 0, // Each pass will load data from PyDataProvider2. CACHE_PASS_IN_MEM = 1, // First pass will load data from PyDataProvider2, // then cache all data in memory. Load data from // memory in rest passes. @@ -87,8 +79,8 @@ struct SlotHeader { // Slot Header will parse from python object's slots field. SeqType seqType; }; -inline std::ostream& operator << (std::ostream& os, const SlotHeader& header) { - os <<"Dim = " << header.dim << " Type = " << header.slotType +inline std::ostream& operator<<(std::ostream& os, const SlotHeader& header) { + os << "Dim = " << header.dim << " Type = " << header.slotType << " SeqType = " << header.seqType; return os; } @@ -158,7 +150,6 @@ protected: SlotHeader* headerPtr_; }; - /** * Py Data Provider Cache Interface. */ @@ -209,17 +200,13 @@ public: PyDataProvider2(const DataConfig& config, const ModelConfig& modelConfig, bool useGpu) - :DataProvider(config, useGpu), - callingContextCreated_(2) { - if (PyArray_API == NULL) - import_array(); + : DataProvider(config, useGpu), callingContextCreated_(2) { + if (PyArray_API == NULL) import_array(); auto& args = config.load_data_args(); PyObjectPtr kwargs = PyObjectPtr(PyDict_New()); if (!args.empty()) { kwargs = callPythonFuncRetPyObj( - "paddle.trainer.PyDataProvider2", - "deserialize_args", - {args}); + "paddle.trainer.PyDataProvider2", "deserialize_args", {args}); } py::DictHelper kwargsDict(kwargs); @@ -245,40 +232,38 @@ public: * Dtor * @note will stop loading thread when destructing */ - virtual ~PyDataProvider2() { - resetImpl(false); - } + virtual ~PyDataProvider2() { resetImpl(false); } private: void createPyDataObj(const std::string& model, const std::string& className, const std::string& fileListName, - PyObjectPtr && kwargs) { - LOG(INFO) << "loading dataprovider " << model <<"::" << className; + PyObjectPtr&& kwargs // NOLINT + ) { + LOG(INFO) << "loading dataprovider " << model << "::" << className; PyObjectPtr module = py::import(model); PyObjectPtr moduleDict(PyModule_GetDict(module.get())); CHECK_PY(moduleDict) << "Invoke module.__dict__ error"; - PyObjectPtr cls(PyDict_GetItemString(moduleDict.get(), - className.c_str())); + PyObjectPtr cls(PyDict_GetItemString(moduleDict.get(), className.c_str())); CHECK_PY(cls) << "load class " << className.c_str() << "error"; // If there are multiple python instance share same module, the PyObjectPtr // only for instance will make python reference-count error. // // So here, we increase reference count manually. - if (gModuleClsPtrs_.find((uintptr_t) module.get()) - != gModuleClsPtrs_.end()) { + if (gModuleClsPtrs_.find((uintptr_t)module.get()) != + gModuleClsPtrs_.end()) { // Multi instance use same module Py_XINCREF(module.get()); Py_XINCREF(moduleDict.get()); } else { - gModuleClsPtrs_.insert((uintptr_t) module.get()); + gModuleClsPtrs_.insert((uintptr_t)module.get()); } - if (gModuleClsPtrs_.find((uintptr_t) cls.get()) != gModuleClsPtrs_.end()) { + if (gModuleClsPtrs_.find((uintptr_t)cls.get()) != gModuleClsPtrs_.end()) { Py_XINCREF(cls.get()); } else { - gModuleClsPtrs_.insert((uintptr_t) cls.get()); + gModuleClsPtrs_.insert((uintptr_t)cls.get()); } PyObjectPtr fileListInPy = loadPyFileLists(fileListName); @@ -294,8 +279,8 @@ private: py::ObjectHelper self(this->instance_); bool ok; - this->skipShuffle_ = !self.getBoolAttr("should_shuffle", - &ok /*isBoolType*/); + this->skipShuffle_ = + !self.getBoolAttr("should_shuffle", &ok /*isBoolType*/); if (!ok) { this->skipShuffle_ = testing; // shuffle when is training, skip shuffle // when is testing. @@ -335,12 +320,12 @@ private: PyObjectPtr headerPtrWrap(hdPtr); py::ObjectHelper hd(headerPtrWrap); header.dim = hd.getIntAttrWithError("dim"); - header.seqType = (SeqType) hd.getIntAttrWithError("seq_type"); - header.slotType = (SlotType) hd.getIntAttrWithError("type"); + header.seqType = (SeqType)hd.getIntAttrWithError("seq_type"); + header.slotType = (SlotType)hd.getIntAttrWithError("type"); } DBG << "Data header size " << headers_.size(); - for (auto & header : headers_) { + for (auto& header : headers_) { DBG << header; } cache_.reset(IPyDataProviderCache::create( @@ -351,8 +336,7 @@ private: loadFileList(fileListName, fileLists_); PyObject* lst = PyList_New(fileLists_.size()); for (size_t i = 0; i < fileLists_.size(); ++i) { - PyList_SET_ITEM(lst, i, - PyString_FromString(fileLists_[i].c_str())); + PyList_SET_ITEM(lst, i, PyString_FromString(fileLists_[i].c_str())); } return PyObjectPtr(lst); } @@ -414,11 +398,12 @@ private: CHECK(ok) << "CalcBatchSize must return int or long"; } - if (this->loadThread_){ // wait poolActualSize < poolSize; + if (this->loadThread_) { // wait poolActualSize < poolSize; std::unique_lock l(mtx_); - pushCV_.wait(l, [this, additionalBatchSize] { - return this->poolActualSize_ < poolSize_; - }); + pushCV_.wait(l, + [this, additionalBatchSize] { + return this->poolActualSize_ < poolSize_; + }); } { @@ -487,14 +472,14 @@ private: std::vector fileLists_; std::vector headers_; static PyObjectPtr zeroTuple_; - static std::unordered_set gModuleClsPtrs_; + static std::unordered_set gModuleClsPtrs_; class PositionRandom { public: - inline explicit PositionRandom(bool skipRand): - eng_(ThreadLocalRandomEngine::get()), skipRand_(skipRand) {} + inline explicit PositionRandom(bool skipRand) + : eng_(ThreadLocalRandomEngine::get()), skipRand_(skipRand) {} - inline size_t operator() (size_t len) { + inline size_t operator()(size_t len) { if (!skipRand_) { if (!dist_ || dist_->b() != len - 1) { dist_.reset(new std::uniform_int_distribution(0, len - 1)); @@ -525,32 +510,31 @@ public: * Shuffle. Do nothing because PyDataProvider do shuffle implicitly by random * select data from datapool. */ - void shuffle() { - } + void shuffle() {} /** * Not limited size. */ - int64_t getSize() { - return -1; - } + int64_t getSize() { return -1; } /** * Loading a batch of data. */ - int64_t getNextBatchInternal(int64_t size_, DataBatch *batch) { + int64_t getNextBatchInternal(int64_t size_, DataBatch* batch) { std::lock_guard guard(mutexForReset_); REGISTER_TIMER("PyDP2.getNextBatchInternal") CHECK_GE(size_, 0); - size_t size = (size_t) size_; + size_t size = (size_t)size_; if (loadThread_) { // loading from thread should wait for data pool ready. // but, loading from cache, cache object should ensure // data pool ready. std::unique_lock l(mtx_); - pullCV_.wait(l, [this, &size] { - return this->poolActualSize_ >= std::max(size, this->minPoolSize_) - || callingContexts_.empty(); - }); + pullCV_.wait(l, + [this, &size] { + return this->poolActualSize_ >= + std::max(size, this->minPoolSize_) || + callingContexts_.empty(); + }); if (unittest::OnPoolFilled) { (*unittest::OnPoolFilled)(this->poolActualSize_); @@ -633,35 +617,35 @@ public: cpuBatch.setSize(bsize); auto& inArgs = cpuBatch.getStreams(); inArgs.resize(headers_.size()); - std::vector > scanners; + std::vector> scanners; scanners.reserve(headers_.size()); for (auto& header : headers_) { scanners.emplace_back(IFieldScanner::create(&header)); } DBG << "Scanner created."; - for (size_t i=0; i < headers_.size(); ++i) { + for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->startPrepare(inArgs[i]); } - for (auto & d : data) { + for (auto& d : data) { py::SequenceHelper s(d); - for (size_t i=0; i < headers_.size(); ++i) { + for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->prepare(inArgs[i], s[i]); } } - for (size_t i=0; i < headers_.size(); ++i) { + for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->finishPrepare(inArgs[i]); } - for (size_t i=0; i < headers_.size(); ++i) { + for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->startFill(inArgs[i]); } - for (auto & d : data) { + for (auto& d : data) { py::SequenceHelper s(d); for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->fill(inArgs[i], s[i]); } } - for (size_t i=0; i < headers_.size(); ++i) { + for (size_t i = 0; i < headers_.size(); ++i) { scanners[i]->finishFill(inArgs[i]); } @@ -679,8 +663,8 @@ public: gpuArguments.resize(cpuArguments.size()); gpuBatch.setSize(size); for (size_t i = 0; i < headers_.size(); ++i) { - gpuArguments[i].resizeAndCopyFrom(cpuArguments[i], useGpu_, - HPPL_STREAM_1); + gpuArguments[i].resizeAndCopyFrom( + cpuArguments[i], useGpu_, HPPL_STREAM_1); } hl_stream_synchronize(HPPL_STREAM_1); } else { @@ -690,31 +674,28 @@ public: } }; -std::unordered_set PyDataProvider2::gModuleClsPtrs_; +std::unordered_set PyDataProvider2::gModuleClsPtrs_; PyObjectPtr PyDataProvider2::zeroTuple_(PyTuple_New(0)); REGISTER_DATA_PROVIDER_EX(py2, PyDataProvider2); - /** * Scanner for dense slot. */ -class DenseScanner: public IFieldScanner { +class DenseScanner : public IFieldScanner { public: - explicit DenseScanner(SlotHeader* ptr):IFieldScanner(ptr), height_(0) {} + explicit DenseScanner(SlotHeader* ptr) : IFieldScanner(ptr), height_(0) {} /** * Prepare. * @param argument target argument * @param obj each timestep of a sample. */ - virtual void prepare(Argument &argument, PyObject *obj) { - ++height_; - } + virtual void prepare(Argument& argument, PyObject* obj) { ++height_; } - virtual void finishPrepare(Argument &argument) { - Matrix::resizeOrCreate(argument.value, height_, headerPtr_->dim, - false, false); + virtual void finishPrepare(Argument& argument) { + Matrix::resizeOrCreate( + argument.value, height_, headerPtr_->dim, false, false); height_ = 0; } @@ -723,24 +704,23 @@ public: * @param argument * @param obj */ - virtual void fill(Argument &argument, PyObject *obj) { + virtual void fill(Argument& argument, PyObject* obj) { real* dat = argument.value->getData() + height_ * headerPtr_->dim; if (PyArray_Check(obj)) { - auto dtype = PyArray_DTYPE((PyArrayObject*)obj); - if (dtype->type == 'f' && dtype->elsize == sizeof(real)) { - real * data = (real*)PyArray_DATA((PyArrayObject*)obj); - auto sz = PyArray_SIZE((PyArrayObject*)obj); - std::copy(data, data + sz, dat); - } else { - LOG(FATAL) << "You should yield float" << sizeof(real) * 8 - << " array"; - } - } else { - py::SequenceHelper s(obj); - // TODO(yuyang18): Here we can use AVX or SSE to accelerate memory copy. - for (size_t i=0; i < headerPtr_->dim; ++i) { - dat[i] = (real) s.getDouble(i); - } + auto dtype = PyArray_DTYPE((PyArrayObject*)obj); + if (dtype->type == 'f' && dtype->elsize == sizeof(real)) { + real* data = (real*)PyArray_DATA((PyArrayObject*)obj); + auto sz = PyArray_SIZE((PyArrayObject*)obj); + std::copy(data, data + sz, dat); + } else { + LOG(FATAL) << "You should yield float" << sizeof(real) * 8 << " array"; + } + } else { + py::SequenceHelper s(obj); + // TODO(yuyang18): Here we can use AVX or SSE to accelerate memory copy. + for (size_t i = 0; i < headerPtr_->dim; ++i) { + dat[i] = (real)s.getDouble(i); + } } ++height_; } @@ -752,20 +732,18 @@ private: /** * Scanner for index slot */ -class IndexScanner: public IFieldScanner { +class IndexScanner : public IFieldScanner { public: - explicit IndexScanner(SlotHeader* ptr):IFieldScanner(ptr), cnt_(0) {} + explicit IndexScanner(SlotHeader* ptr) : IFieldScanner(ptr), cnt_(0) {} /** * Prepare memory space. * * @note obj is a single timestep of sample */ - virtual void prepare(Argument &argument, PyObject *obj) { - ++cnt_; - } + virtual void prepare(Argument& argument, PyObject* obj) { ++cnt_; } - virtual void finishPrepare(Argument &argument) { + virtual void finishPrepare(Argument& argument) { IVector::resizeOrCreate(argument.ids, cnt_, false); cnt_ = 0; } @@ -773,9 +751,9 @@ public: /** * Fill one index to argument. */ - virtual void fill(Argument &argument, PyObject *obj) { + virtual void fill(Argument& argument, PyObject* obj) { bool ok; - argument.ids->getData()[cnt_++] = py::castInt(obj, &ok); + argument.ids->getData()[cnt_++] = py::castInt(obj, &ok); CHECK(ok) << "Cannot cast int " << py::repr(obj); } @@ -785,27 +763,25 @@ private: class SparseNonValueScanner : public IFieldScanner { public: - explicit SparseNonValueScanner(SlotHeader* ptr): IFieldScanner(ptr), - nnz_(0), - height_(0) {} + explicit SparseNonValueScanner(SlotHeader* ptr) + : IFieldScanner(ptr), nnz_(0), height_(0) {} /** * Prepare memory space * @note obj is a timestep of one sample. */ - virtual void prepare(Argument &argument, PyObject *obj) { + virtual void prepare(Argument& argument, PyObject* obj) { ++height_; nnz_ += py::SequenceHelper(obj).size(); } - virtual void finishPrepare(Argument &argument) { - Matrix::resizeOrCreateSparseMatrix(argument.value, height_, - headerPtr_->dim, - nnz_, NO_VALUE); + virtual void finishPrepare(Argument& argument) { + Matrix::resizeOrCreateSparseMatrix( + argument.value, height_, headerPtr_->dim, nnz_, NO_VALUE); } - virtual void startFill(Argument & argument) { - auto smat = (CpuSparseMatrix*) (argument.value.get()); + virtual void startFill(Argument& argument) { + auto smat = (CpuSparseMatrix*)(argument.value.get()); smat->getRows()[0] = 0; nnz_ = 0; height_ = 1; @@ -818,14 +794,14 @@ public: virtual void fill(Argument& argument, PyObject* obj) { py::SequenceHelper s(obj); auto sz = s.size(); - auto smat = (CpuSparseMatrix*) (argument.value.get()); + auto smat = (CpuSparseMatrix*)(argument.value.get()); int* row = smat->getRows(); int* col = smat->getCols(); real* dat = smat->getData(); - row[height_] = row[height_-1] + (int)sz; + row[height_] = row[height_ - 1] + (int)sz; for (decltype(sz) i = 0; i < sz; ++i) { - setData(col+nnz_, dat+nnz_, s[i]); + setData(col + nnz_, dat + nnz_, s[i]); ++nnz_; } ++height_; @@ -839,7 +815,7 @@ protected: * @param [in] obj Python Object. For sparse_non_value is a PyInt or PyLong. * For sparse_value is a Tuple (int, float). */ - virtual void setData(int* col, real * dat, PyObject* obj) { + virtual void setData(int* col, real* dat, PyObject* obj) { bool ok; *col = py::castInt(obj, &ok); CHECK(ok); @@ -851,26 +827,25 @@ protected: class SparseValueScanner : public SparseNonValueScanner { public: - explicit SparseValueScanner(SlotHeader *ptr) : SparseNonValueScanner(ptr) {} + explicit SparseValueScanner(SlotHeader* ptr) : SparseNonValueScanner(ptr) {} - virtual void finishPrepare(Argument &argument) { - Matrix::resizeOrCreateSparseMatrix(argument.value, height_, - headerPtr_->dim, - nnz_, FLOAT_VALUE); + virtual void finishPrepare(Argument& argument) { + Matrix::resizeOrCreateSparseMatrix( + argument.value, height_, headerPtr_->dim, nnz_, FLOAT_VALUE); } protected: - virtual void setData(int *col, real *dat, PyObject *obj) { + virtual void setData(int* col, real* dat, PyObject* obj) { py::SequenceHelper s(obj); SparseNonValueScanner::setData(col, dat, s[0]); - *dat = (real) s.getDouble(1); + *dat = (real)s.getDouble(1); } }; /** * Sequence Scanner. Scanner for sequence or sub-sequence. */ -class SequenceScanner: public IFieldScanner { +class SequenceScanner : public IFieldScanner { public: /** * Ctor @@ -879,15 +854,18 @@ public: * return a sequence start position or a sub-sequence * start position. */ - SequenceScanner(std::unique_ptr&& innerScanner, - const std::function& getSeqStartPos) - : IFieldScanner(nullptr), inner_(std::move(innerScanner)), - cnt_(0), getSeqStartPos_(getSeqStartPos) {} + SequenceScanner( + std::unique_ptr&& innerScanner, + const std::function& getSeqStartPos) + : IFieldScanner(nullptr), + inner_(std::move(innerScanner)), + cnt_(0), + getSeqStartPos_(getSeqStartPos) {} /** * Start prepare. Invoke inner->startPrepare too. */ - virtual void startPrepare(Argument &argument) { + virtual void startPrepare(Argument& argument) { inner_->startPrepare(argument); } @@ -895,10 +873,10 @@ public: * Prepare. obj is a list or tuple. it will invoke inner_->prepare for each * element of sequence obj. */ - virtual void prepare(Argument &argument, PyObject *obj) { + virtual void prepare(Argument& argument, PyObject* obj) { py::SequenceHelper s(obj); ++cnt_; - for (size_t i=0; i < s.size(); ++i) { + for (size_t i = 0; i < s.size(); ++i) { inner_->prepare(argument, s[i]); } } @@ -906,7 +884,7 @@ public: /** * Finish prepare. invoke inner_->finishPrepare too. */ - virtual void finishPrepare(Argument &argument) { + virtual void finishPrepare(Argument& argument) { ICpuGpuVector::resizeOrCreate(getSeqStartPos_(argument), cnt_ + 1, false); inner_->finishPrepare(argument); } @@ -914,7 +892,7 @@ public: /** * Start fill. invoke inner->startFill too. */ - virtual void startFill(Argument &argument) { + virtual void startFill(Argument& argument) { getSeqStartPos_(argument)->getMutableData(false)[0] = 0; cnt_ = 1; inner_->startFill(argument); @@ -925,13 +903,13 @@ public: * sequence obj. And set seqStartPos at same time. The seqStartPos will be * calculated by getSeqStartPos callback passed in ctor. */ - virtual void fill(Argument &argument, PyObject *obj) { + virtual void fill(Argument& argument, PyObject* obj) { getSeqStartPos_(argument)->getMutableData(false)[cnt_] = - getSeqStartPos_(argument)->getMutableData(false)[cnt_ - 1] + - (int)getSize(obj); + getSeqStartPos_(argument)->getMutableData(false)[cnt_ - 1] + + (int)getSize(obj); py::SequenceHelper s(obj); ++cnt_; - for (size_t i=0; i < s.size(); ++i) { + for (size_t i = 0; i < s.size(); ++i) { inner_->fill(argument, s[i]); } } @@ -939,9 +917,7 @@ public: /** * Finish fill. will invoke inner->finishFill too. */ - virtual void finishFill(Argument &argument) { - inner_->finishFill(argument); - } + virtual void finishFill(Argument& argument) { inner_->finishFill(argument); } protected: size_t getSize(PyObject* obj) { @@ -949,7 +925,7 @@ protected: auto sc = dynamic_cast(inner_.get()); if (sc) { size_t sum = 0; - for (size_t i=0; i < s.size(); ++i) { + for (size_t i = 0; i < s.size(); ++i) { sum += sc->getSize(s[i]); } return sum; @@ -964,8 +940,7 @@ private: std::function getSeqStartPos_; }; - -IFieldScanner* IFieldScanner::create(SlotHeader *header) { +IFieldScanner* IFieldScanner::create(SlotHeader* header) { IFieldScanner* retv = nullptr; switch (header->slotType) { case ST_DENSE: @@ -989,15 +964,15 @@ IFieldScanner* IFieldScanner::create(SlotHeader *header) { break; case SQT_SUBSEQ: retv = new SequenceScanner(std::unique_ptr(retv), - [](Argument& arg) -> ICpuGpuVectorPtr& { - return arg.subSequenceStartPositions; - }); - // fall through, not break; + [](Argument& arg) -> ICpuGpuVectorPtr& { + return arg.subSequenceStartPositions; + }); + // fall through, not break; case SQT_SEQ: retv = new SequenceScanner(std::unique_ptr(retv), - [](Argument& arg) -> ICpuGpuVectorPtr& { - return arg.sequenceStartPositions; - }); + [](Argument& arg) -> ICpuGpuVectorPtr& { + return arg.sequenceStartPositions; + }); break; default: LOG(FATAL) << "Not implemented"; @@ -1010,19 +985,13 @@ IFieldScanner* IFieldScanner::create(SlotHeader *header) { * No Cache Strategy. Will destruct old data immediately and load data from * python every pass. */ -class NoCacheStrategy: public IPyDataProviderCache { +class NoCacheStrategy : public IPyDataProviderCache { public: - virtual bool reset() { - return true; - } + virtual bool reset() { return true; } - virtual void drop(std::deque *data) { - data->clear(); - } + virtual void drop(std::deque* data) { data->clear(); } - virtual std::deque* load() { - return nullptr; - } + virtual std::deque* load() { return nullptr; } }; /** @@ -1033,9 +1002,9 @@ public: */ class CacheOnePassInMemory : public IPyDataProviderCache { public: - CacheOnePassInMemory() : objPool_(new std::deque()), - droppedPool_(new std::deque()) - {} + CacheOnePassInMemory() + : objPool_(new std::deque()), + droppedPool_(new std::deque()) {} virtual bool reset() { if (objPool_->empty() && droppedPool_->empty()) { @@ -1048,25 +1017,22 @@ public: } } - virtual void drop(std::deque *data) { + virtual void drop(std::deque* data) { size_t orgSize = droppedPool_->size(); droppedPool_->resize(orgSize + data->size()); - for (size_t i=0; i < data->size(); ++i) { + for (size_t i = 0; i < data->size(); ++i) { std::swap((*droppedPool_)[orgSize + i], (*data)[i]); } data->clear(); } - virtual std::deque* load() { - return objPool_.get(); - } + virtual std::deque* load() { return objPool_.get(); } private: - std::unique_ptr > objPool_; - std::unique_ptr > droppedPool_; + std::unique_ptr> objPool_; + std::unique_ptr> droppedPool_; }; - IPyDataProviderCache* IPyDataProviderCache::create(CacheType ct) { switch (ct) { case NO_CACHE: diff --git a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp index c2625bce9a..8f7d2fb80e 100644 --- a/paddle/gserver/evaluators/CTCErrorEvaluator.cpp +++ b/paddle/gserver/evaluators/CTCErrorEvaluator.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "Evaluator.h" #include "paddle/gserver/gradientmachines/NeuralNetwork.h" @@ -33,7 +32,8 @@ private: str.clear(); int prevLabel = -1; for (std::vector::const_iterator label = path.begin(); - label != path.end(); label++) { + label != path.end(); + label++) { if (*label != blank_ && (str.empty() || *label != str.back() || prevLabel == blank_)) { str.push_back(*label); @@ -58,8 +58,11 @@ private: /* "sp, dp, ip" is the weighting parameter of "substitution, deletion, * insertion" * in edit-distance error */ - real stringAlignment(std::vector& gtStr, std::vector& recogStr, - bool backtrace = true, real sp = 1.0, real dp = 1.0, + real stringAlignment(std::vector& gtStr, + std::vector& recogStr, + bool backtrace = true, + real sp = 1.0, + real dp = 1.0, real ip = 1.0) { std::vector> matrix; int substitutions, deletions, insertions; @@ -165,8 +168,8 @@ private: return distance / maxLen; } - real editDistance(real* output, int numTimes, int numClasses, int* labels, - int labelsLen) { + real editDistance( + real* output, int numTimes, int numClasses, int* labels, int labelsLen) { numTimes_ = numTimes; numClasses_ = numClasses; blank_ = numClasses_ - 1; @@ -207,7 +210,8 @@ public: real err = 0; err = editDistance( output.value->getData() + output.value->getWidth() * outputStarts[i], - outputStarts[i+1] - outputStarts[i], output.value->getWidth(), + outputStarts[i + 1] - outputStarts[i], + output.value->getWidth(), label.ids->getData() + labelStarts[i], labelStarts[i + 1] - labelStarts[i]); diff --git a/paddle/gserver/evaluators/ChunkEvaluator.cpp b/paddle/gserver/evaluators/ChunkEvaluator.cpp index 6f5d2b47c3..923e77fc9d 100644 --- a/paddle/gserver/evaluators/ChunkEvaluator.cpp +++ b/paddle/gserver/evaluators/ChunkEvaluator.cpp @@ -144,7 +144,8 @@ public: size_t numSequences = sequenceStartPositions->getSize() - 1; const int* starts = sequenceStartPositions->getData(); for (size_t i = 0; i < numSequences; ++i) { - eval1(output->getData() + starts[i], label->getData() + starts[i], + eval1(output->getData() + starts[i], + label->getData() + starts[i], starts[i + 1] - starts[i]); } return 0; diff --git a/paddle/gserver/evaluators/Evaluator.cpp b/paddle/gserver/evaluators/Evaluator.cpp index d43dceea74..f5df2b18de 100644 --- a/paddle/gserver/evaluators/Evaluator.cpp +++ b/paddle/gserver/evaluators/Evaluator.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "paddle/gserver/evaluators/Evaluator.h" @@ -74,17 +73,19 @@ public: } const MatrixPtr errorMat = Matrix::create(output->getHeight(), - 1, /* trans= */ false, useGpu(arguments[0].deviceId)); + 1, + /* trans= */ false, + useGpu(arguments[0].deviceId)); errorMat->zeroMem(); if (label != nullptr) { errorMat->classificationError(output, label); } else if (dynamic_cast(multiBinaryLabel.get()) || dynamic_cast(multiBinaryLabel.get())) { - errorMat->classificationErrorMulti(*output, *multiBinaryLabel, - config_.classification_threshold()); + errorMat->classificationErrorMulti( + *output, *multiBinaryLabel, config_.classification_threshold()); } else { - errorMat->binaryClassificationError(0, *output, *multiBinaryLabel, - config_.classification_threshold()); + errorMat->binaryClassificationError( + 0, *output, *multiBinaryLabel, config_.classification_threshold()); } if (supportWeight) { @@ -126,8 +127,8 @@ public: int errCounter = 0; CpuVector errorVec(0, nullptr); for (size_t i = 0; i < sequenceStartPositions->getSize() - 1; ++i) { - errorVec.subVecFrom(errorMat->getData(), starts[i], - starts[i + 1] - starts[i]); + errorVec.subVecFrom( + errorMat->getData(), starts[i], starts[i + 1] - starts[i]); if (errorVec.getSum() > 0) { errCounter += 1; } @@ -330,8 +331,8 @@ public: } void distributeEval(ParameterClient2* client) { - client->reduce(sum_->getData(), sum_->getData(), colNum_, FLAGS_trainer_id, - 0); + client->reduce( + sum_->getData(), sum_->getData(), colNum_, FLAGS_trainer_id, 0); client->reduce(&numSamples_, &numSamples_, 1, FLAGS_trainer_id, 0); } @@ -379,8 +380,11 @@ real AucEvaluator::evalImp(std::vector& arguments) { } if (dynamic_cast(output.get())) { - Matrix::resizeOrCreate(cpuOutput_, insNum, outputDim, - /* trans=*/false, /* useGpu=*/false); + Matrix::resizeOrCreate(cpuOutput_, + insNum, + outputDim, + /* trans=*/false, + /* useGpu=*/false); cpuOutput_->copyFrom(*output); IVector::resizeOrCreate(cpuLabel_, insNum, false); cpuLabel_->copyFrom(*label); @@ -479,19 +483,24 @@ real RankAucEvaluator::evalImp(std::vector& arguments) { for (size_t i = 0; i < batchNum; ++i) { int beginPos = startPosData[i]; int endPos = startPosData[i + 1]; - batchAuc += calcRankAuc(outputData + beginPos, clickData + beginPos, - pvData + beginPos, endPos - beginPos); + batchAuc += calcRankAuc(outputData + beginPos, + clickData + beginPos, + pvData + beginPos, + endPos - beginPos); } return batchAuc; } -double RankAucEvaluator::calcRankAuc(real* outputData, real* clickData, - real* pvData, size_t size) { +double RankAucEvaluator::calcRankAuc(real* outputData, + real* clickData, + real* pvData, + size_t size) { outputPair_.clear(); for (size_t i = 0; i < size; ++i) { outputPair_.push_back(std::make_pair(outputData[i], i)); } - std::sort(outputPair_.begin(), outputPair_.end(), + std::sort(outputPair_.begin(), + outputPair_.end(), [](const std::pair& a, const std::pair& b) { return a.first > b.first; }); @@ -790,8 +799,12 @@ real PnpairEvaluator::evalImp(std::vector& arguments) { return 0; } -void PnpairEvaluator::stat(size_t start, size_t end, PredictionResult* answers, - double& pos, double& neg, double& spe) { +void PnpairEvaluator::stat(size_t start, + size_t end, + PredictionResult* answers, + double& pos, + double& neg, + double& spe) { for (size_t i = start; i < end; i++) { for (size_t j = i + 1; j < end; j++) { CHECK_EQ(answers[i].queryid, answers[j].queryid); @@ -817,7 +830,8 @@ void PnpairEvaluator::stat(size_t start, size_t end, PredictionResult* answers, } void PnpairEvaluator::calc(std::vector& predictArray) { - std::sort(predictArray.begin(), predictArray.end(), + std::sort(predictArray.begin(), + predictArray.end(), [](const PredictionResult& x, const PredictionResult& y) { return x.queryid < y.queryid; }); @@ -828,11 +842,16 @@ void PnpairEvaluator::calc(std::vector& predictArray) { auto start = predictArray.begin(); while (start != predictArray.end()) { auto end = std::find_if( - start + 1, predictArray.end(), + start + 1, + predictArray.end(), [=](const PredictionResult& x) { return x.queryid != start->queryid; }); CHECK(end != start); - stat(start - predictArray.begin(), end - predictArray.begin(), - predictArray.data(), pos, neg, special); + stat(start - predictArray.begin(), + end - predictArray.begin(), + predictArray.data(), + pos, + neg, + special); start = end; } @@ -1120,8 +1139,8 @@ public: auto resizeMatrix = [](MatrixPtr& dest, const MatrixPtr& src) { if (src && src->useGpu()) { - Matrix::resizeOrCreate(dest, src->getHeight(), src->getWidth(), false, - false); + Matrix::resizeOrCreate( + dest, src->getHeight(), src->getWidth(), false, false); dest->copyFrom(*src); } else { dest = src; diff --git a/paddle/gserver/evaluators/Evaluator.h b/paddle/gserver/evaluators/Evaluator.h index e9957a5ce2..732abb6079 100644 --- a/paddle/gserver/evaluators/Evaluator.h +++ b/paddle/gserver/evaluators/Evaluator.h @@ -12,7 +12,6 @@ WITHOUT 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/pserver/ParameterClient2.h" @@ -110,7 +109,7 @@ public: return os; } - friend std::ostream&& operator<<(std::ostream&& os, // NOLINT + friend std::ostream&& operator<<(std::ostream&& os, // NOLINT const Evaluator& evaluator) { evaluator.printStats(os); return std::move(os); @@ -184,7 +183,9 @@ private: AucEvaluator() {} - inline static double trapezoidArea(double X1, double X2, double Y1, + inline static double trapezoidArea(double X1, + double X2, + double Y1, double Y2) { return (X1 > X2 ? (X1 - X2) : (X2 - X1)) * (Y1 + Y2) / 2.0; } @@ -218,7 +219,9 @@ private: MatrixPtr pv_; std::vector> outputPair_; - double calcRankAuc(real* outputData, real* clickData, real* pvData, + double calcRankAuc(real* outputData, + real* clickData, + real* pvData, size_t size); }; /** @@ -269,10 +272,12 @@ private: IVectorPtr cpuLabel_; MatrixPtr cpuWeight_; - void calcStatsInfo(const MatrixPtr& output, const IVectorPtr& label, + void calcStatsInfo(const MatrixPtr& output, + const IVectorPtr& label, const MatrixPtr& weight); - void calcStatsInfoMulti(const MatrixPtr& output, const MatrixPtr& label, + void calcStatsInfoMulti(const MatrixPtr& output, + const MatrixPtr& label, const MatrixPtr& weight); inline static double calcPrecision(double TP, double FP) { @@ -333,8 +338,12 @@ public: } } - void stat(size_t start, size_t end, PredictionResult* answers, double& pos, - double& neg, double& spe); + void stat(size_t start, + size_t end, + PredictionResult* answers, + double& pos, + double& neg, + double& spe); void calc(std::vector& predictArray); virtual void finish() { calc(predictArray_); } diff --git a/paddle/gserver/gradientmachines/GradientMachine.cpp b/paddle/gserver/gradientmachines/GradientMachine.cpp index b20525f664..3761fda5f3 100644 --- a/paddle/gserver/gradientmachines/GradientMachine.cpp +++ b/paddle/gserver/gradientmachines/GradientMachine.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "GradientMachine.h" #include "paddle/utils/Logging.h" @@ -29,7 +28,8 @@ limitations under the License. */ namespace paddle { GradientMachine* GradientMachine::create( - const ModelConfig& config, int mode, + const ModelConfig& config, + int mode, const std::vector& parameterTypes) { if (auto gm = IGradientMachineMode::tryCreateGradientMachine(mode, config)) { return gm; @@ -49,10 +49,11 @@ GradientMachine* GradientMachine::create( /* single thread calculate */ nn = NeuralNetwork::create(config); } - ParamInitCallback testParamInitCb = - [](int paramId, Parameter* para) { para->enableType(PARAMETER_VALUE); }; - nn->init(config, mode == kTesting ? testParamInitCb : nullptr, - parameterTypes); + ParamInitCallback testParamInitCb = [](int paramId, Parameter* para) { + para->enableType(PARAMETER_VALUE); + }; + nn->init( + config, mode == kTesting ? testParamInitCb : nullptr, parameterTypes); return nn; } LOG(FATAL) << "Unknown model type: " << config.type(); diff --git a/paddle/gserver/gradientmachines/GradientMachine.h b/paddle/gserver/gradientmachines/GradientMachine.h index 986a1ee71d..27cdf7f789 100644 --- a/paddle/gserver/gradientmachines/GradientMachine.h +++ b/paddle/gserver/gradientmachines/GradientMachine.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -84,10 +83,11 @@ public: * Parameter will have parameterTypes */ static GradientMachine* create( - const ModelConfig& config, int mode = kNormal, + const ModelConfig& config, + int mode = kNormal, const std::vector& parameterTypes = - std::vector{PARAMETER_VALUE, PARAMETER_GRADIENT, - PARAMETER_MOMENTUM}); + std::vector{ + PARAMETER_VALUE, PARAMETER_GRADIENT, PARAMETER_MOMENTUM}); /** * Create a gradient machine from the merged model file. @@ -137,7 +137,8 @@ public: * @note: if passType==PASS_TEST, then backward() should not be called */ virtual void forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType) = 0; + std::vector* outArgs, + PassType passType) = 0; /** * @brief Backward propagation. @@ -211,7 +212,7 @@ public: * @note This function will only been implemented and used in a * multithreaded environment. */ - virtual void start(const TrainerConfig& config, + virtual void start(const TrainerConfig& config, DataProviderPtr dataProvider) { (void)config; (void)dataProvider; @@ -246,7 +247,6 @@ public: */ virtual void restart() {} - /// Set the gradient of the output from outside. virtual void setOutputGrad(const std::vector& args) { LOG(FATAL) << "Not implemented!"; diff --git a/paddle/gserver/gradientmachines/GradientMachineMode.h b/paddle/gserver/gradientmachines/GradientMachineMode.h index 9aff9c616c..f2f55a7067 100644 --- a/paddle/gserver/gradientmachines/GradientMachineMode.h +++ b/paddle/gserver/gradientmachines/GradientMachineMode.h @@ -23,10 +23,10 @@ public: virtual ~IGradientMachineMode() {} public: // interfaces - /** - * @brief create current mode's gradient machine by model config. - * @param config model config - */ + /** + * @brief create current mode's gradient machine by model config. + * @param config model config + */ virtual GradientMachine* create(const ModelConfig& config) = 0; /** @@ -37,11 +37,10 @@ public: // interfaces * @param isGpu is using gpu. * @return true if mode should be this mode. */ - virtual bool shouldBeMe( - const std::string& algo, - size_t trainerCount, - bool isLocal, - bool isGpu) const = 0; + virtual bool shouldBeMe(const std::string& algo, + size_t trainerCount, + bool isLocal, + bool isGpu) const = 0; /** * @brief Is data must be in cpu even if using gpu mode. @@ -57,13 +56,13 @@ public: // interfaces virtual bool needTrainWholeDataInOneBatch() const = 0; public: // static methods. - /** - * @brief register a custom gradient machine mode. - * @note For user to register a custom gradient machine mode, id should >= - * kCustom. - * @param mode mode id. - * @param ptr mode description object. - */ + /** + * @brief register a custom gradient machine mode. + * @note For user to register a custom gradient machine mode, id should >= + * kCustom. + * @param mode mode id. + * @param ptr mode description object. + */ static void regGradientMachineMode( int32_t mode, std::unique_ptr&& ptr) { modes_.insert(std::make_pair(mode, std::move(ptr))); @@ -102,9 +101,11 @@ public: // static methods. * @param [in] isGpu using gpu or not. * @return true if there is a custom mode fit these conditions. */ - static bool tryGetMode(int* mode, const std::string& algo, + static bool tryGetMode(int* mode, + const std::string& algo, int32_t trainerCount, - bool isLocal, bool isGpu) { + bool isLocal, + bool isGpu) { for (auto it = modes_.begin(); it != modes_.end(); ++it) { if (it->second->shouldBeMe(algo, trainerCount, isLocal, isGpu)) { *mode = it->first; @@ -130,8 +131,8 @@ public: // static methods. * @brief try to create gradient machine by mode & config. * @return nullptr if we cannot create a gradient machine by such mode. */ - static GradientMachine* tryCreateGradientMachine( - int32_t mode, const ModelConfig& config) { + static GradientMachine* tryCreateGradientMachine(int32_t mode, + const ModelConfig& config) { auto m = IGradientMachineMode::mode(mode); if (m) { return m->create(config); @@ -142,7 +143,7 @@ public: // static methods. private: static std::unordered_map> - modes_; + modes_; }; } // namespace paddle diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp index 0ded30eeb4..148451f18d 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "MultiGradientMachine.h" #include "paddle/utils/Logging.h" @@ -22,7 +21,8 @@ limitations under the License. */ #include "NeuralNetwork.h" #include "ParallelNeuralNetwork.h" -P_DEFINE_bool(allow_only_one_model_on_one_gpu, true, +P_DEFINE_bool(allow_only_one_model_on_one_gpu, + true, "If true, do not allow multiple models on one GPU device"); #ifdef PADDLE_METRIC_LEARNING P_DECLARE_bool(external); @@ -32,15 +32,15 @@ namespace paddle { // get types of the parameters which need to be merged after backward() static void fillMergeTypes(PassType passType, - std::vector* mergeTypes) { + std::vector* mergeTypes) { mergeTypes->clear(); if (passType != PASS_TEST) { mergeTypes->push_back(PARAMETER_GRADIENT); } } -MultiGradientMachine::MultiGradientMachine( - const ModelConfig& config, bool useGpu) +MultiGradientMachine::MultiGradientMachine(const ModelConfig& config, + bool useGpu) : useGpu_(useGpu), trainerBarrier_(FLAGS_trainer_count), allBarrier_(FLAGS_trainer_count + 1), @@ -65,13 +65,11 @@ MultiGradientMachine::MultiGradientMachine( if (para->useGpu()) return; if (para->isSparseRemoteUpdate()) { - para->enableType( - PARAMETER_VALUE, - FLAGS_loadsave_parameters_in_pserver - ? Parameter::MAT_SPARSE_ROW_PREFETCH - : Parameter::MAT_SPARSE_ROW_PREFETCH_FULL_SIZE); - para->enableType( - PARAMETER_GRADIENT, Parameter::MAT_SPARSE_ROW); + para->enableType(PARAMETER_VALUE, + FLAGS_loadsave_parameters_in_pserver + ? Parameter::MAT_SPARSE_ROW_PREFETCH + : Parameter::MAT_SPARSE_ROW_PREFETCH_FULL_SIZE); + para->enableType(PARAMETER_GRADIENT, Parameter::MAT_SPARSE_ROW); } else if (para->isGradSparseUpdate()) { para->enableType(PARAMETER_VALUE); para->enableType(PARAMETER_GRADIENT, Parameter::MAT_SPARSE_ROW_IDS); @@ -100,17 +98,16 @@ MultiGradientMachine::MultiGradientMachine( if (useGpu_) { numLogicalDevices_ = 1; - for (size_t pid = 0; pid < parameters_.size(); pid++) { + for (size_t pid = 0; pid < parameters_.size(); pid++) { if (parameters_[pid]->getConfig().device() + 1 > numLogicalDevices_) { numLogicalDevices_ = parameters_[pid]->getConfig().device() + 1; } } LOG(INFO) << "numLogicalDevices=" << numLogicalDevices_ - << " numThreads=" << numThreads_ - << " numDevices=" << numDevices_; + << " numThreads=" << numThreads_ << " numDevices=" << numDevices_; - if (numLogicalDevices_ * numThreads_ > numDevices_ - && FLAGS_allow_only_one_model_on_one_gpu) { + if (numLogicalDevices_ * numThreads_ > numDevices_ && + FLAGS_allow_only_one_model_on_one_gpu) { LOG(FATAL) << "trainer_count * num_devices_in_model " << "(" << numThreads_ << "*" << numLogicalDevices_ << ")" << "=" << numThreads_ * numLogicalDevices_ @@ -130,11 +127,7 @@ MultiGradientMachine::MultiGradientMachine( } for (int i = 0; i < numThreads_; ++i) { - threads_.emplace_back( - new TrainerThread( - config, - i, - this)); + threads_.emplace_back(new TrainerThread(config, i, this)); } bufferSizes_.resize(numLogicalDevices_, 0); @@ -162,7 +155,7 @@ MultiGradientMachine::MultiGradientMachine( // combination of all trainers mainPara into GradientMachine parameters hasNonstaticCpuParamters_ = false; - for (size_t pid = 0; pid < parameters_.size(); pid++) { + for (size_t pid = 0; pid < parameters_.size(); pid++) { if (parameters_[pid]->useGpu()) { parameters_[pid] = threads_[paraMainThread_[pid]]->getParameters()[pid]; } else if (!parameters_[pid]->isStatic()) { @@ -209,7 +202,7 @@ void MultiGradientMachine::allocGradBufs() { SetDevice device(logicalDeviceId2RealDeviceId(d, i)); for (size_t j = 0; j < mergeTypes_.size(); j++) { gradBufs_[i][d].bufs.push_back( - Vector::create(bufferSizes_[d], /* useGpu= */true)); + Vector::create(bufferSizes_[d], /* useGpu= */ true)); } } } @@ -249,18 +242,16 @@ void MultiGradientMachine::prefetch(const std::vector& inArgs) { } } -void MultiGradientMachine::forward( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType) { +void MultiGradientMachine::forward(const std::vector& inArgs, + std::vector* outArgs, + PassType passType) { forwardImp(inArgs, outArgs, passType, TASK_FORWARD); } -void MultiGradientMachine::forwardImp( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType, - TaskType taskType) { +void MultiGradientMachine::forwardImp(const std::vector& inArgs, + std::vector* outArgs, + PassType passType, + TaskType taskType) { updateThreadParameters(); passType_ = passType; @@ -282,18 +273,16 @@ void MultiGradientMachine::backward(const UpdateCallback& callback) { backwardImp(callback); } -void MultiGradientMachine::forwardBackward( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType, - const UpdateCallback& callback) { +void MultiGradientMachine::forwardBackward(const std::vector& inArgs, + std::vector* outArgs, + PassType passType, + const UpdateCallback& callback) { backwardCallback_ = callback; forwardImp(inArgs, outArgs, passType, TASK_FORWARD_BACKWARD); backwardImp(callback); } -void MultiGradientMachine::backwardImp( - const UpdateCallback& callback) { +void MultiGradientMachine::backwardImp(const UpdateCallback& callback) { for (size_t i = 0; i < parameters_.size(); i++) { if (!parameters_[i]->useGpu() || parameters_[i]->isStatic()) continue; REGISTER_TIMER("controller_dequeue"); @@ -349,9 +338,8 @@ void MultiGradientMachine::eval(Evaluator* evaluator) { } } -void MultiGradientMachine::getOutArgs( - std::vector* outArgs, - PassType passType) { +void MultiGradientMachine::getOutArgs(std::vector* outArgs, + PassType passType) { for (auto& thread : threads_) { REGISTER_TIMER("waitOutArgs"); thread->waitOutArgsReady(); @@ -375,7 +363,6 @@ void MultiGradientMachine::getOutArgs( *outArgs = outArgs_; } - void MultiGradientMachine::setOutputGrad(const std::vector& args) { CHECK_EQ(args.size(), outArgs_.size()); for (size_t i = 0; i < args.size(); i++) { @@ -390,10 +377,9 @@ void MultiGradientMachine::startTask(TaskType taskType) { } } -TrainerThread::TrainerThread( - const ModelConfig& config, - int threadId, - MultiGradientMachine* multiMachine) +TrainerThread::TrainerThread(const ModelConfig& config, + int threadId, + MultiGradientMachine* multiMachine) : multiMachine_(multiMachine), config_(config), threadId_(threadId), @@ -407,8 +393,9 @@ TrainerThread::TrainerThread( partnerId_ = mod(threadId_ - 1, numThreads); - deviceId_ = !multiMachine_->useGpu() ? -1 - : multiMachine_->logicalDeviceId2RealDeviceId(0, threadId_); + deviceId_ = !multiMachine_->useGpu() + ? -1 + : multiMachine_->logicalDeviceId2RealDeviceId(0, threadId_); SetDevice gpuDevice(deviceId_); NeuralNetwork* nn = nullptr; @@ -418,22 +405,20 @@ TrainerThread::TrainerThread( nn = new ParallelNeuralNetwork(); for (auto& paraConfig : *config_.mutable_parameters()) { if (paraConfig.device() != -1) { - paraConfig.set_device( - multiMachine_->logicalDeviceId2RealDeviceId( + paraConfig.set_device(multiMachine_->logicalDeviceId2RealDeviceId( paraConfig.device(), threadId_)); } } for (auto& layerConfig : *config_.mutable_layers()) { if (layerConfig.device() != -1) { - layerConfig.set_device( - multiMachine_->logicalDeviceId2RealDeviceId( + layerConfig.set_device(multiMachine_->logicalDeviceId2RealDeviceId( layerConfig.device(), threadId_)); } } } // Only GPU do not share parameter values with main paramters. - ParamInitCallback slaveParamInitCb = std::bind(parameterInitNN, _1, _2, - &mainParas); + ParamInitCallback slaveParamInitCb = + std::bind(parameterInitNN, _1, _2, &mainParas); nn->init(config_, slaveParamInitCb); gradientMachine_.reset(nn); parameters_ = gradientMachine_->getParameters(); @@ -443,9 +428,8 @@ TrainerThread::TrainerThread( } } - backwardCallback_ = std::bind( - &TrainerThread::backwardCallback, - this, std::placeholders::_1); + backwardCallback_ = + std::bind(&TrainerThread::backwardCallback, this, std::placeholders::_1); gradStream_ = HPPL_STREAM_2; valueStream_ = HPPL_STREAM_3; @@ -454,25 +438,21 @@ TrainerThread::TrainerThread( parameterUpdated_ = false; } -TrainerThread::~TrainerThread() { - stop(); -} +TrainerThread::~TrainerThread() { stop(); } void TrainerThread::start() { - gradientMachine_->start(*(TrainerConfig*)nullptr, (DataProviderPtr)nullptr); + gradientMachine_->start(*(TrainerConfig*)nullptr, (DataProviderPtr) nullptr); - computeThread_.reset(new std::thread( - [this](){ computeThread(); })); + computeThread_.reset(new std::thread([this]() { computeThread(); })); if (multiMachine_->useGpu()) { - gradCollectThread_.reset(new std::thread( - [this](){ gradCollectThread(); })); + gradCollectThread_.reset( + new std::thread([this]() { gradCollectThread(); })); - valueDispatchThread_.reset(new std::thread( - [this](){ valueDispatchThread(); })); + valueDispatchThread_.reset( + new std::thread([this]() { valueDispatchThread(); })); - copyThread_.reset(new std::thread( - [this](){ copyGradToBufferThread(); })); + copyThread_.reset(new std::thread([this]() { copyGradToBufferThread(); })); } } @@ -565,20 +545,14 @@ void TrainerThread::forward() { { REGISTER_TIMER("wait_value"); - valueReadyCond_.wait( - [this]() { - return !parameterUpdated_; - }); + valueReadyCond_.wait([this]() { return !parameterUpdated_; }); } - { - fillMergeTypes(multiMachine_->getPassType(), &mergeTypes_); - } + { fillMergeTypes(multiMachine_->getPassType(), &mergeTypes_); } { REGISTER_TIMER("thread_forward"); - gradientMachine_->forward( - inArgs_, &outArgs_, multiMachine_->getPassType()); + gradientMachine_->forward(inArgs_, &outArgs_, multiMachine_->getPassType()); } outArgsReadySem_.post(); } @@ -602,9 +576,8 @@ void TrainerThread::backwardCallback(Parameter* para) { if (multiMachine_->getNumThreads() == 1) { // no need to do merge if there is only one thread doCallback(paramId); - } else if (threadId_ == - mod(multiMachine_->paraMainThread(paramId) - 1, - multiMachine_->getNumThreads())) { + } else if (threadId_ == mod(multiMachine_->paraMainThread(paramId) - 1, + multiMachine_->getNumThreads())) { notifyCopyGradToBuffer(paramId); } else { notifyGradientCollect(paramId); @@ -625,7 +598,7 @@ void TrainerThread::copyGradToBufferThread() { if (stopping_) break; int pdeviceId = multiMachine_->realDeviceId2LogicalDeviceId( - parameters_[pid]->getDeviceId(), threadId_); + parameters_[pid]->getDeviceId(), threadId_); auto& gradBuf = gradBufs[pdeviceId]; @@ -639,9 +612,9 @@ void TrainerThread::copyGradToBufferThread() { SetDevice setDevice(parameters_[pid]->getDeviceId()); for (size_t i = 0; i < mergeTypes_.size(); ++i) { gradBuf.bufs[i]->resize( - parameters_[pid]->getBuf(mergeTypes_[i])->getSize()); - gradBuf.bufs[i]->copyFrom( - *parameters_[pid]->getBuf(mergeTypes_[i]), gradStream_); + parameters_[pid]->getBuf(mergeTypes_[i])->getSize()); + gradBuf.bufs[i]->copyFrom(*parameters_[pid]->getBuf(mergeTypes_[i]), + gradStream_); } hl_stream_synchronize(gradStream_); } @@ -667,7 +640,7 @@ void TrainerThread::gradCollectThread() { if (++gradReadyCount[pid] < 2) continue; gradReadyCount[pid] = 0; int pdeviceId = multiMachine_->realDeviceId2LogicalDeviceId( - parameters_[pid]->getDeviceId(), threadId_); + parameters_[pid]->getDeviceId(), threadId_); auto& gradBuf = gradBufs[pdeviceId]; @@ -741,8 +714,7 @@ void TrainerThread::valueDispatchThread() { void TrainerThread::notifyValueReady(int paramId) { if (--updateCounter_ == 0) { - valueReadyCond_.notify_all( - [this] { parameterUpdated_ = false; }); + valueReadyCond_.notify_all([this] { parameterUpdated_ = false; }); } notifyValueDispatch(paramId); @@ -750,7 +722,7 @@ void TrainerThread::notifyValueReady(int paramId) { void TrainerThread::copyInArgs() { const std::vector& fullInArgs = multiMachine_->getInArgs(); - int numThreads = multiMachine_->getAllThreads().size(); + int numThreads = multiMachine_->getAllThreads().size(); int32_t numSequences = fullInArgs[0].getNumSequences(); int32_t startSeq = numSequences * threadId_ / numThreads; int32_t endSeq = numSequences * (threadId_ + 1) / numThreads; @@ -767,9 +739,11 @@ void TrainerThread::copyInArgs() { return; } - for (size_t i=0; i < fullInArgs.size(); i++) { + for (size_t i = 0; i < fullInArgs.size(); i++) { inArgs_[i].resizeAndCopyFrom( - fullInArgs[i], startSeq, copySize, + fullInArgs[i], + startSeq, + copySize, FLAGS_parallel_nn ? false : multiMachine_->useGpu()); } } @@ -814,10 +788,8 @@ void TrainerThread::mergeGradSparse( std::vector& ids = mainMat->getIds(threadId_); for (auto slaveParams : slaveParameters) { - SparseRowCpuMatrix* mat = - dynamic_cast((*slaveParams)[pid] - ->getMat(PARAMETER_GRADIENT) - .get()); + SparseRowCpuMatrix* mat = dynamic_cast( + (*slaveParams)[pid]->getMat(PARAMETER_GRADIENT).get()); mat->addTo(*mainMat, ids, threadId_, multiMachine_->getNumThreads()); // we use a sample hash method(%) instead of range partition, // because range partition has balance issue sometimes, @@ -847,9 +819,10 @@ void TrainerThread::mergeGradDense( Parameter* para, std::vector*>& slaveParameters) { size_t pid = para->getID(); - auto interval = - calcSplitArrayInterval(para->getSize(), (size_t)threadId_, - multiMachine_->getNumThreads(), 8LU /*for avx*/); + auto interval = calcSplitArrayInterval(para->getSize(), + (size_t)threadId_, + multiMachine_->getNumThreads(), + 8LU /*for avx*/); size_t startSeq = interval.first; size_t copySize = interval.second - interval.first; @@ -861,8 +834,7 @@ void TrainerThread::mergeGradDense( CpuVector slaveGradSub(0, nullptr); for (auto slaveParams : slaveParameters) { slaveGradSub.subVecFrom( - *(*slaveParams)[pid]->getBuf(PARAMETER_GRADIENT), - startSeq, copySize); + *(*slaveParams)[pid]->getBuf(PARAMETER_GRADIENT), startSeq, copySize); destGrad.add(slaveGradSub); } } @@ -876,7 +848,9 @@ void TrainerThread::copyOutputGrad() { int32_t copySize = endSeq - startSeq; outArgs_.resize(outputGradArgs.size()); for (size_t i = 0; i < outputGradArgs.size(); i++) { - outArgs_[i].resizeAndCopyFrom(outputGradArgs[i], startSeq, copySize, + outArgs_[i].resizeAndCopyFrom(outputGradArgs[i], + startSeq, + copySize, multiMachine_->useGpu(), HPPL_STREAM_DEFAULT); } diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.h b/paddle/gserver/gradientmachines/MultiGradientMachine.h index d13cf426c2..58c5486810 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.h +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -52,7 +51,8 @@ struct GradBuffer { * * It handles GPU and Cpu parameters differently. In GPU, one computing thread * generally corresponds to one GPU device. Thus, each thread keeps a separate - * copy of the parameter in its own device's memory. In CPU, we only need to keep + * copy of the parameter in its own device's memory. In CPU, we only need to + keep * one copy of the parameters in the main memory. After, each computing thread * computes its own parameter gradient, the update process needs to accumulate * the parameter gradients from all the computing threads, and update the @@ -66,16 +66,21 @@ struct GradBuffer { * computing thread so that the parameters in all the computing threads are * synchronized. The scatter and gather process are implemented by ring-style * communication. Assume we have N computing threads, its thread ids will be - * 0, 1, ..., N-1. For each parameter, the id of the main thread is specified in - * paraMainThread_[pid], where pid is the id of the parameter. Each thread i only + * 0, 1, ..., N-1. For each parameter, the id of the main thread is specified + in + * paraMainThread_[pid], where pid is the id of the parameter. Each thread i + only * sends data to its partner thread (i - 1) % N. For example, for a parameter * gradient that is computed in thread 4, and its main thread is 2. Its - * traveling process would be 4, 5,..., N-1, 0, 1, 2. In each step, the gradient + * traveling process would be 4, 5,..., N-1, 0, 1, 2. In each step, the + gradient * buffer is added to the local gradient, and the local gradient is then copied * to the gradient buffer of the next thread. At last, its main thread 2 will * get the accumulated parameter gradient. For the same parameter, after its - * value is updated, the value's traveling process would be 2, 1, 0, N-1, ... 3. - * At the end, all the computing threads would have the updated parameter value. + * value is updated, the value's traveling process would be 2, 1, 0, N-1, ... + 3. + * At the end, all the computing threads would have the updated parameter + value. * * A computing thread (TrainerThread) uses 4 threads to do different jobs: * @@ -94,8 +99,10 @@ struct GradBuffer { * * Handling of sparse update * Currently, sparse update is only supported for CPU parameters. - * Sparse updates refers to gradient caculation where the gradient is sparse. For - * example, if the input argument to a 'fc' layer is sparse, the gradient of the + * Sparse updates refers to gradient caculation where the gradient is sparse. + For + * example, if the input argument to a 'fc' layer is sparse, the gradient of + the * weight matrix of this layer will be sparse. It is usually more efficient to * treat the gradient explicitly as sparse vector during the parameter update. @@ -104,7 +111,8 @@ struct GradBuffer { * For both types of sparse updates, there is one copy of parameter value and * gradient called main parameter value and gradient, and there is a copy of - * parameter value and gradient for each computing thread called slave parameter + * parameter value and gradient for each computing thread called slave + parameter * value and gradient. The slave parameter values are always shared with the * corresponding main parameter value. The slave parameter grad is a sparse row * matrix. The sparse pattern for slave parameter grads are different, because @@ -124,7 +132,8 @@ struct GradBuffer { * (SparseAutoGrowRowCpuMatrix). It is a sparse row matrix. * * During backward() of each TrainerThread, SparseAutoGrowRowCpuMatrix will - * gather all the non-zero gradient. And After backward(), they will be merged + * gather all the non-zero gradient. And After backward(), they will be + merged * into main parameter grad (SparseRowIdsCpuMatrix), with indices indicating * which rows have nonzero gradient. * @@ -136,9 +145,11 @@ struct GradBuffer { * parameter values that are prefetched is up-to-date. * * Main parameter grad type is MAT_SPARSE_ROW (SparseRowCpuMatrix). - * And it shares sparse pattern with value by sharing indexDictHandle_, which + * And it shares sparse pattern with value by sharing indexDictHandle_, + which * is an internal data structure used by SparseRowCpuMatrixto specify the - * sparsity pattern of Slave parameter value shares with main parameter value. + * sparsity pattern of Slave parameter value shares with main parameter + value. * * Slave parameter grad type is MAT_SPARSE_ROW_AUTO_GROW * (SparsePrefetchRowCpuMatrix). It is a sparse row matrix @@ -148,8 +159,10 @@ struct GradBuffer { * parameter server. * * During backward() of each TrainerThread, SparseAutoGrowRowCpuMatrix will - * gather all the non-zero gradient. And After backward(), they will be merged - * into main parameter grad (SparseRowCpuMatrix). And the framework will send + * gather all the non-zero gradient. And After backward(), they will be + merged + * into main parameter grad (SparseRowCpuMatrix). And the framework will + send * the merged gradient to parameter server. */ class MultiGradientMachine : public GradientMachine { @@ -165,18 +178,16 @@ public: virtual void prefetch(const std::vector& inArgs); - virtual void forward( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType); + virtual void forward(const std::vector& inArgs, + std::vector* outArgs, + PassType passType); virtual void backward(const UpdateCallback& callback = nullptr); - void forwardBackward( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType, - const UpdateCallback& callback); + void forwardBackward(const std::vector& inArgs, + std::vector* outArgs, + PassType passType, + const UpdateCallback& callback); virtual void onPassEnd(); @@ -186,9 +197,7 @@ public: virtual void eval(Evaluator* evaluator); - bool useGpu() const { - return useGpu_; - } + bool useGpu() const { return useGpu_; } /// @return whether to pass the gradients in outArgs_ to each threads. bool isPassGrad() { return isPassGrad_; } @@ -203,9 +212,7 @@ public: protected: friend class TrainerThread; - std::vector& getAllThreads() { - return threads_; - } + std::vector& getAllThreads() { return threads_; } /// Calculate the real device id based on the logical device id and the /// thread id. int logicalDeviceId2RealDeviceId(int logicalId, int threadId = 0) const { @@ -229,9 +236,7 @@ protected: std::vector*> getSlaveParameters(); - bool hasNonstaticCpuParamters() const { - return hasNonstaticCpuParamters_; - } + bool hasNonstaticCpuParamters() const { return hasNonstaticCpuParamters_; } /// Called TrainerThread to wait before merging CPU parameter gradients. void waitBeforeMerge() { trainerBarrier_.wait(); } @@ -244,59 +249,41 @@ protected: /// finishing void waitForCopyInArgs() { allBarrier_.wait(); } - TrainerThreadPtr& getThread(int threadId) { - return threads_[threadId]; - } + TrainerThreadPtr& getThread(int threadId) { return threads_[threadId]; } std::vector& getGradBuf(int threadId) { return gradBufs_[threadId]; } - PassType getPassType() const { - return passType_; - } + PassType getPassType() const { return passType_; } /// Called by TrainerThread to notify MultiGradientMachine that the gradient /// for paramId is ready void notifyGradientTransfer(int paramId); - const std::vector& getInArgs() { - return inArgs_; - } + const std::vector& getInArgs() { return inArgs_; } - TaskType getTaskType() const { - return taskType_; - } + TaskType getTaskType() const { return taskType_; } const UpdateCallback& getBackwardCallback() const { return backwardCallback_; } - int getNumDevices() const { - return numDevices_; - } + int getNumDevices() const { return numDevices_; } - int getNumLogicalDevices() const { - return numLogicalDevices_; - } + int getNumLogicalDevices() const { return numLogicalDevices_; } - int getNumThreads() const { - return numThreads_; - } + int getNumThreads() const { return numThreads_; } - int paraMainThread(int pid) const { - return paraMainThread_[pid]; - } + int paraMainThread(int pid) const { return paraMainThread_[pid]; } protected: - virtual void forwardImp( - const std::vector& inArgs, - std::vector* outArgs, - PassType passType, - TaskType taskType); + virtual void forwardImp(const std::vector& inArgs, + std::vector* outArgs, + PassType passType, + TaskType taskType); - virtual void backwardImp( - const UpdateCallback& callback = NULL); + virtual void backwardImp(const UpdateCallback& callback = NULL); /// update all parameters void updateThreadParameters(); @@ -329,9 +316,9 @@ protected: /// ParameterType which needs to be merged from each GPU std::vector mergeTypes_; - int numDevices_; /* number of gpu devices */ + int numDevices_; /* number of gpu devices */ int numLogicalDevices_; // number of GPU used by one NN - int numThreads_; /* number of train threads */ + int numThreads_; /* number of train threads */ UpdateCallback backwardCallback_; @@ -350,38 +337,25 @@ protected: class TrainerThread { public: - TrainerThread( - const ModelConfig& config, - int threadId, - MultiGradientMachine* multiMachine); + TrainerThread(const ModelConfig& config, + int threadId, + MultiGradientMachine* multiMachine); ~TrainerThread(); void start(); - void onPassEnd() { - gradientMachine_->onPassEnd(); - } + void onPassEnd() { gradientMachine_->onPassEnd(); } - void waitOutArgsReady() { - outArgsReadySem_.wait(); - } + void waitOutArgsReady() { outArgsReadySem_.wait(); } - void notifyTaskReady() { - taskReadySem_.post(); - } + void notifyTaskReady() { taskReadySem_.post(); } - int getDeviceId() const { - return deviceId_; - } + int getDeviceId() const { return deviceId_; } - GradientMachine* getGradientMachine() { - return gradientMachine_.get(); - } + GradientMachine* getGradientMachine() { return gradientMachine_.get(); } - const std::vector& getParameters() { - return parameters_; - } + const std::vector& getParameters() { return parameters_; } void stop(); @@ -391,26 +365,18 @@ public: return parameters_[paramId]->getBuf(PARAMETER_VALUE); } - const std::vector& getOutArgs() { - return outArgs_; - } + const std::vector& getOutArgs() { return outArgs_; } void incUpdateCounter(int n = 1) { updateCounter_ += n; parameterUpdated_ = true; } - void notifyGradientCollect(int paramId) { - gradQueue_.enqueue(paramId); - } + void notifyGradientCollect(int paramId) { gradQueue_.enqueue(paramId); } - void notifyCopyGradToBuffer(int paramId) { - gradBufQueue_.enqueue(paramId); - } + void notifyCopyGradToBuffer(int paramId) { gradBufQueue_.enqueue(paramId); } - void notifyValueDispatch(int paramId) { - valueReadyQueue_.enqueue(paramId); - } + void notifyValueDispatch(int paramId) { valueReadyQueue_.enqueue(paramId); } void prefetch(); @@ -421,16 +387,16 @@ protected: void mergeCpuGradients(); void mergeGradSparse( - Parameter* para, - std::vector*>& slaveParameters); + Parameter* para, + std::vector*>& slaveParameters); void mergeGradSparseRemote( - Parameter* para, - std::vector*>& slaveParameters); + Parameter* para, + std::vector*>& slaveParameters); void mergeGradDense( - Parameter* para, - std::vector*>& slaveParameters); + Parameter* para, + std::vector*>& slaveParameters); void computeThread(); void valueDispatchThread(); @@ -499,5 +465,4 @@ protected: bool inArgsCopied_; }; - } // namespace paddle diff --git a/paddle/gserver/gradientmachines/MultiNetwork.cpp b/paddle/gserver/gradientmachines/MultiNetwork.cpp index b85d2e0c99..e5be19cad6 100644 --- a/paddle/gserver/gradientmachines/MultiNetwork.cpp +++ b/paddle/gserver/gradientmachines/MultiNetwork.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "paddle/utils/Util.h" #include @@ -24,7 +23,8 @@ limitations under the License. */ namespace paddle { -void MultiNetwork::init(const ModelConfig& config, ParamInitCallback callback, +void MultiNetwork::init(const ModelConfig& config, + ParamInitCallback callback, const std::vector& parameterTypes, bool useGpu) { CHECK_GT(config.sub_models_size(), 1) << "sub_models_size should GT 1"; @@ -40,10 +40,10 @@ void MultiNetwork::init(const ModelConfig& config, ParamInitCallback callback, std::string subModelName = config.sub_models(i).name(); if (FLAGS_parallel_nn) { subNetworks_[i - 1] = std::unique_ptr( - new ParallelNeuralNetwork(subModelName, this)); + new ParallelNeuralNetwork(subModelName, this)); } else { subNetworks_[i - 1] = std::unique_ptr( - NeuralNetwork::newNeuralNetwork(subModelName, this)); + NeuralNetwork::newNeuralNetwork(subModelName, this)); } subNetworks_[i - 1]->init(config); } @@ -64,7 +64,8 @@ void MultiNetwork::prefetch(const std::vector& inArgs) { } void MultiNetwork::forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType) { + std::vector* outArgs, + PassType passType) { // split inArgs to several vectors std::vector> argumentGroups; Argument::splitByDataId(inArgs, &argumentGroups); diff --git a/paddle/gserver/gradientmachines/MultiNetwork.h b/paddle/gserver/gradientmachines/MultiNetwork.h index a162420c3b..779a2267f5 100644 --- a/paddle/gserver/gradientmachines/MultiNetwork.h +++ b/paddle/gserver/gradientmachines/MultiNetwork.h @@ -12,7 +12,6 @@ WITHOUT 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 "GradientMachine.h" @@ -27,19 +26,22 @@ public: explicit MultiNetwork(std::string subModelName = "") : NeuralNetwork(subModelName) {} - virtual void init(const ModelConfig& config, ParamInitCallback callback, + virtual void init(const ModelConfig& config, + ParamInitCallback callback, const std::vector& parameterTypes, bool useGpu); virtual void prefetch(const std::vector& inArgs); virtual void forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType); + std::vector* outArgs, + PassType passType); virtual void backward(const UpdateCallback& callback = nullptr); void forwardBackward(const std::vector& inArgs, - std::vector* outArgs, PassType passType, + std::vector* outArgs, + PassType passType, const UpdateCallback& callback); virtual void onPassEnd(); @@ -52,8 +54,7 @@ public: return subNetworks_; } - virtual void start(const TrainerConfig& config, - DataProviderPtr dataProvider); + virtual void start(const TrainerConfig& config, DataProviderPtr dataProvider); virtual void finish(); diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index c77b00eb06..9932ea655e 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "paddle/utils/Logging.h" @@ -26,7 +25,8 @@ limitations under the License. */ #include "paddle/gserver/layers/AgentLayer.h" namespace paddle { -void parameterInitNN(int paramId, Parameter* para, +void parameterInitNN(int paramId, + Parameter* para, std::vector* sharedParams) { // Create parameters values. if (!para->useGpu() && sharedParams) { @@ -35,10 +35,10 @@ void parameterInitNN(int paramId, Parameter* para, (*sharedParams)[paramId]->getMat(PARAMETER_VALUE)); } else { if (para->isSparseRemoteUpdate()) { - para->enableType( - PARAMETER_VALUE, FLAGS_loadsave_parameters_in_pserver - ? Parameter::MAT_SPARSE_ROW_PREFETCH - : Parameter::MAT_SPARSE_ROW_PREFETCH_FULL_SIZE); + para->enableType(PARAMETER_VALUE, + FLAGS_loadsave_parameters_in_pserver + ? Parameter::MAT_SPARSE_ROW_PREFETCH + : Parameter::MAT_SPARSE_ROW_PREFETCH_FULL_SIZE); } else { para->enableType(PARAMETER_VALUE); } @@ -65,7 +65,8 @@ NeuralNetwork* NeuralNetwork::create(const ModelConfig& config) { std::map NeuralNetwork::dllInitMap; -void NeuralNetwork::init(const ModelConfig& config, ParamInitCallback callback, +void NeuralNetwork::init(const ModelConfig& config, + ParamInitCallback callback, const std::vector& parameterTypes, bool useGpu) { using std::placeholders::_1; @@ -89,12 +90,13 @@ void NeuralNetwork::init(const ModelConfig& config, ParamInitCallback callback, } else { parameters_.reserve(config.parameters_size()); for (const auto& para_config : config.parameters()) { - auto parameter = std::make_shared(para_config, useGpu, + auto parameter = std::make_shared(para_config, + useGpu, /*initialize=*/false); paramCallback(parameters_.size(), parameter.get()); if (!callback) { for (ParameterType type : - (parameter->isStatic() + (parameter->isStatic() ? std::vector{PARAMETER_VALUE} : parameterTypes)) { if (type != PARAMETER_VALUE && type != PARAMETER_GRADIENT) { @@ -117,18 +119,19 @@ void NeuralNetwork::init(const ModelConfig& config, ParamInitCallback callback, layerMap_[layer->getName()] = layer; }; - auto subModelConfig = - std::find_if(config.sub_models().begin(), config.sub_models().end(), - [=](const SubModelConfig& sub_model) { - return sub_model.name() == subModelName_; - }); + auto subModelConfig = std::find_if(config.sub_models().begin(), + config.sub_models().end(), + [=](const SubModelConfig& sub_model) { + return sub_model.name() == subModelName_; + }); bool useSubModel = (subModelConfig != config.sub_models().end()); CHECK_EQ(useSubModel, !subModelName_.empty()); if (useSubModel) { layers_.reserve(subModelConfig->layer_names_size()); for (const auto& layer_name : subModelConfig->layer_names()) { auto layer_config = - std::find_if(config.layers().begin(), config.layers().end(), + std::find_if(config.layers().begin(), + config.layers().end(), [=](const LayerConfig& layer_config) { return layer_config.name() == layer_name; }); @@ -176,14 +179,16 @@ void NeuralNetwork::init(const ModelConfig& config, ParamInitCallback callback, } } -void NeuralNetwork::connect(LayerPtr agentLayer, LayerPtr realLayer, +void NeuralNetwork::connect(LayerPtr agentLayer, + LayerPtr realLayer, int height) { AgentLayer* agent = dynamic_cast(agentLayer.get()); CHECK_NOTNULL(agent); agent->setRealLayer(realLayer, height); } -void NeuralNetwork::connect(std::string agentLayerName, NeuralNetwork* srcNN, +void NeuralNetwork::connect(std::string agentLayerName, + NeuralNetwork* srcNN, std::string realLayerName) { connect(this->getLayer(agentLayerName), srcNN->getLayer(realLayerName)); } @@ -195,7 +200,7 @@ void NeuralNetwork::prefetch(const std::vector& inArgs) { for (auto& para : parameters_) { if (para->isSparseRemoteUpdate()) { auto mat = dynamic_cast( - para->getMat(PARAMETER_VALUE).get()); + para->getMat(PARAMETER_VALUE).get()); para->clearGradient(); mat->clearIndices(); } @@ -217,10 +222,10 @@ void NeuralNetwork::prefetch(const std::vector& inArgs) { for (auto& para : parameters_) { if (para->isSparseRemoteUpdate()) { auto mat = dynamic_cast( - para->getMat(PARAMETER_VALUE).get()); + para->getMat(PARAMETER_VALUE).get()); mat->setupIndices(); auto matGrad = dynamic_cast( - para->getMat(PARAMETER_GRADIENT).get()); + para->getMat(PARAMETER_GRADIENT).get()); matGrad->reserveStore(); } } @@ -228,7 +233,8 @@ void NeuralNetwork::prefetch(const std::vector& inArgs) { } void NeuralNetwork::forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType) { + std::vector* outArgs, + PassType passType) { CHECK_EQ(inArgs.size(), dataLayers_.size()); outArgs->resize(outputLayers_.size()); for (size_t i = 0; i != dataLayers_.size(); ++i) { @@ -344,11 +350,11 @@ protected: Evaluator* NeuralNetwork::makeEvaluator() { CombinedEvaluator* combinedEvaluator = new CombinedEvaluator(); - auto subModelConfig = - std::find_if(config_.sub_models().begin(), config_.sub_models().end(), - [=](const SubModelConfig& sub_model) { - return sub_model.name() == subModelName_; - }); + auto subModelConfig = std::find_if(config_.sub_models().begin(), + config_.sub_models().end(), + [=](const SubModelConfig& sub_model) { + return sub_model.name() == subModelName_; + }); bool useSubModel = (subModelConfig != config_.sub_models().end()); CHECK_EQ(useSubModel, !subModelName_.empty()); if (useSubModel) { @@ -356,7 +362,8 @@ Evaluator* NeuralNetwork::makeEvaluator() { for (int i = 0; i < subModelConfig->evaluator_names_size(); ++i) { // find evaluator by name auto thisEvalConfig = std::find_if( - config_.evaluators().begin(), config_.evaluators().end(), + config_.evaluators().begin(), + config_.evaluators().end(), [=](const EvaluatorConfig& ecfg) { return ecfg.name() == subModelConfig->evaluator_names(i); }); @@ -385,17 +392,17 @@ void NeuralNetwork::setOutputGrad(const std::vector& args) { } } -extern NeuralNetwork* newCustomNerualNetwork( - const std::string& name, NeuralNetwork* network) __attribute__((weak)); +extern NeuralNetwork* newCustomNerualNetwork(const std::string& name, + NeuralNetwork* network) + __attribute__((weak)); -NeuralNetwork* NeuralNetwork::newNeuralNetwork( - const std::string& name, - NeuralNetwork* rootNetwork) { - if (newCustomNerualNetwork) { - return newCustomNerualNetwork(name, rootNetwork); - } else { - return new NeuralNetwork(name, rootNetwork); - } +NeuralNetwork* NeuralNetwork::newNeuralNetwork(const std::string& name, + NeuralNetwork* rootNetwork) { + if (newCustomNerualNetwork) { + return newCustomNerualNetwork(name, rootNetwork); + } else { + return new NeuralNetwork(name, rootNetwork); + } } } // namespace paddle diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.h b/paddle/gserver/gradientmachines/NeuralNetwork.h index 06c679a63c..55ef45c5ee 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.h +++ b/paddle/gserver/gradientmachines/NeuralNetwork.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -52,14 +51,15 @@ namespace paddle { * GPU value: NORMAL * GPU param: NORMAL */ -void parameterInitNN(int paramId, Parameter* para, +void parameterInitNN(int paramId, + Parameter* para, std::vector* sharedParams); - class NeuralNetwork : public GradientMachine { public: virtual void init( - const ModelConfig& config, ParamInitCallback callback = nullptr, + const ModelConfig& config, + ParamInitCallback callback = nullptr, const std::vector& parameterTypes = std::vector{PARAMETER_VALUE, PARAMETER_GRADIENT, @@ -76,13 +76,15 @@ public: * @param agentLayer The up-submodel's input agent layer. */ static void connect(LayerPtr agentLayer, LayerPtr realLayer, int height = 0); - void connect(std::string agentLayerName, NeuralNetwork* srcNN, + void connect(std::string agentLayerName, + NeuralNetwork* srcNN, std::string realLayerName); virtual void prefetch(const std::vector& inArgs); virtual void forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType); + std::vector* outArgs, + PassType passType); virtual void backward(const UpdateCallback& callback = nullptr); @@ -117,16 +119,15 @@ public: */ template void forEachLayer(T callback) { - for (auto & l : layers_) { + for (auto& l : layers_) { if (callback(l)) { break; } } } - static NeuralNetwork* newNeuralNetwork(const std::string& name = "", - NeuralNetwork* rootNetwork = nullptr); + NeuralNetwork* rootNetwork = nullptr); protected: /** @@ -139,8 +140,7 @@ protected: */ NeuralNetwork(std::string subModelName = "", NeuralNetwork* rootNetwork = nullptr) - : subModelName_(subModelName), - rootNetwork_(rootNetwork) {} + : subModelName_(subModelName), rootNetwork_(rootNetwork) {} std::string subModelName_; ModelConfig config_; diff --git a/paddle/gserver/gradientmachines/ParallelNeuralNetwork.cpp b/paddle/gserver/gradientmachines/ParallelNeuralNetwork.cpp index 22698f5867..9dbf418c31 100644 --- a/paddle/gserver/gradientmachines/ParallelNeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/ParallelNeuralNetwork.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "paddle/utils/Util.h" @@ -24,14 +23,16 @@ limitations under the License. */ namespace paddle { void ParallelNeuralNetwork::init( - const ModelConfig& config, ParamInitCallback callback, - const std::vector& parameterTypes, bool useGpu) { + const ModelConfig& config, + ParamInitCallback callback, + const std::vector& parameterTypes, + bool useGpu) { NeuralNetwork::init(config, callback, parameterTypes, useGpu); if (config.type() == "recurrent_nn") { LOG(FATAL) - << "You can not add `--parallel_nn=true` on the command line, " - << "parallel_nn training mode does not support the recurrent_nn model."; + << "You can not add `--parallel_nn=true` on the command line, " + << "parallel_nn training mode does not support the recurrent_nn model."; } useGpu_ = useGpu; @@ -54,8 +55,8 @@ void ParallelNeuralNetwork::addComputeThread(int deviceId) { } } - threads_.emplace_back(new ParallelThread(threads_.size(), deviceId, - deviceId >= 0 ? useGpu_ : false)); + threads_.emplace_back(new ParallelThread( + threads_.size(), deviceId, deviceId >= 0 ? useGpu_ : false)); } void ParallelNeuralNetwork::waitAllThread() { @@ -68,7 +69,8 @@ void ParallelNeuralNetwork::waitAllThread() { } } -void ParallelNeuralNetwork::dispatchByDeviceId(int deviceId, LayerPtr layer, +void ParallelNeuralNetwork::dispatchByDeviceId(int deviceId, + LayerPtr layer, TaskType task) { for (auto& thread : threads_) { if (thread->getDeviceId() == deviceId) { diff --git a/paddle/gserver/gradientmachines/ParallelNeuralNetwork.h b/paddle/gserver/gradientmachines/ParallelNeuralNetwork.h index 2a3db654f4..71488bc3b7 100644 --- a/paddle/gserver/gradientmachines/ParallelNeuralNetwork.h +++ b/paddle/gserver/gradientmachines/ParallelNeuralNetwork.h @@ -12,7 +12,6 @@ WITHOUT 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 "NeuralNetwork.h" @@ -35,24 +34,27 @@ enum TaskType { class ParallelNeuralNetwork : public NeuralNetwork { public: ParallelNeuralNetwork(std::string subModelName = "", - NeuralNetwork* rootNetwork = nullptr) - : NeuralNetwork(subModelName, rootNetwork) {} + NeuralNetwork *rootNetwork = nullptr) + : NeuralNetwork(subModelName, rootNetwork) {} virtual void init( - const ModelConfig &config, ParamInitCallback callback = nullptr, - const std::vector & - parameterTypes = std::vector{PARAMETER_VALUE, - PARAMETER_GRADIENT, - PARAMETER_MOMENTUM}, + const ModelConfig &config, + ParamInitCallback callback = nullptr, + const std::vector + ¶meterTypes = std::vector{PARAMETER_VALUE, + PARAMETER_GRADIENT, + PARAMETER_MOMENTUM}, bool useGpu = FLAGS_use_gpu); virtual void forward(const std::vector &inArgs, - std::vector *outArgs, PassType passType); + std::vector *outArgs, + PassType passType); virtual void backward(const UpdateCallback &callback = nullptr); void forwardBackward(const std::vector &inArgs, - std::vector *outArgs, PassType passType, + std::vector *outArgs, + PassType passType, const UpdateCallback &callback = NULL); virtual void start(const TrainerConfig &config, DataProviderPtr dataProvider); diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp index 340cd1b9f8..516b617576 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp @@ -53,8 +53,8 @@ typedef int (*DiyStartCalcProbCallback)(size_t nNodes, int* nodes); * path. * @NOTE: Return -INFINITY will DROP this path IMMEDIATELY!! */ -typedef real (*DiyCalcProbCallback)(int handler, size_t nNodes, int* nodes, - real curProb, bool atEos); +typedef real (*DiyCalcProbCallback)( + int handler, size_t nNodes, int* nodes, real curProb, bool atEos); /** * Finish Custom Calculation of Probability callback type. @@ -190,13 +190,16 @@ public: }; void RecurrentGradientMachine::init( - const ModelConfig& config, ParamInitCallback callback, - const std::vector& parameterTypes, bool useGpu) { + const ModelConfig& config, + ParamInitCallback callback, + const std::vector& parameterTypes, + bool useGpu) { NeuralNetwork::init(config, callback, parameterTypes, useGpu); useGpu_ = useGpu; auto subModelConfig = - std::find_if(config.sub_models().begin(), config.sub_models().end(), + std::find_if(config.sub_models().begin(), + config.sub_models().end(), [this](const SubModelConfig& sub_model) { return sub_model.name() == this->subModelName_; }); @@ -224,7 +227,8 @@ void RecurrentGradientMachine::init( memoryFrameLines_[i].layerName = memoryConfig.layer_name(); memoryFrameLines_[i].linkName = memoryConfig.link_name(); auto agentConfig = - std::find_if(config.layers().begin(), config.layers().end(), + std::find_if(config.layers().begin(), + config.layers().end(), [&memoryConfig](const LayerConfig& layerConfig) { return layerConfig.name() == memoryConfig.link_name(); }); @@ -413,7 +417,8 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, // sample is one sentence if (shareInlinkInfo) { CHECK_EQ(input1.getBatchSize(), batchSize); - CHECK(std::equal(starts, starts + numSequences + 1, + CHECK(std::equal(starts, + starts + numSequences + 1, input1.sequenceStartPositions->getData(false))); } } @@ -428,7 +433,8 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, const Argument& input1 = inFrameLines_[i].inLayer->getOutput(); CHECK_EQ((size_t)input1.getNumSubSequences(), numSubSequences); if (shareInlinkInfo) { - CHECK(std::equal(subStarts, subStarts + numSubSequences + 1, + CHECK(std::equal(subStarts, + subStarts + numSubSequences + 1, input1.subSequenceStartPositions->getData(false))); } } @@ -460,8 +466,10 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, // inFrameLine select rows in real layer one time for (size_t i = 0; i < inFrameLines_.size(); i++) { int curInlinkId = shareInlinkInfo ? 0 : i; - selectRowsOneTime(inFrameLines_[i].inLayer, info_[curInlinkId].allIds, - &(inFrameLines_[i].outArg), passType); + selectRowsOneTime(inFrameLines_[i].inLayer, + info_[curInlinkId].allIds, + &(inFrameLines_[i].outArg), + passType); } } resizeOrCreateFrames(maxSequenceLength_); @@ -472,15 +480,17 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, auto scatterAgent = dynamic_cast(memoryFrameLine.rootAgent.get()); createMemoryFrameInfo(&memoryFrameLine, passType); - scatterAgent->setRealLayerAndOutput( - memoryFrameLine.rootLayer, memoryFrameLine.outArg, - memoryFrameLine.allIds, - /* idIndex */ 0, memoryFrameLine.allIds->getSize()); + scatterAgent->setRealLayerAndOutput(memoryFrameLine.rootLayer, + memoryFrameLine.outArg, + memoryFrameLine.allIds, + /* idIndex */ 0, + memoryFrameLine.allIds->getSize()); if (memoryFrameLine.is_sequence) { // memoryConfig is sequence int size = memoryFrameLine.sequenceStartPositions->getSize(); scatterAgent->setSequenceStartPositions( memoryFrameLine.sequenceStartPositions, - /* seqStartPosIndex */ 0, size); + /* seqStartPosIndex */ 0, + size); } } } @@ -489,7 +499,8 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, auto gatherAgent = dynamic_cast(outFrameLine.agentLayer.get()); CHECK_NOTNULL(gatherAgent); - gatherAgent->copyIdAndSequenceInfo(input, info_[targetInfoInlinkId_].allIds, + gatherAgent->copyIdAndSequenceInfo(input, + info_[targetInfoInlinkId_].allIds, info_[targetInfoInlinkId_].idIndex); } @@ -504,15 +515,15 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, auto scatterAgent = dynamic_cast(inFrameLine.agents[i].get()); scatterAgent->setRealLayerAndOutput(inFrameLine.inLayer, - inFrameLine.outArg, info.allIds, - info.idIndex[i], idSize); + inFrameLine.outArg, + info.allIds, + info.idIndex[i], + idSize); if (hasSubseq) { // size: the length of subsequence - int size = - info.seqStartPosIndex[i + 1] - info.seqStartPosIndex[i]; - scatterAgent->setSequenceStartPositions(info.sequenceStartPositions, - info.seqStartPosIndex[i], - size); + int size = info.seqStartPosIndex[i + 1] - info.seqStartPosIndex[i]; + scatterAgent->setSequenceStartPositions( + info.sequenceStartPositions, info.seqStartPosIndex[i], size); } } @@ -547,7 +558,7 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, if (hasSubseq) { for (auto& outFrameLine : outFrameLines_) { CHECK(outFrameLine.frames[i]->getOutput().sequenceStartPositions) - << "In hierachical RNN, all out links should be from sequences."; + << "In hierachical RNN, all out links should be from sequences."; } } } @@ -573,8 +584,10 @@ void RecurrentGradientMachine::backward(const UpdateCallback& callback) { } void RecurrentGradientMachine::forwardBackward( - const std::vector& inArgs, std::vector* outArgs, - PassType passType, const UpdateCallback& callback) { + const std::vector& inArgs, + std::vector* outArgs, + PassType passType, + const UpdateCallback& callback) { LOG(FATAL) << "should not use this function"; } @@ -729,12 +742,15 @@ void RecurrentGradientMachine::createMemoryFrameInfo( // copy and check scatterId copyScattedId(allIds, &(*memoryFrameLine).allIds, input.getBatchSize()); // memoryFrameLine select rows in real layer one time - selectRowsOneTime((*memoryFrameLine).rootLayer, (*memoryFrameLine).allIds, - &(*memoryFrameLine).outArg, passType); + selectRowsOneTime((*memoryFrameLine).rootLayer, + (*memoryFrameLine).allIds, + &(*memoryFrameLine).outArg, + passType); } void RecurrentGradientMachine::copyScattedId(std::vector& srcIds, - IVectorPtr* dstIds, int size) { + IVectorPtr* dstIds, + int size) { int idSize = srcIds.size(); CHECK_EQ(idSize, size); IVector::resizeOrCreate(*dstIds, idSize, useGpu_); @@ -756,12 +772,12 @@ void RecurrentGradientMachine::selectRowsOneTime(LayerPtr layer, int height = realV->getHeight(); int width = realV->getWidth(); Matrix::resizeOrCreate( - arg->value, height, width, /* trans */ false, useGpu_); + arg->value, height, width, /* trans */ false, useGpu_); arg->value->zeroMem(); arg->value->selectRows(*realV, *allIds); if (passType != PASS_TEST) { - Matrix::resizeOrCreate(arg->grad, height, width, /* trans */ false, - useGpu_); + Matrix::resizeOrCreate( + arg->grad, height, width, /* trans */ false, useGpu_); arg->grad->zeroMem(); } } @@ -833,8 +849,8 @@ void RecurrentGradientMachine::generateSequence() { << "boot layer must be a sequence when is_sequence = true"; } } - NeuralNetwork::connect(memoryFrameLine.agents[0], memoryFrameLine.bootLayer, - ids.size()); + NeuralNetwork::connect( + memoryFrameLine.agents[0], memoryFrameLine.bootLayer, ids.size()); } // boot layer forward @@ -847,14 +863,19 @@ void RecurrentGradientMachine::generateSequence() { size_t resultNum = generator_.config.num_results_per_sample(); IVector::resizeOrCreate( generator_.outArg.ids, - generator_.config.max_num_frames() * numSequences * resultNum, false); + generator_.config.max_num_frames() * numSequences * resultNum, + false); if (resultNum > 1) { CHECK_LE(resultNum, static_cast(generator_.config.beam_size())); - Matrix::resizeOrCreate(generator_.outArg.in, /* height */ numSequences, - /* width */ resultNum, false, /* useGpu */ false); + Matrix::resizeOrCreate(generator_.outArg.in, + /* height */ numSequences, + /* width */ resultNum, + false, + /* useGpu */ false); } ICpuGpuVector::resizeOrCreate(generator_.outArg.sequenceStartPositions, - numSequences + 1, /* useGpu */ false); + numSequences + 1, + /* useGpu */ false); if (getBeamSize() > 1) { beamSearch(numSequences); } else { @@ -906,7 +927,8 @@ void RecurrentGradientMachine::oneWaySearch(size_t batchSize) { auto scatterAgent = dynamic_cast( memoryFrameLine.scatterAgents[machineCur].get()); scatterAgent->setRealLayer(memoryFrameLine.frames[machinePrev], - scatterIds, memoryFrameLine.is_sequence); + scatterIds, + memoryFrameLine.is_sequence); scatterAgent->forward(PASS_TEST); NeuralNetwork::connect(memoryFrameLine.agents[machineCur], memoryFrameLine.scatterAgents[machineCur]); @@ -948,7 +970,8 @@ void RecurrentGradientMachine::oneWaySearch(size_t batchSize) { starts[0] = 0; generator_.ids.clear(); for (size_t i = 0; i < batchSize; ++i) { - generator_.ids.insert(generator_.ids.end(), finalPaths[i].ids.begin(), + generator_.ids.insert(generator_.ids.end(), + finalPaths[i].ids.begin(), finalPaths[i].ids.end()); starts[i + 1] = generator_.ids.size(); batchMachineIdVec_.insert(batchMachineIdVec_.end(), @@ -999,8 +1022,11 @@ void RecurrentGradientMachine::forwardFrame(int machineCur) { if (useGpu_) { IVector::resizeOrCreate(cpuId_, ids->getSize(), false /* useGpu */); cpuId_->copyFrom(*ids); - Matrix::resizeOrCreate(cpuProb_, in->getHeight(), in->getWidth(), - false /* trans */, false /* useGpu */); + Matrix::resizeOrCreate(cpuProb_, + in->getHeight(), + in->getWidth(), + false /* trans */, + false /* useGpu */); cpuProb_->copyFrom(*in); IVector::resizeOrCreate(cpuEos_, eos->getSize(), false /* useGpu */); cpuEos_->copyFrom(*eos); @@ -1011,7 +1037,8 @@ void RecurrentGradientMachine::forwardFrame(int machineCur) { } } -void RecurrentGradientMachine::singlePathExpand(Path& curPath, size_t curPathId, +void RecurrentGradientMachine::singlePathExpand(Path& curPath, + size_t curPathId, std::vector& newPaths, size_t expandWidth) { int calc_id = @@ -1037,8 +1064,8 @@ void RecurrentGradientMachine::singlePathExpand(Path& curPath, size_t curPathId, if (id == -1) break; real newLogProb = generator_.config.log_prob() ? std::log(prob) : prob; - Path newPath(curPath, id, newLogProb, curPathId /*machineId*/, - k /*topIndex*/); + Path newPath( + curPath, id, newLogProb, curPathId /*machineId*/, k /*topIndex*/); if (this->beamSearchCtrlCallbacks_) { if (beamSearchCtrlCallbacks_->stopDetermineCandidates( newPath.seqId, newPath.ids, newPath.probHistory)) @@ -1104,7 +1131,8 @@ size_t RecurrentGradientMachine::beamShrink(std::vector& newPaths, } std::nth_element(newPaths.begin() + totalExpandCount, newPaths.begin() + totalExpandCount + minNewPathSize, - newPaths.end(), Path::greaterPath); + newPaths.end(), + Path::greaterPath); newPaths.resize(totalExpandCount + minNewPathSize); real minPathLogProb = @@ -1116,7 +1144,8 @@ size_t RecurrentGradientMachine::beamShrink(std::vector& newPaths, // Remove the already formed paths that are relatively short finalPaths_[seqId].erase( - std::remove_if(finalPaths_[seqId].begin(), finalPaths_[seqId].end(), + std::remove_if(finalPaths_[seqId].begin(), + finalPaths_[seqId].end(), [&](Path& p) { return p.logProb < minPathLogProb; }), finalPaths_[seqId].end()); for (auto p : finalPaths_[seqId]) { @@ -1139,7 +1168,8 @@ void RecurrentGradientMachine::fillGenOutputs() { size_t minFinalPathsSize = std::min(numResults, finalPaths_[i].size()); std::partial_sort(finalPaths_[i].begin(), finalPaths_[i].begin() + minFinalPathsSize, - finalPaths_[i].end(), Path::greaterPath); + finalPaths_[i].end(), + Path::greaterPath); finalPaths_[i].resize(minFinalPathsSize); } @@ -1154,8 +1184,8 @@ void RecurrentGradientMachine::fillGenOutputs() { for (size_t j = 0; j < finalPaths_[i].size(); ++j) { Path& path = finalPaths_[i][j]; generator_.ids.push_back(path.ids.size()); // sequence size - generator_.ids.insert(generator_.ids.end(), path.ids.begin(), - path.ids.end()); + generator_.ids.insert( + generator_.ids.end(), path.ids.begin(), path.ids.end()); generator_.ids.push_back(-1); // end of sequence probs[i * numResults + j] = path.logProb; @@ -1198,8 +1228,12 @@ void RecurrentGradientMachine::createDataOutlink( } for (size_t i = 0; i < dataArgsSize_; i++) { - dataArgs_[i].concat(dataArgsFrame_[i], machineIdVec, starts, useGpu_, - HPPL_STREAM_1, PASS_TEST); + dataArgs_[i].concat(dataArgsFrame_[i], + machineIdVec, + starts, + useGpu_, + HPPL_STREAM_1, + PASS_TEST); auto dataAgent = dynamic_cast(outFrameLines_[i + 1].agentLayer.get()); @@ -1235,7 +1269,8 @@ void RecurrentGradientMachine::beamSearch(size_t batchSize) { auto ptr = new ScopedCallbacks(beamSearchStatistics_->onEachStepStarted, - beamSearchStatistics_->onEachStepStoped, i); + beamSearchStatistics_->onEachStepStoped, + i); statisticsBlock.reset(ptr); } if (stopBeamSearch_) break; @@ -1246,7 +1281,9 @@ void RecurrentGradientMachine::beamSearch(size_t batchSize) { std::vector*> prefixes; prefixes.resize(paths.size()); std::transform( - paths.begin(), paths.end(), prefixes.begin(), + paths.begin(), + paths.end(), + prefixes.begin(), [](const Path& p) { return const_cast*>(&p.ids); }); beamSearchCtrlCallbacks_->beamSearchCandidateAdjust( prefixes, frames_[machineCur].get(), i); diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h index 6328213793..cb74a67e52 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h @@ -44,19 +44,22 @@ public: this->removeBeamSearchControlCallbacks(); } - virtual void init(const ModelConfig& config, ParamInitCallback callback, + virtual void init(const ModelConfig& config, + ParamInitCallback callback, const std::vector& parameterTypes, bool useGpu); virtual void prefetch(const std::vector& inArgs); virtual void forward(const std::vector& inArgs, - std::vector* outArgs, PassType passType); + std::vector* outArgs, + PassType passType); virtual void backward(const UpdateCallback& callback = nullptr); void forwardBackward(const std::vector& inArgs, - std::vector* outArgs, PassType passType, + std::vector* outArgs, + PassType passType, const UpdateCallback& callback); virtual void resetState() {} @@ -81,8 +84,8 @@ public: * beam search, so that user can customize different operations in different * beam search iterations. */ - typedef std::function*>&, - NeuralNetwork*, const int)> + typedef std::function*>&, NeuralNetwork*, const int)> BeamSearchCandidatesAdjustCallback; /** @@ -99,8 +102,9 @@ public: * * Return true if this prefix or candidate is expected to be dropped. */ - typedef std::function&, - const std::vector&)> DropCallback; + typedef std::function&, const std::vector&)> + DropCallback; /** * @brief NormOrDropNodeCallback @@ -115,8 +119,9 @@ public: * * The fourth parameter is the probability of the whole path. */ - typedef std::function&, - std::vector&, real*)> NormOrDropNodeCallback; + typedef std::function&, std::vector&, real*)> + NormOrDropNodeCallback; /** * @brief Register beam search control callbacks. Used for prediction. @@ -346,7 +351,8 @@ protected: * If hasSubseq, will also create scattered sequenceStartPositions infomation * for all realLayer of inFrameLines one time. */ - void createInFrameInfo(int inlinks_id, const Argument& input, + void createInFrameInfo(int inlinks_id, + const Argument& input, PassType passType); void createMemoryFrameInfo(MemoryFrameLine* memoryFrameLine, @@ -354,8 +360,10 @@ protected: void copyScattedId(std::vector& srcIds, IVectorPtr* dstIds, int size); - void selectRowsOneTime(LayerPtr layer, const IVectorPtr& allIds, - Argument* arg, PassType passType); + void selectRowsOneTime(LayerPtr layer, + const IVectorPtr& allIds, + Argument* arg, + PassType passType); void createSeqPos(const std::vector& sequenceStartPosition, ICpuGpuVectorPtr* sequenceStartPositions); @@ -459,7 +467,8 @@ private: * @param totalExpandCount : number of already shrinked paths in newPaths * @return size of retained paths at the end of a beam search iteration */ - size_t beamShrink(std::vector& newPaths, size_t seqId, + size_t beamShrink(std::vector& newPaths, + size_t seqId, size_t totalExpandCount); /* @@ -469,8 +478,10 @@ private: * @param curPathId : index of curPath in member newPaths * @param expandWidth : number of paths to be expanded */ - void singlePathExpand(Path& curPath, size_t curPathId, - std::vector& newPaths, size_t expandWidth); + void singlePathExpand(Path& curPath, + size_t curPathId, + std::vector& newPaths, + size_t expandWidth); /* * @brief A new beam search iteration. Each half-generated paths in previous diff --git a/paddle/gserver/layers/AddtoLayer.cpp b/paddle/gserver/layers/AddtoLayer.cpp index 083b1957f3..8a9aecfa19 100644 --- a/paddle/gserver/layers/AddtoLayer.cpp +++ b/paddle/gserver/layers/AddtoLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "AddtoLayer.h" #include "paddle/utils/Logging.h" diff --git a/paddle/gserver/layers/AddtoLayer.h b/paddle/gserver/layers/AddtoLayer.h index 0f2ca0bf19..883d186f3e 100644 --- a/paddle/gserver/layers/AddtoLayer.h +++ b/paddle/gserver/layers/AddtoLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -21,15 +20,16 @@ limitations under the License. */ namespace paddle { -/** - * This layer just simply add all input layers together, then activate - * the sum inputs. Each input of this layer should be the same size, +/** + * This layer just simply add all input layers together, then activate + * the sum inputs. Each input of this layer should be the same size, * which is also the output size of this layer. * \f[ * y=f(\sum_{i}x_i + b) * \f] - * where \f$y\f$ is output, \f$x\f$ is input, \f$b\f$ is bias, and \f$f\f$ is activation function. - * + * where \f$y\f$ is output, \f$x\f$ is input, \f$b\f$ is bias, and \f$f\f$ is + * activation function. + * * The config file api is addto_layer. */ class AddtoLayer : public Layer { @@ -41,20 +41,20 @@ public: ~AddtoLayer() {} - /** - * Intialization of AddtoLayer. + /** + * Intialization of AddtoLayer. */ bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); - /** + /** * Forward propagation. - * @note There is no weight matrix for each input, + * @note There is no weight matrix for each input, * because it just a simple add operation. */ void forward(PassType passType); - /** - * Backward propagation. + /** + * Backward propagation. */ void backward(const UpdateCallback& callback = nullptr); }; diff --git a/paddle/gserver/layers/AgentLayer.cpp b/paddle/gserver/layers/AgentLayer.cpp index 5e07446c71..eb89281cb1 100644 --- a/paddle/gserver/layers/AgentLayer.cpp +++ b/paddle/gserver/layers/AgentLayer.cpp @@ -44,8 +44,8 @@ void AgentLayer::forward(PassType passType) { if (realOutput.ids) { output_.ids->subVecFrom(*realOutput.ids, 0, numSamples_); } else { - output_.subArgFrom(realOutput, /* offset */ 0, numSamples_, getSize(), - useGpu_); + output_.subArgFrom( + realOutput, /* offset */ 0, numSamples_, getSize(), useGpu_); } } else { output_ = realOutput; @@ -64,9 +64,15 @@ void SequenceAgentLayer::forward(PassType passType) { int numRows = realOutput.sequenceStartPositions->getData(false)[numSamples_]; CHECK(!realOutput.ids) << "Not supported"; - output_.subArgFrom(realOutput, /* offset */ 0, numRows, getSize(), useGpu_, - /* trans */ false, /* seqFlag */ true, - /* seqStart */ 0, /* seqSize */ numSamples_ + 1); + output_.subArgFrom(realOutput, + /* offset */ 0, + numRows, + getSize(), + useGpu_, + /* trans */ false, + /* seqFlag */ true, + /* seqStart */ 0, + /* seqSize */ numSamples_ + 1); } else { output_ = realOutput; } @@ -107,7 +113,8 @@ void GatherAgentLayer::forward(PassType passType) { for (size_t i = 0; i < realLayers_.size(); ++i) { const MatrixPtr& realV = realLayers_[i]->getOutputValue(); idsVec_[i] = IVector::create(allIds_->getData() + idIndex_[i], - /* size */ realV->getHeight(), useGpu_); + /* size */ realV->getHeight(), + useGpu_); realV->addToRows(*outV, *idsVec_[i]); } } @@ -140,8 +147,8 @@ void ScatterAgentLayer::forward(PassType passType) { int width = this->getSize(); if (realOutArg_.value || realOutArg_.ids) { - output_.subArgFrom(realOutArg_, /* offset */ idIndex_, idSize_, width, - useGpu_); + output_.subArgFrom( + realOutArg_, /* offset */ idIndex_, idSize_, width, useGpu_); } else { // used in generation if (realLayer_->getOutput().ids) { IVector::resizeOrCreate(output_.ids, ids_->getSize(), useGpu_); @@ -223,8 +230,13 @@ void SequenceScatterAgentLayer::forward(PassType passType) { if (realOutArg_.value || realOutArg_.ids) { CHECK(realOutArg_.sequenceStartPositions); - output_.subArgFrom(realOutArg_, /* offset */ idIndex_, idSize_, width, - useGpu_, /* trans */ false, /* seqFlag */ true, + output_.subArgFrom(realOutArg_, + /* offset */ idIndex_, + idSize_, + width, + useGpu_, + /* trans */ false, + /* seqFlag */ true, /* seqStart */ seqStartPosIndex_, /* seqSize */ numSequences_); } else { @@ -247,8 +259,8 @@ void SequenceScatterAgentLayer::forward(PassType passType) { CHECK_NE(input.sequenceStartPositions.get(), output_.sequenceStartPositions.get()); - ICpuGpuVector::resizeOrCreate(output_.sequenceStartPositions, - numSequences + 1, false); + ICpuGpuVector::resizeOrCreate( + output_.sequenceStartPositions, numSequences + 1, false); int* outStarts = output_.sequenceStartPositions->getMutableData(false); ICpuGpuVector::resizeOrCreate(inputStartPos_, height, false); diff --git a/paddle/gserver/layers/AgentLayer.h b/paddle/gserver/layers/AgentLayer.h index 3d7bf55834..0186653c0f 100644 --- a/paddle/gserver/layers/AgentLayer.h +++ b/paddle/gserver/layers/AgentLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -82,7 +81,8 @@ public: bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); // call before addRealLayer - void copyIdAndSequenceInfo(const Argument& input, const IVectorPtr& allIds, + void copyIdAndSequenceInfo(const Argument& input, + const IVectorPtr& allIds, const std::vector& idIndex); // add one real layer, can call many times @@ -140,11 +140,12 @@ public: * * @param layer[input] realLayer * @param ids[input] row id in real layer - * @param copyId[input] whether to copy a cpu version of ids, - * false(default) in ScatterAgentLayer, and + * @param copyId[input] whether to copy a cpu version of ids, + * false(default) in ScatterAgentLayer, and * true in SequenceScatterAgentLayer. */ - void setRealLayer(LayerPtr layer, const std::vector& ids, + void setRealLayer(LayerPtr layer, + const std::vector& ids, bool copyId = false) { realLayer_ = layer; IVector::resizeOrCreate(ids_, ids.size(), useGpu_); @@ -161,8 +162,11 @@ public: // set real layer and output, [idIndex, idIndex + idSize) of *ids* // are selected row for realOutArg in realLayer - void setRealLayerAndOutput(LayerPtr layer, const Argument& outArg, - const IVectorPtr& ids, int idIndex, int idSize) { + void setRealLayerAndOutput(LayerPtr layer, + const Argument& outArg, + const IVectorPtr& ids, + int idIndex, + int idSize) { realLayer_ = layer; realOutArg_ = outArg; ids_ = ids; @@ -170,9 +174,9 @@ public: idSize_ = idSize; } - void setSequenceStartPositions( - const ICpuGpuVectorPtr& sequenceStartPositions, - int seqStartPosIndex, int numSequences) { + void setSequenceStartPositions(const ICpuGpuVectorPtr& sequenceStartPositions, + int seqStartPosIndex, + int numSequences) { realOutArg_.sequenceStartPositions = sequenceStartPositions; seqStartPosIndex_ = seqStartPosIndex; numSequences_ = numSequences; diff --git a/paddle/gserver/layers/AverageLayer.cpp b/paddle/gserver/layers/AverageLayer.cpp index 7401cdc9a5..af64e15fe3 100644 --- a/paddle/gserver/layers/AverageLayer.cpp +++ b/paddle/gserver/layers/AverageLayer.cpp @@ -75,8 +75,8 @@ void AverageLayer::backward(const UpdateCallback& callback) { // empty sequence continue; } - dataMtx_->setData(gradientData + starts[sequenceId] * dim, sequenceLength, - dim); + dataMtx_->setData( + gradientData + starts[sequenceId] * dim, sequenceLength, dim); outMtx_->setData(gradient + sequenceId * dim); switch (mode_) { case kAverage: { diff --git a/paddle/gserver/layers/BatchNormBaseLayer.cpp b/paddle/gserver/layers/BatchNormBaseLayer.cpp index 8052b35ec6..2d5bcff29f 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.cpp +++ b/paddle/gserver/layers/BatchNormBaseLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "Layer.h" #include "BatchNormBaseLayer.h" diff --git a/paddle/gserver/layers/BatchNormBaseLayer.h b/paddle/gserver/layers/BatchNormBaseLayer.h index 2302d1a8e0..d65882d39d 100644 --- a/paddle/gserver/layers/BatchNormBaseLayer.h +++ b/paddle/gserver/layers/BatchNormBaseLayer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Stat.h" @@ -21,14 +20,15 @@ limitations under the License. */ namespace paddle { /** - * @brief Batch normalization layer use to normalizes the input to across the batch. + * @brief Batch normalization layer use to normalizes the input to across the + * batch. * * By default, calculating global mean and variance statistics via a running * average in the training peroid. Then the pre-calculated global mean and * variance are used for testing. * * Moving mean and variance are located in Parameter object when constructing - * and the calculation will change them. Now we only save global mean and + * and the calculation will change them. Now we only save global mean and * variance of one thread in first node for GPU. * But the calculation in CPU is different, because parameters are shared by * multiple threads. Here using ShareCpuMatrix with lock to calculate. We @@ -41,8 +41,7 @@ namespace paddle { class BatchNormBaseLayer : public Layer { public: - explicit BatchNormBaseLayer(const LayerConfig& config) - : Layer(config) {} + explicit BatchNormBaseLayer(const LayerConfig& config) : Layer(config) {} ~BatchNormBaseLayer() {} @@ -55,8 +54,8 @@ public: virtual bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); - /** - * @brief Calculate feature map size. Some input uses frameHeight and + /** + * @brief Calculate feature map size. Some input uses frameHeight and * frameWidth to store feature size */ void calFeatureMapSize(); diff --git a/paddle/gserver/layers/BatchNormalizationLayer.cpp b/paddle/gserver/layers/BatchNormalizationLayer.cpp index b2921e6d40..e431c03311 100644 --- a/paddle/gserver/layers/BatchNormalizationLayer.cpp +++ b/paddle/gserver/layers/BatchNormalizationLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #ifndef PADDLE_ONLY_CPU #include "hl_batch_transpose.h" @@ -44,8 +43,8 @@ void BatchNormalizationLayer::calMeanAndStd(const MatrixPtr& mat) { tmpMat_->square(); savedInvVar_->zeroMem(); savedInvVar_->accumulateColSum(*tmpMat_); - savedInvVar_->mulScalar(1.0 / numSamples); // E[x^2] - savedInvVar_->addSquare(*savedMean_, -1.0); // E[x^2] - E^2[x] + savedInvVar_->mulScalar(1.0 / numSamples); // E[x^2] + savedInvVar_->addSquare(*savedMean_, -1.0); // E[x^2] - E^2[x] // Variance may be small negative value // because of the subtraction operation. @@ -104,17 +103,23 @@ void BatchNormalizationLayer::expandMat(const MatrixPtr& in, MatrixPtr& out) { #ifdef PADDLE_ONLY_CPU LOG(FATAL) << "paddle is compiled only for cpu"; #else - batchTranspose(in->getData(), out->getData(), imgPixels_, - channels_, batchSize); + batchTranspose( + in->getData(), out->getData(), imgPixels_, channels_, batchSize); #endif } else { for (size_t i = 0; i < batchSize; i++) { const MatrixPtr inTmp = - Matrix::create(in->getData() + i * imgPixels_ * channels_, channels_, - imgPixels_, false, useGpu_); + Matrix::create(in->getData() + i * imgPixels_ * channels_, + channels_, + imgPixels_, + false, + useGpu_); MatrixPtr outTmp = Matrix::create(out->getData() + i * imgPixels_ * channels_, - imgPixels_, channels_, false, useGpu_); + imgPixels_, + channels_, + false, + useGpu_); inTmp->transpose(outTmp, false); } } @@ -135,23 +140,27 @@ void BatchNormalizationLayer::shrinkMat(const MatrixPtr& in, MatrixPtr& out) { #ifdef PADDLE_ONLY_CPU LOG(FATAL) << "paddle is compiled only for cpu"; #else - batchTranspose(in->getData(), out->getData(), channels_, - imgPixels_, batchSize); + batchTranspose( + in->getData(), out->getData(), channels_, imgPixels_, batchSize); #endif } else { for (size_t i = 0; i < batchSize; i++) { const MatrixPtr inTmp = - Matrix::create(in->getData() + i * channels_ * imgPixels_, imgPixels_, - channels_, false, useGpu_); + Matrix::create(in->getData() + i * channels_ * imgPixels_, + imgPixels_, + channels_, + false, + useGpu_); MatrixPtr outTmp = - Matrix::create(out->getData() + i * imgPixels_ * channels_, channels_, - imgPixels_, useGpu_); + Matrix::create(out->getData() + i * imgPixels_ * channels_, + channels_, + imgPixels_, + useGpu_); inTmp->transpose(outTmp, false); } } } - void BatchNormalizationLayer::forward(PassType passType) { Layer::forward(passType); @@ -165,12 +174,12 @@ void BatchNormalizationLayer::forward(PassType passType) { useGlobalStats_ = config_.use_global_stats(); } - Matrix::resizeOrCreate(expandedIn_, batchSize * imgPixels_, channels_, false, - useGpu_); - Matrix::resizeOrCreate(normIn_, batchSize * imgPixels_, channels_, false, - useGpu_); - Matrix::resizeOrCreate(expandedOut_, batchSize * imgPixels_, channels_, false, - useGpu_); + Matrix::resizeOrCreate( + expandedIn_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + normIn_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + expandedOut_, batchSize * imgPixels_, channels_, false, useGpu_); expandMat(getInputValue(0), expandedIn_); if (useGlobalStats_) { @@ -184,7 +193,7 @@ void BatchNormalizationLayer::forward(PassType passType) { } normIn_->assign(*expandedIn_); - normIn_->addBias(*savedMean_, -1); // subtract mean. + normIn_->addBias(*savedMean_, -1); // subtract mean. normIn_->divRowVector(*savedInvVar_); // divide std. expandedOut_->assign(*normIn_); @@ -211,18 +220,18 @@ void BatchNormalizationLayer::backward(const UpdateCallback& callback) { Matrix::resizeOrCreate(meanGrad_, 1, channels_, false, useGpu_); Matrix::resizeOrCreate(stdGrad_, 1, channels_, false, useGpu_); - Matrix::resizeOrCreate(expandedInGrad_, batchSize * imgPixels_, channels_, - false, useGpu_); - Matrix::resizeOrCreate(inGrad_, batchSize, imgPixels_ * channels_, false, - useGpu_); - Matrix::resizeOrCreate(normInGrad_, batchSize * imgPixels_, channels_, false, - useGpu_); - Matrix::resizeOrCreate(expandedOutGrad_, batchSize * imgPixels_, channels_, - false, useGpu_); - Matrix::resizeOrCreate(tmpMat_, batchSize * imgPixels_, channels_, false, - useGpu_); - Matrix::resizeOrCreate(tmpGrad_, batchSize * imgPixels_, channels_, false, - useGpu_); + Matrix::resizeOrCreate( + expandedInGrad_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + inGrad_, batchSize, imgPixels_ * channels_, false, useGpu_); + Matrix::resizeOrCreate( + normInGrad_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + expandedOutGrad_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + tmpMat_, batchSize * imgPixels_, channels_, false, useGpu_); + Matrix::resizeOrCreate( + tmpGrad_, batchSize * imgPixels_, channels_, false, useGpu_); expandMat(getOutputGrad(), expandedOutGrad_); diff --git a/paddle/gserver/layers/BatchNormalizationLayer.h b/paddle/gserver/layers/BatchNormalizationLayer.h index 175b9a80e6..36925a5ed2 100644 --- a/paddle/gserver/layers/BatchNormalizationLayer.h +++ b/paddle/gserver/layers/BatchNormalizationLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/BilinearInterpLayer.cpp b/paddle/gserver/layers/BilinearInterpLayer.cpp index ac5f87be7a..c30e26dc03 100644 --- a/paddle/gserver/layers/BilinearInterpLayer.cpp +++ b/paddle/gserver/layers/BilinearInterpLayer.cpp @@ -40,10 +40,10 @@ size_t BilinearInterpLayer::getSize() { CHECK(inImgH_ > 0 && inImgW_ > 0); CHECK(numChannels_); - ratioH_ = (outImgH_ > 1) ? - static_cast(inImgH_ - 1) / (outImgH_ - 1) : 0.f; - ratioW_ = (outImgW_ > 1) ? - static_cast(inImgW_ - 1) / (outImgW_ - 1) : 0.f; + ratioH_ = + (outImgH_ > 1) ? static_cast(inImgH_ - 1) / (outImgH_ - 1) : 0.f; + ratioW_ = + (outImgW_ > 1) ? static_cast(inImgW_ - 1) / (outImgW_ - 1) : 0.f; getOutput().setFrameHeight(outImgH_); getOutput().setFrameWidth(outImgW_); @@ -74,21 +74,33 @@ void BilinearInterpLayer::forward(PassType passType) { MatrixPtr outV = getOutputValue(); { REGISTER_TIMER_INFO("FwBilinearInterpTimer", getName().c_str()); - outV->bilinearForward(*inV, inImgH_, inImgW_, outImgH_, outImgW_, - numChannels_, ratioH_, ratioW_); + outV->bilinearForward(*inV, + inImgH_, + inImgW_, + outImgH_, + outImgW_, + numChannels_, + ratioH_, + ratioW_); } } void BilinearInterpLayer::backward(const UpdateCallback& callback) { - (void) callback; + (void)callback; MatrixPtr inputG = getInputGrad(0); MatrixPtr outG = getOutputGrad(); { REGISTER_TIMER_INFO("BwBilinearInterpTimer", getName().c_str()); if (inputG) { - inputG->bilinearBackward(*outG, outImgH_, outImgW_, inImgH_, inImgW_, - numChannels_, ratioH_, ratioW_); + inputG->bilinearBackward(*outG, + outImgH_, + outImgW_, + inImgH_, + inImgW_, + numChannels_, + ratioH_, + ratioW_); } } } diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index 8da159def8..17d77879b2 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "BlockExpandLayer.h" #include "paddle/utils/Logging.h" @@ -52,7 +51,7 @@ size_t BlockExpandLayer::getBlockNum() { if (imgSizeW_ == 0) { imgSizeW_ = blockConf.img_size_x(); } - size_t tmpH = 2 * paddingH_ + imgSizeH_ - blockH_; + size_t tmpH = 2 * paddingH_ + imgSizeH_ - blockH_; outputH_ = (int)tmpH < 0 ? 1 : 1 + (tmpH + strideH_ - 1) / strideH_; size_t tmpW = 2 * paddingW_ + imgSizeW_ - blockW_; outputW_ = (int)tmpW < 0 ? 1 : 1 + (tmpW + strideW_ - 1) / strideW_; @@ -73,8 +72,8 @@ void BlockExpandLayer::forward(PassType passType) { MatrixPtr input = getPrev(0)->getOutputValue(); Matrix::resizeOrCreate(outVTrans_, blockSize, blockNum, false, useGpu_); - ICpuGpuVector::resizeOrCreate(out.sequenceStartPositions, - batchSize + 1, false); + ICpuGpuVector::resizeOrCreate( + out.sequenceStartPositions, batchSize + 1, false); IVector::resizeOrCreate(out.cpuSequenceDims, 2 * batchSize, false); int* start = out.sequenceStartPositions->getMutableData(false); int* dims = out.cpuSequenceDims->getData(); @@ -82,14 +81,29 @@ void BlockExpandLayer::forward(PassType passType) { outVTrans_->zeroMem(); /* expand each block as one row */ MatrixPtr inputTmp = - Matrix::create(input->getData() + i * input->getWidth(), 1, - input->getWidth(), false, useGpu_); - outVTrans_->convExpand(*inputTmp, imgSizeH_, imgSizeW_, channels_, blockH_, - blockW_, strideH_, strideW_, paddingH_, paddingW_, - outputH_, outputW_); + Matrix::create(input->getData() + i * input->getWidth(), + 1, + input->getWidth(), + false, + useGpu_); + outVTrans_->convExpand(*inputTmp, + imgSizeH_, + imgSizeW_, + channels_, + blockH_, + blockW_, + strideH_, + strideW_, + paddingH_, + paddingW_, + outputH_, + outputW_); MatrixPtr outVTmp = - Matrix::create(outV->getData() + i * blockNum * blockSize, blockNum, - blockSize, false, useGpu_); + Matrix::create(outV->getData() + i * blockNum * blockSize, + blockNum, + blockSize, + false, + useGpu_); outVTrans_->transpose(outVTmp, false); start[i] = i * blockNum; dims[2 * i] = outputH_; @@ -115,15 +129,32 @@ void BlockExpandLayer::backward(const UpdateCallback& callback) { for (size_t i = 0; i < batchSize; i++) { MatrixPtr gradTmp = - Matrix::create(grad->getData() + i * blockNum * blockSize, blockNum, - blockSize, false, useGpu_); + Matrix::create(grad->getData() + i * blockNum * blockSize, + blockNum, + blockSize, + false, + useGpu_); gradTmp->transpose(gradTrans, false); MatrixPtr preGradTmp = - Matrix::create(preGrad->getData() + i * preGrad->getWidth(), 1, - preGrad->getWidth(), false, useGpu_); - preGradTmp->convShrink(*gradTrans, imgSizeH_, imgSizeW_, channels_, blockH_, - blockW_, strideH_, strideW_, paddingH_, paddingW_, - outputH_, outputW_, 1.0, 1.0); + Matrix::create(preGrad->getData() + i * preGrad->getWidth(), + 1, + preGrad->getWidth(), + false, + useGpu_); + preGradTmp->convShrink(*gradTrans, + imgSizeH_, + imgSizeW_, + channels_, + blockH_, + blockW_, + strideH_, + strideW_, + paddingH_, + paddingW_, + outputH_, + outputW_, + 1.0, + 1.0); } } diff --git a/paddle/gserver/layers/BlockExpandLayer.h b/paddle/gserver/layers/BlockExpandLayer.h index f8f8172127..1496fb681a 100644 --- a/paddle/gserver/layers/BlockExpandLayer.h +++ b/paddle/gserver/layers/BlockExpandLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/CRFDecodingLayer.cpp b/paddle/gserver/layers/CRFDecodingLayer.cpp index d3dfbb7c80..8986741dc3 100644 --- a/paddle/gserver/layers/CRFDecodingLayer.cpp +++ b/paddle/gserver/layers/CRFDecodingLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "CRFDecodingLayer.h" namespace paddle { @@ -46,7 +45,8 @@ void CRFDecodingLayer::forward(PassType passType) { for (size_t i = 0; i < numSequences; ++i) { crf_->decode(output.value->getData() + numClasses_ * starts[i], - output_.ids->getData() + starts[i], starts[i + 1] - starts[i]); + output_.ids->getData() + starts[i], + starts[i + 1] - starts[i]); } if (inputLayers_.size() == 2) { diff --git a/paddle/gserver/layers/CRFDecodingLayer.h b/paddle/gserver/layers/CRFDecodingLayer.h index 005bffff6b..1914062011 100644 --- a/paddle/gserver/layers/CRFDecodingLayer.h +++ b/paddle/gserver/layers/CRFDecodingLayer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include diff --git a/paddle/gserver/layers/CRFLayer.cpp b/paddle/gserver/layers/CRFLayer.cpp index c1dcad2b5f..ed4f864ba9 100644 --- a/paddle/gserver/layers/CRFLayer.cpp +++ b/paddle/gserver/layers/CRFLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "CRFLayer.h" namespace paddle { @@ -73,12 +72,13 @@ void CRFLayer::forward(PassType passType) { crfs_.emplace_back(numClasses_, parameter_->getBuf(PARAMETER_VALUE)->getData(), parameter_->getBuf(PARAMETER_GRADIENT) - ? parameter_->getBuf(PARAMETER_GRADIENT)->getData() - : nullptr); + ? parameter_->getBuf(PARAMETER_GRADIENT)->getData() + : nullptr); } - output_.value->getData()[i] = crfs_[i].forward( - output.value->getData() + numClasses_ * starts[i], - label.ids->getData() + starts[i], starts[i + 1] - starts[i]); + output_.value->getData()[i] = + crfs_[i].forward(output.value->getData() + numClasses_ * starts[i], + label.ids->getData() + starts[i], + starts[i + 1] - starts[i]); } if (weightLayer_) { @@ -87,7 +87,7 @@ void CRFLayer::forward(PassType passType) { } } -void CRFLayer::backward(const UpdateCallback &callback) { +void CRFLayer::backward(const UpdateCallback& callback) { const Argument& output = getInput(0); const Argument& label = getInput(1); const int* starts = label.sequenceStartPositions->getData(false); @@ -100,7 +100,7 @@ void CRFLayer::backward(const UpdateCallback &callback) { starts[i + 1] - starts[i]); if (weightLayer_) { real weight = getInputValue(*weightLayer_)->getElement(i, 0); - MatrixPtr grad = output.grad->subRowMatrix(starts[i], starts[i+1]); + MatrixPtr grad = output.grad->subRowMatrix(starts[i], starts[i + 1]); grad->mulScalar(weight); } } diff --git a/paddle/gserver/layers/CRFLayer.h b/paddle/gserver/layers/CRFLayer.h index 58902a0d3b..21c7fc61e1 100644 --- a/paddle/gserver/layers/CRFLayer.h +++ b/paddle/gserver/layers/CRFLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -39,7 +38,7 @@ protected: ParameterPtr parameter_; std::vector crfs_; LayerPtr weightLayer_; // weight for each sequence - real coeff_; // weight for the layer + real coeff_; // weight for the layer }; } // namespace paddle diff --git a/paddle/gserver/layers/CTCLayer.cpp b/paddle/gserver/layers/CTCLayer.cpp index 6b9ffc5c74..be5d2c8c75 100644 --- a/paddle/gserver/layers/CTCLayer.cpp +++ b/paddle/gserver/layers/CTCLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "CTCLayer.h" /* Please reference the Chapter7 in @@ -71,8 +70,7 @@ void CTCLayer::forwardImp(const Argument& softmaxSeqs, resizeOutput(numSequences, 1); std::vector out(numSequences); - const int* labelSeqsStarts = - labelSeqs.sequenceStartPositions->getData(false); + const int* labelSeqsStarts = labelSeqs.sequenceStartPositions->getData(false); const int* softmaxSeqsStarts = softmaxSeqs.sequenceStartPositions->getData(false); @@ -81,22 +79,22 @@ void CTCLayer::forwardImp(const Argument& softmaxSeqs, ctcs_.emplace_back(numClasses_, normByTimes_); } out[i] = ctcs_[i].forward( - softmaxSeqs.value->getData() + numClasses_ * softmaxSeqsStarts[i], - softmaxSeqsStarts[i + 1] - softmaxSeqsStarts[i], - labelSeqs.ids->getData() + labelSeqsStarts[i], - labelSeqsStarts[i + 1] - labelSeqsStarts[i]); + softmaxSeqs.value->getData() + numClasses_ * softmaxSeqsStarts[i], + softmaxSeqsStarts[i + 1] - softmaxSeqsStarts[i], + labelSeqs.ids->getData() + labelSeqsStarts[i], + labelSeqsStarts[i + 1] - labelSeqsStarts[i]); } output_.value->copyFrom(out.data(), numSequences); } -void CTCLayer::backward(const UpdateCallback &callback) { +void CTCLayer::backward(const UpdateCallback& callback) { (void)callback; if (useGpu_) { backwardImp(callback, tmpCpuInput_[0], tmpCpuInput_[1]); - const_cast(getInput(0)). - resizeAndCopyFrom(tmpCpuInput_[0], true, HPPL_STREAM_DEFAULT); - const_cast(getInput(1)). - resizeAndCopyFrom(tmpCpuInput_[1], true, HPPL_STREAM_DEFAULT); + const_cast(getInput(0)) + .resizeAndCopyFrom(tmpCpuInput_[0], true, HPPL_STREAM_DEFAULT); + const_cast(getInput(1)) + .resizeAndCopyFrom(tmpCpuInput_[1], true, HPPL_STREAM_DEFAULT); } else { backwardImp(callback, getInput(0), getInput(1)); } @@ -107,8 +105,7 @@ void CTCLayer::backwardImp(const UpdateCallback& callback, const Argument& labelSeqs) { size_t numSequences = labelSeqs.sequenceStartPositions->getSize() - 1; - const int* labelSeqsStarts = - labelSeqs.sequenceStartPositions->getData(false); + const int* labelSeqsStarts = labelSeqs.sequenceStartPositions->getData(false); const int* softmaxSeqsStarts = softmaxSeqs.sequenceStartPositions->getData(false); diff --git a/paddle/gserver/layers/CTCLayer.h b/paddle/gserver/layers/CTCLayer.h index 49a059e43e..18ba12583b 100644 --- a/paddle/gserver/layers/CTCLayer.h +++ b/paddle/gserver/layers/CTCLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -28,7 +27,8 @@ public: void forwardImp(const Argument& softmaxSeqs, const Argument& labelSeqs); virtual void backward(const UpdateCallback& callback); void backwardImp(const UpdateCallback& callback, - const Argument& softmaxSeqs, const Argument& labelSeqs); + const Argument& softmaxSeqs, + const Argument& labelSeqs); protected: size_t numClasses_; diff --git a/paddle/gserver/layers/ConcatenateLayer.cpp b/paddle/gserver/layers/ConcatenateLayer.cpp index a986ec10b4..910eec8bbc 100644 --- a/paddle/gserver/layers/ConcatenateLayer.cpp +++ b/paddle/gserver/layers/ConcatenateLayer.cpp @@ -97,8 +97,7 @@ void ConcatenateLayer::backward(const UpdateCallback& callback) { */ class ConcatenateLayer2 : public Layer { public: - explicit ConcatenateLayer2(const LayerConfig& config) : - Layer(config) {} + explicit ConcatenateLayer2(const LayerConfig& config) : Layer(config) {} ~ConcatenateLayer2() {} @@ -130,8 +129,8 @@ bool ConcatenateLayer2::init(const LayerMap& layerMap, size_t startCol = 0; size_t endCol = 0; for (size_t i = 0; i < inputLayers_.size(); i++) { - projections_.emplace_back(Projection::create(config_.inputs(i).proj_conf(), - parameters_[i], useGpu_)); + projections_.emplace_back(Projection::create( + config_.inputs(i).proj_conf(), parameters_[i], useGpu_)); endCol += projections_[i]->getOutputSize(); projCol_.push_back(std::make_pair(startCol, endCol)); diff --git a/paddle/gserver/layers/ContextProjection.cpp b/paddle/gserver/layers/ContextProjection.cpp index 3b1498f7e9..30dbf168fb 100644 --- a/paddle/gserver/layers/ContextProjection.cpp +++ b/paddle/gserver/layers/ContextProjection.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "ContextProjection.h" @@ -21,7 +20,8 @@ namespace paddle { REGISTER_PROJECTION(context, ContextProjection); ContextProjection::ContextProjection(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu) + ParameterPtr parameter, + bool useGpu) : Projection(config, parameter, useGpu) { CHECK(config.has_context_start()); CHECK(config.has_context_length()); @@ -44,10 +44,13 @@ void ContextProjection::resetState() { CHECK_LE(config_.context_start() + config_.context_length(), 1) << "state is not allowed for future context"; if (config_.context_start() >= 0) return; - Matrix::resizeOrCreate(state_, -config_.context_start(), config_.input_size(), + Matrix::resizeOrCreate(state_, + -config_.context_start(), + config_.input_size(), false, // trans useGpu_); - Matrix::resizeOrCreate(state2_, -config_.context_start(), + Matrix::resizeOrCreate(state2_, + -config_.context_start(), config_.input_size(), false, // trans useGpu_); @@ -78,8 +81,7 @@ void ContextProjection::forward() { CHECK(in_->value); CHECK(in_->sequenceStartPositions); - auto startPositions = - in_->sequenceStartPositions->getVector(useGpu_); + auto startPositions = in_->sequenceStartPositions->getVector(useGpu_); int64_t inputDim = in_->value->getWidth(); int64_t dim = out_->value->getWidth(); @@ -88,9 +90,13 @@ void ContextProjection::forward() { REGISTER_TIMER_INFO("ContextProjectionForward", getName().c_str()); bool isPadding = config_.trainable_padding(); out_->value->contextProjectionForward( - in_->value, state_ ? state_ : isPadding ? weight_->getW() : nullptr, - *startPositions, config_.context_length(), config_.context_start(), - beginPad_, state_ ? true : isPadding); + in_->value, + state_ ? state_ : isPadding ? weight_->getW() : nullptr, + *startPositions, + config_.context_length(), + config_.context_start(), + beginPad_, + state_ ? true : isPadding); if (state_ && config_.context_start() < 0) { CHECK_EQ(1, in_->getNumSequences()); @@ -116,27 +122,35 @@ void ContextProjection::backward(const UpdateCallback& callback) { int64_t inputDim = in_->value->getWidth(); int64_t dim = out_->value->getWidth(); CHECK_EQ(dim, inputDim * config_.context_length()); - auto startPositions = - in_->sequenceStartPositions->getVector(useGpu_); + auto startPositions = in_->sequenceStartPositions->getVector(useGpu_); REGISTER_TIMER_INFO("ContextProjectionBackward", getName().c_str()); bool isPadding = config_.trainable_padding(); if (!out_->grad->useGpu()) { out_->grad->contextProjectionBackward( - in_->grad, isPadding ? weight_->getWGrad() : nullptr, *startPositions, - config_.context_length(), config_.context_start(), beginPad_, + in_->grad, + isPadding ? weight_->getWGrad() : nullptr, + *startPositions, + config_.context_length(), + config_.context_start(), + beginPad_, isPadding); } else { if (in_->grad) { - out_->grad->contextProjectionBackwardData(in_->grad, *startPositions, + out_->grad->contextProjectionBackwardData(in_->grad, + *startPositions, config_.context_length(), config_.context_start()); } if (isPadding && weight_->getWGrad()) { out_->grad->contextProjectionBackwardWeight( - weight_->getWGrad(), *startPositions, config_.context_length(), - config_.context_start(), weight_->getWGrad()->getHeight(), beginPad_); + weight_->getWGrad(), + *startPositions, + config_.context_length(), + config_.context_start(), + weight_->getWGrad()->getHeight(), + beginPad_); } } diff --git a/paddle/gserver/layers/ContextProjection.h b/paddle/gserver/layers/ContextProjection.h index 0786ee28f2..188dec0fb3 100644 --- a/paddle/gserver/layers/ContextProjection.h +++ b/paddle/gserver/layers/ContextProjection.h @@ -12,7 +12,6 @@ WITHOUT 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 "Projection.h" @@ -50,7 +49,8 @@ public: * and if it is set, constructor will set learned weight, which is used to * pad output. */ - ContextProjection(const ProjectionConfig& config, ParameterPtr parameter, + ContextProjection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu); virtual void forward(); virtual void backward(const UpdateCallback& callback); diff --git a/paddle/gserver/layers/ConvBaseLayer.cpp b/paddle/gserver/layers/ConvBaseLayer.cpp index 6bc3b3b801..7637e245a3 100644 --- a/paddle/gserver/layers/ConvBaseLayer.cpp +++ b/paddle/gserver/layers/ConvBaseLayer.cpp @@ -22,7 +22,8 @@ bool ConvBaseLayer::init(const LayerMap& layerMap, /* Initialize the basic parent class */ Layer::init(layerMap, parameterMap); isDeconv_ = (config_.type() == "exconv" || config_.type() == "cudnn_conv") - ? false : true; + ? false + : true; /* Initialize the convolutional layer parameter */ numFilters_ = config_.num_filters(); @@ -88,33 +89,25 @@ size_t ConvBaseLayer::calOutputSize() { auto setLayerSize = [&](IntV& inH, IntV& inW, IntV& outH, IntV& outW) { for (size_t i = 0; i < inputLayers_.size(); i++) { - inH.push_back(inputLayers_[i]->getOutput().getFrameHeight()); - inW.push_back(inputLayers_[i]->getOutput().getFrameWidth()); - if (isDeconv_) { - if (inH[i] == 0) - inH[i] = config_.inputs(i).conv_conf().output_x(); - if (inW[i] == 0) - inW[i] = config_.inputs(i).conv_conf().output_x(); - outH.push_back( - imageSize(inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], - caffeMode_)); - outW.push_back( - imageSize(inW[i], filterSize_[i], padding_[i], stride_[i], - caffeMode_)); - } else { - if (inH[i] == 0) - inH[i] = config_.inputs(i).conv_conf().img_size(); - if (inW[i] == 0) - inW[i] = config_.inputs(i).conv_conf().img_size(); - outH.push_back( - outputSize(inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], - caffeMode_)); - outW.push_back( - outputSize(inW[i], filterSize_[i], padding_[i], stride_[i], - caffeMode_)); - } - CHECK_EQ(outH[i], outH[0]); - CHECK_EQ(outW[i], outW[0]); + inH.push_back(inputLayers_[i]->getOutput().getFrameHeight()); + inW.push_back(inputLayers_[i]->getOutput().getFrameWidth()); + if (isDeconv_) { + if (inH[i] == 0) inH[i] = config_.inputs(i).conv_conf().output_x(); + if (inW[i] == 0) inW[i] = config_.inputs(i).conv_conf().output_x(); + outH.push_back(imageSize( + inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], caffeMode_)); + outW.push_back(imageSize( + inW[i], filterSize_[i], padding_[i], stride_[i], caffeMode_)); + } else { + if (inH[i] == 0) inH[i] = config_.inputs(i).conv_conf().img_size(); + if (inW[i] == 0) inW[i] = config_.inputs(i).conv_conf().img_size(); + outH.push_back(outputSize( + inH[i], filterSizeY_[i], paddingY_[i], strideY_[i], caffeMode_)); + outW.push_back(outputSize( + inW[i], filterSize_[i], padding_[i], stride_[i], caffeMode_)); + } + CHECK_EQ(outH[i], outH[0]); + CHECK_EQ(outW[i], outW[0]); } getOutput().setFrameHeight(outH[0]); getOutput().setFrameWidth(outW[0]); diff --git a/paddle/gserver/layers/ConvBaseLayer.h b/paddle/gserver/layers/ConvBaseLayer.h index b80cab8995..85f57dbe0b 100644 --- a/paddle/gserver/layers/ConvBaseLayer.h +++ b/paddle/gserver/layers/ConvBaseLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/ConvOperator.cpp b/paddle/gserver/layers/ConvOperator.cpp index 2d9c892fe5..9b8e18b1ba 100644 --- a/paddle/gserver/layers/ConvOperator.cpp +++ b/paddle/gserver/layers/ConvOperator.cpp @@ -155,9 +155,15 @@ void ConvOperator::reshape(int batchSize) { reshapeImageDescriptors(); if (!isSelectAlgo_) { - hl_conv_workspace(inputDesc_, outputDesc_, filterDesc_, convDesc_, - &fwdAlgo_, &fwdLimitBytes_, &bwdDataAlgo_, - &bwdDataLimitBytes_, &bwdFilterAlgo_, + hl_conv_workspace(inputDesc_, + outputDesc_, + filterDesc_, + convDesc_, + &fwdAlgo_, + &fwdLimitBytes_, + &bwdDataAlgo_, + &bwdDataLimitBytes_, + &bwdFilterAlgo_, &bwdFilterLimitBytes_); size_t maxWorkSpace = 0; @@ -171,26 +177,48 @@ void ConvOperator::reshape(int batchSize) { } void ConvOperator::computeConvSizes() { - hl_create_filter_descriptor(&filterDesc_, channels_, numFilters_, - filterSizeY_, filterSize_); + hl_create_filter_descriptor( + &filterDesc_, channels_, numFilters_, filterSizeY_, filterSize_); hl_create_tensor_descriptor(&inputDesc_); int outputX = outputSize(imgSize_, filterSize_, padding_, stride_, caffeMode_); CHECK_EQ(outputX, outputX_); hl_create_tensor_descriptor(&outputDesc_); - hl_create_convolution_descriptor(&convDesc_, inputDesc_, filterDesc_, - paddingY_, padding_, strideY_, stride_); + hl_create_convolution_descriptor(&convDesc_, + inputDesc_, + filterDesc_, + paddingY_, + padding_, + strideY_, + stride_); } void ConvOperator::reshapeImageDescriptors() { - hl_tensor_reshape(inputDesc_, 1, channels_, imageH_, imageW_, - channels_ * imageH_ * imageW_, imageH_ * imageW_, imageW_, + hl_tensor_reshape(inputDesc_, + 1, + channels_, + imageH_, + imageW_, + channels_ * imageH_ * imageW_, + imageH_ * imageW_, + imageW_, 1); - hl_tensor_reshape(outputDesc_, 1, numFilters_, outputH_, outputW_, - numFilters_ * outputH_ * outputW_, outputH_ * outputW_, - outputW_, 1); - hl_reset_convolution_descriptor(convDesc_, inputDesc_, filterDesc_, paddingY_, - padding_, strideY_, stride_); + hl_tensor_reshape(outputDesc_, + 1, + numFilters_, + outputH_, + outputW_, + numFilters_ * outputH_ * outputW_, + outputH_ * outputW_, + outputW_, + 1); + hl_reset_convolution_descriptor(convDesc_, + inputDesc_, + filterDesc_, + paddingY_, + padding_, + strideY_, + stride_); inputOffset_ = channels_ * imageH_ * imageW_; outputOffset_ = numFilters_ * outputH_ * outputW_; weightOffset_ = numFilters_ * channels_ * filterSize_ * filterSize_; @@ -220,17 +248,27 @@ void ConvOperator::forward() { reshape(batchSize); CHECK_EQ(ins_[1]->value->getHeight(), batchSize); checkFilterSize(ins_[1]->value); - Matrix::resizeOrCreate(out_->value, batchSize, - outputH_ * outputW_ * numFilters_, false, useGpu_); + Matrix::resizeOrCreate(out_->value, + batchSize, + outputH_ * outputW_ * numFilters_, + false, + useGpu_); { AsyncGpuBlock block; for (size_t batchId = 0; batchId < batchSize; ++batchId) { real *inputData = ins_[0]->value->getData() + inputOffset_ * batchId; real *wgtData = ins_[1]->value->getData() + weightOffset_ * batchId; real *outData = out_->value->getData() + outputOffset_ * batchId; - hl_convolution_forward(inputDesc_, inputData, outputDesc_, outData, - filterDesc_, wgtData, convDesc_, workSpace_, - workSpaceInBytes_, fwdAlgo_); + hl_convolution_forward(inputDesc_, + inputData, + outputDesc_, + outData, + filterDesc_, + wgtData, + convDesc_, + workSpace_, + workSpaceInBytes_, + fwdAlgo_); } } } @@ -244,9 +282,15 @@ void ConvOperator::backward() { if (ins_[1]->grad) { real *inputData = ins_[0]->value->getData() + inputOffset_ * batchId; real *weightGrad = ins_[1]->grad->getData() + weightOffset_ * batchId; - hl_convolution_backward_filter(inputDesc_, inputData, outputDesc_, - outGrad, filterDesc_, weightGrad, - convDesc_, workSpace_, workSpaceInBytes_, + hl_convolution_backward_filter(inputDesc_, + inputData, + outputDesc_, + outGrad, + filterDesc_, + weightGrad, + convDesc_, + workSpace_, + workSpaceInBytes_, bwdFilterAlgo_); } @@ -254,9 +298,16 @@ void ConvOperator::backward() { if (NULL != preGrad) { real *inputGrad = preGrad->getData() + inputOffset_ * batchId; real *wgtData = ins_[1]->value->getData() + weightOffset_ * batchId; - hl_convolution_backward_data( - inputDesc_, inputGrad, outputDesc_, outGrad, filterDesc_, wgtData, - convDesc_, workSpace_, workSpaceInBytes_, bwdDataAlgo_); + hl_convolution_backward_data(inputDesc_, + inputGrad, + outputDesc_, + outGrad, + filterDesc_, + wgtData, + convDesc_, + workSpace_, + workSpaceInBytes_, + bwdDataAlgo_); } } } diff --git a/paddle/gserver/layers/ConvProjection.cpp b/paddle/gserver/layers/ConvProjection.cpp index d1ce53fe26..4ab0a1dc84 100644 --- a/paddle/gserver/layers/ConvProjection.cpp +++ b/paddle/gserver/layers/ConvProjection.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "ConvProjection.h" @@ -20,12 +19,12 @@ namespace paddle { REGISTER_PROJECTION(conv, ConvProjection); -ThreadLocalD> ConvProjection::convMem_; +ThreadLocalD> ConvProjection::convMem_; -ConvProjection::ConvProjection(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu) +ConvProjection::ConvProjection(const ProjectionConfig &config, + ParameterPtr parameter, + bool useGpu) : Projection(config, parameter, useGpu) { - CHECK(useGpu); // only support GPU getConvParams(); initCudnn(); @@ -59,12 +58,17 @@ void ConvProjection::getConvParams() { } void ConvProjection::initCudnn() { - hl_create_filter_descriptor(&filterDesc_, channels_, numFilters_, - filterH_, filterW_); + hl_create_filter_descriptor( + &filterDesc_, channels_, numFilters_, filterH_, filterW_); hl_create_tensor_descriptor(&inputDesc_); hl_create_tensor_descriptor(&outputDesc_); - hl_create_convolution_descriptor(&convDesc_, inputDesc_, filterDesc_, - paddingH_, paddingW_, strideH_, strideW_); + hl_create_convolution_descriptor(&convDesc_, + inputDesc_, + filterDesc_, + paddingH_, + paddingW_, + strideH_, + strideW_); // initialize all to default algorithms fwdAlgo_ = 0; @@ -80,11 +84,22 @@ void ConvProjection::initCudnn() { } void ConvProjection::reshapeTensorDesc(int batchSize) { - hl_tensor_reshape(inputDesc_, batchSize, channels_, imageH_, imageW_, - channels_ * imageH_ * imageW_, imageH_ * imageW_, - imageW_, 1); - hl_reset_convolution_descriptor(convDesc_, inputDesc_, filterDesc_, - paddingH_, paddingW_, strideH_, strideW_); + hl_tensor_reshape(inputDesc_, + batchSize, + channels_, + imageH_, + imageW_, + channels_ * imageH_ * imageW_, + imageH_ * imageW_, + imageW_, + 1); + hl_reset_convolution_descriptor(convDesc_, + inputDesc_, + filterDesc_, + paddingH_, + paddingW_, + strideH_, + strideW_); // The stride between two consecutive images in ConvProjection may not be 1, // for example, in the case of layer ConcatenateLayer2 with two @@ -98,8 +113,15 @@ void ConvProjection::reshapeTensorDesc(int batchSize) { nStride = out_->value->getStride(); } - hl_tensor_reshape(outputDesc_, batchSize, numFilters_, outputH_, outputW_, - nStride, outputH_ * outputW_, outputW_, 1); + hl_tensor_reshape(outputDesc_, + batchSize, + numFilters_, + outputH_, + outputW_, + nStride, + outputH_ * outputW_, + outputW_, + 1); } void ConvProjection::reshape(int batchSize) { @@ -111,20 +133,24 @@ void ConvProjection::reshape(int batchSize) { if (!isSelectAlgo_) { reshapeTensorDesc(batchSize); - hl_conv_workspace(inputDesc_, outputDesc_, filterDesc_, - convDesc_, &fwdAlgo_, &fwdLimitBytes_, - &bwdDataAlgo_, &bwdDataLimitBytes_, - &bwdFilterAlgo_, &bwdFilterLimitBytes_); + hl_conv_workspace(inputDesc_, + outputDesc_, + filterDesc_, + convDesc_, + &fwdAlgo_, + &fwdLimitBytes_, + &bwdDataAlgo_, + &bwdDataLimitBytes_, + &bwdFilterAlgo_, + &bwdFilterLimitBytes_); size_t maxWorkSpace = 0; maxWorkSpace = std::max(fwdLimitBytes_, bwdDataLimitBytes_); maxWorkSpace = std::max(maxWorkSpace, bwdFilterLimitBytes_); workSpaceInBytes_ = maxWorkSpace; - VLOG(3) << getName() << " Fwd / BwdData / BwdFilter algo: " << fwdAlgo_ - << " / " << bwdDataAlgo_ - << " / " << bwdFilterAlgo_; + << " / " << bwdDataAlgo_ << " / " << bwdFilterAlgo_; } isSelectAlgo_ = true; @@ -134,7 +160,7 @@ void ConvProjection::forward() { int batchSize = in_->value->getHeight(); reshape(batchSize); - void* workSpace = NULL; + void *workSpace = NULL; if (workSpaceInBytes_ > 0) { workSpace = getSpaceBytes(workSpaceInBytes_); } @@ -145,17 +171,23 @@ void ConvProjection::forward() { real *inputData = in_->value->getData() + g * inputOffset_; real *wgtData = weight_->getW()->getData() + g * weightOffset_; real *outData = out_->value->getData() + g * outputOffset_; - hl_convolution_forward(inputDesc_, inputData, outputDesc_, - outData, filterDesc_, wgtData, - convDesc_, workSpace, - fwdLimitBytes_, fwdAlgo_); + hl_convolution_forward(inputDesc_, + inputData, + outputDesc_, + outData, + filterDesc_, + wgtData, + convDesc_, + workSpace, + fwdLimitBytes_, + fwdAlgo_); } } -void ConvProjection::backward(const UpdateCallback& callback) { +void ConvProjection::backward(const UpdateCallback &callback) { REGISTER_TIMER_INFO("CudnnConvBpTimer", getName().c_str()); - void* workSpace = NULL; + void *workSpace = NULL; if (workSpaceInBytes_ > 0) { workSpace = getSpaceBytes(workSpaceInBytes_); } @@ -165,35 +197,47 @@ void ConvProjection::backward(const UpdateCallback& callback) { if (weight_->getWGrad()) { real *inputData = in_->value->getData() + g * inputOffset_; real *weightGrad = weight_->getWGrad()->getData() + g * weightOffset_; - hl_convolution_backward_filter( - inputDesc_, inputData, outputDesc_, outGrad, filterDesc_, - weightGrad, convDesc_, workSpace, bwdFilterLimitBytes_, - bwdFilterAlgo_); + hl_convolution_backward_filter(inputDesc_, + inputData, + outputDesc_, + outGrad, + filterDesc_, + weightGrad, + convDesc_, + workSpace, + bwdFilterLimitBytes_, + bwdFilterAlgo_); } MatrixPtr preGrad = in_->grad; if (NULL != preGrad) { real *inputGrad = preGrad->getData() + g * inputOffset_; - real *wgtData = weight_->getW()->getData() + g* weightOffset_; - hl_convolution_backward_data( - inputDesc_, inputGrad, outputDesc_, outGrad, filterDesc_, - wgtData, convDesc_, workSpace, bwdDataLimitBytes_, - bwdDataAlgo_); + real *wgtData = weight_->getW()->getData() + g * weightOffset_; + hl_convolution_backward_data(inputDesc_, + inputGrad, + outputDesc_, + outGrad, + filterDesc_, + wgtData, + convDesc_, + workSpace, + bwdDataLimitBytes_, + bwdDataAlgo_); } } weight_->getParameterPtr()->incUpdate(callback); } -void* ConvProjection::getSpaceBytes(size_t size) { - std::vector& convMem = *convMem_; +void *ConvProjection::getSpaceBytes(size_t size) { + std::vector &convMem = *convMem_; if (convMem.empty()) { int numDevices = hl_get_device_count(); convMem.resize(numDevices); } int devId = hl_get_device(); - MemoryHandle** localMem = &(convMem[devId]); + MemoryHandle **localMem = &(convMem[devId]); if (NULL == *localMem || size > (*localMem)->getAllocSize()) { *localMem = new GpuMemoryHandle(size); } diff --git a/paddle/gserver/layers/ConvProjection.h b/paddle/gserver/layers/ConvProjection.h index d0bfe9a6ed..779fe1455a 100644 --- a/paddle/gserver/layers/ConvProjection.h +++ b/paddle/gserver/layers/ConvProjection.h @@ -27,7 +27,8 @@ public: /** * Constructor. */ - ConvProjection(const ProjectionConfig& config, ParameterPtr parameter, + ConvProjection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu); ~ConvProjection(); @@ -47,9 +48,15 @@ protected: imageW_ = in_->getFrameWidth(); if (imageH_ == 0) imageH_ = configImgH_; if (imageW_ == 0) imageW_ = configImgW_; - outputH_ = outputSize(imageH_, filterH_, paddingH_, strideH_, + outputH_ = outputSize(imageH_, + filterH_, + paddingH_, + strideH_, /* caffeMode */ true); - outputW_ = outputSize(imageW_, filterW_, paddingW_, strideW_, + outputW_ = outputSize(imageW_, + filterW_, + paddingW_, + strideW_, /* caffeMode */ true); const_cast(out_)->setFrameHeight(outputH_); diff --git a/paddle/gserver/layers/ConvShiftLayer.cpp b/paddle/gserver/layers/ConvShiftLayer.cpp index 6b3881e3cc..6e77c1f14e 100644 --- a/paddle/gserver/layers/ConvShiftLayer.cpp +++ b/paddle/gserver/layers/ConvShiftLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" diff --git a/paddle/gserver/layers/ConvexCombinationLayer.cpp b/paddle/gserver/layers/ConvexCombinationLayer.cpp index a81cf939af..7e1fef8bc6 100644 --- a/paddle/gserver/layers/ConvexCombinationLayer.cpp +++ b/paddle/gserver/layers/ConvexCombinationLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -70,12 +69,21 @@ bool ConvexCombinationLayer::init(const LayerMap& layerMap, CHECK_EQ(weightDim * dataDim, inputLayers_[1]->getSize()) << "Dimension mismatch"; - tmpRow0 = Matrix::create(nullptr, /* height= */ 1, weightDim, - /* trans= */ false, useGpu_); - tmpRow1 = Matrix::create(nullptr, /* height= */ 1, dataDim, - /* trans= */ false, useGpu_); - tmpMtx0 = Matrix::create(nullptr, /* height= */ weightDim, dataDim, - /* trans= */ false, useGpu_); + tmpRow0 = Matrix::create(nullptr, + /* height= */ 1, + weightDim, + /* trans= */ false, + useGpu_); + tmpRow1 = Matrix::create(nullptr, + /* height= */ 1, + dataDim, + /* trans= */ false, + useGpu_); + tmpMtx0 = Matrix::create(nullptr, + /* height= */ weightDim, + dataDim, + /* trans= */ false, + useGpu_); return true; } diff --git a/paddle/gserver/layers/CosSimLayer.cpp b/paddle/gserver/layers/CosSimLayer.cpp index 05a70aeff5..894cb5b0d8 100644 --- a/paddle/gserver/layers/CosSimLayer.cpp +++ b/paddle/gserver/layers/CosSimLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "CosSimLayer.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" @@ -57,9 +56,12 @@ void CosSimLayer::backward(const UpdateCallback& callback) { REGISTER_TIMER_INFO("CosBpAtvTimer", getName().c_str()); MatrixPtr outG = this->getOutputGrad(); - outG->cosSimDerivative(*this->getOutputValue(), *getInputValue(0), - *getInputValue(1), *getInputGrad(0), - *getInputGrad(1), config_.cos_scale()); + outG->cosSimDerivative(*this->getOutputValue(), + *getInputValue(0), + *getInputValue(1), + *getInputGrad(0), + *getInputGrad(1), + config_.cos_scale()); } } diff --git a/paddle/gserver/layers/CosSimLayer.h b/paddle/gserver/layers/CosSimLayer.h index 65eb807ab2..bc47998c11 100644 --- a/paddle/gserver/layers/CosSimLayer.h +++ b/paddle/gserver/layers/CosSimLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -35,8 +34,7 @@ namespace paddle { */ class CosSimLayer : public Layer { public: - explicit CosSimLayer(const LayerConfig& config) - : Layer(config) {} + explicit CosSimLayer(const LayerConfig& config) : Layer(config) {} ~CosSimLayer() {} diff --git a/paddle/gserver/layers/CosSimVecMatLayer.cpp b/paddle/gserver/layers/CosSimVecMatLayer.cpp index 7d251ace6f..56d177da64 100644 --- a/paddle/gserver/layers/CosSimVecMatLayer.cpp +++ b/paddle/gserver/layers/CosSimVecMatLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -67,19 +66,37 @@ bool CosSimVecMatLayer::init(const LayerMap& layerMap, CHECK_EQ(dataDim * numKeys, memoryDim) << "Dimension mismatch"; - tmpRow0 = Matrix::create(nullptr, /* height= */ 1, dataDim, - /* trans= */ false, useGpu_); - tmpRow1 = Matrix::create(nullptr, /* height= */ 1, dataDim, - /* trans= */ false, useGpu_); - tmpRow2 = Matrix::create(nullptr, /* height= */ numKeys, 1, - /* trans= */ false, useGpu_); - tmpRow3 = Matrix::create(nullptr, /* height= */ numKeys, 1, - /* trans= */ false, useGpu_); - - tmpMtx0 = Matrix::create(nullptr, /* height= */ numKeys, dataDim, - /* trans= */ false, useGpu_); - tmpMtx1 = Matrix::create(nullptr, /* height= */ numKeys, dataDim, - /* trans= */ false, useGpu_); + tmpRow0 = Matrix::create(nullptr, + /* height= */ 1, + dataDim, + /* trans= */ false, + useGpu_); + tmpRow1 = Matrix::create(nullptr, + /* height= */ 1, + dataDim, + /* trans= */ false, + useGpu_); + tmpRow2 = Matrix::create(nullptr, + /* height= */ numKeys, + 1, + /* trans= */ false, + useGpu_); + tmpRow3 = Matrix::create(nullptr, + /* height= */ numKeys, + 1, + /* trans= */ false, + useGpu_); + + tmpMtx0 = Matrix::create(nullptr, + /* height= */ numKeys, + dataDim, + /* trans= */ false, + useGpu_); + tmpMtx1 = Matrix::create(nullptr, + /* height= */ numKeys, + dataDim, + /* trans= */ false, + useGpu_); return true; } @@ -131,8 +148,12 @@ void CosSimVecMatLayer::backward(const UpdateCallback& callback) { tmpRow2->setData(outV->rowBuf(i)); tmpRow3->setData(outG->rowBuf(i)); - tmpRow3->cosSimDerivative(*(tmpRow2), *(tmpMtx0), *(tmpRow0), *(tmpMtx1), - *(tmpRow1), config_.cos_scale()); + tmpRow3->cosSimDerivative(*(tmpRow2), + *(tmpMtx0), + *(tmpRow0), + *(tmpMtx1), + *(tmpRow1), + config_.cos_scale()); } } else { CHECK(!inG0 || !inG1) << "Not supported"; diff --git a/paddle/gserver/layers/CostLayer.cpp b/paddle/gserver/layers/CostLayer.cpp index 3c2df52fed..094c36ceb1 100644 --- a/paddle/gserver/layers/CostLayer.cpp +++ b/paddle/gserver/layers/CostLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "paddle/utils/Logging.h" @@ -88,13 +87,15 @@ bool MultiClassCrossEntropy::init(const LayerMap& layerMap, return CostLayer::init(layerMap, parameterMap); } -void MultiClassCrossEntropy::forwardImp(Matrix& output, Argument& label, +void MultiClassCrossEntropy::forwardImp(Matrix& output, + Argument& label, Matrix& target) { target.oneHotCrossEntropy(output, *label.ids); } -void MultiClassCrossEntropy::backwardImp( - Matrix& output, Argument& label, Matrix& outputG) { +void MultiClassCrossEntropy::backwardImp(Matrix& output, + Argument& label, + Matrix& outputG) { outputG.oneHotCrossEntropyBp(output, *label.ids); } @@ -152,17 +153,19 @@ bool SoftBinaryClassCrossEntropy::init(const LayerMap& layerMap, return CostLayer::init(layerMap, parameterMap); } -void SoftBinaryClassCrossEntropy::forwardImp(Matrix& output, Argument& label, +void SoftBinaryClassCrossEntropy::forwardImp(Matrix& output, + Argument& label, Matrix& target) { - Matrix::resizeOrCreate(targetPerDim_, output.getHeight(), output.getWidth(), - false, useGpu_); + Matrix::resizeOrCreate( + targetPerDim_, output.getHeight(), output.getWidth(), false, useGpu_); targetPerDim_->softCrossEntropy(output, *label.value); targetPerDim_->rowSum(target); } -void SoftBinaryClassCrossEntropy::backwardImp( - Matrix& output, Argument& label, Matrix& outputG) { +void SoftBinaryClassCrossEntropy::backwardImp(Matrix& output, + Argument& label, + Matrix& outputG) { outputG.softCrossEntropyBp(output, *label.value); } @@ -177,13 +180,15 @@ bool SumOfSquaresCostLayer::init(const LayerMap& layerMap, return CostLayer::init(layerMap, parameterMap); } -void SumOfSquaresCostLayer::forwardImp(Matrix& output, Argument& label, +void SumOfSquaresCostLayer::forwardImp(Matrix& output, + Argument& label, Matrix& target) { target.sumOfSquares(output, *label.value); } -void SumOfSquaresCostLayer::backwardImp( - Matrix& output, Argument& label, Matrix& outputG) { +void SumOfSquaresCostLayer::backwardImp(Matrix& output, + Argument& label, + Matrix& outputG) { outputG.sumOfSquaresBp(output, *label.value); } @@ -219,8 +224,8 @@ void RankingCost::forward(PassType passType) { IVectorPtr idLabel = getInput(*getLabelLayer()).ids; CHECK(idLabel) << "label layer has neither value nor ids"; CHECK_EQ((size_t)batchSize, idLabel->getSize()); - Matrix::resizeOrCreate(labelBuf_, batchSize, /*width*/ 1, /*trans*/ false, - useGpu_); + Matrix::resizeOrCreate( + labelBuf_, batchSize, /*width*/ 1, /*trans*/ false, useGpu_); labelBuf_->copyFrom(*idLabel); label = labelBuf_; } @@ -261,8 +266,8 @@ void RankingCost::backward(const UpdateCallback& callback) { label = labelBuf_; } - Matrix::resizeOrCreate(marginGrad_, label->getHeight(), 1, /* trans= */ false, - useGpu_); + Matrix::resizeOrCreate( + marginGrad_, label->getHeight(), 1, /* trans= */ false, useGpu_); marginGrad_->zeroMem(); marginGrad_->logisticRegressionLossBp(*margin_, *label); if (weightLayer_) { @@ -317,15 +322,14 @@ void LambdaCost::forward(PassType passType) { real* outputData = output->getData(); real* targetData = target->getData(); - auto startPos = - getInput(*getOutputLayer()).sequenceStartPositions; + auto startPos = getInput(*getOutputLayer()).sequenceStartPositions; const int* startPosData = startPos->getData(false); size_t batchNum = startPos->getSize() - 1; for (size_t i = 0; i < batchNum; ++i) { int beginPos = startPosData[i]; int endPos = startPosData[i + 1]; - real NDCG = calcNDCG(outputData + beginPos, scoreData + beginPos, - endPos - beginPos); + real NDCG = calcNDCG( + outputData + beginPos, scoreData + beginPos, endPos - beginPos); for (int j = beginPos; j < endPos; ++j) { targetData[j] = NDCG; } @@ -336,23 +340,27 @@ void LambdaCost::backward(const UpdateCallback& callback) { (void)callback; MatrixPtr score = getInputValue(*getScoreLayer()); MatrixPtr output = getInputValue(*getOutputLayer()); - Matrix::resizeOrCreate(marginGrad_, score->getHeight(), 1, - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(marginGrad_, + score->getHeight(), + 1, + /* trans= */ false, + useGpu_); marginGrad_->zeroMem(); real* gradData = marginGrad_->getData(); real* scoreData = score->getData(); real* outputData = output->getData(); - auto startPos = - getInput(*getOutputLayer()).sequenceStartPositions; + auto startPos = getInput(*getOutputLayer()).sequenceStartPositions; const int* startPosData = startPos->getData(false); size_t batchNum = startPos->getSize() - 1; for (size_t i = 0; i < batchNum; ++i) { int beginPos = startPosData[i]; int endPos = startPosData[i + 1]; - calcGrad(outputData + beginPos, scoreData + beginPos, gradData + beginPos, + calcGrad(outputData + beginPos, + scoreData + beginPos, + gradData + beginPos, endPos - beginPos); } @@ -361,8 +369,10 @@ void LambdaCost::backward(const UpdateCallback& callback) { void LambdaCost::onPassEnd() {} -void LambdaCost::calcGrad(const real* outputScore, const real* score, - real* gradData, int size) { +void LambdaCost::calcGrad(const real* outputScore, + const real* score, + real* gradData, + int size) { CHECK_GE(size, truncationSize_) << "Invalid: (Sample num in the same list) < (NDCG truncation num) !"; int sortSize = maxSortSize_ == -1 ? size : std::min(maxSortSize_, size); @@ -372,13 +382,16 @@ void LambdaCost::calcGrad(const real* outputScore, const real* score, scorePair_.push_back(std::make_pair(score[i], i)); } if (size <= sortSize) { - std::sort(scorePair_.begin(), scorePair_.end(), + std::sort(scorePair_.begin(), + scorePair_.end(), [](const std::pair& a, const std::pair& b) { return a.first > b.first; }); } else { std::partial_sort( - scorePair_.begin(), scorePair_.begin() + sortSize, scorePair_.end(), + scorePair_.begin(), + scorePair_.begin() + sortSize, + scorePair_.end(), [](const std::pair& a, const std::pair& b) { return a.first > b.first; }); @@ -414,7 +427,8 @@ void LambdaCost::calcGrad(const real* outputScore, const real* score, } } -real LambdaCost::calcNDCG(const real* outputScore, const real* score, +real LambdaCost::calcNDCG(const real* outputScore, + const real* score, int size) { CHECK_GE(size, truncationSize_) << "Invalid: (Sample num in the same list) < (NDCG truncation num) !"; @@ -424,7 +438,8 @@ real LambdaCost::calcNDCG(const real* outputScore, const real* score, outputScorePair_.push_back(std::make_pair(outputScore[i], i)); } std::partial_sort( - outputScorePair_.begin(), outputScorePair_.begin() + truncationSize_, + outputScorePair_.begin(), + outputScorePair_.begin() + truncationSize_, outputScorePair_.end(), [](const std::pair& a, const std::pair& b) { return a.first > b.first; @@ -439,8 +454,10 @@ real LambdaCost::calcNDCG(const real* outputScore, const real* score, scoreVec_.resize(size); std::copy(score, score + size, scoreVec_.begin()); real maxDCG = 0; - std::partial_sort(scoreVec_.begin(), scoreVec_.begin() + truncationSize_, - scoreVec_.end(), std::greater()); + std::partial_sort(scoreVec_.begin(), + scoreVec_.begin() + truncationSize_, + scoreVec_.end(), + std::greater()); for (int i = 0; i < truncationSize_; ++i) { maxDCG += (std::pow(2, scoreVec_[i]) - 1) / std::log(i + 2); } @@ -460,7 +477,8 @@ bool MultiBinaryLabelCrossEntropy::init(const LayerMap& layerMap, return CostLayer::init(layerMap, parameterMap); } -void MultiBinaryLabelCrossEntropy::forwardImp(Matrix& output, Argument& label, +void MultiBinaryLabelCrossEntropy::forwardImp(Matrix& output, + Argument& label, Matrix& target) { MatrixPtr value = nullptr; if (label.ids) { @@ -475,16 +493,17 @@ void MultiBinaryLabelCrossEntropy::forwardImp(Matrix& output, Argument& label, dynamic_cast(value.get())) { target.multiBinaryLabelCrossEntropy(output, *value); } else { - Matrix::resizeOrCreate(targetPerDim_, output.getHeight(), output.getWidth(), - false, useGpu_); + Matrix::resizeOrCreate( + targetPerDim_, output.getHeight(), output.getWidth(), false, useGpu_); targetPerDim_->binaryLabelCrossEntropy(output, *value); targetPerDim_->rowSum(target); } } -void MultiBinaryLabelCrossEntropy::backwardImp( - Matrix& output, Argument& label, Matrix& outputG) { +void MultiBinaryLabelCrossEntropy::backwardImp(Matrix& output, + Argument& label, + Matrix& outputG) { MatrixPtr value = nullptr; if (label.ids) { CHECK(!value); @@ -519,8 +538,7 @@ bool HuberTwoClass::init(const LayerMap& layerMap, return true; } -void HuberTwoClass::forwardImp(Matrix &output, Argument &label, - Matrix &cost) { +void HuberTwoClass::forwardImp(Matrix& output, Argument& label, Matrix& cost) { if (useGpu_) { for (size_t i = 0; i < inputLayers_.size(); i++) { tmpCpuInput_[i].resizeAndCopyFrom( @@ -531,7 +549,8 @@ void HuberTwoClass::forwardImp(Matrix &output, Argument &label, forwardImpIn(output, label, cost); } -void HuberTwoClass::forwardImpIn(Matrix& output, Argument& label, +void HuberTwoClass::forwardImpIn(Matrix& output, + Argument& label, Matrix& target) { size_t numSamples = target.getHeight(); CHECK_EQ((*label.ids).getSize(), numSamples); @@ -539,7 +558,7 @@ void HuberTwoClass::forwardImpIn(Matrix& output, Argument& label, CHECK_EQ(output.getWidth(), (size_t)1); CHECK_EQ(target.getWidth(), (size_t)1); - real* out = useGpu_ ? tmpCpuInput_[0].value->getData(): output.getData(); + real* out = useGpu_ ? tmpCpuInput_[0].value->getData() : output.getData(); int* lbl = useGpu_ ? tmpCpuInput_[1].ids->getData() : (*label.ids).getData(); std::vector cost(numSamples); for (size_t i = 0; i < numSamples; ++i) { @@ -554,19 +573,21 @@ void HuberTwoClass::forwardImpIn(Matrix& output, Argument& label, target.copyFrom(cost.data(), numSamples); } -void HuberTwoClass::backwardImp(Matrix &outputValue, - Argument &label, Matrix &outputGrad) { +void HuberTwoClass::backwardImp(Matrix& outputValue, + Argument& label, + Matrix& outputGrad) { if (useGpu_) { - backwardImpIn(*tmpCpuInput_[0].value, tmpCpuInput_[1], - *tmpCpuInput_[0].grad); + backwardImpIn( + *tmpCpuInput_[0].value, tmpCpuInput_[1], *tmpCpuInput_[0].grad); outputGrad.copyFrom(*tmpCpuInput_[0].grad); } else { backwardImpIn(outputValue, label, outputGrad); } } -void HuberTwoClass::backwardImpIn( - Matrix& output, Argument& label, Matrix& outputG) { +void HuberTwoClass::backwardImpIn(Matrix& output, + Argument& label, + Matrix& outputG) { size_t numSamples = output.getHeight(); real* out = output.getData(); real* grad = outputG.getData(); @@ -605,7 +626,7 @@ public: int batchSize = input->getHeight(); int size = 1; resizeOutput(batchSize, size); - output_.value->sumRows(*input, /* scaleSum= */1, /* scaleDest= */0); + output_.value->sumRows(*input, /* scaleSum= */ 1, /* scaleDest= */ 0); } virtual void backward(const UpdateCallback& callback = nullptr) { diff --git a/paddle/gserver/layers/CostLayer.h b/paddle/gserver/layers/CostLayer.h index f263c68821..120ff9bd2d 100644 --- a/paddle/gserver/layers/CostLayer.h +++ b/paddle/gserver/layers/CostLayer.h @@ -42,10 +42,12 @@ public: virtual void backward(const UpdateCallback& callback = nullptr); - virtual void forwardImp(Matrix& outputValue, Argument& label, + virtual void forwardImp(Matrix& outputValue, + Argument& label, Matrix& cost) = 0; - virtual void backwardImp(Matrix& outputValue, Argument& label, + virtual void backwardImp(Matrix& outputValue, + Argument& label, Matrix& outputGrad) = 0; protected: @@ -225,7 +227,9 @@ public: void onPassEnd(); real calcNDCG(const real* outputScore, const real* score, int size); - void calcGrad(const real* outputScore, const real* score, real* gradData, + void calcGrad(const real* outputScore, + const real* score, + real* gradData, int size); private: @@ -274,6 +278,7 @@ public: */ class HuberTwoClass : public CostLayer { std::vector tmpCpuInput_; + public: explicit HuberTwoClass(const LayerConfig& config) : CostLayer(config) {} diff --git a/paddle/gserver/layers/CudnnBatchNormLayer.cpp b/paddle/gserver/layers/CudnnBatchNormLayer.cpp index 3c6d13b0bf..6be62b1a25 100644 --- a/paddle/gserver/layers/CudnnBatchNormLayer.cpp +++ b/paddle/gserver/layers/CudnnBatchNormLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "Layer.h" #include "CudnnBatchNormLayer.h" @@ -65,16 +64,31 @@ void CudnnBatchNormLayer::forward(PassType passType) { REGISTER_TIMER_INFO("CudnnBatchFwTimer", getName().c_str()); real* savedMean = savedMean_->getData(); real* savedInvVar = savedInvVar_->getData(); - hl_batch_norm_forward_training(ioDesc_, input, ioDesc_, output, + hl_batch_norm_forward_training(ioDesc_, + input, + ioDesc_, + output, bnParamDesc_, - gamma, beta, 1.0 - movingAvgFraction_, - movingMean, movingVar, - EPS, savedMean, savedInvVar); + gamma, + beta, + 1.0 - movingAvgFraction_, + movingMean, + movingVar, + EPS, + savedMean, + savedInvVar); } else { // used movingMean and movingVar in testing - hl_batch_norm_forward_inference(ioDesc_, input, ioDesc_, output, - bnParamDesc_, gamma, beta, - movingMean, movingVar, EPS); + hl_batch_norm_forward_inference(ioDesc_, + input, + ioDesc_, + output, + bnParamDesc_, + gamma, + beta, + movingMean, + movingVar, + EPS); } /* activation */ { @@ -115,10 +129,19 @@ void CudnnBatchNormLayer::backward(const UpdateCallback& callback) { create(tmpBiasGrad_, 1, channels_, &betaGrad); } - hl_batch_norm_backward(ioDesc_, input, ioDesc_, outGrad, - ioDesc_, inGrad, bnParamDesc_, - gamma, gammaGrad, betaGrad, - EPS, savedMean, savedInvVar); + hl_batch_norm_backward(ioDesc_, + input, + ioDesc_, + outGrad, + ioDesc_, + inGrad, + bnParamDesc_, + gamma, + gammaGrad, + betaGrad, + EPS, + savedMean, + savedInvVar); { REGISTER_TIMER_INFO("WeightUpdate", getName().c_str()); diff --git a/paddle/gserver/layers/CudnnBatchNormLayer.h b/paddle/gserver/layers/CudnnBatchNormLayer.h index 03f4f591c3..6220e77ceb 100644 --- a/paddle/gserver/layers/CudnnBatchNormLayer.h +++ b/paddle/gserver/layers/CudnnBatchNormLayer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Stat.h" @@ -23,7 +22,8 @@ namespace paddle { /** * @brief Cudnn Batch normalization layer use to cuDNN lib to implentment. - * @note Cudnn version must >= v4.0, and better to use the latest version (v5.1). + * @note Cudnn version must >= v4.0, and better to use the latest version + * (v5.1). * * The config file api is batch_norm_layer. */ diff --git a/paddle/gserver/layers/CudnnConvLayer.cpp b/paddle/gserver/layers/CudnnConvLayer.cpp index 23ba234118..93c5565d2f 100644 --- a/paddle/gserver/layers/CudnnConvLayer.cpp +++ b/paddle/gserver/layers/CudnnConvLayer.cpp @@ -32,16 +32,16 @@ bool CudnnConvLayer::init(const LayerMap &layerMap, numFilters_ = config_.num_filters(); CHECK(config_.shared_biases()); for (size_t i = 0; i < inputLayers_.size(); i++) { - ProjectionConfig* conf = new ProjectionConfig(); + ProjectionConfig *conf = new ProjectionConfig(); conf->set_type("conv"); conf->set_num_filters(numFilters_); - ConvConfig* convConf = conf->mutable_conv_conf(); + ConvConfig *convConf = conf->mutable_conv_conf(); *convConf = *(config_.mutable_inputs(i)->mutable_conv_conf()); conf->set_input_size(getPrev(i)->getSize()); conf->set_output_size(getSize()); projConf_.emplace_back(conf); - projections_.emplace_back(Projection::create(*projConf_[i], - parameters_[i], useGpu_)); + projections_.emplace_back( + Projection::create(*projConf_[i], parameters_[i], useGpu_)); } if (biases_.get() && sharedBiases_) { @@ -67,15 +67,21 @@ void CudnnConvLayer::forward(PassType passType) { if (biases_) { REGISTER_TIMER_INFO("CudnnConvBiasTimer", getName().c_str()); int batchSize = inputLayers_[0]->getOutputValue()->getHeight(); - hl_tensor_reshape(outputDesc_, batchSize, numFilters_ / groups_[0], - outputH_[0], outputW_[0], numFilters_ * outputH_[0] * outputW_[0], - outputH_[0] * outputW_[0], outputW_[0], 1); + hl_tensor_reshape(outputDesc_, + batchSize, + numFilters_ / groups_[0], + outputH_[0], + outputW_[0], + numFilters_ * outputH_[0] * outputW_[0], + outputH_[0] * outputW_[0], + outputW_[0], + 1); outputOffset_ = getOutputValue()->getWidth() / groups_[0]; for (int g = 0; g < groups_[0]; ++g) { real *biasData = biases_->getW()->getData() + biasOffset_ * g; real *outData = getOutputValue()->getData() + outputOffset_ * g; - hl_convolution_forward_add_bias(biasDesc_, biasData, - outputDesc_, outData); + hl_convolution_forward_add_bias( + biasDesc_, biasData, outputDesc_, outData); } } diff --git a/paddle/gserver/layers/CudnnConvLayer.h b/paddle/gserver/layers/CudnnConvLayer.h index 6390d96315..6cfbadfb53 100644 --- a/paddle/gserver/layers/CudnnConvLayer.h +++ b/paddle/gserver/layers/CudnnConvLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 "ConvBaseLayer.h" diff --git a/paddle/gserver/layers/CudnnPoolLayer.cpp b/paddle/gserver/layers/CudnnPoolLayer.cpp index 24adb50a98..21d8e2579f 100644 --- a/paddle/gserver/layers/CudnnPoolLayer.cpp +++ b/paddle/gserver/layers/CudnnPoolLayer.cpp @@ -61,8 +61,13 @@ bool CudnnPoolLayer::init(const LayerMap &layerMap, strideHeight = strideY_; strideWidth = stride_; - hl_create_pooling_descriptor(&poolingDesc_, mode_, windowHeight, windowWidth, - heightPadding, widthPadding, strideHeight, + hl_create_pooling_descriptor(&poolingDesc_, + mode_, + windowHeight, + windowWidth, + heightPadding, + widthPadding, + strideHeight, strideWidth); return true; @@ -79,7 +84,10 @@ void CudnnPoolLayer::reshape(int batchSize) { } CHECK_EQ(inputLayers_[0]->getOutput().value->getWidth(), channels_ * imageH_ * imageW_); - outputH_ = outputSize(imageH_, sizeY_, confPaddingY_, strideY_, + outputH_ = outputSize(imageH_, + sizeY_, + confPaddingY_, + strideY_, /* caffeMode */ false); outputW_ = outputSize(imageW_, sizeX_, confPadding_, stride_, /* caffeMode */ false); @@ -113,8 +121,13 @@ void CudnnPoolLayer::backward(const UpdateCallback &callback) { real *inputGrad = getInputGrad(0)->getData(); real *outData = getOutputValue()->getData(); real *outGrad = getOutputGrad()->getData(); - hl_pooling_backward(inputDesc_, inputData, inputGrad, outputDesc_, outData, - outGrad, poolingDesc_); + hl_pooling_backward(inputDesc_, + inputData, + inputGrad, + outputDesc_, + outData, + outGrad, + poolingDesc_); } CudnnPoolLayer::~CudnnPoolLayer() { diff --git a/paddle/gserver/layers/CudnnPoolLayer.h b/paddle/gserver/layers/CudnnPoolLayer.h index 2ef94720d2..6a6b28db96 100644 --- a/paddle/gserver/layers/CudnnPoolLayer.h +++ b/paddle/gserver/layers/CudnnPoolLayer.h @@ -12,19 +12,18 @@ WITHOUT 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 "PoolLayer.h" namespace paddle { - /** - * @brief CudnnPoolLayer is subclass of PoolLayer, which is implemented by - * cudnn api and only supports GPU. - * - * The config file api is img_pool_layer. - */ +/** + * @brief CudnnPoolLayer is subclass of PoolLayer, which is implemented by + * cudnn api and only supports GPU. + * + * The config file api is img_pool_layer. + */ class CudnnPoolLayer : public PoolLayer { protected: diff --git a/paddle/gserver/layers/DataLayer.cpp b/paddle/gserver/layers/DataLayer.cpp index 79b9181e69..9a4b2e9d3e 100644 --- a/paddle/gserver/layers/DataLayer.cpp +++ b/paddle/gserver/layers/DataLayer.cpp @@ -32,19 +32,20 @@ void DataLayer::copyDataToOutput(Argument& output) { data_.value->getWidth(), useGpu(output.deviceId)); } else { - output.value->resize(data_.value->getHeight(), - data_.value->getWidth()); + output.value->resize(data_.value->getHeight(), data_.value->getWidth()); } output.value->copyFrom(*data_.value); } if (data_.grad) { - Matrix::resizeOrCreate(output.grad, data_.grad->getHeight(), + Matrix::resizeOrCreate(output.grad, + data_.grad->getHeight(), data_.grad->getWidth(), - /* trans= */ false, useGpu(output.deviceId)); + /* trans= */ false, + useGpu(output.deviceId)); } if (data_.ids) { - IVector::resizeOrCreate(output.ids, data_.ids->getSize(), - useGpu(output.deviceId)); + IVector::resizeOrCreate( + output.ids, data_.ids->getSize(), useGpu(output.deviceId)); output.ids->copyFrom(*data_.ids); } } diff --git a/paddle/gserver/layers/DataLayer.h b/paddle/gserver/layers/DataLayer.h index 3abec1b065..da74702201 100644 --- a/paddle/gserver/layers/DataLayer.h +++ b/paddle/gserver/layers/DataLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -20,7 +19,7 @@ limitations under the License. */ #include "Layer.h" namespace paddle { -/** +/** * This layer just copy data to output, and has no backward propagation. * * The config file api is data_layer. @@ -34,12 +33,10 @@ public: /** * Prefetch sparse matrix/ids only. */ - void prefetch() { - output_ = data_; - } + void prefetch() { output_ = data_; } - /** - * Forward propagation. Copy data_ (value, in, grad, ids, cpuSequenceDims, + /** + * Forward propagation. Copy data_ (value, in, grad, ids, cpuSequenceDims, * sequenceStartPositions, subSequenceStartPositions, strs) to output_. */ virtual void forward(PassType passType) { diff --git a/paddle/gserver/layers/DataNormLayer.cpp b/paddle/gserver/layers/DataNormLayer.cpp index 150977ce1a..b398f3dbed 100644 --- a/paddle/gserver/layers/DataNormLayer.cpp +++ b/paddle/gserver/layers/DataNormLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "DataNormLayer.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" @@ -37,16 +36,28 @@ bool DataNormLayer::init(const LayerMap& layerMap, << "The parameter of DataNormLayer must be static"; weight_ = std::unique_ptr(new Weight(5, getSize(), parameters_[0])); - min_ = Matrix::create(nullptr, /* height= */ 1, getSize(), /* trans= */ false, - useGpu_); - rangeReciprocal_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - mean_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - stdReciprocal_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - decimalReciprocal_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + min_ = Matrix::create( + nullptr, /* height= */ 1, getSize(), /* trans= */ false, useGpu_); + rangeReciprocal_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + mean_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + stdReciprocal_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + decimalReciprocal_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); min_->setData(weight_->getW()->getData()); rangeReciprocal_->setData(weight_->getW()->getData() + getSize()); diff --git a/paddle/gserver/layers/DataNormLayer.h b/paddle/gserver/layers/DataNormLayer.h index 232c73f034..1179d94fbb 100644 --- a/paddle/gserver/layers/DataNormLayer.h +++ b/paddle/gserver/layers/DataNormLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/DotMulOperator.cpp b/paddle/gserver/layers/DotMulOperator.cpp index e6d2375b47..9409493fda 100644 --- a/paddle/gserver/layers/DotMulOperator.cpp +++ b/paddle/gserver/layers/DotMulOperator.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Operator.h" namespace paddle { @@ -42,8 +41,8 @@ DotMulOperator::DotMulOperator(const OperatorConfig& config, bool useGpu) } void DotMulOperator::forward() { - out_->value->addDotMul(*ins_[0]->value, *ins_[1]->value, 1, - config_.dotmul_scale()); + out_->value->addDotMul( + *ins_[0]->value, *ins_[1]->value, 1, config_.dotmul_scale()); } void DotMulOperator::backward() { diff --git a/paddle/gserver/layers/DotMulProjection.cpp b/paddle/gserver/layers/DotMulProjection.cpp index f6f14c4429..862eeb6f01 100644 --- a/paddle/gserver/layers/DotMulProjection.cpp +++ b/paddle/gserver/layers/DotMulProjection.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Projection.h" namespace paddle { @@ -29,7 +28,8 @@ namespace paddle { class DotMulProjection : public Projection { public: DotMulProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu); + const ParameterPtr& parameter, + bool useGpu); virtual void forward(); virtual void backward(const UpdateCallback& callback); @@ -41,7 +41,8 @@ protected: REGISTER_PROJECTION(dot_mul, DotMulProjection); DotMulProjection::DotMulProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu) + const ParameterPtr& parameter, + bool useGpu) : Projection(config, parameter, useGpu) { weight_.reset(new Weight(1LU, config.output_size(), parameter)); } diff --git a/paddle/gserver/layers/EosIdCheckLayer.cpp b/paddle/gserver/layers/EosIdCheckLayer.cpp index 2d0778a451..3a43705d26 100644 --- a/paddle/gserver/layers/EosIdCheckLayer.cpp +++ b/paddle/gserver/layers/EosIdCheckLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" @@ -20,7 +19,7 @@ namespace paddle { /** * A layer for checking EOS for each sample: * - output_id = (input_id == conf.eos_id) - * + * * The result is stored in output_.ids. * It is used by recurrent layer group. */ diff --git a/paddle/gserver/layers/ExpandConvBaseLayer.cpp b/paddle/gserver/layers/ExpandConvBaseLayer.cpp index 0bab0ca764..71a69bd0d0 100644 --- a/paddle/gserver/layers/ExpandConvBaseLayer.cpp +++ b/paddle/gserver/layers/ExpandConvBaseLayer.cpp @@ -12,14 +12,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "ExpandConvBaseLayer.h" #include "paddle/utils/Logging.h" namespace paddle { bool ExpandConvBaseLayer::init(const LayerMap &layerMap, - const ParameterMap ¶meterMap) { + const ParameterMap ¶meterMap) { /* Initialize the basic convolutional parent class */ ConvBaseLayer::init(layerMap, parameterMap); @@ -76,9 +75,11 @@ void ExpandConvBaseLayer::addSharedBias() { transOutValue_->reshape(transOutValue_->getElementCnt() / numFilters_, numFilters_); - MatrixPtr bias = - Matrix::create(biases_->getW()->getData(), 1, - biases_->getW()->getElementCnt(), false, useGpu_); + MatrixPtr bias = Matrix::create(biases_->getW()->getData(), + 1, + biases_->getW()->getElementCnt(), + false, + useGpu_); transOutValue_->addBias(*bias, 1.0f); transOutValue_->reshape(mapW, mapH); @@ -90,32 +91,46 @@ void ExpandConvBaseLayer::addSharedBias() { void ExpandConvBaseLayer::addUnsharedBias() { MatrixPtr outValue = getOutputValue(); - MatrixPtr bias = - Matrix::create(biases_->getW()->getData(), 1, - biases_->getW()->getElementCnt(), false, useGpu_); + MatrixPtr bias = Matrix::create(biases_->getW()->getData(), + 1, + biases_->getW()->getElementCnt(), + false, + useGpu_); outValue->addBias(*bias, 1.0f); } - -void ExpandConvBaseLayer::expandOneFrame(MatrixPtr image, size_t startIdx, - int inIdx) { +void ExpandConvBaseLayer::expandOneFrame(MatrixPtr image, + size_t startIdx, + int inIdx) { int channel = isDeconv_ ? numFilters_ : channels_[inIdx]; resetExpandInput(subK_[inIdx] * groups_[inIdx], subN_[inIdx]); real *imgData = image->getData() + startIdx * image->getWidth(); - MatrixPtr imageTmp = Matrix::create( - imgData, 1, imgSizeH_[inIdx] * imgSizeW_[inIdx] * channel, false, - useGpu_); - expandInput_->convExpand(*imageTmp, imgSizeH_[inIdx], imgSizeW_[inIdx], - channel, filterSize_[inIdx], - filterSize_[inIdx], stride_[inIdx], stride_[inIdx], - padding_[inIdx], padding_[inIdx], - outputH_[inIdx], outputW_[inIdx]); + MatrixPtr imageTmp = + Matrix::create(imgData, + 1, + imgSizeH_[inIdx] * imgSizeW_[inIdx] * channel, + false, + useGpu_); + expandInput_->convExpand(*imageTmp, + imgSizeH_[inIdx], + imgSizeW_[inIdx], + channel, + filterSize_[inIdx], + filterSize_[inIdx], + stride_[inIdx], + stride_[inIdx], + padding_[inIdx], + padding_[inIdx], + outputH_[inIdx], + outputW_[inIdx]); imageTmp->clear(); } -void ExpandConvBaseLayer::expandFwdOnce(MatrixPtr image, MatrixPtr out, - int inIdx, int startIdx) { +void ExpandConvBaseLayer::expandFwdOnce(MatrixPtr image, + MatrixPtr out, + int inIdx, + int startIdx) { int subM = subM_[inIdx]; int subN = subN_[inIdx]; int subK = subK_[inIdx]; @@ -124,8 +139,7 @@ void ExpandConvBaseLayer::expandFwdOnce(MatrixPtr image, MatrixPtr out, int numFilters = isDeconv_ ? channels_[inIdx] : numFilters_; - real *outData = - out->getData() + startIdx * subN * numFilters; + real *outData = out->getData() + startIdx * subN * numFilters; real *wgtData = weights_[inIdx]->getW()->getData(); real *expInData = expandInput_->getData(); @@ -145,7 +159,8 @@ void ExpandConvBaseLayer::expandFwdOnce(MatrixPtr image, MatrixPtr out, } } -void ExpandConvBaseLayer::bpropActs(MatrixPtr out, MatrixPtr image, +void ExpandConvBaseLayer::bpropActs(MatrixPtr out, + MatrixPtr image, int inpIdx) { int channel = isDeconv_ ? numFilters_ : channels_[inpIdx]; @@ -183,15 +198,26 @@ void ExpandConvBaseLayer::bpropActs(MatrixPtr out, MatrixPtr image, // shrink one frame outGrad MatrixPtr oneGradTmp = Matrix::create( expandInput_->getData(), subK * groups_[inpIdx], subN, false, useGpu_); - MatrixPtr vTmp = Matrix::create( - tgtGradData, 1, - imgSizeH_[inpIdx] * imgSizeW_[inpIdx] * channel, false, - useGpu_); - vTmp->convShrink(*oneGradTmp, imgSizeH_[inpIdx], imgSizeW_[inpIdx], - channel, filterSize_[inpIdx], - filterSize_[inpIdx], stride_[inpIdx], stride_[inpIdx], - padding_[inpIdx], padding_[inpIdx], - outputH_[inpIdx], outputW_[inpIdx], 1.0f, 1.0f); + MatrixPtr vTmp = + Matrix::create(tgtGradData, + 1, + imgSizeH_[inpIdx] * imgSizeW_[inpIdx] * channel, + false, + useGpu_); + vTmp->convShrink(*oneGradTmp, + imgSizeH_[inpIdx], + imgSizeW_[inpIdx], + channel, + filterSize_[inpIdx], + filterSize_[inpIdx], + stride_[inpIdx], + stride_[inpIdx], + padding_[inpIdx], + padding_[inpIdx], + outputH_[inpIdx], + outputW_[inpIdx], + 1.0f, + 1.0f); vTmp->clear(); oneGradTmp->clear(); @@ -200,8 +226,9 @@ void ExpandConvBaseLayer::bpropActs(MatrixPtr out, MatrixPtr image, } } -void ExpandConvBaseLayer::bpropWeights(MatrixPtr image, MatrixPtr out, - int inpIdx) { +void ExpandConvBaseLayer::bpropWeights(MatrixPtr image, + MatrixPtr out, + int inpIdx) { MatrixPtr weightGrad = weights_[inpIdx]->getWGrad(); int subM = subM_[inpIdx]; @@ -249,9 +276,11 @@ void ExpandConvBaseLayer::bpropSharedBias(MatrixPtr biases, MatrixPtr v) { } void ExpandConvBaseLayer::bpropBiases(MatrixPtr v) { - MatrixPtr biases = - Matrix::create(biases_->getWGrad()->getData(), 1, - biases_->getWGrad()->getElementCnt(), false, useGpu_); + MatrixPtr biases = Matrix::create(biases_->getWGrad()->getData(), + 1, + biases_->getWGrad()->getElementCnt(), + false, + useGpu_); if (sharedBiases_) { bpropSharedBias(biases, v); } else { diff --git a/paddle/gserver/layers/ExpandConvBaseLayer.h b/paddle/gserver/layers/ExpandConvBaseLayer.h index 9858fa348c..5939d27e2a 100644 --- a/paddle/gserver/layers/ExpandConvBaseLayer.h +++ b/paddle/gserver/layers/ExpandConvBaseLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 "ConvBaseLayer.h" @@ -45,7 +44,7 @@ protected: public: explicit ExpandConvBaseLayer(const LayerConfig& config) - : ConvBaseLayer(config) {} + : ConvBaseLayer(config) {} ~ExpandConvBaseLayer() {} diff --git a/paddle/gserver/layers/ExpandConvLayer.cpp b/paddle/gserver/layers/ExpandConvLayer.cpp index 5ea1fdece5..0649289c1c 100644 --- a/paddle/gserver/layers/ExpandConvLayer.cpp +++ b/paddle/gserver/layers/ExpandConvLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" #include "ExpandConvLayer.h" @@ -58,7 +57,6 @@ void ExpandConvLayer::forward(PassType passType) { forwardActivation(); } - void ExpandConvLayer::backward(const UpdateCallback &callback) { backwardActivation(); diff --git a/paddle/gserver/layers/ExpandConvLayer.h b/paddle/gserver/layers/ExpandConvLayer.h index c07188a406..82a9e88a42 100644 --- a/paddle/gserver/layers/ExpandConvLayer.h +++ b/paddle/gserver/layers/ExpandConvLayer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/math/Matrix.h" @@ -31,8 +30,8 @@ namespace paddle { class ExpandConvLayer : public ExpandConvBaseLayer { public: - explicit ExpandConvLayer(const LayerConfig& config) : - ExpandConvBaseLayer(config) {} + explicit ExpandConvLayer(const LayerConfig& config) + : ExpandConvBaseLayer(config) {} ~ExpandConvLayer() {} diff --git a/paddle/gserver/layers/ExpandConvTransLayer.cpp b/paddle/gserver/layers/ExpandConvTransLayer.cpp index a3e160f1f4..1132ab4f92 100644 --- a/paddle/gserver/layers/ExpandConvTransLayer.cpp +++ b/paddle/gserver/layers/ExpandConvTransLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" #include "ExpandConvTransLayer.h" @@ -27,7 +26,7 @@ namespace paddle { REGISTER_LAYER(exconvt, ExpandConvTransLayer); bool ExpandConvTransLayer::init(const LayerMap &layerMap, - const ParameterMap ¶meterMap) { + const ParameterMap ¶meterMap) { /* Initialize the basic convolutional parent class */ ExpandConvBaseLayer::init(layerMap, parameterMap); @@ -88,5 +87,4 @@ void ExpandConvTransLayer::backward(const UpdateCallback &callback) { } } - } // namespace paddle diff --git a/paddle/gserver/layers/ExpandConvTransLayer.h b/paddle/gserver/layers/ExpandConvTransLayer.h index 87c464a97f..47efe3f656 100644 --- a/paddle/gserver/layers/ExpandConvTransLayer.h +++ b/paddle/gserver/layers/ExpandConvTransLayer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/math/Matrix.h" @@ -30,8 +29,8 @@ namespace paddle { */ class ExpandConvTransLayer : public ExpandConvBaseLayer { public: - explicit ExpandConvTransLayer(const LayerConfig& config) : - ExpandConvBaseLayer(config) {} + explicit ExpandConvTransLayer(const LayerConfig& config) + : ExpandConvBaseLayer(config) {} ~ExpandConvTransLayer() {} diff --git a/paddle/gserver/layers/FeatureMapExpandLayer.cpp b/paddle/gserver/layers/FeatureMapExpandLayer.cpp index d18b51dd79..97c8d143fe 100644 --- a/paddle/gserver/layers/FeatureMapExpandLayer.cpp +++ b/paddle/gserver/layers/FeatureMapExpandLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "paddle/math/Matrix.h" #include "paddle/utils/Stat.h" @@ -79,9 +78,12 @@ void FeatureMapExpandLayer::forward(PassType passType) { for (size_t i = 0; i < batchSize; i++) { MatrixPtr outVTmp = Matrix::create(outputV->getData() + i * imgSize * numFilters_, - numFilters_, imgSize, false, useGpu_); - MatrixPtr inVTmp = Matrix::create(inputV->getData() + i * imgSize, 1, - imgSize, false, useGpu_); + numFilters_, + imgSize, + false, + useGpu_); + MatrixPtr inVTmp = Matrix::create( + inputV->getData() + i * imgSize, 1, imgSize, false, useGpu_); outVTmp->addRowVector(*inVTmp); } } @@ -101,9 +103,12 @@ void FeatureMapExpandLayer::backward(const UpdateCallback& callback) { for (size_t i = 0; i < batchSize; i++) { MatrixPtr outGradTmp = Matrix::create(outGrad->getData() + i * imgSize * numFilters_, - numFilters_, imgSize, false, useGpu_); - MatrixPtr inGradTmp = Matrix::create(inGrad->getData() + i * imgSize, 1, - imgSize, false, useGpu_); + numFilters_, + imgSize, + false, + useGpu_); + MatrixPtr inGradTmp = Matrix::create( + inGrad->getData() + i * imgSize, 1, imgSize, false, useGpu_); inGradTmp->collectBias(*outGradTmp, 1); } } diff --git a/paddle/gserver/layers/FullMatrixProjection.cpp b/paddle/gserver/layers/FullMatrixProjection.cpp index f17c1b05bd..35a5cb5b7a 100644 --- a/paddle/gserver/layers/FullMatrixProjection.cpp +++ b/paddle/gserver/layers/FullMatrixProjection.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "FullMatrixProjection.h" namespace paddle { diff --git a/paddle/gserver/layers/FullMatrixProjection.h b/paddle/gserver/layers/FullMatrixProjection.h index e99444b33b..ddb1e7b18c 100644 --- a/paddle/gserver/layers/FullMatrixProjection.h +++ b/paddle/gserver/layers/FullMatrixProjection.h @@ -30,7 +30,8 @@ namespace paddle { class FullMatrixProjection : public Projection { public: FullMatrixProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu); + const ParameterPtr& parameter, + bool useGpu); virtual void forward(); virtual void backward(const UpdateCallback& callback); diff --git a/paddle/gserver/layers/FullyConnectedLayer.cpp b/paddle/gserver/layers/FullyConnectedLayer.cpp index c754f8fd94..70c56499a7 100644 --- a/paddle/gserver/layers/FullyConnectedLayer.cpp +++ b/paddle/gserver/layers/FullyConnectedLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "FullyConnectedLayer.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" diff --git a/paddle/gserver/layers/FullyConnectedLayer.h b/paddle/gserver/layers/FullyConnectedLayer.h index 334eb4b722..e15e1236cd 100644 --- a/paddle/gserver/layers/FullyConnectedLayer.h +++ b/paddle/gserver/layers/FullyConnectedLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -20,9 +19,9 @@ limitations under the License. */ #include "paddle/utils/ThreadLocal.h" namespace paddle { -/** +/** * A layer has full connections to all neurons in the previous layer. - * It computes an inner product with a set of learned weights, and + * It computes an inner product with a set of learned weights, and * (optionally) adds biases. * * The config file api is fc_layer. @@ -34,8 +33,7 @@ protected: std::unique_ptr biases_; public: - explicit FullyConnectedLayer(const LayerConfig& config) - : Layer(config) {} + explicit FullyConnectedLayer(const LayerConfig& config) : Layer(config) {} ~FullyConnectedLayer() {} bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); diff --git a/paddle/gserver/layers/GatedRecurrentLayer.cpp b/paddle/gserver/layers/GatedRecurrentLayer.cpp index e0c6ff7ea2..495c2174f3 100644 --- a/paddle/gserver/layers/GatedRecurrentLayer.cpp +++ b/paddle/gserver/layers/GatedRecurrentLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "GatedRecurrentLayer.h" #include "paddle/utils/Stat.h" @@ -30,8 +29,8 @@ bool GatedRecurrentLayer::init(const LayerMap& layerMap, CHECK_EQ(getSize() * 3, biasParameter_->getSize()); weight_.reset(new Weight(getSize(), getSize() * 3, parameters_[0])); gateWeight_.reset(new Weight(getSize(), getSize() * 2, parameters_[0], 0)); - stateWeight_.reset(new Weight(getSize(), getSize(), parameters_[0], - 2 * getSize() * getSize())); + stateWeight_.reset(new Weight( + getSize(), getSize(), parameters_[0], 2 * getSize() * getSize())); if (biasParameter_.get() != NULL) { bias_.reset(new Weight(1, getSize() * 3, biasParameter_)); } @@ -48,8 +47,8 @@ bool GatedRecurrentLayer::init(const LayerMap& layerMap, void GatedRecurrentLayer::resetState() { CHECK(!reversed_) << "state is not allowed for reversed gated " "recurrent layer"; - Matrix::resizeOrCreate(prevOutput_, 1, getSize(), /* trans= */ false, - useGpu_); + Matrix::resizeOrCreate( + prevOutput_, 1, getSize(), /* trans= */ false, useGpu_); prevOutput_->zeroMem(); // TODO(hedaoyuan): support prev_batch_state @@ -85,10 +84,16 @@ void GatedRecurrentLayer::forward(PassType passType) { // batchSize = length of total frames in a batch (NOT size of mini-batch) CHECK_EQ(starts[numSequences], batchSize); - Matrix::resizeOrCreate(gate_.value, /* height= */batchSize, - getSize() * 3, /* trans= */false, useGpu_); - Matrix::resizeOrCreate(resetOutput_.value, /* height= */batchSize, - getSize(), /* trans= */false, useGpu_); + Matrix::resizeOrCreate(gate_.value, + /* height= */ batchSize, + getSize() * 3, + /* trans= */ false, + useGpu_); + Matrix::resizeOrCreate(resetOutput_.value, + /* height= */ batchSize, + getSize(), + /* trans= */ false, + useGpu_); if (useBatch_) { forwardBatch(batchSize, numSequences, starts, input.value); @@ -105,10 +110,16 @@ void GatedRecurrentLayer::backward(const UpdateCallback& callback) { const int* starts = input.sequenceStartPositions->getData(false); size_t numSequences = input.getNumSequences(); - Matrix::resizeOrCreate(gate_.grad, /* height= */batchSize, - getSize() * 3, /* trans= */false, useGpu_); - Matrix::resizeOrCreate(resetOutput_.grad, /* height= */batchSize, - getSize(), /* trans= */false, useGpu_); + Matrix::resizeOrCreate(gate_.grad, + /* height= */ batchSize, + getSize() * 3, + /* trans= */ false, + useGpu_); + Matrix::resizeOrCreate(resetOutput_.grad, + /* height= */ batchSize, + getSize(), + /* trans= */ false, + useGpu_); if (useBatch_) { backwardBatch(batchSize, input.grad); @@ -125,7 +136,7 @@ void GatedRecurrentLayer::backward(const UpdateCallback& callback) { void GatedRecurrentLayer::forwardSequence(int batchSize, size_t numSequences, - const int *starts, + const int* starts, MatrixPtr inputValue) { REGISTER_TIMER_INFO("GruFwSequenceTime", getName().c_str()); gate_.value->assign(*inputValue); @@ -198,7 +209,7 @@ void GatedRecurrentLayer::forwardSequence(int batchSize, void GatedRecurrentLayer::backwardSequence(int batchSize, size_t numSequences, - const int *starts, + const int* starts, MatrixPtr inputGrad) { REGISTER_TIMER_INFO("GruBwSequenceTime", getName().c_str()); @@ -211,9 +222,10 @@ void GatedRecurrentLayer::backwardSequence(int batchSize, hl_gru_grad gruGrad; gruGrad.gateWeightGrad = - (gateWeight_->getWGrad() ? gateWeight_->getWGrad()->getData() : nullptr); + (gateWeight_->getWGrad() ? gateWeight_->getWGrad()->getData() : nullptr); gruGrad.stateWeightGrad = - (stateWeight_->getWGrad() ? stateWeight_->getWGrad()->getData() : nullptr); + (stateWeight_->getWGrad() ? stateWeight_->getWGrad()->getData() + : nullptr); gruGrad.gateGrad = gate_.grad->getData(); gruGrad.resetOutputGrad = resetOutput_.grad->getData(); gruGrad.outputGrad = output_.grad->getData(); @@ -298,11 +310,10 @@ void GatedRecurrentLayer::forwardBatch(int batchSize, if (!batchValue_) { batchValue_.reset(new SequenceToBatch(useGpu_)); } - batchValue_->resizeOrCreateBatch(batchSize, numSequences, starts, - reversed_); + batchValue_->resizeOrCreateBatch(batchSize, numSequences, starts, reversed_); batchValue_->resizeOrCreate(*output_.value); - batchValue_->copy(*inputValue, *gate_.value, /* seq2batch */true); + batchValue_->copy(*inputValue, *gate_.value, /* seq2batch */ true); if (bias_ && bias_->getWGrad()) { gate_.value->addBias(*(bias_->getW()), 1); } @@ -315,14 +326,14 @@ void GatedRecurrentLayer::forwardBatch(int batchSize, MatrixPtr outputValueTmp = batchValue_->getBatchValue(n); gruValue.outputValue = outputValueTmp->getData(); gruValue.gateValue = - (batchValue_->getBatchValue(*gate_.value, n))->getData(); + (batchValue_->getBatchValue(*gate_.value, n))->getData(); gruValue.resetOutputValue = - (batchValue_->getBatchValue(*resetOutput_.value, n))->getData(); + (batchValue_->getBatchValue(*resetOutput_.value, n))->getData(); batchSize = outputValueTmp->getHeight(); gruValue.prevOutValue = - (n == 0 ? nullptr - : (batchValue_->getBatchValue(n - 1, batchSize))->getData()); + (n == 0 ? nullptr + : (batchValue_->getBatchValue(n - 1, batchSize))->getData()); { if (useGpu_) { @@ -333,13 +344,10 @@ void GatedRecurrentLayer::forwardBatch(int batchSize, } } } - { - batchValue_->copyBackSeq(*output_.value); - } + { batchValue_->copyBackSeq(*output_.value); } } -void GatedRecurrentLayer::backwardBatch(int batchSize, - MatrixPtr inputGrad) { +void GatedRecurrentLayer::backwardBatch(int batchSize, MatrixPtr inputGrad) { REGISTER_TIMER_INFO("GruBwBatchTime", getName().c_str()); hl_gru_value gruValue; gruValue.gateWeight = (gateWeight_->getW())->getData(); @@ -347,18 +355,17 @@ void GatedRecurrentLayer::backwardBatch(int batchSize, hl_gru_grad gruGrad; gruGrad.gateWeightGrad = - (gateWeight_->getWGrad() ? gateWeight_->getWGrad()->getData() : nullptr); + (gateWeight_->getWGrad() ? gateWeight_->getWGrad()->getData() : nullptr); gruGrad.stateWeightGrad = - (stateWeight_->getWGrad() ? stateWeight_->getWGrad()->getData() : nullptr); + (stateWeight_->getWGrad() ? stateWeight_->getWGrad()->getData() + : nullptr); if (!batchGrad_) { batchGrad_.reset(new SequenceToBatch(useGpu_)); } batchGrad_->shareIndexWith(*batchValue_); - { - batchGrad_->copyFromSeq(*output_.grad); - } + { batchGrad_->copyFromSeq(*output_.grad); } { int numBatch = batchGrad_->getNumBatch(); @@ -366,39 +373,36 @@ void GatedRecurrentLayer::backwardBatch(int batchSize, AsyncGpuBlock asyncGpuBlock; for (int n = (int)numBatch - 1; n >= 0; n--) { gruValue.gateValue = - (batchGrad_->getBatchValue(*gate_.value, n))->getData(); + (batchGrad_->getBatchValue(*gate_.value, n))->getData(); gruValue.resetOutputValue = - (batchGrad_->getBatchValue(*resetOutput_.value, n))->getData(); + (batchGrad_->getBatchValue(*resetOutput_.value, n))->getData(); - MatrixPtr outputGradTmp = batchGrad_->getBatchValue(n); + MatrixPtr outputGradTmp = batchGrad_->getBatchValue(n); gruGrad.outputGrad = outputGradTmp->getData(); - gruGrad.gateGrad = - (batchGrad_->getBatchValue(*gate_.grad , n))->getData(); + gruGrad.gateGrad = (batchGrad_->getBatchValue(*gate_.grad, n))->getData(); gruGrad.resetOutputGrad = - (batchGrad_->getBatchValue(*resetOutput_.grad , n))->getData(); + (batchGrad_->getBatchValue(*resetOutput_.grad, n))->getData(); { batchSize = outputGradTmp->getHeight(); gruValue.prevOutValue = - (n == 0 ? nullptr - : (batchValue_->getBatchValue(n - 1, batchSize))->getData()); + (n == 0 ? nullptr : (batchValue_->getBatchValue(n - 1, batchSize)) + ->getData()); gruGrad.prevOutGrad = - (n == 0 ? nullptr - : (batchGrad_->getBatchValue(n - 1, batchSize))->getData()); + (n == 0 ? nullptr + : (batchGrad_->getBatchValue(n - 1, batchSize))->getData()); if (useGpu_) { - GruCompute::backward<1>(gruValue, gruGrad, getSize(), - batchSize); + GruCompute::backward<1>(gruValue, gruGrad, getSize(), batchSize); } else { - GruCompute::backward<0>(gruValue, gruGrad, getSize(), - batchSize); + GruCompute::backward<0>(gruValue, gruGrad, getSize(), batchSize); } } } } if (inputGrad) { - batchGrad_->add(*inputGrad, *gate_.grad, /* seq2batch */false); + batchGrad_->add(*inputGrad, *gate_.grad, /* seq2batch */ false); } if (bias_ && bias_->getWGrad()) { bias_->getWGrad()->collectBias(*gate_.grad, /* scale */ 1); diff --git a/paddle/gserver/layers/GatedRecurrentLayer.h b/paddle/gserver/layers/GatedRecurrentLayer.h index 19f71206bc..3b8706a44e 100644 --- a/paddle/gserver/layers/GatedRecurrentLayer.h +++ b/paddle/gserver/layers/GatedRecurrentLayer.h @@ -63,13 +63,19 @@ public: LayerStatePtr getState(); protected: - void forwardSequence(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputValue); - void backwardSequence(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputGrad); - - void forwardBatch(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputValue); + void forwardSequence(int batchSize, + size_t numSequences, + const int* starts, + MatrixPtr inputValue); + void backwardSequence(int batchSize, + size_t numSequences, + const int* starts, + MatrixPtr inputGrad); + + void forwardBatch(int batchSize, + size_t numSequences, + const int* starts, + MatrixPtr inputValue); void backwardBatch(int batchSize, MatrixPtr inputGrad); protected: diff --git a/paddle/gserver/layers/GetOutputLayer.cpp b/paddle/gserver/layers/GetOutputLayer.cpp index f036cd2b52..01579d55fd 100644 --- a/paddle/gserver/layers/GetOutputLayer.cpp +++ b/paddle/gserver/layers/GetOutputLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" namespace paddle { diff --git a/paddle/gserver/layers/GruCompute.cpp b/paddle/gserver/layers/GruCompute.cpp index c942122633..d9d423af44 100644 --- a/paddle/gserver/layers/GruCompute.cpp +++ b/paddle/gserver/layers/GruCompute.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "GruCompute.h" #include "hl_recurrent_apply.cuh" @@ -20,14 +19,12 @@ limitations under the License. */ namespace paddle { void GruCompute::init(LayerConfig &config) { - activeNode_ = hlActiveType(config.active_type()); - activeGate_ = hlActiveType(config.active_gate_type()); + activeNode_ = hlActiveType(config.active_type()); + activeGate_ = hlActiveType(config.active_gate_type()); } template <> -void GruCompute::forward<0>(hl_gru_value value, - int frameSize, - int batchSize) { +void GruCompute::forward<0>(hl_gru_value value, int frameSize, int batchSize) { hl_cpu_gru_forward(hppl::forward::gru_resetOutput(), hppl::forward::gru_finalOutput(), value, @@ -39,17 +36,17 @@ void GruCompute::forward<0>(hl_gru_value value, template <> void GruCompute::backward<0>(hl_gru_value value, - hl_gru_grad grad, - int frameSize, - int batchSize) { -hl_cpu_gru_backward(hppl::backward::gru_stateGrad(), - hppl::backward::gru_resetGrad(), - value, - grad, - frameSize, - batchSize, - activeNode_, - activeGate_); + hl_gru_grad grad, + int frameSize, + int batchSize) { + hl_cpu_gru_backward(hppl::backward::gru_stateGrad(), + hppl::backward::gru_resetGrad(), + value, + grad, + frameSize, + batchSize, + activeNode_, + activeGate_); } } // namespace paddle diff --git a/paddle/gserver/layers/GruCompute.h b/paddle/gserver/layers/GruCompute.h index 3a1b69b940..58b5aacba0 100644 --- a/paddle/gserver/layers/GruCompute.h +++ b/paddle/gserver/layers/GruCompute.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/TypeDefs.h" @@ -29,7 +28,9 @@ public: void forward(hl_gru_value value, int frameSize, int batchSize = 1); template - void backward(hl_gru_value value, hl_gru_grad grad, int frameSize, + void backward(hl_gru_value value, + hl_gru_grad grad, + int frameSize, int batchSize = 1); public: diff --git a/paddle/gserver/layers/GruStepLayer.cpp b/paddle/gserver/layers/GruStepLayer.cpp index 501229d10a..6c9b0c5771 100644 --- a/paddle/gserver/layers/GruStepLayer.cpp +++ b/paddle/gserver/layers/GruStepLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "GruCompute.h" #include "paddle/utils/Stat.h" @@ -32,7 +31,8 @@ namespace paddle { * \f[ * update \ gate: z_t = actGate(xz_t + U_z * prev_out + bias_z) \\ * reset \ gate: r_t = actGate(xr_t + U_r * prev_out + bias_r) \\ - * output \ candidate: {h}_t = actNode(xi_t + U * dot(r_t, prev_out) + bias_o) \\ + * output \ candidate: {h}_t = actNode(xi_t + U * dot(r_t, prev_out) + bias_o) + * \\ * output: h_t = dot((1-z_t), prev_out) + dot(z_t, prev_out) * \f] * @@ -91,10 +91,16 @@ void GruStepLayer::forward(PassType passType) { int batchSize = input.getBatchSize(); resetOutput(batchSize, getSize()); - resetSpecifyOutput(gate_, batchSize, getSize() * 3, - /* isValueClean */ false, /* isGradClean */ false); - resetSpecifyOutput(resetOutput_, batchSize, getSize(), - /* isValueClean */ false, /* isGradClean */ false); + resetSpecifyOutput(gate_, + batchSize, + getSize() * 3, + /* isValueClean */ false, + /* isGradClean */ false); + resetSpecifyOutput(resetOutput_, + batchSize, + getSize(), + /* isValueClean */ false, + /* isGradClean */ false); gate_.value->assign(*input.value); if (bias_) { gate_.value->addBias(*(bias_->getW()), 1); @@ -103,7 +109,7 @@ void GruStepLayer::forward(PassType passType) { hl_gru_value gruValue; gruValue.gateWeight = weight_->getW()->getData(); gruValue.stateWeight = weight_->getW()->getData() + getSize() * getSize() * 2; - gruValue.gateValue = gate_.value->getData();; + gruValue.gateValue = gate_.value->getData(); gruValue.resetOutputValue = resetOutput_.value->getData(); gruValue.outputValue = output_.value->getData(); gruValue.prevOutValue = prevOutput.value->getData(); @@ -125,17 +131,18 @@ void GruStepLayer::backward(const UpdateCallback& callback) { hl_gru_value gruValue; gruValue.gateWeight = weight_->getW()->getData(); gruValue.stateWeight = weight_->getW()->getData() + getSize() * getSize() * 2; - gruValue.gateValue = gate_.value->getData();; + gruValue.gateValue = gate_.value->getData(); gruValue.resetOutputValue = resetOutput_.value->getData(); gruValue.outputValue = output_.value->getData(); gruValue.prevOutValue = prevOutput.value->getData(); - hl_gru_grad gruGrad; + hl_gru_grad gruGrad; gruGrad.gateWeightGrad = - (weight_->getWGrad() ? weight_->getWGrad()->getData() : nullptr); + (weight_->getWGrad() ? weight_->getWGrad()->getData() : nullptr); gruGrad.stateWeightGrad = - (weight_->getWGrad() ? - weight_->getWGrad()->getData() + getSize() * getSize() * 2 : nullptr); + (weight_->getWGrad() + ? weight_->getWGrad()->getData() + getSize() * getSize() * 2 + : nullptr); gruGrad.gateGrad = gate_.grad->getData(); gruGrad.resetOutputGrad = resetOutput_.grad->getData(); diff --git a/paddle/gserver/layers/HierarchicalSigmoidLayer.cpp b/paddle/gserver/layers/HierarchicalSigmoidLayer.cpp index 7091c6aa22..61bc777785 100644 --- a/paddle/gserver/layers/HierarchicalSigmoidLayer.cpp +++ b/paddle/gserver/layers/HierarchicalSigmoidLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "HierarchicalSigmoidLayer.h" #include "paddle/utils/Util.h" @@ -61,10 +60,16 @@ void HierarchicalSigmoidLayer::forward(PassType passType) { int batchSize = getInputValue(0)->getHeight(); int size = getSize(); reserveOutput(batchSize, size); - Matrix::resizeOrCreate(preOutput_.value, batchSize, codeLength_, - /* trans */ false, useGpu(deviceId_)); - Matrix::resizeOrCreate(preOutput_.grad, batchSize, codeLength_, - /* trans */ false, useGpu(deviceId_)); + Matrix::resizeOrCreate(preOutput_.value, + batchSize, + codeLength_, + /* trans */ false, + useGpu(deviceId_)); + Matrix::resizeOrCreate(preOutput_.grad, + batchSize, + codeLength_, + /* trans */ false, + useGpu(deviceId_)); IVectorPtr label = getInput(*getLabelLayer()).ids; @@ -76,16 +81,18 @@ void HierarchicalSigmoidLayer::forward(PassType passType) { } for (size_t i = 0; i < inputLayers_.size() - 1; ++i) { MatrixPtr input = getInputValue(i); - preOutput_.value->mulByBitCode(numClasses_, *label, *weights_[i]->getW(), - *input); + preOutput_.value->mulByBitCode( + numClasses_, *label, *weights_[i]->getW(), *input); } // keep consistent with the clipping in the following softrelu preOutput_.value->clip(-40.0, 40.0); - preOutput_.value->sumByBitCode(numClasses_, *label, *output_.value, + preOutput_.value->sumByBitCode(numClasses_, + *label, + *output_.value, -1); // scaleSum preOutput_.value->softrelu(*preOutput_.value); - MatrixPtr sum = Matrix::create(batchSize, - 1, /* trans= */ false, useGpu(deviceId_)); + MatrixPtr sum = + Matrix::create(batchSize, 1, /* trans= */ false, useGpu(deviceId_)); preOutput_.value->rowSum(*sum); output_.value->add(*sum); } @@ -97,8 +104,8 @@ void HierarchicalSigmoidLayer::backward(const UpdateCallback& callback) { preOutput_.grad->subByBitCode(numClasses_, *label); if (biases_ && biases_->getWGrad()) { - preOutput_.grad->addByBitCodeBackward(numClasses_, *label, - *biases_->getWGrad()); + preOutput_.grad->addByBitCodeBackward( + numClasses_, *label, *biases_->getWGrad()); /* Increasing the number of gradient */ biases_->getParameterPtr()->incUpdate(callback); diff --git a/paddle/gserver/layers/HierarchicalSigmoidLayer.h b/paddle/gserver/layers/HierarchicalSigmoidLayer.h index 1942c5fe1e..10762bc926 100644 --- a/paddle/gserver/layers/HierarchicalSigmoidLayer.h +++ b/paddle/gserver/layers/HierarchicalSigmoidLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -20,15 +19,15 @@ limitations under the License. */ namespace paddle { /** - * Organize the classes into a binary tree. At each node, a sigmoid function + * Organize the classes into a binary tree. At each node, a sigmoid function * is used to calculate the probability of belonging to the right branch. - * This idea is from "F. Morin, Y. Bengio (AISTATS 05): + * This idea is from "F. Morin, Y. Bengio (AISTATS 05): * Hierarchical Probabilistic Neural Network Language Model." * * Here we uses a simple way of making the binary tree. * Assuming the number of classes C = 6, * The classes are organized as a binary tree in the following way: - * + * * @code{.py} * *-*-*- 2 * | | |- 3 @@ -44,15 +43,15 @@ namespace paddle { * - Node 0 ... C-2 are internal nodes. * - Node C-1 ... 2C-2 are leaf nodes. * - Class c is represented by leaf node \f$c+C-1\f$. - * + * * We assign an id for each node: * - the id of root be 0. * - the left child of a node i is 2*i+1. * - the right child of a node i is 2*i+2. * * It's easy to see that: - * - the parent of node i is \f$\left\lfloor(i-1)/2\right\rfloor\f$. - * - the j-th level ancestor of node i is + * - the parent of node i is \f$\left\lfloor(i-1)/2\right\rfloor\f$. + * - the j-th level ancestor of node i is * \f$\left\lfloor(i+1)/2^{j+1}\right\rfloor - 1\f$. * - A node i is a left child of its parent if \f$(i-1)\%2==0\f$. * @@ -69,7 +68,7 @@ public: protected: /** * The last of inputs is label layer. - */ + */ LayerPtr getLabelLayer() { return inputLayers_.back(); } WeightList weights_; diff --git a/paddle/gserver/layers/IdentityProjection.cpp b/paddle/gserver/layers/IdentityProjection.cpp index 6b7d20cc50..b38656c960 100644 --- a/paddle/gserver/layers/IdentityProjection.cpp +++ b/paddle/gserver/layers/IdentityProjection.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "Projection.h" @@ -29,7 +28,8 @@ namespace paddle { class IdentityProjection : public Projection { public: IdentityProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu); + const ParameterPtr& parameter, + bool useGpu); virtual void forward(); virtual void backward(const UpdateCallback& callback); }; @@ -70,7 +70,8 @@ void IdentityProjection::backward(const UpdateCallback& callback) { class IdentityOffsetProjection : public Projection { public: IdentityOffsetProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu); + const ParameterPtr& parameter, + bool useGpu); virtual void forward(); virtual void backward(const UpdateCallback& callback); }; diff --git a/paddle/gserver/layers/InterpolationLayer.cpp b/paddle/gserver/layers/InterpolationLayer.cpp index 4102df840a..b00bee2356 100644 --- a/paddle/gserver/layers/InterpolationLayer.cpp +++ b/paddle/gserver/layers/InterpolationLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -26,8 +25,8 @@ namespace paddle { * \f[ * y.row[i] = w[i] * x_1.row[i] + (1 - w[i]) * x_2.row[i] * \f] - * where \f$x_1\f$ and \f$x_2\f$ are two (batchSize x dataDim) inputs, - * \f$w\f$ is (batchSize x 1) weight vector, + * where \f$x_1\f$ and \f$x_2\f$ are two (batchSize x dataDim) inputs, + * \f$w\f$ is (batchSize x 1) weight vector, * and \f$y\f$ is (batchSize x dataDim) output. * * The config file api is interpolation_layer. diff --git a/paddle/gserver/layers/Layer.cpp b/paddle/gserver/layers/Layer.cpp index 78d15c5530..0f9e7c0ff8 100644 --- a/paddle/gserver/layers/Layer.cpp +++ b/paddle/gserver/layers/Layer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "paddle/utils/Logging.h" @@ -123,19 +122,22 @@ LayerPtr Layer::create(const LayerConfig& config) { return LayerPtr(registrar_.createByType(config.type(), config)); } -void Layer::resetSpecifyOutput(Argument& output, size_t height, size_t width, - bool isValueClean, bool isGradClean) { +void Layer::resetSpecifyOutput(Argument& output, + size_t height, + size_t width, + bool isValueClean, + bool isGradClean) { SetDevice device(output.deviceId); - Matrix::resizeOrCreate(output.value, height, width, /* trans */ false, - useGpu(output.deviceId)); + Matrix::resizeOrCreate( + output.value, height, width, /* trans */ false, useGpu(output.deviceId)); if (isValueClean) { output.value->zeroMem(); } if (passType_ != PASS_TEST && needGradient()) { - Matrix::resizeOrCreate(output.grad, height, width, /* trans */ false, - useGpu(output.deviceId)); + Matrix::resizeOrCreate( + output.grad, height, width, /* trans */ false, useGpu(output.deviceId)); if (isGradClean) { output.grad->zeroMem(); } @@ -227,8 +229,10 @@ void Layer::waitAndMergeOutputGrad() { if (outputOtherDevice_.size() == 1) return; } - Matrix::resizeOrCreate(tmpGrad_, output_.grad->getHeight(), - output_.grad->getWidth(), /* trans */ false, + Matrix::resizeOrCreate(tmpGrad_, + output_.grad->getHeight(), + output_.grad->getWidth(), + /* trans */ false, useGpu(output_.deviceId)); for (; i != outputOtherDevice_.size(); i++) { @@ -258,8 +262,8 @@ void Layer::zeroGrad() { } void Layer::initNeedFlags() { - auto initFlag = [this](bool& flag, bool (Layer::*flagQueryFunc)() const, - ParameterType type) { + auto initFlag = [this]( + bool& flag, bool (Layer::*flagQueryFunc)() const, ParameterType type) { flag = false; if (biasParameter_ && biasParameter_->hasType(type)) { flag = true; @@ -293,10 +297,12 @@ void Layer::showOutputStats() { } MatrixPtr outSquare; if (dynamic_cast(out.get())) { - GpuSparseMatrix *tmp = dynamic_cast(out.get()); - outSquare = std::make_shared( - tmp->getHeight(), tmp->getWidth(), tmp->getElementCnt(), - tmp->getValueType(), tmp->getFormat()); + GpuSparseMatrix* tmp = dynamic_cast(out.get()); + outSquare = std::make_shared(tmp->getHeight(), + tmp->getWidth(), + tmp->getElementCnt(), + tmp->getValueType(), + tmp->getFormat()); } else { outSquare = out->clone(); } @@ -321,8 +327,7 @@ void Layer::showOutputStats() { std = std > 0 ? std : 0; LOG(INFO) << "The output state of " << config_.name() << ": mean=" << mean << ", " - << "std=" << std - << ", " + << "std=" << std << ", " << "min=" << min << ", " << "max=" << max; } @@ -348,8 +353,8 @@ void Layer::backwardActivation() { if (config_.error_clipping_threshold() > 0.0f) { if (FLAGS_log_error_clipping) { CpuVector outGradVec(0, nullptr); - outGradVec.subVecFrom(output_.grad->getData(), 0, - output_.grad->getElementCnt()); + outGradVec.subVecFrom( + output_.grad->getData(), 0, output_.grad->getElementCnt()); real maxAbsGrad = outGradVec.getAbsMax(); if (maxAbsGrad > config_.error_clipping_threshold()) { real avgAbsGrad = outGradVec.getAbsSum() / outGradVec.getSize(); @@ -376,16 +381,19 @@ void Layer::forwardDropOut() { if (passType_ == PASS_TRAIN || passType_ == PASS_METRIC_TRAIN || passType_ == PASS_METRIC_TRAIN_WITH_NOERROR) { // new dropOutMask_ if dropOutMask_ is null ptr - Matrix::resizeOrCreate(dropOutMask_, outV->getHeight(), outV->getWidth(), - false, useGpu(deviceId_)); + Matrix::resizeOrCreate(dropOutMask_, + outV->getHeight(), + outV->getWidth(), + false, + useGpu(deviceId_)); dropOutMask_->randomizeUniform(); // generate a uniform random matrix dropOutMask_->biggerThanScalar(config_.drop_rate()); // random mask outV->dotMul(*outV, *dropOutMask_); // dropout } else if (passType_ == PASS_GC) { // only initialize once if (!dropOutMask_) { - dropOutMask_ = Matrix::create(outV->getHeight(), outV->getWidth(), false, - useGpu(deviceId_)); + dropOutMask_ = Matrix::create( + outV->getHeight(), outV->getWidth(), false, useGpu(deviceId_)); // We use cpu matrix to generate mask so that the mask // will be same for both gpu version and cpu version. // This will help unittest to make sure they have same result. diff --git a/paddle/gserver/layers/Layer.h b/paddle/gserver/layers/Layer.h index ae7cdb0028..3d427a1ac6 100644 --- a/paddle/gserver/layers/Layer.h +++ b/paddle/gserver/layers/Layer.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -109,7 +108,7 @@ public: virtual void waitInputValue(); /** - * Copy layer's output_ to other device. + * Copy layer's output_ to other device. * If output layer is in other device, called after Layer::forward() function. */ virtual void copyOutputToOtherDevice(); @@ -189,8 +188,11 @@ protected: * Reset to value zero if isValueClean = true, * Reset to grad zero if isGradClean = true. */ - void resetSpecifyOutput(Argument& output, size_t height, size_t width, - bool isValueClean, bool isGradClean); + void resetSpecifyOutput(Argument& output, + size_t height, + size_t width, + bool isValueClean, + bool isGradClean); /** * Add output argument to other devices. @@ -204,48 +206,48 @@ public: /// Register a Layer static ClassRegistrar registrar_; - /** + /** * Get the flag whether layer need to compute gradient. */ bool needGradient() const { return needGradient_; } - /** + /** * Set the flag whether layer need to compute gradient. */ void setNeedGradient(bool need) { needGradient_ = need; } - /** + /** * Set the flag whether layer need to re-compute sequence information, * which includes sequenceStartPositions or subSequenceStartPositions. */ void setNeedSequenceInfo(bool need) { needSequenceInfo_ = need; } - /** + /** * Get layer's name. */ const std::string& getName() const { return config_.name(); } - /** + /** * Get layer's type. */ const std::string& getType() const { return config_.type(); } - /** + /** * Get layer's size. */ size_t getSize() const { return config_.size(); } - /** + /** * Get layer's deviceId. */ int getDeviceId() const { return deviceId_; } - /** + /** * Add the inputLayer. */ void addPrev(LayerPtr l) { inputLayers_.push_back(l); } - /** + /** * Get the size of inputLayer[i]. */ const LayerPtr& getPrev(size_t i) { return inputLayers_[i]; } @@ -265,7 +267,7 @@ public: */ const MatrixPtr& getOutputGrad() { return output_.grad; } /** - * If layer has multi-output, set output into outputMap_. + * If layer has multi-output, set output into outputMap_. */ void setOutput(const std::string& name, Argument* output) { outputMap_[name] = output; @@ -351,8 +353,8 @@ public: /** * Intialization for sub network if there has sub network. * @param rootNetwork root network - * @param config model config - * @param parameterTypes parameter's type + * @param config model config + * @param parameterTypes parameter's type * @param useGpu whether to use gpu or not */ virtual void initSubNetwork(NeuralNetwork* rootNetwork, @@ -391,7 +393,8 @@ public: /** * Reset the internal state variables. * Allocate them if they have not been allocated. - * This function need to called before Layer::forward() for generating sequence. + * This function need to called before Layer::forward() for generating + * sequence. * * This is used for sequence generation. When generating sequence, the * calculation at current timestamp depends on the state from previous @@ -407,7 +410,7 @@ public: virtual void setState(LayerStatePtr state) {} /** - * Get layer state. + * Get layer state. * @return A copy of internal state. */ virtual LayerStatePtr getState() { return nullptr; } diff --git a/paddle/gserver/layers/LinearChainCRF.cpp b/paddle/gserver/layers/LinearChainCRF.cpp index fb54fd26cf..2b3a50b2e2 100644 --- a/paddle/gserver/layers/LinearChainCRF.cpp +++ b/paddle/gserver/layers/LinearChainCRF.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LinearChainCRF.h" diff --git a/paddle/gserver/layers/LinearChainCRF.h b/paddle/gserver/layers/LinearChainCRF.h index c33c83b259..6368f2b9de 100644 --- a/paddle/gserver/layers/LinearChainCRF.h +++ b/paddle/gserver/layers/LinearChainCRF.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/math/Matrix.h" @@ -31,7 +30,8 @@ public: * \f$P(s) = (1/Z) exp(a_{s_1} + b_{s_L} * + \sum_{l=1}^L x_{s_l} * + \sum_{l=2}^L w_{s_{l-1},s_l})\f$ - * where \f$Z\f$ is a normalization value so that the sum of \f$P(s)\f$ over all possible + * where \f$Z\f$ is a normalization value so that the sum of \f$P(s)\f$ over + * all possible * sequences is \f$1\f$, and \f$x\f$ is the input feature to the CRF. */ LinearChainCRF(int numClasses, real* para, real* grad); diff --git a/paddle/gserver/layers/LinearChainCTC.cpp b/paddle/gserver/layers/LinearChainCTC.cpp index c0ffadbd91..3368eb4d8a 100644 --- a/paddle/gserver/layers/LinearChainCTC.cpp +++ b/paddle/gserver/layers/LinearChainCTC.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LinearChainCTC.h" #include @@ -90,7 +89,9 @@ LinearChainCTC::LinearChainCTC(int numClasses, bool normByTimes) Matrix::resizeOrCreate(gradTerms_, 1, numClasses_); } -real LinearChainCTC::forward(real* softmaxSeq, int softmaxSeqLen, int* labelSeq, +real LinearChainCTC::forward(real* softmaxSeq, + int softmaxSeqLen, + int* labelSeq, int labelSeqLen) { isInvalid_ = false; totalTime_ = softmaxSeqLen; @@ -215,7 +216,9 @@ real LinearChainCTC::forward(real* softmaxSeq, int softmaxSeqLen, int* labelSeq, return -logProb_; } -void LinearChainCTC::backward(real* softmaxSeq, real* grad, int* labelSeq, +void LinearChainCTC::backward(real* softmaxSeq, + real* grad, + int* labelSeq, int labelSeqLen) { /* if not meet the conditions of CTC computing, then set the grads to zeros */ if (isInvalid_) { @@ -246,9 +249,9 @@ void LinearChainCTC::backward(real* softmaxSeq, real* grad, int* labelSeq, logMul(logProb_, logActsData[i * numClasses_ + j]))) / totalTime_; } else { - grad[i * numClasses_ + j] += -safeExp(logDiv( - gradTermsData[j], - logMul(logProb_, logActsData[i * numClasses_ + j]))); + grad[i * numClasses_ + j] += -safeExp( + logDiv(gradTermsData[j], + logMul(logProb_, logActsData[i * numClasses_ + j]))); } } } diff --git a/paddle/gserver/layers/LinearChainCTC.h b/paddle/gserver/layers/LinearChainCTC.h index b09218e3e7..0a93d2e9a6 100644 --- a/paddle/gserver/layers/LinearChainCTC.h +++ b/paddle/gserver/layers/LinearChainCTC.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -25,11 +24,15 @@ public: LinearChainCTC(int numClasses, bool normByTimes); // Calculate the negative log probability as loss - real forward(real* softmaxSeq, int softmaxSeqLen, int* labelSeq, + real forward(real* softmaxSeq, + int softmaxSeqLen, + int* labelSeq, int labelSeqLen); // calculate the gradient - void backward(real* softmaxSeq, real* softmaxSeqGrad, int* labelSeq, + void backward(real* softmaxSeq, + real* softmaxSeqGrad, + int* labelSeq, int labelSeqLen); protected: diff --git a/paddle/gserver/layers/LstmCompute.cpp b/paddle/gserver/layers/LstmCompute.cpp index ced9636d35..38057636ed 100644 --- a/paddle/gserver/layers/LstmCompute.cpp +++ b/paddle/gserver/layers/LstmCompute.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "hl_recurrent_apply.cuh" #include "LstmCompute.h" @@ -27,22 +26,31 @@ void LstmCompute::init(LayerConfig &config) { template <> void LstmCompute::forwardOneSequence<0>(hl_lstm_value value, int frameSize) { - hl_cpu_lstm_forward(hppl::forward::lstm(), value, - frameSize, activeNode_, activeGate_, + hl_cpu_lstm_forward(hppl::forward::lstm(), + value, + frameSize, + activeNode_, + activeGate_, activeState_); } template <> -void LstmCompute::backwardOneSequence<0>(hl_lstm_value value, hl_lstm_grad grad, - int frameSize) { - hl_cpu_lstm_backward(hppl::backward::lstm(), value, grad, - frameSize, activeNode_, activeGate_, +void LstmCompute::backwardOneSequence<0>(hl_lstm_value value, + hl_lstm_grad grad, + int frameSize) { + hl_cpu_lstm_backward(hppl::backward::lstm(), + value, + grad, + frameSize, + activeNode_, + activeGate_, activeState_); } template <> -void LstmCompute::forwardBatch<0>(hl_lstm_value value, int frameSize, - int batchSize) { +void LstmCompute::forwardBatch<0>(hl_lstm_value value, + int frameSize, + int batchSize) { for (int b = 0; b < batchSize; b++) { forwardOneSequence<0>(value, frameSize); @@ -57,8 +65,10 @@ void LstmCompute::forwardBatch<0>(hl_lstm_value value, int frameSize, } template <> -void LstmCompute::backwardBatch<0>(hl_lstm_value value, hl_lstm_grad grad, - int frameSize, int batchSize) { +void LstmCompute::backwardBatch<0>(hl_lstm_value value, + hl_lstm_grad grad, + int frameSize, + int batchSize) { for (int b = 0; b < batchSize; b++) { backwardOneSequence<0>(value, grad, frameSize); diff --git a/paddle/gserver/layers/LstmCompute.h b/paddle/gserver/layers/LstmCompute.h index 638acdb56d..97be7218f2 100644 --- a/paddle/gserver/layers/LstmCompute.h +++ b/paddle/gserver/layers/LstmCompute.h @@ -35,7 +35,9 @@ public: void forwardBatch(hl_lstm_value value, int frameSize, int batchSize); template - void backwardBatch(hl_lstm_value value, hl_lstm_grad grad, int frameSize, + void backwardBatch(hl_lstm_value value, + hl_lstm_grad grad, + int frameSize, int batchSize); /** @@ -51,7 +53,8 @@ public: template void forwardOneSequence(hl_lstm_value value, int frameSize); template - void backwardOneSequence(hl_lstm_value value, hl_lstm_grad grad, + void backwardOneSequence(hl_lstm_value value, + hl_lstm_grad grad, int frameSize); public: diff --git a/paddle/gserver/layers/LstmLayer.cpp b/paddle/gserver/layers/LstmLayer.cpp index 61ad47a7fb..e70a20e5c0 100644 --- a/paddle/gserver/layers/LstmLayer.cpp +++ b/paddle/gserver/layers/LstmLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LstmLayer.h" #include "paddle/math/Matrix.h" #include "paddle/math/BaseMatrix.h" @@ -35,14 +34,26 @@ bool LstmLayer::init(const LayerMap &layerMap, if (biasParameter_.get() != NULL) { bias_.reset(new Weight(1, getSize() * 7, biasParameter_)); if (bias_->getW()) { - localBias_ = Matrix::create(nullptr, /* height= */ 1, getSize() * 4, - /* trans= */ false, useGpu_); - checkIg_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - checkFg_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - checkOg_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + localBias_ = Matrix::create(nullptr, + /* height= */ 1, + getSize() * 4, + /* trans= */ false, + useGpu_); + checkIg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkFg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkOg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); localBias_->setData(bias_->getW()->getData()); checkIg_->setData(bias_->getW()->getData() + getSize() * 4); @@ -51,14 +62,26 @@ bool LstmLayer::init(const LayerMap &layerMap, } if (bias_->getWGrad()) { - localBiasGrad_ = Matrix::create(nullptr, /* height= */ 1, getSize() * 4, - /* trans= */ false, useGpu_); - checkIgGrad_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - checkFgGrad_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - checkOgGrad_ = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + localBiasGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize() * 4, + /* trans= */ false, + useGpu_); + checkIgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkFgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkOgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); localBiasGrad_->setData(bias_->getWGrad()->getData()); checkIgGrad_->setData(bias_->getWGrad()->getData() + getSize() * 4); checkFgGrad_->setData(bias_->getWGrad()->getData() + getSize() * 5); @@ -84,8 +107,8 @@ bool LstmLayer::init(const LayerMap &layerMap, void LstmLayer::resetState() { CHECK(!reversed_) << "state is not allowed for reversed lstmemory layer"; - Matrix::resizeOrCreate(prevOutput_, 1, getSize(), /* trans= */ false, - useGpu_); + Matrix::resizeOrCreate( + prevOutput_, 1, getSize(), /* trans= */ false, useGpu_); Matrix::resizeOrCreate(prevState_, 1, getSize(), /* trans= */ false, useGpu_); prevOutput_->resize(0, getSize()); prevState_->resize(0, getSize()); @@ -138,8 +161,10 @@ void LstmLayer::forward(PassType passType) { CHECK_EQ(starts[numSequences], batchSize); Matrix::resizeOrCreate(gate_.value, - /* height= */ batchSize, getSize() * 4, - /* trans= */ false, useGpu_); + /* height= */ batchSize, + getSize() * 4, + /* trans= */ false, + useGpu_); if (prevOutput_) { size_t prevNumSeq = useBatch_ ? numSequences : 1; if (prevOutput_->getHeight() == 0) { @@ -151,18 +176,29 @@ void LstmLayer::forward(PassType passType) { CHECK_EQ(prevOutput_->getHeight(), prevNumSeq) << "the number of sequences must be the same"; } - Matrix::resizeOrCreate(totalState_, prevState_->getHeight() + batchSize, - getSize(), /*trans*/ false, useGpu_); - state_.value = Matrix::create(nullptr, /* height= */ batchSize, getSize(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(totalState_, + prevState_->getHeight() + batchSize, + getSize(), + /*trans*/ false, + useGpu_); + state_.value = Matrix::create(nullptr, + /* height= */ batchSize, + getSize(), + /* trans= */ false, + useGpu_); state_.value->setData(totalState_->getData() + prevState_->getHeight() * getSize()); } else { - Matrix::resizeOrCreate(state_.value, /* height= */ batchSize, getSize(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(state_.value, + /* height= */ batchSize, + getSize(), + /* trans= */ false, + useGpu_); } Matrix::resizeOrCreate(preOutput_.value, - /* height= */ batchSize, getSize(), /* trans= */ false, + /* height= */ batchSize, + getSize(), + /* trans= */ false, useGpu_); if (!useBatch_) { @@ -171,7 +207,7 @@ void LstmLayer::forward(PassType passType) { if (!useSeqParallel_) { forwardBatch(batchSize, numSequences, starts, input.value); } else { - const int* starts = input.sequenceStartPositions->getData(useGpu_); + const int *starts = input.sequenceStartPositions->getData(useGpu_); forwardSeqParallel(batchSize, numSequences, starts, input.value); } } @@ -188,13 +224,19 @@ void LstmLayer::backward(const UpdateCallback &callback) { size_t numSequences = input.getNumSequences(); Matrix::resizeOrCreate(gate_.grad, - /* height= */ batchSize, getSize() * 4, - /* trans= */ false, useGpu_); + /* height= */ batchSize, + getSize() * 4, + /* trans= */ false, + useGpu_); Matrix::resizeOrCreate(state_.grad, - /* height= */ batchSize, getSize(), /* trans= */ false, + /* height= */ batchSize, + getSize(), + /* trans= */ false, useGpu_); Matrix::resizeOrCreate(preOutput_.grad, - /* height= */ batchSize, getSize(), /* trans= */ false, + /* height= */ batchSize, + getSize(), + /* trans= */ false, useGpu_); state_.grad->zero(); @@ -205,7 +247,7 @@ void LstmLayer::backward(const UpdateCallback &callback) { if (!useSeqParallel_) { backwardBatch(batchSize, numSequences, starts, input.grad); } else { - const int* starts = input.sequenceStartPositions->getData(useGpu_); + const int *starts = input.sequenceStartPositions->getData(useGpu_); backwardSeqParallel(batchSize, numSequences, starts, input.grad); } } @@ -216,8 +258,10 @@ void LstmLayer::backward(const UpdateCallback &callback) { weight_->getParameterPtr()->incUpdate(callback); } -void LstmLayer::forwardSequence(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputValue) { +void LstmLayer::forwardSequence(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputValue) { REGISTER_TIMER_INFO("LstmFwSequenceTime", getName().c_str()); gate_.value->assign(*inputValue); if (bias_) { @@ -255,10 +299,16 @@ void LstmLayer::forwardSequence(int batchSize, size_t numSequences, } }; - MatrixPtr frameGate = Matrix::create(nullptr, /* height= */ 1, getSize() * 4, - /* trans= */ false, useGpu_); - MatrixPtr frameOutput = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + MatrixPtr frameGate = Matrix::create(nullptr, + /* height= */ 1, + getSize() * 4, + /* trans= */ false, + useGpu_); + MatrixPtr frameOutput = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); if (!reversed_) { if (prevState_) { @@ -316,8 +366,10 @@ void LstmLayer::forwardSequence(int batchSize, size_t numSequences, } } -void LstmLayer::backwardSequence(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputGrad) { +void LstmLayer::backwardSequence(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputGrad) { REGISTER_TIMER_INFO("LstmBwSequenceTime", getName().c_str()); MatrixPtr weightT = weight_->getW()->getTranspose(); @@ -381,10 +433,16 @@ void LstmLayer::backwardSequence(int batchSize, size_t numSequences, } }; - MatrixPtr frameGate = Matrix::create(nullptr, /* height= */ 1, getSize() * 4, - /* trans= */ false, useGpu_); - MatrixPtr frameOutput = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + MatrixPtr frameGate = Matrix::create(nullptr, + /* height= */ 1, + getSize() * 4, + /* trans= */ false, + useGpu_); + MatrixPtr frameOutput = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); { AsyncGpuBlock asyncGpuBlock; @@ -422,11 +480,15 @@ void LstmLayer::backwardSequence(int batchSize, size_t numSequences, if (!reversed_) { weight_->getWGrad()->mul( output_.value->subMatrix(start, length - 1)->getTranspose(), - gate_.grad->subMatrix(start + 1, length - 1), 1, 1); + gate_.grad->subMatrix(start + 1, length - 1), + 1, + 1); } else { weight_->getWGrad()->mul( output_.value->subMatrix(start + 1, length - 1)->getTranspose(), - gate_.grad->subMatrix(start, length - 1), 1, 1); + gate_.grad->subMatrix(start, length - 1), + 1, + 1); } } } @@ -440,8 +502,10 @@ void LstmLayer::backwardSequence(int batchSize, size_t numSequences, } } -void LstmLayer::forwardBatch(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputValue) { +void LstmLayer::forwardBatch(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputValue) { REGISTER_TIMER_INFO("LstmFwBatchTime", getName().c_str()); hl_lstm_value lstmValue; @@ -452,8 +516,8 @@ void LstmLayer::forwardBatch(int batchSize, size_t numSequences, if (!batchValue_) { batchValue_.reset(new SequenceToBatch(useGpu_)); } - batchValue_->resizeOrCreateBatch(batchSize, numSequences, starts, reversed_, - prevOutput_ ? true : false); + batchValue_->resizeOrCreateBatch( + batchSize, numSequences, starts, reversed_, prevOutput_ ? true : false); batchValue_->resizeOrCreate(*output_.value); batchValue_->copy(*inputValue, *gate_.value, /* seq2batch */ true); @@ -479,8 +543,11 @@ void LstmLayer::forwardBatch(int batchSize, size_t numSequences, MatrixPtr batch1 = batchValue_->getBatchValue(n - 1, batchSize); gateValue->mul(batch1, weight_->getW(), 1, 1); } else if (prevOutput_) { - Matrix::resizeOrCreate(prevBatchOutput2_, gateValue->getHeight(), - getSize(), false, useGpu_); + Matrix::resizeOrCreate(prevBatchOutput2_, + gateValue->getHeight(), + getSize(), + false, + useGpu_); batchValue_->prevOutput2Batch(*prevOutput_, *prevBatchOutput2_); gateValue->mul(prevBatchOutput2_, weight_->getW(), 1, 1); @@ -525,8 +592,10 @@ void LstmLayer::getPrevBatchState(size_t numSequences) { batchValue_->getSeqOutputFromBatch(*prevState_, *state_.value); } -void LstmLayer::backwardBatch(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputGrad) { +void LstmLayer::backwardBatch(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputGrad) { REGISTER_TIMER_INFO("LstmBwBatchTime", getName().c_str()); hl_lstm_value lstmValue; @@ -593,11 +662,11 @@ void LstmLayer::backwardBatch(int batchSize, size_t numSequences, } } if (useGpu_) { - LstmCompute::backwardBatch<1>(lstmValue, lstmGrad, - getSize(), batchSize); + LstmCompute::backwardBatch<1>( + lstmValue, lstmGrad, getSize(), batchSize); } else { - LstmCompute::backwardBatch<0>(lstmValue, lstmGrad, - getSize(), batchSize); + LstmCompute::backwardBatch<0>( + lstmValue, lstmGrad, getSize(), batchSize); } } @@ -611,8 +680,8 @@ void LstmLayer::backwardBatch(int batchSize, size_t numSequences, MatrixPtr outputValue = batchValue_->getBatchValue(n - 1, batchSize); weight_->getWGrad()->mul(outputValue->getTranspose(), gateGrad, 1, 1); } else if (prevOutput_ && weight_->getWGrad()) { - weight_->getWGrad()->mul(prevBatchOutput2_->getTranspose(), gateGrad, 1, - 1); + weight_->getWGrad()->mul( + prevBatchOutput2_->getTranspose(), gateGrad, 1, 1); } } } @@ -625,8 +694,10 @@ void LstmLayer::backwardBatch(int batchSize, size_t numSequences, } } -void LstmLayer::forwardSeqParallel(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputValue) { +void LstmLayer::forwardSeqParallel(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputValue) { REGISTER_TIMER_INFO("LstmFwSeqParallelTime", getName().c_str()); gate_.value->assign(*inputValue); if (bias_) { @@ -641,14 +712,27 @@ void LstmLayer::forwardSeqParallel(int batchSize, size_t numSequences, real *checkFg = checkFg_->getData(); real *checkOg = checkOg_->getData(); real *weight = weight_->getW()->getData(); - hl_lstm_parallel_forward( - gateValue, stateValue, preOutputValue, outputValue, checkIg, checkFg, - checkOg, weight, starts, getSize(), numSequences, reversed_, activeNode_, - activeGate_, activeState_); + hl_lstm_parallel_forward(gateValue, + stateValue, + preOutputValue, + outputValue, + checkIg, + checkFg, + checkOg, + weight, + starts, + getSize(), + numSequences, + reversed_, + activeNode_, + activeGate_, + activeState_); } -void LstmLayer::backwardSeqParallel(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputGrad) { +void LstmLayer::backwardSeqParallel(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputGrad) { REGISTER_TIMER_INFO("LstmBwSeqParallelTime", getName().c_str()); real *gateValue = gate_.value->getData(); real *gateGrad = gate_.grad->getData(); @@ -675,11 +759,27 @@ void LstmLayer::backwardSeqParallel(int batchSize, size_t numSequences, checkOgGrad = nullptr; } - hl_lstm_parallel_backward_data( - gateValue, gateGrad, stateValue, stateGrad, preOutputValue, preOutputGrad, - outputGrad, checkIg, checkIgGrad, checkFg, checkFgGrad, checkOg, - checkOgGrad, weight, starts, getSize(), numSequences, reversed_, - activeNode_, activeGate_, activeState_); + hl_lstm_parallel_backward_data(gateValue, + gateGrad, + stateValue, + stateGrad, + preOutputValue, + preOutputGrad, + outputGrad, + checkIg, + checkIgGrad, + checkFg, + checkFgGrad, + checkOg, + checkOgGrad, + weight, + starts, + getSize(), + numSequences, + reversed_, + activeNode_, + activeGate_, + activeState_); if (inputGrad) { inputGrad->add(*gate_.grad); @@ -691,9 +791,14 @@ void LstmLayer::backwardSeqParallel(int batchSize, size_t numSequences, real *outputValue = output_.value->getData(); if (weight_->getWGrad()) { real *weightGrad = weight_->getWGrad()->getData(); - hl_lstm_parallel_backward_weight(weightGrad, outputValue, gateGrad, - starts, getSize(), batchSize, - numSequences, reversed_); + hl_lstm_parallel_backward_weight(weightGrad, + outputValue, + gateGrad, + starts, + getSize(), + batchSize, + numSequences, + reversed_); } } diff --git a/paddle/gserver/layers/LstmLayer.h b/paddle/gserver/layers/LstmLayer.h index e080a40141..5b936ff44e 100644 --- a/paddle/gserver/layers/LstmLayer.h +++ b/paddle/gserver/layers/LstmLayer.h @@ -97,12 +97,16 @@ protected: * @param starts Each start position of each samples. * @param inputValue The input values. */ - void forwardSequence(int batchSize, size_t numSequences, const int *starts, + void forwardSequence(int batchSize, + size_t numSequences, + const int *starts, MatrixPtr inputValue); /** * Compute lstm backward one sequence by one sequence. */ - void backwardSequence(int batchSize, size_t numSequences, const int *starts, + void backwardSequence(int batchSize, + size_t numSequences, + const int *starts, MatrixPtr inputGrad); /** @@ -121,12 +125,16 @@ protected: * } * @endcode */ - void forwardBatch(int batchSize, size_t numSequences, const int *starts, + void forwardBatch(int batchSize, + size_t numSequences, + const int *starts, MatrixPtr inputValue); /** * Compute lstm backward one batch by one batch. */ - void backwardBatch(int batchSize, size_t numSequences, const int *starts, + void backwardBatch(int batchSize, + size_t numSequences, + const int *starts, MatrixPtr inputGrad); /** @@ -134,13 +142,17 @@ protected: * batch value. It will launch one kernel to parallelly compute forward * propagation in sequence level. */ - void forwardSeqParallel(int batchSize, size_t numSequences, const int *starts, + void forwardSeqParallel(int batchSize, + size_t numSequences, + const int *starts, MatrixPtr inputValue); /** * Backward propagation corresponding to forwardSeqParallel. */ - void backwardSeqParallel(int batchSize, size_t numSequences, - const int *starts, MatrixPtr inputGrad); + void backwardSeqParallel(int batchSize, + size_t numSequences, + const int *starts, + MatrixPtr inputGrad); /** * This function is used for sequence generation and get output after * forwardBatch. diff --git a/paddle/gserver/layers/LstmStepLayer.cpp b/paddle/gserver/layers/LstmStepLayer.cpp index fb0fdbf7e9..e7a8d519f2 100644 --- a/paddle/gserver/layers/LstmStepLayer.cpp +++ b/paddle/gserver/layers/LstmStepLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "LstmCompute.h" #include "paddle/utils/Stat.h" @@ -49,24 +48,36 @@ bool LstmStepLayer::init(const LayerMap& layerMap, if (!Layer::init(layerMap, parameterMap)) return false; CHECK_EQ(2U, inputLayers_.size()); - checkIg_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); - checkFg_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); - checkOg_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); - checkIgGrad_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); - checkFgGrad_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); - checkOgGrad_ = - Matrix::create(nullptr, - /* height= */ 1, getSize(), /* trans= */ false, useGpu_); + checkIg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkFg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkOg_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkIgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkFgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + checkOgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); if (biasParameter_.get() != NULL) { CHECK_EQ(getSize() * 3, biasParameter_->getSize()); @@ -101,12 +112,21 @@ void LstmStepLayer::forward(PassType passType) { CHECK_EQ(getSize(), prevState.value->getWidth()); int batchSize = input.getBatchSize(); reserveOutput(batchSize, getSize()); - resetSpecifyOutput(state_, batchSize, getSize(), /* isValueClean */ false, + resetSpecifyOutput(state_, + batchSize, + getSize(), + /* isValueClean */ false, /* isGradClean */ true); - resetSpecifyOutput(gate_, batchSize, getSize() * 4, - /* isValueClean */ false, /* isGradClean */ false); - resetSpecifyOutput(stateActive_, batchSize, getSize(), - /* isValueClean */ false, /* isGradClean */ false); + resetSpecifyOutput(gate_, + batchSize, + getSize() * 4, + /* isValueClean */ false, + /* isGradClean */ false); + resetSpecifyOutput(stateActive_, + batchSize, + getSize(), + /* isValueClean */ false, + /* isGradClean */ false); gate_.value->assign(*input.value); hl_lstm_value lstmValue; @@ -156,11 +176,9 @@ void LstmStepLayer::backward(const UpdateCallback& callback) { lstmGrad.checkOgGrad = checkOgGrad_->getData(); if (useGpu_) { - LstmCompute::backwardBatch<1>(lstmValue, lstmGrad, getSize(), - batchSize); + LstmCompute::backwardBatch<1>(lstmValue, lstmGrad, getSize(), batchSize); } else { - LstmCompute::backwardBatch<0>(lstmValue, lstmGrad, getSize(), - batchSize); + LstmCompute::backwardBatch<0>(lstmValue, lstmGrad, getSize(), batchSize); } if (input.grad) { diff --git a/paddle/gserver/layers/MDLstmLayer.cpp b/paddle/gserver/layers/MDLstmLayer.cpp index 8ca92dee6d..93f52c1c31 100644 --- a/paddle/gserver/layers/MDLstmLayer.cpp +++ b/paddle/gserver/layers/MDLstmLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LstmLayer.h" #include "paddle/math/Matrix.h" #include "paddle/math/BaseMatrix.h" @@ -106,7 +105,8 @@ public: bool end() { return end_; } - bool getPrePos(const std::vector& delays, int idx, + bool getPrePos(const std::vector& delays, + int idx, std::vector& prePos) { bool isAvial = true; prePos.clear(); @@ -129,7 +129,8 @@ public: return isAvial; } - bool getNextPos(const std::vector& delays, int idx, + bool getNextPos(const std::vector& delays, + int idx, std::vector& nextPos) { bool isAvial = true; nextPos.clear(); @@ -232,24 +233,46 @@ bool MDLstmLayer::init(const LayerMap& layerMap, new Weight(numBlocks_, numBlocks_ * (3 + numDims_), parameters_[0])); if (biasParameter_.get() != NULL) { bias_.reset(new Weight(1, numBlocks_ * (5 + 2 * numDims_), biasParameter_)); - localBias_ = - Matrix::create(nullptr, /* height= */ 1, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); - checkIg_ = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - checkFg_ = Matrix::create(nullptr, /* height= */ numDims_, numBlocks_, - /* trans= */ false, useGpu_); - checkOg_ = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - localBiasGrad_ = - Matrix::create(nullptr, /* height= */ 1, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); - checkIgGrad_ = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - checkFgGrad_ = Matrix::create(nullptr, /* height= */ numDims_, numBlocks_, - /* trans= */ false, useGpu_); - checkOgGrad_ = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); + localBias_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); + checkIg_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + checkFg_ = Matrix::create(nullptr, + /* height= */ numDims_, + numBlocks_, + /* trans= */ false, + useGpu_); + checkOg_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + localBiasGrad_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); + checkIgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + checkFgGrad_ = Matrix::create(nullptr, + /* height= */ numDims_, + numBlocks_, + /* trans= */ false, + useGpu_); + checkOgGrad_ = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); localBias_->setData(bias_->getW()->getData()); checkIg_->setData(bias_->getW()->getData() + numBlocks_ * (3 + numDims_)); @@ -315,49 +338,79 @@ void MDLstmLayer::forward(PassType passType) { frameOutput_.reserve(batchSize); Matrix::resizeOrCreate(gate_.value, - /* height= */ batchSize, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); + /* height= */ batchSize, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); for (int i = frameGate_.size(); i < batchSize; i++) { Argument arg; - arg.value = - Matrix::create(nullptr, /* height= */ 1, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); - arg.grad = - Matrix::create(nullptr, /* height= */ 1, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); frameGate_.push_back(arg); } for (int i = frameInputGate_.size(); i < batchSize; i++) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); frameInputGate_.push_back(arg); } for (int i = frameForgetGate_.size(); i < batchSize; i++) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ numDims_, numBlocks_, - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ numDims_, numBlocks_, - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ numDims_, + numBlocks_, + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ numDims_, + numBlocks_, + /* trans= */ false, + useGpu_); frameForgetGate_.push_back(arg); } for (int i = frameOutputGate_.size(); i < batchSize; i++) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); frameOutputGate_.push_back(arg); } for (int i = frameInputNode_.size(); i < batchSize; i++) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); frameInputNode_.push_back(arg); } for (int i = frameState_.size(); i < batchSize; i++) { @@ -374,10 +427,16 @@ void MDLstmLayer::forward(PassType passType) { } for (int i = frameOutput_.size(); i < batchSize; i++) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ 1, numBlocks_, - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + numBlocks_, + /* trans= */ false, + useGpu_); frameOutput_.push_back(arg); } @@ -432,13 +491,19 @@ void MDLstmLayer::forwardGate2OutputSequence(int start, *frameState_[start + preOffsetV[i]].value, *checkIg_, 1.0, 1.0); MatrixPtr fgGateOneDim = Matrix::create( - frameForgetGate_[idxCurr].value->getData() + i * numBlocks_, 1, - numBlocks_, false, useGpu_); + frameForgetGate_[idxCurr].value->getData() + i * numBlocks_, + 1, + numBlocks_, + false, + useGpu_); MatrixPtr checkFgOneDim = - Matrix::create(checkFg_->getData() + i * numBlocks_, 1.0, numBlocks_, - false, useGpu_); - fgGateOneDim->addDotMul(*frameState_[start + preOffsetV[i]].value, - *checkFgOneDim, 1.0, 1.0); + Matrix::create(checkFg_->getData() + i * numBlocks_, + 1.0, + numBlocks_, + false, + useGpu_); + fgGateOneDim->addDotMul( + *frameState_[start + preOffsetV[i]].value, *checkFgOneDim, 1.0, 1.0); } } activationGate_->forward(frameInputGate_[idxCurr]); @@ -449,18 +514,22 @@ void MDLstmLayer::forwardGate2OutputSequence(int start, for (int i = 0; i < numDims_; i++) { if (preOffsetV[i] >= 0) { MatrixPtr fgGateOneDim = Matrix::create( - frameForgetGate_[idxCurr].value->getData() + i * numBlocks_, 1, - numBlocks_, false, useGpu_); + frameForgetGate_[idxCurr].value->getData() + i * numBlocks_, + 1, + numBlocks_, + false, + useGpu_); frameState_[idxCurr].value->addDotMul( *frameState_[start + preOffsetV[i]].value, *fgGateOneDim, 1.0, 1.0); } } frameState_[idxCurr].value->addDotMul(*frameInputNode_[idxCurr].value, - *frameInputGate_[idxCurr].value, 1.0, + *frameInputGate_[idxCurr].value, + 1.0, 1.0); - frameOutputGate_[idxCurr].value->addDotMul(*frameState_[idxCurr].value, - *checkOg_, 1.0, 1.0); + frameOutputGate_[idxCurr].value->addDotMul( + *frameState_[idxCurr].value, *checkOg_, 1.0, 1.0); activationGate_->forward(frameOutputGate_[idxCurr]); framePreOutput_[idxCurr].value->copyFrom(*(frameState_[idxCurr].value)); @@ -493,8 +562,10 @@ void MDLstmLayer::backward(const UpdateCallback& callback) { size_t numSequences = input.getNumSequences(); Matrix::resizeOrCreate(gate_.grad, - /* height= */ batchSize, numBlocks_ * (3 + numDims_), - /* trans= */ false, useGpu_); + /* height= */ batchSize, + numBlocks_ * (3 + numDims_), + /* trans= */ false, + useGpu_); for (int i = 0; i < batchSize; i++) { if (frameState_[i].grad == NULL) @@ -576,8 +647,8 @@ void MDLstmLayer::backwardGate2OutputSequence(int start, *framePreOutput_[idxCurr].value); activationGate_->backward(frameOutputGate_[idxCurr]); - frameState_[idxCurr].grad->addDotMul(*frameOutputGate_[idxCurr].grad, - *checkOg_, 1.0, 1.0); + frameState_[idxCurr].grad->addDotMul( + *frameOutputGate_[idxCurr].grad, *checkOg_, 1.0, 1.0); for (int i = 0; i < numDims_; i++) { if (nextOffsetV[i] >= 0) { frameState_[idxCurr].grad->addDotMul( @@ -586,18 +657,26 @@ void MDLstmLayer::backwardGate2OutputSequence(int start, MatrixPtr fgGateOneDimGrad = Matrix::create( frameForgetGate_[start + nextOffsetV[i]].grad->getData() + i * numBlocks_, - 1, numBlocks_, false, useGpu_); + 1, + numBlocks_, + false, + useGpu_); MatrixPtr fgGateOneDimVal = Matrix::create( frameForgetGate_[start + nextOffsetV[i]].value->getData() + i * numBlocks_, - 1, numBlocks_, false, useGpu_); + 1, + numBlocks_, + false, + useGpu_); MatrixPtr checkFgOneDim = Matrix::create( checkFg_->getData() + i * numBlocks_, 1, numBlocks_, false, useGpu_); - frameState_[idxCurr].grad->addDotMul(*fgGateOneDimGrad, *checkFgOneDim, - 1.0, 1.0); frameState_[idxCurr].grad->addDotMul( - *frameState_[start + nextOffsetV[i]].grad, *fgGateOneDimVal, 1.0, + *fgGateOneDimGrad, *checkFgOneDim, 1.0, 1.0); + frameState_[idxCurr].grad->addDotMul( + *frameState_[start + nextOffsetV[i]].grad, + *fgGateOneDimVal, + 1.0, 1.0); } } @@ -611,11 +690,15 @@ void MDLstmLayer::backwardGate2OutputSequence(int start, for (int i = 0; i < numDims_; i++) { if (preOffsetV[i] >= 0) { MatrixPtr fgGateOneDimGrad = Matrix::create( - frameForgetGate_[idxCurr].grad->getData() + i * numBlocks_, 1, - numBlocks_, false, useGpu_); + frameForgetGate_[idxCurr].grad->getData() + i * numBlocks_, + 1, + numBlocks_, + false, + useGpu_); fgGateOneDimGrad->addDotMul(*frameState_[idxCurr].grad, *frameState_[start + preOffsetV[i]].value, - 1.0, 1.0); + 1.0, + 1.0); } } @@ -627,22 +710,30 @@ void MDLstmLayer::backwardGate2OutputSequence(int start, for (int i = 0; i < numDims_; i++) { if (preOffsetV[i] >= 0) { checkIgGrad_->addDotMul(*frameInputGate_[idxCurr].grad, - *frameState_[start + preOffsetV[i]].value, 1.0, + *frameState_[start + preOffsetV[i]].value, + 1.0, 1.0); MatrixPtr fgGateOneDimGrad = Matrix::create( - frameForgetGate_[idxCurr].grad->getData() + i * numBlocks_, 1, - numBlocks_, false, useGpu_); + frameForgetGate_[idxCurr].grad->getData() + i * numBlocks_, + 1, + numBlocks_, + false, + useGpu_); MatrixPtr checkFgOneDimGrad = - Matrix::create(checkFgGrad_->getData() + i * numBlocks_, 1, - numBlocks_, false, useGpu_); + Matrix::create(checkFgGrad_->getData() + i * numBlocks_, + 1, + numBlocks_, + false, + useGpu_); checkFgOneDimGrad->addDotMul(*fgGateOneDimGrad, *frameState_[start + preOffsetV[i]].value, - 1.0, 1.0); + 1.0, + 1.0); } } - checkOgGrad_->addDotMul(*frameOutputGate_[idxCurr].grad, - *frameState_[idxCurr].value, 1.0, 1.0); + checkOgGrad_->addDotMul( + *frameOutputGate_[idxCurr].grad, *frameState_[idxCurr].value, 1.0, 1.0); } } @@ -660,7 +751,9 @@ void MDLstmLayer::backwardOneSequence(int start, CoordIterator& coordIter) { if (weight_->getWGrad()) { weight_->getWGrad()->mul( frameOutput_[start + preOffset].value->getTranspose(), - frameGate_[start + offset].grad, 1.0, 1.0); + frameGate_[start + offset].grad, + 1.0, + 1.0); } } } diff --git a/paddle/gserver/layers/MaxIdLayer.cpp b/paddle/gserver/layers/MaxIdLayer.cpp index b80de87b4e..22670fa121 100644 --- a/paddle/gserver/layers/MaxIdLayer.cpp +++ b/paddle/gserver/layers/MaxIdLayer.cpp @@ -45,7 +45,10 @@ public: const Argument& input = getInput(0); size_t batchSize = input.getBatchSize(); IVector::resizeOrCreate(output_.ids, batchSize * beamSize_, useGpu_); - Matrix::resizeOrCreate(output_.in, batchSize, beamSize_, false, + Matrix::resizeOrCreate(output_.in, + batchSize, + beamSize_, + false, /* useGpu */ useGpu_); output_.value = nullptr; input.value->rowMax(*output_.ids, *output_.in); diff --git a/paddle/gserver/layers/MaxLayer.cpp b/paddle/gserver/layers/MaxLayer.cpp index c4ffe894ec..42bc6bb815 100644 --- a/paddle/gserver/layers/MaxLayer.cpp +++ b/paddle/gserver/layers/MaxLayer.cpp @@ -23,8 +23,8 @@ REGISTER_LAYER(max, MaxLayer); void MaxLayer::forward(PassType passType) { SequencePoolLayer::forward(passType); - IVector::resizeOrCreate(maxIndex_, newBatchSize_ * getSize(), - useGpu(deviceId_)); + IVector::resizeOrCreate( + maxIndex_, newBatchSize_ * getSize(), useGpu(deviceId_)); maxIndex_->zeroMem(); MatrixPtr inputValue = getInputValue(0); diff --git a/paddle/gserver/layers/MaxLayer.h b/paddle/gserver/layers/MaxLayer.h index e6dcfe9c67..74df0b8b57 100644 --- a/paddle/gserver/layers/MaxLayer.h +++ b/paddle/gserver/layers/MaxLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 "SequencePoolLayer.h" diff --git a/paddle/gserver/layers/MixedLayer.cpp b/paddle/gserver/layers/MixedLayer.cpp index 26b1360290..1392188fca 100644 --- a/paddle/gserver/layers/MixedLayer.cpp +++ b/paddle/gserver/layers/MixedLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "MixedLayer.h" @@ -29,8 +28,8 @@ bool MixedLayer::init(const LayerMap& layerMap, projections_.resize(inputLayers_.size()); for (size_t i = 0; i < inputLayers_.size(); i++) { if (config_.inputs(i).has_proj_conf()) { - projections_[i].reset(Projection::create(config_.inputs(i).proj_conf(), - parameters_[i], useGpu_)); + projections_[i].reset(Projection::create( + config_.inputs(i).proj_conf(), parameters_[i], useGpu_)); } else { CHECK(!parameters_[i]) << "should no parameters for operators"; } @@ -46,8 +45,7 @@ bool MixedLayer::init(const LayerMap& layerMap, if (biasParameter_.get() != NULL) { sharedBias_ = config_.shared_biases(); size_t psize = config_.bias_size(); - biases_ = std::unique_ptr( - new Weight(1, psize, biasParameter_)); + biases_ = std::unique_ptr(new Weight(1, psize, biasParameter_)); } return true; diff --git a/paddle/gserver/layers/MixedLayer.h b/paddle/gserver/layers/MixedLayer.h index 5842e51e1d..271e0c2538 100644 --- a/paddle/gserver/layers/MixedLayer.h +++ b/paddle/gserver/layers/MixedLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -22,8 +21,8 @@ limitations under the License. */ namespace paddle { /** - * A mixed layer has multiple input layers. - * Each input layer was processed by a Projection or Operator. + * A mixed layer has multiple input layers. + * Each input layer was processed by a Projection or Operator. * The results of all projections or Operators are summed together with bias * (if configured), and then go through an activation function and dropout * (if configured). @@ -43,7 +42,7 @@ public: virtual void backward(const UpdateCallback& callback = nullptr); virtual void resetState(); /** - * setState() should be called after getState(). + * setState() should be called after getState(). * Argument state consists of all projections states. */ virtual void setState(LayerStatePtr state); diff --git a/paddle/gserver/layers/MultinomialSampler.cpp b/paddle/gserver/layers/MultinomialSampler.cpp index 518dc0c60c..e85dca72d3 100644 --- a/paddle/gserver/layers/MultinomialSampler.cpp +++ b/paddle/gserver/layers/MultinomialSampler.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "MultinomialSampler.h" namespace paddle { diff --git a/paddle/gserver/layers/MultinomialSampler.h b/paddle/gserver/layers/MultinomialSampler.h index 442124704a..59683d2ee2 100644 --- a/paddle/gserver/layers/MultinomialSampler.h +++ b/paddle/gserver/layers/MultinomialSampler.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include diff --git a/paddle/gserver/layers/MultiplexLayer.cpp b/paddle/gserver/layers/MultiplexLayer.cpp index a70172d9a6..c681eb0623 100644 --- a/paddle/gserver/layers/MultiplexLayer.cpp +++ b/paddle/gserver/layers/MultiplexLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" diff --git a/paddle/gserver/layers/NCELayer.cpp b/paddle/gserver/layers/NCELayer.cpp index 4faebe5d2a..50b29cdea5 100644 --- a/paddle/gserver/layers/NCELayer.cpp +++ b/paddle/gserver/layers/NCELayer.cpp @@ -23,7 +23,8 @@ namespace paddle { /** * Noise-contrastive estimation. * Implements the method in the following paper: - * A fast and simple algorithm for training neural probabilistic language models. + * A fast and simple algorithm for training neural probabilistic language + * models. * * The config file api is nce_layer. */ @@ -180,8 +181,11 @@ public: int size = getSize(); resetOutput(batchSize, size); - Matrix::resizeOrCreate(sampleOut_.value, 1, samples_.size(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(sampleOut_.value, + 1, + samples_.size(), + /* trans= */ false, + useGpu_); forwardBias(); @@ -195,8 +199,11 @@ public: } void backward(const UpdateCallback& callback) { - Matrix::resizeOrCreate(sampleOut_.grad, 1, samples_.size(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(sampleOut_.grad, + 1, + samples_.size(), + /* trans= */ false, + useGpu_); backwardCost(); @@ -241,7 +248,8 @@ public: real* sampleOut = sampleOut_.value->getData(); for (size_t i = 0; i < samples_.size(); ++i) { - sampleOut[i] += dotProduct(dim, inputMat->getRowBuf(samples_[i].sampleId), + sampleOut[i] += dotProduct(dim, + inputMat->getRowBuf(samples_[i].sampleId), weightMat->getRowBuf(samples_[i].labelId)); } } @@ -257,7 +265,9 @@ public: if (weightGradMat) { for (size_t i = 0; i < samples_.size(); ++i) { - axpy(dim, sampleGrad[i], inputMat->getRowBuf(samples_[i].sampleId), + axpy(dim, + sampleGrad[i], + inputMat->getRowBuf(samples_[i].sampleId), weightGradMat->getRowBuf(samples_[i].labelId)); } weights_[layerId]->incUpdate(callback); @@ -265,7 +275,9 @@ public: if (inputGradMat) { for (size_t i = 0; i < samples_.size(); ++i) { - axpy(dim, sampleGrad[i], weightMat->getRowBuf(samples_[i].labelId), + axpy(dim, + sampleGrad[i], + weightMat->getRowBuf(samples_[i].labelId), inputGradMat->getRowBuf(samples_[i].sampleId)); } } diff --git a/paddle/gserver/layers/NormLayer.cpp b/paddle/gserver/layers/NormLayer.cpp index ad8b92d2ff..7f6ffe2298 100644 --- a/paddle/gserver/layers/NormLayer.cpp +++ b/paddle/gserver/layers/NormLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "NormLayer.h" #include "NormProjectionLayer.h" diff --git a/paddle/gserver/layers/NormLayer.h b/paddle/gserver/layers/NormLayer.h index 2b05be6fcb..9e848e5268 100644 --- a/paddle/gserver/layers/NormLayer.h +++ b/paddle/gserver/layers/NormLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -44,8 +43,8 @@ public: /** * @brief response normalization within feature maps - * namely normalize in independent channel - * When code refactoring, we delete the original implementation. + * namely normalize in independent channel + * When code refactoring, we delete the original implementation. * Need to implement in the futrue. */ class ResponseNormLayer : public NormLayer { diff --git a/paddle/gserver/layers/NormProjectionLayer.cpp b/paddle/gserver/layers/NormProjectionLayer.cpp index eab6e904ee..6ac468e6fc 100644 --- a/paddle/gserver/layers/NormProjectionLayer.cpp +++ b/paddle/gserver/layers/NormProjectionLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" #include "NormProjectionLayer.h" @@ -65,8 +64,8 @@ void CMRProjectionNormLayer::forward(PassType passType) { denoms_->zeroMem(); - outV->crossMapNormalFwd(*input, imgSizeH_, imgSizeW_, *denoms_, channels_, - size_, scale_, pow_); + outV->crossMapNormalFwd( + *input, imgSizeH_, imgSizeW_, *denoms_, channels_, size_, scale_, pow_); } void CMRProjectionNormLayer::backward(const UpdateCallback& callback) { @@ -81,8 +80,15 @@ void CMRProjectionNormLayer::backward(const UpdateCallback& callback) { MatrixPtr localOutV = getOutputValue(); MatrixPtr preOutV = inputLayers_[0]->getOutputValue(); - preOutGrad->crossMapNormalBwd(*localGrad, *denoms_, *preOutV, *localOutV, - channels_, imgSizeH_, imgSizeW_, size_, scale_, + preOutGrad->crossMapNormalBwd(*localGrad, + *denoms_, + *preOutV, + *localOutV, + channels_, + imgSizeH_, + imgSizeW_, + size_, + scale_, pow_); } } // namespace paddle diff --git a/paddle/gserver/layers/NormProjectionLayer.h b/paddle/gserver/layers/NormProjectionLayer.h index 728806ea76..b42e98ab09 100644 --- a/paddle/gserver/layers/NormProjectionLayer.h +++ b/paddle/gserver/layers/NormProjectionLayer.h @@ -12,7 +12,6 @@ WITHOUT 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 "NormLayer.h" diff --git a/paddle/gserver/layers/Operator.cpp b/paddle/gserver/layers/Operator.cpp index 5fa8239ac5..b89c474014 100644 --- a/paddle/gserver/layers/Operator.cpp +++ b/paddle/gserver/layers/Operator.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Operator.h" namespace paddle { diff --git a/paddle/gserver/layers/Operator.h b/paddle/gserver/layers/Operator.h index 9ee16f70ee..ff6558dc73 100644 --- a/paddle/gserver/layers/Operator.h +++ b/paddle/gserver/layers/Operator.h @@ -12,7 +12,6 @@ WITHOUT 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/parameter/Parameter.h" @@ -48,12 +47,14 @@ public: static ClassRegistrar registrar_; /** - * Forward propagation. If backward() will be called, in and out must be kept valid until then. + * Forward propagation. If backward() will be called, in and out must be kept + * valid until then. * @param ins inputs of operator * @param out output of operator * @param passType PASS_TRAIN of PASS_TEST */ - void forward(std::vector ins, Argument* out, + void forward(std::vector ins, + Argument* out, PassType passType) { ins_ = ins; out_ = out; diff --git a/paddle/gserver/layers/OuterProdLayer.cpp b/paddle/gserver/layers/OuterProdLayer.cpp index 708c901ba9..9b24a4f440 100644 --- a/paddle/gserver/layers/OuterProdLayer.cpp +++ b/paddle/gserver/layers/OuterProdLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -58,12 +57,15 @@ bool OuterProdLayer::init(const LayerMap& layerMap, CHECK_EQ(dim0 * dim1, getSize()) << "Dimension mismatch"; - tmpRow0 = Matrix::create(nullptr, /* height= */ 1, dim0, /* trans= */ false, - useGpu_); - tmpRow1 = Matrix::create(nullptr, /* height= */ 1, dim1, /* trans= */ false, + tmpRow0 = Matrix::create( + nullptr, /* height= */ 1, dim0, /* trans= */ false, useGpu_); + tmpRow1 = Matrix::create( + nullptr, /* height= */ 1, dim1, /* trans= */ false, useGpu_); + tmpMtx0 = Matrix::create(nullptr, + /* height= */ dim0, + dim1, + /* trans= */ false, useGpu_); - tmpMtx0 = Matrix::create(nullptr, /* height= */ dim0, dim1, - /* trans= */ false, useGpu_); return true; } diff --git a/paddle/gserver/layers/ParameterReluLayer.cpp b/paddle/gserver/layers/ParameterReluLayer.cpp index 98d108db5f..cd3bffa2e1 100644 --- a/paddle/gserver/layers/ParameterReluLayer.cpp +++ b/paddle/gserver/layers/ParameterReluLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterReluLayer.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" @@ -59,8 +58,8 @@ void ParameterReluLayer::backward(const UpdateCallback& callback) { } MatrixPtr preGrad = getInputGrad(0); - preGrad->paramReluBackwardDiff(*getOutputGrad(), *(getInputValue(0)), - *(weight_->getW())); + preGrad->paramReluBackwardDiff( + *getOutputGrad(), *(getInputValue(0)), *(weight_->getW())); { REGISTER_TIMER_INFO("WeightUpdate", getName().c_str()); weight_->getParameterPtr()->incUpdate(callback); diff --git a/paddle/gserver/layers/ParameterReluLayer.h b/paddle/gserver/layers/ParameterReluLayer.h index 367e4e787c..029c09381f 100644 --- a/paddle/gserver/layers/ParameterReluLayer.h +++ b/paddle/gserver/layers/ParameterReluLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/PoolLayer.cpp b/paddle/gserver/layers/PoolLayer.cpp index 2fbc9001f1..511dfd87c1 100644 --- a/paddle/gserver/layers/PoolLayer.cpp +++ b/paddle/gserver/layers/PoolLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "PoolLayer.h" #include "PoolProjectionLayer.h" diff --git a/paddle/gserver/layers/PoolLayer.h b/paddle/gserver/layers/PoolLayer.h index e87ad08251..59be295a53 100644 --- a/paddle/gserver/layers/PoolLayer.h +++ b/paddle/gserver/layers/PoolLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/PoolProjection.cpp b/paddle/gserver/layers/PoolProjection.cpp index 9be5aba3d5..1b227c8084 100644 --- a/paddle/gserver/layers/PoolProjection.cpp +++ b/paddle/gserver/layers/PoolProjection.cpp @@ -19,7 +19,8 @@ namespace paddle { REGISTER_PROJECTION_CREATE_FUNC(pool, &PoolProjection::create); PoolProjection::PoolProjection(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu) + ParameterPtr parameter, + bool useGpu) : Projection(config, parameter, useGpu) { const PoolConfig& conf = config_.pool_conf(); poolType_ = conf.pool_type(); @@ -47,9 +48,15 @@ size_t PoolProjection::getSize() { if (imgSize_ == 0) { imgSize_ = conf.img_size(); } - outputY_ = outputSize(imgSizeY_, sizeY_, confPaddingY_, strideY_, + outputY_ = outputSize(imgSizeY_, + sizeY_, + confPaddingY_, + strideY_, /* caffeMode */ false); - outputX_ = outputSize(imgSize_, sizeX_, confPadding_, stride_, + outputX_ = outputSize(imgSize_, + sizeX_, + confPadding_, + stride_, /* caffeMode */ false); const_cast(out_)->setFrameHeight(outputY_); @@ -59,7 +66,8 @@ size_t PoolProjection::getSize() { } PoolProjection* PoolProjection::create(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu) { + ParameterPtr parameter, + bool useGpu) { const std::string& pool = config.pool_conf().pool_type(); if (pool == "max-projection") { return new MaxPoolProjection(config, parameter, useGpu); @@ -76,8 +84,17 @@ void MaxPoolProjection::forward() { CHECK_EQ(width, out_->value->getWidth()); MatrixPtr inputV = in_->value; MatrixPtr outV = out_->value; - outV->maxPoolForward(*inputV, imgSizeY_, imgSize_, channels_, sizeX_, sizeY_, - strideY_, stride_, outputY_, outputX_, confPaddingY_, + outV->maxPoolForward(*inputV, + imgSizeY_, + imgSize_, + channels_, + sizeX_, + sizeY_, + strideY_, + stride_, + outputY_, + outputX_, + confPaddingY_, confPadding_); } @@ -91,9 +108,21 @@ void MaxPoolProjection::backward(const UpdateCallback& callback) { if (NULL == inputGrad) { return; } - inputGrad->maxPoolBackward(*inputV, imgSizeY_, imgSize_, *outGrad, *outV, - sizeX_, sizeY_, strideY_, stride_, outputY_, - outputX_, 1, 1, confPaddingY_, confPadding_); + inputGrad->maxPoolBackward(*inputV, + imgSizeY_, + imgSize_, + *outGrad, + *outV, + sizeX_, + sizeY_, + strideY_, + stride_, + outputY_, + outputX_, + 1, + 1, + confPaddingY_, + confPadding_); } void AvgPoolProjection::forward() { @@ -101,8 +130,17 @@ void AvgPoolProjection::forward() { CHECK_EQ(width, out_->value->getWidth()); MatrixPtr inputV = in_->value; MatrixPtr outV = out_->value; - outV->avgPoolForward(*inputV, imgSizeY_, imgSize_, channels_, sizeX_, sizeY_, - strideY_, stride_, outputY_, outputX_, confPaddingY_, + outV->avgPoolForward(*inputV, + imgSizeY_, + imgSize_, + channels_, + sizeX_, + sizeY_, + strideY_, + stride_, + outputY_, + outputX_, + confPaddingY_, confPadding_); } @@ -116,8 +154,18 @@ void AvgPoolProjection::backward(const UpdateCallback& callback) { return; } - inputGrad->avgPoolBackward(*outputGrad, imgSizeY_, imgSize_, sizeX_, sizeY_, - strideY_, stride_, outputY_, outputX_, 1, 1, - confPaddingY_, confPadding_); + inputGrad->avgPoolBackward(*outputGrad, + imgSizeY_, + imgSize_, + sizeX_, + sizeY_, + strideY_, + stride_, + outputY_, + outputX_, + 1, + 1, + confPaddingY_, + confPadding_); } } // namespace paddle diff --git a/paddle/gserver/layers/PoolProjection.h b/paddle/gserver/layers/PoolProjection.h index a11e25b729..9c3191bd80 100644 --- a/paddle/gserver/layers/PoolProjection.h +++ b/paddle/gserver/layers/PoolProjection.h @@ -30,11 +30,13 @@ protected: std::string poolType_; public: - PoolProjection(const ProjectionConfig& config, ParameterPtr parameter, + PoolProjection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu); static PoolProjection* create(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu); + ParameterPtr parameter, + bool useGpu); const std::string& getPoolType() const { return poolType_; } @@ -43,7 +45,8 @@ public: class MaxPoolProjection : public PoolProjection { public: - MaxPoolProjection(const ProjectionConfig& config, ParameterPtr parameter, + MaxPoolProjection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu) : PoolProjection(config, parameter, useGpu) {} @@ -53,7 +56,8 @@ public: class AvgPoolProjection : public PoolProjection { public: - AvgPoolProjection(const ProjectionConfig& config, ParameterPtr parameter, + AvgPoolProjection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu) : PoolProjection(config, parameter, useGpu) {} diff --git a/paddle/gserver/layers/PoolProjectionLayer.cpp b/paddle/gserver/layers/PoolProjectionLayer.cpp index cabb346d6c..aabc60af19 100644 --- a/paddle/gserver/layers/PoolProjectionLayer.cpp +++ b/paddle/gserver/layers/PoolProjectionLayer.cpp @@ -18,7 +18,6 @@ limitations under the License. */ namespace paddle { - size_t PoolProjectionLayer::getSize() { CHECK_EQ(inputLayers_.size(), 1UL); size_t layerSize = 0; @@ -31,9 +30,15 @@ size_t PoolProjectionLayer::getSize() { imgSizeW_ = imgSize_; } - outputH_ = outputSize(imgSizeH_, sizeY_, confPaddingY_, strideY_, + outputH_ = outputSize(imgSizeH_, + sizeY_, + confPaddingY_, + strideY_, /* caffeMode */ false); - outputW_ = outputSize(imgSizeW_, sizeX_, confPadding_, stride_, + outputW_ = outputSize(imgSizeW_, + sizeX_, + confPadding_, + stride_, /* caffeMode */ false); layerSize = outputH_ * outputW_ * channels_; diff --git a/paddle/gserver/layers/PowerLayer.cpp b/paddle/gserver/layers/PowerLayer.cpp index 44c5e6063b..0b9672f220 100644 --- a/paddle/gserver/layers/PowerLayer.cpp +++ b/paddle/gserver/layers/PowerLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -26,7 +25,7 @@ namespace paddle { * \f[ * y = x^w * \f] - * where \f$x\f$ is a input vector, \f$w\f$ is scalar weight, + * where \f$x\f$ is a input vector, \f$w\f$ is scalar weight, * and output \f$y\f$ is a vector. * * The config file api is power_layer. diff --git a/paddle/gserver/layers/PrintLayer.cpp b/paddle/gserver/layers/PrintLayer.cpp index 68fee69f44..95be7b34cb 100644 --- a/paddle/gserver/layers/PrintLayer.cpp +++ b/paddle/gserver/layers/PrintLayer.cpp @@ -18,8 +18,7 @@ namespace paddle { class PrintLayer : public Layer { public: - explicit PrintLayer(const LayerConfig& config) - : Layer(config) {} + explicit PrintLayer(const LayerConfig& config) : Layer(config) {} void forward(PassType passType); void backward(const UpdateCallback& callback) {} }; diff --git a/paddle/gserver/layers/Projection.cpp b/paddle/gserver/layers/Projection.cpp index aebc08f4a0..c7eb4b6442 100644 --- a/paddle/gserver/layers/Projection.cpp +++ b/paddle/gserver/layers/Projection.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Projection.h" #include "ContextProjection.h" @@ -25,7 +24,8 @@ ClassRegistrar Projection::registrar_; Projection* Projection::create(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu) { + ParameterPtr parameter, + bool useGpu) { return registrar_.createByType(config.type(), config, parameter, useGpu); } diff --git a/paddle/gserver/layers/Projection.h b/paddle/gserver/layers/Projection.h index 203edc5396..798503113d 100644 --- a/paddle/gserver/layers/Projection.h +++ b/paddle/gserver/layers/Projection.h @@ -39,9 +39,11 @@ namespace paddle { class Projection { public: static Projection* create(const ProjectionConfig& config, - ParameterPtr parameter, bool useGpu); + ParameterPtr parameter, + bool useGpu); - Projection(const ProjectionConfig& config, ParameterPtr parameter, + Projection(const ProjectionConfig& config, + ParameterPtr parameter, bool useGpu) : config_(config), parameter_(parameter), useGpu_(useGpu) {} diff --git a/paddle/gserver/layers/RecurrentLayer.cpp b/paddle/gserver/layers/RecurrentLayer.cpp index 30ef679f92..08453e21b8 100644 --- a/paddle/gserver/layers/RecurrentLayer.cpp +++ b/paddle/gserver/layers/RecurrentLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "paddle/utils/Stat.h" #include "SequenceToBatch.h" @@ -143,8 +142,8 @@ bool RecurrentLayer::init(const LayerMap& layerMap, void RecurrentLayer::resetState() { CHECK(!reversed_) << "state is not allowed for reversed recurrent layer"; - Matrix::resizeOrCreate(prevOutput_, 1, getSize(), /* trans= */ false, - useGpu_); + Matrix::resizeOrCreate( + prevOutput_, 1, getSize(), /* trans= */ false, useGpu_); prevOutput_->zeroMem(); } @@ -183,16 +182,23 @@ void RecurrentLayer::forward(PassType passType) { } } -void RecurrentLayer::forwardSequence(int batchSize, size_t numSequences, +void RecurrentLayer::forwardSequence(int batchSize, + size_t numSequences, const int* starts) { REGISTER_TIMER_INFO("RecurrentFwSequence", getName().c_str()); frameOutput_.reserve(batchSize); for (int i = frameOutput_.size(); i < batchSize; ++i) { Argument arg; - arg.value = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); - arg.grad = Matrix::create(nullptr, /* height= */ 1, getSize(), - /* trans= */ false, useGpu_); + arg.value = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); + arg.grad = Matrix::create(nullptr, + /* height= */ 1, + getSize(), + /* trans= */ false, + useGpu_); frameOutput_.push_back(arg); } @@ -213,8 +219,8 @@ void RecurrentLayer::forwardOneSequence(int start, int length) { } activation_->forward(frameOutput_[start]); for (int i = 1; i < length; ++i) { - frameOutput_[start + i].value->mul(frameOutput_[start + i - 1].value, - weight_->getW(), 1, 1); + frameOutput_[start + i].value->mul( + frameOutput_[start + i - 1].value, weight_->getW(), 1, 1); activation_->forward(frameOutput_[start + i]); } if (prevOutput_) { @@ -223,8 +229,8 @@ void RecurrentLayer::forwardOneSequence(int start, int length) { } else { activation_->forward(frameOutput_[start + length - 1]); for (int i = length - 2; i >= 0; --i) { - frameOutput_[start + i].value->mul(frameOutput_[start + i + 1].value, - weight_->getW(), 1, 1); + frameOutput_[start + i].value->mul( + frameOutput_[start + i + 1].value, weight_->getW(), 1, 1); activation_->forward(frameOutput_[start + i]); } } @@ -256,7 +262,8 @@ void RecurrentLayer::backward(const UpdateCallback& callback) { weight_->getParameterPtr()->incUpdate(callback); } -void RecurrentLayer::backwardSequence(int batchSize, size_t numSequences, +void RecurrentLayer::backwardSequence(int batchSize, + size_t numSequences, const int* starts) { REGISTER_TIMER_INFO("RecurrentBwSequence", getName().c_str()); for (int i = 0; i < batchSize; ++i) { @@ -274,31 +281,36 @@ void RecurrentLayer::backwardOneSequence(int start, int length) { if (!reversed_) { for (int i = length - 1; i > 0; --i) { activation_->backward(frameOutput_[start + i]); - frameOutput_[start + i - 1].grad->mul(frameOutput_[start + i].grad, - weightT, 1, 1); + frameOutput_[start + i - 1].grad->mul( + frameOutput_[start + i].grad, weightT, 1, 1); } activation_->backward(frameOutput_[start]); if (weight_->getWGrad()) { weight_->getWGrad()->mul( output_.value->subMatrix(start, length - 1)->getTranspose(), - output_.grad->subMatrix(start + 1, length - 1), 1, 1); + output_.grad->subMatrix(start + 1, length - 1), + 1, + 1); } } else { for (int i = 0; i < length - 1; ++i) { activation_->backward(frameOutput_[start + i]); - frameOutput_[start + i + 1].grad->mul(frameOutput_[start + i].grad, - weightT, 1, 1); + frameOutput_[start + i + 1].grad->mul( + frameOutput_[start + i].grad, weightT, 1, 1); } activation_->backward(frameOutput_[start + length - 1]); if (weight_->getWGrad()) { weight_->getWGrad()->mul( output_.value->subMatrix(start + 1, length - 1)->getTranspose(), - output_.grad->subMatrix(start, length - 1), 1, 1); + output_.grad->subMatrix(start, length - 1), + 1, + 1); } } } -void RecurrentLayer::forwardBatch(int batchSize, size_t numSequences, +void RecurrentLayer::forwardBatch(int batchSize, + size_t numSequences, const int* starts) { if (!batchValue_) { batchValue_.reset(new SequenceToBatch(useGpu_)); @@ -327,7 +339,8 @@ void RecurrentLayer::forwardBatch(int batchSize, size_t numSequences, batchValue_->copyBackSeq(*output_.value); } -void RecurrentLayer::backwardBatch(int batchSize, size_t numSequences, +void RecurrentLayer::backwardBatch(int batchSize, + size_t numSequences, const int* starts) { if (!batchGrad_) { batchGrad_.reset(new SequenceToBatch(useGpu_)); @@ -377,11 +390,15 @@ void RecurrentLayer::backwardBatch(int batchSize, size_t numSequences, if (!reversed_) { weight_->getWGrad()->mul( output_.value->subMatrix(starts[seq], len - 1)->getTranspose(), - output_.grad->subMatrix(starts[seq] + 1, len - 1), 1, 1); + output_.grad->subMatrix(starts[seq] + 1, len - 1), + 1, + 1); } else { weight_->getWGrad()->mul( output_.value->subMatrix(starts[seq] + 1, len - 1)->getTranspose(), - output_.grad->subMatrix(starts[seq], len - 1), 1, 1); + output_.grad->subMatrix(starts[seq], len - 1), + 1, + 1); } } } diff --git a/paddle/gserver/layers/RecurrentLayerGroup.cpp b/paddle/gserver/layers/RecurrentLayerGroup.cpp index 62dbaa2674..a5443975da 100644 --- a/paddle/gserver/layers/RecurrentLayerGroup.cpp +++ b/paddle/gserver/layers/RecurrentLayerGroup.cpp @@ -12,7 +12,6 @@ WITHOUT 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/gserver/layers/Layer.h" #include @@ -31,7 +30,8 @@ class RecurrentLayerGroup : public Layer { public: explicit RecurrentLayerGroup(const LayerConfig& config) : Layer(config) {} - void initSubNetwork(NeuralNetwork* rootNetwork, const ModelConfig& config, + void initSubNetwork(NeuralNetwork* rootNetwork, + const ModelConfig& config, const std::vector& parameterTypes, bool useGpu); @@ -53,7 +53,7 @@ public: /** * @see Layer.accessSubNetwork */ - void accessSubNetwork(const std::function &callback) { + void accessSubNetwork(const std::function& callback) { callback(*network_); } @@ -64,8 +64,10 @@ private: REGISTER_LAYER(recurrent_layer_group, RecurrentLayerGroup); void RecurrentLayerGroup::initSubNetwork( - NeuralNetwork* rootNetwork, const ModelConfig& config, - const std::vector& parameterTypes, bool useGpu) { + NeuralNetwork* rootNetwork, + const ModelConfig& config, + const std::vector& parameterTypes, + bool useGpu) { setNeedGradient(true); network_.reset(new RecurrentGradientMachine(config_.name(), rootNetwork)); diff --git a/paddle/gserver/layers/ResizeLayer.cpp b/paddle/gserver/layers/ResizeLayer.cpp index dc573e838f..3c478a33e3 100644 --- a/paddle/gserver/layers/ResizeLayer.cpp +++ b/paddle/gserver/layers/ResizeLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Layer.h" #include "paddle/math/Matrix.h" #include "paddle/math/BaseMatrix.h" @@ -68,9 +67,11 @@ void ResizeLayer::backward(const UpdateCallback& callback) { return; } - MatrixPtr tmp = - Matrix::create(input.grad->getData(), height * width / getSize(), - getSize(), false, useGpu_); + MatrixPtr tmp = Matrix::create(input.grad->getData(), + height * width / getSize(), + getSize(), + false, + useGpu_); tmp->add(*output_.grad); } diff --git a/paddle/gserver/layers/ScalingLayer.cpp b/paddle/gserver/layers/ScalingLayer.cpp index a494b401ff..71570810f9 100644 --- a/paddle/gserver/layers/ScalingLayer.cpp +++ b/paddle/gserver/layers/ScalingLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -26,7 +25,7 @@ namespace paddle { * \f[ * y.row[i] = w[i] * x.row[i] * \f] - * where \f$x\f$ is (batchSize x dataDim) input, \f$w\f$ is + * where \f$x\f$ is (batchSize x dataDim) input, \f$w\f$ is * (batchSize x 1) weight vector, and \f$y\f$ is (batchSize x dataDim) output. * * The config file api is scaling_layer. diff --git a/paddle/gserver/layers/ScalingProjection.cpp b/paddle/gserver/layers/ScalingProjection.cpp index c0a7072c6a..7999d02d38 100644 --- a/paddle/gserver/layers/ScalingProjection.cpp +++ b/paddle/gserver/layers/ScalingProjection.cpp @@ -19,7 +19,8 @@ namespace paddle { class ScalingProjection : public Projection { public: ScalingProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu) + const ParameterPtr& parameter, + bool useGpu) : Projection(config, parameter, useGpu) { CHECK_EQ(parameter->getSize(), 1UL); weight_.reset(new Weight(1, 1, parameter)); @@ -33,10 +34,13 @@ public: void backward(const UpdateCallback& callback) { if (weight_->getWGrad()) { auto sum = Matrix::create(in_->value->getHeight(), 1, false, useGpu_); - sum->sumOfProducts(*in_->value, *out_->grad, - /* scaleSum= */1, /* scaleDest= */0); + sum->sumOfProducts(*in_->value, + *out_->grad, + /* scaleSum= */ 1, + /* scaleDest= */ 0); weight_->getWGrad()->sumCols(*sum, - /* scaleSum= */1, /* scaleDest= */1); + /* scaleSum= */ 1, + /* scaleDest= */ 1); parameter_->incUpdate(callback); } if (in_->grad) { diff --git a/paddle/gserver/layers/SelectiveFullyConnectedLayer.cpp b/paddle/gserver/layers/SelectiveFullyConnectedLayer.cpp index 25ae9d5195..4dfa2c179d 100644 --- a/paddle/gserver/layers/SelectiveFullyConnectedLayer.cpp +++ b/paddle/gserver/layers/SelectiveFullyConnectedLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "SelectiveFullyConnectedLayer.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" @@ -49,11 +48,11 @@ bool SelectiveFullyConnectedLayer::init(const LayerMap& layerMap, void SelectiveFullyConnectedLayer::prefetch() {} -void SelectiveFullyConnectedLayer::reserveOutput(size_t height, size_t width, +void SelectiveFullyConnectedLayer::reserveOutput(size_t height, + size_t width, size_t nnz) { bool flag = (passType_ == PASS_TEST && - config_.selective_fc_pass_generation() && - !fullOutput_); + config_.selective_fc_pass_generation() && !fullOutput_); SetDevice device(output_.deviceId); if (flag) { // output_.value is sparse matrix @@ -61,8 +60,12 @@ void SelectiveFullyConnectedLayer::reserveOutput(size_t height, size_t width, dynamic_cast(output_.value.get())) { output_.value = nullptr; } - Matrix::resizeOrCreateSparseMatrix(output_.value, height, width, nnz, - FLOAT_VALUE, SPARSE_CSR, + Matrix::resizeOrCreateSparseMatrix(output_.value, + height, + width, + nnz, + FLOAT_VALUE, + SPARSE_CSR, /*trans=*/false, /*useGpu=*/useGpu_); output_.value->copyFrom(*selCols_); @@ -74,19 +77,31 @@ void SelectiveFullyConnectedLayer::reserveOutput(size_t height, size_t width, dynamic_cast(output_.value.get())) { output_.value = nullptr; } - Matrix::resizeOrCreate(output_.value, height, width, - /*trans=*/false, /*useGpu=*/useGpu_); + Matrix::resizeOrCreate(output_.value, + height, + width, + /*trans=*/false, + /*useGpu=*/useGpu_); interOutput_ = output_.value; } else { // output_.value is dense matrix, but width = nnz /height CHECK_EQ(nnz % height, 0U); CHECK(nnz / height); - Matrix::resizeOrCreate(output_.value, height, nnz / height, - /*trans=*/false, /*useGpu=*/useGpu_); - interOutput_ = Matrix::createSparseMatrix( - output_.value->getData(), selCols_->getRows(), selCols_->getCols(), - height, width, nnz, FLOAT_VALUE, SPARSE_CSR, - /*trans=*/false, /*useGpu=*/useGpu_); + Matrix::resizeOrCreate(output_.value, + height, + nnz / height, + /*trans=*/false, + /*useGpu=*/useGpu_); + interOutput_ = Matrix::createSparseMatrix(output_.value->getData(), + selCols_->getRows(), + selCols_->getCols(), + height, + width, + nnz, + FLOAT_VALUE, + SPARSE_CSR, + /*trans=*/false, + /*useGpu=*/useGpu_); } } interOutput_->zeroMem(); @@ -97,8 +112,11 @@ void SelectiveFullyConnectedLayer::reserveOutput(size_t height, size_t width, CHECK(nnz / height) << "during training, " "each sample must have at least one column selected."; - Matrix::resizeOrCreate(output_.grad, height, nnz / height, - /*trans=*/false, /*useGpu=*/useGpu_); + Matrix::resizeOrCreate(output_.grad, + height, + nnz / height, + /*trans=*/false, + /*useGpu=*/useGpu_); output_.grad->zeroMem(); } } @@ -131,7 +149,7 @@ void SelectiveFullyConnectedLayer::forward(PassType passType) { real scaleT = i == 0 ? real(0) : real(1); flag = nnz < (hsize * wsize) * config_.selective_fc_full_mul_ratio() && - !fullOutput_; + !fullOutput_; if (flag) { // if the indecies are highly sparse, // manully compute the multiplication of @@ -145,8 +163,11 @@ void SelectiveFullyConnectedLayer::forward(PassType passType) { if (fullOutput_) { interOutput_->mul(input, weight->getTranspose(), 1, scaleT); } else { - Matrix::resizeOrCreate(mmat_, hsize, wsize, - /*trans=*/false, /*useGpu=*/useGpu_); + Matrix::resizeOrCreate(mmat_, + hsize, + wsize, + /*trans=*/false, + /*useGpu=*/useGpu_); mmat_->mul(input, weight->getTranspose()); interOutput_->add3(mmat_); } @@ -158,7 +179,7 @@ void SelectiveFullyConnectedLayer::forward(PassType passType) { } flag = (passType_ == PASS_TEST && config_.selective_fc_pass_generation() && - !fullOutput_); + !fullOutput_); if (flag) { // during generation, output of this layer is a sparse csr matrix, // which is probably the input of maxid layer @@ -166,8 +187,11 @@ void SelectiveFullyConnectedLayer::forward(PassType passType) { // activiation of this layer should be exponential, not softmax. Argument arg; - arg.value = Matrix::create(interOutput_->getData(), 1, nnz, - /*trans=*/false, /*useGpu=*/useGpu_); + arg.value = Matrix::create(interOutput_->getData(), + 1, + nnz, + /*trans=*/false, + /*useGpu=*/useGpu_); activation_->forward(arg); } else /* train and test in train, not generating */ { // during training, this layer output value is *Matrix*, which is input of @@ -187,17 +211,22 @@ void SelectiveFullyConnectedLayer::backward(const UpdateCallback& callback) { backwardActivation(); MatrixPtr oGrad = getOutputGrad(); if (!fullOutput_) { - interOutGrad_ = Matrix::createSparseMatrix( - oGrad->getData(), interOutput_->getRows(), interOutput_->getCols(), - interOutput_->getHeight(), interOutput_->getWidth(), - interOutput_->getElementCnt(), FLOAT_VALUE, SPARSE_CSR, - /*trans=*/false, - /*useGpu=*/useGpu_); + interOutGrad_ = Matrix::createSparseMatrix(oGrad->getData(), + interOutput_->getRows(), + interOutput_->getCols(), + interOutput_->getHeight(), + interOutput_->getWidth(), + interOutput_->getElementCnt(), + FLOAT_VALUE, + SPARSE_CSR, + /*trans=*/false, + /*useGpu=*/useGpu_); } else { - interOutGrad_ = - Matrix::create(oGrad->getData(), oGrad->getHeight(), oGrad->getWidth(), - /*trans=*/false, - /*useGpu=*/useGpu_); + interOutGrad_ = Matrix::create(oGrad->getData(), + oGrad->getHeight(), + oGrad->getWidth(), + /*trans=*/false, + /*useGpu=*/useGpu_); } if (biases_ && biases_->getWGrad()) { @@ -240,13 +269,21 @@ void paddle::SelectiveFullyConnectedLayer::fillSelectiveData( size_t sampleNum = candidates->size(); size_t outputWidth = getSize(); size_t nnz = - std::accumulate(candidates->begin(), candidates->end(), 0UL, + std::accumulate(candidates->begin(), + candidates->end(), + 0UL, [](size_t a, const std::pair& arr) { return a + arr.second; }); Matrix::resizeOrCreateSparseMatrix(this->cpuSelCols_, - sampleNum, outputWidth, nnz, NO_VALUE, SPARSE_CSR, false, false); + sampleNum, + outputWidth, + nnz, + NO_VALUE, + SPARSE_CSR, + false, + false); CHECK(this->cpuSelCols_ != nullptr); CpuSparseMatrixPtr selCols = std::dynamic_pointer_cast(cpuSelCols_); @@ -272,7 +309,13 @@ void paddle::SelectiveFullyConnectedLayer::fillSelectiveData( this->selCols_ = this->cpuSelCols_; } else { Matrix::resizeOrCreateSparseMatrix(this->selCols_, - sampleNum, outputWidth, nnz, NO_VALUE, SPARSE_CSR, false, true); + sampleNum, + outputWidth, + nnz, + NO_VALUE, + SPARSE_CSR, + false, + true); this->selCols_->copyFrom(*cpuSelCols_, HPPL_STREAM_1); hl_stream_synchronize(HPPL_STREAM_1); } diff --git a/paddle/gserver/layers/SelectiveFullyConnectedLayer.h b/paddle/gserver/layers/SelectiveFullyConnectedLayer.h index c152151cff..9f92ae0605 100644 --- a/paddle/gserver/layers/SelectiveFullyConnectedLayer.h +++ b/paddle/gserver/layers/SelectiveFullyConnectedLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" @@ -98,8 +97,6 @@ private: /** * @brief Make SelectiveFC act as FullyConnectedLayer */ - void fillFullySelectiveData() { - fullOutput_ = true; - } + void fillFullySelectiveData() { fullOutput_ = true; } }; } // namespace paddle diff --git a/paddle/gserver/layers/SequenceConcatLayer.cpp b/paddle/gserver/layers/SequenceConcatLayer.cpp index dfce4dcb19..bd72ba3d16 100644 --- a/paddle/gserver/layers/SequenceConcatLayer.cpp +++ b/paddle/gserver/layers/SequenceConcatLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -68,13 +67,11 @@ void SequenceConcatLayer::forward(PassType passType) { const Argument& input1 = getInput(0); size_t numSequences1 = input1.getNumSequences(); - auto startPositions1 = - input1.sequenceStartPositions->getVector(false); + auto startPositions1 = input1.sequenceStartPositions->getVector(false); const Argument& input2 = getInput(1); size_t numSequences2 = input2.getNumSequences(); - auto startPositions2 = - input2.sequenceStartPositions->getVector(false); + auto startPositions2 = input2.sequenceStartPositions->getVector(false); CHECK_EQ(dim, input1.value->getWidth()); CHECK_EQ(startPositions1->getData()[numSequences1], input1.getBatchSize()); @@ -117,8 +114,8 @@ void SequenceConcatLayer::forward(PassType passType) { } // modify the sequenceStartPositions - ICpuGpuVector::resizeOrCreate(output_.sequenceStartPositions, - numSequences1 + 1, false); + ICpuGpuVector::resizeOrCreate( + output_.sequenceStartPositions, numSequences1 + 1, false); int* tgtBuf = output_.sequenceStartPositions->getMutableData(false); @@ -150,10 +147,8 @@ void SequenceConcatLayer::backward(const UpdateCallback& callback) { MatrixPtr inputGrad1 = getInputGrad(0); MatrixPtr inputGrad2 = getInputGrad(1); MatrixPtr outputGrad = getOutputGrad(); - auto startPositions1 = - getInput(0).sequenceStartPositions->getVector(false); - auto startPositions2 = - getInput(1).sequenceStartPositions->getVector(false); + auto startPositions1 = getInput(0).sequenceStartPositions->getVector(false); + auto startPositions2 = getInput(1).sequenceStartPositions->getVector(false); size_t numSequences1 = startPositions1->getSize() - 1; size_t numSequences2 = startPositions2->getSize() - 1; diff --git a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp index 26d9536dd5..0e9531eabb 100644 --- a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp +++ b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "SequencePoolLayer.h" diff --git a/paddle/gserver/layers/SequencePoolLayer.cpp b/paddle/gserver/layers/SequencePoolLayer.cpp index 55be73d363..c9f19b7d3b 100644 --- a/paddle/gserver/layers/SequencePoolLayer.cpp +++ b/paddle/gserver/layers/SequencePoolLayer.cpp @@ -58,7 +58,7 @@ void SequencePoolLayer::forward(PassType passType) { resetOutput(newBatchSize_, dim); if (type_) { CHECK(input.subSequenceStartPositions) - << "when trans_type = seq, input must hasSubseq"; + << "when trans_type = seq, input must hasSubseq"; } /* If type_ = kNonSeq, both seq has or not has sub-seq degrade to a non-seq, * thus, in this case, output_ has no sequenceStartPositions. diff --git a/paddle/gserver/layers/SequenceReshapeLayer.cpp b/paddle/gserver/layers/SequenceReshapeLayer.cpp index 05766706b0..5ca9b8b300 100644 --- a/paddle/gserver/layers/SequenceReshapeLayer.cpp +++ b/paddle/gserver/layers/SequenceReshapeLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -69,8 +68,7 @@ void SequenceReshapeLayer::forward(PassType passType) { size_t outDim = getSize(); size_t numSequences = input.getNumSequences(); - auto startPositions = - input.sequenceStartPositions->getVector(false); + auto startPositions = input.sequenceStartPositions->getVector(false); const int* starts = startPositions->getData(); CHECK_EQ(starts[numSequences], input.getBatchSize()); @@ -96,9 +94,7 @@ void SequenceReshapeLayer::forward(PassType passType) { // modify the sequenceStartPositions ICpuGpuVector::resizeOrCreate( - output_.sequenceStartPositions, - numSequences + 1, - false); + output_.sequenceStartPositions, numSequences + 1, false); int* tgtBuf = output_.sequenceStartPositions->getMutableData(false); @@ -134,8 +130,11 @@ void SequenceReshapeLayer::backward(const UpdateCallback& callback) { REGISTER_TIMER_INFO("SequenceReshapeLayerBackward", getName().c_str()); if (inputGrad) { - Matrix::resizeOrCreate(reshapedOutputGrad, inputGrad->getHeight(), - inputGrad->getWidth(), false, useGpu_); + Matrix::resizeOrCreate(reshapedOutputGrad, + inputGrad->getHeight(), + inputGrad->getWidth(), + false, + useGpu_); reshapedOutputGrad->copyFrom(*outputGrad); inputGrad->add(*reshapedOutputGrad); } diff --git a/paddle/gserver/layers/SequenceToBatch.cpp b/paddle/gserver/layers/SequenceToBatch.cpp index 88eace28b2..04402db9c8 100644 --- a/paddle/gserver/layers/SequenceToBatch.cpp +++ b/paddle/gserver/layers/SequenceToBatch.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "SequenceToBatch.h" @@ -21,8 +20,10 @@ limitations under the License. */ namespace paddle { -void SequenceToBatch::resizeOrCreateBatch(int batchSize, size_t numSequences, - const int *seqStarts, bool reversed, +void SequenceToBatch::resizeOrCreateBatch(int batchSize, + size_t numSequences, + const int *seqStarts, + bool reversed, bool prevBatchState) { CHECK_EQ(seqStarts[numSequences], batchSize); IVector::resizeOrCreate(seq2BatchIdx_, batchSize, useGpu_); @@ -50,7 +51,8 @@ void SequenceToBatch::resizeOrCreateBatch(int batchSize, size_t numSequences, int length = seqStarts[seqId + 1] - seqStarts[seqId]; seqStartAndLength.emplace_back(seqStarts[seqId], length, seqId); } - std::sort(seqStartAndLength.begin(), seqStartAndLength.end(), + std::sort(seqStartAndLength.begin(), + seqStartAndLength.end(), [](SeqStartAndLength a, SeqStartAndLength b) { return a.length_ > b.length_; }); @@ -122,15 +124,19 @@ void SequenceToBatch::resizeOrCreateBatch(int batchSize, size_t numSequences, } void SequenceToBatch::resizeOrCreate(Matrix &seqValue) { - Matrix::resizeOrCreate(batchValue_, seqValue.getHeight(), seqValue.getWidth(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(batchValue_, + seqValue.getHeight(), + seqValue.getWidth(), + /* trans= */ false, + useGpu_); } MatrixPtr SequenceToBatch::getBatchValue(int batchId, int numRows) { return getBatchValue(*batchValue_, batchId, numRows); } -MatrixPtr SequenceToBatch::getBatchValue(Matrix &batchValue, int batchId, +MatrixPtr SequenceToBatch::getBatchValue(Matrix &batchValue, + int batchId, int numRows) { int *batchStartPositions = batchStartPositions_->getData(); int start = batchStartPositions[batchId]; @@ -151,7 +157,8 @@ void SequenceToBatch::getSeqOutputFromBatch(Matrix &sequence, Matrix &batch) { sequence2BatchCopy(sequence, batch, *seqEndIdxInBatch_, true); } -void SequenceToBatch::sequence2BatchCopy(Matrix &batch, Matrix &sequence, +void SequenceToBatch::sequence2BatchCopy(Matrix &batch, + Matrix &sequence, IVector &seq2BatchIdx, bool seq2batch) { int seqWidth = sequence.getWidth(); @@ -161,23 +168,27 @@ void SequenceToBatch::sequence2BatchCopy(Matrix &batch, Matrix &sequence, int *idxData = seq2BatchIdx.getData(); if (useGpu_) { - hl_sequence2batch_copy(batchData, seqData, idxData, seqWidth, - batchCount, seq2batch); + hl_sequence2batch_copy( + batchData, seqData, idxData, seqWidth, batchCount, seq2batch); } else { for (int i = 0; i < batchCount; ++i) { if (seq2batch) { - memcpy(batch.rowBuf(i), sequence.rowBuf(idxData[i]), + memcpy(batch.rowBuf(i), + sequence.rowBuf(idxData[i]), seqWidth * sizeof(real)); } else { - memcpy(sequence.rowBuf(idxData[i]), batch.rowBuf(i), + memcpy(sequence.rowBuf(idxData[i]), + batch.rowBuf(i), seqWidth * sizeof(real)); } } } } -void SequenceToBatch::sequence2BatchAdd(Matrix &batch, Matrix &sequence, - IVector &seq2BatchIdx, bool seq2batch) { +void SequenceToBatch::sequence2BatchAdd(Matrix &batch, + Matrix &sequence, + IVector &seq2BatchIdx, + bool seq2batch) { int seqWidth = sequence.getWidth(); int batchCount = batch.getHeight(); real *batchData = batch.getData(); @@ -185,8 +196,8 @@ void SequenceToBatch::sequence2BatchAdd(Matrix &batch, Matrix &sequence, int *idxData = seq2BatchIdx.getData(); if (useGpu_) { - hl_sequence2batch_add(batchData, seqData, idxData, seqWidth, - batchCount, seq2batch); + hl_sequence2batch_add( + batchData, seqData, idxData, seqWidth, batchCount, seq2batch); } else { for (int i = 0; i < batchCount; ++i) { if (seq2batch) { @@ -199,8 +210,11 @@ void SequenceToBatch::sequence2BatchAdd(Matrix &batch, Matrix &sequence, } void SequenceToBatch::copyFromSeq(Matrix &seqValue) { - Matrix::resizeOrCreate(batchValue_, seqValue.getHeight(), seqValue.getWidth(), - /* trans= */ false, useGpu_); + Matrix::resizeOrCreate(batchValue_, + seqValue.getHeight(), + seqValue.getWidth(), + /* trans= */ false, + useGpu_); sequence2BatchCopy(*batchValue_, seqValue, *seq2BatchIdx_, true); } @@ -208,12 +222,14 @@ void SequenceToBatch::copyBackSeq(Matrix &seqValue) { sequence2BatchCopy(*batchValue_, seqValue, *seq2BatchIdx_, false); } -void SequenceToBatch::copy(Matrix &seqValue, Matrix &batchValue, +void SequenceToBatch::copy(Matrix &seqValue, + Matrix &batchValue, bool seq2batch) { sequence2BatchCopy(batchValue, seqValue, *seq2BatchIdx_, seq2batch); } -void SequenceToBatch::add(Matrix &seqValue, Matrix &batchValue, +void SequenceToBatch::add(Matrix &seqValue, + Matrix &batchValue, bool seq2batch) { sequence2BatchAdd(batchValue, seqValue, *seq2BatchIdx_, seq2batch); } diff --git a/paddle/gserver/layers/SequenceToBatch.h b/paddle/gserver/layers/SequenceToBatch.h index 8cba7ea3b9..6bc12f207e 100644 --- a/paddle/gserver/layers/SequenceToBatch.h +++ b/paddle/gserver/layers/SequenceToBatch.h @@ -43,8 +43,10 @@ public: explicit SequenceToBatch(bool useGpu) : useGpu_(useGpu) {} /* resize and calculate the batchIndex_ */ - void resizeOrCreateBatch(int batchSize, size_t numSequences, - const int *seqStarts, bool reversed, + void resizeOrCreateBatch(int batchSize, + size_t numSequences, + const int *seqStarts, + bool reversed, bool prevBatchState = false); /* sequence matrix and batch matrix copy: @@ -81,9 +83,13 @@ public: } protected: - void sequence2BatchCopy(Matrix &batch, Matrix &sequence, - IVector &seq2BatchIdx, bool seq2batch); - void sequence2BatchAdd(Matrix &batch, Matrix &sequence, IVector &seq2BatchIdx, + void sequence2BatchCopy(Matrix &batch, + Matrix &sequence, + IVector &seq2BatchIdx, + bool seq2batch); + void sequence2BatchAdd(Matrix &batch, + Matrix &sequence, + IVector &seq2BatchIdx, bool seq2batch); IVectorPtr batchStartPositions_; diff --git a/paddle/gserver/layers/SlopeInterceptLayer.cpp b/paddle/gserver/layers/SlopeInterceptLayer.cpp index af5fccf650..dd6ffcd50b 100644 --- a/paddle/gserver/layers/SlopeInterceptLayer.cpp +++ b/paddle/gserver/layers/SlopeInterceptLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -21,7 +20,8 @@ limitations under the License. */ namespace paddle { /** - * @brief A layer for applying a slope and an intercept to the input element-wise. + * @brief A layer for applying a slope and an intercept to the input + * element-wise. * This layer is used in NEURAL TURING MACHINE. * @note There is no activation and weight in this layer. * @@ -29,7 +29,8 @@ namespace paddle { * y = ax + b * \f] * - * Here, a is scale and b is offset, which are provided as attributes of the layer. + * Here, a is scale and b is offset, which are provided as attributes of the + * layer. * * The config file api is slope_intercept_layer. */ diff --git a/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp b/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp index 2fcfc8e1ae..9609919695 100644 --- a/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp +++ b/paddle/gserver/layers/SpatialPyramidPoolLayer.cpp @@ -93,7 +93,8 @@ bool SpatialPyramidPoolLayer::init(const LayerMap& layerMap, size_t endCol = 0; for (size_t i = 0; i < pyramidHeight_; i++) { poolProjections_.emplace_back(PoolProjection::create( - getConfig(imgSizeW_, imgSizeH_, channels_, i, poolType_), nullptr, + getConfig(imgSizeW_, imgSizeH_, channels_, i, poolType_), + nullptr, useGpu_)); endCol += poolProjections_[i]->getOutputSize(); projCol_.push_back(std::make_pair(startCol, endCol)); diff --git a/paddle/gserver/layers/SpatialPyramidPoolLayer.h b/paddle/gserver/layers/SpatialPyramidPoolLayer.h index e15b6d2f85..79db574d99 100644 --- a/paddle/gserver/layers/SpatialPyramidPoolLayer.h +++ b/paddle/gserver/layers/SpatialPyramidPoolLayer.h @@ -24,7 +24,7 @@ namespace paddle { * @brief A layer for spatial pyramid pooling on the input image by taking * the max, average, etc. within regions, so that the result vector of * different sized images are of the same size. - * + * * The config file api is spp_layer. */ @@ -47,8 +47,11 @@ public: virtual bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); - ProjectionConfig getConfig(size_t sizeX_, size_t sizeY_, size_t channels, - size_t pyamidLevel_, std::string& poolType_); + ProjectionConfig getConfig(size_t sizeX_, + size_t sizeY_, + size_t channels, + size_t pyamidLevel_, + std::string& poolType_); size_t getSize(); virtual void forward(PassType passType); diff --git a/paddle/gserver/layers/SubSequenceLayer.cpp b/paddle/gserver/layers/SubSequenceLayer.cpp index ccf65ba649..664f9e13c0 100644 --- a/paddle/gserver/layers/SubSequenceLayer.cpp +++ b/paddle/gserver/layers/SubSequenceLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -75,18 +74,15 @@ void SubSequenceLayer::forward(PassType passType) { const Argument& input = getInput(0); size_t numSequences1 = input.getNumSequences(); - auto startPositions1 = - input.sequenceStartPositions->getVector(false); + auto startPositions1 = input.sequenceStartPositions->getVector(false); const Argument& offsetSeq = getInput(1); size_t numSequences2 = offsetSeq.getNumSequences(); - auto startPositions2 = - offsetSeq.sequenceStartPositions->getVector(false); + auto startPositions2 = offsetSeq.sequenceStartPositions->getVector(false); const Argument& sizeSeq = getInput(2); size_t numSequences3 = sizeSeq.getNumSequences(); - auto startPositions3 = - sizeSeq.sequenceStartPositions->getVector(false); + auto startPositions3 = sizeSeq.sequenceStartPositions->getVector(false); CHECK_EQ(dim, input.value->getWidth()); @@ -143,8 +139,8 @@ void SubSequenceLayer::forward(PassType passType) { } // modify the sequenceStartPositions - ICpuGpuVector::resizeOrCreate(output_.sequenceStartPositions, - numSequences1 + 1, false); + ICpuGpuVector::resizeOrCreate( + output_.sequenceStartPositions, numSequences1 + 1, false); int* tgtBuf = output_.sequenceStartPositions->getMutableData(false); int offset = 0; @@ -177,8 +173,7 @@ void SubSequenceLayer::backward(const UpdateCallback& callback) { MatrixPtr inputGrad1 = getInputGrad(0); MatrixPtr outputGrad = getOutputGrad(); - auto startPositions1 = - getInput(0).sequenceStartPositions->getVector(false); + auto startPositions1 = getInput(0).sequenceStartPositions->getVector(false); size_t numSequences1 = startPositions1->getSize() - 1; const int* starts1 = startPositions1->getData(); diff --git a/paddle/gserver/layers/SumToOneNormLayer.cpp b/paddle/gserver/layers/SumToOneNormLayer.cpp index 7b61dd0822..bcf3916840 100644 --- a/paddle/gserver/layers/SumToOneNormLayer.cpp +++ b/paddle/gserver/layers/SumToOneNormLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "Layer.h" #include "paddle/math/Matrix.h" @@ -21,7 +20,7 @@ limitations under the License. */ namespace paddle { /** - * A layer for sum-to-one normalization, + * A layer for sum-to-one normalization, * which is used in NEURAL TURING MACHINE. * \f[ * out[i] = \frac {in[i]} {\sum_{k=1}^N in[k]} diff --git a/paddle/gserver/layers/TableProjection.cpp b/paddle/gserver/layers/TableProjection.cpp index 947d8cf9be..2bc0d329d9 100644 --- a/paddle/gserver/layers/TableProjection.cpp +++ b/paddle/gserver/layers/TableProjection.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "TableProjection.h" namespace paddle { @@ -20,7 +19,8 @@ namespace paddle { REGISTER_PROJECTION(table, TableProjection); TableProjection::TableProjection(const ProjectionConfig& config, - const ParameterPtr& parameter, bool useGpu) + const ParameterPtr& parameter, + bool useGpu) : Projection(config, parameter, useGpu) { table_.reset( new Weight(config.input_size(), config.output_size(), parameter)); diff --git a/paddle/gserver/layers/TableProjection.h b/paddle/gserver/layers/TableProjection.h index eadf2de623..97c672508a 100644 --- a/paddle/gserver/layers/TableProjection.h +++ b/paddle/gserver/layers/TableProjection.h @@ -12,7 +12,6 @@ WITHOUT 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 "Projection.h" @@ -34,7 +33,8 @@ namespace paddle { */ class TableProjection : public Projection { public: - TableProjection(const ProjectionConfig& config, const ParameterPtr& parameter, + TableProjection(const ProjectionConfig& config, + const ParameterPtr& parameter, bool useGpu); /** * If use sparse row matrix as parameter, prefetch feature ids in input label. diff --git a/paddle/gserver/layers/TensorLayer.cpp b/paddle/gserver/layers/TensorLayer.cpp index 84fe9005b0..03586cc6ff 100644 --- a/paddle/gserver/layers/TensorLayer.cpp +++ b/paddle/gserver/layers/TensorLayer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "TensorLayer.h" #include "paddle/utils/Logging.h" @@ -72,7 +71,9 @@ void TensorLayer::forward(PassType passType) { MatrixPtr input1 = getInputValue(0); MatrixPtr input2 = getInputValue(1); MatrixPtr tmpMat = Matrix::create(input2->getHeight(), - input2->getWidth(), /* trans= */ false, input2->useGpu()); + input2->getWidth(), + /* trans= */ false, + input2->useGpu()); REGISTER_TIMER_INFO("TensorFwMulTimer", getName().c_str()); for (size_t i = 0; i < getSize(); ++i) { MatrixPtr weights = weights_[i]->getW(); @@ -101,7 +102,9 @@ void TensorLayer::backward(const UpdateCallback& callback) { MatrixPtr input2 = getInputValue(1); MatrixPtr oGrad = getOutputGrad(); MatrixPtr tmpMat = Matrix::create(input1->getHeight(), - input1->getWidth(), /* trans= */ false, input1->useGpu()); + input1->getWidth(), + /* trans= */ false, + input1->useGpu()); /* trans(grad * e1) * e2 */ { REGISTER_TIMER_INFO("TensorGradMulTimer", getName().c_str()); diff --git a/paddle/gserver/layers/TensorLayer.h b/paddle/gserver/layers/TensorLayer.h index 83b87b1307..9ac651de4d 100644 --- a/paddle/gserver/layers/TensorLayer.h +++ b/paddle/gserver/layers/TensorLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/TransLayer.cpp b/paddle/gserver/layers/TransLayer.cpp index f8827bec63..53a24d4cc4 100644 --- a/paddle/gserver/layers/TransLayer.cpp +++ b/paddle/gserver/layers/TransLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "TransLayer.h" namespace paddle { diff --git a/paddle/gserver/layers/TransLayer.h b/paddle/gserver/layers/TransLayer.h index 867ccb4d19..25b091f9f4 100644 --- a/paddle/gserver/layers/TransLayer.h +++ b/paddle/gserver/layers/TransLayer.h @@ -12,7 +12,6 @@ WITHOUT 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" diff --git a/paddle/gserver/layers/TransposedFullMatrixProjection.cpp b/paddle/gserver/layers/TransposedFullMatrixProjection.cpp index 6e3f6bf2e4..c883283f78 100644 --- a/paddle/gserver/layers/TransposedFullMatrixProjection.cpp +++ b/paddle/gserver/layers/TransposedFullMatrixProjection.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Stat.h" #include "Projection.h" @@ -27,7 +26,8 @@ namespace paddle { class TransposedFullMatrixProjection : public Projection { public: TransposedFullMatrixProjection(const ProjectionConfig& config, - ParameterPtr parameter, bool useGPu); + ParameterPtr parameter, + bool useGPu); virtual void forward(); virtual void backward(const UpdateCallback& callback); diff --git a/paddle/gserver/layers/ValidationLayer.cpp b/paddle/gserver/layers/ValidationLayer.cpp index 48a7b54338..0fee4bd246 100644 --- a/paddle/gserver/layers/ValidationLayer.cpp +++ b/paddle/gserver/layers/ValidationLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include @@ -68,8 +67,11 @@ void AucValidation::validationImp(MatrixPtr output, IVectorPtr label) { if (dynamic_cast(output.get())) { size_t height = output->getHeight(); size_t width = output->getWidth(); - Matrix::resizeOrCreate(cpuOutput_, height, width, - /* trans=*/false, /* useGpu=*/false); + Matrix::resizeOrCreate(cpuOutput_, + height, + width, + /* trans=*/false, + /* useGpu=*/false); cpuOutput_->copyFrom(*output); IVector::resizeOrCreate(cpuLabel_, height, false); cpuLabel_->copyFrom(*label); diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index bc7bee0e4b..4757516917 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LayerGradUtil.h" P_DECLARE_bool(thread_local_rand_use_global_seed); @@ -28,8 +27,13 @@ real getCostSum(LayerPtr& testLayer, MatrixPtr weights) { return Argument::sumCosts(outArgs); } -real getDiffAndPrint(real newCost1, real newCost2, real callbackCount, - char fill, string testLayerName, string name, real step, +real getDiffAndPrint(real newCost1, + real newCost2, + real callbackCount, + char fill, + string testLayerName, + string name, + real step, real delta) { EXPECT_FALSE(std::isnan(newCost1)); EXPECT_FALSE(std::isnan(newCost2)); @@ -49,7 +53,8 @@ real getDiffAndPrint(real newCost1, real newCost2, real callbackCount, return diff; } -void testState(LayerPtr testLayer, vector& dataLayers, +void testState(LayerPtr testLayer, + vector& dataLayers, vector& datas) { auto batchSize = datas[0].getBatchSize(); Argument data; @@ -82,8 +87,8 @@ void testState(LayerPtr testLayer, vector& dataLayers, data.value = datas[j].value->subMatrix(batchId, 1); } if (datas[j].ids) { - data.ids = IVector::create(datas[j].ids->getData() + batchId, 1, - FLAGS_use_gpu); + data.ids = IVector::create( + datas[j].ids->getData() + batchId, 1, FLAGS_use_gpu); } dataLayers[j]->setData(data); dataLayers[j]->forward(PASS_TEST); @@ -128,7 +133,8 @@ void testState(LayerPtr testLayer, vector& dataLayers, } } -void testBatchState(LayerPtr testLayer, vector& dataLayers, +void testBatchState(LayerPtr testLayer, + vector& dataLayers, vector& datas) { auto batchSize = datas[0].getBatchSize(); Argument data; @@ -192,8 +198,10 @@ void testBatchState(LayerPtr testLayer, vector& dataLayers, splitData.sequenceStartPositions = cpuSeqStartPos; for (size_t j = 0; j < datas.size(); ++j) { if (datas[j].value) { - Matrix::resizeOrCreate(splitData.value, splitBatchSize, - datas[j].value->getWidth(), false, + Matrix::resizeOrCreate(splitData.value, + splitBatchSize, + datas[j].value->getWidth(), + false, FLAGS_use_gpu); for (size_t seqId = 0; seqId < numSequences; ++seqId) { if (seqLens[seqId]) { @@ -268,8 +276,10 @@ void initWeight(MatrixPtr& weights) { weights->copyFrom(*tmpMat); } -void initBatchState(LayerPtr dataLayer, LayerPtr testLayer, - LayerStatePtr state, bool useGpu) { +void initBatchState(LayerPtr dataLayer, + LayerPtr testLayer, + LayerStatePtr state, + bool useGpu) { int sequenceNum = dataLayer->getOutput().getNumSequences(); MatrixPtr prevBatchOutput = Matrix::create(sequenceNum, testLayer->getSize(), false, useGpu); @@ -282,9 +292,13 @@ void initBatchState(LayerPtr dataLayer, LayerPtr testLayer, state->value.push_back(prevBatchState); } -void initDataLayer(TestConfig testConf, std::vector* dataLayers, - vector* datas, LayerMap* layerMap, - string testLayerName, size_t batchSize, bool trans, +void initDataLayer(TestConfig testConf, + std::vector* dataLayers, + vector* datas, + LayerMap* layerMap, + string testLayerName, + size_t batchSize, + bool trans, bool useGpu) { ICpuGpuVectorPtr sequenceStartPositions; ICpuGpuVectorPtr subSequenceStartPositions; @@ -328,13 +342,17 @@ void initDataLayer(TestConfig testConf, std::vector* dataLayers, break; case INPUT_SPARSE_NON_VALUE_DATA: data.value = makeRandomSparseMatrix( - batchSize, layer->getSize(), - /* withValue= */ false, useGpu, + batchSize, + layer->getSize(), + /* withValue= */ false, + useGpu, testConf.inputDefs[i].sparse.equalNnzPerSample); break; case INPUT_SPARSE_FLOAT_VALUE_DATA: - data.value = makeRandomSparseMatrix(batchSize, layer->getSize(), - /* withValue= */ true, useGpu); + data.value = makeRandomSparseMatrix(batchSize, + layer->getSize(), + /* withValue= */ true, + useGpu); break; case INPUT_DENSE_DIM_DATA: fillData(trans, layer->getSize(), numSequence); @@ -379,16 +397,21 @@ void initDataLayer(TestConfig testConf, std::vector* dataLayers, } } -void initTestLayer(TestConfig testConf, LayerMap* layerMap, - std::vector* parameters, LayerPtr* testLayer) { +void initTestLayer(TestConfig testConf, + LayerMap* layerMap, + std::vector* parameters, + LayerPtr* testLayer) { ParameterMap parameterMap; size_t index = 0; LayerConfig testConfig = testConf.layerConfig; CHECK_EQ(testConf.inputDefs.size(), size_t(testConf.layerConfig.inputs_size())); - auto initParameter = [&](string paraName, size_t paraSize, bool isStatic, - bool initialize, ParameterConfig paraConfig) { + auto initParameter = [&](string paraName, + size_t paraSize, + bool isStatic, + bool initialize, + ParameterConfig paraConfig) { paraConfig.set_name(paraName); paraConfig.set_size(paraSize); paraConfig.set_initial_std(1); @@ -431,8 +454,11 @@ void initTestLayer(TestConfig testConf, LayerMap* layerMap, if (testConf.biasSize) { testConfig.set_bias_parameter_name("bias"); ParameterConfig paraConfig; - initParameter(testConfig.bias_parameter_name(), testConf.biasSize, - testConf.staticBias, true, paraConfig); + initParameter(testConfig.bias_parameter_name(), + testConf.biasSize, + testConf.staticBias, + true, + paraConfig); } *testLayer = Layer::create(testConfig); @@ -441,9 +467,13 @@ void initTestLayer(TestConfig testConf, LayerMap* layerMap, (*testLayer)->setNeedGradient(true); } -void testPerturbParameter(TestConfig testConf, const MatrixPtr weights, - const LayerStatePtr state, real cost, - real callbackCount, real* maxDiff, LayerPtr testLayer, +void testPerturbParameter(TestConfig testConf, + const MatrixPtr weights, + const LayerStatePtr state, + real cost, + real callbackCount, + real* maxDiff, + LayerPtr testLayer, std::vector* parameters) { char fill = ' '; for (auto& parameter : *parameters) { @@ -481,9 +511,14 @@ void testPerturbParameter(TestConfig testConf, const MatrixPtr weights, parameter->setValueUpdated(); newCost[k] = getCostSum(testLayer, weights); } - real diff = getDiffAndPrint(newCost[0], newCost[1], callbackCount, fill, - testLayer->getName(), parameter->getName(), - step, delta); + real diff = getDiffAndPrint(newCost[0], + newCost[1], + callbackCount, + fill, + testLayer->getName(), + parameter->getName(), + step, + delta); *maxDiff = std::max(*maxDiff, abs(diff)); // restore parameter parameter->getBuf(PARAMETER_VALUE)->copyFrom(oldPara); @@ -492,9 +527,13 @@ void testPerturbParameter(TestConfig testConf, const MatrixPtr weights, } } -void testPerturbInput(TestConfig testConf, const MatrixPtr weights, - const LayerStatePtr state, real cost, real callbackCount, - real* maxDiff, LayerPtr testLayer, +void testPerturbInput(TestConfig testConf, + const MatrixPtr weights, + const LayerStatePtr state, + real cost, + real callbackCount, + real* maxDiff, + LayerPtr testLayer, std::vector dataLayers) { char fill = ' '; for (size_t index = 0; index < testConf.inputDefs.size(); index++) { @@ -539,9 +578,14 @@ void testPerturbInput(TestConfig testConf, const MatrixPtr weights, newCost[k] = getCostSum(testLayer, weights); } - real diff = getDiffAndPrint(newCost[0], newCost[1], callbackCount, fill, + real diff = getDiffAndPrint(newCost[0], + newCost[1], + callbackCount, + fill, testLayer->getName(), - dataLayers[index]->getName(), step, delta); + dataLayers[index]->getName(), + step, + delta); *maxDiff = std::max(*maxDiff, abs(diff)); // restore parameter outV->copyFrom(oldPara); @@ -549,9 +593,13 @@ void testPerturbInput(TestConfig testConf, const MatrixPtr weights, } } -void testLayerGradKernel(TestConfig testConf, string testLayerName, - size_t batchSize, bool trans, bool useGpu, - bool useWeight, float epsilon) { +void testLayerGradKernel(TestConfig testConf, + string testLayerName, + size_t batchSize, + bool trans, + bool useGpu, + bool useWeight, + float epsilon) { #ifdef PADDLE_ONLY_CPU if (useGpu) return; #endif @@ -566,8 +614,14 @@ void testLayerGradKernel(TestConfig testConf, string testLayerName, std::vector dataLayers; LayerMap layerMap; vector datas; - initDataLayer(testConf, &dataLayers, &datas, &layerMap, testLayerName, - batchSize, trans, useGpu); + initDataLayer(testConf, + &dataLayers, + &datas, + &layerMap, + testLayerName, + batchSize, + trans, + useGpu); // test layer initialize std::vector parameters; LayerPtr testLayer; @@ -620,17 +674,28 @@ void testLayerGradKernel(TestConfig testConf, string testLayerName, ++callbackCount; } for (size_t i = 0; i < parameters.size(); ++i) { - EXPECT_EQ(parameters[i]->isStatic() ? 0 : callbackCount, - callbackFlags[i]); + EXPECT_EQ(parameters[i]->isStatic() ? 0 : callbackCount, callbackFlags[i]); } // Test whether the layer's forward calculation is stable // by adding perturbation to its parameters or its input layers real maxDiff = 0; - testPerturbParameter(testConf, weights, state, cost, callbackCount, &maxDiff, - testLayer, ¶meters); - testPerturbInput(testConf, weights, state, cost, callbackCount, &maxDiff, - testLayer, dataLayers); + testPerturbParameter(testConf, + weights, + state, + cost, + callbackCount, + &maxDiff, + testLayer, + ¶meters); + testPerturbInput(testConf, + weights, + state, + cost, + callbackCount, + &maxDiff, + testLayer, + dataLayers); EXPECT_LE(fabs(maxDiff), epsilon); if (testConf.testState) { @@ -641,10 +706,15 @@ void testLayerGradKernel(TestConfig testConf, string testLayerName, } } -void testLayerGrad(TestConfig testConf, string testLayerName, size_t batchSize, - bool trans, bool useGpu, bool useWeight, float epsilon) { - testLayerGradKernel(testConf, testLayerName, batchSize, trans, useGpu, - useWeight, epsilon); +void testLayerGrad(TestConfig testConf, + string testLayerName, + size_t batchSize, + bool trans, + bool useGpu, + bool useWeight, + float epsilon) { + testLayerGradKernel( + testConf, testLayerName, batchSize, trans, useGpu, useWeight, epsilon); bool isStaticTest = false; LayerConfig testConfig = testConf.layerConfig; for (size_t i = 0; i < testConf.inputDefs.size(); i++) { @@ -662,14 +732,19 @@ void testLayerGrad(TestConfig testConf, string testLayerName, size_t batchSize, isStaticTest = true; } if (isStaticTest) { - testLayerGradKernel(testConf, testLayerName, batchSize, trans, useGpu, - useWeight, epsilon); + testLayerGradKernel( + testConf, testLayerName, batchSize, trans, useGpu, useWeight, epsilon); } } -void testProjectionGrad(ProjectionConfig conf, InputType inputType, - size_t parameterSize, size_t batchSize, bool useGpu, - bool testState, int biasSize, bool sharedBias) { +void testProjectionGrad(ProjectionConfig conf, + InputType inputType, + size_t parameterSize, + size_t batchSize, + bool useGpu, + bool testState, + int biasSize, + bool sharedBias) { TestConfig config; conf.set_name(conf.type()); config.layerConfig.set_type("mixed"); @@ -684,8 +759,11 @@ void testProjectionGrad(ProjectionConfig conf, InputType inputType, testLayerGrad(config, "mixed", batchSize, false, useGpu); } -void testOperatorGrad(TestConfig& config, OperatorConfig& operatorConf, - size_t batchSize, bool useGpu, bool testState) { +void testOperatorGrad(TestConfig& config, + OperatorConfig& operatorConf, + size_t batchSize, + bool useGpu, + bool testState) { config.layerConfig.set_type("mixed"); operatorConf.set_output_size(config.layerConfig.size()); diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 3b9ec80395..a061c7fc53 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -72,7 +72,10 @@ struct InputDef { sparse = {""}; isStatic = false; } - InputDef(InputType type, string nameIn, size_t dimIn, size_t sizeIn, + InputDef(InputType type, + string nameIn, + size_t dimIn, + size_t sizeIn, ParaSparse sparseIn) { inputType = type; name = nameIn; @@ -98,11 +101,18 @@ struct TestConfig { testBatchState(false) {} }; -real getCostSum(ParameterPtr& parameter, CpuVector& cpuPara, - LayerPtr& testLayer, MatrixPtr weights = nullptr); +real getCostSum(ParameterPtr& parameter, + CpuVector& cpuPara, + LayerPtr& testLayer, + MatrixPtr weights = nullptr); -real getDiffAndPrint(real newCost1, real newCost2, real callbackCount, - char fill, string testLayerName, string name, real step, +real getDiffAndPrint(real newCost1, + real newCost2, + real callbackCount, + char fill, + string testLayerName, + string name, + real step, real delta); /** @@ -113,7 +123,8 @@ real getDiffAndPrint(real newCost1, real newCost2, real callbackCount, * @param dataLayers[in/out] dataLayers * @param datas[in/out] data of dataLayers */ -void testState(LayerPtr testLayer, vector& dataLayers, +void testState(LayerPtr testLayer, + vector& dataLayers, vector& datas); /** @@ -124,7 +135,8 @@ void testState(LayerPtr testLayer, vector& dataLayers, * @param dataLayers[in/out] dataLayers * @param datas[in/out] data of dataLayers */ -void testBatchState(LayerPtr testLayer, vector& dataLayers, +void testBatchState(LayerPtr testLayer, + vector& dataLayers, vector& datas); /** @@ -144,8 +156,10 @@ double genPerturbation(const real* oldGrad, real* newGrad, size_t dim); void initWeight(MatrixPtr& weights); -void initBatchState(LayerPtr dataLayer, LayerPtr testLayer, - LayerStatePtr state, bool useGpu); +void initBatchState(LayerPtr dataLayer, + LayerPtr testLayer, + LayerStatePtr state, + bool useGpu); /** * @brief initialize the dataLayer by its inputType @@ -155,9 +169,13 @@ void initBatchState(LayerPtr dataLayer, LayerPtr testLayer, * datas[out] initialized data of dataLayers * layerMap[out] layerMap */ -void initDataLayer(TestConfig testConf, std::vector* dataLayers, - vector* datas, LayerMap* layerMap, - string testLayerName, size_t batchSize, bool trans, +void initDataLayer(TestConfig testConf, + std::vector* dataLayers, + vector* datas, + LayerMap* layerMap, + string testLayerName, + size_t batchSize, + bool trans, bool useGpu); /** @@ -168,8 +186,10 @@ void initDataLayer(TestConfig testConf, std::vector* dataLayers, * parameters[out] parameters of testLayer * testLayer[out] testLayer */ -void initTestLayer(TestConfig testConf, LayerMap* layerMap, - std::vector* parameters, LayerPtr* testLayer); +void initTestLayer(TestConfig testConf, + LayerMap* layerMap, + std::vector* parameters, + LayerPtr* testLayer); /** * @brief Test whether the layer's forward calculation is stable by adding @@ -184,9 +204,13 @@ void initTestLayer(TestConfig testConf, LayerMap* layerMap, * testLayer[in/out] testLayer * parameters[in/out] parameters of testLayer */ -void testPerturbParameter(TestConfig testConf, const MatrixPtr weights, - const LayerStatePtr state, real cost, - real callbackCount, real* maxDiff, LayerPtr testLayer, +void testPerturbParameter(TestConfig testConf, + const MatrixPtr weights, + const LayerStatePtr state, + real cost, + real callbackCount, + real* maxDiff, + LayerPtr testLayer, std::vector* parameters); /** @@ -202,25 +226,44 @@ void testPerturbParameter(TestConfig testConf, const MatrixPtr weights, * testLayer[in/out] testLayer * dataLayers[in/out] dataLayers */ -void testPerturbInput(TestConfig testConf, const MatrixPtr weights, - const LayerStatePtr state, real cost, real callbackCount, - real* maxDiff, LayerPtr testLayer, +void testPerturbInput(TestConfig testConf, + const MatrixPtr weights, + const LayerStatePtr state, + real cost, + real callbackCount, + real* maxDiff, + LayerPtr testLayer, std::vector dataLayers); -void testLayerGradKernel(TestConfig testConf, string testLayerName, - size_t batchSize, bool trans, bool useGpu, - bool useWeight = false, float epsilon = 0.02); +void testLayerGradKernel(TestConfig testConf, + string testLayerName, + size_t batchSize, + bool trans, + bool useGpu, + bool useWeight = false, + float epsilon = 0.02); -void testLayerGrad(TestConfig testConf, string testLayerName, size_t batchSize, - bool trans, bool useGpu, bool useWeight = false, +void testLayerGrad(TestConfig testConf, + string testLayerName, + size_t batchSize, + bool trans, + bool useGpu, + bool useWeight = false, float epsilon = 0.02); -void testProjectionGrad(ProjectionConfig conf, InputType inputType, - size_t parameterSize, size_t batchSize, bool useGpu, - bool testState = false, int biasSize = 0, +void testProjectionGrad(ProjectionConfig conf, + InputType inputType, + size_t parameterSize, + size_t batchSize, + bool useGpu, + bool testState = false, + int biasSize = 0, bool sharedBias = false); -void testOperatorGrad(TestConfig& config, OperatorConfig& operatorConf, - size_t batchSize, bool useGpu, bool testState = false); +void testOperatorGrad(TestConfig& config, + OperatorConfig& operatorConf, + size_t batchSize, + bool useGpu, + bool testState = false); } // namespace paddle diff --git a/paddle/gserver/tests/TestUtil.cpp b/paddle/gserver/tests/TestUtil.cpp index 97fbcc8176..84d516683c 100644 --- a/paddle/gserver/tests/TestUtil.cpp +++ b/paddle/gserver/tests/TestUtil.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "TestUtil.h" #include "paddle/utils/CommandLineParser.h" @@ -30,8 +29,11 @@ std::string randStr(const int len) { return s; } -MatrixPtr makeRandomSparseMatrix(size_t height, size_t width, bool withValue, - bool useGpu, bool equalNnzPerSample) { +MatrixPtr makeRandomSparseMatrix(size_t height, + size_t width, + bool withValue, + bool useGpu, + bool equalNnzPerSample) { std::vector ids(height); std::vector indices(height + 1); indices[0] = 0; @@ -55,8 +57,8 @@ MatrixPtr makeRandomSparseMatrix(size_t height, size_t width, bool withValue, for (size_t i = 0; i < data.size(); ++i) { data[i].col = uniformRandom(width); } - auto mat = Matrix::createSparseMatrix(height, width, data.size(), NO_VALUE, - SPARSE_CSR, false, useGpu); + auto mat = Matrix::createSparseMatrix( + height, width, data.size(), NO_VALUE, SPARSE_CSR, false, useGpu); if (useGpu) { std::dynamic_pointer_cast(mat)->copyFrom( ids.data(), indices.data(), data.data(), HPPL_STREAM_DEFAULT); @@ -93,7 +95,7 @@ void generateSequenceStartPositions(size_t batchSize, } void generateSequenceStartPositions(size_t batchSize, - ICpuGpuVectorPtr& sequenceStartPositions) { + ICpuGpuVectorPtr& sequenceStartPositions) { int numSeqs; if (FLAGS_fixed_seq_length != 0) { numSeqs = std::ceil((float)batchSize / (float)FLAGS_fixed_seq_length); @@ -101,7 +103,7 @@ void generateSequenceStartPositions(size_t batchSize, numSeqs = batchSize / 10 + 1; } sequenceStartPositions = - ICpuGpuVector::create(numSeqs + 1, /* useGpu= */false); + ICpuGpuVector::create(numSeqs + 1, /* useGpu= */ false); int* buf = sequenceStartPositions->getMutableData(false); int64_t pos = 0; int len = FLAGS_fixed_seq_length; @@ -109,7 +111,8 @@ void generateSequenceStartPositions(size_t batchSize, for (int i = 0; i < numSeqs; ++i) { if (FLAGS_fixed_seq_length == 0) { len = uniformRandom( - std::min(maxLen, batchSize - pos - numSeqs + i)) + 1; + std::min(maxLen, batchSize - pos - numSeqs + i)) + + 1; } buf[i] = pos; pos += len; @@ -118,7 +121,6 @@ void generateSequenceStartPositions(size_t batchSize, buf[numSeqs] = batchSize; } - void generateSubSequenceStartPositions( const ICpuGpuVectorPtr& sequenceStartPositions, ICpuGpuVectorPtr& subSequenceStartPositions) { @@ -148,7 +150,6 @@ void generateSubSequenceStartPositions( subBuf[j] = buf[numSeqs]; } - void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, IVectorPtr& cpuSequenceDims) { /* generate sequences with 2 dims */ @@ -174,9 +175,8 @@ void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, } } -void generateMDimSequenceData( - const ICpuGpuVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims) { +void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims) { /* generate sequences with 2 dims */ int numSeqs = sequenceStartPositions->getSize() - 1; int numDims = 2; diff --git a/paddle/gserver/tests/TestUtil.h b/paddle/gserver/tests/TestUtil.h index 6a75f92ffe..000f8884e8 100644 --- a/paddle/gserver/tests/TestUtil.h +++ b/paddle/gserver/tests/TestUtil.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -28,8 +27,11 @@ inline bool approximatelyEqual(float a, float b, float epsilon) { return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); } -MatrixPtr makeRandomSparseMatrix(size_t height, size_t width, bool withValue, - bool useGpu, bool equalNnzPerSample = false); +MatrixPtr makeRandomSparseMatrix(size_t height, + size_t width, + bool withValue, + bool useGpu, + bool equalNnzPerSample = false); /** * @brief generate sequenceStartPositions for INPUT_SEQUENCE_DATA, @@ -39,10 +41,10 @@ MatrixPtr makeRandomSparseMatrix(size_t height, size_t width, bool withValue, * sequenceStartPositions[out] generation output */ void generateSequenceStartPositions(size_t batchSize, - IVectorPtr& sequenceStartPositions); + IVectorPtr& sequenceStartPositions); void generateSequenceStartPositions(size_t batchSize, - ICpuGpuVectorPtr& sequenceStartPositions); + ICpuGpuVectorPtr& sequenceStartPositions); /** * @brief generate subSequenceStartPositions for INPUT_HASSUB_SEQUENCE_DATA @@ -51,9 +53,8 @@ void generateSequenceStartPositions(size_t batchSize, * @param sequenceStartPositions[in] input * subSequenceStartPositions[out] generation output */ -void generateSubSequenceStartPositions( - const IVectorPtr& sequenceStartPositions, - IVectorPtr& subSequenceStartPositions); +void generateSubSequenceStartPositions(const IVectorPtr& sequenceStartPositions, + IVectorPtr& subSequenceStartPositions); void generateSubSequenceStartPositions( const ICpuGpuVectorPtr& sequenceStartPositions, @@ -66,12 +67,10 @@ void generateSubSequenceStartPositions( * @param sequenceStartPositions[in] input * cpuSequenceDims[out] generation output */ -void generateMDimSequenceData( - const IVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims); -void generateMDimSequenceData( - const ICpuGpuVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims); +void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims); +void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims); void checkMatrixEqual(const MatrixPtr& a, const MatrixPtr& b); diff --git a/paddle/gserver/tests/test_ActivationGrad.cpp b/paddle/gserver/tests/test_ActivationGrad.cpp index 2c5d17090d..e54c5109e7 100644 --- a/paddle/gserver/tests/test_ActivationGrad.cpp +++ b/paddle/gserver/tests/test_ActivationGrad.cpp @@ -42,9 +42,9 @@ void testActivation(const string& act) { testLayerGrad(config, act + "_activation", 100, - /* trans= */false, + /* trans= */ false, useGpu, - /* useWeight */true); + /* useWeight */ true); } } diff --git a/paddle/gserver/tests/test_ConvTrans.cpp b/paddle/gserver/tests/test_ConvTrans.cpp index bff7222b29..f3efdfb428 100644 --- a/paddle/gserver/tests/test_ConvTrans.cpp +++ b/paddle/gserver/tests/test_ConvTrans.cpp @@ -36,206 +36,206 @@ P_DECLARE_bool(prev_batch_state); // Test that the convTrans forward is the same as conv backward TEST(Layer, convTransLayerFwd) { - // Setting up conv-trans layer - TestConfig configt; - configt.biasSize = 3; - configt.layerConfig.set_type("exconvt"); - configt.layerConfig.set_num_filters(3); - configt.layerConfig.set_partial_sum(1); - configt.layerConfig.set_shared_biases(true); - - configt.inputDefs.push_back({INPUT_DATA, "layer_0", 1024, 384}); - LayerInputConfig* input = configt.layerConfig.add_inputs(); - ConvConfig* conv = input->mutable_conv_conf(); - conv->set_filter_size(2); - conv->set_filter_size_y(4); - conv->set_channels(16); - conv->set_padding(0); - conv->set_padding_y(1); - conv->set_stride(2); - conv->set_stride_y(2); - conv->set_groups(1); - conv->set_filter_channels(3 / conv->groups()); - conv->set_img_size(16); - conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), - conv->padding(), conv->stride(), - /* caffeMode */ true)); - configt.layerConfig.set_size(conv->img_size() * conv->img_size() * - configt.layerConfig.num_filters()); - configt.layerConfig.set_name("convTrans"); - - // data layer initialize - std::vector dataLayers; - LayerMap layerMap; - vector datas; - initDataLayer(configt, &dataLayers, &datas, &layerMap, "convTrans", - 100, false, false); - // test layer initialize - std::vector parameters; - LayerPtr convtLayer; - initTestLayer(configt, &layerMap, ¶meters, &convtLayer); - convtLayer->getBiasParameter()->zeroMem(); - convtLayer->forward(PASS_GC); - - // Setting up conv-layer config - TestConfig config; - config.biasSize = 16; - config.layerConfig.set_type("exconv"); - config.layerConfig.set_num_filters(16); - config.layerConfig.set_partial_sum(1); - config.layerConfig.set_shared_biases(true); - - config.inputDefs.push_back({INPUT_DATA, "layer_1", 768, 384}); - input = config.layerConfig.add_inputs(); - conv = input->mutable_conv_conf(); - conv->set_filter_size(2); - conv->set_filter_size_y(4); - conv->set_channels(3); - conv->set_padding(0); - conv->set_padding_y(1); - conv->set_stride(2); - conv->set_stride_y(2); - conv->set_groups(1); - conv->set_filter_channels(conv->channels() / conv->groups()); - conv->set_img_size(16); - conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), - conv->padding(), conv->stride(), - /* caffeMode */ true)); - config.layerConfig.set_size(conv->output_x() * conv->output_x() * - config.layerConfig.num_filters()); - config.layerConfig.set_name("conv"); - - // data layer initialize - std::vector dataLayers2; - LayerMap layerMap2; - vector datas2; - initDataLayer(config, &dataLayers2, &datas2, &layerMap2, "conv", - 100, false, false); - // test layer initialize - std::vector parameters2; - LayerPtr convLayer; - initTestLayer(config, &layerMap2, ¶meters2, &convLayer); - - // Sync convLayer and convtLayer parameter - convLayer->getBiasParameter()->zeroMem(); - convLayer->getParameters()[0]->getBuf(PARAMETER_VALUE)->copyFrom( - *(convtLayer->getParameters()[0]->getBuf(PARAMETER_VALUE))); - - // Set convLayer outputGrad as convTransLayer input value - convLayer->forward(PASS_GC); - convLayer->getOutput().grad->copyFrom(*(dataLayers[0]->getOutputValue())); - - vector callbackFlags(parameters2.size(), 0); - auto callback = [&](Parameter* para) { ++callbackFlags[para->getID()]; }; - convLayer->backward(callback); - - // Check that the convLayer backward is the same as convTransLayer forward - checkMatrixEqual(convtLayer->getOutputValue(), - dataLayers2[0]->getOutputGrad()); + // Setting up conv-trans layer + TestConfig configt; + configt.biasSize = 3; + configt.layerConfig.set_type("exconvt"); + configt.layerConfig.set_num_filters(3); + configt.layerConfig.set_partial_sum(1); + configt.layerConfig.set_shared_biases(true); + + configt.inputDefs.push_back({INPUT_DATA, "layer_0", 1024, 384}); + LayerInputConfig* input = configt.layerConfig.add_inputs(); + ConvConfig* conv = input->mutable_conv_conf(); + conv->set_filter_size(2); + conv->set_filter_size_y(4); + conv->set_channels(16); + conv->set_padding(0); + conv->set_padding_y(1); + conv->set_stride(2); + conv->set_stride_y(2); + conv->set_groups(1); + conv->set_filter_channels(3 / conv->groups()); + conv->set_img_size(16); + conv->set_output_x(outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), + /* caffeMode */ true)); + configt.layerConfig.set_size(conv->img_size() * conv->img_size() * + configt.layerConfig.num_filters()); + configt.layerConfig.set_name("convTrans"); + + // data layer initialize + std::vector dataLayers; + LayerMap layerMap; + vector datas; + initDataLayer( + configt, &dataLayers, &datas, &layerMap, "convTrans", 100, false, false); + // test layer initialize + std::vector parameters; + LayerPtr convtLayer; + initTestLayer(configt, &layerMap, ¶meters, &convtLayer); + convtLayer->getBiasParameter()->zeroMem(); + convtLayer->forward(PASS_GC); + + // Setting up conv-layer config + TestConfig config; + config.biasSize = 16; + config.layerConfig.set_type("exconv"); + config.layerConfig.set_num_filters(16); + config.layerConfig.set_partial_sum(1); + config.layerConfig.set_shared_biases(true); + + config.inputDefs.push_back({INPUT_DATA, "layer_1", 768, 384}); + input = config.layerConfig.add_inputs(); + conv = input->mutable_conv_conf(); + conv->set_filter_size(2); + conv->set_filter_size_y(4); + conv->set_channels(3); + conv->set_padding(0); + conv->set_padding_y(1); + conv->set_stride(2); + conv->set_stride_y(2); + conv->set_groups(1); + conv->set_filter_channels(conv->channels() / conv->groups()); + conv->set_img_size(16); + conv->set_output_x(outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), + /* caffeMode */ true)); + config.layerConfig.set_size(conv->output_x() * conv->output_x() * + config.layerConfig.num_filters()); + config.layerConfig.set_name("conv"); + + // data layer initialize + std::vector dataLayers2; + LayerMap layerMap2; + vector datas2; + initDataLayer( + config, &dataLayers2, &datas2, &layerMap2, "conv", 100, false, false); + // test layer initialize + std::vector parameters2; + LayerPtr convLayer; + initTestLayer(config, &layerMap2, ¶meters2, &convLayer); + + // Sync convLayer and convtLayer parameter + convLayer->getBiasParameter()->zeroMem(); + convLayer->getParameters()[0] + ->getBuf(PARAMETER_VALUE) + ->copyFrom(*(convtLayer->getParameters()[0]->getBuf(PARAMETER_VALUE))); + + // Set convLayer outputGrad as convTransLayer input value + convLayer->forward(PASS_GC); + convLayer->getOutput().grad->copyFrom(*(dataLayers[0]->getOutputValue())); + + vector callbackFlags(parameters2.size(), 0); + auto callback = [&](Parameter* para) { ++callbackFlags[para->getID()]; }; + convLayer->backward(callback); + + // Check that the convLayer backward is the same as convTransLayer forward + checkMatrixEqual(convtLayer->getOutputValue(), + dataLayers2[0]->getOutputGrad()); } - // Do one forward pass of convTrans layer and check to see if its output // matches the given result -void doOneConvtTest(size_t imgSize, size_t output_x, size_t stride, - size_t padding, size_t filter_size, MatrixPtr& result) { - TestConfig configt; - configt.biasSize = 1; - configt.layerConfig.set_type("exconvt"); - configt.layerConfig.set_num_filters(1); - configt.layerConfig.set_partial_sum(1); - configt.layerConfig.set_shared_biases(true); - - configt.inputDefs.push_back({INPUT_DATA, "layer_0", output_x * output_x, - filter_size * filter_size}); - LayerInputConfig* input = configt.layerConfig.add_inputs(); - ConvConfig* conv = input->mutable_conv_conf(); - conv->set_filter_size(filter_size); - conv->set_filter_size_y(filter_size); - conv->set_channels(1); - conv->set_padding(padding); - conv->set_padding_y(padding); - conv->set_stride(stride); - conv->set_stride_y(stride); - conv->set_groups(1); - conv->set_filter_channels(1); - conv->set_img_size(imgSize); - conv->set_output_x(output_x); - - configt.layerConfig.set_size(conv->img_size() * conv->img_size() * - configt.layerConfig.num_filters()); - configt.layerConfig.set_name("convTrans"); - - std::vector dataLayers; - LayerMap layerMap; - vector datas; - initDataLayer(configt, &dataLayers, &datas, &layerMap, "convTrans", - 1, false, false); - dataLayers[0]->getOutputValue()->zeroMem(); - dataLayers[0]->getOutputValue()->add(1.0); - - // test layer initialize - std::vector parameters; - LayerPtr convtLayer; - initTestLayer(configt, &layerMap, ¶meters, &convtLayer); - convtLayer->getBiasParameter()->zeroMem(); - convtLayer->getParameters()[0]->zeroMem(); - convtLayer->getParameters()[0]->getBuf(PARAMETER_VALUE)->add(1.0); - convtLayer->forward(PASS_GC); - - checkMatrixEqual(convtLayer->getOutputValue(), result); +void doOneConvtTest(size_t imgSize, + size_t output_x, + size_t stride, + size_t padding, + size_t filter_size, + MatrixPtr& result) { + TestConfig configt; + configt.biasSize = 1; + configt.layerConfig.set_type("exconvt"); + configt.layerConfig.set_num_filters(1); + configt.layerConfig.set_partial_sum(1); + configt.layerConfig.set_shared_biases(true); + + configt.inputDefs.push_back( + {INPUT_DATA, "layer_0", output_x * output_x, filter_size * filter_size}); + LayerInputConfig* input = configt.layerConfig.add_inputs(); + ConvConfig* conv = input->mutable_conv_conf(); + conv->set_filter_size(filter_size); + conv->set_filter_size_y(filter_size); + conv->set_channels(1); + conv->set_padding(padding); + conv->set_padding_y(padding); + conv->set_stride(stride); + conv->set_stride_y(stride); + conv->set_groups(1); + conv->set_filter_channels(1); + conv->set_img_size(imgSize); + conv->set_output_x(output_x); + + configt.layerConfig.set_size(conv->img_size() * conv->img_size() * + configt.layerConfig.num_filters()); + configt.layerConfig.set_name("convTrans"); + + std::vector dataLayers; + LayerMap layerMap; + vector datas; + initDataLayer( + configt, &dataLayers, &datas, &layerMap, "convTrans", 1, false, false); + dataLayers[0]->getOutputValue()->zeroMem(); + dataLayers[0]->getOutputValue()->add(1.0); + + // test layer initialize + std::vector parameters; + LayerPtr convtLayer; + initTestLayer(configt, &layerMap, ¶meters, &convtLayer); + convtLayer->getBiasParameter()->zeroMem(); + convtLayer->getParameters()[0]->zeroMem(); + convtLayer->getParameters()[0]->getBuf(PARAMETER_VALUE)->add(1.0); + convtLayer->forward(PASS_GC); + + checkMatrixEqual(convtLayer->getOutputValue(), result); } TEST(Layer, convTransLayerFwd2) { - MatrixPtr result; - result = Matrix::create(1, 5 * 5, false, false); - result->zeroMem(); - result->add(1.0); - doOneConvtTest(/* imgSize */ 5, - /* output_x */ 1, - /* stride */ 1, - /* padding */ 0, - /* filter_size */ 5, - result); - - float resultData[] = {1, 2, 2, 2, 1, - 2, 4, 4, 4, 2, - 2, 4, 4, 4, 2, - 2, 4, 4, 4, 2, - 1, 2, 2, 2, 1}; - result->setData(resultData); - doOneConvtTest(/* imgSize */ 5, - /* output_x */ 2, - /* stride */ 1, - /* padding */ 0, - /* filter_size */ 4, - result); - - float resultData2[] = {1, 2, 2, 2, 1, - 2, 4, 4, 4, 2, - 2, 4, 4, 4, 2, - 2, 4, 4, 4, 2, - 1, 2, 2, 2, 1}; - result->setData(resultData2); - doOneConvtTest(/* imgSize */ 5, - /* output_x */ 2, - /* stride */ 2, - /* padding */ 1, - /* filter_size */ 5, - result); - - float resultData3[] = {1, 1, 2, 1, 1, - 1, 1, 2, 1, 1, - 2, 2, 4, 2, 2, - 1, 1, 2, 1, 1, - 1, 1, 2, 1, 1}; - result->setData(resultData3); - doOneConvtTest(/* imgSize */ 5, - /* output_x */ 2, - /* stride */ 2, - /* padding */ 0, - /* filter_size */ 3, - result);} + MatrixPtr result; + result = Matrix::create(1, 5 * 5, false, false); + result->zeroMem(); + result->add(1.0); + doOneConvtTest(/* imgSize */ 5, + /* output_x */ 1, + /* stride */ 1, + /* padding */ 0, + /* filter_size */ 5, + result); + + float resultData[] = {1, 2, 2, 2, 1, 2, 4, 4, 4, 2, 2, 4, 4, + 4, 2, 2, 4, 4, 4, 2, 1, 2, 2, 2, 1}; + result->setData(resultData); + doOneConvtTest(/* imgSize */ 5, + /* output_x */ 2, + /* stride */ 1, + /* padding */ 0, + /* filter_size */ 4, + result); + + float resultData2[] = {1, 2, 2, 2, 1, 2, 4, 4, 4, 2, 2, 4, 4, + 4, 2, 2, 4, 4, 4, 2, 1, 2, 2, 2, 1}; + result->setData(resultData2); + doOneConvtTest(/* imgSize */ 5, + /* output_x */ 2, + /* stride */ 2, + /* padding */ 1, + /* filter_size */ 5, + result); + + float resultData3[] = {1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 4, + 2, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1}; + result->setData(resultData3); + doOneConvtTest(/* imgSize */ 5, + /* output_x */ 2, + /* stride */ 2, + /* padding */ 0, + /* filter_size */ 3, + result); +} int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); diff --git a/paddle/gserver/tests/test_Evaluator.cpp b/paddle/gserver/tests/test_Evaluator.cpp index 3a591a316b..be639ea093 100644 --- a/paddle/gserver/tests/test_Evaluator.cpp +++ b/paddle/gserver/tests/test_Evaluator.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "ModelConfig.pb.h" @@ -48,8 +47,10 @@ struct TestConfig { TestConfig() : testAccumulate(true) {} }; -void testEvaluator(TestConfig testConf, string testEvaluatorName, - size_t batchSize, bool useGpu) { +void testEvaluator(TestConfig testConf, + string testEvaluatorName, + size_t batchSize, + bool useGpu) { #ifdef PADDLE_ONLY_CPU if (useGpu) return; #endif @@ -79,8 +80,10 @@ void testEvaluator(TestConfig testConf, string testEvaluatorName, data.ids->rand(dim); // now rand number can be 0 to inputDefs[i].dim. break; case INPUT_SPARSE_NON_VALUE_DATA: - data.value = makeRandomSparseMatrix(batchSize, dim, - /* withValue= */ false, useGpu); + data.value = makeRandomSparseMatrix(batchSize, + dim, + /* withValue= */ false, + useGpu); break; default: LOG(FATAL) << " unknown inputType "; @@ -116,8 +119,9 @@ void testEvaluator(TestConfig testConf, string testEvaluatorName, } } -void testEvaluatorAll(TestConfig testConf, string testEvaluatorName, - size_t batchSize) { +void testEvaluatorAll(TestConfig testConf, + string testEvaluatorName, + size_t batchSize) { testEvaluator(testConf, testEvaluatorName, batchSize, true); testEvaluator(testConf, testEvaluatorName, batchSize, false); } @@ -142,8 +146,8 @@ TEST(Evaluator, classification_error) { config.evaluatorConfig.set_classification_threshold(0.4); config.inputDefs.push_back({INPUT_DATA, "weight", 1}); // Not support GPU - testEvaluator(config, "classification_error_weight_multi_binary_label", 50, - false); + testEvaluator( + config, "classification_error_weight_multi_binary_label", 50, false); } TEST(Evaluator, sum) { @@ -211,8 +215,8 @@ TEST(Evaluator, precision_recall) { config.evaluatorConfig.set_classification_threshold(0.4); config.inputDefs.push_back({INPUT_DATA, "weight", 1}); // Not support GPU - testEvaluator(config, "precision_recall_weight_multi_binary_label", 100, - false); + testEvaluator( + config, "precision_recall_weight_multi_binary_label", 100, false); } TEST(Evaluator, ctc_error_evaluator) { diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index a79dfe39c9..374ae57dd3 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -69,8 +69,10 @@ TEST(Projection, context) { std::max(0, conf.context_start() + conf.context_length() - 1); for (auto useGpu : {false, true}) { testProjectionGrad( - conf, INPUT_SEQUENCE_DATA, - trainablePadding ? conf.input_size() * pad : 0, batchSize, + conf, + INPUT_SEQUENCE_DATA, + trainablePadding ? conf.input_size() * pad : 0, + batchSize, useGpu, contextStart + contextLength <= 1); // = testState } @@ -86,8 +88,11 @@ TEST(Projection, trans_fc) { conf.set_input_size(50); conf.set_output_size(20); for (auto useGpu : {false, true}) { - testProjectionGrad(conf, INPUT_DATA, /* parameterSize */ 1000, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_DATA, + /* parameterSize */ 1000, + /* batchSize */ 100, + useGpu); } } @@ -97,8 +102,11 @@ TEST(Projection, fc) { conf.set_input_size(10); conf.set_output_size(20); for (auto useGpu : {false, true}) { - testProjectionGrad(conf, INPUT_DATA, /* parameterSize */ 200, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_DATA, + /* parameterSize */ 200, + /* batchSize */ 100, + useGpu); } } @@ -108,8 +116,11 @@ TEST(Projection, dot_mul) { conf.set_input_size(20); conf.set_output_size(20); for (auto useGpu : {false, true}) { - testProjectionGrad(conf, INPUT_DATA, /* parameterSize */ 20, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_DATA, + /* parameterSize */ 20, + /* batchSize */ 100, + useGpu); } } @@ -119,8 +130,11 @@ TEST(Projection, table) { conf.set_input_size(10); conf.set_output_size(20); for (auto useGpu : {false, true}) { - testProjectionGrad(conf, INPUT_LABEL, /* parameterSize */ 200, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_LABEL, + /* parameterSize */ 200, + /* batchSize */ 100, + useGpu); } } @@ -130,8 +144,11 @@ TEST(Projection, identity) { conf.set_input_size(10); conf.set_output_size(10); for (auto useGpu : {false, true}) { - testProjectionGrad(conf, INPUT_DATA, /* parameterSize */ 0, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_DATA, + /* parameterSize */ 0, + /* batchSize */ 100, + useGpu); } } @@ -141,8 +158,11 @@ TEST(Projection, scaling) { conf.set_input_size(10); conf.set_output_size(10); for (auto useGpu : {false}) { - testProjectionGrad(conf, INPUT_DATA, /* parameterSize */ 1, - /* batchSize */ 100, useGpu); + testProjectionGrad(conf, + INPUT_DATA, + /* parameterSize */ 1, + /* batchSize */ 100, + useGpu); } } @@ -169,20 +189,29 @@ TEST(Projection, conv) { conv->set_groups(1); conv->set_filter_channels(conv->channels() / conv->groups()); conv->set_img_size(IMAGE_SIZE); - int output_x = - outputSize(conv->img_size(), conv->filter_size(), conv->padding(), - conv->stride(), /* caffeMode */ true); - int output_y = - outputSize(conv->img_size(), conv->filter_size_y(), conv->padding_y(), - conv->stride_y(), /* caffeMode */ true); + int output_x = outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), + /* caffeMode */ true); + int output_y = outputSize(conv->img_size(), + conv->filter_size_y(), + conv->padding_y(), + conv->stride_y(), + /* caffeMode */ true); conv->set_output_x(output_x); conf.set_input_size(IMAGE_SIZE * IMAGE_SIZE * CHANNELS); conf.set_output_size(output_x * output_y * NUM_FILTERS); testProjectionGrad( - conf, INPUT_DATA, + conf, + INPUT_DATA, /* parameterSize */ NUM_FILTERS * CHANNELS * FILTER_SIZE * FILTER_SIZE_Y, - /* batchSize */ 100, true, false, NUM_FILTERS, true); + /* batchSize */ 100, + true, + false, + NUM_FILTERS, + true); } #endif @@ -253,8 +282,13 @@ TEST(Layer, CRFLayer) { config.layerConfig.add_inputs(); // Not support GPU now - testLayerGrad(config, "crf", 100, /* trans */ false, /* useGpu */ false, - false /*useWeight*/, 0.03 /*epsilon*/); + testLayerGrad(config, + "crf", + 100, + /* trans */ false, + /* useGpu */ false, + false /*useWeight*/, + 0.03 /*epsilon*/); } TEST(Layer, CTCLayer) { @@ -327,8 +361,10 @@ void testConvLayer(const string& type, bool trans, bool useGpu) { conv->set_groups(1); conv->set_filter_channels(conv->channels() / conv->groups()); conv->set_img_size(16); - conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), - conv->padding(), conv->stride(), + conv->set_output_x(outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), /* caffeMode */ true)); config.layerConfig.set_size(conv->output_x() * conv->output_x() * config.layerConfig.num_filters()); @@ -346,7 +382,6 @@ TEST(Layer, convLayer) { #endif } - void testConvTransLayer(const string& type, bool trans, bool useGpu) { TestConfig config; config.biasSize = 3; @@ -368,8 +403,10 @@ void testConvTransLayer(const string& type, bool trans, bool useGpu) { conv->set_groups(1); conv->set_filter_channels(3 / conv->groups()); conv->set_img_size(16); - conv->set_output_x(outputSize(conv->img_size(), conv->filter_size(), - conv->padding(), conv->stride(), + conv->set_output_x(outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), /* caffeMode */ true)); config.layerConfig.set_size(conv->img_size() * conv->img_size() * @@ -403,14 +440,16 @@ TEST(Layer, blockExpandLayer) { blockExpand->set_block_y(32); blockExpand->set_stride_x(2); blockExpand->set_stride_y(2); - blockExpand->set_output_x( - outputSize(blockExpand->img_size_x(), blockExpand->block_x(), - blockExpand->padding_x(), blockExpand->stride_x(), - /* caffeMode */ false)); - blockExpand->set_output_y( - outputSize(blockExpand->img_size_y(), blockExpand->block_y(), - blockExpand->padding_y(), blockExpand->stride_y(), - /* caffeMode */ false)); + blockExpand->set_output_x(outputSize(blockExpand->img_size_x(), + blockExpand->block_x(), + blockExpand->padding_x(), + blockExpand->stride_x(), + /* caffeMode */ false)); + blockExpand->set_output_y(outputSize(blockExpand->img_size_y(), + blockExpand->block_y(), + blockExpand->padding_y(), + blockExpand->stride_y(), + /* caffeMode */ false)); config.layerConfig.set_size(blockExpand->block_x() * blockExpand->block_y() * blockExpand->channels()); @@ -453,7 +492,11 @@ void testFcLayer(string format, size_t nnz) { << config.inputDefs[0].sparse.format; for (auto useGpu : {false, true}) { - testLayerGrad(config, "fc", 100, /* trans */ false, useGpu, + testLayerGrad(config, + "fc", + 100, + /* trans */ false, + useGpu, /* weight */ true); } } @@ -481,11 +524,19 @@ TEST(Layer, SelectiveFullyConnectedLayer) { {INPUT_SPARSE_NON_VALUE_DATA, "index", nout, 0, ParaSparse("csr", true)}); config.layerConfig.add_inputs(); - testLayerGrad(config, "selective_fc", 100, - /* trans= */ false, /* useGup= */ false, false); + testLayerGrad(config, + "selective_fc", + 100, + /* trans= */ false, + /* useGup= */ false, + false); #ifndef PADDLE_ONLY_CPU - testLayerGrad(config, "selective_fc", 100, - /* trans= */ false, /* useGup= */ true, false); + testLayerGrad(config, + "selective_fc", + 100, + /* trans= */ false, + /* useGup= */ true, + false); #endif } @@ -502,7 +553,10 @@ TEST(Layer, DataNormLayer) { for (auto strategy : {"z-score", "min-max", "decimal-scaling"}) { config.layerConfig.set_data_norm_strategy(strategy); // The parameters are static, so not support GPU now - testLayerGrad(config, "data_norm", 200, /* trans */ false, + testLayerGrad(config, + "data_norm", + 200, + /* trans */ false, /* useGpu */ false); } } @@ -534,8 +588,8 @@ TEST(Layer, multi_cross) { config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "multi-class-cross-entropy", 100, /* trans */ false, - useGpu); + testLayerGrad( + config, "multi-class-cross-entropy", 100, /* trans */ false, useGpu); } } @@ -550,8 +604,11 @@ TEST(Layer, multi_binary_label_sparse_mat) { config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "multi_binary_label_cross_entropy", 100, - /* trans */ false, useGpu); + testLayerGrad(config, + "multi_binary_label_cross_entropy", + 100, + /* trans */ false, + useGpu); } } @@ -566,8 +623,11 @@ TEST(layer, multi_binary_label_id) { config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "multi_binary_label_cross_entropy", 100, - /* trans */ false, useGpu); + testLayerGrad(config, + "multi_binary_label_cross_entropy", + 100, + /* trans */ false, + useGpu); } } @@ -583,7 +643,9 @@ TEST(Layer, multi_cross_with_selfnorm) { config.layerConfig.add_inputs(); // Not support GPU now - testLayerGrad(config, "multi_class_cross_entropy_with_selfnorm", 100, + testLayerGrad(config, + "multi_class_cross_entropy_with_selfnorm", + 100, /* trans */ false, /* useGpu */ false); } @@ -599,8 +661,11 @@ TEST(Layer, multi_cross_soft) { config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "soft_binary_class_cross_entropy", 100, - /* trans */ false, useGpu); + testLayerGrad(config, + "soft_binary_class_cross_entropy", + 100, + /* trans */ false, + useGpu); } } @@ -630,7 +695,10 @@ TEST(Layer, sparse_square_error) { config.layerConfig.add_inputs(); // "GpuSparseMatrix" as label is not supported - testLayerGrad(config, "square_error", 100, /* trans */ false, + testLayerGrad(config, + "square_error", + 100, + /* trans */ false, /* useGpu */ false); } @@ -645,7 +713,10 @@ TEST(Layer, sparse_float_square_error) { config.layerConfig.add_inputs(); // "GpuSparseMatrix" as label is not supported - testLayerGrad(config, "square_error", 100, /* trans */ false, + testLayerGrad(config, + "square_error", + 100, + /* trans */ false, /* useGpu */ false); } @@ -688,10 +759,14 @@ void testExpandLayer(string trans_type, bool hasSubseq) { config.inputDefs.push_back( {trans_type == "non-seq" ? INPUT_DENSE_DIM_DATA : INPUT_SEQUENCE_DATA, - "layer_0", 10, 0}); + "layer_0", + 10, + 0}); config.inputDefs.push_back( - {hasSubseq ? INPUT_HASSUB_SEQUENCE_DATA : INPUT_SEQUENCE_DATA, "layer_1", - 10, 0}); + {hasSubseq ? INPUT_HASSUB_SEQUENCE_DATA : INPUT_SEQUENCE_DATA, + "layer_1", + 10, + 0}); config.layerConfig.add_inputs(); config.layerConfig.add_inputs(); config.layerConfig.set_trans_type(trans_type); @@ -715,8 +790,10 @@ void testDegradeLayer(bool hasSubseq, string layer_type, string trans_type) { config.biasSize = 0; config.inputDefs.push_back( - {hasSubseq ? INPUT_HASSUB_SEQUENCE_DATA : INPUT_SEQUENCE_DATA, "layer_0", - 10, 0}); + {hasSubseq ? INPUT_HASSUB_SEQUENCE_DATA : INPUT_SEQUENCE_DATA, + "layer_0", + 10, + 0}); config.layerConfig.add_inputs(); config.layerConfig.set_trans_type(trans_type); @@ -746,9 +823,11 @@ TEST(Layer, MaxLayer) { } TEST(Layer, SequenceLastInstanceLayer) { - testDegradeLayer(false, "seqlastins", + testDegradeLayer(false, + "seqlastins", "non-seq"); // seq seqlastins to non-seq - testDegradeLayer(true, "seqlastins", + testDegradeLayer(true, + "seqlastins", "non-seq"); // hasSubseq seqlastins to non-seq testDegradeLayer(true, "seqlastins", "seq"); // hasSubseq seqlastins to seq } @@ -933,7 +1012,8 @@ TEST(Layer, NormLayer) { } #endif -void setPoolConfig(TestConfig* config, PoolConfig* pool, +void setPoolConfig(TestConfig* config, + PoolConfig* pool, const string& poolType) { (*config).biasSize = 0; (*config).layerConfig.set_type("pool"); @@ -1009,7 +1089,9 @@ TEST(Layer, PoolLayer) { #endif } -void testSppLayer(const string& poolType, const int pyramidHeight, bool trans, +void testSppLayer(const string& poolType, + const int pyramidHeight, + bool trans, bool useGpu) { TestConfig config; config.layerConfig.set_type("spp"); @@ -1232,7 +1314,8 @@ TEST(Layer, NCELayer) { for (auto isIdLabel : {false, true}) { config.inputDefs[1] = { - isIdLabel ? INPUT_LABEL : INPUT_SPARSE_NON_VALUE_DATA, "label", + isIdLabel ? INPUT_LABEL : INPUT_SPARSE_NON_VALUE_DATA, + "label", /* dim= */ numClasses, /* paraSize= */ 0}; @@ -1254,7 +1337,10 @@ TEST(Layer, NCELayer) { << " isIdLabel=" << isIdLabel << " withWeight=" << withWeight << " withDist=" << withDist; // Not support GPU now - testLayerGrad(config, "nce", 100, /* trans= */ false, + testLayerGrad(config, + "nce", + 100, + /* trans= */ false, /* useGpu */ false); } } @@ -1332,7 +1418,8 @@ void testBatchNormLayer(const string& type, bool trans, bool useGpu) { config.layerConfig.set_size(CHANNELS * IMG_SIZE * IMG_SIZE); config.layerConfig.set_active_type("sigmoid"); config.biasSize = CHANNELS; - config.inputDefs.push_back({INPUT_DATA, "layer_0", + config.inputDefs.push_back({INPUT_DATA, + "layer_0", /* dim= */ IMG_SIZE * IMG_SIZE * CHANNELS, /* paraSize= */ CHANNELS}); @@ -1349,7 +1436,11 @@ void testBatchNormLayer(const string& type, bool trans, bool useGpu) { img_conf->set_channels(CHANNELS); img_conf->set_img_size(IMG_SIZE); - testLayerGrad(config, "batch_norm", 64, /* trans= */ trans, useGpu, + testLayerGrad(config, + "batch_norm", + 64, + /* trans= */ trans, + useGpu, /* useWeight */ true); } @@ -1384,9 +1475,11 @@ TEST(Operator, conv) { conv->set_groups(1); conv->set_filter_channels(conv->channels() / conv->groups()); conv->set_img_size(IMAGE_SIZE); - int output_x = - outputSize(conv->img_size(), conv->filter_size(), conv->padding(), - conv->stride(), /* caffeMode */ true); + int output_x = outputSize(conv->img_size(), + conv->filter_size(), + conv->padding(), + conv->stride(), + /* caffeMode */ true); conv->set_output_x(output_x); config.layerConfig.set_size(output_x * output_x * config.layerConfig.num_filters()); @@ -1396,8 +1489,10 @@ TEST(Operator, conv) { config.inputDefs.push_back( {INPUT_DATA, "layer_0", IMAGE_SIZE * IMAGE_SIZE * CHANNELS, 0}); config.inputDefs.push_back( - {INPUT_DATA, "layer_1", - FILTER_SIZE * FILTER_SIZE_Y * CHANNELS * NUM_FILTERS, 0}); + {INPUT_DATA, + "layer_1", + FILTER_SIZE * FILTER_SIZE_Y * CHANNELS * NUM_FILTERS, + 0}); config.layerConfig.add_inputs(); config.layerConfig.add_inputs(); @@ -1411,12 +1506,17 @@ TEST(Layer, FeatureMapExpandLayer) { const int INPUT_SIZE = 100; config.layerConfig.set_size(INPUT_SIZE * CHANNELS); config.layerConfig.set_num_filters(CHANNELS); - config.inputDefs.push_back({INPUT_SEQUENCE_DATA, "layer_0", - /* dim= */ INPUT_SIZE, /* paraSize= */ 0}); + config.inputDefs.push_back({INPUT_SEQUENCE_DATA, + "layer_0", + /* dim= */ INPUT_SIZE, + /* paraSize= */ 0}); config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "featmap_expand", - /*batch_size*/ 100, /* trans= */ false, useGpu, + testLayerGrad(config, + "featmap_expand", + /*batch_size*/ 100, + /* trans= */ false, + useGpu, /* useWeight */ true); } } diff --git a/paddle/gserver/tests/test_LinearChainCRF.cpp b/paddle/gserver/tests/test_LinearChainCRF.cpp index f45e40c8b6..913d6ed751 100644 --- a/paddle/gserver/tests/test_LinearChainCRF.cpp +++ b/paddle/gserver/tests/test_LinearChainCRF.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "paddle/gserver/layers/LinearChainCRF.h" diff --git a/paddle/gserver/tests/test_MultinomialSampler.cpp b/paddle/gserver/tests/test_MultinomialSampler.cpp index 73b4d0b8b7..3fc099adbd 100644 --- a/paddle/gserver/tests/test_MultinomialSampler.cpp +++ b/paddle/gserver/tests/test_MultinomialSampler.cpp @@ -12,7 +12,6 @@ WITHOUT 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 @@ -43,7 +42,7 @@ TEST(MultinomialSampler, gen) { int size = 1024 * 4; default_random_engine reng; - for (size_t iter=0; iter < 256; ++iter) { + for (size_t iter = 0; iter < 256; ++iter) { uniform_int_distribution rand(1, numGrids / size * 1.8); vector prob; int sum = 0; @@ -138,7 +137,6 @@ void benchmarkRandom() { LOG(INFO) << "sum1=" << sum1; } - int main(int argc, char** argv) { initMain(argc, argv); testing::InitGoogleTest(&argc, argv); diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index 8d3eac5aca..1810bc31fc 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -41,7 +41,8 @@ struct DataOut { std::vector paraGrads; }; -void initArgument(DataIn& data, const std::string& configPath, +void initArgument(DataIn& data, + const std::string& configPath, bool useGpu = FLAGS_use_gpu) { TrainerConfigHelper config(configPath); size_t batchSize = config.getOptConfig().batch_size(); @@ -122,9 +123,10 @@ void calcGradient(DataIn& in, DataOut& out, const std::string& configPath) { } gradientMachine->backward(); for (size_t i = 0; i < in.outGrads.size(); i++) { - MatrixPtr value = - Matrix::create(outArgs[i].value->getHeight(), - outArgs[i].value->getWidth(), false, false); + MatrixPtr value = Matrix::create(outArgs[i].value->getHeight(), + outArgs[i].value->getWidth(), + false, + false); value->copyFrom(*outArgs[i].value); out.outValues.push_back(value); } @@ -147,8 +149,12 @@ void calcGradient(DataIn& in, DataOut& out, const std::string& configPath) { gradientMachine->finish(); } -void checkBuffer(real* A, const char* desA, real* B, const char* desB, - size_t len, size_t width = 1) { +void checkBuffer(real* A, + const char* desA, + real* B, + const char* desB, + size_t len, + size_t width = 1) { int nNum = 0; for (size_t i = 0; i < len; ++i) { real diff = fabs(A[i] - B[i]); @@ -168,8 +174,10 @@ void compareGradient(DataOut& outA, DataOut& outB) { << "------------------------------"; for (size_t i = 0; i < outA.outValues.size(); ++i) { LOG(INFO) << "OUTPUT VALUE: " << i; - checkBuffer(outA.outValues[i]->getData(), "network A output", - outB.outValues[i]->getData(), "network B output", + checkBuffer(outA.outValues[i]->getData(), + "network A output", + outB.outValues[i]->getData(), + "network B output", outA.outValues[i]->getElementCnt(), outA.outValues[i]->getWidth()); } @@ -180,8 +188,10 @@ void compareGradient(DataOut& outA, DataOut& outB) { << "------------------------------"; for (size_t i = 0; i < outA.paraGrads.size(); ++i) { LOG(INFO) << "PARAMETER GRADIENT: " << i; - checkBuffer(outA.paraGrads[i]->getData(), "Network A", - outB.paraGrads[i]->getData(), "Network B", + checkBuffer(outA.paraGrads[i]->getData(), + "Network A", + outB.paraGrads[i]->getData(), + "Network B", outA.paraGrads[i]->getSize()); } } @@ -247,7 +257,6 @@ TEST(Compare, img_conv) { } #endif - P_DEFINE_string(config_file_a, "", "config of one network to compare"); P_DEFINE_string(config_file_b, "", "config of another network to compare"); TEST(Compare, network) { diff --git a/paddle/gserver/tests/test_ProtoDataProvider.cpp b/paddle/gserver/tests/test_ProtoDataProvider.cpp index 68f7f43261..01070bc1cb 100644 --- a/paddle/gserver/tests/test_ProtoDataProvider.cpp +++ b/paddle/gserver/tests/test_ProtoDataProvider.cpp @@ -12,7 +12,6 @@ WITHOUT 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 @@ -41,7 +40,9 @@ const int kSpraseMatrixDim = 1024; using namespace paddle; // NOLINT -void prepareData(DataBatch* batch, const int* numPerSlotType, bool iid, +void prepareData(DataBatch* batch, + const int* numPerSlotType, + bool iid, bool useGpu) { batch->clear(); int64_t size = uniformRandom(100) + 10; @@ -137,7 +138,7 @@ inline int getSlotDim(const Argument& arg) { inline SlotDef::SlotType getSlotType(const Argument& arg) { if (arg.value) { - auto & m = *arg.value; + auto& m = *arg.value; auto& type = typeid(m); if (type == typeid(CpuMatrix) || type == typeid(GpuMatrix)) { return SlotDef::VECTOR_DENSE; @@ -169,8 +170,12 @@ inline SlotDef::SlotType getSlotType(const Argument& arg) { return SlotDef::VECTOR_DENSE; } -void getColRow(const Argument& arg, int64_t pos, bool useGpu, int* colNum, - const int** rowCols, const real** rowValues) { +void getColRow(const Argument& arg, + int64_t pos, + bool useGpu, + int* colNum, + const int** rowCols, + const real** rowValues) { SlotDef::SlotType type = getSlotType(arg); GpuSparseMatrixPtr matGpu; CpuSparseMatrixPtr matCpu; @@ -190,8 +195,11 @@ void getColRow(const Argument& arg, int64_t pos, bool useGpu, int* colNum, } } -void makeSample(const vector& arguments, int64_t pos, - bool isBeginning, DataSample* sample, bool useGpu) { +void makeSample(const vector& arguments, + int64_t pos, + bool isBeginning, + DataSample* sample, + bool useGpu) { sample->set_is_beginning(isBeginning); int slotid = 0; for (auto& arg : arguments) { @@ -272,8 +280,7 @@ void writeData(const DataBatch& batch, bool useGpu, bool dataCompression) { int64_t totalSeqs = batch.getNumSequences(); int64_t seq = 0; - ICpuGpuVectorPtr sequenceStartPositions = - arguments[0].sequenceStartPositions; + ICpuGpuVectorPtr sequenceStartPositions = arguments[0].sequenceStartPositions; int64_t numWritten = 0; vector curProtoFiles = dataCompression ? protoFilesCompressed : protoFiles; @@ -306,8 +313,11 @@ void writeData(const DataBatch& batch, bool useGpu, bool dataCompression) { } // check that the sample at pos1 in args1 is same as the sample at pos2 in args2 -void checkSample(const vector& args1, int64_t pos1, - const vector& args2, int64_t pos2, bool useGpu) { +void checkSample(const vector& args1, + int64_t pos1, + const vector& args2, + int64_t pos2, + bool useGpu) { EXPECT_EQ(args1.size(), args2.size()); VLOG(1) << " pos1=" << pos1 << " pos2=" << pos2; @@ -361,8 +371,11 @@ void checkSample(const vector& args1, int64_t pos1, } } -void testProtoDataProvider(int* numPerSlotType, bool iid, bool async, - bool useGpu, bool dataCompression, +void testProtoDataProvider(int* numPerSlotType, + bool iid, + bool async, + bool useGpu, + bool dataCompression, int numConstantSlots = 0) { mkDir(kTestDir); DataBatch data; @@ -377,7 +390,9 @@ void testProtoDataProvider(int* numPerSlotType, bool iid, bool async, for (int i = 0; i < numConstantSlots; ++i) { config.add_constant_slots(i + 11); - MatrixPtr w = Matrix::create(data.getSize(), 1, /* trans= */ false, + MatrixPtr w = Matrix::create(data.getSize(), + 1, + /* trans= */ false, /* useGpu= */ false); w->assign(config.constant_slots(i)); data.appendData(w); @@ -393,16 +408,14 @@ void testProtoDataProvider(int* numPerSlotType, bool iid, bool async, size_t seq1 = 0; vector& args1 = data.getStreams(); - ICpuGpuVectorPtr sequenceStartPositions1 = - args1[0].sequenceStartPositions; + ICpuGpuVectorPtr sequenceStartPositions1 = args1[0].sequenceStartPositions; dataProvider->reset(); while (dataProvider->getNextBatch(batchSize, &batch) > 0) { CHECK_EQ(data.getNumStreams(), batch.getNumStreams()); vector& args2 = batch.getStreams(); - ICpuGpuVectorPtr sequenceStartPositions2 = - args2[0].sequenceStartPositions; + ICpuGpuVectorPtr sequenceStartPositions2 = args2[0].sequenceStartPositions; for (auto& arg : args2) { EXPECT_EQ(iid, !arg.sequenceStartPositions); } @@ -494,8 +507,8 @@ TEST(ProtoDataProvider, test) { numSparseValueVectorSlots; numPerSlotType[SlotDef::INDEX] = numIdSlots; numPerSlotType[SlotDef::STRING] = numStrSlots; - testProtoDataProvider(numPerSlotType, iid, async, useGpu, - dataCompression); + testProtoDataProvider( + numPerSlotType, iid, async, useGpu, dataCompression); } // end for (int dataCompression : numTwoArray) } // end for (int useGpu : numTwoArray) } // end for (int async : numTwoArray) @@ -531,7 +544,9 @@ TEST(ProtoDataProvider, constant_slots) { numPerSlotType[SlotDef::INDEX] = 1; testProtoDataProvider(numPerSlotType, /* iid= */ true, - /* async= */ false, useGpu, dataCompression, + /* async= */ false, + useGpu, + dataCompression, numConstantSlots); } // end for (int dataCompression : numTwoArray) } // end for (int useGpu : numTwoArray) @@ -541,16 +556,17 @@ TEST(ProtoDataProvider, constant_slots) { } void checkSampleSequence(const vector& args1, - const vector& args2, int64_t offset, - int64_t numSeqs, bool useGpu) { + const vector& args2, + int64_t offset, + int64_t numSeqs, + bool useGpu) { // check slot num are equal EXPECT_EQ(args1.size(), args2.size()); for (size_t i = 0; i < args1.size(); i++) { auto type = getSlotType(args1[i]); // check for args2: sequenceStartPositions vs numSeqs // (1) size - EXPECT_EQ(args2[i].sequenceStartPositions->getSize(), - (size_t)numSeqs + 1); + EXPECT_EQ(args2[i].sequenceStartPositions->getSize(), (size_t)numSeqs + 1); // (2) content auto checkArgContent = [&](const Argument& args, int numSeqs) { for (int j = 0; j <= numSeqs; j++) { @@ -579,8 +595,8 @@ void checkSampleSequence(const vector& args1, const real* rowValues1; // nullptr int totalLength = 0; for (int j = 0; j < numSeqs; j++) { - getColRow(args1[i], offset + j, useGpu, &colNum1, &rowCols1, - &rowValues1); + getColRow( + args1[i], offset + j, useGpu, &colNum1, &rowCols1, &rowValues1); // (1) lengths EXPECT_EQ(totalLength, args2[i].sequenceStartPositions->getElement(j)); @@ -626,13 +642,16 @@ void checkSampleSequence(const vector& args1, } } -void testProtoSequenceDataProvider(int* numPerSlotType, bool async, +void testProtoSequenceDataProvider(int* numPerSlotType, + bool async, bool useGpu) { mkDir(kTestDir); DataBatch data; - prepareData(&data, numPerSlotType, - /* iid */ true, useGpu); + prepareData(&data, + numPerSlotType, + /* iid */ true, + useGpu); writeData(data, useGpu, /* dataCompression */ false); DataConfig config; @@ -649,8 +668,7 @@ void testProtoSequenceDataProvider(int* numPerSlotType, bool async, DataBatch batch; vector& args1 = data.getStreams(); - ICpuGpuVectorPtr sequenceStartPositions1 = - args1[0].sequenceStartPositions; + ICpuGpuVectorPtr sequenceStartPositions1 = args1[0].sequenceStartPositions; dataProvider->reset(); @@ -658,8 +676,7 @@ void testProtoSequenceDataProvider(int* numPerSlotType, bool async, while (dataProvider->getNextBatch(batchSize, &batch) > 0) { CHECK_EQ(data.getNumStreams(), batch.getNumStreams()); vector& args2 = batch.getStreams(); - ICpuGpuVectorPtr sequenceStartPositions2 = - args2[0].sequenceStartPositions; + ICpuGpuVectorPtr sequenceStartPositions2 = args2[0].sequenceStartPositions; for (auto& arg : args1) { // args1 should not has sequence EXPECT_EQ(true, !arg.sequenceStartPositions); diff --git a/paddle/gserver/tests/test_PyDataProvider.cpp b/paddle/gserver/tests/test_PyDataProvider.cpp index 6ad45e3a65..802f9aa4cb 100644 --- a/paddle/gserver/tests/test_PyDataProvider.cpp +++ b/paddle/gserver/tests/test_PyDataProvider.cpp @@ -12,7 +12,6 @@ WITHOUT 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 @@ -114,9 +113,10 @@ void simpleValueCheck(const vector& argumentList, bool useGpu) { // Dense real* data; if (useGpu) { - MatrixPtr cpuMatrixPtr = - Matrix::create(argumentList[0].value->getHeight(), - argumentList[0].value->getWidth(), 0, 0); + MatrixPtr cpuMatrixPtr = Matrix::create(argumentList[0].value->getHeight(), + argumentList[0].value->getWidth(), + 0, + 0); cpuMatrixPtr->copyFrom(*argumentList[0].value); data = cpuMatrixPtr->getData(); } else { diff --git a/paddle/gserver/tests/test_PyDataProvider2.cpp b/paddle/gserver/tests/test_PyDataProvider2.cpp index b9867a728d..24aa73910f 100644 --- a/paddle/gserver/tests/test_PyDataProvider2.cpp +++ b/paddle/gserver/tests/test_PyDataProvider2.cpp @@ -31,14 +31,11 @@ extern void clearOnPoolFilledHook(); } // namespace unittest } // namespace paddle - const paddle::real epsilon = 1e-5; -static inline int64_t readDataBatch( - paddle::DataBatch* batch, - const std::string& funcName, - int64_t batchSize = 65535) { - +static inline int64_t readDataBatch(paddle::DataBatch* batch, + const std::string& funcName, + int64_t batchSize = 65535) { paddle::DataConfig config; config.set_type("py2"); config.set_files(FLAGS_train_list.c_str()); @@ -64,18 +61,19 @@ TEST(PyDataProvider2, dense_no_seq) { provider->setSkipShuffle(); // skip shuffle for unittest. paddle::DataBatch batch; - for (size_t pass=0; pass < 2; ++pass) { // read 2 passes + for (size_t pass = 0; pass < 2; ++pass) { // read 2 passes provider->reset(); int64_t num = provider->getNextBatchInternal(100, &batch); ASSERT_NE(num, 0); ASSERT_EQ((size_t)batch.getStreams().size(), (size_t)1); ASSERT_EQ((size_t)batch.getSize(), (size_t)100); // Check batch data. - for (size_t i=0; i < 100; ++i) { - for (size_t j=0; j < 200; ++j) { - paddle::real tmp = (paddle::real)((j-100.0) * (i+1) / 200.0); - ASSERT_NEAR(batch.getStreams()[0].value->getData()[i*200 + j], - tmp, epsilon);} + for (size_t i = 0; i < 100; ++i) { + for (size_t j = 0; j < 200; ++j) { + paddle::real tmp = (paddle::real)((j - 100.0) * (i + 1) / 200.0); + ASSERT_NEAR( + batch.getStreams()[0].value->getData()[i * 200 + j], tmp, epsilon); + } } num = provider->getNextBatchInternal(100, &batch); @@ -83,12 +81,13 @@ TEST(PyDataProvider2, dense_no_seq) { ASSERT_EQ(batch.getStreams().size(), (size_t)1); ASSERT_EQ((size_t)batch.getSize(), (size_t)100); // Check batch data. - for (size_t i=0; i < 100; ++i) { + for (size_t i = 0; i < 100; ++i) { size_t ii = i + 100; - for (size_t j=0; j < 200; ++j) { - paddle::real tmp = (paddle::real)((j-100.0) * (ii+1) / 200.0); - ASSERT_NEAR(batch.getStreams()[0].value->getData()[i*200 + j], - tmp, epsilon);} + for (size_t j = 0; j < 200; ++j) { + paddle::real tmp = (paddle::real)((j - 100.0) * (ii + 1) / 200.0); + ASSERT_NEAR( + batch.getStreams()[0].value->getData()[i * 200 + j], tmp, epsilon); + } } num = provider->getNextBatchInternal(100, &batch); ASSERT_EQ(num, 0); @@ -106,11 +105,11 @@ TEST(PyDataProvider2, index_no_seq) { provider->setSkipShuffle(); // skip shuffle for unittest. paddle::DataBatch batch; - for (size_t pass=0; pass < 2; ++pass) { + for (size_t pass = 0; pass < 2; ++pass) { provider->reset(); int64_t num = provider->getNextBatchInternal(10000, &batch); CHECK_EQ(num, 200); - for (int i=0; i < 200; ++i) { + for (int i = 0; i < 200; ++i) { CHECK_EQ(i, batch.getStreams()[0].ids->getData()[i]); } } @@ -118,13 +117,14 @@ TEST(PyDataProvider2, index_no_seq) { TEST(PyDataProvider2, init_hook) { paddle::PyObjectPtr pickle = paddle::py::import("pickle"); - paddle::PyObjectPtr globals( - PyModule_GetDict(PyImport_AddModule("__main__"))); + paddle::PyObjectPtr globals(PyModule_GetDict(PyImport_AddModule("__main__"))); PyDict_SetItemString(globals.get(), "pickle", pickle.get()); paddle::PyObjectPtr locals(PyDict_New()); paddle::PyObjectPtr mdl(PyRun_String( "dumps = pickle.dumps({'value':[float(x) for x in xrange(20)]})", - Py_file_input, globals.get(), locals.get())); + Py_file_input, + globals.get(), + locals.get())); CHECK_PY(mdl) << "Error!"; paddle::PyObjectPtr dps(PyDict_GetItemString(locals.get(), "dumps")); CHECK_PY(dps) << "Error!"; @@ -145,9 +145,9 @@ TEST(PyDataProvider2, init_hook) { ASSERT_EQ(num, 200); auto& mat = batch.getStreams()[0].value; ASSERT_EQ((size_t)mat->getWidth(), (size_t)20); - for (size_t i=0; i < 200; ++i) { - for (size_t j=0; j < 20; ++j) { - ASSERT_NEAR((paddle::real)j, mat->getData()[i*20 + j], epsilon); + for (size_t i = 0; i < 200; ++i) { + for (size_t j = 0; j < 20; ++j) { + ASSERT_NEAR((paddle::real)j, mat->getData()[i * 20 + j], epsilon); } } } @@ -168,11 +168,11 @@ TEST(PyDataProvider2, sparse_no_value_no_seq) { auto csm = std::dynamic_pointer_cast( batch.getStreams()[0].value); CHECK(csm != nullptr); - for (int i=0; i < 200; ++i) { + for (int i = 0; i < 200; ++i) { CHECK_EQ(csm->getColNum(i), (size_t)10); int* cols = csm->getRowCols(i); - for (int j=0; j < 10; ++j) { - CHECK_EQ(cols[j], (i+1)*(j+1)); + for (int j = 0; j < 10; ++j) { + CHECK_EQ(cols[j], (i + 1) * (j + 1)); } } } @@ -183,13 +183,13 @@ TEST(PyDataProvider2, sparse_value_no_seq) { auto csm = std::dynamic_pointer_cast( batch.getStreams()[0].value); CHECK(csm != nullptr); - for (int i=0; i < 200; ++i) { + for (int i = 0; i < 200; ++i) { CHECK_EQ(csm->getColNum(i), (size_t)10); int* cols = csm->getRowCols(i); real* dat = csm->getRowValues(i); - for (int j=0; j < 10; ++j) { - EXPECT_EQ(cols[j], (i+1)*(j+1)); - EXPECT_EQ(dat[j], real(j)/real(i+1)); + for (int j = 0; j < 10; ++j) { + EXPECT_EQ(cols[j], (i + 1) * (j + 1)); + EXPECT_EQ(dat[j], real(j) / real(i + 1)); } } } @@ -198,10 +198,10 @@ TEST(PyDataProvider2, index_seq) { paddle::DataBatch batch; CHECK_EQ(readDataBatch(&batch, "test_index_seq"), 200); auto& arg = batch.getStreams()[0]; - CHECK_EQ((int)arg.ids->getSize(), (200 + 1) * 200 /2); + CHECK_EQ((int)arg.ids->getSize(), (200 + 1) * 200 / 2); size_t tmp = 0; - for (size_t i=0; i < 200; ++i) { // CHECK DATA CORRECT - for (size_t j=0; j < i+1; ++j) { + for (size_t i = 0; i < 200; ++i) { // CHECK DATA CORRECT + for (size_t j = 0; j < i + 1; ++j) { ASSERT_EQ((size_t)arg.ids->getData()[tmp], j); ++tmp; } @@ -221,9 +221,9 @@ TEST(PyDataProvider2, index_sub_seq) { ASSERT_EQ(readDataBatch(&batch, "test_index_sub_seq"), 200); auto& arg = batch.getStreams()[0]; size_t tmp = 0; - for (size_t i=0; i < 200; ++i) { - for (size_t j=0; j < i+1; ++j) { - for (size_t k=0; k < j+1; ++k) { + for (size_t i = 0; i < 200; ++i) { + for (size_t j = 0; j < i + 1; ++j) { + for (size_t k = 0; k < j + 1; ++k) { CHECK_EQ((size_t)arg.ids->getData()[tmp++], k); } } @@ -236,14 +236,14 @@ TEST(PyDataProvider2, index_sub_seq) { ASSERT_EQ(arg.sequenceStartPositions->getData(false)[0], 0); size_t idx = 1; tmp = 0; - for (size_t i=0; i < 200; ++i) { - for (size_t j=0; j < i+1; ++j) { - tmp += j+1; + for (size_t i = 0; i < 200; ++i) { + for (size_t j = 0; j < i + 1; ++j) { + tmp += j + 1; ASSERT_EQ((size_t)arg.subSequenceStartPositions->getData(false)[idx], - (size_t)tmp); + (size_t)tmp); ++idx; } - ASSERT_EQ((size_t)arg.sequenceStartPositions->getData(false)[i+1], tmp); + ASSERT_EQ((size_t)arg.sequenceStartPositions->getData(false)[i + 1], tmp); } } @@ -264,7 +264,7 @@ TEST(PyDataProvider2, min_pool_size) { paddle::unittest::pydp2::setOnPoolFilledHook([&](size_t poolSize) { if (totalData > batchSize) { - CHECK_GE(poolSize, std::min(totalData-batchSize, minPoolSize)); + CHECK_GE(poolSize, std::min(totalData - batchSize, minPoolSize)); } }); while (true) { @@ -287,7 +287,7 @@ TEST(PyDataProvider2, can_over_batch_size) { config.set_load_data_args(""); paddle::DataBatch batch; std::unique_ptr provider( - paddle::DataProvider::create(config, false)); + paddle::DataProvider::create(config, false)); provider->reset(); constexpr size_t batchSize = 100; while (true) { @@ -313,7 +313,7 @@ TEST(PyDataProvider2, input_order) { *modelConfig.add_input_layer_names() = "input2"; paddle::DataBatch batch; std::unique_ptr provider( - paddle::DataProvider::create(config, modelConfig, false)); + paddle::DataProvider::create(config, modelConfig, false)); provider->reset(); constexpr size_t batchSize = 100; while (true) { @@ -338,7 +338,7 @@ TEST(PyDataProvider2, test_check) { config.set_load_data_args(""); paddle::DataBatch batch; std::unique_ptr provider( - paddle::DataProvider::create(config, false)); + paddle::DataProvider::create(config, false)); provider->reset(); while (true) { size_t realBatchSize = provider->getNextBatchInternal(100, &batch); @@ -346,7 +346,7 @@ TEST(PyDataProvider2, test_check) { break; } else { auto& ivec = batch.getStream(0).ids; - for (size_t i=0; i < ivec->getSize(); ++i) { + for (size_t i = 0; i < ivec->getSize(); ++i) { CHECK_LT(ivec->getData()[i], 10); } } diff --git a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp index d104db3e5b..80d713dac0 100644 --- a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp +++ b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp @@ -45,12 +45,16 @@ public: auto p = const_cast(this); auto& params = p->getGradientMachine()->getParameters(); return std::accumulate( - params.begin(), params.end(), 0UL, + params.begin(), + params.end(), + 0UL, [](size_t a, const ParameterPtr& p) { return a + p->getSize(); }); } }; -void CalCost(const string& conf, const string& dir, real* cost, +void CalCost(const string& conf, + const string& dir, + real* cost, int num_passes) { auto config = std::make_shared(conf); TrainerForTest trainer; @@ -82,8 +86,8 @@ void CalCost(const string& conf, const string& dir, real* cost, int num = dataProvider->getNextBatch(batchSize, &dataBatch); if (num == 0) break; totalCost += trainer.calcGradient(dataBatch, vecW, vecGradient); - sgdUpdate(learningRate, momentum, decayRate, &vecW, &vecGradient, - &vecMomentum); + sgdUpdate( + learningRate, momentum, decayRate, &vecW, &vecGradient, &vecMomentum); } cost[i] = totalCost; } @@ -119,7 +123,8 @@ TEST(RecurrentGradientMachine, HasSubSequence) { for (bool useGpu : {false, true}) { test("gserver/tests/sequence_layer_group.conf", "gserver/tests/sequence_nest_layer_group.conf", - 1e-5, useGpu); + 1e-5, + useGpu); } } @@ -127,7 +132,8 @@ TEST(RecurrentGradientMachine, rnn) { for (bool useGpu : {false, true}) { test("gserver/tests/sequence_rnn.conf", "gserver/tests/sequence_nest_rnn.conf", - 1e-6, useGpu); + 1e-6, + useGpu); } } @@ -135,16 +141,18 @@ TEST(RecurrentGradientMachine, rnn_multi_input) { for (bool useGpu : {false, true}) { test("gserver/tests/sequence_rnn_multi_input.conf", "gserver/tests/sequence_nest_rnn_multi_input.conf", - 1e-6, useGpu); + 1e-6, + useGpu); } } TEST(RecurrentGradientMachine, rnn_multi_unequalength_input) { - for (bool useGpu : {false, true}) { - test("gserver/tests/sequence_rnn_multi_unequalength_inputs.conf", - "gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf", - 1e-6, useGpu); - } + for (bool useGpu : {false, true}) { + test("gserver/tests/sequence_rnn_multi_unequalength_inputs.conf", + "gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf", + 1e-6, + useGpu); + } } int main(int argc, char** argv) { diff --git a/paddle/gserver/tests/test_RecurrentLayer.cpp b/paddle/gserver/tests/test_RecurrentLayer.cpp index 1c8497e8c5..0643cec38b 100644 --- a/paddle/gserver/tests/test_RecurrentLayer.cpp +++ b/paddle/gserver/tests/test_RecurrentLayer.cpp @@ -71,7 +71,9 @@ void checkError(const CpuVector& vector1, const CpuVector& vector2) { EXPECT_EQ(count, 0) << "There are " << count << " different element."; } -LayerPtr creatDataLayer(string name, size_t batchSize, int layerSize, +LayerPtr creatDataLayer(string name, + size_t batchSize, + int layerSize, bool useGpu) { LayerConfig dataConfig; dataConfig.set_name(name); @@ -96,7 +98,9 @@ LayerPtr creatDataLayer(string name, size_t batchSize, int layerSize, return layer; } -ParameterPtr creatParameter(string name, int pid, size_t paraSize, +ParameterPtr creatParameter(string name, + int pid, + size_t paraSize, bool useGpu) { ParameterConfig paraConfig; paraConfig.set_name(name); @@ -112,7 +116,9 @@ ParameterPtr creatParameter(string name, int pid, size_t paraSize, return parameter; } -ParameterPtr creatParameterBias(string name, int pid, size_t paraSize, +ParameterPtr creatParameterBias(string name, + int pid, + size_t paraSize, bool useGpu) { ParameterConfig paraConfig; paraConfig.set_name(name); @@ -127,8 +133,10 @@ ParameterPtr creatParameterBias(string name, int pid, size_t paraSize, return parameter; } -LayerPtr initRecurrentLayer(LayerConfig layerConfig, size_t batchSize, - int layerSize, bool useGpu) { +LayerPtr initRecurrentLayer(LayerConfig layerConfig, + size_t batchSize, + int layerSize, + bool useGpu) { FLAGS_use_gpu = useGpu; LayerMap layerMap; ParameterMap parameterMap; @@ -214,7 +222,7 @@ TEST(Layer, RecurrentLayer) { #define protected public #include "paddle/gserver/layers/LstmLayer.h" #include "paddle/gserver/layers/GatedRecurrentLayer.h" -template +template class TestRecurrentLayer { public: LayerConfig config_; @@ -227,25 +235,34 @@ public: LayerMap layerMap_; ParameterMap parameterMap_; TestRecurrentLayer(const LayerConfig& config, - bool useGpu, bool useBatch = false) - : config_(config), useGpu_(useGpu), useBatch_(useBatch) {} + bool useGpu, + bool useBatch = false) + : config_(config), useGpu_(useGpu), useBatch_(useBatch) {} void init(size_t batchSize) { FLAGS_use_gpu = useGpu_; testLayer_ = Layer::create(config_); if (typeid(T) == typeid(GatedRecurrentLayer)) { dataLayer_ = creatDataLayer(config_.mutable_inputs(0)->input_layer_name(), - batchSize, config_.size() * 3, useGpu_); + batchSize, + config_.size() * 3, + useGpu_); para_ = creatParameter(config_.mutable_inputs(0)->input_parameter_name(), - 0, config_.size() * config_.size() * 3, useGpu_); - bias_ = creatParameterBias(config_.bias_parameter_name(), - 1, config_.size() * 3, useGpu_); + 0, + config_.size() * config_.size() * 3, + useGpu_); + bias_ = creatParameterBias( + config_.bias_parameter_name(), 1, config_.size() * 3, useGpu_); } else if (typeid(T) == typeid(LstmLayer)) { dataLayer_ = creatDataLayer(config_.mutable_inputs(0)->input_layer_name(), - batchSize, config_.size() * 4, useGpu_); + batchSize, + config_.size() * 4, + useGpu_); para_ = creatParameter(config_.mutable_inputs(0)->input_parameter_name(), - 0, config_.size() * config_.size() * 4, useGpu_); - bias_ = creatParameterBias(config_.bias_parameter_name(), - 1, config_.size() * 7, useGpu_); + 0, + config_.size() * config_.size() * 4, + useGpu_); + bias_ = creatParameterBias( + config_.bias_parameter_name(), 1, config_.size() * 7, useGpu_); } layerMap_[dataLayer_->getName()] = dataLayer_; parameterMap_[para_->getName()] = para_; @@ -266,15 +283,17 @@ public: } }; -template -void checkRecurrentLayer(LayerConfig layerConfig, size_t batchSize, - bool cpuBatch, bool gpuBatch) { +template +void checkRecurrentLayer(LayerConfig layerConfig, + size_t batchSize, + bool cpuBatch, + bool gpuBatch) { TestRecurrentLayer testCpu(layerConfig, false, cpuBatch); TestRecurrentLayer testGpu(layerConfig, true, gpuBatch); testCpu.init(batchSize); testGpu.init(batchSize); - auto checkError = [](MatrixPtr cpu, MatrixPtr gpu, - int numSequences, const char* str) { + auto checkError = []( + MatrixPtr cpu, MatrixPtr gpu, int numSequences, const char* str) { CpuMatrix check(gpu->getHeight(), gpu->getWidth()); check.copyFrom(*gpu); int height = cpu->getHeight(); @@ -290,8 +309,8 @@ void checkRecurrentLayer(LayerConfig layerConfig, size_t batchSize, } } } - EXPECT_EQ(count, 0) << "[" << str << "]" << - "There are " << count << " different element."; + EXPECT_EQ(count, 0) << "[" << str << "]" + << "There are " << count << " different element."; }; T* cpuLayer = dynamic_cast(testCpu.testLayer_.get()); T* gpuLayer = dynamic_cast(testGpu.testLayer_.get()); @@ -312,8 +331,8 @@ void checkRecurrentLayer(LayerConfig layerConfig, size_t batchSize, testCpu.forward(); testGpu.forward(); - checkError(cpuLayer->getOutputValue(), - gpuLayer->getOutputValue(), 1, "outputValue"); + checkError( + cpuLayer->getOutputValue(), gpuLayer->getOutputValue(), 1, "outputValue"); /* check backward */ cpuLayer->getOutputGrad()->randomizeUniform(); @@ -327,11 +346,15 @@ void checkRecurrentLayer(LayerConfig layerConfig, size_t batchSize, checkError(cpuInput.grad, gpuInput.grad, 1, "inputGrad"); // check weight grad int numSequences = cpuInput.getNumSequences(); - checkError(cpuLayer->weight_->getWGrad(), gpuLayer->weight_->getWGrad(), - numSequences, "weightGrad"); + checkError(cpuLayer->weight_->getWGrad(), + gpuLayer->weight_->getWGrad(), + numSequences, + "weightGrad"); // check bias grad - checkError(cpuLayer->bias_->getWGrad(), gpuLayer->bias_->getWGrad(), - numSequences, "biasGrad"); + checkError(cpuLayer->bias_->getWGrad(), + gpuLayer->bias_->getWGrad(), + numSequences, + "biasGrad"); } TEST(Layer, GatedRecurrentLayer) { @@ -357,7 +380,7 @@ TEST(Layer, GatedRecurrentLayer) { layerConfig.set_size(frameSize); layerConfig.set_reversed(reversed); checkRecurrentLayer( - layerConfig, batchSize, cpuBatch, gpuBatch); + layerConfig, batchSize, cpuBatch, gpuBatch); } } } @@ -388,8 +411,8 @@ TEST(Layer, LstmLayer) { << " cpuBatch=" << cpuBatch << " gpuBatch=" << gpuBatch; layerConfig.set_size(frameSize); layerConfig.set_reversed(reversed); - checkRecurrentLayer - (layerConfig, batchSize, cpuBatch, gpuBatch); + checkRecurrentLayer( + layerConfig, batchSize, cpuBatch, gpuBatch); } } } diff --git a/paddle/gserver/tests/test_SelectiveFCLayer.cpp b/paddle/gserver/tests/test_SelectiveFCLayer.cpp index 9a83217f1a..204b03332f 100644 --- a/paddle/gserver/tests/test_SelectiveFCLayer.cpp +++ b/paddle/gserver/tests/test_SelectiveFCLayer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include @@ -53,7 +52,7 @@ int randint(int* data, size_t int_max, size_t size) { int this_int = 0; while (count < size) { - this_int = std::rand() % int_max; // NOLINT + this_int = std::rand() % int_max; // NOLINT if (tmp.find(this_int) == tmp.end()) { tmp[this_int] = 0; count += 1; @@ -71,8 +70,10 @@ int randint(int* data, size_t int_max, size_t size) { return 0; } -void calcOutput(ComData& comData, const string configFile, - const string configArgs, bool useGpu) { +void calcOutput(ComData& comData, + const string configFile, + const string configArgs, + bool useGpu) { FLAGS_config = configFile; FLAGS_config_args = configArgs; FLAGS_use_gpu = useGpu; @@ -95,8 +96,8 @@ void calcOutput(ComData& comData, const string configFile, vector& inArgs = dataBatch.getStreams(); trainer.getGradientMachine()->start(trainer.getConfig(), nullptr); - trainer.getGradientMachine()->forwardBackward(inArgs, &comData.outArgs, - PASS_TRAIN); + trainer.getGradientMachine()->forwardBackward( + inArgs, &comData.outArgs, PASS_TRAIN); trainer.getGradientMachine()->finish(); } @@ -108,8 +109,8 @@ void checkMatrix(real* A, real* B, size_t matSize) { #endif int diffNum = 0; for (size_t i = 0; i < matSize; ++i) { - if (std::isinf(A[i]) || std::isnan(A[i]) - || std::isinf(B[i]) || std::isnan(B[i])) { + if (std::isinf(A[i]) || std::isnan(A[i]) || std::isinf(B[i]) || + std::isnan(B[i])) { } else if (fabs(A[i] - B[i]) > err) { diffNum++; } @@ -117,8 +118,10 @@ void checkMatrix(real* A, real* B, size_t matSize) { EXPECT_EQ(0, diffNum); } -void checkTranspose(real* matrix, real* transpose, - size_t width, size_t matSize) { +void checkTranspose(real* matrix, + real* transpose, + size_t width, + size_t matSize) { #ifndef PADDLE_TYPE_DOUBLE real err = 1e-3; #else @@ -149,20 +152,20 @@ void compareOutput(ComData& fcData, ComData& selFcData) { // check cost LOG(INFO) << "Check cost"; CpuMatrix fcCost(outArgsFc[0].value->getHeight(), - outArgsFc[0].value->getWidth()); + outArgsFc[0].value->getWidth()); CpuMatrix selfcCost(outArgsSelfc[0].value->getHeight(), - outArgsSelfc[0].value->getWidth()); + outArgsSelfc[0].value->getWidth()); fcCost.copyFrom(*outArgsFc[0].value); selfcCost.copyFrom(*outArgsSelfc[0].value); checkMatrix(fcCost.getData(), selfcCost.getData(), fcCost.getElementCnt()); // check selective fc output and fc output - LOG(INFO) << "Compare output of SelectiveFullyConectedLayer " << - "with FullyConectedLayer"; + LOG(INFO) << "Compare output of SelectiveFullyConectedLayer " + << "with FullyConectedLayer"; CpuMatrix fcOut(outArgsFc[1].value->getHeight(), - outArgsFc[1].value->getWidth()); + outArgsFc[1].value->getWidth()); CpuMatrix selfcOut(outArgsSelfc[1].value->getHeight(), - outArgsSelfc[1].value->getWidth()); + outArgsSelfc[1].value->getWidth()); fcOut.copyFrom(*outArgsFc[1].value); selfcOut.copyFrom(*outArgsSelfc[1].value); @@ -189,32 +192,40 @@ void compareOutput(ComData& fcData, ComData& selFcData) { CpuVector paraGrad1(*p1->getBuf(PARAMETER_GRADIENT)); CpuVector paraGrad2(*p2->getBuf(PARAMETER_GRADIENT)); if (paramName == "rand_fc_param.bias") { - checkMatrix(paraValue1.getData(), - paraValue2.getData(), - paraValue1.getSize()); - checkMatrix(paraGrad1.getData(), - paraGrad2.getData(), - paraGrad1.getSize()); + checkMatrix( + paraValue1.getData(), paraValue2.getData(), paraValue1.getSize()); + checkMatrix( + paraGrad1.getData(), paraGrad2.getData(), paraGrad1.getSize()); } else { - checkTranspose(paraValue1.getData(), paraValue2.getData(), - fcLayerWidth, paraValue1.getSize()); - checkTranspose(paraGrad1.getData(), paraGrad2.getData(), - fcLayerWidth, paraGrad1.getSize()); + checkTranspose(paraValue1.getData(), + paraValue2.getData(), + fcLayerWidth, + paraValue1.getSize()); + checkTranspose(paraGrad1.getData(), + paraGrad2.getData(), + fcLayerWidth, + paraGrad1.getSize()); } } } -void compareSparseMulOutput(real* fcOutput, real* selOutput, size_t nnz, - const std::shared_ptr > > &selCols) { +void compareSparseMulOutput( + real* fcOutput, + real* selOutput, + size_t nnz, + const std::shared_ptr>>& selCols) { #ifndef PADDLE_TYPE_DOUBLE real err = 1e-3; #else real err = 1e-10; #endif - size_t nnzCount = std::accumulate(selCols->begin(), selCols->end(), 0UL, - [](size_t a, const std::pair& arr){ - return a+arr.second; - }); + size_t nnzCount = + std::accumulate(selCols->begin(), + selCols->end(), + 0UL, + [](size_t a, const std::pair& arr) { + return a + arr.second; + }); EXPECT_EQ(nnz, nnzCount); size_t sampleNum = selCols->size(); @@ -225,18 +236,20 @@ void compareSparseMulOutput(real* fcOutput, real* selOutput, size_t nnz, size_t selIdx = (*selCols)[i].first[j]; if (fabs(fcOutput[i * fcLayerWidth + selIdx] - selOutput[count]) > err) { diffNum++; - LOG(INFO) << count << " diff : " - << fcOutput[i * fcLayerWidth + selIdx] << "\t" - << selOutput[count]; - } + LOG(INFO) << count << " diff : " << fcOutput[i * fcLayerWidth + selIdx] + << "\t" << selOutput[count]; + } count++; } } EXPECT_EQ(0, diffNum); } -LayerPtr creatDataLayer(string name, size_t batchSize, size_t layerSize, - std::vector& values, bool useGpu) { +LayerPtr creatDataLayer(string name, + size_t batchSize, + size_t layerSize, + std::vector& values, + bool useGpu) { LayerConfig dataConfig; dataConfig.set_name(name); dataConfig.set_type("data"); @@ -253,8 +266,8 @@ LayerPtr creatDataLayer(string name, size_t batchSize, size_t layerSize, return layer; } -ParameterPtr creatParameter(string name, int pid, size_t paraSize, - string paramFile, bool useGpu) { +ParameterPtr creatParameter( + string name, int pid, size_t paraSize, string paramFile, bool useGpu) { ParameterConfig paraConfig; paraConfig.set_name(name); paraConfig.set_size(paraSize); @@ -268,16 +281,19 @@ ParameterPtr creatParameter(string name, int pid, size_t paraSize, return parameter; } -LayerPtr initFcLayer(LayerPtr dataLayer, LayerConfig layerConfig, - int dataLayerSize, int fcLayerSize, - string paraName, string paraFile, bool useGpu) { +LayerPtr initFcLayer(LayerPtr dataLayer, + LayerConfig layerConfig, + int dataLayerSize, + int fcLayerSize, + string paraName, + string paraFile, + bool useGpu) { LayerMap layerMap; ParameterMap parameterMap; layerMap[dataLayer->getName()] = dataLayer; - ParameterPtr para = - creatParameter(paraName, 0, dataLayerSize * fcLayerSize, - paraFile, useGpu); + ParameterPtr para = creatParameter( + paraName, 0, dataLayerSize * fcLayerSize, paraFile, useGpu); parameterMap[para->getName()] = para; layerConfig.add_inputs(); @@ -296,14 +312,13 @@ LayerPtr initFcLayer(LayerPtr dataLayer, LayerConfig layerConfig, #ifndef PADDLE_TYPE_DOUBLE // The parameter file used in fc.conf and selective_fc.conf is float TEST(Layer, SelectiveFcLayer_train_dense_mul) { - const string& fcConfig = - "gserver/tests/SelectiveFcTest/conf/fc.conf"; + const string& fcConfig = "gserver/tests/SelectiveFcTest/conf/fc.conf"; const string& fcConfigArgs = - "filelist=gserver/tests/SelectiveFcTest/dense_mul_list"; + "filelist=gserver/tests/SelectiveFcTest/dense_mul_list"; const string& selFcConfig = "gserver/tests/SelectiveFcTest/conf/selective_fc.conf"; const string& selConfigArgs = - "filelist=gserver/tests/SelectiveFcTest/dense_mul_list"; + "filelist=gserver/tests/SelectiveFcTest/dense_mul_list"; for (auto useGpu : {false, true}) { #ifdef PADDLE_ONLY_CPU @@ -323,7 +338,7 @@ TEST(Layer, SelectiveFcLayer_train_dense_mul) { } #endif // PADDLE_TYPE_DOUBLE -void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, +void testSelectiveFcLayerTrainSparseMul(const LayerConfig& config, bool useGpu) { FLAGS_use_gpu = useGpu; size_t batchSize = 100; @@ -332,21 +347,26 @@ void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, for (size_t j = 0; j < batchSize * dataLayerSize; ++j) { values[j] = std::rand() / real(RAND_MAX); } - LayerPtr dataLayer = creatDataLayer( - "data", batchSize, dataLayerSize, values, useGpu); + LayerPtr dataLayer = + creatDataLayer("data", batchSize, dataLayerSize, values, useGpu); const string& selfcParaFile = - "gserver/tests/SelectiveFcTest/model/rand_fc_param.w.transpose"; + "gserver/tests/SelectiveFcTest/model/rand_fc_param.w.transpose"; const string& selfcParaName = "rand_fc_param.w.transpose"; std::shared_ptr selfcLayer = - std::dynamic_pointer_cast(initFcLayer( - dataLayer, config, dataLayerSize, fcLayerWidth, - selfcParaName, selfcParaFile, useGpu)); + std::dynamic_pointer_cast( + initFcLayer(dataLayer, + config, + dataLayerSize, + fcLayerWidth, + selfcParaName, + selfcParaFile, + useGpu)); // create selected columns - std::shared_ptr > > selCols( - new std::vector > (batchSize)); + std::shared_ptr>> selCols( + new std::vector>(batchSize)); size_t maxNNZ = 30; srand((size_t)(time(NULL))); int total = 0; @@ -364,8 +384,9 @@ void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, MatrixPtr outMatSelfc = selfcLayer->getOutputValue(); CpuSparseMatrixPtr cpuOutMatSelfc( - new CpuSparseMatrix(outMatSelfc->getHeight(), outMatSelfc->getWidth(), - outMatSelfc->getElementCnt())); + new CpuSparseMatrix(outMatSelfc->getHeight(), + outMatSelfc->getWidth(), + outMatSelfc->getElementCnt())); cpuOutMatSelfc->copyFrom(*outMatSelfc, HPPL_STREAM_DEFAULT); #ifndef PADDLE_ONLY_CPU if (useGpu) { @@ -376,7 +397,7 @@ void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, size_t nnz = cpuOutMatSelfc->getElementCnt(); const string& fcParaFile = - "gserver/tests/SelectiveFcTest/model/rand_fc_param.w"; + "gserver/tests/SelectiveFcTest/model/rand_fc_param.w"; const string& fcParaName = "rand_fc_param.w"; LayerConfig fcLayerConfig; fcLayerConfig.set_name("fc_layer"); @@ -384,13 +405,18 @@ void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, fcLayerConfig.set_active_type("linear"); fcLayerConfig.set_size(fcLayerWidth); - LayerPtr fcLayer = initFcLayer(dataLayer, fcLayerConfig, - dataLayerSize, fcLayerWidth, fcParaName, fcParaFile, useGpu); + LayerPtr fcLayer = initFcLayer(dataLayer, + fcLayerConfig, + dataLayerSize, + fcLayerWidth, + fcParaName, + fcParaFile, + useGpu); fcLayer->forward(PASS_TEST); MatrixPtr outMatFc = fcLayer->getOutputValue(); MatrixPtr cpuOutMatFc( - new CpuMatrix(outMatFc->getHeight(), outMatFc->getWidth())); + new CpuMatrix(outMatFc->getHeight(), outMatFc->getWidth())); cpuOutMatFc->copyFrom(*outMatFc, HPPL_STREAM_DEFAULT); #ifndef PADDLE_ONLY_CPU if (useGpu) { @@ -401,7 +427,7 @@ void testSelectiveFcLayerTrainSparseMul(const LayerConfig &config, compareSparseMulOutput(outValueFc, outValueSelfc, nnz, selCols); for (size_t i = 0; i < batchSize; ++i) { - delete [](*selCols)[i].first; + delete[](*selCols)[i].first; } } diff --git a/paddle/math/Allocator.h b/paddle/math/Allocator.h index f7aa60380f..cba8b37289 100644 --- a/paddle/math/Allocator.h +++ b/paddle/math/Allocator.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -48,10 +47,10 @@ public: * @return Pointer to the allocated memory */ virtual void* alloc(size_t size) { - void* ptr; - CHECK_EQ(posix_memalign(&ptr, 32ul, size), 0); - CHECK(ptr) << "Fail to allocate CPU memory: size=" << size; - return ptr; + void* ptr; + CHECK_EQ(posix_memalign(&ptr, 32ul, size), 0); + CHECK(ptr) << "Fail to allocate CPU memory: size=" << size; + return ptr; } /** @@ -59,12 +58,12 @@ public: * @param ptr Pointer to be free. */ virtual void free(void* ptr) { - if (ptr) { ::free(ptr); } + if (ptr) { + ::free(ptr); + } } - virtual std::string getName() { - return "cpu_alloc"; - } + virtual std::string getName() { return "cpu_alloc"; } }; /** @@ -81,7 +80,7 @@ public: */ virtual void* alloc(size_t size) { void* ptr = hl_malloc_device(size); - CHECK(ptr)<< "Fail to allocate GPU memory " << size << " bytes"; + CHECK(ptr) << "Fail to allocate GPU memory " << size << " bytes"; return ptr; } @@ -95,9 +94,7 @@ public: } } - virtual std::string getName() { - return "gpu_alloc"; - } + virtual std::string getName() { return "gpu_alloc"; } }; /** @@ -128,9 +125,7 @@ public: } } - virtual std::string getName() { - return "cuda_host_alloc"; - } + virtual std::string getName() { return "cuda_host_alloc"; } }; } // namespace paddle diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index 3a91fdc3c3..d41dcee682 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include @@ -52,9 +51,14 @@ public: size_t cRow_; size_t dCol_; size_t dRow_; - MatrixOffset(size_t aCol = 0, size_t aRow = 0, size_t bCol = 0, - size_t bRow = 0, size_t cCol = 0, size_t cRow = 0, - size_t dCol = 0, size_t dRow = 0) + MatrixOffset(size_t aCol = 0, + size_t aRow = 0, + size_t bCol = 0, + size_t bRow = 0, + size_t cCol = 0, + size_t cRow = 0, + size_t dCol = 0, + size_t dRow = 0) : aCol_(aCol), aRow_(aRow), bCol_(bCol), @@ -65,7 +69,7 @@ public: dRow_(dRow) {} }; -template +template class BaseMatrixT { public: size_t height_, width_; @@ -97,8 +101,12 @@ public: trans_(mat.trans_), useGpu_(useGpu) {} - BaseMatrixT(size_t height, size_t width, size_t stride, T* data, bool trans, - bool use_gpu) + BaseMatrixT(size_t height, + size_t width, + size_t stride, + T* data, + bool trans, + bool use_gpu) : height_(height), width_(width), stride_(stride), @@ -167,12 +175,17 @@ public: * @endcode */ template - int applyBinary(Op op, BaseMatrixT& b, int numRows, int numCols, - MatrixOffset& offset, bAsRowVector, bAsColVector); + int applyBinary(Op op, + BaseMatrixT& b, + int numRows, + int numCols, + MatrixOffset& offset, + bAsRowVector, + bAsColVector); template - int applyBinary(Op op, BaseMatrixT& b, int numRows, int numCols, - MatrixOffset& offset); + int applyBinary( + Op op, BaseMatrixT& b, int numRows, int numCols, MatrixOffset& offset); /** * ternary operator: element wise op(a, b, c). @@ -212,13 +225,22 @@ public: * @endcode */ template - int applyTernary(Op op, BaseMatrixT& b, BaseMatrixT& c, int numRows, - int numCols, MatrixOffset& offset, cAsRowVector, + int applyTernary(Op op, + BaseMatrixT& b, + BaseMatrixT& c, + int numRows, + int numCols, + MatrixOffset& offset, + cAsRowVector, cAsColVector); template - int applyTernary(Op op, BaseMatrixT& b, BaseMatrixT& c, int numRows, - int numCols, MatrixOffset& offset); + int applyTernary(Op op, + BaseMatrixT& b, + BaseMatrixT& c, + int numRows, + int numCols, + MatrixOffset& offset); /** * quaternary operator: element wise op(a, b, c, d). @@ -247,8 +269,13 @@ public: * @endcode */ template - int applyQuaternary(Op op, BaseMatrixT& b, BaseMatrixT& c, BaseMatrixT& d, - int numRows, int numCols, MatrixOffset& offset); + int applyQuaternary(Op op, + BaseMatrixT& b, + BaseMatrixT& c, + BaseMatrixT& d, + int numRows, + int numCols, + MatrixOffset& offset); /** * a aggregate expression that apply each row(or column) of matrix b. @@ -266,10 +293,20 @@ public: * a[i] = sv(a[i], dst) * @endcode */ - template - int aggregate(Agg agg, Op op, Saver sv, BaseMatrixT& b, int numRows, - int numCols, MatrixOffset& offset, aAsRowVector, aAsColVector); + int aggregate(Agg agg, + Op op, + Saver sv, + BaseMatrixT& b, + int numRows, + int numCols, + MatrixOffset& offset, + aAsRowVector, + aAsColVector); /** * a aggregate expression that apply each row(or column) of matrix b and c. @@ -288,10 +325,20 @@ public: * a[i] = sv(a[i], dst) * @endcode */ - template - int aggregate(Agg agg, Op op, Saver sv, BaseMatrixT& b, BaseMatrixT& c, - int numRows, int numCols, MatrixOffset& offset, aAsRowVector, + int aggregate(Agg agg, + Op op, + Saver sv, + BaseMatrixT& b, + BaseMatrixT& c, + int numRows, + int numCols, + MatrixOffset& offset, + aAsRowVector, aAsColVector); /** @@ -319,8 +366,12 @@ public: // Same as the above with the special handing of sv=add2(scaleDest, scaleAgg) template - int applyRow(Agg agg, Op op, real scaleDest, real scaleAgg, - BaseMatrixT& b, BaseMatrixT& c); + int applyRow(Agg agg, + Op op, + real scaleDest, + real scaleAgg, + BaseMatrixT& b, + BaseMatrixT& c); /** * a aggregate expression that apply each row of matrix b. @@ -664,8 +715,7 @@ public: * this = a*p1 + b*p2 + c*p3 * @endcode */ - void add3(BaseMatrixT& b, BaseMatrixT& c, BaseMatrixT& d, T p1, T p2, - T p3); + void add3(BaseMatrixT& b, BaseMatrixT& c, BaseMatrixT& d, T p1, T p2, T p3); /** * @code @@ -675,9 +725,9 @@ public: */ void sgdUpdate(BaseMatrixT& b, // grad BaseMatrixT& c, // mom - T p1, // learningRate, - T p2, // momentum, - T p3); // decayRate + T p1, // learningRate, + T p2, // momentum, + T p3); // decayRate /** * @code @@ -688,9 +738,9 @@ public: void sgdUpdate(BaseMatrixT& b, // grad, BaseMatrixT& c, // mom, BaseMatrixT& d, // lr, - T p1, // learningRate, - T p2, // momentum, - T p3); // decayRate + T p1, // learningRate, + T p2, // momentum, + T p3); // decayRate /// apply L1/L2 to *this* void applyL1(T learningRate, T decayRate); @@ -767,17 +817,21 @@ public: * this = b>c ? b : c * @endcode */ - void max(BaseMatrixT& b, BaseMatrixT& c); // NOLINT + void max(BaseMatrixT& b, BaseMatrixT& c); // NOLINT /** * @code * this[destCol] += (b>p1 == c>p1) ? 0 : 1) * @endcode */ - void binaryClassificationError(size_t destCol, BaseMatrixT& b, BaseMatrixT& c, + void binaryClassificationError(size_t destCol, + BaseMatrixT& b, + BaseMatrixT& c, T p); - void binaryClassificationError2(size_t destCol, BaseMatrixT& b, - BaseMatrixT& c, T p); + void binaryClassificationError2(size_t destCol, + BaseMatrixT& b, + BaseMatrixT& c, + T p); /** * @code @@ -833,8 +887,8 @@ public: * this += sqr(p1*b + p2*c + p3*d) * @endcode */ - void addSquareSum(BaseMatrixT& b, BaseMatrixT& c, BaseMatrixT d, T p1, - T p2, T p3); + void addSquareSum( + BaseMatrixT& b, BaseMatrixT& c, BaseMatrixT d, T p1, T p2, T p3); /** * @code @@ -965,12 +1019,13 @@ public: void sumCols(BaseMatrixT& b, T scaleSum, T scaleDest); /// this_i = scaleDest * this_i + scaleSum * \sum_j (b_{ij} - c_{ij})^2 - void sumOfSquaredDiffs(BaseMatrixT& b, BaseMatrixT& c, - T scaleSum, T scaleDest); + void sumOfSquaredDiffs(BaseMatrixT& b, + BaseMatrixT& c, + T scaleSum, + T scaleDest); /// this_i = scaleDest * this_i + scaleSum * \sum_j b_{ij} * c_{ij} - void sumOfProducts(BaseMatrixT& b, BaseMatrixT& c, - T scaleSum, T scaleDest); + void sumOfProducts(BaseMatrixT& b, BaseMatrixT& c, T scaleSum, T scaleDest); /** * @code @@ -985,9 +1040,7 @@ public: */ void rowPow(size_t cCol, BaseMatrixT& b, BaseMatrixT& c); - virtual bool isSparse() const { - return false; - } + virtual bool isSparse() const { return false; } }; typedef BaseMatrixT BaseMatrix; diff --git a/paddle/math/CpuSparseMatrix.cpp b/paddle/math/CpuSparseMatrix.cpp index 64ee124a56..ad3f8e64ef 100644 --- a/paddle/math/CpuSparseMatrix.cpp +++ b/paddle/math/CpuSparseMatrix.cpp @@ -12,7 +12,6 @@ WITHOUT 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_gpu.h" #include "CpuSparseMatrix.h" #include "SparseMatrix.h" @@ -24,24 +23,35 @@ namespace paddle { const size_t CpuSparseMatrix::DEFAULT_AVG_WIDTH; -CpuSparseMatrix::CpuSparseMatrix(size_t height, size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, +CpuSparseMatrix::CpuSparseMatrix(size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans) : Matrix(NULL, height, width, trans, false) { resize(height, width, nnz, valueType, format); } -CpuSparseMatrix::CpuSparseMatrix(CpuMemHandlePtr dataHandle, size_t height, - size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, +CpuSparseMatrix::CpuSparseMatrix(CpuMemHandlePtr dataHandle, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans) : Matrix(dataHandle, height, width, trans, false) { resize(height, width, nnz, valueType, format); } -CpuSparseMatrix::CpuSparseMatrix(real* data, int* rows, int* cols, - size_t height, size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, +CpuSparseMatrix::CpuSparseMatrix(real* data, + int* rows, + int* cols, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans) : Matrix(NULL, height, width, trans, false) { cols_ = cols; @@ -54,8 +64,11 @@ CpuSparseMatrix::CpuSparseMatrix(real* data, int* rows, int* cols, format_ = format; } -void CpuSparseMatrix::resize(size_t newHeight, size_t newWidth, size_t newNnz, - SparseValueType valueType, SparseFormat format) { +void CpuSparseMatrix::resize(size_t newHeight, + size_t newWidth, + size_t newNnz, + SparseValueType valueType, + SparseFormat format) { CHECK_LE(newNnz, newHeight * newWidth); size_t newSize = 0; if (format == SPARSE_CSR) { @@ -110,23 +123,38 @@ void CpuSparseMatrix::sparseResize() { } void CpuSparseMatrix::resize(size_t newHeight, size_t newWidth) { - resize(newHeight, newWidth, newHeight * std::min(DEFAULT_AVG_WIDTH, newWidth), - valueType_, format_); + resize(newHeight, + newWidth, + newHeight * std::min(DEFAULT_AVG_WIDTH, newWidth), + valueType_, + format_); } MatrixPtr CpuSparseMatrix::getTranspose() { if (!memoryHandle_ && !value_) { - MatrixPtr dest(new CpuSparseMatrix(height_, width_, elementCnt_, valueType_, - format_, true)); + MatrixPtr dest(new CpuSparseMatrix( + height_, width_, elementCnt_, valueType_, format_, true)); return dest; } else if (memoryHandle_) { MatrixPtr dest(new CpuSparseMatrix( - std::dynamic_pointer_cast(memoryHandle_), height_, - width_, elementCnt_, valueType_, format_, true)); + std::dynamic_pointer_cast(memoryHandle_), + height_, + width_, + elementCnt_, + valueType_, + format_, + true)); return dest; } else if (value_) { - MatrixPtr dest(new CpuSparseMatrix(value_, rows_, cols_, height_, width_, - elementCnt_, valueType_, format_, true)); + MatrixPtr dest(new CpuSparseMatrix(value_, + rows_, + cols_, + height_, + width_, + elementCnt_, + valueType_, + format_, + true)); return dest; } else { return NULL; @@ -140,7 +168,10 @@ void CpuSparseMatrix::mul(MatrixPtr a, MatrixPtr b, real scaleAB, real scaleT) { if (dynamic_cast(a.get()) && dynamic_cast(b.get())) { CpuMatrix::mul(dynamic_cast(a.get()), - dynamic_cast(b.get()), this, scaleAB, scaleT); + dynamic_cast(b.get()), + this, + scaleAB, + scaleT); } else { LOG(FATAL) << "not supported"; } @@ -243,7 +274,8 @@ void CpuSparseMatrix::randomizeUniform() { } } -void CpuSparseMatrix::copyFrom(std::vector& rows, std::vector& cols, +void CpuSparseMatrix::copyFrom(std::vector& rows, + std::vector& cols, std::vector& values) { size_t size = format_ == SPARSE_CSR ? cols.size() : rows.size(); resize(height_, width_, size, valueType_, format_); @@ -302,11 +334,11 @@ MatrixPtr CpuSparseMatrix::clone(size_t height, size_t width, bool useGpu) { } CHECK(width && height); if (!useGpu) { - return std::make_shared(height, width, 0, valueType_, - format_); + return std::make_shared( + height, width, 0, valueType_, format_); } else { - return std::make_shared(height, width, elementCnt_, - valueType_, format_); + return std::make_shared( + height, width, elementCnt_, valueType_, format_); } } @@ -315,13 +347,25 @@ MatrixPtr CpuSparseMatrix::subMatrix(size_t startRow, size_t numRows) { CHECK_EQ(format_, SPARSE_CSR); if (valueType_ == NO_VALUE) { return std::make_shared( - nullptr, rows_ + startRow, cols_, numRows, width_, - rows_[startRow + numRows] - rows_[startRow], valueType_, format_, + nullptr, + rows_ + startRow, + cols_, + numRows, + width_, + rows_[startRow + numRows] - rows_[startRow], + valueType_, + format_, trans_); } else { return std::make_shared( - value_, rows_ + startRow, cols_, numRows, width_, - rows_[startRow + numRows] - rows_[startRow], valueType_, format_, + value_, + rows_ + startRow, + cols_, + numRows, + width_, + rows_[startRow + numRows] - rows_[startRow], + valueType_, + format_, trans_); } } @@ -404,8 +448,10 @@ void CpuSparseMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { } } -void CpuSparseMatrix::setRow(size_t row, size_t colNum, - const unsigned int* cols, const real* values) { +void CpuSparseMatrix::setRow(size_t row, + size_t colNum, + const unsigned int* cols, + const real* values) { if (format_ == SPARSE_CSR) { CHECK_LT(row, height_); CHECK(NULL != cols); @@ -494,11 +540,23 @@ void CpuSparseMatrix::copyFrom(const GpuSparseMatrix& src, hl_stream_t stream) { CHECK_EQ(size_t(elementCnt_), src.getElementCnt()); size_t valSize = valueType_ == NO_VALUE ? 0 : elementCnt_; if (format_ == SPARSE_CSC) - hl_memcpy_from_csc_matrix(value_, valSize, rows_, elementCnt_, cols_, - width_ + 1, src.sMatrix_.get(), stream); + hl_memcpy_from_csc_matrix(value_, + valSize, + rows_, + elementCnt_, + cols_, + width_ + 1, + src.sMatrix_.get(), + stream); else - hl_memcpy_from_csr_matrix(value_, valSize, rows_, height_ + 1, cols_, - elementCnt_, src.sMatrix_.get(), stream); + hl_memcpy_from_csr_matrix(value_, + valSize, + rows_, + height_ + 1, + cols_, + elementCnt_, + src.sMatrix_.get(), + stream); } void CpuSparseMatrix::copyFrom(const CpuSparseMatrix& src) { @@ -536,14 +594,16 @@ void CpuSparseMatrix::copyFrom(const CpuSparseMatrix& src) { } } -void CpuSparseMatrix::copyRow(int offsets, size_t colNum, +void CpuSparseMatrix::copyRow(int offsets, + size_t colNum, const sparse_non_value_t* row) { for (size_t j = 0; j < colNum; j++) { cols_[offsets + j] = row[j].col; } } -void CpuSparseMatrix::copyRow(int offsets, size_t colNum, +void CpuSparseMatrix::copyRow(int offsets, + size_t colNum, const sparse_float_value_t* row) { for (size_t j = 0; j < colNum; j++) { cols_[offsets + j] = row[j].col; @@ -596,7 +656,8 @@ void CpuSparseMatrix::trimFrom(const CpuSparseMatrix& src) { if (format_ == SPARSE_CSR) { int* srcCols = src.getCols(); size_t numLessWidth = - std::count_if(srcCols, srcCols + src.getElementCnt(), + std::count_if(srcCols, + srcCols + src.getElementCnt(), [this](size_t n) { return n < this->width_; }); resize(height_, width_, numLessWidth, valueType_, format_); rows_[0] = 0; @@ -636,13 +697,15 @@ void CpuSparseMatrix::trimFrom(const CpuSparseMatrix& src) { void CpuSparseMatrix::zeroMem() { CHECK(valueType_ == FLOAT_VALUE); - memset(value_, 0, elementCnt_* sizeof(real)); + memset(value_, 0, elementCnt_ * sizeof(real)); } -template void CpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, +template void CpuSparseMatrix::copyFrom(int64_t* ids, + int64_t* indices, sparse_non_value_t* data); -template void CpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, +template void CpuSparseMatrix::copyFrom(int64_t* ids, + int64_t* indices, sparse_float_value_t* data); template void CpuSparseMatrix::copyFrom(int64_t* indices, @@ -673,7 +736,9 @@ void CpuSparseMatrix::rowMax(IVector& maxIds, Matrix& maxVal) { } size_t outsize = std::min(num, beam); - std::partial_sort(vec.begin(), vec.begin() + outsize, vec.end(), + std::partial_sort(vec.begin(), + vec.begin() + outsize, + vec.end(), [](const valuepair& a, const valuepair& b) { return a.first > b.first; }); diff --git a/paddle/math/CpuSparseMatrix.h b/paddle/math/CpuSparseMatrix.h index fd3b5030be..8615645551 100644 --- a/paddle/math/CpuSparseMatrix.h +++ b/paddle/math/CpuSparseMatrix.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include "Matrix.h" @@ -21,24 +20,38 @@ namespace paddle { class CpuSparseMatrix : public Matrix { public: - CpuSparseMatrix(size_t height, size_t width, + CpuSparseMatrix(size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType = FLOAT_VALUE, - SparseFormat format = SPARSE_CSR, bool trans = false); - - CpuSparseMatrix(CpuMemHandlePtr memHandle, size_t height, size_t width, - size_t nnz, SparseValueType valueType, SparseFormat format, + SparseFormat format = SPARSE_CSR, + bool trans = false); + + CpuSparseMatrix(CpuMemHandlePtr memHandle, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans); - CpuSparseMatrix(real* data, int* rows, int* cols, size_t height, size_t width, - size_t nnz, SparseValueType valueType, SparseFormat format, + CpuSparseMatrix(real* data, + int* rows, + int* cols, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans); ~CpuSparseMatrix() {} - void resize(size_t newHeight, size_t newWidth, + void resize(size_t newHeight, + size_t newWidth, size_t newNnz, /* used to allocate space */ - SparseValueType valueType, SparseFormat format); + SparseValueType valueType, + SparseFormat format); void resize(size_t newHeight, size_t newWidth); MatrixPtr getTranspose(); @@ -75,8 +88,6 @@ public: } } - - real* getColumn(size_t i) const { if (format_ == SPARSE_CSC) { return value_ + cols_[i]; @@ -182,7 +193,7 @@ public: * getData is convenient to get value */ real* getData() { return getValue(); } - const real* getData() const { return getValue();} + const real* getData() const { return getValue(); } /** * @brief only set value_ of FLOAT_VALUE sparse matrix to zero @@ -220,7 +231,9 @@ public: void printOneRow(std::ostream& os, size_t idx) const; - void setRow(size_t row, size_t colNum, const unsigned int* cols, + void setRow(size_t row, + size_t colNum, + const unsigned int* cols, const real* values); void randomizeUniform(); @@ -241,7 +254,8 @@ public: virtual MatrixPtr subMatrix(size_t startRow, size_t numRows); - void copyFrom(std::vector& rows, std::vector& cols, + void copyFrom(std::vector& rows, + std::vector& cols, std::vector& values); void copyFrom(const CpuMatrix& src); @@ -285,9 +299,7 @@ protected: // BaseMatrixT interface public: - bool isSparse() const { - return true; - } + bool isSparse() const { return true; } private: using Matrix::copyFrom; diff --git a/paddle/math/ExecViaCpu.h b/paddle/math/ExecViaCpu.h index 64e5b83121..67fb6c0cda 100644 --- a/paddle/math/ExecViaCpu.h +++ b/paddle/math/ExecViaCpu.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - /* execViaCpu is used to do operations on GpuMatirx and/or GpuIVector through cpu functions. It can automatically make a temporary CPU copy for the @@ -46,8 +45,10 @@ public: explicit CopyToCpu(Matrix& arg) : arg_(arg) { if (arg.useGpu()) { CHECK(!arg.isTransposed()) << "Not supported"; - copied_ = Matrix::create(arg.getHeight(), arg.getWidth(), - /* trans= */ false, /* useGpu= */ false); + copied_ = Matrix::create(arg.getHeight(), + arg.getWidth(), + /* trans= */ false, + /* useGpu= */ false); copied_->copyFrom(arg); } } @@ -69,8 +70,10 @@ public: explicit CopyToCpu(const Matrix& arg) : arg_(arg) { if (arg.useGpu()) { CHECK(!arg.isTransposed()) << "Not supported"; - copied_ = Matrix::create(arg.getHeight(), arg.getWidth(), - /* trans= */ false, /* useGpu= */ false); + copied_ = Matrix::create(arg.getHeight(), + arg.getWidth(), + /* trans= */ false, + /* useGpu= */ false); copied_->copyFrom(arg); } } @@ -165,7 +168,8 @@ class GpuFuncWrapper2 std::is_function::value, std::is_pointer::value && std::is_function::type>::value, - std::is_class::value, F> {}; + std::is_class::value, + F> {}; template class GpuFuncWrapper diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index e0b2a2bb5b..1217163bee 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -12,36 +12,79 @@ WITHOUT 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 "MathFunctions.h" #include "hl_matrix_ops.cuh" #include "hl_matrix_apply.cuh" namespace paddle { -template<> -void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, - const int M, const int N, const int K, - const float alpha, const float* A, const int lda, - const float* B, const int ldb, - const float beta, float* C, const int ldc) { - cblas_sgemm(CblasRowMajor, transA, transB, M, N, K, alpha, A, lda, B, ldb, - beta, C, ldc); -} - -template<> -void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, - const int M, const int N, const int K, - const double alpha, const double* A, const int lda, - const double* B, const int ldb, - const double beta, double* C, const int ldc) { - cblas_dgemm(CblasRowMajor, transA, transB, M, N, K, alpha, A, lda, B, ldb, - beta, C, ldc); -} - -template<> -int getrf(const CBLAS_ORDER order, const int M, const int N, - float *A, const int lda, int *ipiv) { +template <> +void gemm(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE transB, + const int M, + const int N, + const int K, + const float alpha, + const float* A, + const int lda, + const float* B, + const int ldb, + const float beta, + float* C, + const int ldc) { + cblas_sgemm(CblasRowMajor, + transA, + transB, + M, + N, + K, + alpha, + A, + lda, + B, + ldb, + beta, + C, + ldc); +} + +template <> +void gemm(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE transB, + const int M, + const int N, + const int K, + const double alpha, + const double* A, + const int lda, + const double* B, + const int ldb, + const double beta, + double* C, + const int ldc) { + cblas_dgemm(CblasRowMajor, + transA, + transB, + M, + N, + K, + alpha, + A, + lda, + B, + ldb, + beta, + C, + ldc); +} + +template <> +int getrf(const CBLAS_ORDER order, + const int M, + const int N, + float* A, + const int lda, + int* ipiv) { #ifdef PADDLE_USE_ATLAS return clapack_sgetrf(order, M, N, A, lda, ipiv); #else @@ -49,9 +92,13 @@ int getrf(const CBLAS_ORDER order, const int M, const int N, #endif } -template<> -int getrf(const CBLAS_ORDER order, const int M, const int N, - double *A, const int lda, int *ipiv) { +template <> +int getrf(const CBLAS_ORDER order, + const int M, + const int N, + double* A, + const int lda, + int* ipiv) { #ifdef PADDLE_USE_ATLAS return clapack_dgetrf(order, M, N, A, lda, ipiv); #else @@ -59,9 +106,12 @@ int getrf(const CBLAS_ORDER order, const int M, const int N, #endif } -template<> -int getri(const CBLAS_ORDER order, const int N, float *A, - const int lda, const int *ipiv) { +template <> +int getri(const CBLAS_ORDER order, + const int N, + float* A, + const int lda, + const int* ipiv) { #ifdef PADDLE_USE_ATLAS return clapack_sgetri(order, N, A, lda, ipiv); #else @@ -69,9 +119,12 @@ int getri(const CBLAS_ORDER order, const int N, float *A, #endif } -template<> -int getri(const CBLAS_ORDER order, const int N, double *A, - const int lda, const int *ipiv) { +template <> +int getri(const CBLAS_ORDER order, + const int N, + double* A, + const int lda, + const int* ipiv) { #ifdef PADDLE_USE_ATLAS return clapack_dgetri(order, N, A, lda, ipiv); #else @@ -79,149 +132,155 @@ int getri(const CBLAS_ORDER order, const int N, double *A, #endif } -template<> +template <> void axpy(const int n, const float alpha, const float* x, float* y) { cblas_saxpy(n, alpha, x, 1, y, 1); } -template<> +template <> void axpy(const int n, const double alpha, const double* x, double* y) { cblas_daxpy(n, alpha, x, 1, y, 1); } -template<> +template <> float dotProduct(const int n, const float* x, const float* y) { return cblas_sdot(n, x, 1, y, 1); } -template<> +template <> double dotProduct(const int n, const double* x, const double* y) { return cblas_ddot(n, x, 1, y, 1); } #ifdef PADDLE_USE_MKL -template<> +template <> void vExp(const int n, const float* a, float* r) { vsExp(n, a, r); } -template<> +template <> void vExp(const int n, const double* a, double* r) { vdExp(n, a, r); } -template<> +template <> void vPow(const int n, const float* a, const float b, float* r) { vsPowx(n, a, b, r); } -template<> +template <> void vPow(const int n, const double* a, const double b, double* r) { vdPowx(n, a, b, r); } -template<> +template <> void vLog(const int n, const float* a, float* r) { vsLn(n, a, r); } -template<> +template <> void vLog(const int n, const double* a, double* r) { vdLn(n, a, r); } -template<> +template <> void vAdd(const int n, const float* a, const float* b, float* r) { vsAdd(n, a, b, r); } -template<> +template <> void vAdd(const int n, const double* a, const double* b, double* r) { vdAdd(n, a, b, r); } -template<> +template <> void vInvSqrt(const int n, const float* a, float* r) { vsInvSqrt(n, a, r); } -template<> +template <> void vInvSqrt(const int n, const double* a, double* r) { vdInvSqrt(n, a, r); } -template<> +template <> void vLog1p(const int n, const float* a, float* r) { vsLog1p(n, a, r); } -template<> +template <> void vLog1p(const int n, const double* a, double* r) { vdLog1p(n, a, r); } -template<> +template <> void vTanh(const int n, const float* a, float* r) { vsTanh(n, a, r); } -template<> +template <> void vTanh(const int n, const double* a, double* r) { vdTanh(n, a, r); } #else DEFINE_MATRIX_BINARY_OP(vExp, b = std::exp(a)); -template +template void vExp(const int n, const T* a, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vExp(), const_cast(a), r, 1, n, n, n); + binary::vExp(), const_cast(a), r, 1, n, n, n); } DEFINE_MATRIX_BINARY_OP(vLog, b = std::log(a)); -template +template void vLog(const int n, const T* a, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vLog(), const_cast(a), r, 1, n, n, n); + binary::vLog(), const_cast(a), r, 1, n, n, n); } DEFINE_MATRIX_BINARY_OP(vInvSqrt, b = 1.0f / std::sqrt(a)); -template +template void vInvSqrt(const int n, const T* a, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vInvSqrt(), const_cast(a), r, 1, n, n, n); + binary::vInvSqrt(), const_cast(a), r, 1, n, n, n); } DEFINE_MATRIX_BINARY_OP(vLog1p, b = std::log(1.0f + a)); -template +template void vLog1p(const int n, const T* a, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vLog1p(), const_cast(a), r, 1, n, n, n); + binary::vLog1p(), const_cast(a), r, 1, n, n, n); } -DEFINE_MATRIX_BINARY_OP(vTanh, - T tmp = -2.0 * a; - tmp = (tmp > EXP_MAX_INPUT) ? EXP_MAX_INPUT : tmp; - b = 2.0 / (1.0 + std::exp(tmp)) - 1.0); -template +DEFINE_MATRIX_BINARY_OP(vTanh, T tmp = -2.0 * a; + tmp = (tmp > EXP_MAX_INPUT) ? EXP_MAX_INPUT : tmp; + b = 2.0 / (1.0 + std::exp(tmp)) - 1.0); +template void vTanh(const int n, const T* a, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vTanh(), const_cast(a), r, 1, n, n, n); + binary::vTanh(), const_cast(a), r, 1, n, n, n); } DEFINE_MATRIX_BINARY_PARAMETER_OP(vPow, ONE_PARAMETER, b = std::pow(a, p)); -template +template void vPow(const int n, const T* a, const T b, T* r) { hl_cpu_apply_binary_op, 0, 0>( - binary::vPow(b), const_cast(a), r, 1, n, n, n); + binary::vPow(b), const_cast(a), r, 1, n, n, n); } DEFINE_MATRIX_TERNARY_OP(vAdd, c = a + b); -template +template void vAdd(const int n, const T* a, const T* b, T* r) { hl_cpu_apply_ternary_op, 0, 0>(ternary::vAdd(), - const_cast(a), const_cast(b), r, 1, n, n, n , n); + const_cast(a), + const_cast(b), + r, + 1, + n, + n, + n, + n); } template void vExp(const int n, const float* a, float* r); diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 29c07467c7..0741c45678 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -35,46 +35,58 @@ extern "C" { namespace paddle { -template -void gemm(const CBLAS_TRANSPOSE transA, const CBLAS_TRANSPOSE transB, - const int M, const int N, const int K, - const T alpha, const T* A, const int lda, - const T* B, const int ldb, - const T beta, T* C, const int ldc); - -template -int getrf(const CBLAS_ORDER Order, const int M, const int N, - T *A, const int lda, int *ipiv); - -template -int getri(const CBLAS_ORDER Order, const int N, T *A, - const int lda, const int *ipiv); - -template +template +void gemm(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE transB, + const int M, + const int N, + const int K, + const T alpha, + const T* A, + const int lda, + const T* B, + const int ldb, + const T beta, + T* C, + const int ldc); + +template +int getrf(const CBLAS_ORDER Order, + const int M, + const int N, + T* A, + const int lda, + int* ipiv); + +template +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); -template +template T dotProduct(const int n, const T* x, const T* y); -template +template void vExp(const int n, const T* a, T* r); -template +template void vPow(const int n, const T* a, const T b, T* r); -template +template void vLog(const int n, const T* a, T* r); -template +template void vAdd(const int n, const T* a, const T* b, T* r); -template +template void vInvSqrt(const int n, const T* a, T* r); -template +template void vLog1p(const int n, const T* a, T* r); -template +template void vTanh(const int n, const T* a, T* r); } // namespace paddle diff --git a/paddle/math/MathUtils.cpp b/paddle/math/MathUtils.cpp index 548f179363..878e0b8723 100644 --- a/paddle/math/MathUtils.cpp +++ b/paddle/math/MathUtils.cpp @@ -23,8 +23,8 @@ namespace paddle { * major is rows and minor is cols, according to * major value to initialize minor value" */ -void sparseRand(int* major, int* minor, int nnz, int majorLen, int minorMax, - bool useGpu) { +void sparseRand( + int* major, int* minor, int nnz, int majorLen, int minorMax, bool useGpu) { CHECK(size_t(nnz) > size_t(1)); int* cpuMajor; int* cpuMinor; @@ -57,7 +57,8 @@ void sparseRand(int* major, int* minor, int nnz, int majorLen, int minorMax, cpuMinor[j] = idx; used[idx] = 1; } - std::sort(cpuMinor + cpuMajor[i], cpuMinor + cpuMajor[i + 1], + std::sort(cpuMinor + cpuMajor[i], + cpuMinor + cpuMajor[i + 1], [](int a, int b) { return a < b; }); } /*memcpy result to gpu*/ @@ -67,8 +68,8 @@ void sparseRand(int* major, int* minor, int nnz, int majorLen, int minorMax, } } -int outputSize(int imageSize, int filterSize, int padding, int stride, - bool caffeMode) { +int outputSize( + int imageSize, int filterSize, int padding, int stride, bool caffeMode) { int outputSize; if (!caffeMode) { outputSize = @@ -80,14 +81,14 @@ int outputSize(int imageSize, int filterSize, int padding, int stride, return outputSize; } -int imageSize(int outputSize, int filterSize, int padding, int stride, - bool caffeMode) { +int imageSize( + int outputSize, int filterSize, int padding, int stride, bool caffeMode) { int imageSize; if (!caffeMode) { - imageSize = - (outputSize - 1) * stride + filterSize - 2 * padding - stride + 1; + imageSize = + (outputSize - 1) * stride + filterSize - 2 * padding - stride + 1; } else { - imageSize = (outputSize - 1) * stride + filterSize - 2 * padding; + imageSize = (outputSize - 1) * stride + filterSize - 2 * padding; } CHECK_GE(imageSize, 1); return imageSize; diff --git a/paddle/math/MathUtils.h b/paddle/math/MathUtils.h index 91683dc3e9..907116c002 100644 --- a/paddle/math/MathUtils.h +++ b/paddle/math/MathUtils.h @@ -41,8 +41,8 @@ namespace paddle { * * rows is [1, 3, 4, 0, 2, 4, 1, 2, 3, 4] */ -void sparseRand(int* major, int* minor, int nnz, int majorLen, int minorMax, - bool useGpu); +void sparseRand( + int* major, int* minor, int nnz, int majorLen, int minorMax, bool useGpu); /** * Calculate output size based on caffeMode_. @@ -57,14 +57,14 @@ void sparseRand(int* major, int* minor, int nnz, int majorLen, int minorMax, * - output: (012), (234), (456), (678), (9) * - outputSize = 5; */ -int outputSize(int imageSize, int filterSize, int padding, int stride, - bool caffeMode); +int outputSize( + int imageSize, int filterSize, int padding, int stride, bool caffeMode); /** * Calculate image size based on output size and caffeMode_. * It is the reverse function of outputSize() */ -int imageSize(int outputSize, int filterSize, int padding, int stride, - bool caffeMode); +int imageSize( + int outputSize, int filterSize, int padding, int stride, bool caffeMode); } // namespace paddle diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 706a598d0c..b70b47a5fc 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -40,58 +40,75 @@ inline real _square(real a) { return a * a; } inline real _safelog(real a) { return a > 0.0f ? std::log(a) : -40.0f; } -Matrix::Matrix(MemoryHandlePtr memHandle, size_t height, size_t width, - bool trans, bool use_gpu) +Matrix::Matrix(MemoryHandlePtr memHandle, + size_t height, + size_t width, + bool trans, + bool use_gpu) : BaseMatrix( - height, width, + height, + width, memHandle ? (reinterpret_cast(memHandle->getBuf())) : nullptr, - trans, use_gpu) { + trans, + use_gpu) { elementCnt_ = width * height; memoryHandle_ = memHandle; } -Matrix::Matrix(real* data, size_t height, size_t width, bool trans, - bool use_gpu) +Matrix::Matrix( + real* data, size_t height, size_t width, bool trans, bool use_gpu) : BaseMatrix(height, width, data, trans, use_gpu) { elementCnt_ = width * height; } -Matrix::Matrix(real* data, size_t height, size_t width, size_t stride, - bool trans, bool use_gpu) +Matrix::Matrix(real* data, + size_t height, + size_t width, + size_t stride, + bool trans, + bool use_gpu) : BaseMatrix(height, width, stride, data, trans, use_gpu) { elementCnt_ = width * height; } -MatrixPtr Matrix::createSparseMatrix(real* data, int* row, int* col, - size_t height, size_t width, +MatrixPtr Matrix::createSparseMatrix(real* data, + int* row, + int* col, + size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType, /*value type*/ - SparseFormat format, bool trans, + SparseFormat format, + bool trans, bool useGpu) { if (useGpu) { - return std::make_shared(data, row, col, height, width, nnz, - valueType, format, trans); + return std::make_shared( + data, row, col, height, width, nnz, valueType, format, trans); } else { - return std::make_shared(data, row, col, height, width, nnz, - valueType, format, trans); + return std::make_shared( + data, row, col, height, width, nnz, valueType, format, trans); } } -MatrixPtr Matrix::createSparseMatrix(size_t height, size_t width, +MatrixPtr Matrix::createSparseMatrix(size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType, /*value type*/ - SparseFormat format, bool trans, + SparseFormat format, + bool trans, bool useGpu) { if (useGpu) { - return std::make_shared(height, width, nnz, valueType, - format, trans); + return std::make_shared( + height, width, nnz, valueType, format, trans); } else { - return std::make_shared(height, width, nnz, valueType, - format, trans); + return std::make_shared( + height, width, nnz, valueType, format, trans); } } -MatrixPtr Matrix::create(MemoryHandlePtr memHandle, size_t height, size_t width, +MatrixPtr Matrix::create(MemoryHandlePtr memHandle, + size_t height, + size_t width, bool trans) { if (auto gpuHandle = std::dynamic_pointer_cast(memHandle)) { return std::make_shared(gpuHandle, height, width, trans); @@ -112,8 +129,8 @@ MatrixPtr Matrix::create(size_t height, size_t width, bool trans, bool useGpu) { } } -MatrixPtr Matrix::create(real* data, size_t height, size_t width, bool trans, - bool useGpu) { +MatrixPtr Matrix::create( + real* data, size_t height, size_t width, bool trans, bool useGpu) { if (useGpu) { return std::make_shared(data, height, width, trans); } else { @@ -121,8 +138,12 @@ MatrixPtr Matrix::create(real* data, size_t height, size_t width, bool trans, } } -MatrixPtr Matrix::create(real* data, size_t height, size_t width, size_t stride, - bool trans, bool useGpu) { +MatrixPtr Matrix::create(real* data, + size_t height, + size_t width, + size_t stride, + bool trans, + bool useGpu) { if (useGpu) { return std::make_shared(data, height, width, stride, trans); } else { @@ -130,20 +151,23 @@ MatrixPtr Matrix::create(real* data, size_t height, size_t width, size_t stride, } } -MatrixPtr Matrix::createSparseMatrix(size_t height, size_t width, size_t nnz, - SparseValueType valueType, bool trans, +MatrixPtr Matrix::createSparseMatrix(size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + bool trans, bool useGpu) { if (useGpu) { - return std::make_shared(height, width, nnz, valueType, - SPARSE_CSR, trans); + return std::make_shared( + height, width, nnz, valueType, SPARSE_CSR, trans); } else { - return std::make_shared(height, width, nnz, valueType, - SPARSE_CSR, trans); + return std::make_shared( + height, width, nnz, valueType, SPARSE_CSR, trans); } } -void Matrix::resizeOrCreate(MatrixPtr& matrix, size_t height, size_t width, - bool trans, bool useGpu) { +void Matrix::resizeOrCreate( + MatrixPtr& matrix, size_t height, size_t width, bool trans, bool useGpu) { if (!matrix) { matrix = Matrix::create(height, width, trans, useGpu); } else { @@ -152,14 +176,17 @@ void Matrix::resizeOrCreate(MatrixPtr& matrix, size_t height, size_t width, } } -void Matrix::resizeOrCreateSparseMatrix(MatrixPtr& matrix, size_t height, - size_t width, size_t nnz, +void Matrix::resizeOrCreateSparseMatrix(MatrixPtr& matrix, + size_t height, + size_t width, + size_t nnz, SparseValueType valueType, - SparseFormat format, bool trans, + SparseFormat format, + bool trans, bool useGpu) { if (!matrix) { - matrix = Matrix::createSparseMatrix(height, width, nnz, valueType, format, - trans, useGpu); + matrix = Matrix::createSparseMatrix( + height, width, nnz, valueType, format, trans, useGpu); } else { CHECK(dynamic_cast(matrix.get()) || dynamic_cast(matrix.get())); @@ -176,7 +203,9 @@ void Matrix::reshape(size_t height, size_t width) { stride_ = width_; } -MatrixPtr Matrix::subMatrix(size_t startRow, size_t endRow, size_t startCol, +MatrixPtr Matrix::subMatrix(size_t startRow, + size_t endRow, + size_t startCol, size_t endCol) { CHECK_LE(startRow, endRow); CHECK_LE(endRow, getHeight()); @@ -184,8 +213,11 @@ MatrixPtr Matrix::subMatrix(size_t startRow, size_t endRow, size_t startCol, CHECK_LE(endCol, getWidth()); return Matrix::create(getData() + startRow * getStride() + startCol, - endRow - startRow, endCol - startCol, getStride(), - trans_, useGpu_); + endRow - startRow, + endCol - startCol, + getStride(), + trans_, + useGpu_); } void Matrix::setDiag(real value) { @@ -199,7 +231,10 @@ void Matrix::setDiag(real value) { GpuMatrix::GpuMatrix(size_t height, size_t width, bool trans) : Matrix(std::make_shared(height * width * sizeof(real)), - height, width, trans, true) {} + height, + width, + trans, + true) {} GpuMatrix::~GpuMatrix() {} @@ -258,11 +293,11 @@ void GpuMatrix::copyFrom(const Matrix& src) { CHECK(elementCnt_ == src.getElementCnt()); if (typeid(src) == typeid(CpuMatrix)) { - hl_memcpy_host2device(data_, const_cast(src.getData()), - sizeof(real) * elementCnt_); + hl_memcpy_host2device( + data_, const_cast(src.getData()), sizeof(real) * elementCnt_); } else if (typeid(src) == typeid(GpuMatrix)) { - hl_memcpy_device2device(data_, const_cast(src.getData()), - sizeof(real) * elementCnt_); + hl_memcpy_device2device( + data_, const_cast(src.getData()), sizeof(real) * elementCnt_); } else { LOG(FATAL) << "Wrong"; } @@ -272,8 +307,10 @@ void GpuMatrix::copyFrom(const Matrix& src, hl_stream_t stream) { CHECK(isContiguous()); CHECK(src.isContiguous()); CHECK(elementCnt_ == src.getElementCnt()); - hl_memcpy_async(this->getData(), const_cast(src.getData()), - sizeof(real) * elementCnt_, stream); + hl_memcpy_async(this->getData(), + const_cast(src.getData()), + sizeof(real) * elementCnt_, + stream); } void GpuMatrix::copyFrom(const real* hostSrc, size_t size) { @@ -324,7 +361,9 @@ MatrixPtr GpuMatrix::getTranspose() { if (memoryHandle_.get() != NULL) { MatrixPtr copy_T( new GpuMatrix(std::dynamic_pointer_cast(memoryHandle_), - height_, width_, true)); + height_, + width_, + true)); return copy_T; } else { MatrixPtr copy_T(new GpuMatrix(data_, height_, width_, true)); @@ -346,7 +385,6 @@ void GpuMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { hl_matrix_transpose(data, dataTrans, height_, width_, lda, ldc); } - MatrixPtr GpuMatrix::getInverse() { MatrixPtr matInv; inverse(matInv, true); @@ -379,17 +417,16 @@ void GpuMatrix::addSharedBias(Matrix& b, real scale) { CHECK(b.getHeight() == 1) << "the Bias should be a vector"; CHECK_LE(b.getWidth(), getWidth()); CHECK_EQ(getWidth() % b.getWidth(), 0UL); - hl_matrix_add_shared_bias(getData(), b.getData(), b.getWidth(), - getHeight(), getWidth(), scale); + hl_matrix_add_shared_bias( + getData(), b.getData(), b.getWidth(), getHeight(), getWidth(), scale); } - void GpuMatrix::collectBias(Matrix& a, real scale) { CHECK_EQ(getHeight(), (size_t)1); CHECK_EQ(width_, a.getWidth()); GpuSparseMatrix* sMatPtr = dynamic_cast(&a); if (!sMatPtr) { - sumCols(a, /* scaleSum= */scale, /* scaleDest= */1); + sumCols(a, /* scaleSum= */ scale, /* scaleDest= */ 1); } else { real* data = getData(); hl_sparse_matrix_s A_d = sMatPtr->sMatrix_.get(); @@ -397,15 +434,13 @@ void GpuMatrix::collectBias(Matrix& a, real scale) { } } - void GpuMatrix::collectSharedBias(Matrix& a, real scale) { CHECK_EQ(getHeight(), (size_t)1); CHECK_EQ(a.getWidth() % getWidth(), 0UL); - hl_matrix_collect_shared_bias(getData(), a.getData(), getWidth(), - a.getHeight(), a.getWidth(), scale); + hl_matrix_collect_shared_bias( + getData(), a.getData(), getWidth(), a.getHeight(), a.getWidth(), scale); } - void GpuMatrix::sequenceAvgForward(Matrix& a, const IVector& startsPos, int mode) { @@ -421,7 +456,9 @@ void GpuMatrix::sequenceAvgForward(Matrix& a, } /* this = scaleAB*(a*b) + scaleT*this */ -void GpuMatrix::mul(const GpuMatrix& a, const GpuMatrix& b, real scaleAB, +void GpuMatrix::mul(const GpuMatrix& a, + const GpuMatrix& b, + real scaleAB, real scaleT) { CHECK(!isTransposed()) << "Not supported"; @@ -453,11 +490,24 @@ void GpuMatrix::mul(const GpuMatrix& a, const GpuMatrix& b, real scaleAB, hl_trans_op_t transa = !a.isTransposed() ? HPPL_OP_N : HPPL_OP_T; hl_trans_op_t transb = !b.isTransposed() ? HPPL_OP_N : HPPL_OP_T; - hl_matrix_mul(A_d, transa, B_d, transb, C_d, dimM, dimN, dimK, scaleAB, - scaleT, lda, ldb, ldc); -} - -void GpuMatrix::mul(const GpuSparseMatrix& a, const GpuMatrix& b, real scaleAB, + hl_matrix_mul(A_d, + transa, + B_d, + transb, + C_d, + dimM, + dimN, + dimK, + scaleAB, + scaleT, + lda, + ldb, + ldc); +} + +void GpuMatrix::mul(const GpuSparseMatrix& a, + const GpuMatrix& b, + real scaleAB, real scaleT) { CHECK(isContiguous()); CHECK(b.isContiguous()); @@ -475,11 +525,21 @@ void GpuMatrix::mul(const GpuSparseMatrix& a, const GpuMatrix& b, real scaleAB, hl_sparse_matrix_s A_d = a.sMatrix_.get(); real* B_d = b.data_; real* C_d = data_; - hl_matrix_csr_mul_dense(A_d, transA, B_d, HPPL_OP_N, C_d, height_, width_, - b.height_, scaleAB, scaleT); -} - -void GpuMatrix::mul(const GpuMatrix& a, const GpuSparseMatrix& b, real scaleAB, + hl_matrix_csr_mul_dense(A_d, + transA, + B_d, + HPPL_OP_N, + C_d, + height_, + width_, + b.height_, + scaleAB, + scaleT); +} + +void GpuMatrix::mul(const GpuMatrix& a, + const GpuSparseMatrix& b, + real scaleAB, real scaleT) { CHECK(isContiguous()); CHECK(a.isContiguous()); @@ -497,11 +557,27 @@ void GpuMatrix::mul(const GpuMatrix& a, const GpuSparseMatrix& b, real scaleAB, << "Matrix dimensions are not equal"; } if (b.format_ == SPARSE_CSC) { - hl_matrix_dense_mul_csc(A_d, HPPL_OP_N, B_d, transB, C_d, height_, width_, - a.width_, scaleAB, scaleT); + hl_matrix_dense_mul_csc(A_d, + HPPL_OP_N, + B_d, + transB, + C_d, + height_, + width_, + a.width_, + scaleAB, + scaleT); } else { - hl_matrix_dense_mul_csr(A_d, HPPL_OP_N, B_d, transB, C_d, height_, width_, - a.width_, scaleAB, scaleT); + hl_matrix_dense_mul_csr(A_d, + HPPL_OP_N, + B_d, + transB, + C_d, + height_, + width_, + a.width_, + scaleAB, + scaleT); } } @@ -510,7 +586,9 @@ void GpuMatrix::mul(const MatrixPtr a, const MatrixPtr b) { mul(a, b, 1.0, 0.0); } -void GpuMatrix::mul(const MatrixPtr a, const MatrixPtr b, real scaleAB, +void GpuMatrix::mul(const MatrixPtr a, + const MatrixPtr b, + real scaleAB, real scaleT) { GpuMatrixPtr a_ptr = std::dynamic_pointer_cast(a); GpuMatrixPtr b_ptr = std::dynamic_pointer_cast(b); @@ -563,8 +641,14 @@ void GpuMatrix::selectRows(Matrix& table, IVector& ids) { size_t tableSize = table.getHeight(); int* index = ids.getData(); - hl_matrix_select_rows(a, stride_, table.getData(), table.stride_, index, - numSamples, tableSize, dim); + hl_matrix_select_rows(a, + stride_, + table.getData(), + table.stride_, + index, + numSamples, + tableSize, + dim); #endif } @@ -581,15 +665,21 @@ void GpuMatrix::addToRows(Matrix& table, IVector& ids) { size_t tableSize = table.getHeight(); int* index = ids.getData(); - hl_matrix_add_to_rows(table.getData(), table.stride_, a, stride_, index, - numSamples, tableSize, dim); + hl_matrix_add_to_rows(table.getData(), + table.stride_, + a, + stride_, + index, + numSamples, + tableSize, + dim); #endif } void GpuMatrix::colMerge(Matrix& src) { CHECK(src.height_ == height_); if (!trans_ && !src.trans_) { - sumRows(src, /* scaleSum= */1, /* scaleDest= */0); + sumRows(src, /* scaleSum= */ 1, /* scaleDest= */ 0); } else { LOG(FATAL) << "Is not supported"; } @@ -599,7 +689,7 @@ void GpuMatrix::rowSum(Matrix& sum) { CHECK_EQ(sum.getHeight(), getHeight()); CHECK_EQ(sum.getWidth(), (size_t)1); - sum.sumRows(*this, /* scaleSum= */1, /* scaleDest= */0); + sum.sumRows(*this, /* scaleSum= */ 1, /* scaleDest= */ 0); } void GpuMatrix::rowMax(Matrix& max) { @@ -617,8 +707,13 @@ void GpuMatrix::rowMax(IVector& maxIds, Matrix& maxVal) { CHECK_EQ(maxIds.getSize(), numSamples * beam); CHECK_EQ(maxVal.getHeight(), numSamples); - hl_matrix_top_k(maxVal.getData(), maxVal.getStride(), maxIds.getData(), - this->getData(), this->getStride(), this->getWidth(), beam, + hl_matrix_top_k(maxVal.getData(), + maxVal.getStride(), + maxIds.getData(), + this->getData(), + this->getStride(), + this->getWidth(), + beam, numSamples); #endif } @@ -634,7 +729,9 @@ void GpuMatrix::colMax(IVector& maxIds, Matrix& maxVal) { LOG(FATAL) << "Is not supported"; } -void GpuMatrix::maxoutForward(Matrix& a, IVector& id, size_t channels, +void GpuMatrix::maxoutForward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { CHECK(dynamic_cast(&a)); CHECK(dynamic_cast(&id)); @@ -646,11 +743,13 @@ void GpuMatrix::maxoutForward(Matrix& a, IVector& id, size_t channels, real* output = getData(); int* idForGpu = id.getData(); - hl_maxout_forward(input, output, idForGpu, batchSize, size, size / channels, - groups); + hl_maxout_forward( + input, output, idForGpu, batchSize, size, size / channels, groups); } -void GpuMatrix::maxoutBackward(Matrix& a, IVector& id, size_t channels, +void GpuMatrix::maxoutBackward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { CHECK(dynamic_cast(&a)); CHECK(dynamic_cast(&id)); @@ -662,8 +761,8 @@ void GpuMatrix::maxoutBackward(Matrix& a, IVector& id, size_t channels, const real* output = a.getData(); const int* idForGpu = id.getData(); - hl_maxout_backward(input, output, idForGpu, batchSize, size, size / channels, - groups); + hl_maxout_backward( + input, output, idForGpu, batchSize, size, size / channels, groups); } /*calulate the error of classification */ @@ -679,8 +778,8 @@ void GpuMatrix::classificationError(MatrixPtr output, IVectorPtr label) { real* recResult_d = data_; int* label_d = label_ptr->getData(); - hl_matrix_classification_error(output_d, label_d, recResult_d, height_, - output_ptr->width_); + hl_matrix_classification_error( + output_d, label_d, recResult_d, height_, output_ptr->width_); } /* copy -log(output[i * width + label]) to this->data[i] */ @@ -717,13 +816,15 @@ void GpuMatrix::oneHotCrossEntropyBp(Matrix& outputV, IVector& label) { hl_matrix_cross_entropy_bp(grad_d, output_d, label_d, height_, width_); } -void GpuMatrix::oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, +void GpuMatrix::oneHotCrossEntropyWithSelfNorm(Matrix& output, + IVector& label, real alpha) { LOG(FATAL) << "Not implemented"; } void GpuMatrix::oneHotCrossEntropyWithSelfNormBp(Matrix& outputV, - IVector& label, real alpha) { + IVector& label, + real alpha) { LOG(FATAL) << "Not implemented"; } @@ -790,8 +891,10 @@ void GpuMatrix::sumOfSquares(Matrix& output, Matrix& label) { LOG(FATAL) << "not supported: GpuSparseMatrix as label"; } - BaseMatrix::sumOfSquaredDiffs(output, label, - /* scaleSum= */1, /* scaleDest= */1); + BaseMatrix::sumOfSquaredDiffs(output, + label, + /* scaleSum= */ 1, + /* scaleDest= */ 1); } void GpuMatrix::sumOfSquaresBp(Matrix& outputV, Matrix& label) { @@ -826,9 +929,12 @@ void GpuMatrix::cosSim(Matrix& output1, Matrix& output2, real scale) { real* y = output2.getData(); hl_cossim(out, x, y, dim, output1.getHeight(), output2.getHeight(), scale); } -void GpuMatrix::cosSimDerivative(Matrix& output, Matrix& prevOut1, - Matrix& prevOut2, Matrix& prevGrad1, - Matrix& prevGrad2, real scale) { +void GpuMatrix::cosSimDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, + Matrix& prevGrad2, + real scale) { CHECK(output.useGpu_ == true && prevOut1.useGpu_ == true && prevOut2.useGpu_ == true && prevGrad1.useGpu_ == true && prevGrad2.useGpu_ == true) @@ -852,8 +958,16 @@ void GpuMatrix::cosSimDerivative(Matrix& output, Matrix& prevOut1, real* prevOutY = prevOut2.getData(); real* prevGradX = prevGrad1.getData(); real* prevGradY = prevGrad2.getData(); - hl_cossim_derivative(grad, out, prevOutX, prevOutY, prevGradX, prevGradY, dim, - prevOut1.getHeight(), prevOut2.getHeight(), scale); + hl_cossim_derivative(grad, + out, + prevOutX, + prevOutY, + prevGradX, + prevGradY, + dim, + prevOut1.getHeight(), + prevOut2.getHeight(), + scale); } void GpuMatrix::randomizeUniform() { @@ -902,9 +1016,17 @@ void GpuMatrix::check(std::ostream& os, Matrix& refMat, bool printDiff) { LOG(INFO) << "the diffCnt is " << diffCnt; } -void GpuMatrix::convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, - int channels, int blockH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, int outputH, +void GpuMatrix::convExpand(Matrix& feature, + int feaImgHeight, + int feaImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, int outputW) { CHECK(feature.useGpu_ == true) << "Matrix type are not equal"; @@ -915,15 +1037,34 @@ void GpuMatrix::convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, size_t elemCnt = outputH * outputW * blockH * blockW * channels; CHECK_EQ(elemCnt, height_ * width_) << "Matrix dimensions are not equal"; - hl_expand_feature2col(feature.getData(), channels, feaImgHeight, feaImgWidth, - blockH, blockW, strideH, strideW, paddingH, paddingW, - outputH, outputW, getData()); -} - -void GpuMatrix::convShrink(Matrix& expandFeat, int thisImgHeight, - int thisImgWidth, int channels, int blockH, - int blockW, int strideH, int strideW, int paddingH, - int paddingW, int outputH, int outputW, real alpha, + hl_expand_feature2col(feature.getData(), + channels, + feaImgHeight, + feaImgWidth, + blockH, + blockW, + strideH, + strideW, + paddingH, + paddingW, + outputH, + outputW, + getData()); +} + +void GpuMatrix::convShrink(Matrix& expandFeat, + int thisImgHeight, + int thisImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW, + real alpha, real beta) { CHECK(expandFeat.useGpu_ == true) << "Matrix type are not equal"; CHECK_EQ(size_t(thisImgHeight * thisImgWidth * channels), @@ -933,16 +1074,34 @@ void GpuMatrix::convShrink(Matrix& expandFeat, int thisImgHeight, size_t elemCnt = outputH * outputW * blockW * blockH * channels; CHECK(elemCnt == expandFeat.getHeight() * expandFeat.getWidth()) << "Matrix dimensions are not equal"; - hl_shrink_col2feature(expandFeat.getData(), channels, thisImgHeight, - thisImgWidth, blockH, blockW, strideH, strideW, - paddingH, paddingW, outputH, outputW, getData(), alpha, + hl_shrink_col2feature(expandFeat.getData(), + channels, + thisImgHeight, + thisImgWidth, + blockH, + blockW, + strideH, + strideW, + paddingH, + paddingW, + outputH, + outputW, + getData(), + alpha, beta); } -void GpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, - size_t imgSizeW, size_t channels, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, size_t paddingH, +void GpuMatrix::maxPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, size_t paddingW) { CHECK(inputMat.useGpu_ == true) << "Matrix type are not equal"; @@ -954,17 +1113,38 @@ void GpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, CHECK(height_ == inputMat.getHeight()); CHECK(width_ == outputH * outputW * channels); - hl_maxpool_forward(frameNum, inputData, channels, height, width, outputH, - outputW, sizeX, sizeY, strideH, strideW, paddingH, - paddingW, data_, getStride()); -} - -void GpuMatrix::maxPoolBackward(Matrix& inputMat, size_t imgSizeH, - size_t imgSizeW, Matrix& outGrad, Matrix& outV, - size_t sizeX, size_t sizeY, size_t strideH, - size_t strideW, size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW) { + hl_maxpool_forward(frameNum, + inputData, + channels, + height, + width, + outputH, + outputW, + sizeX, + sizeY, + strideH, + strideW, + paddingH, + paddingW, + data_, + getStride()); +} + +void GpuMatrix::maxPoolBackward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + Matrix& outGrad, + Matrix& outV, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW) { CHECK(inputMat.useGpu_ == true && outGrad.useGpu_ == true && outV.useGpu_ == true) << "Matrix type are not equal"; @@ -982,16 +1162,38 @@ void GpuMatrix::maxPoolBackward(Matrix& inputMat, size_t imgSizeH, CHECK(outGrad.getHeight() == outV.getHeight() && outGrad.getWidth() == outV.getWidth()); - hl_maxpool_backward(frameNum, inputData, outData, outDiff, channels, height, - width, outputH, outputW, sizeX, sizeY, strideH, strideW, - paddingH, paddingW, scaleTargets, scaleOutput, data_, + hl_maxpool_backward(frameNum, + inputData, + outData, + outDiff, + channels, + height, + width, + outputH, + outputW, + sizeX, + sizeY, + strideH, + strideW, + paddingH, + paddingW, + scaleTargets, + scaleOutput, + data_, outGrad.getStride()); } -void GpuMatrix::avgPoolForward(Matrix& inputMat, size_t imgSizeH, - size_t imgSizeW, size_t channels, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, size_t paddingH, +void GpuMatrix::avgPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, size_t paddingW) { CHECK(inputMat.useGpu_ == true) << "Matrix type are not equal"; @@ -1003,16 +1205,35 @@ void GpuMatrix::avgPoolForward(Matrix& inputMat, size_t imgSizeH, CHECK(height_ == inputMat.getHeight()); CHECK(width_ == outputH * outputW * channels); - hl_avgpool_forward(frameNum, inputData, channels, height, width, outputH, - outputW, sizeX, sizeY, strideH, strideW, paddingH, - paddingW, data_, getStride()); -} - -void GpuMatrix::avgPoolBackward(Matrix& outGrad, size_t imgSizeH, - size_t imgSizeW, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, size_t outputH, - size_t outputW, real scaleTargets, - real scaleOutput, size_t paddingH, + hl_avgpool_forward(frameNum, + inputData, + channels, + height, + width, + outputH, + outputW, + sizeX, + sizeY, + strideH, + strideW, + paddingH, + paddingW, + data_, + getStride()); +} + +void GpuMatrix::avgPoolBackward(Matrix& outGrad, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, size_t paddingW) { CHECK(outGrad.useGpu_ == true) << "Matrix type are not equal"; @@ -1025,15 +1246,32 @@ void GpuMatrix::avgPoolBackward(Matrix& outGrad, size_t imgSizeH, CHECK(height_ == outGrad.getHeight()); CHECK(outGrad.getWidth() == outputH * outputW * channels); - hl_avgpool_backward(frameNum, outDiff, channels, height, width, outputH, - outputW, sizeX, sizeY, strideH, strideW, paddingH, - paddingW, scaleTargets, scaleOutput, data_, + hl_avgpool_backward(frameNum, + outDiff, + channels, + height, + width, + outputH, + outputW, + sizeX, + sizeY, + strideH, + strideW, + paddingH, + paddingW, + scaleTargets, + scaleOutput, + data_, outGrad.getStride()); } -void GpuMatrix::crossMapNormalFwd(Matrix& input, size_t imgSizeH, - size_t imgSizeW, Matrix& denoms, - size_t channels, size_t sizeX, float scale, +void GpuMatrix::crossMapNormalFwd(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + Matrix& denoms, + size_t channels, + size_t sizeX, + float scale, float pow) { size_t num = input.getHeight(); size_t height = imgSizeH; @@ -1043,14 +1281,27 @@ void GpuMatrix::crossMapNormalFwd(Matrix& input, size_t imgSizeH, CHECK(denoms.getHeight() == input.getHeight() && denoms.getWidth() == input.getWidth() && input.getHeight() == height_ && input.getWidth() == width_); - hl_CMRNorm_forward(num, input.getData(), denoms.getData(), data_, channels, - height, width, sizeX, scale, -pow); -} - -void GpuMatrix::crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, - Matrix& preOutV, Matrix& localOutV, - size_t channels, size_t imgSizeH, - size_t imgSizeW, size_t sizeX, float scale, + hl_CMRNorm_forward(num, + input.getData(), + denoms.getData(), + data_, + channels, + height, + width, + sizeX, + scale, + -pow); +} + +void GpuMatrix::crossMapNormalBwd(Matrix& localGrad, + Matrix& denoms, + Matrix& preOutV, + Matrix& localOutV, + size_t channels, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + float scale, float pow) { size_t num = preOutV.getHeight(); size_t height = imgSizeH; @@ -1063,12 +1314,22 @@ void GpuMatrix::crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, CHECK(denoms.getHeight() == localGrad.getHeight() && denoms.getWidth() == localGrad.getWidth()); - hl_CMRNorm_backward(num, preOutV.getData(), denoms.getData(), - localOutV.getData(), localGrad.getData(), data_, channels, - height, width, sizeX, -pow, 2.0f * pow * scale); -} - -void GpuMatrix::maxSequenceForward(Matrix& input, const IVector& sequence, + hl_CMRNorm_backward(num, + preOutV.getData(), + denoms.getData(), + localOutV.getData(), + localGrad.getData(), + data_, + channels, + height, + width, + sizeX, + -pow, + 2.0f * pow * scale); +} + +void GpuMatrix::maxSequenceForward(Matrix& input, + const IVector& sequence, IVector& index) { CHECK(dynamic_cast(&input)); CHECK(dynamic_cast(&sequence)); @@ -1085,11 +1346,12 @@ void GpuMatrix::maxSequenceForward(Matrix& input, const IVector& sequence, CHECK_EQ(numSequences, sequence.getSize() - 1); CHECK_EQ(numSequences * dim, index.getSize()); - hl_max_sequence_forward(inputData, starts, outData, maxIndex, numSequences, - dim); + hl_max_sequence_forward( + inputData, starts, outData, maxIndex, numSequences, dim); } -void GpuMatrix::maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, +void GpuMatrix::maxSequenceBackward(Matrix& outputGrad, + const IVector& sequence, IVector& index) { CHECK(dynamic_cast(&outputGrad)); CHECK(dynamic_cast(&sequence)); @@ -1108,10 +1370,13 @@ void GpuMatrix::maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, hl_max_sequence_backward(outGrad, maxIndex, inputGrad, numSequences, dim); } -void GpuMatrix::contextProjectionForward(MatrixPtr input, MatrixPtr weight, +void GpuMatrix::contextProjectionForward(MatrixPtr input, + MatrixPtr weight, const IVector& sequence, - int contextLength, int contextStart, - size_t beginPad, bool isPadding) { + int contextLength, + int contextStart, + size_t beginPad, + bool isPadding) { CHECK(dynamic_cast(input.get())); CHECK(dynamic_cast(&sequence)); if (weight) CHECK(dynamic_cast(weight.get())); @@ -1125,9 +1390,16 @@ void GpuMatrix::contextProjectionForward(MatrixPtr input, MatrixPtr weight, real* inputData = input->getData(); const int* starts = sequence.getData(); - hl_context_projection_forward( - inputData, starts, isPadding ? weight->getData() : NULL, outData, - numSequences, inputDim, contextLength, contextStart, beginPad, isPadding); + hl_context_projection_forward(inputData, + starts, + isPadding ? weight->getData() : NULL, + outData, + numSequences, + inputDim, + contextLength, + contextStart, + beginPad, + isPadding); } void GpuMatrix::contextProjectionBackwardData(MatrixPtr inputGrad, @@ -1146,14 +1418,20 @@ void GpuMatrix::contextProjectionBackwardData(MatrixPtr inputGrad, real* inGrad = inputGrad->getData(); const int* starts = sequence.getData(); - hl_context_projection_backward_data(outGrad, starts, inGrad, numSequences, - inputDim, contextLength, contextStart); + hl_context_projection_backward_data(outGrad, + starts, + inGrad, + numSequences, + inputDim, + contextLength, + contextStart); } void GpuMatrix::contextProjectionBackwardWeight(MatrixPtr weightGrad, const IVector& sequence, int contextLength, - int contextStart, int totalPad, + int contextStart, + int totalPad, size_t beginPad) { CHECK(dynamic_cast(weightGrad.get())); CHECK(dynamic_cast(&sequence)); @@ -1167,9 +1445,15 @@ void GpuMatrix::contextProjectionBackwardWeight(MatrixPtr weightGrad, real* wtGrad = weightGrad->getData(); const int* starts = sequence.getData(); - hl_context_projection_backward_weight(outGrad, starts, wtGrad, numSequences, - weightDim, totalPad, contextLength, - contextStart, beginPad); + hl_context_projection_backward_weight(outGrad, + starts, + wtGrad, + numSequences, + weightDim, + totalPad, + contextLength, + contextStart, + beginPad); } void GpuMatrix::paramReluForward(Matrix& data, Matrix& W) { @@ -1193,8 +1477,8 @@ void GpuMatrix::paramReluBackwardW(Matrix& oGrad, Matrix& data) { size_t numElements = data.getWidth(); size_t numSamples = data.getHeight(); size_t partial_sum = numElements / (this->getHeight() * this->getWidth()); - hl_param_relu_backward_w(wgrad, ograd, input, numElements, numSamples, - partial_sum); + hl_param_relu_backward_w( + wgrad, ograd, input, numElements, numSamples, partial_sum); } void GpuMatrix::paramReluBackwardDiff(Matrix& oGrad, Matrix& data, Matrix& W) { @@ -1205,8 +1489,8 @@ void GpuMatrix::paramReluBackwardDiff(Matrix& oGrad, Matrix& data, Matrix& W) { size_t numElements = data.getWidth(); size_t numSamples = data.getHeight(); size_t partial_sum = numElements / (W.getHeight() * W.getWidth()); - hl_param_relu_backward_diff(ograd, input, w, diff, numElements, numSamples, - partial_sum); + hl_param_relu_backward_diff( + ograd, input, w, diff, numElements, numSamples, partial_sum); } void GpuMatrix::addColumnVector(const Matrix& b) { @@ -1229,15 +1513,24 @@ void GpuMatrix::bilinearForward(const Matrix& in, const size_t inputH = in.getHeight(); real* outData = getData(); - const real* inData = in.getData(); + const real* inData = in.getData(); if (inImgH == outImgW && inImgW == outImgW) { this->copyFrom(in); } else { - hl_bilinear_forward( - inData, inImgH, inImgW, inputH, inputW, outData, - outImgH, outImgW, outputH, outputW, numChannels, - ratioH, ratioW); + hl_bilinear_forward(inData, + inImgH, + inImgW, + inputH, + inputW, + outData, + outImgH, + outImgW, + outputH, + outputW, + numChannels, + ratioH, + ratioW); } } @@ -1262,47 +1555,56 @@ void GpuMatrix::bilinearBackward(const Matrix& out, if (outImgH == inImgH && outImgW == inImgW) { this->add(const_cast(out)); } else { - hl_bilinear_backward( - inGrad, inImgH, inImgW, inputH, inputW, outGrad, - outImgH, outImgW, outputH, outputW, numChannels, - ratioH, ratioW); + hl_bilinear_backward(inGrad, + inImgH, + inImgW, + inputH, + inputW, + outGrad, + outImgH, + outImgW, + outputH, + outputW, + numChannels, + ratioH, + ratioW); } } void GpuMatrix::multiBinaryLabelCrossEntropy(Matrix& output, Matrix& label) { - GpuMatrix* outputPtr = dynamic_cast(&output); - auto labelPtr = dynamic_cast(&label); - - CHECK(outputPtr && labelPtr) << "Invalid argument pointer"; - CHECK(labelPtr->format_ == SPARSE_CSR) << "Matrix format not supported"; - CHECK(height_ == outputPtr->height_ && width_ == 1 - && outputPtr->width_ == labelPtr->getWidth() - && outputPtr->height_ == labelPtr->getHeight()) - << "Matrix dimensions are not equal"; + GpuMatrix* outputPtr = dynamic_cast(&output); + auto labelPtr = dynamic_cast(&label); + + CHECK(outputPtr && labelPtr) << "Invalid argument pointer"; + CHECK(labelPtr->format_ == SPARSE_CSR) << "Matrix format not supported"; + CHECK(height_ == outputPtr->height_ && width_ == 1 && + outputPtr->width_ == labelPtr->getWidth() && + outputPtr->height_ == labelPtr->getHeight()) + << "Matrix dimensions are not equal"; - real* output_d = outputPtr->data_; - real* entropy_d = data_; - hl_sparse_matrix_s mat_d = labelPtr->sMatrix_.get(); - hl_matrix_multi_binary_cross_entropy( - output_d, entropy_d, mat_d, height_, outputPtr->width_); + real* output_d = outputPtr->data_; + real* entropy_d = data_; + hl_sparse_matrix_s mat_d = labelPtr->sMatrix_.get(); + hl_matrix_multi_binary_cross_entropy( + output_d, entropy_d, mat_d, height_, outputPtr->width_); } -void GpuMatrix::multiBinaryLabelCrossEntropyBp(Matrix &output, Matrix &label) { - GpuMatrix* outputPtr = dynamic_cast(&output); - auto labelPtr = dynamic_cast(&label); +void GpuMatrix::multiBinaryLabelCrossEntropyBp(Matrix& output, Matrix& label) { + GpuMatrix* outputPtr = dynamic_cast(&output); + auto labelPtr = dynamic_cast(&label); - CHECK(outputPtr && labelPtr) << "Invalid argument pointer"; - CHECK(labelPtr->format_ == SPARSE_CSR) << "Matrix format not supported"; - CHECK(height_ == outputPtr->height_ && width_ == outputPtr->width_ - && outputPtr->width_ == labelPtr->getWidth() - && outputPtr->height_ == labelPtr->getHeight()) - << "Matrix dimensions are not equal"; + CHECK(outputPtr && labelPtr) << "Invalid argument pointer"; + CHECK(labelPtr->format_ == SPARSE_CSR) << "Matrix format not supported"; + CHECK(height_ == outputPtr->height_ && width_ == outputPtr->width_ && + outputPtr->width_ == labelPtr->getWidth() && + outputPtr->height_ == labelPtr->getHeight()) + << "Matrix dimensions are not equal"; - real* output_d = outputPtr->data_; - real* grad_d = data_; - hl_sparse_matrix_s mat_d = labelPtr->sMatrix_.get(); - hl_matrix_multi_binary_cross_entropy_bp( - output_d, grad_d, mat_d, height_, width_); + real* output_d = outputPtr->data_; + real* grad_d = data_; + hl_sparse_matrix_s mat_d = labelPtr->sMatrix_.get(); + hl_matrix_multi_binary_cross_entropy_bp( + output_d, grad_d, mat_d, height_, width_); } /** @@ -1311,7 +1613,10 @@ void GpuMatrix::multiBinaryLabelCrossEntropyBp(Matrix &output, Matrix &label) { CpuMatrix::CpuMatrix(size_t height, size_t width, bool trans) : Matrix(std::make_shared(height * width * sizeof(real)), - height, width, trans, false) {} + height, + width, + trans, + false) {} CpuMatrix::~CpuMatrix() {} @@ -1333,8 +1638,8 @@ void CpuMatrix::copyFrom(const Matrix& src) { if (typeid(src) == typeid(GpuMatrix)) { CHECK(src.isContiguous()); CHECK(elementCnt_ == src.getElementCnt()); - hl_memcpy_device2host(data_, const_cast(src.getData()), - sizeof(real) * elementCnt_); + hl_memcpy_device2host( + data_, const_cast(src.getData()), sizeof(real) * elementCnt_); } else if (typeid(src) == typeid(CpuMatrix) || typeid(src) == typeid(SharedCpuMatrix)) { CHECK(src.isContiguous()); @@ -1399,8 +1704,10 @@ void CpuMatrix::copyFrom(const Matrix& src, hl_stream_t stream) { CHECK(src.isContiguous()); CHECK(elementCnt_ == src.getElementCnt()); if (typeid(src) == typeid(GpuMatrix)) { - hl_memcpy_async(this->getData(), const_cast(src.getData()), - sizeof(real) * elementCnt_, stream); + hl_memcpy_async(this->getData(), + const_cast(src.getData()), + sizeof(real) * elementCnt_, + stream); } else if (typeid(src) == typeid(CpuMatrix)) { memcpy(data_, src.getData(), sizeof(real) * elementCnt_); } else { @@ -1502,7 +1809,7 @@ void CpuMatrix::accumulateColSum(Matrix& src) { CHECK_EQ(getWidth(), src.getWidth()); CHECK_EQ(getHeight(), (size_t)1); - sumCols(src, /* scaleSum= */1, /* scaleDest= */1); + sumCols(src, /* scaleSum= */ 1, /* scaleDest= */ 1); } real CpuMatrix::getAbsSum() { @@ -1519,8 +1826,10 @@ real CpuMatrix::getAbsSum() { MatrixPtr CpuMatrix::getTranspose() { if (memoryHandle_.get() != NULL) { return std::make_shared( - std::dynamic_pointer_cast(memoryHandle_), height_, - width_, true); + std::dynamic_pointer_cast(memoryHandle_), + height_, + width_, + true); } else { MatrixPtr copy_T(new CpuMatrix(data_, height_, width_, true)); return copy_T; @@ -1545,7 +1854,6 @@ void CpuMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { } } - MatrixPtr CpuMatrix::getInverse() { MatrixPtr matInv; inverse(matInv, true); @@ -1586,9 +1894,17 @@ void CpuMatrix::inverse(MatrixPtr matInv, bool memAlloc) { CHECK_EQ(info, 0); } -void CpuMatrix::convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, - int channels, int blockH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, int outputH, +void CpuMatrix::convExpand(Matrix& feature, + int feaImgHeight, + int feaImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, int outputW) { CHECK(feature.useGpu_ == false) << "Matrix type are not equal"; @@ -1626,10 +1942,19 @@ void CpuMatrix::convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, } } -void CpuMatrix::convShrink(Matrix& expandFeat, int thisImgHeight, - int thisImgWidth, int channels, int blockH, - int blockW, int strideH, int strideW, int paddingH, - int paddingW, int outputH, int outputW, real alpha, +void CpuMatrix::convShrink(Matrix& expandFeat, + int thisImgHeight, + int thisImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW, + real alpha, real beta) { CHECK(expandFeat.useGpu_ == false) << "Matrix type are not equal"; CHECK_EQ(size_t(thisImgHeight * thisImgWidth * channels), @@ -1666,10 +1991,17 @@ void CpuMatrix::convShrink(Matrix& expandFeat, int thisImgHeight, } } -void CpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, - size_t imgSizeW, size_t channels, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, size_t paddingH, +void CpuMatrix::maxPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, size_t paddingW) { real* inputData = inputMat.getData(); real* outData = data_; @@ -1717,12 +2049,21 @@ void CpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, } } -void CpuMatrix::maxPoolBackward(Matrix& image, size_t imgSizeH, size_t imgSizeW, - Matrix& outGrad, Matrix& outV, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW) { +void CpuMatrix::maxPoolBackward(Matrix& image, + size_t imgSizeH, + size_t imgSizeW, + Matrix& outGrad, + Matrix& outV, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW) { size_t num = image.getHeight(); size_t channels = size_t(width_ / imgSizeH / imgSizeW); CHECK(image.getWidth() == imgSizeH * imgSizeW * channels); @@ -1772,10 +2113,17 @@ void CpuMatrix::maxPoolBackward(Matrix& image, size_t imgSizeH, size_t imgSizeW, } } -void CpuMatrix::avgPoolForward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, size_t outputH, - size_t outputW, size_t paddingH, +void CpuMatrix::avgPoolForward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, size_t paddingW) { // The main loop size_t num = input.getHeight(); @@ -1820,11 +2168,19 @@ void CpuMatrix::avgPoolForward(Matrix& input, size_t imgSizeH, size_t imgSizeW, } } -void CpuMatrix::avgPoolBackward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t sizeX, size_t sizeY, size_t strideH, - size_t strideW, size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW) { +void CpuMatrix::avgPoolBackward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW) { size_t num = input.getHeight(); size_t channels = input.getWidth() / outputH / outputW; CHECK(imgSizeH * imgSizeW * channels == getWidth()); @@ -1863,9 +2219,13 @@ void CpuMatrix::avgPoolBackward(Matrix& input, size_t imgSizeH, size_t imgSizeW, } } -void CpuMatrix::crossMapNormalFwd(Matrix& input, size_t imgSizeH, - size_t imgSizeW, Matrix& denoms, - size_t channels, size_t sizeX, float scale, +void CpuMatrix::crossMapNormalFwd(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + Matrix& denoms, + size_t channels, + size_t sizeX, + float scale, float pow) { size_t num = input.getHeight(); size_t height = imgSizeH; @@ -1915,10 +2275,15 @@ void CpuMatrix::crossMapNormalFwd(Matrix& input, size_t imgSizeH, integralData = NULL; } -void CpuMatrix::crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, - Matrix& preOutV, Matrix& localOutV, - size_t channels, size_t imgSizeH, - size_t imgSizeW, size_t size, float scale, +void CpuMatrix::crossMapNormalBwd(Matrix& localGrad, + Matrix& denoms, + Matrix& preOutV, + Matrix& localOutV, + size_t channels, + size_t imgSizeH, + size_t imgSizeW, + size_t size, + float scale, float pow) { LOG(FATAL) << "Not implemented"; @@ -1937,7 +2302,8 @@ void CpuMatrix::crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, * Output: output size is the number of input sequences (NOT input instances). * output[i] is set to max_{for each instance in this sequence}{input[i]} */ -void CpuMatrix::maxSequenceForward(Matrix& input, const IVector& sequence, +void CpuMatrix::maxSequenceForward(Matrix& input, + const IVector& sequence, IVector& index) { CHECK(dynamic_cast(&input)); CHECK(dynamic_cast(&sequence)); @@ -1978,7 +2344,8 @@ void CpuMatrix::maxSequenceForward(Matrix& input, const IVector& sequence, } } -void CpuMatrix::maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, +void CpuMatrix::maxSequenceBackward(Matrix& outputGrad, + const IVector& sequence, IVector& index) { CHECK(dynamic_cast(&outputGrad)); CHECK(dynamic_cast(&sequence)); @@ -2004,10 +2371,13 @@ void CpuMatrix::maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, } } -void CpuMatrix::contextProjectionForward(MatrixPtr input, MatrixPtr weight, +void CpuMatrix::contextProjectionForward(MatrixPtr input, + MatrixPtr weight, const IVector& sequence, - int contextLength, int contextStart, - size_t beginPad, bool isPadding) { + int contextLength, + int contextStart, + size_t beginPad, + bool isPadding) { CHECK(dynamic_cast(input.get())); CHECK(dynamic_cast(&sequence)); if (weight) CHECK(dynamic_cast(weight.get())); @@ -2058,8 +2428,10 @@ void CpuMatrix::contextProjectionForward(MatrixPtr input, MatrixPtr weight, void CpuMatrix::contextProjectionBackward(MatrixPtr inputGrad, MatrixPtr weightGrad, const IVector& sequence, - int contextLength, int contextStart, - size_t beginPad, bool isPadding) { + int contextLength, + int contextStart, + size_t beginPad, + bool isPadding) { if (inputGrad) CHECK(dynamic_cast(inputGrad.get())); if (weightGrad) CHECK(dynamic_cast(weightGrad.get())); CHECK(dynamic_cast(&sequence)); @@ -2125,15 +2497,15 @@ inline void vecAddTo(real* a, const real* b, real scaleB, size_t len) { } } -inline void colVecAddTo(real* a, const real* b, size_t len, size_t aWidth, - size_t bWidth) { +inline void colVecAddTo( + real* a, const real* b, size_t len, size_t aWidth, size_t bWidth) { for (unsigned int i = 0; i < len; ++i) { a[i * aWidth] += b[i * bWidth]; } } -inline void colVecAddTo(real* a, real* b, real c, size_t len, size_t aWidth, - size_t bWidth) { +inline void colVecAddTo( + real* a, real* b, real c, size_t len, size_t aWidth, size_t bWidth) { for (unsigned int i = 0; i < len; ++i) { a[i * aWidth] += b[i * bWidth] * c; } @@ -2189,7 +2561,7 @@ void CpuMatrix::collectBias(Matrix& a, real scale) { CHECK_EQ(width_, a.getWidth()); CpuSparseMatrix* aptr = dynamic_cast(&a); if (!aptr) { - sumCols(a, /* scaleSum= */scale, /* scaleDest= */1); + sumCols(a, /* scaleSum= */ scale, /* scaleDest= */ 1); } else { size_t nnz = aptr->getElementCnt(); int* cols = aptr->getCols(); @@ -2240,15 +2612,17 @@ void CpuMatrix::sequenceAvgForward(Matrix& a, dataMtx->setData(src + starts[i] * width, sequenceLength, width); if (mode == 0) { // plain average - outMtx->sumCols(*dataMtx, (real)1 / (real)sequenceLength, - /* scaleDest= */1); + outMtx->sumCols(*dataMtx, + (real)1 / (real)sequenceLength, + /* scaleDest= */ 1); } else if (mode == 1) { // sum instead of average - outMtx->sumCols(*dataMtx, /* scaleSum= */1, /* scaleDest= */1); + outMtx->sumCols(*dataMtx, /* scaleSum= */ 1, /* scaleDest= */ 1); } else if (mode == 2) { // divide by square root of sequenceLength - outMtx->sumCols(*dataMtx, (real)1 / std::sqrt(sequenceLength), - /* scaleDest= */1); + outMtx->sumCols(*dataMtx, + (real)1 / std::sqrt(sequenceLength), + /* scaleDest= */ 1); } else { LOG(FATAL) << "should not reach here"; } @@ -2256,27 +2630,37 @@ void CpuMatrix::sequenceAvgForward(Matrix& a, } /* this = scaleAB*(a*b) + scaleT*this*/ -void CpuMatrix::mul(const MatrixPtr a, const MatrixPtr b, real scaleAB, +void CpuMatrix::mul(const MatrixPtr a, + const MatrixPtr b, + real scaleAB, real scaleT) { CHECK(!isTransposed()) << "Not supported"; if (dynamic_cast(a.get()) && dynamic_cast(b.get())) { - mul(dynamic_cast(a.get()), dynamic_cast(b.get()), - scaleAB, scaleT); + mul(dynamic_cast(a.get()), + dynamic_cast(b.get()), + scaleAB, + scaleT); } else if (dynamic_cast(a.get()) && dynamic_cast(b.get())) { mul(dynamic_cast(a.get()), - dynamic_cast(b.get()), scaleAB, scaleT); + dynamic_cast(b.get()), + scaleAB, + scaleT); } else if (dynamic_cast(a.get()) && dynamic_cast(b.get())) { mul(dynamic_cast(a.get()), - dynamic_cast(b.get()), scaleAB, scaleT); + dynamic_cast(b.get()), + scaleAB, + scaleT); } else { LOG(FATAL) << "Not supported"; } } -void CpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, +void CpuMatrix::mul(CpuSparseMatrix* a, + CpuMatrix* b, + real scaleAB, real scaleT) { if (dynamic_cast(b)) { return mul(a, dynamic_cast(b), this, scaleAB, scaleT); @@ -2326,11 +2710,35 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { int ldb = b->getStride(); int ldc = getStride(); #ifndef PADDLE_TYPE_DOUBLE - cblas_sgemm(CblasRowMajor, a_trans, b_trans, M, N, K, scaleAB, A, lda, B, ldb, - scaleT, C, ldc); + cblas_sgemm(CblasRowMajor, + a_trans, + b_trans, + M, + N, + K, + scaleAB, + A, + lda, + B, + ldb, + scaleT, + C, + ldc); #else - cblas_dgemm(CblasRowMajor, a_trans, b_trans, M, N, K, scaleAB, A, lda, B, ldb, - scaleT, C, ldc); + cblas_dgemm(CblasRowMajor, + a_trans, + b_trans, + M, + N, + K, + scaleAB, + A, + lda, + B, + ldb, + scaleT, + C, + ldc); // TODO(yuyang18): Is gemm defined other place? #endif @@ -2338,8 +2746,8 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, real scaleAB, real scaleT) { << " B[1]=" << B[1] << " C[0]=" << C[0] << " C[1]=" << C[1]; } -void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, CpuSparseMatrix* c, - real scaleAB, real scaleT) { +void CpuMatrix::mul( + CpuMatrix* a, CpuMatrix* b, CpuSparseMatrix* c, real scaleAB, real scaleT) { CHECK(!c->isTransposed()) << "Not supported"; CHECK_EQ(c->getValueType(), FLOAT_VALUE); @@ -2446,7 +2854,9 @@ void CpuMatrix::mul(CpuMatrix* a, CpuMatrix* b, CpuSparseMatrix* c, } } -void CpuMatrix::mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, +void CpuMatrix::mul(CpuMatrix* a, + CpuSparseMatrix* b, + real scaleAB, real scaleT) { CHECK(!trans_) << "Not supported"; CHECK(!a->isTransposed()) << "Not supported"; @@ -2484,8 +2894,8 @@ void CpuMatrix::mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, int start = b->getColStartIdx(j); int end = b->getColStartIdx(j + 1); for (int i = start; i < end; ++i) { - colVecAddTo(C + j, A + rows[i], B[i], height_, width_, - a->getWidth()); + colVecAddTo( + C + j, A + rows[i], B[i], height_, width_, a->getWidth()); } } } @@ -2507,8 +2917,8 @@ void CpuMatrix::mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, int start = b->getColStartIdx(i); int end = b->getColStartIdx(i + 1); for (int j = start; j < end; ++j) { - colVecAddTo(C + rows[j], A + i, B[j], height_, width_, - a->getWidth()); + colVecAddTo( + C + rows[j], A + i, B[j], height_, width_, a->getWidth()); } } } @@ -2533,8 +2943,8 @@ void CpuMatrix::mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, int start = b->getRowStartIdx(j); int end = b->getRowStartIdx(j + 1); for (int i = start; i < end; ++i) { - colVecAddTo(C + cols[i], A + j, B[i], height_, width_, - a->getWidth()); + colVecAddTo( + C + cols[i], A + j, B[i], height_, width_, a->getWidth()); } } } @@ -2556,8 +2966,8 @@ void CpuMatrix::mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, int start = b->getRowStartIdx(i); int end = b->getRowStartIdx(i + 1); for (int j = start; j < end; ++j) { - colVecAddTo(C + i, A + cols[j], B[j], height_, width_, - a->getWidth()); + colVecAddTo( + C + i, A + cols[j], B[j], height_, width_, a->getWidth()); } } } @@ -2656,8 +3066,8 @@ void CpuMatrix::addToRowsImp(TableMatType& table, IVector& ids) { static ThreadLocal> threadLocalColArray; template -void CpuMatrix::mul(CpuSparseMatrix* a, MatBType* b, MatCType* c, real scaleAB, - real scaleT) { +void CpuMatrix::mul( + CpuSparseMatrix* a, MatBType* b, MatCType* c, real scaleAB, real scaleT) { CHECK(!c->isTransposed()) << "Not supported"; CHECK(!b->isTransposed()) << "Not supported"; // TODO(yuyang18): Maybe bug implementation here. @@ -2760,18 +3170,26 @@ void CpuMatrix::mul(CpuSparseMatrix* a, MatBType* b, MatCType* c, real scaleAB, // instantiation mul() called in SparseRowMatrix.cpp template void CpuMatrix::mul( - CpuSparseMatrix* a, CpuMatrix* b, SparseRowCpuMatrix* c, real scaleAB, + CpuSparseMatrix* a, + CpuMatrix* b, + SparseRowCpuMatrix* c, + real scaleAB, real scaleT); template void CpuMatrix::mul( - CpuSparseMatrix* a, CpuMatrix* b, SparseAutoGrowRowCpuMatrix* c, - real scaleAB, real scaleT); + CpuSparseMatrix* a, + CpuMatrix* b, + SparseAutoGrowRowCpuMatrix* c, + real scaleAB, + real scaleT); template void CpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, CacheRowCpuMatrix* c, real scaleAB, real scaleT); -void SharedCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, +void SharedCpuMatrix::mul(CpuSparseMatrix* a, + CpuMatrix* b, + real scaleAB, real scaleT) { CHECK(!isTransposed()) << "Not supported"; CHECK(!b->isTransposed()) << "Not supported"; @@ -2811,8 +3229,8 @@ void SharedCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, for (int k = 0; k < blockNum_; ++k) { blockSeq.push_back(k); } - std::shuffle(blockSeq.begin(), blockSeq.end(), - ThreadLocalRandomEngine::get()); + std::shuffle( + blockSeq.begin(), blockSeq.end(), ThreadLocalRandomEngine::get()); } std::vector& localBufRows = *localBufRows_; int* cols = a->getCols(); @@ -2850,8 +3268,8 @@ void SharedCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, localBufRows.push_back(i); size_t bufPos = localBufRows.size() - 1; for (int j = start; j < end; ++j) { - vecAddTo(localC + bufPos * width, B + cols[j] * width, value[j], - width); + vecAddTo( + localC + bufPos * width, B + cols[j] * width, value[j], width); } } } @@ -2935,7 +3353,7 @@ void CpuMatrix::rowSum(Matrix& sum) { CHECK_EQ(sum.getHeight(), getHeight()); CHECK_EQ(sum.getWidth(), (size_t)1); - sum.sumRows(*this, /* scaleSum= */1, /* scaleDest= */0); + sum.sumRows(*this, /* scaleSum= */ 1, /* scaleDest= */ 0); } void CpuMatrix::rowMaxId(IVector& maxIds) { @@ -2987,7 +3405,9 @@ void CpuMatrix::rowMax(IVector& maxIds, Matrix& maxVal) { } std::partial_sort( - vec.begin(), vec.begin() + beam, vec.end(), + vec.begin(), + vec.begin() + beam, + vec.end(), [](const std::pair& l, const std::pair& r) { return l.first > r.first; }); @@ -3023,7 +3443,9 @@ void CpuMatrix::colMax(IVector& maxIds, Matrix& maxVal) { } std::partial_sort( - vec.begin(), vec.begin() + beam, vec.end(), + vec.begin(), + vec.begin() + beam, + vec.end(), [](const std::pair& l, const std::pair& r) { return l.first > r.first; }); @@ -3034,7 +3456,9 @@ void CpuMatrix::colMax(IVector& maxIds, Matrix& maxVal) { } } -void CpuMatrix::maxoutForward(Matrix& a, IVector& id, size_t channels, +void CpuMatrix::maxoutForward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { CHECK(dynamic_cast(&a)); CHECK(dynamic_cast(&id)); @@ -3067,7 +3491,9 @@ void CpuMatrix::maxoutForward(Matrix& a, IVector& id, size_t channels, } } -void CpuMatrix::maxoutBackward(Matrix& a, IVector& id, size_t channels, +void CpuMatrix::maxoutBackward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { CHECK(dynamic_cast(&a)); CHECK(dynamic_cast(&id)); @@ -3189,7 +3615,8 @@ void CpuMatrix::oneHotCrossEntropyBp(Matrix& output, IVector& label) { but we define the scalar function here for sanity check deletion of the function does not affect anything neverthelss */ -void CpuMatrix::oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, +void CpuMatrix::oneHotCrossEntropyWithSelfNorm(Matrix& output, + IVector& label, real alpha) { CHECK(dynamic_cast(&output)); CHECK(dynamic_cast(&label)); @@ -3220,7 +3647,8 @@ void CpuMatrix::oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, but we define the scalar function here for sanity check deletion of the function does not affect anything neverthelss */ -void CpuMatrix::oneHotCrossEntropyWithSelfNormBp(Matrix& output, IVector& label, +void CpuMatrix::oneHotCrossEntropyWithSelfNormBp(Matrix& output, + IVector& label, real alpha) { CHECK(dynamic_cast(&output)); CHECK(dynamic_cast(&label)); @@ -3301,10 +3729,16 @@ void CpuMatrix::sequenceSoftmax(Matrix& output, const IVector& index) { CHECK_EQ(output.getWidth(), 1UL); CHECK(isContiguous()); - MatrixPtr inTmp = Matrix::create(nullptr, /* height= */ 1, 1, - /* trans= */ false, false); - MatrixPtr outTmp = Matrix::create(nullptr, /* height= */ 1, 1, - /* trans= */ false, false); + MatrixPtr inTmp = Matrix::create(nullptr, + /* height= */ 1, + 1, + /* trans= */ false, + false); + MatrixPtr outTmp = Matrix::create(nullptr, + /* height= */ 1, + 1, + /* trans= */ false, + false); size_t numSequences = index.getSize() - 1; auto starts = index.getData(); for (size_t i = 0; i < numSequences; ++i) { @@ -3360,9 +3794,12 @@ void CpuMatrix::cosSim(Matrix& output1, Matrix& output2, real scale) { } } -void CpuMatrix::cosSimDerivative(Matrix& output, Matrix& prevOut1, - Matrix& prevOut2, Matrix& prevGrad1, - Matrix& prevGrad2, real scale) { +void CpuMatrix::cosSimDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, + Matrix& prevGrad2, + real scale) { CHECK(output.useGpu_ == false) << "Matrix type are not equal"; CHECK_EQ(getWidth(), 1UL); @@ -3392,8 +3829,11 @@ void CpuMatrix::cosSimDerivative(Matrix& output, Matrix& prevOut1, CHECK_EQ(prevOut2.getHeight(), numSamples); CHECK_EQ(prevGrad2.getHeight(), numSamples); } - for (size_t i = 0; i < numSamples; ++i, prevOutX += dim, prevOutY += yInc, - prevGradX += dim, prevGradY += yInc) { + for (size_t i = 0; i < numSamples; ++i, + prevOutX += dim, + prevOutY += yInc, + prevGradX += dim, + prevGradY += yInc) { real squareSumX = 0; real squareSumY = 0; real xy = 0; @@ -3450,7 +3890,8 @@ void CpuMatrix::sumOfSquares(Matrix& output, Matrix& label) { int* cols = labelptr->getCols(); for (size_t i = 0; i < numSamples; ++i) { for (size_t j = labelptr->getRowStartIdx(i); - j < labelptr->getRowStartIdx(i + 1); ++j) { + j < labelptr->getRowStartIdx(i + 1); + ++j) { cost[i] += 1.0 - 2.0 * out[i * dim + cols[j]]; /* * explanation of above line: original codes are follows: @@ -3466,7 +3907,8 @@ void CpuMatrix::sumOfSquares(Matrix& output, Matrix& label) { real sum1 = 0; real sum2 = 0; for (size_t j = labelptr->getRowStartIdx(i); - j < labelptr->getRowStartIdx(i + 1); ++j) { + j < labelptr->getRowStartIdx(i + 1); + ++j) { sum1 += values[j] * values[j]; sum2 += values[j] * out[i * dim + cols[j]]; /* @@ -3488,8 +3930,10 @@ void CpuMatrix::sumOfSquares(Matrix& output, Matrix& label) { } } - BaseMatrix::sumOfSquaredDiffs(output, label, - /* scaleSum= */1, /* scaleDest= */1); + BaseMatrix::sumOfSquaredDiffs(output, + label, + /* scaleSum= */ 1, + /* scaleDest= */ 1); } /* calculate the error of outputV according to label */ @@ -3519,7 +3963,8 @@ void CpuMatrix::sumOfSquaresBp(Matrix& output, Matrix& label) { int* cols = labelptr->getCols(); for (size_t i = 0; i < numSamples; ++i) { for (size_t j = labelptr->getRowStartIdx(i); - j < labelptr->getRowStartIdx(i + 1); ++j) { + j < labelptr->getRowStartIdx(i + 1); + ++j) { grad[i * dim + cols[j]] -= 2.0; /* * explanation of above line: original codes are follows: @@ -3534,7 +3979,8 @@ void CpuMatrix::sumOfSquaresBp(Matrix& output, Matrix& label) { real* values = labelptr->getValue(); for (size_t i = 0; i < numSamples; ++i) { for (size_t j = labelptr->getRowStartIdx(i); - j < labelptr->getRowStartIdx(i + 1); ++j) { + j < labelptr->getRowStartIdx(i + 1); + ++j) { grad[i * dim + cols[j]] -= 2.0 * values[j]; /* * explanation of above line: original codes are follows: @@ -3809,8 +4255,8 @@ void CpuMatrix::circularConv(Matrix& in0, Matrix& in1) { } } -void CpuMatrix::circularConvDerivative(Matrix& outG, Matrix& in0, Matrix& in1, - Matrix& inG0, Matrix& inG1) { +void CpuMatrix::circularConvDerivative( + Matrix& outG, Matrix& in0, Matrix& in1, Matrix& inG0, Matrix& inG1) { size_t height = in0.getHeight(); size_t width0 = in0.getWidth(); size_t width1 = in1.getWidth(); @@ -3830,8 +4276,12 @@ void CpuMatrix::circularConvDerivative(Matrix& outG, Matrix& in0, Matrix& in1, real* inGV1 = inG1.getData(); int leftCtxLen = (width1 - 1) / 2; - for (size_t x = 0; x < height; ++x, outGV += width0, inV0 += width0, - inV1 += width1, inGV0 += width0, inGV1 += width1) { + for (size_t x = 0; x < height; ++x, + outGV += width0, + inV0 += width0, + inV1 += width1, + inGV0 += width0, + inGV1 += width1) { for (size_t j = 0; j < width1; ++j) { // iterate over width1 for (size_t i = 0; i < width0; ++i) { // such over all dimensions of outG @@ -3900,7 +4350,8 @@ void CpuMatrix::multiBinaryLabelCrossEntropyBp(Matrix& output, Matrix& label) { } /* calculate the classification error for multi binary label */ -void CpuMatrix::classificationErrorMulti(Matrix& output, Matrix& label, +void CpuMatrix::classificationErrorMulti(Matrix& output, + Matrix& label, real threshold) { CHECK(dynamic_cast(&output)); auto labelPtr = dynamic_cast(&label); @@ -3954,12 +4405,12 @@ void CpuMatrix::bilinearForward(const Matrix& in, (void)(inputH); real* outData = getData(); - const real* inData = in.getData(); + const real* inData = in.getData(); if (inImgH == outImgH && inImgW == outImgW) { this->copyFrom(in); } else { - for (size_t k = 0; k < batchSize; ++k) { // loop for batches + for (size_t k = 0; k < batchSize; ++k) { // loop for batches for (size_t i = 0; i < outImgH; ++i) { // loop for images size_t h = ratioH * i; size_t hid = (h < inImgH - 1) ? 1 : 0; @@ -3977,9 +4428,9 @@ void CpuMatrix::bilinearForward(const Matrix& in, for (size_t c = 0; c < numChannels; ++c) { // loop for channels // bilinear interpolation outPos[0] = - h2lambda * (w2lambda * inPos[0] + w1lambda * inPos[wid]) + - h1lambda * (w2lambda * inPos[hid * inImgW] + - w1lambda * inPos[hid * inImgW + wid]); + h2lambda * (w2lambda * inPos[0] + w1lambda * inPos[wid]) + + h1lambda * (w2lambda * inPos[hid * inImgW] + + w1lambda * inPos[hid * inImgW + wid]); inPos += inPosOffset; outPos += outPosOffset; } @@ -4013,7 +4464,7 @@ void CpuMatrix::bilinearBackward(const Matrix& out, if (inImgH == outImgH && inImgW == outImgW) { this->add(const_cast(out)); } else { - for (size_t k = 0; k < batchSize; ++k) { // loop for batches + for (size_t k = 0; k < batchSize; ++k) { // loop for batches for (size_t i = 0; i < outImgH; ++i) { // loop for images size_t h = ratioH * i; size_t hid = (h < inImgH - 1) ? 1 : 0; diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 6c3c4804d2..075dc84576 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -77,12 +76,19 @@ typedef std::shared_ptr CpuSparseMatrixPtr; */ class Matrix : public BaseMatrix { protected: - Matrix(MemoryHandlePtr memHandle, size_t height, size_t width, bool trans, + Matrix(MemoryHandlePtr memHandle, + size_t height, + size_t width, + bool trans, bool use_gpu); Matrix(real* data, size_t height, size_t width, bool trans, bool use_gpu); - Matrix(real* data, size_t height, size_t width, size_t stride, bool trans, + Matrix(real* data, + size_t height, + size_t width, + size_t stride, + bool trans, bool use_gpu); static ThreadLocal tmpMat_; @@ -94,38 +100,66 @@ public: public: virtual ~Matrix() {} - static MatrixPtr create(MemoryHandlePtr memHandle, size_t height, - size_t width, bool trans = false); - static MatrixPtr create(size_t height, size_t width, bool trans = false, + static MatrixPtr create(MemoryHandlePtr memHandle, + size_t height, + size_t width, + bool trans = false); + static MatrixPtr create(size_t height, + size_t width, + bool trans = false, + bool useGpu = false); + static MatrixPtr create(real* data, + size_t height, + size_t width, + bool trans = false, bool useGpu = false); - static MatrixPtr create(real* data, size_t height, size_t width, - bool trans = false, bool useGpu = false); - static MatrixPtr create(real* data, size_t height, size_t width, - size_t stride, bool trans = false, + static MatrixPtr create(real* data, + size_t height, + size_t width, + size_t stride, + bool trans = false, bool useGpu = false); - static MatrixPtr createSparseMatrix(size_t height, size_t width, size_t nnz, + static MatrixPtr createSparseMatrix(size_t height, + size_t width, + size_t nnz, SparseValueType valueType = FLOAT_VALUE, - bool trans = false, bool useGpu = false); - static MatrixPtr createSparseMatrix(size_t height, size_t width, size_t nnz, + bool trans = false, + bool useGpu = false); + static MatrixPtr createSparseMatrix(size_t height, + size_t width, + size_t nnz, SparseValueType valueType = FLOAT_VALUE, SparseFormat foramt = SPARSE_CSR, - bool trans = false, bool useGpu = false); - - static MatrixPtr createSparseMatrix(real* data, int* row, int* col, - size_t height, size_t width, + bool trans = false, + bool useGpu = false); + + static MatrixPtr createSparseMatrix(real* data, + int* row, + int* col, + size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType, /*value type*/ - SparseFormat format, bool trans, + SparseFormat format, + bool trans, bool useGpu); static void resizeOrCreateSparseMatrix( - MatrixPtr& matrix, size_t height, size_t width, size_t nnz, - SparseValueType valueType = FLOAT_VALUE, SparseFormat foramt = SPARSE_CSR, - bool trans = false, bool useGpu = false); - - static void resizeOrCreate(MatrixPtr& a, size_t height, size_t width, - bool trans = false, bool useGpu = false); + MatrixPtr& matrix, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType = FLOAT_VALUE, + SparseFormat foramt = SPARSE_CSR, + bool trans = false, + bool useGpu = false); + + static void resizeOrCreate(MatrixPtr& a, + size_t height, + size_t width, + bool trans = false, + bool useGpu = false); /** * @brief set the data buffer used to hold the matrix data. @@ -163,12 +197,12 @@ public: // if refactor sparse matrix virtual int* getRows() const { LOG(FATAL) << "Not implemented"; - return nullptr; //! suppress warning for no return value. + return nullptr; //! suppress warning for no return value. } virtual int* getCols() const { LOG(FATAL) << "Not implemented"; - return nullptr; //! suppress warning for no return value. + return nullptr; //! suppress warning for no return value. } virtual SparseFormat getFormat() const { @@ -178,7 +212,7 @@ public: virtual SparseValueType getValueType() const { LOG(FATAL) << "Not implemented"; - return NO_VALUE; //! suppress warning for no return value. + return NO_VALUE; //! suppress warning for no return value. } /** @@ -208,7 +242,9 @@ public: LOG(FATAL) << "Not implemented"; } - MatrixPtr subMatrix(size_t startRow, size_t endRow, size_t startCol, + MatrixPtr subMatrix(size_t startRow, + size_t endRow, + size_t startCol, size_t endCol); MatrixPtr subRowMatrix(size_t startRow, size_t endRow) { @@ -221,8 +257,11 @@ public: virtual MatrixPtr subMatrix(size_t startRow, size_t numRows) { CHECK_LE(startRow + numRows, getHeight()); - return Matrix::create(getData() + startRow * getWidth(), numRows, - getWidth(), trans_, useGpu_); + return Matrix::create(getData() + startRow * getWidth(), + numRows, + getWidth(), + trans_, + useGpu_); } virtual MatrixPtr subMatrix(size_t startRow, size_t numRows, MatrixPtr dest) { CHECK_LE(startRow + numRows, getHeight()); @@ -267,7 +306,8 @@ public: * as this, otherwise the new matrix will have the specified size. * */ - virtual MatrixPtr clone(size_t height = 0, size_t width = 0, + virtual MatrixPtr clone(size_t height = 0, + size_t width = 0, bool useGpu = false) { LOG(FATAL) << "Not implemented"; return nullptr; @@ -305,9 +345,11 @@ public: /** * @note This should only be used for sparse matrix. */ - virtual void resize(size_t newHeight, size_t newWidth, + virtual void resize(size_t newHeight, + size_t newWidth, size_t newNnz, /* total item used to allocate space */ - SparseValueType valueType, SparseFormat format) = 0; + SparseValueType valueType, + SparseFormat format) = 0; /** * @brief This should only be used for sparse matrix. @@ -315,7 +357,9 @@ public: * Currently must be called for each row in order. * The matrix is not valid until setRow is called for the last row. */ - virtual void setRow(size_t row, size_t colNum, const unsigned int* cols, + virtual void setRow(size_t row, + size_t colNum, + const unsigned int* cols, const real* values) = 0; virtual MatrixPtr getTranspose() = 0; @@ -389,8 +433,9 @@ public: } } - virtual void sequenceAvgForward(Matrix& a, const IVector& startsPos, - int mode) { + virtual void sequenceAvgForward(Matrix& a, + const IVector& startsPos, + int mode) { LOG(FATAL) << "Not implemented"; } @@ -399,7 +444,9 @@ public: * this = scaleAB*(a*b) + scaleT*this * @endcode */ - virtual void mul(const MatrixPtr a, const MatrixPtr b, real scaleAB, + virtual void mul(const MatrixPtr a, + const MatrixPtr b, + real scaleAB, real scaleT) { LOG(FATAL) << "Not implemented"; } @@ -416,7 +463,8 @@ public: * where index(i, j) = ((codes(i) + numClasses) >> (j + 1)) - 1 * @endcode */ - virtual void addByBitCode(size_t numClasses, const IVector& codes, + virtual void addByBitCode(size_t numClasses, + const IVector& codes, const Matrix& vec) { (void)numClasses; (void)codes; @@ -431,7 +479,8 @@ public: * where index is same as the index for addByBitCode * @endcode */ - virtual void addByBitCodeBackward(size_t numClasses, const IVector& codes, + virtual void addByBitCodeBackward(size_t numClasses, + const IVector& codes, Matrix& vec) { (void)numClasses; (void)codes; @@ -446,8 +495,10 @@ public: * where index is same as the index for addByBitCode * @endcode */ - virtual void mulByBitCode(size_t numClasses, const IVector& codes, - const Matrix& mat, const Matrix& input) { + virtual void mulByBitCode(size_t numClasses, + const IVector& codes, + const Matrix& mat, + const Matrix& input) { (void)numClasses; (void)codes; (void)mat; @@ -463,7 +514,8 @@ public: * @endcode */ virtual void mulByBitCodeBackwardWeight(size_t numClasses, - const IVector& codes, Matrix& mat, + const IVector& codes, + Matrix& mat, const Matrix& input) { (void)numClasses; (void)codes; @@ -481,7 +533,8 @@ public: */ virtual void mulByBitCodeBackwardError(size_t numClasses, const IVector& codes, - const Matrix& mat, Matrix& input) { + const Matrix& mat, + Matrix& input) { (void)numClasses; (void)codes; (void)mat; @@ -496,7 +549,9 @@ public: * where bit(i, j) = ((codes(i) + numClasses) & 2^j) ? 1 : 0 * @endcode */ - virtual void sumByBitCode(size_t numClasses, IVector& codes, Matrix& sum, + virtual void sumByBitCode(size_t numClasses, + IVector& codes, + Matrix& sum, real scaleSum) { (void)numClasses; (void)codes; @@ -550,12 +605,16 @@ public: LOG(FATAL) << "not implemented"; } - virtual void maxoutForward(Matrix& a, IVector& id, size_t channels, + virtual void maxoutForward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { LOG(FATAL) << "not implemented"; } - virtual void maxoutBackward(Matrix& a, IVector& id, size_t channels, + virtual void maxoutBackward(Matrix& a, + IVector& id, + size_t channels, size_t groups) { LOG(FATAL) << "not implemented"; } @@ -634,7 +693,8 @@ public: } /// copy -log(output[label]) to this->data[i]. - virtual void oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, + virtual void oneHotCrossEntropyWithSelfNorm(Matrix& output, + IVector& label, real alpha) { LOG(FATAL) << "Not implemented"; } @@ -660,13 +720,14 @@ public: LOG(FATAL) << "Not implemented"; } - virtual void circularConvDerivative(Matrix& output, Matrix& prevOut1, - Matrix& prevOut2, Matrix& prevGrad1, + virtual void circularConvDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, Matrix& prevGrad2) { LOG(FATAL) << "Not implemented"; } - /* output_ij = exp(this_{ij}) / (sum_j exp(this_ij)) */ virtual void softmax(Matrix& output) { (void)output; @@ -727,9 +788,12 @@ public: LOG(FATAL) << "Not implemented"; } - virtual void cosSimDerivative(Matrix& output, Matrix& prevOut1, - Matrix& prevOut2, Matrix& prevGrad1, - Matrix& prevGrad2, real scale = 1.0f) { + virtual void cosSimDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, + Matrix& prevGrad2, + real scale = 1.0f) { LOG(FATAL) << "Not implemented"; } @@ -781,10 +845,18 @@ public: * It will expand a feature matrix according to the * convolution filters */ - virtual void convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, - int channels, int blockH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, - int outputH, int outputW) { + virtual void convExpand(Matrix& feature, + int feaImgHeight, + int feaImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW) { LOG(FATAL) << "Not implemeted"; } @@ -793,11 +865,20 @@ public: * * Its function is to restore a expanded-matrix into a feature matrix */ - virtual void convShrink(Matrix& expandColMat, int thisImgHeight, - int thisImgWidth, int channels, int blockH, - int blockW, int strideH, int strideW, int paddingH, - int paddingW, int outputH, int outputW, - real alpha = 1.0f, real beta = 0.0f) { + virtual void convShrink(Matrix& expandColMat, + int thisImgHeight, + int thisImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW, + real alpha = 1.0f, + real beta = 0.0f) { LOG(FATAL) << "Not implemeted"; } @@ -805,54 +886,93 @@ public: * Pooling forward operation, pick out the largest element * in the sizeX of value */ - virtual void maxPoolForward(Matrix& inputMat, size_t imgSizeH, - size_t imgSizeW, size_t channels, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW) { + virtual void maxPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW) { LOG(FATAL) << "Not implemeted"; } /// Pooling backward operation. - virtual void maxPoolBackward(Matrix& image, size_t imgSizeH, size_t imgSizeW, - Matrix& outGrad, Matrix& outV, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW) { + virtual void maxPoolBackward(Matrix& image, + size_t imgSizeH, + size_t imgSizeW, + Matrix& outGrad, + Matrix& outV, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW) { LOG(FATAL) << "Not implemeted"; } /// Pooling forward operation, caculate the average of sizeX elements. - virtual void avgPoolForward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW) { + virtual void avgPoolForward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW) { LOG(FATAL) << "Not implemeted"; } - virtual void avgPoolBackward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW) { + virtual void avgPoolBackward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW) { LOG(FATAL) << "Not implemeted"; } /// normalize-operation. - virtual void crossMapNormalFwd(Matrix& input, size_t imgSizeH, - size_t imgSizeW, Matrix& denoms, - size_t channels, size_t sizeX, float scale, + virtual void crossMapNormalFwd(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + Matrix& denoms, + size_t channels, + size_t sizeX, + float scale, float pow) { LOG(FATAL) << "Not implemeted"; } - virtual void crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, - Matrix& preOutV, Matrix& localOutV, - size_t channels, size_t imgSizeH, - size_t imgSizeW, size_t size, float scale, + virtual void crossMapNormalBwd(Matrix& localGrad, + Matrix& denoms, + Matrix& preOutV, + Matrix& localOutV, + size_t channels, + size_t imgSizeH, + size_t imgSizeW, + size_t size, + float scale, float pow) { LOG(FATAL) << "Not implemeted"; } @@ -865,20 +985,24 @@ public: * * output[i] is set to max_input[i]. */ - virtual void maxSequenceForward(Matrix& input, const IVector& sequence, + virtual void maxSequenceForward(Matrix& input, + const IVector& sequence, IVector& index) { LOG(FATAL) << "Not implemeted"; } - virtual void maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, + virtual void maxSequenceBackward(Matrix& outputGrad, + const IVector& sequence, IVector& index) { LOG(FATAL) << "Not implemeted"; } - virtual void contextProjectionForward(MatrixPtr input, MatrixPtr weight, + virtual void contextProjectionForward(MatrixPtr input, + MatrixPtr weight, const IVector& sequence, int contextLength, - int contextStart, size_t beginPad, + int contextStart, + size_t beginPad, bool isPadding) { LOG(FATAL) << "Not implemeted"; } @@ -887,7 +1011,8 @@ public: MatrixPtr weightGrad, const IVector& sequence, int contextLength, - int contextStart, size_t beginPad, + int contextStart, + size_t beginPad, bool isPadding) { LOG(FATAL) << "Not implemeted"; } @@ -902,7 +1027,8 @@ public: virtual void contextProjectionBackwardWeight(MatrixPtr weightGrad, const IVector& sequence, int contextLength, - int contextStart, int totalPad, + int contextStart, + int totalPad, size_t beginPad) { LOG(FATAL) << "Not implemeted"; } @@ -981,7 +1107,8 @@ public: * / output->getWidth() * @endcode */ - virtual void classificationErrorMulti(Matrix& output, Matrix& label, + virtual void classificationErrorMulti(Matrix& output, + Matrix& label, real threshold) { LOG(FATAL) << "Not implemented"; } @@ -1029,10 +1156,15 @@ public: GpuMatrix(size_t height, size_t width, bool trans = false); GpuMatrix(real* data, size_t height, size_t width, bool trans = false) : Matrix(data, height, width, trans, true) {} - GpuMatrix(real* data, size_t height, size_t width, size_t stride, + GpuMatrix(real* data, + size_t height, + size_t width, + size_t stride, bool trans = false) : Matrix(data, height, width, stride, trans, true) {} - GpuMatrix(GpuMemHandlePtr dataHandle, size_t height, size_t width, + GpuMatrix(GpuMemHandlePtr dataHandle, + size_t height, + size_t width, bool trans = false) : Matrix(dataHandle, height, width, trans, true) {} ~GpuMatrix(); @@ -1042,12 +1174,16 @@ public: void setDiag(real value); void resize(size_t newHeight, size_t newWidth); - void resize(size_t newHeight, size_t newWidth, + void resize(size_t newHeight, + size_t newWidth, size_t newNnz, /* used to allocate space */ - SparseValueType valueType, SparseFormat format) { + SparseValueType valueType, + SparseFormat format) { LOG(FATAL) << "Only Support Sparse Matrix"; } - void setRow(size_t row, size_t colNum, const unsigned int* cols, + void setRow(size_t row, + size_t colNum, + const unsigned int* cols, const real* values) { LOG(FATAL) << "Only Support Sparse Matrix"; } @@ -1137,10 +1273,14 @@ public: void mul(const GpuMatrix& a, const GpuMatrix& b, real scaleAB, real scaleT); - void mul(const GpuSparseMatrix& a, const GpuMatrix& b, real scaleAB, + void mul(const GpuSparseMatrix& a, + const GpuMatrix& b, + real scaleAB, real scaleT); - void mul(const GpuMatrix& a, const GpuSparseMatrix& b, real scaleAB, + void mul(const GpuMatrix& a, + const GpuSparseMatrix& b, + real scaleAB, real scaleT); /** @@ -1182,9 +1322,11 @@ public: void oneHotCrossEntropy(Matrix& output, IVector& label); void oneHotCrossEntropyBp(Matrix& outputV, IVector& label); - void oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, + void oneHotCrossEntropyWithSelfNorm(Matrix& output, + IVector& label, real alpha); - void oneHotCrossEntropyWithSelfNormBp(Matrix& outputV, IVector& label, + void oneHotCrossEntropyWithSelfNormBp(Matrix& outputV, + IVector& label, real alpha); void softmax(Matrix& output); @@ -1204,8 +1346,12 @@ public: void scaledTanh(Matrix& output, real p1, real p2); void cosSim(Matrix& output1, Matrix& output2, real scale); - void cosSimDerivative(Matrix& output, Matrix& prevOut1, Matrix& prevOut2, - Matrix& prevGrad1, Matrix& prevGrad2, real scale); + void cosSimDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, + Matrix& prevGrad2, + real scale); virtual void print(std::ostream& os) const; virtual void print(std::ostream& os, size_t height, size_t width) const; @@ -1219,71 +1365,136 @@ public: void classificationError(MatrixPtr output, IVectorPtr label); - void convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, - int channels, int blockH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, - int outputH, int outputW); - - void convShrink(Matrix& expandColMat, int thisImgHeight, int thisImgWidth, - int channels, int blockH, int blochW, int strideH, - int strideW, int paddingH, int paddingWreal, - int outputH, int outputW, - real alpha = 1.0f, real beta = 0.0f); - - void maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW); - - void maxPoolBackward(Matrix& image, size_t imgSizeH, size_t imgSizeW, - Matrix& outGrad, Matrix& outV, size_t sizeX, - size_t sizeY, size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW); - - void avgPoolForward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW); - - void avgPoolBackward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW); - - void crossMapNormalFwd(Matrix& input, size_t imgSizeH, size_t imgSizeW, - Matrix& denoms, size_t channels, size_t sizeX, - float scale, float pow); - - void crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, Matrix& preOutV, - Matrix& localOutV, size_t channels, size_t imgSizeH, - size_t imgSizeW, size_t sizeX, - float scale, float pow); - - void maxSequenceForward(Matrix& input, const IVector& sequence, + void convExpand(Matrix& feature, + int feaImgHeight, + int feaImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW); + + void convShrink(Matrix& expandColMat, + int thisImgHeight, + int thisImgWidth, + int channels, + int blockH, + int blochW, + int strideH, + int strideW, + int paddingH, + int paddingWreal, + int outputH, + int outputW, + real alpha = 1.0f, + real beta = 0.0f); + + void maxPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW); + + void maxPoolBackward(Matrix& image, + size_t imgSizeH, + size_t imgSizeW, + Matrix& outGrad, + Matrix& outV, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW); + + void avgPoolForward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW); + + void avgPoolBackward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW); + + void crossMapNormalFwd(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + Matrix& denoms, + size_t channels, + size_t sizeX, + float scale, + float pow); + + void crossMapNormalBwd(Matrix& localGrad, + Matrix& denoms, + Matrix& preOutV, + Matrix& localOutV, + size_t channels, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + float scale, + float pow); + + void maxSequenceForward(Matrix& input, + const IVector& sequence, IVector& index); - void maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, + void maxSequenceBackward(Matrix& outputGrad, + const IVector& sequence, IVector& index); - void contextProjectionForward(MatrixPtr input, MatrixPtr weight, - const IVector& sequence, int contextLength, - int contextStart, size_t beginPad, + void contextProjectionForward(MatrixPtr input, + MatrixPtr weight, + const IVector& sequence, + int contextLength, + int contextStart, + size_t beginPad, bool isPadding); void contextProjectionBackwardData(MatrixPtr inputGrad, const IVector& sequence, - int contextLength, int contextStart); + int contextLength, + int contextStart); void contextProjectionBackwardWeight(MatrixPtr weightGrad, const IVector& sequence, int contextLength, - int contextStart, int totalPad, + int contextStart, + int totalPad, size_t beginPad); void bilinearForward(const Matrix& in, @@ -1314,11 +1525,16 @@ public: CpuMatrix(size_t height, size_t width, bool trans = false); CpuMatrix(real* data, size_t height, size_t width, bool trans = false) : Matrix(data, height, width, trans, false) {} - CpuMatrix(real* data, size_t height, size_t width, size_t stride, + CpuMatrix(real* data, + size_t height, + size_t width, + size_t stride, bool trans = false) : Matrix(data, height, width, stride, trans, false) {} - CpuMatrix(CpuMemHandlePtr dataHandle, size_t height, size_t width, + CpuMatrix(CpuMemHandlePtr dataHandle, + size_t height, + size_t width, bool trans = false) : Matrix(dataHandle, height, width, trans, false) {} @@ -1329,12 +1545,16 @@ public: void setDiag(real value); void resize(size_t newHeight, size_t newWidth); - void resize(size_t newHeight, size_t newWidth, + void resize(size_t newHeight, + size_t newWidth, size_t newNnz, /* used to allocate space */ - SparseValueType valueType, SparseFormat format) { + SparseValueType valueType, + SparseFormat format) { LOG(FATAL) << "Only Support Sparse Matrix"; } - void setRow(size_t row, size_t colNum, const unsigned int* cols, + void setRow(size_t row, + size_t colNum, + const unsigned int* cols, const real* values) { LOG(FATAL) << "Only Support Sparse Matrix"; } @@ -1366,67 +1586,132 @@ public: MatrixPtr clone(size_t height, size_t width, bool useGpu = false); - void convExpand(Matrix& feature, int feaImgHeight, int feaImgWidth, - int channels, int blcokH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, - int outputH, int outputW); - - void convShrink(Matrix& expandFeat, int thisImgHeight, int thisImgWidth, - int channels, int blockH, int blockW, int strideH, - int strideW, int paddingH, int paddingW, - int outputH, int outputW, - real alpha = 1.0f, real beta = 0.0f); - - void maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW); - - void maxPoolBackward(Matrix& image, size_t imgSizeH, size_t imgSizeW, - Matrix& outGrad, Matrix& outV, - size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW); - - void avgPoolForward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t channels, size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - size_t paddingH, size_t paddingW); - - void avgPoolBackward(Matrix& input, size_t imgSizeH, size_t imgSizeW, - size_t sizeX, size_t sizeY, - size_t strideH, size_t strideW, - size_t outputH, size_t outputW, - real scaleTargets, real scaleOutput, - size_t paddingH, size_t paddingW); - - void crossMapNormalFwd(Matrix& input, size_t imgSizeH, size_t imgSizeW, - Matrix& denoms, size_t channels, size_t sizeX, - float scale, float pow); - - void crossMapNormalBwd(Matrix& localGrad, Matrix& denoms, Matrix& preOutV, - Matrix& localOutV, size_t channels, size_t imgSizeH, - size_t imgSizeW, size_t sizeX, - float scale, float pow); - - void maxSequenceForward(Matrix& input, const IVector& sequence, + void convExpand(Matrix& feature, + int feaImgHeight, + int feaImgWidth, + int channels, + int blcokH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW); + + void convShrink(Matrix& expandFeat, + int thisImgHeight, + int thisImgWidth, + int channels, + int blockH, + int blockW, + int strideH, + int strideW, + int paddingH, + int paddingW, + int outputH, + int outputW, + real alpha = 1.0f, + real beta = 0.0f); + + void maxPoolForward(Matrix& inputMat, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW); + + void maxPoolBackward(Matrix& image, + size_t imgSizeH, + size_t imgSizeW, + Matrix& outGrad, + Matrix& outV, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW); + + void avgPoolForward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t channels, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + size_t paddingH, + size_t paddingW); + + void avgPoolBackward(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + size_t sizeY, + size_t strideH, + size_t strideW, + size_t outputH, + size_t outputW, + real scaleTargets, + real scaleOutput, + size_t paddingH, + size_t paddingW); + + void crossMapNormalFwd(Matrix& input, + size_t imgSizeH, + size_t imgSizeW, + Matrix& denoms, + size_t channels, + size_t sizeX, + float scale, + float pow); + + void crossMapNormalBwd(Matrix& localGrad, + Matrix& denoms, + Matrix& preOutV, + Matrix& localOutV, + size_t channels, + size_t imgSizeH, + size_t imgSizeW, + size_t sizeX, + float scale, + float pow); + + void maxSequenceForward(Matrix& input, + const IVector& sequence, IVector& index); - void maxSequenceBackward(Matrix& outputGrad, const IVector& sequence, + void maxSequenceBackward(Matrix& outputGrad, + const IVector& sequence, IVector& index); - void contextProjectionForward(MatrixPtr input, MatrixPtr weight, - const IVector& sequence, int contextLength, - int contextStart, size_t beginPad, + void contextProjectionForward(MatrixPtr input, + MatrixPtr weight, + const IVector& sequence, + int contextLength, + int contextStart, + size_t beginPad, bool isPadding); - void contextProjectionBackward(MatrixPtr inputGrad, MatrixPtr weightGrad, - const IVector& sequence, int contextLength, - int contextStart, size_t beginPad, + void contextProjectionBackward(MatrixPtr inputGrad, + MatrixPtr weightGrad, + const IVector& sequence, + int contextLength, + int contextStart, + size_t beginPad, bool isPadding); real* getRow(size_t row) { return BaseMatrix::rowBuf(row); } @@ -1443,7 +1728,6 @@ public: void sequenceAvgForward(Matrix& a, const IVector& startsPos, int mode); - /** * @code * this.row[i] += table.row[ids[i]] @@ -1490,7 +1774,10 @@ public: void mul(CpuMatrix* a, CpuSparseMatrix* b, real scaleAB, real scaleT); - static void mul(CpuMatrix* a, CpuMatrix* b, CpuSparseMatrix* c, real scaleAB, + static void mul(CpuMatrix* a, + CpuMatrix* b, + CpuSparseMatrix* c, + real scaleAB, real scaleT); /** @@ -1500,8 +1787,8 @@ public: * Define B,C as template instead of virtual class for performance sake. */ template - static void mul(CpuSparseMatrix* a, MatBType* b, MatCType* c, real scaleAB, - real scaleT); + static void mul( + CpuSparseMatrix* a, MatBType* b, MatCType* c, real scaleAB, real scaleT); virtual void mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, real scaleT); @@ -1525,14 +1812,18 @@ public: void oneHotCrossEntropy(Matrix& output, IVector& label); void oneHotCrossEntropyBp(Matrix& outputV, IVector& label); - void oneHotCrossEntropyWithSelfNorm(Matrix& output, IVector& label, + void oneHotCrossEntropyWithSelfNorm(Matrix& output, + IVector& label, real alpha); - void oneHotCrossEntropyWithSelfNormBp(Matrix& outputV, IVector& label, + void oneHotCrossEntropyWithSelfNormBp(Matrix& outputV, + IVector& label, real alpha); void circularConv(Matrix& b, Matrix& c); - void circularConvDerivative(Matrix& output, Matrix& prevOut1, - Matrix& prevOut2, Matrix& prevGrad1, + void circularConvDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, Matrix& prevGrad2); void softmax(Matrix& output); @@ -1553,8 +1844,12 @@ public: void scaledTanh(Matrix& output, real p1, real p2); void cosSim(Matrix& output1, Matrix& output2, real scale); - void cosSimDerivative(Matrix& output, Matrix& prevOut1, Matrix& prevOut2, - Matrix& prevGrad1, Matrix& prevGrad2, real scale); + void cosSimDerivative(Matrix& output, + Matrix& prevOut1, + Matrix& prevOut2, + Matrix& prevGrad1, + Matrix& prevGrad2, + real scale); void print(std::ostream& os) const; void print(std::ostream& os, size_t height, size_t width) const; @@ -1575,19 +1870,28 @@ public: void addByBitCode(size_t numClasses, const IVector& codes, const Matrix& vec); - void addByBitCodeBackward(size_t numClasses, const IVector& codes, + void addByBitCodeBackward(size_t numClasses, + const IVector& codes, Matrix& vec); - void mulByBitCode(size_t numClasses, const IVector& codes, const Matrix& mat, + void mulByBitCode(size_t numClasses, + const IVector& codes, + const Matrix& mat, const Matrix& input); - void mulByBitCodeBackwardWeight(size_t numClasses, const IVector& codes, - Matrix& mat, const Matrix& input); + void mulByBitCodeBackwardWeight(size_t numClasses, + const IVector& codes, + Matrix& mat, + const Matrix& input); - void mulByBitCodeBackwardError(size_t numClasses, const IVector& codes, - const Matrix& mat, Matrix& input); + void mulByBitCodeBackwardError(size_t numClasses, + const IVector& codes, + const Matrix& mat, + Matrix& input); - void sumByBitCode(size_t numClasses, IVector& codes, Matrix& sum, + void sumByBitCode(size_t numClasses, + IVector& codes, + Matrix& sum, real scaleSum); void subByBitCode(size_t numClasses_, IVector& codes); @@ -1622,20 +1926,25 @@ public: : CpuMatrix(height, width, trans) { initShared(blockNum); } - SharedCpuMatrix(int blockNum, real* data, size_t height, size_t width, - bool trans = false) + SharedCpuMatrix( + int blockNum, real* data, size_t height, size_t width, bool trans = false) : CpuMatrix(data, height, width, trans) { initShared(blockNum); } - SharedCpuMatrix(int blockNum, CpuMemHandlePtr dataHandle, size_t height, - size_t width, bool trans = false) + SharedCpuMatrix(int blockNum, + CpuMemHandlePtr dataHandle, + size_t height, + size_t width, + bool trans = false) : CpuMatrix(dataHandle, height, width, trans) { initShared(blockNum); } - SharedCpuMatrix(CpuMemHandlePtr dataHandle, size_t height, - size_t width, bool trans = false) + SharedCpuMatrix(CpuMemHandlePtr dataHandle, + size_t height, + size_t width, + bool trans = false) : CpuMatrix(dataHandle, height, width, trans) { initBlock(1); } diff --git a/paddle/math/MatrixBitCode.cpp b/paddle/math/MatrixBitCode.cpp index 8497c26e35..ac5b10c7bd 100644 --- a/paddle/math/MatrixBitCode.cpp +++ b/paddle/math/MatrixBitCode.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include "paddle/utils/Util.h" #include "Matrix.h" @@ -80,8 +79,8 @@ private: op(tmat(i, j), vec(0, index(i, j))) */ template -static void addByBitCodeT(Op op, CodeTable codeTable, const IVector& codes, - TMat& tmat, Mat& vec) { +static void addByBitCodeT( + Op op, CodeTable codeTable, const IVector& codes, TMat& tmat, Mat& vec) { CHECK(!vec.useGpu()); size_t numClasses = codeTable.size(); @@ -109,7 +108,8 @@ static void addByBitCodeT(Op op, CodeTable codeTable, const IVector& codes, /* For j < codeLength: this(i, j) += vec(0, index(i, j)) */ -void CpuMatrix::addByBitCode(size_t numClasses, const IVector& codes, +void CpuMatrix::addByBitCode(size_t numClasses, + const IVector& codes, const Matrix& vec) { auto op = [](real& t, real v) { t += v; }; addByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, vec); @@ -118,7 +118,8 @@ void CpuMatrix::addByBitCode(size_t numClasses, const IVector& codes, /* For j < codeLength: vec(0, index(i, j)) += this(i, j) */ -void CpuMatrix::addByBitCodeBackward(size_t numClasses, const IVector& codes, +void CpuMatrix::addByBitCodeBackward(size_t numClasses, + const IVector& codes, Matrix& vec) { auto op = [](real t, real& v) { v += t; }; addByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, vec); @@ -129,10 +130,18 @@ void CpuMatrix::addByBitCodeBackward(size_t numClasses, const IVector& codes, for j < codeLength: op(tmat(i, j), mat.row(index(i, j)), input.row(i)) */ -template -void mulByBitCodeT(Op op, CodeTable codeTable, IVec& codes, TMat& tmat, - WMat& weight, InMat& input) { +void mulByBitCodeT(Op op, + CodeTable codeTable, + IVec& codes, + TMat& tmat, + WMat& weight, + InMat& input) { CHECK(!tmat.useGpu() && !weight.useGpu() && !input.useGpu()); size_t numClasses = codeTable.size(); @@ -161,10 +170,12 @@ void mulByBitCodeT(Op op, CodeTable codeTable, IVec& codes, TMat& tmat, /* For j < codeLength: this(i, j) += */ -void CpuMatrix::mulByBitCode(size_t numClasses, const IVector& codes, - const Matrix& weight, const Matrix& input) { - auto op = [](real& t, const real* weightRow, const real* inputRow, - size_t inputDim) { +void CpuMatrix::mulByBitCode(size_t numClasses, + const IVector& codes, + const Matrix& weight, + const Matrix& input) { + auto op = []( + real& t, const real* weightRow, const real* inputRow, size_t inputDim) { real sum = 0; for (size_t k = 0; k < inputDim; ++k) { sum += weightRow[k] * inputRow[k]; @@ -179,14 +190,15 @@ void CpuMatrix::mulByBitCode(size_t numClasses, const IVector& codes, weight.row(index(i, j)) += this(i, j) * input.row(i) */ void CpuMatrix::mulByBitCodeBackwardWeight(size_t numClasses, - const IVector& codes, Matrix& weight, + const IVector& codes, + Matrix& weight, const Matrix& input) { - auto op = - [](const real t, real* weightRow, const real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - weightRow[k] += t * inputRow[k]; - } - }; + auto op = []( + const real t, real* weightRow, const real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + weightRow[k] += t * inputRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } @@ -196,20 +208,24 @@ void CpuMatrix::mulByBitCodeBackwardWeight(size_t numClasses, */ void CpuMatrix::mulByBitCodeBackwardError(size_t numClasses, const IVector& codes, - const Matrix& weight, Matrix& input) { - auto op = - [](const real t, const real* weightRow, real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - inputRow[k] += t * weightRow[k]; - } - }; + const Matrix& weight, + Matrix& input) { + auto op = []( + const real t, const real* weightRow, real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + inputRow[k] += t * weightRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } template -void sumByBitCodeT(CodeTable codeTable, IVector& codes, const CpuMatrix& tmat, - Matrix& sum, real scaleSum) { +void sumByBitCodeT(CodeTable codeTable, + IVector& codes, + const CpuMatrix& tmat, + Matrix& sum, + real scaleSum) { size_t maxCodeLength = codeTable.getMaxCodeLength(); size_t numSamples = tmat.getHeight(); size_t oWidth = tmat.getWidth(); @@ -237,7 +253,9 @@ void sumByBitCodeT(CodeTable codeTable, IVector& codes, const CpuMatrix& tmat, /* For j < codeLength: sum(i, 0) = \sum_j bit(i, j) * this(i, j) */ -void CpuMatrix::sumByBitCode(size_t numClasses, IVector& codes, Matrix& sum, +void CpuMatrix::sumByBitCode(size_t numClasses, + IVector& codes, + Matrix& sum, real scaleSum) { sumByBitCodeT(SimpleCodeTable(numClasses), codes, *this, sum, scaleSum); } diff --git a/paddle/math/MemoryHandle.cpp b/paddle/math/MemoryHandle.cpp index 11f746df5c..9101957fc6 100644 --- a/paddle/math/MemoryHandle.cpp +++ b/paddle/math/MemoryHandle.cpp @@ -21,8 +21,7 @@ namespace paddle { /** * Calculate the actual allocation size according to the required size. */ -MemoryHandle::MemoryHandle(size_t size) - : size_(size), buf_(nullptr) { +MemoryHandle::MemoryHandle(size_t size) : size_(size), buf_(nullptr) { if (size_ <= 256) { // Memory allocation in cuda is always aligned to at least 256 bytes. // In many cases it is 512 bytes. @@ -44,9 +43,7 @@ GpuMemoryHandle::GpuMemoryHandle(size_t size) : MemoryHandle(size) { buf_ = allocator_->alloc(allocSize_); } -GpuMemoryHandle::~GpuMemoryHandle() { - allocator_->free(buf_, allocSize_); -} +GpuMemoryHandle::~GpuMemoryHandle() { allocator_->free(buf_, allocSize_); } CpuMemoryHandle::CpuMemoryHandle(size_t size) : MemoryHandle(size) { CHECK(size != 0) << " allocate 0 bytes"; @@ -54,8 +51,6 @@ CpuMemoryHandle::CpuMemoryHandle(size_t size) : MemoryHandle(size) { buf_ = allocator_->alloc(allocSize_); } -CpuMemoryHandle::~CpuMemoryHandle() { - allocator_->free(buf_, allocSize_); -} +CpuMemoryHandle::~CpuMemoryHandle() { allocator_->free(buf_, allocSize_); } } // namespace paddle diff --git a/paddle/math/MemoryHandle.h b/paddle/math/MemoryHandle.h index 809fba2d0a..f12635d5d4 100644 --- a/paddle/math/MemoryHandle.h +++ b/paddle/math/MemoryHandle.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -32,9 +31,9 @@ public: protected: PoolAllocator* allocator_; - size_t size_; // the requested size - size_t allocSize_; // the allocated size - int deviceId_; // the device id of memory if gpu memory + size_t size_; // the requested size + size_t allocSize_; // the allocated size + int deviceId_; // the device id of memory if gpu memory void* buf_; }; diff --git a/paddle/math/PoolAllocator.cpp b/paddle/math/PoolAllocator.cpp index 3a03496eb1..2c150949dd 100644 --- a/paddle/math/PoolAllocator.cpp +++ b/paddle/math/PoolAllocator.cpp @@ -12,21 +12,19 @@ WITHOUT 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 "PoolAllocator.h" namespace paddle { PoolAllocator::PoolAllocator(Allocator* allocator, - size_t sizeLimit, const std::string& name) + size_t sizeLimit, + const std::string& name) : allocator_(allocator), sizeLimit_(sizeLimit), poolMemorySize_(0), name_(name) {} -PoolAllocator::~PoolAllocator() { - freeAll(); -} +PoolAllocator::~PoolAllocator() { freeAll(); } void* PoolAllocator::alloc(size_t size) { if (sizeLimit_ > 0) { diff --git a/paddle/math/PoolAllocator.h b/paddle/math/PoolAllocator.h index aca8ffb0ab..5d33b45312 100644 --- a/paddle/math/PoolAllocator.h +++ b/paddle/math/PoolAllocator.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include diff --git a/paddle/math/SIMDFunctions.cpp b/paddle/math/SIMDFunctions.cpp index 6147bed3d8..1fb156f29b 100644 --- a/paddle/math/SIMDFunctions.cpp +++ b/paddle/math/SIMDFunctions.cpp @@ -12,8 +12,6 @@ WITHOUT 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 "SIMDFunctions.h" #include #include @@ -85,7 +83,9 @@ static void batch_addto_sse(float* a, const float* b[], int batch, size_t len) { return; } -static void col_max_sse(float* result, const float* data, int dim, +static void col_max_sse(float* result, + const float* data, + int dim, int numSamples) { // first sample, direct copy for (int d = 0; d < dim; ++d) { @@ -195,7 +195,9 @@ static void batch_addto_avx(float* a, const float* b[], int batch, size_t len) { return; } -static void col_max_avx(float* result, const float* data, int dim, +static void col_max_avx(float* result, + const float* data, + int dim, int numSamples) { // first sample, direct copy for (int d = 0; d < dim; ++d) { @@ -289,8 +291,8 @@ static void decayL1_avx(float* dst, float* src, float lambda, size_t sz) { } } -static void decayL1_avx(float* dst, float* src, float* lr, float lambda, - size_t sz) { +static void decayL1_avx( + float* dst, float* src, float* lr, float lambda, size_t sz) { int64_t i; int64_t size = sz; float src_val; @@ -379,8 +381,8 @@ void colMaxImpl(float* result, const float* data, int dim, int numSamples) { void decayL1AvxImpl(float* dst, float* src, float lambda, size_t len) { decayL1_avx(dst, src, lambda, len); } -void decayL1AvxImpl(float* dst, float* src, float* lr, float lambda, - size_t len) { +void decayL1AvxImpl( + float* dst, float* src, float* lr, float lambda, size_t len) { decayL1_avx(dst, src, lr, lambda, len); } diff --git a/paddle/math/SIMDFunctions.h b/paddle/math/SIMDFunctions.h index 2b984d5f96..ac82f10910 100644 --- a/paddle/math/SIMDFunctions.h +++ b/paddle/math/SIMDFunctions.h @@ -12,8 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - - #pragma once #include #include @@ -123,8 +121,8 @@ void batchAddToImpl(float* a, const float* b[], int batch, size_t len); void colMaxImpl(float* result, const float* data, int dim, int numSamples); #ifdef __AVX__ void decayL1AvxImpl(float* dst, float* src, float lambda, size_t len); -void decayL1AvxImpl(float* dst, float* src, float* lr, float lambda, - size_t len); +void decayL1AvxImpl( + float* dst, float* src, float* lr, float lambda, size_t len); #endif } // namespace internal @@ -153,8 +151,8 @@ inline void decayL1(float* dst, float* src, float lambda, size_t len) { } template <> -inline void decayL1(float* dst, float* src, float* lr, float lambda, - size_t len) { +inline void decayL1( + float* dst, float* src, float* lr, float lambda, size_t len) { #ifdef __AVX__ internal::decayL1AvxImpl(dst, src, lr, lambda, len); #else diff --git a/paddle/math/SparseMatrix.cpp b/paddle/math/SparseMatrix.cpp index 67ac048862..2b0bff9535 100644 --- a/paddle/math/SparseMatrix.cpp +++ b/paddle/math/SparseMatrix.cpp @@ -22,18 +22,25 @@ limitations under the License. */ namespace paddle { -GpuSparseMatrix::GpuSparseMatrix(size_t height, size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, +GpuSparseMatrix::GpuSparseMatrix(size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans) : Matrix(NULL, height, width, trans, true) { resize(height, width, nnz, valueType, format); } GpuSparseMatrix::GpuSparseMatrix(GpuMemHandlePtr dataHandle, - hl_sparse_matrix_s_ptr sMatrix, size_t height, - size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, - bool trans, MemoryHandlePtr sMemoryHandle) + hl_sparse_matrix_s_ptr sMatrix, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, + bool trans, + MemoryHandlePtr sMemoryHandle) : Matrix(dataHandle, height, width, trans, true) { CHECK(dataHandle && sMatrix) << "Invalid argument pointer"; @@ -67,10 +74,14 @@ GpuSparseMatrix::GpuSparseMatrix(GpuMemHandlePtr dataHandle, sparseResizeCSC(); } -GpuSparseMatrix::GpuSparseMatrix(hl_sparse_matrix_s_ptr sMatrix, size_t height, - size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, - bool trans, MemoryHandlePtr sMemoryHandle) +GpuSparseMatrix::GpuSparseMatrix(hl_sparse_matrix_s_ptr sMatrix, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, + bool trans, + MemoryHandlePtr sMemoryHandle) : Matrix(NULL, height, width, trans, true) { CHECK(sMatrix) << "Invalid argument pointer"; sMatrix_ = sMatrix; @@ -80,9 +91,14 @@ GpuSparseMatrix::GpuSparseMatrix(hl_sparse_matrix_s_ptr sMatrix, size_t height, valueType_ = valueType; } -GpuSparseMatrix::GpuSparseMatrix(real* value, int* rows, int* cols, - size_t height, size_t width, size_t nnz, - SparseValueType valueType, SparseFormat format, +GpuSparseMatrix::GpuSparseMatrix(real* value, + int* rows, + int* cols, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, bool trans) : Matrix(NULL, height, width, trans, true) { size_t size = 0; @@ -118,9 +134,15 @@ GpuSparseMatrix::GpuSparseMatrix(real* value, int* rows, int* cols, /* construct hl_sparse_matrix_s */ hl_sparse_matrix_s tmp; hl_construct_sparse_matrix( - &tmp, value, rows, cols, HL_SPARSE_CSR, - valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, height_, - width_, elementCnt_); + &tmp, + value, + rows, + cols, + HL_SPARSE_CSR, + valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, + height_, + width_, + elementCnt_); hl_sparse_matrix_s_ptr tmp2(tmp, hl_destruct_sparse_matrix); sMatrix_ = tmp2; } @@ -143,9 +165,15 @@ GpuSparseMatrix::GpuSparseMatrix(real* value, int* rows, int* cols, /* construct hl_sparse_matrix_s */ hl_sparse_matrix_s tmp; hl_construct_sparse_matrix( - &tmp, value, rows, cols, HL_SPARSE_CSC, - valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, height_, - width_, elementCnt_); + &tmp, + value, + rows, + cols, + HL_SPARSE_CSC, + valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, + height_, + width_, + elementCnt_); hl_sparse_matrix_s_ptr tmp2(tmp, hl_destruct_sparse_matrix); sMatrix_ = tmp2; } @@ -171,8 +199,13 @@ void GpuSparseMatrix::sparseResizeCSR() { /* construct hl_sparse_matrix_s */ hl_sparse_matrix_s tmp; hl_construct_sparse_matrix( - &tmp, data_, memoryHandle_->getSize(), HL_SPARSE_CSR, - valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, height_, width_, + &tmp, + data_, + memoryHandle_->getSize(), + HL_SPARSE_CSR, + valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, + height_, + width_, elementCnt_); hl_sparse_matrix_s_ptr tmp2(tmp, hl_destruct_sparse_matrix); sMatrix_ = tmp2; @@ -197,16 +230,24 @@ void GpuSparseMatrix::sparseResizeCSC() { /* construct hl_sparse_matrix_s */ hl_sparse_matrix_s tmp; hl_construct_sparse_matrix( - &tmp, memoryHandle_->getBuf(), memoryHandle_->getSize(), HL_SPARSE_CSC, - valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, height_, width_, + &tmp, + memoryHandle_->getBuf(), + memoryHandle_->getSize(), + HL_SPARSE_CSC, + valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE, + height_, + width_, elementCnt_); hl_sparse_matrix_s_ptr tmp2(tmp, hl_destruct_sparse_matrix); sMatrix_ = tmp2; } } -void GpuSparseMatrix::resize(size_t newHeight, size_t newWidth, size_t newNnz, - SparseValueType valueType, SparseFormat format) { +void GpuSparseMatrix::resize(size_t newHeight, + size_t newWidth, + size_t newNnz, + SparseValueType valueType, + SparseFormat format) { if (format == SPARSE_CSR) { resizeCSR(newHeight, newWidth, newNnz, valueType); } else { @@ -214,8 +255,10 @@ void GpuSparseMatrix::resize(size_t newHeight, size_t newWidth, size_t newNnz, } } -void GpuSparseMatrix::resizeCSR(size_t newHeight, size_t newWidth, - size_t newNnz, SparseValueType valueType) { +void GpuSparseMatrix::resizeCSR(size_t newHeight, + size_t newWidth, + size_t newNnz, + SparseValueType valueType) { size_t newSize = (newHeight + 1) * sizeof(int) + newNnz * sizeof(int); if (NO_VALUE != valueType) { newSize += newNnz * sizeof(real); @@ -266,8 +309,10 @@ void GpuSparseMatrix::resizeCSR(size_t newHeight, size_t newWidth, } } -void GpuSparseMatrix::resizeCSC(size_t newHeight, size_t newWidth, - size_t newNnz, SparseValueType valueType) { +void GpuSparseMatrix::resizeCSC(size_t newHeight, + size_t newWidth, + size_t newNnz, + SparseValueType valueType) { size_t newSize = (newWidth + 1) * sizeof(int) + newNnz * sizeof(int); if (NO_VALUE != valueType) { newSize += newNnz * sizeof(real); @@ -327,24 +372,37 @@ MatrixPtr GpuSparseMatrix::getTranspose() { CHECK(memoryHandle_.get() || sMatrix_) << "not supported"; if (memoryHandle_.get()) { MatrixPtr copy_T(new GpuSparseMatrix( - std::dynamic_pointer_cast(memoryHandle_), sMatrix_, - height_, width_, elementCnt_, valueType_, format_, true, + std::dynamic_pointer_cast(memoryHandle_), + sMatrix_, + height_, + width_, + elementCnt_, + valueType_, + format_, + true, sMemoryHandle_)); return copy_T; } else { - MatrixPtr copy_T(new GpuSparseMatrix(sMatrix_, height_, width_, elementCnt_, - valueType_, format_, true, + MatrixPtr copy_T(new GpuSparseMatrix(sMatrix_, + height_, + width_, + elementCnt_, + valueType_, + format_, + true, sMemoryHandle_)); return copy_T; } } -void GpuSparseMatrix::copyRow(int offsets, size_t colNum, +void GpuSparseMatrix::copyRow(int offsets, + size_t colNum, const sparse_non_value_t* row) { memcpy(cols_ + offsets, row, sizeof(int) * colNum); } -void GpuSparseMatrix::copyRow(int offsets, size_t colNum, +void GpuSparseMatrix::copyRow(int offsets, + size_t colNum, const sparse_float_value_t* row) { for (size_t j = 0; j < colNum; j++) { cols_[offsets + j] = row[j].col; @@ -368,7 +426,9 @@ void GpuSparseMatrix::copyFrom(const Matrix& src) { } template -void GpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, T* data, +void GpuSparseMatrix::copyFrom(int64_t* ids, + int64_t* indices, + T* data, hl_stream_t stream) { CHECK_EQ(format_, SPARSE_CSR); size_t nnz = 0; @@ -377,7 +437,9 @@ void GpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, T* data, nnz += indices[id + 1] - indices[id]; } - resize(height_, width_, nnz, + resize(height_, + width_, + nnz, sizeof(T) == sizeof(sparse_non_value_t) ? NO_VALUE : FLOAT_VALUE, format_); @@ -399,8 +461,10 @@ void GpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, T* data, hl_memcpy_csr_matrix(sMatrix_.get(), value_, rows_, cols_, stream); } -void GpuSparseMatrix::setRow(size_t row, size_t colNum, - const unsigned int* cols, const real* values) { +void GpuSparseMatrix::setRow(size_t row, + size_t colNum, + const unsigned int* cols, + const real* values) { CHECK_EQ(format_, SPARSE_CSR); if (NO_VALUE == valueType_) { CHECK_LT(row, height_); @@ -427,8 +491,8 @@ void GpuSparseMatrix::setRow(size_t row, size_t colNum, sMatrix_->rows = height_; sMatrix_->cols = width_; sMatrix_->nnz = elementCnt_; - hl_memcpy_csr_matrix(sMatrix_.get(), value_, rows_, cols_, - HPPL_STREAM_DEFAULT); + hl_memcpy_csr_matrix( + sMatrix_.get(), value_, rows_, cols_, HPPL_STREAM_DEFAULT); } } @@ -438,8 +502,8 @@ void GpuSparseMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { CHECK_EQ(format_, SPARSE_CSC); int nnz = sMatrix_->nnz; if (memAlloc) { - matTrans = std::make_shared(width_, height_, nnz, - valueType_, format_, false); + matTrans = std::make_shared( + width_, height_, nnz, valueType_, format_, false); } else { CHECK(matTrans != nullptr); } @@ -449,9 +513,14 @@ void GpuSparseMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { CpuIVector cols_full(nnz); CpuVector value(nnz); hl_stream_t stream = HPPL_STREAM_1; - hl_memcpy_from_csc_matrix(value.getData(), nnz, rows.getData(), nnz, - cols.getData(), width_ + 1, - sMatrix_.get(), stream); + hl_memcpy_from_csc_matrix(value.getData(), + nnz, + rows.getData(), + nnz, + cols.getData(), + width_ + 1, + sMatrix_.get(), + stream); hl_stream_synchronize(stream); @@ -465,12 +534,14 @@ void GpuSparseMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { /*sort row index and column index by the ascending order*/ for (int i = 0; i < nnz; i++) { - dataVec.emplace_back(rows.getData()[i], cols_full.getData()[i], - value.getData()[i]); + dataVec.emplace_back( + rows.getData()[i], cols_full.getData()[i], value.getData()[i]); } - std::sort(dataVec.begin(), dataVec.end(), [](Element a, Element b) { - return a.row < b.row || (a.row == b.row && a.col < b.col); - }); + std::sort(dataVec.begin(), + dataVec.end(), + [](Element a, Element b) { + return a.row < b.row || (a.row == b.row && a.col < b.col); + }); /*get sorted data, row index, and col index, put them in the right place*/ cols.resize(height_ + 1); @@ -494,13 +565,18 @@ void GpuSparseMatrix::transpose(MatrixPtr matTrans, bool memAlloc) { /*copy back from cpu*/ GpuSparseMatrixPtr dest = std::dynamic_pointer_cast(matTrans); - hl_memcpy_csc_matrix((dest->sMatrix_).get(), value.getData(), - rows.getData(), cols.getData(), stream); + hl_memcpy_csc_matrix((dest->sMatrix_).get(), + value.getData(), + rows.getData(), + cols.getData(), + stream); hl_stream_synchronize(stream); } -void GpuSparseMatrix::mul(const GpuMatrixPtr a, const GpuMatrixPtr b, - real scaleAB, real scaleT) { +void GpuSparseMatrix::mul(const GpuMatrixPtr a, + const GpuMatrixPtr b, + real scaleAB, + real scaleT) { CHECK(a->useGpu_ && b->useGpu_) << "type not match"; CHECK(!trans_) << "trans not supported"; real* A_d = a->getData(); @@ -527,11 +603,13 @@ void GpuSparseMatrix::mul(const GpuMatrixPtr a, const GpuMatrixPtr b, int dimM = height_; int dimN = width_; int dimK = !b->trans_ ? b->getHeight() : b->getWidth(); - hl_sparse_matrix_mul(A_d, a_trans, B_d, b_trans, C_d, dimM, - dimN, dimK, scaleAB, scaleT); + hl_sparse_matrix_mul( + A_d, a_trans, B_d, b_trans, C_d, dimM, dimN, dimK, scaleAB, scaleT); } -void GpuSparseMatrix::mul(const MatrixPtr a, const MatrixPtr b, real scaleAB, +void GpuSparseMatrix::mul(const MatrixPtr a, + const MatrixPtr b, + real scaleAB, real scaleT) { if (std::dynamic_pointer_cast(a) && std::dynamic_pointer_cast(b)) { @@ -559,9 +637,14 @@ void GpuSparseMatrix::print(std::ostream& os) const { IVectorPtr cols = IVector::create(width_ + 1, false); VectorPtr value = Vector::create(nnz, false); hl_stream_t stream = HPPL_STREAM_DEFAULT; - hl_memcpy_from_csc_matrix( - value->getData(), value->getSize(), rows->getData(), rows->getSize(), - cols->getData(), cols->getSize(), sMatrix_.get(), stream); + hl_memcpy_from_csc_matrix(value->getData(), + value->getSize(), + rows->getData(), + rows->getSize(), + cols->getData(), + cols->getSize(), + sMatrix_.get(), + stream); hl_stream_synchronize(stream); printBuf(os, cols->getData(), width_ + 1, "col idx"); @@ -574,11 +657,10 @@ void GpuSparseMatrix::copyFromCSR(CpuSparseMatrix& src, hl_stream_t stream) { trans_ = src.trans_; size_t nnz = src.getElementCnt(); - resize(src.getHeight(), src.getWidth(), nnz, valueType_, - src.getFormat()); + resize(src.getHeight(), src.getWidth(), nnz, valueType_, src.getFormat()); // if have different value type, only copy rows and cols SparseValueType vType = - valueType_ != src.getValueType() ? NO_VALUE : valueType_; + valueType_ != src.getValueType() ? NO_VALUE : valueType_; sMatrix_->format = HL_SPARSE_CSR; sMatrix_->type = vType == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE; @@ -588,7 +670,9 @@ void GpuSparseMatrix::copyFromCSR(CpuSparseMatrix& src, hl_stream_t stream) { hl_memcpy_csr_matrix(sMatrix_.get(), vType == NO_VALUE ? NULL : src.getValue(), - src.getRows(), src.getCols(), stream); + src.getRows(), + src.getCols(), + stream); // restore type of sMatrix_ sMatrix_->type = valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE; @@ -598,12 +682,11 @@ void GpuSparseMatrix::copyFromCSC(CpuSparseMatrix& src, hl_stream_t stream) { trans_ = src.trans_; size_t nnz = src.getElementCnt(); - resize(src.getHeight(), src.getWidth(), nnz, valueType_, - src.getFormat()); + resize(src.getHeight(), src.getWidth(), nnz, valueType_, src.getFormat()); // if have different value type, only copy rows and cols SparseValueType vType = - valueType_ != src.getValueType() ? NO_VALUE : valueType_; + valueType_ != src.getValueType() ? NO_VALUE : valueType_; sMatrix_->format = HL_SPARSE_CSC; sMatrix_->type = vType == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE; @@ -613,7 +696,9 @@ void GpuSparseMatrix::copyFromCSC(CpuSparseMatrix& src, hl_stream_t stream) { hl_memcpy_csc_matrix(sMatrix_.get(), vType == NO_VALUE ? NULL : src.getValue(), - src.getRows(), src.getCols(), stream); + src.getRows(), + src.getCols(), + stream); // restore type of sMatrix_ sMatrix_->type = valueType_ == NO_VALUE ? HL_NO_VALUE : HL_FLOAT_VALUE; @@ -622,23 +707,24 @@ void GpuSparseMatrix::copyFromCSC(CpuSparseMatrix& src, hl_stream_t stream) { void GpuSparseMatrix::copyFrom(GpuSparseMatrix& src, hl_stream_t stream) { CHECK(trans_ == src.trans_); CHECK(format_ == src.getFormat()); - resize(src.getHeight(), src.getWidth(), elementCnt_, valueType_, + resize(src.getHeight(), + src.getWidth(), + elementCnt_, + valueType_, src.getFormat()); size_t rowSize = format_ == SPARSE_CSC ? elementCnt_ : height_ + 1; size_t colSize = format_ == SPARSE_CSC ? width_ + 1 : elementCnt_; if (valueType_ == FLOAT_VALUE && src.getValueType() == FLOAT_VALUE) { - hl_memcpy_async(getValue(), src.getValue(), - sizeof(real) * elementCnt_, stream); + hl_memcpy_async( + getValue(), src.getValue(), sizeof(real) * elementCnt_, stream); } CHECK(getRows()); CHECK(src.getRows()); - hl_memcpy_async(getRows(), src.getRows(), - sizeof(int) * rowSize, stream); - hl_memcpy_async(getCols(), src.getCols(), - sizeof(int) * colSize, stream); + hl_memcpy_async(getRows(), src.getRows(), sizeof(int) * rowSize, stream); + hl_memcpy_async(getCols(), src.getCols(), sizeof(int) * colSize, stream); } void GpuSparseMatrix::copyFrom(CpuSparseMatrix& src, hl_stream_t stream) { @@ -652,7 +738,8 @@ void GpuSparseMatrix::copyFrom(CpuSparseMatrix& src, hl_stream_t stream) { void GpuSparseMatrix::trimFromCSR(const CpuSparseMatrix& src) { trans_ = src.trans_; int* srcCols = src.getCols(); - size_t nnz = std::count_if(srcCols, srcCols + src.getElementCnt(), + size_t nnz = std::count_if(srcCols, + srcCols + src.getElementCnt(), [this](size_t n) { return n < this->width_; }); resize(height_, width_, nnz, valueType_, format_); @@ -678,9 +765,11 @@ void GpuSparseMatrix::trimFromCSR(const CpuSparseMatrix& src) { sMatrix_->cols = width_; sMatrix_->nnz = nnz; - hl_memcpy_csr_matrix( - sMatrix_.get(), valueType_ == NO_VALUE ? NULL : value_, rows_, cols_, - /*default stream = */ HPPL_STREAM_DEFAULT); + hl_memcpy_csr_matrix(sMatrix_.get(), + valueType_ == NO_VALUE ? NULL : value_, + rows_, + cols_, + /*default stream = */ HPPL_STREAM_DEFAULT); } void GpuSparseMatrix::trimFromCSC(const CpuSparseMatrix& src) { @@ -703,9 +792,11 @@ void GpuSparseMatrix::trimFromCSC(const CpuSparseMatrix& src) { sMatrix_->cols = width_; sMatrix_->nnz = nnz; - hl_memcpy_csc_matrix( - sMatrix_.get(), valueType_ == NO_VALUE ? NULL : value_, rows_, cols_, - /*default stream = */ HPPL_STREAM_DEFAULT); + hl_memcpy_csc_matrix(sMatrix_.get(), + valueType_ == NO_VALUE ? NULL : value_, + rows_, + cols_, + /*default stream = */ HPPL_STREAM_DEFAULT); } void GpuSparseMatrix::trimFrom(const CpuSparseMatrix& src) { @@ -766,10 +857,12 @@ void GpuSparseMatrix::rowMax(IVector& maxIds, Matrix& maxVal) { #endif } -template void GpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, +template void GpuSparseMatrix::copyFrom(int64_t* ids, + int64_t* indices, sparse_non_value_t* data, hl_stream_t stream); -template void GpuSparseMatrix::copyFrom(int64_t* ids, int64_t* indices, +template void GpuSparseMatrix::copyFrom(int64_t* ids, + int64_t* indices, sparse_float_value_t* data, hl_stream_t stream); } // namespace paddle diff --git a/paddle/math/SparseMatrix.h b/paddle/math/SparseMatrix.h index 4b9a03302b..175ef54b85 100644 --- a/paddle/math/SparseMatrix.h +++ b/paddle/math/SparseMatrix.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include "Matrix.h" @@ -35,25 +34,41 @@ public: SparseFormat format_; public: - GpuSparseMatrix(size_t height, size_t width, + GpuSparseMatrix(size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType = FLOAT_VALUE, - SparseFormat format_ = SPARSE_CSR, bool trans = false); + SparseFormat format_ = SPARSE_CSR, + bool trans = false); - GpuSparseMatrix(GpuMemHandlePtr dataHandle, hl_sparse_matrix_s_ptr sMatrix, - size_t height, size_t width, + GpuSparseMatrix(GpuMemHandlePtr dataHandle, + hl_sparse_matrix_s_ptr sMatrix, + size_t height, + size_t width, size_t nnz, /* used to allocate space */ SparseValueType valueType = FLOAT_VALUE, - SparseFormat format_ = SPARSE_CSR, bool trans = false, + SparseFormat format_ = SPARSE_CSR, + bool trans = false, MemoryHandlePtr sMemoryHandle = NULL); - GpuSparseMatrix(real* value, int* rows, int* cols, size_t height, - size_t width, size_t nnz, SparseValueType valueType, - SparseFormat format, bool trans); - - GpuSparseMatrix(hl_sparse_matrix_s_ptr sMatrix, size_t height, size_t width, - size_t nnz, SparseValueType valueType, SparseFormat format, - bool trans, MemoryHandlePtr sMemoryHandle); + GpuSparseMatrix(real* value, + int* rows, + int* cols, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, + bool trans); + + GpuSparseMatrix(hl_sparse_matrix_s_ptr sMatrix, + size_t height, + size_t width, + size_t nnz, + SparseValueType valueType, + SparseFormat format, + bool trans, + MemoryHandlePtr sMemoryHandle); protected: struct Element { @@ -67,9 +82,11 @@ protected: public: ~GpuSparseMatrix() {} - void resize(size_t newHeight, size_t newWidth, + void resize(size_t newHeight, + size_t newWidth, size_t newNnz, /* used to allocate space */ - SparseValueType valueType, SparseFormat format); + SparseValueType valueType, + SparseFormat format); void resize(size_t newHeight, size_t newWidth); @@ -77,13 +94,19 @@ public: void sparseResizeCSC(); - void resizeCSR(size_t newHeight, size_t newWidth, size_t newNnz, + void resizeCSR(size_t newHeight, + size_t newWidth, + size_t newNnz, SparseValueType valueType); - void resizeCSC(size_t newHeight, size_t newWidth, size_t newNnz, + void resizeCSC(size_t newHeight, + size_t newWidth, + size_t newNnz, SparseValueType valueType); - void mul(const GpuMatrixPtr a, const GpuMatrixPtr b, real scaleAB, + void mul(const GpuMatrixPtr a, + const GpuMatrixPtr b, + real scaleAB, real scaleT); /// B = A , B.trans = !A.trans MatrixPtr getTranspose(); @@ -104,7 +127,9 @@ public: template void copyFrom(int64_t* ids, int64_t* indices, T* data, hl_stream_t stream); - void setRow(size_t row, size_t colNum, const unsigned int* cols, + void setRow(size_t row, + size_t colNum, + const unsigned int* cols, const real* values); SparseValueType getValueType() const; SparseFormat getFormat() const { return format_; } @@ -173,7 +198,7 @@ public: * getData is convenient to get value */ real* getData() { return getValue(); } - const real* getData() const { return getValue();} + const real* getData() const { return getValue(); } /** * @brief Get top k value of each row in sparse matrix. @@ -204,9 +229,7 @@ public: // BaseMatrixT interface public: - bool isSparse() const { - return true; - } + bool isSparse() const { return true; } private: using Matrix::mul; diff --git a/paddle/math/SparseRowMatrix.cpp b/paddle/math/SparseRowMatrix.cpp index 6986624d25..eefaf4b71f 100644 --- a/paddle/math/SparseRowMatrix.cpp +++ b/paddle/math/SparseRowMatrix.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "SparseRowMatrix.h" #include "CpuSparseMatrix.h" @@ -26,7 +25,8 @@ limitations under the License. */ #include "paddle/utils/Util.h" #include "paddle/utils/Thread.h" -P_DEFINE_bool(allow_inefficient_sparse_update, false, +P_DEFINE_bool(allow_inefficient_sparse_update, + false, "Whether to allow inefficient sparse update"); namespace paddle { @@ -45,7 +45,9 @@ void SparseRowCpuMatrix::init(size_t height, size_t width) { globalIndices_ = indexDictHandle_->globalIndices.data(); } -void SparseRowCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, +void SparseRowCpuMatrix::mul(CpuSparseMatrix* a, + CpuMatrix* b, + real scaleAB, real scaleT) { CpuMatrix::mul(a, b, this, scaleAB, scaleT); } @@ -55,24 +57,25 @@ void SparseRowCpuMatrix::copyFrom(const real* src, size_t size) { } void SparseRowCpuMatrix::zeroMem() { - apply( - [](real* buf, size_t len) { - memset(buf, 0, sizeof(real) * len); - }); + apply([](real* buf, size_t len) { memset(buf, 0, sizeof(real) * len); }); clearRows(); } void SparseRowCpuMatrix::applyL1Decay(real learningRate, real decayRate) { apply([=](real* buf, size_t len) { - CpuVector value(0, nullptr); - value.subVecFrom(buf, 0, len); - value.applyL1(learningRate, decayRate); - }); + CpuVector value(0, nullptr); + value.subVecFrom(buf, 0, len); + value.applyL1(learningRate, decayRate); + }); } -void SparseRowCpuMatrix::sgdUpdate(BaseMatrix& value, IVector& t0, - real learningRate, int currentTime, - real decayRate, bool useL1, bool fini) { +void SparseRowCpuMatrix::sgdUpdate(BaseMatrix& value, + IVector& t0, + real learningRate, + int currentTime, + real decayRate, + bool useL1, + bool fini) { std::vector& localIndices = indexDictHandle_->localIndices; // t0 and value are vectors @@ -124,7 +127,7 @@ void SparseRowCpuMatrix::sgdUpdate(BaseMatrix& value, IVector& t0, for (size_t j = 0; j < this->width_; ++j) { v[j] -= learningRate * g[j]; } - simd::decayL1(v, v, learningRate*decayRate, this->width_); + simd::decayL1(v, v, learningRate * decayRate, this->width_); // state update to t+1 t[0] = currentTime + 1; @@ -173,8 +176,10 @@ void SparseRowCpuMatrix::sgdUpdate(BaseMatrix& value, IVector& t0, } } -void SparseRowCpuMatrix::addTo(BaseMatrix& dest, std::vector& ids, - size_t tid, size_t numThreads) { +void SparseRowCpuMatrix::addTo(BaseMatrix& dest, + std::vector& ids, + size_t tid, + size_t numThreads) { CHECK(!dest.useGpu_); CHECK_EQ(dest.height_ * dest.width_, this->height_ * this->width_); @@ -182,14 +187,14 @@ void SparseRowCpuMatrix::addTo(BaseMatrix& dest, std::vector& ids, for (size_t i = 0; i < localIndices.size(); ++i) { uint32_t id = localIndices[i]; if (id % numThreads == tid) { - simd::addTo(dest.rowBuf(id), getLocalRow(i), - this->width_); + simd::addTo(dest.rowBuf(id), getLocalRow(i), this->width_); ids.push_back(id); } } } -void SparseRowCpuMatrix::addTo(SparseRowCpuMatrix& dest, size_t tid, +void SparseRowCpuMatrix::addTo(SparseRowCpuMatrix& dest, + size_t tid, size_t numThreads) { CHECK(!dest.useGpu_); CHECK_EQ(dest.height_ * dest.width_, this->height_ * this->width_); @@ -214,24 +219,28 @@ void SparseRowCpuMatrix::zeroMemThread(size_t tid, size_t numThreads) { } } -void SparseAutoGrowRowCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, - real scaleAB, real scaleT) { - CpuMatrix::mul(a, b, this, scaleAB, - scaleT); +void SparseAutoGrowRowCpuMatrix::mul(CpuSparseMatrix* a, + CpuMatrix* b, + real scaleAB, + real scaleT) { + CpuMatrix::mul( + a, b, this, scaleAB, scaleT); } -void CacheRowCpuMatrix::mul(CpuSparseMatrix* a, CpuMatrix* b, real scaleAB, +void CacheRowCpuMatrix::mul(CpuSparseMatrix* a, + CpuMatrix* b, + real scaleAB, real scaleT) { CpuMatrix::mul(a, b, this, scaleAB, scaleT); } void SparsePrefetchRowCpuMatrix::addRows(const unsigned int* ids, size_t len) { std::vector& localIndices = indexDictHandle_->localIndices; - for (size_t i = 0; i < len; i ++) { + for (size_t i = 0; i < len; i++) { CHECK_LT(*(ids + i), this->getHeight()) - << "id:" << *(ids + i) << "Height:" << this->getHeight() - << "sparse id value exceeds the max input dimension, " - << "it could be caused invalid input data samples"; + << "id:" << *(ids + i) << "Height:" << this->getHeight() + << "sparse id value exceeds the max input dimension, " + << "it could be caused invalid input data samples"; } localIndices.insert(localIndices.end(), ids, ids + len); } @@ -252,9 +261,9 @@ void SparsePrefetchRowCpuMatrix::addRows(IVectorPtr ids) { unsigned int id = (unsigned int)index[i]; CHECK_LT(id, this->getHeight()) - << "id:" << id << "Height:" << this->getHeight() - << "sparse id value exceeds the max input dimension, " - << "it could be caused invalid input data samples"; + << "id:" << id << "Height:" << this->getHeight() + << "sparse id value exceeds the max input dimension, " + << "it could be caused invalid input data samples"; localIndices.push_back(id); } } diff --git a/paddle/math/SparseRowMatrix.h b/paddle/math/SparseRowMatrix.h index 2dcd81188d..56f113a361 100644 --- a/paddle/math/SparseRowMatrix.h +++ b/paddle/math/SparseRowMatrix.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -41,12 +40,15 @@ public: /// heightStore is max number of rows of the sparse matrix. SparseRowCpuMatrix(CpuMemHandlePtr dataHandle, - size_t height, size_t width, - IndexDictPtr indexDictHandle = nullptr, bool trans = false) + size_t height, + size_t width, + IndexDictPtr indexDictHandle = nullptr, + bool trans = false) : CpuMatrix(nullptr, height, width, trans), storeMat_(dataHandle, dataHandle ? dataHandle->getSize() / sizeof(real) / width : 0, - width, trans), + width, + trans), indexDictHandle_(indexDictHandle) { init(height, width); } @@ -123,8 +125,12 @@ public: * While pass finished, caller should call this func one more time * with (fini=true) to let weight decay catch up current time. */ - void sgdUpdate(BaseMatrix& value, IVector& t0, real learningRate, - int currentTime, real decayRate, bool useL1, + void sgdUpdate(BaseMatrix& value, + IVector& t0, + real learningRate, + int currentTime, + real decayRate, + bool useL1, bool fini = false); /** @@ -135,7 +141,9 @@ public: * ids occured in *this* append to *ids* * filtered by (id % numThreads == tid) */ - void addTo(BaseMatrix& dest, std::vector& ids, size_t tid, + void addTo(BaseMatrix& dest, + std::vector& ids, + size_t tid, size_t numThreads); /** @@ -166,7 +174,7 @@ public: } protected: - template + template void apply(Func f) { real* data = storeMat_.getData() ? storeMat_.getData() : rowStore_.data(); f(data, localIndices_->size() * width_); @@ -211,9 +219,11 @@ class SyncThreadPool; class SparsePrefetchRowCpuMatrix : public SparseRowCpuMatrix { public: SparsePrefetchRowCpuMatrix(CpuMemHandlePtr dataHandle, - size_t height, size_t width, + size_t height, + size_t width, IndexDictPtr indexDictHandle = nullptr, - SyncThreadPool* pool = nullptr, bool trans = false) + SyncThreadPool* pool = nullptr, + bool trans = false) : SparseRowCpuMatrix(dataHandle, height, width, indexDictHandle, trans), pool_(pool) {} @@ -239,7 +249,8 @@ protected: class SparseAutoGrowRowCpuMatrix : public SparseRowCpuMatrix { public: - SparseAutoGrowRowCpuMatrix(size_t height, size_t width, + SparseAutoGrowRowCpuMatrix(size_t height, + size_t width, IndexDictPtr indexDictHandle = nullptr, bool trans = false) : SparseRowCpuMatrix(nullptr, height, width, indexDictHandle, trans) {} @@ -261,8 +272,10 @@ public: class CacheRowCpuMatrix : public SparseAutoGrowRowCpuMatrix { public: - CacheRowCpuMatrix(size_t height, size_t width, - IndexDictPtr indexDictHandle = nullptr, bool trans = false) + CacheRowCpuMatrix(size_t height, + size_t width, + IndexDictPtr indexDictHandle = nullptr, + bool trans = false) : SparseAutoGrowRowCpuMatrix(height, width, indexDictHandle, trans), sourceData_(nullptr) {} @@ -277,8 +290,8 @@ public: id = globalIndices_[row] = localIndices_->size(); localIndices_->push_back(row); checkStoreSize(); - memcpy(getLocalRow(id), sourceData_ + width_ * row, - sizeof(float) * width_); + memcpy( + getLocalRow(id), sourceData_ + width_ * row, sizeof(float) * width_); } return getLocalRow(id); } @@ -300,7 +313,9 @@ public: */ class SparseRowIdsCpuMatrix : public CpuMatrix { public: - SparseRowIdsCpuMatrix(CpuMemHandlePtr dataHandle, size_t height, size_t width, + SparseRowIdsCpuMatrix(CpuMemHandlePtr dataHandle, + size_t height, + size_t width, bool trans = false) : CpuMatrix(dataHandle, height, width, trans) {} diff --git a/paddle/math/Storage.cpp b/paddle/math/Storage.cpp index 0403c3521c..57ea5c9266 100644 --- a/paddle/math/Storage.cpp +++ b/paddle/math/Storage.cpp @@ -12,12 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "Allocator.h" #include "Storage.h" -P_DEFINE_int32(pool_limit_size, 536870912, +P_DEFINE_int32(pool_limit_size, + 536870912, "maximum memory size managed by a memory pool, default is 512M"); namespace paddle { @@ -25,11 +25,10 @@ namespace paddle { // Initialization StorageEngine singleton. // Other modules may rely on storage management, // so StorageEngine need to be initialized before other modules. -static InitFunction __init_storage_engine([](){StorageEngine::singleton();}, +static InitFunction __init_storage_engine([]() { StorageEngine::singleton(); }, std::numeric_limits::max()); -StorageEngine::StorageEngine() : cpuAllocator_(nullptr) { -} +StorageEngine::StorageEngine() : cpuAllocator_(nullptr) {} StorageEngine::~StorageEngine() { if (cpuAllocator_) { @@ -49,8 +48,8 @@ PoolAllocator* StorageEngine::getGpuAllocator(int deviceId) { { // if gpuAllocator_ has been constructed ReadLockGuard guard(lock_); - if (deviceId < static_cast(gpuAllocator_.size()) - && (gpuAllocator_[deviceId] != nullptr)) { + if (deviceId < static_cast(gpuAllocator_.size()) && + (gpuAllocator_[deviceId] != nullptr)) { return gpuAllocator_[deviceId]; } } @@ -63,9 +62,9 @@ PoolAllocator* StorageEngine::getGpuAllocator(int deviceId) { } if (gpuAllocator_[deviceId] == nullptr) { std::string name = - "gpu" + std::to_string(deviceId) + std::string("_pool"); - gpuAllocator_[deviceId] = new PoolAllocator( - new GpuAllocator(), FLAGS_pool_limit_size, name); + "gpu" + std::to_string(deviceId) + std::string("_pool"); + gpuAllocator_[deviceId] = + new PoolAllocator(new GpuAllocator(), FLAGS_pool_limit_size, name); } return gpuAllocator_[deviceId]; } @@ -86,10 +85,10 @@ PoolAllocator* StorageEngine::getCpuAllocator() { if (cpuAllocator_ == nullptr) { if (FLAGS_use_gpu) { cpuAllocator_ = new PoolAllocator( - new CudaHostAllocator(), FLAGS_pool_limit_size, "cuda_host_pool"); + new CudaHostAllocator(), FLAGS_pool_limit_size, "cuda_host_pool"); } else { cpuAllocator_ = new PoolAllocator( - new CpuAllocator(), FLAGS_pool_limit_size, "cpu_pool"); + new CpuAllocator(), FLAGS_pool_limit_size, "cpu_pool"); } } return cpuAllocator_; diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index 68a1518d67..b2ade83138 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "Vector.h" @@ -49,7 +48,8 @@ std::shared_ptr> VectorT::createParallelVector( } template -std::shared_ptr> VectorT::create(T* data, size_t size, +std::shared_ptr> VectorT::create(T* data, + size_t size, bool useGpu) { if (useGpu) { return std::make_shared>(size, data); @@ -63,10 +63,10 @@ std::shared_ptr> VectorT::create(size_t size, MemoryHandlePtr memoryHandle, size_t offset) { if (auto cpuMemHandle = - std::dynamic_pointer_cast(memoryHandle)) { + std::dynamic_pointer_cast(memoryHandle)) { return std::make_shared>(size, cpuMemHandle, offset); } else if (auto gpuMemHandle = - std::dynamic_pointer_cast(memoryHandle)) { + std::dynamic_pointer_cast(memoryHandle)) { return std::make_shared>(size, gpuMemHandle, offset); } else { LOG(FATAL) << "Wrong"; @@ -76,8 +76,8 @@ std::shared_ptr> VectorT::create(size_t size, template <> MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { - LOG(FATAL) << "Wrong for real vector"; - return nullptr; + LOG(FATAL) << "Wrong for real vector"; + return nullptr; } template <> @@ -89,9 +89,9 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { CpuIVector cpuIds(height); cpuIds.copyFrom(*this); - int *idData = cpuIds.getData(); + int* idData = cpuIds.getData(); - for (decltype(height) i = 0; i < height; i ++) { + for (decltype(height) i = 0; i < height; i++) { const unsigned int id = idData[i]; CHECK_LT(id, width); mat->setRow(i, 1, &id, nullptr); @@ -101,21 +101,20 @@ MatrixPtr VectorT::toOneHotSparseMatrix(size_t idRange, bool useGpu) { template GpuVectorT::GpuVectorT(size_t size) - : VectorT(size, std::make_shared(sizeof(T) * size), + : VectorT(size, + std::make_shared(sizeof(T) * size), 0, /* offset = 0 */ true /* useGpu = true */) {} template T GpuVectorT::getElement(size_t i) const { T elem = 0; - hl_memcpy_device2host(&elem, const_cast(&this->getData()[i]), - sizeof(T)); + hl_memcpy_device2host(&elem, const_cast(&this->getData()[i]), sizeof(T)); return elem; } template void GpuVectorT::setElement(size_t i, const T& value) { - hl_memcpy_host2device(&this->getData()[i], const_cast(&value), - sizeof(T)); + hl_memcpy_host2device(&this->getData()[i], const_cast(&value), sizeof(T)); } template @@ -219,8 +218,7 @@ real GpuVectorT::getMin() { template T GpuVectorT::get(size_t pos) { T val = (T)0; - hl_memcpy_device2host((void*)&val, (void*)(this->getData() + pos), - sizeof(T)); + hl_memcpy_device2host((void*)&val, (void*)(this->getData() + pos), sizeof(T)); return val; } @@ -229,7 +227,7 @@ void GpuVectorT::histogram(std::ostream& os, int type) { LOG(FATAL) << "Not implemented"; } -template +template void GpuVectorT::zeroMem() { BaseMatrixT::zero(); } @@ -252,8 +250,10 @@ void GpuVectorT::copyFrom(const VectorT& src) { template void GpuVectorT::copyFrom(const VectorT& src, hl_stream_t stream) { CHECK_EQ(src.getSize(), this->getSize()); - hl_memcpy_async((void*)this->getData(), (void*)src.getData(), - sizeof(T) * this->getSize(), stream); + hl_memcpy_async((void*)this->getData(), + (void*)src.getData(), + sizeof(T) * this->getSize(), + stream); } template @@ -269,15 +269,16 @@ void GpuVectorT::copyFrom(const T* gpuSrc, size_t size, hl_stream_t stream) { CHECK(gpuSrc != NULL); CHECK_LE(size, this->size_); - hl_memcpy_async((void*)this->getData(), (void*)gpuSrc, - sizeof(T) * size, stream); + hl_memcpy_async( + (void*)this->getData(), (void*)gpuSrc, sizeof(T) * size, stream); } template void GpuVectorT::copyTo(CpuVectorT* dest) const { CHECK_EQ(this->getSize(), dest->getSize()); - hl_memcpy_device2host((void*)dest->getData(), (void*)this->getData(), + hl_memcpy_device2host((void*)dest->getData(), + (void*)this->getData(), sizeof(T) * this->getSize()); } @@ -285,7 +286,8 @@ template void GpuVectorT::copyTo(GpuVectorT* dest) const { CHECK_EQ(this->getSize(), dest->getSize()); - hl_memcpy_device2device((void*)dest->getData(), (void*)this->getData(), + hl_memcpy_device2device((void*)dest->getData(), + (void*)this->getData(), sizeof(T) * this->getSize()); } @@ -297,7 +299,8 @@ void GpuVectorT::rand() { template <> void GpuVectorT::print(std::ostream& os, size_t num) const { IVectorPtr dest = IVector::create(this->size_, false); - hl_memcpy_device2host((void*)dest->getData(), (void*)this->getData(), + hl_memcpy_device2host((void*)dest->getData(), + (void*)this->getData(), sizeof(int) * this->getSize()); dest->print(os, num); } @@ -305,7 +308,8 @@ void GpuVectorT::print(std::ostream& os, size_t num) const { template <> void GpuVectorT::print(std::ostream& os, size_t num) const { VectorPtr dest = Vector::create(this->size_, false); - hl_memcpy_device2host((void*)dest->getData(), (void*)this->getData(), + hl_memcpy_device2host((void*)dest->getData(), + (void*)this->getData(), sizeof(int) * this->getSize()); dest->print(os, num); } @@ -428,8 +432,8 @@ void GpuVectorT::randnorm(real mean, real std) { CpuVector cpuVec = CpuVector(this->getSize()); cpuVec.randnorm(mean, std); - hl_memcpy_host2device(data_, cpuVec.getData(), - this->getSize() * sizeof(real)); + hl_memcpy_host2device( + data_, cpuVec.getData(), this->getSize() * sizeof(real)); } template <> @@ -437,19 +441,22 @@ void GpuVectorT::uniform(real left, real right) { CpuVector cpuVec = CpuVector(this->getSize()); cpuVec.uniform(left, right); - hl_memcpy_host2device(data_, cpuVec.getData(), - this->getSize() * sizeof(real)); + hl_memcpy_host2device( + data_, cpuVec.getData(), this->getSize() * sizeof(real)); } template CpuVectorT::CpuVectorT(size_t size) - : VectorT(size, std::make_shared(sizeof(T) * size), + : VectorT(size, + std::make_shared(sizeof(T) * size), 0, /* offset = 0 */ false /* useGpu = false */) {} template CpuVectorT::CpuVectorT(const VectorT& src) - : VectorT(src.getSize(), src.getMemoryHandle(), 0, /* offset = 0 */ + : VectorT(src.getSize(), + src.getMemoryHandle(), + 0, /* offset = 0 */ false /* useGpu = false */) { if (typeid(*this->memoryHandle_.get()) != typeid(CpuMemoryHandle)) { this->memoryHandle_ = @@ -646,8 +653,10 @@ void CpuVectorT::copyFrom(const VectorT& src) { template void CpuVectorT::copyFrom(const VectorT& src, hl_stream_t stream) { if (typeid(src) == typeid(GpuVectorT)) { - hl_memcpy_async((void*)this->getData(), (void*)src.getData(), - sizeof(T) * this->getSize(), stream); + hl_memcpy_async((void*)this->getData(), + (void*)src.getData(), + sizeof(T) * this->getSize(), + stream); } else { src.copyTo(this); } @@ -661,7 +670,8 @@ void CpuVectorT::copyFrom(const T* hostSrc, size_t size) { } template -void CpuVectorT::copyFrom(const T* hostSrc, size_t size, +void CpuVectorT::copyFrom(const T* hostSrc, + size_t size, hl_stream_t stream) { (void)stream; @@ -679,7 +689,8 @@ void CpuVectorT::copyTo(CpuVectorT* dest) const { template void CpuVectorT::copyTo(GpuVectorT* dest) const { CHECK_EQ(this->getSize(), dest->getSize()); - hl_memcpy_host2device((void*)dest->getData(), (void*)this->getData(), + hl_memcpy_host2device((void*)dest->getData(), + (void*)this->getData(), sizeof(T) * this->getSize()); } @@ -723,8 +734,8 @@ void ParallelCpuVectorT::parallelExec(ExecFunc func) { template <> void ParallelCpuVectorT::parallelExec(ExecFunc func) { pool_->exec([this, func](int tid, size_t numThreads) { - auto interval = calcSplitArrayInterval(this->getSize(), (size_t)tid, - numThreads, 8LU /*for avx*/); + auto interval = calcSplitArrayInterval( + this->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); // setup sub bufs CpuVector subVec(0, nullptr); subVec.subVecFrom(*this, interval); @@ -743,7 +754,8 @@ void ParallelCpuVectorT::exec(SyncThreadPool::JobFunc func) { } template -CpuGpuVectorT::CpuGpuVectorT(size_t size, bool useGpu) : sync_(nullptr) { +CpuGpuVectorT::CpuGpuVectorT(size_t size, bool useGpu) + : sync_(nullptr) { if (!useGpu) { cpuVectorT_ = std::make_shared>(size); } else { @@ -754,7 +766,7 @@ CpuGpuVectorT::CpuGpuVectorT(size_t size, bool useGpu) : sync_(nullptr) { template CpuGpuVectorT::CpuGpuVectorT(const std::shared_ptr>& src) - : sync_(nullptr) { + : sync_(nullptr) { bool useGpu = src->useGpu(); if (useGpu) { gpuVectorT_ = src; @@ -766,7 +778,7 @@ CpuGpuVectorT::CpuGpuVectorT(const std::shared_ptr>& src) template CpuGpuVectorT::CpuGpuVectorT(size_t size, T* data, bool useGpu) - : sync_(nullptr) { + : sync_(nullptr) { if (!useGpu) { cpuVectorT_ = std::make_shared>(size, data); setSync(DATA_AT_CPU); @@ -777,8 +789,8 @@ CpuGpuVectorT::CpuGpuVectorT(size_t size, T* data, bool useGpu) } template -std::shared_ptr> -CpuGpuVectorT::create(size_t size, bool useGpu) { +std::shared_ptr> CpuGpuVectorT::create(size_t size, + bool useGpu) { return std::make_shared>(size, useGpu); } @@ -809,9 +821,9 @@ void CpuGpuVectorT::resize(size_t size, bool useGpu) { } template -void CpuGpuVectorT::resizeOrCreate( - std::shared_ptr>& vec, - size_t size, bool useGpu) { +void CpuGpuVectorT::resizeOrCreate(std::shared_ptr>& vec, + size_t size, + bool useGpu) { if (vec) { vec->resize(size, useGpu); } else { @@ -833,7 +845,9 @@ void CpuGpuVectorT::resizeOrCreate(size_t size, bool useGpu) { template CpuGpuVectorT::CpuGpuVectorT(CpuGpuVectorT& src, - size_t offset, size_t size) : sync_(nullptr) { + size_t offset, + size_t size) + : sync_(nullptr) { CHECK_LE(offset + size, static_cast(src.getSize())); #ifndef PADDLE_ONLY_CPU SyncedFlag* flag = src.getSync(); @@ -844,21 +858,21 @@ CpuGpuVectorT::CpuGpuVectorT(CpuGpuVectorT& src, } #endif auto cMemHandle = (src.getVector(false))->getMemoryHandle(); - cpuVectorT_ = std::make_shared>(size, - std::dynamic_pointer_cast(cMemHandle), offset); + cpuVectorT_ = std::make_shared>( + size, std::dynamic_pointer_cast(cMemHandle), offset); #ifndef PADDLE_ONLY_CPU auto gMemHandle = (src.getVector(true))->getMemoryHandle(); - gpuVectorT_ = std::make_shared>(size, - std::dynamic_pointer_cast(gMemHandle), offset); + gpuVectorT_ = std::make_shared>( + size, std::dynamic_pointer_cast(gMemHandle), offset); src.setSync(SYNCED); #endif setSync(src.getSync()); } template -std::shared_ptr> -CpuGpuVectorT::getVector(bool useGpu) const { - auto * self = const_cast*>(this); +std::shared_ptr> CpuGpuVectorT::getVector( + bool useGpu) const { + auto* self = const_cast*>(this); if (useGpu) { self->copyToGpu(); return std::const_pointer_cast>(gpuVectorT_); @@ -964,8 +978,10 @@ void CpuGpuVectorT::copyFrom(const T* data, size_t size, bool useGpu) { } template -void CpuGpuVectorT::copyFrom(const T* data, size_t size, - hl_stream_t stream, bool useGpu) { +void CpuGpuVectorT::copyFrom(const T* data, + size_t size, + hl_stream_t stream, + bool useGpu) { if (useGpu) { copyToGpu(data, size, stream); } else { @@ -975,7 +991,10 @@ void CpuGpuVectorT::copyFrom(const T* data, size_t size, template void CpuGpuVectorT::copyFrom(CpuGpuVectorT& src, - size_t offset, size_t size, bool useGpu, hl_stream_t stream) { + size_t offset, + size_t size, + bool useGpu, + hl_stream_t stream) { if (useGpu) { VectorT::resizeOrCreate(gpuVectorT_, size, true); gpuVectorT_->copyFrom(src.getData(true) + offset, size, stream); @@ -987,8 +1006,7 @@ void CpuGpuVectorT::copyFrom(CpuGpuVectorT& src, } template -void CpuGpuVectorT::copyFrom(CpuGpuVectorT& src, - hl_stream_t stream) { +void CpuGpuVectorT::copyFrom(CpuGpuVectorT& src, hl_stream_t stream) { switch (*src.getSync()) { case DATA_AT_CPU: copyFrom(*(src.getVector(false)), stream); diff --git a/paddle/math/Vector.h b/paddle/math/Vector.h index faf8186b6d..46a25c04df 100644 --- a/paddle/math/Vector.h +++ b/paddle/math/Vector.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -39,12 +38,11 @@ class SyncThreadPool; class Matrix; -template +template class BaseVector : public BaseMatrixT { public: BaseVector(size_t size, T* data, bool useGpu) - : BaseMatrixT(1, size, data, false, useGpu), - size_(this->width_) {} + : BaseMatrixT(1, size, data, false, useGpu), size_(this->width_) {} ~BaseVector() {} @@ -113,7 +111,8 @@ public: this->size_ = newSize; } - static void resizeOrCreate(std::shared_ptr>& vec, size_t size, + static void resizeOrCreate(std::shared_ptr>& vec, + size_t size, bool useGpu) { if (vec) { vec->resize(size); @@ -431,11 +430,7 @@ public: * * SYNCED: data is located in CPU and GPU simultaneously. */ - enum SyncedFlag { - DATA_AT_CPU = 0, - DATA_AT_GPU = 1, - SYNCED = 2 - }; + enum SyncedFlag { DATA_AT_CPU = 0, DATA_AT_GPU = 1, SYNCED = 2 }; /** * @brief A constructor, create cpuVectorT_ or gpuVectorT_. @@ -469,8 +464,7 @@ public: */ CpuGpuVectorT(size_t size, T* data, bool useGpu); - CpuGpuVectorT(CpuGpuVectorT& src, - size_t offset, size_t size); + CpuGpuVectorT(CpuGpuVectorT& src, size_t offset, size_t size); virtual ~CpuGpuVectorT() {} @@ -489,8 +483,8 @@ public: * @brief resize or create CpuGpuVectorT. */ static void resizeOrCreate(std::shared_ptr>& vec, - size_t size, bool useGpu); - + size_t size, + bool useGpu); /** * @brief return a const cpuVectorT_ or gpuVectorT_. @@ -522,10 +516,10 @@ public: */ const T* getData(bool useGpu) const; -// TODO(yuyang18): Make getData more c++ style. -// inline T* getData(bool useGpu) { -// return getMutableData(useGpu); -// } + // TODO(yuyang18): Make getData more c++ style. + // inline T* getData(bool useGpu) { + // return getMutableData(useGpu); + // } T* getMutableData(bool useGpu); @@ -615,8 +609,11 @@ public: /** * @brief copy from (src + offset) using specifed-stream. */ - void copyFrom(CpuGpuVectorT& src, size_t offset, size_t size, - bool useGpu, hl_stream_t stream); + void copyFrom(CpuGpuVectorT& src, + size_t offset, + size_t size, + bool useGpu, + hl_stream_t stream); /** * @brief copy from src using specifed-stream. @@ -626,16 +623,12 @@ public: /** * @brief return sync_. */ - inline SyncedFlag* getSync() const { - return sync_; - } + inline SyncedFlag* getSync() const { return sync_; } /** * @brief set sync_. */ - inline void setSync(SyncedFlag* sync) { - sync_ = sync; - } + inline void setSync(SyncedFlag* sync) { sync_ = sync; } inline void setSync(SyncedFlag syncFlag) { if (sync_) { diff --git a/paddle/math/tests/test_Allocator.cpp b/paddle/math/tests/test_Allocator.cpp index c94e7f043c..084322a1ca 100644 --- a/paddle/math/tests/test_Allocator.cpp +++ b/paddle/math/tests/test_Allocator.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include "paddle/utils/Util.h" #include "paddle/utils/Logging.h" @@ -21,11 +20,12 @@ limitations under the License. */ #include "paddle/math/Allocator.h" #include "paddle/math/PoolAllocator.h" -using namespace paddle; // NOLINT +using namespace paddle; // NOLINT -template +template void testPoolAllocator() { - PoolAllocator* pool = new PoolAllocator(new Allocator(), /* sizeLimit */1024); + PoolAllocator* pool = + new PoolAllocator(new Allocator(), /* sizeLimit */ 1024); /* alloc from system memory */ void* ptr1 = pool->alloc(10); diff --git a/paddle/math/tests/test_ExecViaCpu.cpp b/paddle/math/tests/test_ExecViaCpu.cpp index ae201f1723..b3eca19a72 100644 --- a/paddle/math/tests/test_ExecViaCpu.cpp +++ b/paddle/math/tests/test_ExecViaCpu.cpp @@ -23,7 +23,10 @@ using namespace paddle; // NOLINT const int height = 10; const int width = 16; -real f(Matrix& mat1, const Matrix& mat2, IVector& vec1, const IVector& vec2, +real f(Matrix& mat1, + const Matrix& mat2, + IVector& vec1, + const IVector& vec2, real scalar) { CHECK(!mat1.useGpu()); CHECK(!mat2.useGpu()); @@ -37,8 +40,11 @@ real f(Matrix& mat1, const Matrix& mat2, IVector& vec1, const IVector& vec2, class Functor { public: - real operator()(Matrix& mat1, const Matrix& mat2, IVector& vec1, - const IVector& vec2, real scalar) { + real operator()(Matrix& mat1, + const Matrix& mat2, + IVector& vec1, + const IVector& vec2, + real scalar) { a_ = f(mat1, mat2, vec1, vec2, scalar); return a_; } @@ -93,9 +99,13 @@ TEST(ExecViaCpu, test1) { testWrapper(f); testWrapper(&f); - auto lambda = - [](Matrix& mat1, const Matrix& mat2, IVector& vec1, const IVector& vec2, - real scalar) -> real { return f(mat1, mat2, vec1, vec2, scalar); }; + auto lambda = [](Matrix& mat1, + const Matrix& mat2, + IVector& vec1, + const IVector& vec2, + real scalar) -> real { + return f(mat1, mat2, vec1, vec2, scalar); + }; LOG(INFO) << "lambda is_class=" << std::is_class::value << " is_function=" << std::is_function::value; testWrapper(lambda); diff --git a/paddle/math/tests/test_FPException.cpp b/paddle/math/tests/test_FPException.cpp index 174278c2aa..f996e0dadd 100644 --- a/paddle/math/tests/test_FPException.cpp +++ b/paddle/math/tests/test_FPException.cpp @@ -12,14 +12,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - /** * This test is about floating point calculation exception. * Paddle catches FE_INVALID, FE DIVBYZERO and FE_OVERFLOW exceptions. * - * Some exceptions occur in the middle of a set of formulas, + * Some exceptions occur in the middle of a set of formulas, * that can be circumvented by some tricks. - * For example, + * For example, * calculate tanh * b = 2.0 / (1.0 + exp(-2 * a)) - 1.0 * @@ -34,7 +33,7 @@ limitations under the License. */ #include "paddle/math/Matrix.h" #include "paddle/utils/Excepts.h" -using namespace paddle; // NOLINT +using namespace paddle; // NOLINT void SetTensorValue(Matrix& matrix, real value) { int height = matrix.getHeight(); @@ -53,7 +52,7 @@ void SetTensorValue(Matrix& matrix, real value) { } } -template +template void testTanh(real illegal) { MatrixPtr A = std::make_shared(10, 10); MatrixPtr B = std::make_shared(10, 10); @@ -65,7 +64,7 @@ void testTanh(real illegal) { A->tanh(*B); } -template +template void testSigmoid(real illegal) { MatrixPtr A = std::make_shared(10, 10); MatrixPtr B = std::make_shared(10, 10); diff --git a/paddle/math/tests/test_SIMDFunctions.cpp b/paddle/math/tests/test_SIMDFunctions.cpp index 491b0cda7b..8405b96fc2 100644 --- a/paddle/math/tests/test_SIMDFunctions.cpp +++ b/paddle/math/tests/test_SIMDFunctions.cpp @@ -12,8 +12,6 @@ WITHOUT 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/math/SIMDFunctions.h" #include "paddle/utils/Util.h" @@ -128,13 +126,13 @@ TEST(SIMDFunction, decayL1_WithLR) { typedef std::function DecayL1MethodType; - DecayL1MethodType naive = [](float* d, float* s, float* lr, float l, - size_t len) { + DecayL1MethodType naive = []( + float* d, float* s, float* lr, float l, size_t len) { paddle::simd::naive::decayL1(d, s, lr, l, len); }; - DecayL1MethodType simd = [](float* d, float* s, float* lr, float l, - size_t len) { + DecayL1MethodType simd = []( + float* d, float* s, float* lr, float l, size_t len) { paddle::simd::decayL1(d, s, lr, l, len); }; diff --git a/paddle/math/tests/test_batchTranspose.cpp b/paddle/math/tests/test_batchTranspose.cpp index 737504da38..a9596992b2 100644 --- a/paddle/math/tests/test_batchTranspose.cpp +++ b/paddle/math/tests/test_batchTranspose.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "test_matrixUtil.h" #include "hl_batch_transpose.h" @@ -48,8 +47,8 @@ TEST(MatrixBatchTransTest, test_batch_matrix_transpose) { cData[sample_id * nx * ny + j * nx + i]; // device gMat->copyFrom(*cMat, HPPL_STREAM_DEFAULT); - batchTranspose(gMat->getData(), gBatchTransMat->getData(), nx, ny, - numSamples); + batchTranspose( + gMat->getData(), gBatchTransMat->getData(), nx, ny, numSamples); cMat_d2h->copyFrom(*gBatchTransMat, HPPL_STREAM_DEFAULT); checkMatrixEqual(cBatchTransMat, cMat_d2h); } diff --git a/paddle/math/tests/test_matrix.cpp b/paddle/math/tests/test_matrix.cpp index 71c9622420..3788218aab 100644 --- a/paddle/math/tests/test_matrix.cpp +++ b/paddle/math/tests/test_matrix.cpp @@ -48,7 +48,8 @@ struct MatrixPara { }; #ifndef PADDLE_ONLY_CPU -void test_sparse_matrix_mul(MatrixPara paraA, MatrixPara paraB, +void test_sparse_matrix_mul(MatrixPara paraA, + MatrixPara paraB, MatrixPara paraC) { // for cpu sparse matrix mul MatrixPtr cpuMatrixA, cpuMatrixB, cpuMatrixC, gpuMatrixC_d2h; @@ -58,12 +59,20 @@ void test_sparse_matrix_mul(MatrixPara paraA, MatrixPara paraB, MatrixPtr cpuDenseA, cpuDenseB, cpuDenseC; if (paraA.sparse) { - cpuMatrixA = Matrix::createSparseMatrix(paraA.height, paraA.width, - paraA.nnz, FLOAT_VALUE, - paraA.format, paraA.trans, false); - gpuMatrixA = Matrix::createSparseMatrix(paraA.height, paraA.width, - paraA.nnz, FLOAT_VALUE, - paraA.format, paraA.trans, true); + cpuMatrixA = Matrix::createSparseMatrix(paraA.height, + paraA.width, + paraA.nnz, + FLOAT_VALUE, + paraA.format, + paraA.trans, + false); + gpuMatrixA = Matrix::createSparseMatrix(paraA.height, + paraA.width, + paraA.nnz, + FLOAT_VALUE, + paraA.format, + paraA.trans, + true); } else { cpuMatrixA = Matrix::create(paraA.height, paraA.width, paraA.trans, false); gpuMatrixA = Matrix::create(paraA.height, paraA.width, paraA.trans, true); @@ -71,12 +80,20 @@ void test_sparse_matrix_mul(MatrixPara paraA, MatrixPara paraB, cpuDenseA = Matrix::create(paraA.height, paraA.width, paraA.trans, false); if (paraB.sparse) { - cpuMatrixB = Matrix::createSparseMatrix(paraB.height, paraB.width, - paraB.nnz, FLOAT_VALUE, - paraB.format, paraB.trans, false); - gpuMatrixB = Matrix::createSparseMatrix(paraB.height, paraB.width, - paraB.nnz, FLOAT_VALUE, - paraB.format, paraB.trans, true); + cpuMatrixB = Matrix::createSparseMatrix(paraB.height, + paraB.width, + paraB.nnz, + FLOAT_VALUE, + paraB.format, + paraB.trans, + false); + gpuMatrixB = Matrix::createSparseMatrix(paraB.height, + paraB.width, + paraB.nnz, + FLOAT_VALUE, + paraB.format, + paraB.trans, + true); } else { cpuMatrixB = Matrix::create(paraB.height, paraB.width, paraB.trans, false); gpuMatrixB = Matrix::create(paraB.height, paraB.width, paraB.trans, true); @@ -84,15 +101,27 @@ void test_sparse_matrix_mul(MatrixPara paraA, MatrixPara paraB, cpuDenseB = Matrix::create(paraB.height, paraB.width, paraB.trans, false); if (paraC.sparse) { - cpuMatrixC = Matrix::createSparseMatrix(paraC.height, paraC.width, - paraC.nnz, FLOAT_VALUE, - paraC.format, paraC.trans, false); - gpuMatrixC = Matrix::createSparseMatrix(paraC.height, paraC.width, - paraC.nnz, FLOAT_VALUE, - paraC.format, paraC.trans, true); - gpuMatrixC_d2h = Matrix::createSparseMatrix( - paraC.height, paraC.width, paraC.nnz, FLOAT_VALUE, paraC.format, - paraC.trans, false); + cpuMatrixC = Matrix::createSparseMatrix(paraC.height, + paraC.width, + paraC.nnz, + FLOAT_VALUE, + paraC.format, + paraC.trans, + false); + gpuMatrixC = Matrix::createSparseMatrix(paraC.height, + paraC.width, + paraC.nnz, + FLOAT_VALUE, + paraC.format, + paraC.trans, + true); + gpuMatrixC_d2h = Matrix::createSparseMatrix(paraC.height, + paraC.width, + paraC.nnz, + FLOAT_VALUE, + paraC.format, + paraC.trans, + false); } else { cpuMatrixC = Matrix::create(paraC.height, paraC.width, paraC.trans, false); gpuMatrixC = Matrix::create(paraC.height, paraC.width, paraC.trans, true); @@ -267,8 +296,8 @@ TEST(Matrix, CpuSparseMatrixSubMatrix) { } } -void sparseValid(int* major, int* minor, size_t nnz, size_t majorLen, - size_t minorLen) { +void sparseValid( + int* major, int* minor, size_t nnz, size_t majorLen, size_t minorLen) { CHECK_EQ(nnz, size_t(major[majorLen - 1])); CHECK_EQ(nnz, minorLen); for (size_t i = 0; i < majorLen - 1; i++) { @@ -375,14 +404,25 @@ TEST(Matrix, SparseMatrixCSRFormatTrimFrom) { int64_t trimedIndices[11] = {0, 1, 3, 3, 7, 7, 9, 10, 12, 16, 19}; sparse_float_value_t trimedData[19]; int trimedValue[19] = { - 1, // row_0 : 1 - 3, 1, // row_1 : 2 - 0, 1, 2, 3, // row_3 : 4 - 2, 3, // row_5 : 2 - 3, // row_6 : 1 - 0, 1, // row_7 : 2 - 0, 1, 2, 3, // row_8 : 4 - 2, 3, 1 // row_9 : 3 + 1, // row_0 : 1 + 3, + 1, // row_1 : 2 + 0, + 1, + 2, + 3, // row_3 : 4 + 2, + 3, // row_5 : 2 + 3, // row_6 : 1 + 0, + 1, // row_7 : 2 + 0, + 1, + 2, + 3, // row_8 : 4 + 2, + 3, + 1 // row_9 : 3 }; for (size_t i = 0; i < 19; i++) { trimedData[i].col = trimedValue[i]; @@ -415,9 +455,13 @@ TEST(Matrix, SparseMatrixCSRFormatTrimFrom) { height, trimedWidth, height, FLOAT_VALUE, SPARSE_CSR, true); matC->trimFrom(*mat); - CpuSparseMatrixPtr matD = std::make_shared( - height, trimedWidth, matC->getElementCnt(), FLOAT_VALUE, SPARSE_CSR, - false); + CpuSparseMatrixPtr matD = + std::make_shared(height, + trimedWidth, + matC->getElementCnt(), + FLOAT_VALUE, + SPARSE_CSR, + false); matD->copyFrom(*matC, HPPL_STREAM_DEFAULT); hl_stream_synchronize(HPPL_STREAM_DEFAULT); checkSMatrixEqual2(matA, matD); @@ -462,11 +506,17 @@ TEST(Matrix, SparseMatrixCSCFormatTrimFrom) { int trimedIndices[6] = {0, 1, 5, 5, 9, 13}; int trimedValue[13] = { 1, // col_0 : 1 - 5, 3, 1, + 5, + 3, + 1, 6, // col_1 : 4 - 0, 1, 2, + 0, + 1, + 2, 3, // col_3 : 4 - 4, 5, 6, + 4, + 5, + 6, 7 // col_4 : 4 }; std::vector rowsA(trimedValue, trimedValue + 13); @@ -499,9 +549,13 @@ TEST(Matrix, SparseMatrixCSCFormatTrimFrom) { height, trimedWidth, height, FLOAT_VALUE, SPARSE_CSC, true); matC->trimFrom(*mat); - CpuSparseMatrixPtr matD = std::make_shared( - height, trimedWidth, matC->getElementCnt(), FLOAT_VALUE, SPARSE_CSC, - false); + CpuSparseMatrixPtr matD = + std::make_shared(height, + trimedWidth, + matC->getElementCnt(), + FLOAT_VALUE, + SPARSE_CSC, + false); matD->copyFrom(*matC, HPPL_STREAM_DEFAULT); hl_stream_synchronize(HPPL_STREAM_DEFAULT); checkSMatrixEqual2(matA, matD); diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 9c03695ba5..ae5bc5a86a 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -23,11 +23,10 @@ limitations under the License. */ #include "paddle/gserver/tests/TestUtil.h" #include "paddle/utils/Stat.h" - using namespace paddle; // NOLINT using namespace std; // NOLINT -template +template void VectorCheckEqual(const VectorT& vector1, const VectorT& vector2) { CHECK(vector1.getSize() == vector2.getSize()); @@ -90,7 +89,9 @@ void MatrixCheckErr(const Matrix& matrix1, const Matrix& matrix2) { EXPECT_EQ(count, 0) << "There are " << count << " different element."; } -void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, +void testBilinearFwdBwd(int numSamples, + int imgSizeH, + int imgSizeW, int channels) { int inWidth = imgSizeH * imgSizeW * channels; int outWidth = 2 * imgSizeH * 2 * imgSizeW * channels; @@ -107,10 +108,22 @@ void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, input->randomizeUniform(); inputGpu->copyFrom(*input); - target->bilinearForward(*input, imgSizeH, imgSizeW, - 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); - targetGpu->bilinearForward(*inputGpu, imgSizeH, imgSizeW, - 2 * imgSizeH, 2 * imgSizeW, channels, ratioH, ratioW); + target->bilinearForward(*input, + imgSizeH, + imgSizeW, + 2 * imgSizeH, + 2 * imgSizeW, + channels, + ratioH, + ratioW); + targetGpu->bilinearForward(*inputGpu, + imgSizeH, + imgSizeW, + 2 * imgSizeH, + 2 * imgSizeW, + channels, + ratioH, + ratioW); // check targetCheck->copyFrom(*targetGpu); @@ -121,8 +134,8 @@ void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, MatrixPtr inputGpuGrad = GpuMatrix::create(numSamples, inWidth, false, true); MatrixPtr targetGrad = CpuMatrix::create(numSamples, outWidth, false, false); - MatrixPtr targetGpuGrad = GpuMatrix::create(numSamples, outWidth, false, - true); + MatrixPtr targetGpuGrad = + GpuMatrix::create(numSamples, outWidth, false, true); MatrixPtr targetCheckGrad = CpuMatrix::create(numSamples, inWidth, false, false); @@ -131,10 +144,22 @@ void testBilinearFwdBwd(int numSamples, int imgSizeH, int imgSizeW, inputGpuGrad->copyFrom(*inputGrad); targetGpuGrad->copyFrom(*targetGrad); - inputGrad->bilinearBackward(*targetGrad, 2 * imgSizeH, 2 * imgSizeW, - imgSizeH, imgSizeW, channels, ratioH, ratioW); - inputGpuGrad->bilinearBackward(*targetGpuGrad, 2 * imgSizeH, 2 * imgSizeW, - imgSizeH, imgSizeW, channels, ratioH, ratioW); + inputGrad->bilinearBackward(*targetGrad, + 2 * imgSizeH, + 2 * imgSizeW, + imgSizeH, + imgSizeW, + channels, + ratioH, + ratioW); + inputGpuGrad->bilinearBackward(*targetGpuGrad, + 2 * imgSizeH, + 2 * imgSizeW, + imgSizeH, + imgSizeW, + channels, + ratioH, + ratioW); // check targetCheckGrad->copyFrom(*inputGpuGrad); @@ -146,10 +171,8 @@ TEST(Matrix, BilinearFwdBwd) { for (auto channels : {8, 16}) { for (auto imgSizeH : {14, 28}) { for (auto imgSizeW : {16, 30}) { - VLOG(3) << " numSamples=" << numSamples - << " channels=" << channels - << " imgSizeH=" << imgSizeH - << " imgSizeW=" << imgSizeW; + VLOG(3) << " numSamples=" << numSamples << " channels=" << channels + << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; testBilinearFwdBwd(numSamples, imgSizeH, imgSizeW, channels); } } @@ -157,8 +180,11 @@ TEST(Matrix, BilinearFwdBwd) { } } -void testMatrixProjectionForward(int contextStart, int contextLength, - bool padding, int batchSize, int inputDim) { +void testMatrixProjectionForward(int contextStart, + int contextLength, + bool padding, + int batchSize, + int inputDim) { MatrixPtr cpuInput = std::make_shared(batchSize, inputDim); MatrixPtr gpuInput = std::make_shared(batchSize, inputDim); cpuInput->randomizeUniform(); @@ -190,12 +216,20 @@ void testMatrixProjectionForward(int contextStart, int contextLength, // calculate int beginPad = std::max(0, -contextStart); - cpuOutput->contextProjectionForward(cpuInput, cpuWeight, *cpuSequence, - contextLength, contextStart, beginPad, + cpuOutput->contextProjectionForward(cpuInput, + cpuWeight, + *cpuSequence, + contextLength, + contextStart, + beginPad, padding); - gpuOutput->contextProjectionForward(gpuInput, gpuWeight, *gpuSequence, - contextLength, contextStart, beginPad, + gpuOutput->contextProjectionForward(gpuInput, + gpuWeight, + *gpuSequence, + contextLength, + contextStart, + beginPad, padding); // check @@ -206,8 +240,11 @@ void testMatrixProjectionForward(int contextStart, int contextLength, MatrixCheckEqual(*cpuOutput, *outputCheck); } -void testMatrixProjectionBackward(int contextStart, int contextLength, - bool padding, int batchSize, int inputDim) { +void testMatrixProjectionBackward(int contextStart, + int contextLength, + bool padding, + int batchSize, + int inputDim) { MatrixPtr cpuOutputGrad = std::make_shared(batchSize, inputDim * contextLength); MatrixPtr gpuOutputGrad = @@ -239,15 +276,22 @@ void testMatrixProjectionBackward(int contextStart, int contextLength, // calculate int beginPad = std::max(0, -contextStart); - cpuOutputGrad->contextProjectionBackward(cpuInputGrad, cpuWeightGrad, - *cpuSequence, contextLength, - contextStart, beginPad, padding); - gpuOutputGrad->contextProjectionBackwardData(gpuInputGrad, *gpuSequence, - contextLength, contextStart); + cpuOutputGrad->contextProjectionBackward(cpuInputGrad, + cpuWeightGrad, + *cpuSequence, + contextLength, + contextStart, + beginPad, + padding); + gpuOutputGrad->contextProjectionBackwardData( + gpuInputGrad, *gpuSequence, contextLength, contextStart); if (padding) { - gpuOutputGrad->contextProjectionBackwardWeight( - gpuWeightGrad, *gpuSequence, contextLength, - contextStart, pad, beginPad); + gpuOutputGrad->contextProjectionBackwardWeight(gpuWeightGrad, + *gpuSequence, + contextLength, + contextStart, + pad, + beginPad); } // check @@ -269,13 +313,19 @@ TEST(Matrix, projection) { for (auto batchSize : {1, 2, 5, 20, 100}) { for (auto inputDim : {15, 32, 63, 128, 200}) { VLOG(3) << " contextStart=" << contextStart - << " contextLength=" << contextLength - << " trainablePadding=" << trainablePadding - << " batchSize=" << batchSize << " inputDim=" << inputDim; - testMatrixProjectionForward(contextStart, contextLength, - trainablePadding, batchSize, inputDim); - testMatrixProjectionBackward(contextStart, contextLength, - trainablePadding, batchSize, inputDim); + << " contextLength=" << contextLength + << " trainablePadding=" << trainablePadding + << " batchSize=" << batchSize << " inputDim=" << inputDim; + testMatrixProjectionForward(contextStart, + contextLength, + trainablePadding, + batchSize, + inputDim); + testMatrixProjectionBackward(contextStart, + contextLength, + trainablePadding, + batchSize, + inputDim); } } } @@ -813,7 +863,6 @@ void testSequenceSoftmax(int batchSize) { MatrixCheckErr(*cpuInput, *outputCheck); } - void testMatrixSoftmaxThreshold(int height, int width) { MatrixPtr cpuInput = std::make_shared(height, width); MatrixPtr cpuOutput = std::make_shared(height, width); @@ -1216,7 +1265,7 @@ TEST(Matrix, AtOffset) { for (auto width1 : {1, 32, 100, 512, 1000}) { for (auto width2 : {1, 32, 100, 512, 1000}) { VLOG(3) << " height=" << height << " width1=" << width1 - << " width2=" << width2; + << " width2=" << width2; testMatrixAddAtOffset(height, width1, width2); testMatrixAssignAtOffset(height, width1, width2); @@ -1284,7 +1333,7 @@ TEST(Matrix, tableProjection) { for (auto tableSize : {10, 100}) { for (auto inputDim : {20, 50}) { VLOG(3) << " numSamples=" << numSamples << " tableSize=" << tableSize - << " inputDim=" << inputDim; + << " inputDim=" << inputDim; testMatrixSelectRows(numSamples, tableSize, inputDim); testMatrixAddToRows(numSamples, tableSize, inputDim); } @@ -1359,8 +1408,12 @@ void testSubMatrixMul(bool transa, bool transb, int dimM, int dimN, int dimK) { } }; - auto subMatrix = [](MatrixPtr& sub, MatrixPtr matrix, size_t startRow, - size_t endRow, size_t startCol, size_t endCol) { + auto subMatrix = [](MatrixPtr& sub, + MatrixPtr matrix, + size_t startRow, + size_t endRow, + size_t startCol, + size_t endCol) { if (!matrix->isTransposed()) { sub = matrix->subMatrix(startRow, endRow, startCol, endCol); } else { @@ -1404,9 +1457,9 @@ TEST(Matrix, mul) { continue; } VLOG(3) << setiosflags(ios::left) << setfill(' ') - << " transa=" << transa << " transb=" << transb - << " dimM=" << setw(5) << dimM << " dimN=" << setw(5) - << dimN << " dimK=" << setw(5) << dimK; + << " transa=" << transa << " transb=" << transb + << " dimM=" << setw(5) << dimM << " dimN=" << setw(5) + << dimN << " dimK=" << setw(5) << dimK; testMatrixMul(transa, transb, dimM, dimN, dimK); testSubMatrixMul(transa, transb, dimM, dimN, dimK); @@ -1436,7 +1489,7 @@ TEST(Vector, rowFunc) { } } -template +template void testVectorReset(int size) { std::shared_ptr> cpu = std::make_shared>(size); std::shared_ptr> gpu = std::make_shared>(size); @@ -1450,14 +1503,14 @@ void testVectorReset(int size) { VectorCheckEqual(*cpu, *out); } -template +template void testVecortSelectFrom(int size) { std::shared_ptr> cpuDst = std::make_shared>(size); std::shared_ptr> gpuDst = std::make_shared>(size); - std::shared_ptr> - cpuSrc = std::make_shared>(size*2); - std::shared_ptr> - gpuSrc = std::make_shared>(size*2); + std::shared_ptr> cpuSrc = + std::make_shared>(size * 2); + std::shared_ptr> gpuSrc = + std::make_shared>(size * 2); CpuIVectorPtr cpuIds = std::make_shared>(size); GpuIVectorPtr gpuIds = std::make_shared>(size); @@ -1478,7 +1531,7 @@ void testVecortSelectFrom(int size) { VectorCheckEqual(*cpuDst, *out); } -template +template void testVecotrZeroMem(int size) { std::shared_ptr> cpu = std::make_shared>(size); std::shared_ptr> gpu = std::make_shared>(size); @@ -1491,7 +1544,7 @@ void testVecotrZeroMem(int size) { VectorCheckEqual(*cpu, *out); } -template +template void testVectorIsEqual(int size) { std::shared_ptr> cpuA = std::make_shared>(size); std::shared_ptr> cpuB = std::make_shared>(size); @@ -1549,12 +1602,11 @@ void testMatrixTopK(int samples, int dim, int beamSize) { TEST(Matrix, topK) { for (auto samples : {1, 5, 31, 90, 150, 500}) { - for (auto dim : {1, 5 , 8, 10, 15, 64, 80, 120, 256, 300, - 1280, 5120, 50000}) { + for (auto dim : + {1, 5, 8, 10, 15, 64, 80, 120, 256, 300, 1280, 5120, 50000}) { for (auto beamSize : {1, 5, 10, 20, 40, (int)rand() % dim + 1}) { if (beamSize > dim) continue; - VLOG(3) << " samples=" << samples - << " beamSize=" << beamSize + VLOG(3) << " samples=" << samples << " beamSize=" << beamSize << " dim=" << dim; testMatrixTopK(samples, dim, beamSize); } @@ -1604,10 +1656,8 @@ TEST(SMatrix, topK) { for (auto beamSize : {1, 5, 40, 100, 500}) { for (auto ratio : {0.01, 0.001}) { if (beamSize > dim) continue; - VLOG(3) << " samples=" << samples - << " beamSize=" << beamSize - << " dim=" << dim - << " ratio=" << ratio; + VLOG(3) << " samples=" << samples << " beamSize=" << beamSize + << " dim=" << dim << " ratio=" << ratio; testSMatrixTopK(samples, dim, beamSize, ratio); } } @@ -1728,8 +1778,7 @@ TEST(Matrix, cosSim) { } } -void testCosSimDerivate(int heightX, int heightY, int width, - real scale) { +void testCosSimDerivate(int heightX, int heightY, int width, real scale) { MatrixPtr prevOutX = CpuMatrix::create(heightX, width, false, false); MatrixPtr prevOutY = CpuMatrix::create(heightY, width, false, false); MatrixPtr grad = CpuMatrix::create(heightX, 1, false, false); @@ -1758,12 +1807,8 @@ void testCosSimDerivate(int heightX, int heightY, int width, prevGradXGpu->copyFrom(*prevGradX); prevGradYGpu->copyFrom(*prevGradY); - grad->cosSimDerivative(*output, - *prevOutX, - *prevOutY, - *prevGradX, - *prevGradY, - scale); + grad->cosSimDerivative( + *output, *prevOutX, *prevOutY, *prevGradX, *prevGradY, scale); gradGpu->cosSimDerivative(*outputGpu, *prevOutXGpu, @@ -1772,10 +1817,8 @@ void testCosSimDerivate(int heightX, int heightY, int width, *prevGradYGpu, scale); - MatrixPtr prevGradXCheck = CpuMatrix::create(heightX, width, false, - false); - MatrixPtr prevGradYCheck = CpuMatrix::create(heightY, width, false, - false); + MatrixPtr prevGradXCheck = CpuMatrix::create(heightX, width, false, false); + MatrixPtr prevGradYCheck = CpuMatrix::create(heightY, width, false, false); prevGradXCheck->copyFrom(*prevGradXGpu); prevGradYCheck->copyFrom(*prevGradYGpu); MatrixCheckErr(*prevGradX, *prevGradXCheck); @@ -1794,8 +1837,7 @@ TEST(Matrix, cosSimDerivate) { } } -void testParamReluForward(int height, int width, int w_height, - int w_width) { +void testParamReluForward(int height, int width, int w_height, int w_width) { MatrixPtr output = CpuMatrix::create(height, width, false, false); MatrixPtr input = CpuMatrix::create(height, width, false, false); MatrixPtr w = CpuMatrix::create(w_height, w_width, false, false); @@ -1832,8 +1874,7 @@ TEST(Matrix, paramReluForward) { } } -void testParamReluBackwardW(int height, int width, int w_height, - int w_width) { +void testParamReluBackwardW(int height, int width, int w_height, int w_width) { MatrixPtr oGrad = CpuMatrix::create(height, width, false, false); MatrixPtr input = CpuMatrix::create(height, width, false, false); MatrixPtr w = CpuMatrix::create(w_height, w_width, false, false); @@ -1870,8 +1911,10 @@ TEST(Matrix, paramReluBackwardW) { } } -void testParamReluBackwardDiff(int height, int width, int w_height, - int w_width) { +void testParamReluBackwardDiff(int height, + int width, + int w_height, + int w_width) { MatrixPtr oGrad = CpuMatrix::create(height, width, false, false); MatrixPtr input = CpuMatrix::create(height, width, false, false); MatrixPtr diff = CpuMatrix::create(height, width, false, false); @@ -1943,11 +1986,16 @@ TEST(Matrix, classificationError) { } } -void testMaxPoolFwdBwd(int numSamples, int channels, - int imgSizeH, int imgSizeW, - int ksizeH, int ksizeW, - int strideH, int strideW, - int padH, int padW) { +void testMaxPoolFwdBwd(int numSamples, + int channels, + int imgSizeH, + int imgSizeW, + int ksizeH, + int ksizeW, + int strideH, + int strideW, + int padH, + int padW) { int outH = 0, outW = 0; outH = (imgSizeH - ksizeH + 2 * padH + strideH - 1) / strideH + 1; outW = (imgSizeW - ksizeW + 2 * padW + strideW - 1) / strideW + 1; @@ -1965,12 +2013,30 @@ void testMaxPoolFwdBwd(int numSamples, int channels, inputGpu->copyFrom(*input); targetGpu->copyFrom(*target); - target->maxPoolForward(*input, imgSizeH, imgSizeW, - channels, ksizeW, ksizeH, - strideH, strideW, outH, outW, padH, padW); - targetGpu->maxPoolForward(*inputGpu, imgSizeH, imgSizeW, - channels, ksizeW, ksizeH, - strideH, strideW, outH, outW, padH, padW); + target->maxPoolForward(*input, + imgSizeH, + imgSizeW, + channels, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + padH, + padW); + targetGpu->maxPoolForward(*inputGpu, + imgSizeH, + imgSizeW, + channels, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + padH, + padW); MatrixPtr targetCheck = CpuMatrix::create(numSamples, outWidth, false, false); targetCheck->copyFrom(*targetGpu); checkMatrixEqual(target, targetCheck); @@ -1978,35 +2044,60 @@ void testMaxPoolFwdBwd(int numSamples, int channels, MatrixPtr inputGrad = CpuMatrix::create(numSamples, inWidth, false, false); MatrixPtr inputGpuGrad = GpuMatrix::create(numSamples, inWidth, false, true); MatrixPtr targetGrad = CpuMatrix::create(numSamples, outWidth, false, false); - MatrixPtr targetGpuGrad = GpuMatrix::create(numSamples, outWidth, - false, true); + MatrixPtr targetGpuGrad = + GpuMatrix::create(numSamples, outWidth, false, true); inputGrad->randomizeUniform(); targetGrad->randomizeUniform(); inputGpuGrad->copyFrom(*inputGrad); targetGpuGrad->copyFrom(*targetGrad); - inputGrad->maxPoolBackward(*input, imgSizeH, imgSizeW, - *targetGrad, *target, - ksizeW, ksizeH, - strideH, strideW, - outH, outW, 1.0, 1.0, padH, padW); - inputGpuGrad->maxPoolBackward(*inputGpu, imgSizeH, imgSizeW, - *targetGpuGrad, *targetGpu, - ksizeW, ksizeH, - strideH, strideW, - outH, outW, 1.0, 1.0, padH, padW); - MatrixPtr targetBwdCheck = CpuMatrix::create(numSamples, inWidth, - false, false); + inputGrad->maxPoolBackward(*input, + imgSizeH, + imgSizeW, + *targetGrad, + *target, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + 1.0, + 1.0, + padH, + padW); + inputGpuGrad->maxPoolBackward(*inputGpu, + imgSizeH, + imgSizeW, + *targetGpuGrad, + *targetGpu, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + 1.0, + 1.0, + padH, + padW); + MatrixPtr targetBwdCheck = + CpuMatrix::create(numSamples, inWidth, false, false); targetBwdCheck->copyFrom(*inputGpuGrad); checkMatrixEqual(inputGrad, targetBwdCheck); } -void testAvgPoolFwdBwd(int numSamples, int channels, - int imgSizeH, int imgSizeW, - int ksizeH, int ksizeW, - int strideH, int strideW, - int padH, int padW) { +void testAvgPoolFwdBwd(int numSamples, + int channels, + int imgSizeH, + int imgSizeW, + int ksizeH, + int ksizeW, + int strideH, + int strideW, + int padH, + int padW) { int outH = 0, outW = 0; outH = (imgSizeH - ksizeH + 2 * padH + strideH - 1) / strideH + 1; outW = (imgSizeW - ksizeW + 2 * padW + strideW - 1) / strideW + 1; @@ -2024,12 +2115,30 @@ void testAvgPoolFwdBwd(int numSamples, int channels, inputGpu->copyFrom(*input); targetGpu->copyFrom(*target); - target->avgPoolForward(*input, imgSizeH, imgSizeW, - channels, ksizeW, ksizeH, - strideH, strideW, outH, outW, padH, padW); - targetGpu->avgPoolForward(*inputGpu, imgSizeH, imgSizeW, - channels, ksizeW, ksizeH, - strideH, strideW, outH, outW, padH, padW); + target->avgPoolForward(*input, + imgSizeH, + imgSizeW, + channels, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + padH, + padW); + targetGpu->avgPoolForward(*inputGpu, + imgSizeH, + imgSizeW, + channels, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + padH, + padW); MatrixPtr targetCheck = CpuMatrix::create(numSamples, outWidth, false, false); targetCheck->copyFrom(*targetGpu); MatrixCheckErr(*target, *targetCheck); @@ -2037,24 +2146,42 @@ void testAvgPoolFwdBwd(int numSamples, int channels, MatrixPtr inputGrad = CpuMatrix::create(numSamples, inWidth, false, false); MatrixPtr inputGpuGrad = GpuMatrix::create(numSamples, inWidth, false, true); MatrixPtr targetGrad = CpuMatrix::create(numSamples, outWidth, false, false); - MatrixPtr targetGpuGrad = GpuMatrix::create(numSamples, outWidth, - false, true); + MatrixPtr targetGpuGrad = + GpuMatrix::create(numSamples, outWidth, false, true); inputGrad->randomizeUniform(); targetGrad->randomizeUniform(); inputGpuGrad->copyFrom(*inputGrad); targetGpuGrad->copyFrom(*targetGrad); - inputGrad->avgPoolBackward(*targetGrad, imgSizeH, imgSizeW, - ksizeW, ksizeH, - strideH, strideW, - outH, outW, 1.0, 1.0, padH, padW); - inputGpuGrad->avgPoolBackward(*targetGpuGrad, imgSizeH, imgSizeW, - ksizeW, ksizeH, - strideH, strideW, - outH, outW, 1.0, 1.0, padH, padW); - MatrixPtr targetBwdCheck = CpuMatrix::create(numSamples, inWidth, - false, false); + inputGrad->avgPoolBackward(*targetGrad, + imgSizeH, + imgSizeW, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + 1.0, + 1.0, + padH, + padW); + inputGpuGrad->avgPoolBackward(*targetGpuGrad, + imgSizeH, + imgSizeW, + ksizeW, + ksizeH, + strideH, + strideW, + outH, + outW, + 1.0, + 1.0, + padH, + padW); + MatrixPtr targetBwdCheck = + CpuMatrix::create(numSamples, inWidth, false, false); targetBwdCheck->copyFrom(*inputGpuGrad); MatrixCheckErr(*inputGrad, *targetBwdCheck); } @@ -2068,24 +2195,37 @@ TEST(Matrix, PoolFwdBwd) { for (auto sizeY : {2, 5}) { for (auto sH : {1, 2}) { for (auto sW : {1, 2}) { - for (auto pH : {0, (sizeY - 1)/2}) { - for (auto pW : {0, (sizeX - 1)/2}) { - VLOG(3) << " numSamples=" << numSamples - << " channels=" << channels - << " imgSizeH=" << imgSizeH - << " imgSizeW=" << imgSizeW - << " sizeX=" << sizeX - << " sizeY=" << sizeY - << " strideH=" << sH - << " strideW=" << sW - << " padingH=" << pH - << " padingW=" << pW; - testMaxPoolFwdBwd(numSamples, channels, imgSizeH, - imgSizeW, sizeX, sizeY, sH, sW, pH, pW); - testAvgPoolFwdBwd(numSamples, channels, imgSizeH, - imgSizeW, sizeX, sizeY, sH, sW, pH, pW); - } - } + for (auto pH : {0, (sizeY - 1) / 2}) { + for (auto pW : {0, (sizeX - 1) / 2}) { + VLOG(3) << " numSamples=" << numSamples + << " channels=" << channels + << " imgSizeH=" << imgSizeH + << " imgSizeW=" << imgSizeW << " sizeX=" << sizeX + << " sizeY=" << sizeY << " strideH=" << sH + << " strideW=" << sW << " padingH=" << pH + << " padingW=" << pW; + testMaxPoolFwdBwd(numSamples, + channels, + imgSizeH, + imgSizeW, + sizeX, + sizeY, + sH, + sW, + pH, + pW); + testAvgPoolFwdBwd(numSamples, + channels, + imgSizeH, + imgSizeW, + sizeX, + sizeY, + sH, + sW, + pH, + pW); + } + } } } } @@ -2096,8 +2236,8 @@ TEST(Matrix, PoolFwdBwd) { } } -void testMaxOutFwdBwd(int numSamples, int imgSizeH, int imgSizeW, - int channels, int groups) { +void testMaxOutFwdBwd( + int numSamples, int imgSizeH, int imgSizeW, int channels, int groups) { int inWidth = imgSizeH * imgSizeW * channels; int outChannels = channels / groups; int outWidth = imgSizeH * imgSizeW * outChannels; @@ -2131,10 +2271,10 @@ void testMaxOutFwdBwd(int numSamples, int imgSizeH, int imgSizeW, MatrixPtr inputGpuGrad = GpuMatrix::create(numSamples, inWidth, false, true); MatrixPtr targetGrad = CpuMatrix::create(numSamples, outWidth, false, false); - MatrixPtr targetGpuGrad = GpuMatrix::create(numSamples, outWidth, false, - true); - MatrixPtr targetCheckGrad = CpuMatrix::create(numSamples, inWidth, false, - false); + MatrixPtr targetGpuGrad = + GpuMatrix::create(numSamples, outWidth, false, true); + MatrixPtr targetCheckGrad = + CpuMatrix::create(numSamples, inWidth, false, false); inputGrad->randomizeUniform(); targetGrad->randomizeUniform(); @@ -2155,10 +2295,8 @@ TEST(Matrix, MaxOutFwdBwd) { for (auto imgSizeH : {14, 28}) { for (auto imgSizeW : {16, 30}) { for (auto groups : {2, 4}) { - VLOG(3) << " numSamples=" << numSamples - << " channels=" << channels - << " imgSizeH=" << imgSizeH - << " imgSizeW=" << imgSizeW + VLOG(3) << " numSamples=" << numSamples << " channels=" << channels + << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW << " groups=" << groups; testMaxOutFwdBwd(numSamples, imgSizeH, imgSizeW, channels, groups); } @@ -2232,12 +2370,12 @@ void testMultiBinaryLabelCrossEntropy(int numSamples, int dim) { MatrixPtr cpuGrad = std::make_shared(numSamples, dim); MatrixPtr gpuGrad = std::make_shared(numSamples, dim); - MatrixPtr cpuLabel = std::make_shared - (numSamples, dim, numSamples, NO_VALUE, SPARSE_CSR, false); - MatrixPtr gpuLabel = std::make_shared - (numSamples, dim, numSamples, NO_VALUE, SPARSE_CSR, false); - for (int i = 0; i < numSamples; i ++) { - const unsigned int id = rand() % dim; // NOLINT + MatrixPtr cpuLabel = std::make_shared( + numSamples, dim, numSamples, NO_VALUE, SPARSE_CSR, false); + MatrixPtr gpuLabel = std::make_shared( + numSamples, dim, numSamples, NO_VALUE, SPARSE_CSR, false); + for (int i = 0; i < numSamples; i++) { + const unsigned int id = rand() % dim; // NOLINT cpuLabel->setRow(i, 1, &id, nullptr); gpuLabel->setRow(i, 1, &id, nullptr); } diff --git a/paddle/math/tests/test_matrixUtil.h b/paddle/math/tests/test_matrixUtil.h index fa682164aa..5300e7168b 100644 --- a/paddle/math/tests/test_matrixUtil.h +++ b/paddle/math/tests/test_matrixUtil.h @@ -104,8 +104,7 @@ void checkSMatrixEqual2Dense(const CpuSparseMatrixPtr& a, } } -void checkSMatrixErr(const CpuSparseMatrixPtr& a, - const CpuSparseMatrixPtr& b) { +void checkSMatrixErr(const CpuSparseMatrixPtr& a, const CpuSparseMatrixPtr& b) { #ifndef PADDLE_TYPE_DOUBLE real err = 1e-3; #else @@ -126,7 +125,8 @@ void checkSMatrixErr(const CpuSparseMatrixPtr& a, real bVal = b->getValue()[r]; if (std::abs(aVal - bVal) > err) { if ((std::abs(aVal - bVal) / std::abs(aVal)) > (err / 10.0f)) { - LOG(INFO) << "a=" << aVal << "\t" << "b=" << bVal; + LOG(INFO) << "a=" << aVal << "\t" + << "b=" << bVal; count++; } } diff --git a/paddle/math/tests/test_perturbation.cpp b/paddle/math/tests/test_perturbation.cpp index 4fa9bc7201..837c2f47ba 100644 --- a/paddle/math/tests/test_perturbation.cpp +++ b/paddle/math/tests/test_perturbation.cpp @@ -37,7 +37,9 @@ protected: virtual void TearDown() {} - void allocateMem(real*& gpuAngle, real*& gpuScale, int*& gpuCenterR, + void allocateMem(real*& gpuAngle, + real*& gpuScale, + int*& gpuCenterR, int*& gpuCenterC) { gpuAngle = (real*)hl_malloc_device(sizeof(real) * NUM_IMAGES); gpuScale = (real*)hl_malloc_device(sizeof(real) * NUM_IMAGES); @@ -48,7 +50,8 @@ protected: } // Generate translation parameters for testing. - void generateTranslationParams(int*& gpuCenterR, int*& gpuCenterC, + void generateTranslationParams(int*& gpuCenterR, + int*& gpuCenterC, int imgSize) { int cpuCenterR[NUM_IMAGES * SAMPLING_RATE]; int cpuCenterC[NUM_IMAGES * SAMPLING_RATE]; @@ -59,13 +62,13 @@ protected: gpuCenterR = (int*)hl_malloc_device(sizeof(int) * NUM_IMAGES * SAMPLING_RATE); - hl_memcpy_host2device(gpuCenterR, cpuCenterR, - sizeof(int) * NUM_IMAGES * SAMPLING_RATE); + hl_memcpy_host2device( + gpuCenterR, cpuCenterR, sizeof(int) * NUM_IMAGES * SAMPLING_RATE); gpuCenterC = (int*)hl_malloc_device(sizeof(int) * NUM_IMAGES * SAMPLING_RATE); - hl_memcpy_host2device(gpuCenterC, cpuCenterC, - sizeof(int) * NUM_IMAGES * SAMPLING_RATE); + hl_memcpy_host2device( + gpuCenterC, cpuCenterC, sizeof(int) * NUM_IMAGES * SAMPLING_RATE); } // Generate rotation parameters for testing. @@ -84,8 +87,7 @@ protected: cpuScale[i] = static_cast(TGT_SIZE - 2) / TGT_SIZE; } gpuScale = (real*)hl_malloc_device(sizeof(real) * NUM_IMAGES); - hl_memcpy_host2device(gpuScale, cpuScale, - sizeof(real) * NUM_IMAGES); + hl_memcpy_host2device(gpuScale, cpuScale, sizeof(real) * NUM_IMAGES); } // Generate the test images, only the center regions are set to 1. @@ -111,8 +113,7 @@ protected: } } gpuImages = (real*)hl_malloc_device(sizeof(real) * IMAGE_MEM_SIZE); - hl_memcpy_host2device(gpuImages, cpuImages, - sizeof(real) * IMAGE_MEM_SIZE); + hl_memcpy_host2device(gpuImages, cpuImages, sizeof(real) * IMAGE_MEM_SIZE); } real* gpuImages_; @@ -120,64 +121,99 @@ protected: // Random perturbation. Only to make sure the code does not break. TEST_F(PerturbationTest, random_perturb) { - real* gpuAngle, *gpuScaleRatio; - int* gpuCenterR, *gpuCenterC; + real *gpuAngle, *gpuScaleRatio; + int *gpuCenterR, *gpuCenterC; allocateMem(gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC); real* targets = NULL; const int TARGET_MEM_SIZE = NUM_IMAGES * SAMPLING_RATE * TGT_SIZE * TGT_SIZE * CHANNELS; targets = (real*)hl_malloc_device(sizeof(real) * TARGET_MEM_SIZE); - hl_conv_random_disturb(gpuImages_, IMG_SIZE, TGT_SIZE, CHANNELS, - NUM_IMAGES, 1.0, 1.0, SAMPLING_RATE, gpuAngle, - gpuScaleRatio, gpuCenterR, gpuCenterC, 2, true, + hl_conv_random_disturb(gpuImages_, + IMG_SIZE, + TGT_SIZE, + CHANNELS, + NUM_IMAGES, + 1.0, + 1.0, + SAMPLING_RATE, + gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + 2, + true, targets); real cpuTargets[TARGET_MEM_SIZE]; - hl_memcpy_device2host(cpuTargets, targets, - sizeof(real) * TARGET_MEM_SIZE); + hl_memcpy_device2host(cpuTargets, targets, sizeof(real) * TARGET_MEM_SIZE); } TEST_F(PerturbationTest, identity_perturb) { - real* gpuAngle, *gpuScaleRatio; - int* gpuCenterR, *gpuCenterC; + real *gpuAngle, *gpuScaleRatio; + int *gpuCenterR, *gpuCenterC; allocateMem(gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC); real* targets = NULL; const int TARGET_MEM_SIZE = NUM_IMAGES * SAMPLING_RATE * TGT_SIZE * TGT_SIZE * CHANNELS; targets = (real*)hl_malloc_device(sizeof(real) * TARGET_MEM_SIZE); - hl_conv_random_disturb(gpuImages_, IMG_SIZE, TGT_SIZE, CHANNELS, - NUM_IMAGES, 1.0, 1.0, SAMPLING_RATE, gpuAngle, - gpuScaleRatio, gpuCenterR, gpuCenterC, 2, false, + hl_conv_random_disturb(gpuImages_, + IMG_SIZE, + TGT_SIZE, + CHANNELS, + NUM_IMAGES, + 1.0, + 1.0, + SAMPLING_RATE, + gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + 2, + false, targets); real cpuTargets[TARGET_MEM_SIZE]; - hl_memcpy_device2host(cpuTargets, targets, - sizeof(real) * TARGET_MEM_SIZE); + hl_memcpy_device2host(cpuTargets, targets, sizeof(real) * TARGET_MEM_SIZE); for (int i = 0; i < TARGET_MEM_SIZE; ++i) { EXPECT_FLOAT_EQ(1.0, cpuTargets[i]); } } TEST_F(PerturbationTest, translation_test) { - real* gpuAngle, *gpuScaleRatio; - int* gpuCenterR, *gpuCenterC; + real *gpuAngle, *gpuScaleRatio; + int *gpuCenterR, *gpuCenterC; allocateMem(gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC); - hl_generate_disturb_params(gpuAngle, gpuScaleRatio, gpuCenterR, - gpuCenterC, NUM_IMAGES, IMG_SIZE, 0.0, - 0.0, SAMPLING_RATE, false); + hl_generate_disturb_params(gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + NUM_IMAGES, + IMG_SIZE, + 0.0, + 0.0, + SAMPLING_RATE, + false); generateTranslationParams(gpuCenterR, gpuCenterC, IMG_SIZE); real* targets = NULL; const int TARGET_MEM_SIZE = NUM_IMAGES * SAMPLING_RATE * TGT_SIZE * TGT_SIZE * CHANNELS; targets = (real*)hl_malloc_device(sizeof(real) * TARGET_MEM_SIZE); - hl_conv_random_disturb_with_params( - gpuImages_, IMG_SIZE, TGT_SIZE, CHANNELS, NUM_IMAGES, SAMPLING_RATE, - gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC, 2, targets); + hl_conv_random_disturb_with_params(gpuImages_, + IMG_SIZE, + TGT_SIZE, + CHANNELS, + NUM_IMAGES, + SAMPLING_RATE, + gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + 2, + targets); real cpuTargets[TARGET_MEM_SIZE]; - hl_memcpy_device2host(cpuTargets, targets, - sizeof(real) * TARGET_MEM_SIZE); + hl_memcpy_device2host(cpuTargets, targets, sizeof(real) * TARGET_MEM_SIZE); for (int i = 0; i < SAMPLING_RATE * NUM_IMAGES; ++i) { for (int p = 0; p < TGT_SIZE * TGT_SIZE * CHANNELS; ++p) { const int offset = i * TGT_SIZE * TGT_SIZE * CHANNELS + p; @@ -191,50 +227,80 @@ TEST_F(PerturbationTest, translation_test) { } TEST_F(PerturbationTest, rotation_test) { - real* gpuAngle, *gpuScaleRatio; - int* gpuCenterR, *gpuCenterC; + real *gpuAngle, *gpuScaleRatio; + int *gpuCenterR, *gpuCenterC; allocateMem(gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC); - hl_generate_disturb_params(gpuAngle, gpuScaleRatio, gpuCenterR, - gpuCenterC, NUM_IMAGES, IMG_SIZE, 0.0, - 0.0, SAMPLING_RATE, false); + hl_generate_disturb_params(gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + NUM_IMAGES, + IMG_SIZE, + 0.0, + 0.0, + SAMPLING_RATE, + false); generateRotationParams(gpuAngle); real* targets = NULL; const int TARGET_MEM_SIZE = NUM_IMAGES * SAMPLING_RATE * TGT_SIZE * TGT_SIZE * CHANNELS; targets = (real*)hl_malloc_device(sizeof(real) * TARGET_MEM_SIZE); - hl_conv_random_disturb_with_params( - gpuImages_, IMG_SIZE, TGT_SIZE, CHANNELS, NUM_IMAGES, SAMPLING_RATE, - gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC, 2, targets); + hl_conv_random_disturb_with_params(gpuImages_, + IMG_SIZE, + TGT_SIZE, + CHANNELS, + NUM_IMAGES, + SAMPLING_RATE, + gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + 2, + targets); real cpuTargets[TARGET_MEM_SIZE]; - hl_memcpy_device2host(cpuTargets, targets, - sizeof(real) * TARGET_MEM_SIZE); + hl_memcpy_device2host(cpuTargets, targets, sizeof(real) * TARGET_MEM_SIZE); for (int i = 0; i < TARGET_MEM_SIZE; ++i) { EXPECT_FLOAT_EQ(1.0, cpuTargets[i]); } } TEST_F(PerturbationTest, scale_test) { - real* gpuAngle, *gpuScaleRatio; - int* gpuCenterR, *gpuCenterC; + real *gpuAngle, *gpuScaleRatio; + int *gpuCenterR, *gpuCenterC; allocateMem(gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC); - hl_generate_disturb_params(gpuAngle, gpuScaleRatio, gpuCenterR, - gpuCenterC, NUM_IMAGES, IMG_SIZE, 0.0, - 0.0, SAMPLING_RATE, false); + hl_generate_disturb_params(gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + NUM_IMAGES, + IMG_SIZE, + 0.0, + 0.0, + SAMPLING_RATE, + false); generateScaleParams(gpuScaleRatio); real* targets = NULL; const int TARGET_MEM_SIZE = NUM_IMAGES * SAMPLING_RATE * TGT_SIZE * TGT_SIZE * CHANNELS; targets = (real*)hl_malloc_device(sizeof(real) * TARGET_MEM_SIZE); - hl_conv_random_disturb_with_params( - gpuImages_, IMG_SIZE, TGT_SIZE, CHANNELS, NUM_IMAGES, SAMPLING_RATE, - gpuAngle, gpuScaleRatio, gpuCenterR, gpuCenterC, 2, targets); + hl_conv_random_disturb_with_params(gpuImages_, + IMG_SIZE, + TGT_SIZE, + CHANNELS, + NUM_IMAGES, + SAMPLING_RATE, + gpuAngle, + gpuScaleRatio, + gpuCenterR, + gpuCenterC, + 2, + targets); real cpuTargets[TARGET_MEM_SIZE]; - hl_memcpy_device2host(cpuTargets, targets, - sizeof(real) * TARGET_MEM_SIZE); + hl_memcpy_device2host(cpuTargets, targets, sizeof(real) * TARGET_MEM_SIZE); for (int i = 0; i < SAMPLING_RATE * NUM_IMAGES; ++i) { for (int p = 0; p < TGT_SIZE * TGT_SIZE * CHANNELS; ++p) { const int offset = i * TGT_SIZE * TGT_SIZE * CHANNELS + p; diff --git a/paddle/math/tests/test_sparseMatrixCompare.cpp b/paddle/math/tests/test_sparseMatrixCompare.cpp index 6048dd8112..d7aa20eb98 100644 --- a/paddle/math/tests/test_sparseMatrixCompare.cpp +++ b/paddle/math/tests/test_sparseMatrixCompare.cpp @@ -155,7 +155,7 @@ TEST(SMatrix, sMatrixMul) { for (auto M : {1, 40, 128, 200}) { for (auto N : {100, 2000, 20480}) { for (auto K : {100, 512, 1024}) { - VLOG(3) << " M=" << M << " N=" << N << " K=" << K;; + VLOG(3) << " M=" << M << " N=" << N << " K=" << K; testSpMatrixMul(M, N, K, 0.05); } } diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 42c74661d2..81d53f065b 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -12,14 +12,15 @@ WITHOUT 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 "Argument.h" #include "paddle/math/SparseMatrix.h" #include namespace paddle { -static void resizeAndCopy(MatrixPtr& dest, const MatrixPtr& src, bool useGpu, +static void resizeAndCopy(MatrixPtr& dest, + const MatrixPtr& src, + bool useGpu, hl_stream_t stream) { if (src) { if (!dest) { @@ -34,7 +35,9 @@ static void resizeAndCopy(MatrixPtr& dest, const MatrixPtr& src, bool useGpu, } } -static void resizeAndCopy(IVectorPtr& dest, const IVectorPtr& src, bool useGpu, +static void resizeAndCopy(IVectorPtr& dest, + const IVectorPtr& src, + bool useGpu, hl_stream_t stream) { if (src) { IVector::resizeOrCreate(dest, src->getSize(), useGpu); @@ -56,8 +59,11 @@ static void resizeAndCopy(ICpuGpuVectorPtr& dest, } } -static void resizeAndCopy(MatrixPtr& dest, const MatrixPtr& src, - int32_t startRow, int32_t copySize, bool useGpu, +static void resizeAndCopy(MatrixPtr& dest, + const MatrixPtr& src, + int32_t startRow, + int32_t copySize, + bool useGpu, hl_stream_t stream = HPPL_STREAM_DEFAULT) { if (src) { CHECK_LE((size_t)startRow + copySize, src->getHeight()); @@ -84,8 +90,11 @@ static void resizeAndCopy(MatrixPtr& dest, const MatrixPtr& src, } } -static void resizeAndCopy(IVectorPtr& dest, const IVectorPtr& src, - int32_t startPos, int32_t copySize, bool useGpu, +static void resizeAndCopy(IVectorPtr& dest, + const IVectorPtr& src, + int32_t startPos, + int32_t copySize, + bool useGpu, hl_stream_t stream = HPPL_STREAM_DEFAULT) { if (src) { CHECK_LE((size_t)startPos + copySize, src->getSize()); @@ -115,7 +124,8 @@ static void resizeAndCopy(ICpuGpuVectorPtr& dest, } static void resizeAndCopy(UserDefinedVectorPtr& dest, - const UserDefinedVectorPtr& src, bool useGpu, + const UserDefinedVectorPtr& src, + bool useGpu, hl_stream_t stream) { if (src) { CHECK(!useGpu) << "not implemented"; @@ -132,8 +142,10 @@ static void resizeAndCopy(UserDefinedVectorPtr& dest, } static void resizeAndCopy(UserDefinedVectorPtr& dest, - const UserDefinedVectorPtr& src, int32_t startPos, - int32_t copySize, bool useGpu, + const UserDefinedVectorPtr& src, + int32_t startPos, + int32_t copySize, + bool useGpu, hl_stream_t stream = HPPL_STREAM_DEFAULT) { if (src) { CHECK(!useGpu) << "not implemented"; @@ -151,7 +163,9 @@ static void resizeAndCopy(UserDefinedVectorPtr& dest, } } -static void resizeAndCopy(SVectorPtr& dest, const SVectorPtr& src, bool useGpu, +static void resizeAndCopy(SVectorPtr& dest, + const SVectorPtr& src, + bool useGpu, hl_stream_t stream) { if (src) { size_t height = src->size(); @@ -166,8 +180,11 @@ static void resizeAndCopy(SVectorPtr& dest, const SVectorPtr& src, bool useGpu, } } -static void resizeAndCopy(SVectorPtr& dest, const SVectorPtr& src, - int32_t startPos, int32_t copySize, bool useGpu, +static void resizeAndCopy(SVectorPtr& dest, + const SVectorPtr& src, + int32_t startPos, + int32_t copySize, + bool useGpu, hl_stream_t stream = HPPL_STREAM_DEFAULT) { if (src) { CHECK_LE((size_t)startPos + copySize, src->size()); @@ -184,37 +201,46 @@ static void resizeAndCopy(SVectorPtr& dest, const SVectorPtr& src, } void Argument::resizeAndCopyFrom(const Argument& src, bool useGpu) { - resizeAndCopyFrom(src, useGpu, HPPL_STREAM_DEFAULT); - hl_stream_synchronize(HPPL_STREAM_DEFAULT); + resizeAndCopyFrom(src, useGpu, HPPL_STREAM_DEFAULT); + hl_stream_synchronize(HPPL_STREAM_DEFAULT); } -void Argument::resizeAndCopyFrom(const Argument& src, bool useGpu, +void Argument::resizeAndCopyFrom(const Argument& src, + bool useGpu, hl_stream_t stream) { dataId = src.dataId; resizeAndCopy(value, src.value, useGpu, stream); resizeAndCopy(grad, src.grad, useGpu, stream); resizeAndCopy(in, src.in, useGpu, stream); resizeAndCopy(ids, src.ids, useGpu, stream); - resizeAndCopy(sequenceStartPositions, src.sequenceStartPositions, - false /* useGpu */, stream); + resizeAndCopy(sequenceStartPositions, + src.sequenceStartPositions, + false /* useGpu */, + stream); if (src.hasSubseq()) { resizeAndCopy(subSequenceStartPositions, - src.subSequenceStartPositions, false /* useGpu */, stream); + src.subSequenceStartPositions, + false /* useGpu */, + stream); } resizeAndCopy(udp, src.udp, useGpu, stream); resizeAndCopy(strs, src.strs, useGpu, stream); } -int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, - int32_t copySize, bool useGpu) { - int32_t size = resizeAndCopyFrom(src, startSeq, copySize, useGpu, - HPPL_STREAM_DEFAULT); - hl_stream_synchronize(HPPL_STREAM_DEFAULT); - return size; +int32_t Argument::resizeAndCopyFrom(const Argument& src, + int32_t startSeq, + int32_t copySize, + bool useGpu) { + int32_t size = + resizeAndCopyFrom(src, startSeq, copySize, useGpu, HPPL_STREAM_DEFAULT); + hl_stream_synchronize(HPPL_STREAM_DEFAULT); + return size; } -int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, - int32_t copySize, bool useGpu, +int32_t Argument::resizeAndCopyFrom(const Argument& src, + int32_t startSeq, + int32_t copySize, + bool useGpu, hl_stream_t stream) { dataId = src.dataId; @@ -239,8 +265,12 @@ int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, resizeAndCopy(grad, src.grad, startRow, copyFeatureSize, useGpu, stream); resizeAndCopy(ids, src.ids, startRow, copyFeatureSize, useGpu, stream); resizeAndCopy(udp, src.udp, startRow, copySize, useGpu, stream); - resizeAndCopy(sequenceStartPositions, src.sequenceStartPositions, - startSeq, copySize + 1, false, stream); + resizeAndCopy(sequenceStartPositions, + src.sequenceStartPositions, + startSeq, + copySize + 1, + false, + stream); // modify new sequenceStartPositions int* destSequences = sequenceStartPositions->getMutableData(false); for (int i = 0; i < copySize + 1; i++) { @@ -264,8 +294,11 @@ int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, } int32_t copySubSize = subEndSeq - subStartSeq; resizeAndCopy(subSequenceStartPositions, - src.subSequenceStartPositions, subStartSeq, - copySubSize + 1, false, stream); + src.subSequenceStartPositions, + subStartSeq, + copySubSize + 1, + false, + stream); // modify new subSequenceStartPositions int* destSubSequences = subSequenceStartPositions->getMutableData(false); for (int i = 0; i < copySubSize + 1; i++) { @@ -281,14 +314,19 @@ int32_t Argument::resizeAndCopyFrom(const Argument& src, int32_t startSeq, void Argument::concat(const std::vector& args, const std::vector& selectRows, - const std::vector& seqStartPos, bool useGpu, - hl_stream_t stream, PassType passType) { + const std::vector& seqStartPos, + bool useGpu, + hl_stream_t stream, + PassType passType) { CHECK(!subSequenceStartPositions) - << "undefined behavior for subsequence positions"; + << "undefined behavior for subsequence positions"; size_t batchSize = selectRows.size(); - auto copyArg = [batchSize, stream](MatrixPtr& dst, MatrixPtr src, - int startRow, int pos, int size, + auto copyArg = [batchSize, stream](MatrixPtr& dst, + MatrixPtr src, + int startRow, + int pos, + int size, bool useGpu) { if (!src) { dst.reset(); @@ -305,8 +343,11 @@ void Argument::concat(const std::vector& args, tmpMatrix->copyFrom(*src->subMatrix(pos, size), stream); }; - auto copyIds = [batchSize, stream](IVectorPtr& dst, const IVectorPtr& src, - int startRow, int pos, int size, + auto copyIds = [batchSize, stream](IVectorPtr& dst, + const IVectorPtr& src, + int startRow, + int pos, + int size, bool useGpu) { if (!src) { dst.reset(); @@ -316,8 +357,11 @@ void Argument::concat(const std::vector& args, dst->subVec(startRow, size)->copyFrom(*src->subVec(pos, size), stream); }; - auto copyStrs = [batchSize, stream](SVectorPtr& dst, const SVectorPtr& src, - int startRow, int pos, int size, + auto copyStrs = [batchSize, stream](SVectorPtr& dst, + const SVectorPtr& src, + int startRow, + int pos, + int size, bool useGpu) { if (!src) { dst.reset(); @@ -328,8 +372,8 @@ void Argument::concat(const std::vector& args, } else { dst->resize(batchSize); } - std::copy(src->begin() + pos, src->begin() + pos + size, - dst->begin() + startRow); + std::copy( + src->begin() + pos, src->begin() + pos + size, dst->begin() + startRow); }; dataId = args[0].dataId; @@ -354,14 +398,16 @@ void Argument::concat(const std::vector& args, copyStrs(strs, arg.strs, j, rowIdx, copySize, useGpu); } } - ICpuGpuVector::resizeOrCreate(sequenceStartPositions, - seqStartPos.size(), useGpu); - sequenceStartPositions->copyFrom(seqStartPos.data(), - seqStartPos.size(), useGpu); + ICpuGpuVector::resizeOrCreate( + sequenceStartPositions, seqStartPos.size(), useGpu); + sequenceStartPositions->copyFrom( + seqStartPos.data(), seqStartPos.size(), useGpu); } -void Argument::concat(const std::vector& args, bool useGpu, - hl_stream_t stream, PassType passType) { +void Argument::concat(const std::vector& args, + bool useGpu, + hl_stream_t stream, + PassType passType) { int32_t batchSize = 0; int64_t numSequences = 0; int64_t numSubSequences = 0; @@ -371,8 +417,8 @@ void Argument::concat(const std::vector& args, bool useGpu, numSubSequences += arg.getNumSubSequences(); } - auto copyArg = [batchSize, stream](MatrixPtr& dst, MatrixPtr src, - int startRow, bool useGpu) { + auto copyArg = [batchSize, stream]( + MatrixPtr& dst, MatrixPtr src, int startRow, bool useGpu) { if (!src) { dst.reset(); return; @@ -388,8 +434,8 @@ void Argument::concat(const std::vector& args, bool useGpu, tmpMatrix->copyFrom(*src, stream); }; - auto copyIds = [batchSize, stream](IVectorPtr& dst, const IVectorPtr& src, - int startRow, bool useGpu) { + auto copyIds = [batchSize, stream]( + IVectorPtr& dst, const IVectorPtr& src, int startRow, bool useGpu) { if (!src) { dst.reset(); return; @@ -398,8 +444,8 @@ void Argument::concat(const std::vector& args, bool useGpu, dst->subVec(startRow, src->getSize())->copyFrom(*src, stream); }; - auto copyStrs = [batchSize, stream](SVectorPtr& dst, const SVectorPtr& src, - int startRow, bool useGpu) { + auto copyStrs = [batchSize, stream]( + SVectorPtr& dst, const SVectorPtr& src, int startRow, bool useGpu) { if (!src) { dst.reset(); return; @@ -412,21 +458,23 @@ void Argument::concat(const std::vector& args, bool useGpu, std::copy(src->begin(), src->end(), dst->begin() + startRow); }; - auto copySequencePos = [] - (ICpuGpuVectorPtr& dstSeq, const ICpuGpuVectorPtr& srcSeq, - int dstNumSequences, int srcNumSequences, - int& startSequences, int startRow) { - if (srcSeq) { - ICpuGpuVector::resizeOrCreate(dstSeq, dstNumSequences + 1, false); - const int* src = srcSeq->getData(false); - int* dest = dstSeq->getMutableData(false); - for (int i = 0; i < srcNumSequences + 1; ++i) { - dest[i + startSequences] = src[i] + startRow; - } - startSequences += srcNumSequences; - } else { - dstSeq.reset(); + auto copySequencePos = [](ICpuGpuVectorPtr& dstSeq, + const ICpuGpuVectorPtr& srcSeq, + int dstNumSequences, + int srcNumSequences, + int& startSequences, + int startRow) { + if (srcSeq) { + ICpuGpuVector::resizeOrCreate(dstSeq, dstNumSequences + 1, false); + const int* src = srcSeq->getData(false); + int* dest = dstSeq->getMutableData(false); + for (int i = 0; i < srcNumSequences + 1; ++i) { + dest[i + startSequences] = src[i] + startRow; } + startSequences += srcNumSequences; + } else { + dstSeq.reset(); + } }; int startRow = 0; @@ -479,8 +527,8 @@ void Argument::splitByDataId(const std::vector& argus, void Argument::getSeqInfo(std::vector* seqInfo) const { const int* starts = sequenceStartPositions->getData(false); - const int* subStarts = hasSubseq() - ? subSequenceStartPositions->getData(false) : nullptr; + const int* subStarts = + hasSubseq() ? subSequenceStartPositions->getData(false) : nullptr; size_t numSequences = getNumSequences(); seqInfo->reserve(numSequences); int subSeqEnd = 0; @@ -501,7 +549,8 @@ void Argument::getSeqInfo(std::vector* seqInfo) const { } seqInfo->push_back(info); } - std::sort(seqInfo->begin(), seqInfo->end(), + std::sort(seqInfo->begin(), + seqInfo->end(), [](const SeqInfo& a, const SeqInfo& b) { return a.topLevelLength > b.topLevelLength; }); @@ -535,9 +584,8 @@ void Argument::degradeSequence(const Argument& input, bool useGpu) { CHECK_EQ(input.hasSubseq(), 1UL); size_t numSequences = input.getNumSequences(); size_t numSubSequences = input.getNumSubSequences(); - ICpuGpuVector::resizeOrCreate(sequenceStartPositions, - numSequences + 1, - false); + ICpuGpuVector::resizeOrCreate( + sequenceStartPositions, numSequences + 1, false); int* tgtBuf = sequenceStartPositions->getMutableData(false); const int* starts = input.sequenceStartPositions->getData(false); const int* subStarts = input.subSequenceStartPositions->getData(false); @@ -551,24 +599,29 @@ void Argument::degradeSequence(const Argument& input, bool useGpu) { tgtBuf[numSequences] = numSubSequences; } -void Argument::subArgFrom(const Argument& input, size_t offset, size_t height, - size_t width, bool useGpu, bool trans, bool seqFlag, - size_t seqStart, size_t seqSize) { +void Argument::subArgFrom(const Argument& input, + size_t offset, + size_t height, + size_t width, + bool useGpu, + bool trans, + bool seqFlag, + size_t seqStart, + size_t seqSize) { if (input.value) { - value = Matrix::create(input.value->getData() + offset * width, - height, width, trans, useGpu); + value = Matrix::create( + input.value->getData() + offset * width, height, width, trans, useGpu); } if (input.ids) { ids = IVector::create(input.ids->getData() + offset, height, useGpu); } if (input.grad) { - grad = Matrix::create(input.grad->getData() + offset * width, - height, width, trans, useGpu); + grad = Matrix::create( + input.grad->getData() + offset * width, height, width, trans, useGpu); } if (seqFlag) { sequenceStartPositions = std::make_shared( - *(input.sequenceStartPositions), - seqStart, seqSize); + *(input.sequenceStartPositions), seqStart, seqSize); } } diff --git a/paddle/parameter/Argument.h b/paddle/parameter/Argument.h index 81ff9029bc..2b20122deb 100644 --- a/paddle/parameter/Argument.h +++ b/paddle/parameter/Argument.h @@ -12,7 +12,6 @@ WITHOUT 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 "hl_gpu.h" @@ -153,9 +152,8 @@ struct Argument { } int64_t getNumSubSequences() const { - return subSequenceStartPositions - ? subSequenceStartPositions->getSize() - 1 - : getBatchSize(); + return subSequenceStartPositions ? subSequenceStartPositions->getSize() - 1 + : getBatchSize(); } bool hasSubseq() const { return subSequenceStartPositions != nullptr; } @@ -190,9 +188,14 @@ struct Argument { * @param seqStart[in] offset of input.sequenceStartPositions * @param seqSize[in] lenght of output.sequenceStartPositions */ - void subArgFrom(const Argument& input, size_t offset, size_t height, - size_t width, bool useGpu, bool trans = false, - bool seqFlag = false, size_t seqStart = 0, + void subArgFrom(const Argument& input, + size_t offset, + size_t height, + size_t width, + bool useGpu, + bool trans = false, + bool seqFlag = false, + size_t seqStart = 0, size_t seqSize = 0); /* * for sequence input: @@ -206,16 +209,21 @@ struct Argument { * Note that when specifying the stream explicitly in this case, * synchronize should also be called somewhere after this function */ - int32_t resizeAndCopyFrom(const Argument& src, int32_t startSeq, - int32_t copySize, bool useGpu, hl_stream_t stream); + int32_t resizeAndCopyFrom(const Argument& src, + int32_t startSeq, + int32_t copySize, + bool useGpu, + hl_stream_t stream); /* * same with the above function, except that the stream is * HPPL_STREAM_DEFAULT and synchronize is automatically called * inside it */ - int32_t resizeAndCopyFrom(const Argument& src, int32_t startSeq, - int32_t copySize, bool useGpu = FLAGS_use_gpu); + int32_t resizeAndCopyFrom(const Argument& src, + int32_t startSeq, + int32_t copySize, + bool useGpu = FLAGS_use_gpu); void resizeAndCopyFrom(const Argument& src, bool useGpu, hl_stream_t stream); @@ -237,13 +245,16 @@ struct Argument { */ void concat(const std::vector& args, const std::vector& selectRows, - const std::vector& seqStartPos, bool useGpu, - hl_stream_t stream, PassType passType); + const std::vector& seqStartPos, + bool useGpu, + hl_stream_t stream, + PassType passType); /* Concatenate several args into one and put the result into this. */ - void concat(const std::vector& src, bool useGpu = FLAGS_use_gpu, + void concat(const std::vector& src, + bool useGpu = FLAGS_use_gpu, hl_stream_t stream = HPPL_STREAM_DEFAULT, PassType passType = PASS_TEST); diff --git a/paddle/parameter/AverageOptimizer.cpp b/paddle/parameter/AverageOptimizer.cpp index 4f730059c7..593594761e 100644 --- a/paddle/parameter/AverageOptimizer.cpp +++ b/paddle/parameter/AverageOptimizer.cpp @@ -12,15 +12,16 @@ WITHOUT 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 "AverageOptimizer.h" namespace paddle { // factory method to create an instance of AverageOptimizer ParameterOptimizer* AverageOptimizer::create( - const OptimizationConfig& optConfig, ParameterOptimizer* optimizer, - bool isParameterSparse, bool useParameterApply) { + const OptimizationConfig& optConfig, + ParameterOptimizer* optimizer, + bool isParameterSparse, + bool useParameterApply) { if (optConfig.average_window() <= 0) { return optimizer; } @@ -44,8 +45,8 @@ AverageOptimizer::AverageOptimizer(const OptimizationConfig& optConfig, prevNumUpdates_(0), numAccumulates_(0), oldNumAccumulates_(0), - minAverageWindow_(std::min( - 10000L, optConfig_.max_average_window())), + minAverageWindow_( + std::min(10000L, optConfig_.max_average_window())), maxAverageWindow_(optConfig_.max_average_window()) { parameterTypes_ = optimizer_->getParameterTypes(); addParameterType(PARAMETER_SUM1); @@ -121,17 +122,27 @@ ParameterOptimizer::TraverseCallback AverageOptimizer::apply() { real scale = 1. / (numAccumulates_ + oldNumAccumulates_); if (useApply_) { - return [scale](const VectorPtr vecs[], const ParameterConfig& config, + return [scale](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { - vecs[PARAMETER_APPLY]->add3(*vecs[PARAMETER_SUM1], *vecs[PARAMETER_SUM2], - *vecs[PARAMETER_SUM3], scale, scale, scale); + vecs[PARAMETER_APPLY]->add3(*vecs[PARAMETER_SUM1], + *vecs[PARAMETER_SUM2], + *vecs[PARAMETER_SUM3], + scale, + scale, + scale); }; } else { - return [scale](const VectorPtr vecs[], const ParameterConfig& config, + return [scale](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { vecs[PARAMETER_GRADIENT]->copyFrom(*vecs[PARAMETER_VALUE]); - vecs[PARAMETER_VALUE]->add3(*vecs[PARAMETER_SUM1], *vecs[PARAMETER_SUM2], - *vecs[PARAMETER_SUM3], scale, scale, scale); + vecs[PARAMETER_VALUE]->add3(*vecs[PARAMETER_SUM1], + *vecs[PARAMETER_SUM2], + *vecs[PARAMETER_SUM3], + scale, + scale, + scale); }; } } @@ -144,8 +155,8 @@ ParameterOptimizer::TraverseCallback AverageOptimizer::restore() { return nullptr; } - return [](const VectorPtr vecs[], const ParameterConfig& config, - size_t sparseId) { + return []( + const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) { vecs[PARAMETER_VALUE]->copyFrom(*vecs[PARAMETER_GRADIENT]); vecs[PARAMETER_GRADIENT]->zeroMem(); }; @@ -174,7 +185,8 @@ ParameterOptimizer::TraverseCallback AverageSparseOptimizer::startCatchUpWith() if (timer_ > 0) { callbacks.emplace_back( - [this](const VectorPtr vecs[], const ParameterConfig& config, + [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { this->catchUpWith(vecs, config, sparseId); }); } diff --git a/paddle/parameter/AverageOptimizer.h b/paddle/parameter/AverageOptimizer.h index 8e0ead8412..ccc2612608 100644 --- a/paddle/parameter/AverageOptimizer.h +++ b/paddle/parameter/AverageOptimizer.h @@ -12,7 +12,6 @@ WITHOUT 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 "FirstOrderOptimizer.h" @@ -26,7 +25,8 @@ public: // if *useParameterApply* set, use PARAMETER_APPLY to store averaged parameter // else use PARAMETER_VALUE, and value backup in PARAMETER_GRADIENT AverageOptimizer(const OptimizationConfig& optConfig, - ParameterOptimizer* optimizer, bool useParameterApply); + ParameterOptimizer* optimizer, + bool useParameterApply); static ParameterOptimizer* create(const OptimizationConfig& optConfig, ParameterOptimizer* optimizer, @@ -45,7 +45,8 @@ public: virtual void startBatch(int64_t numSamplesProcessed); virtual void finishBatch(); - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const { optimizer_->update(vecs, paraConfig, sparseId); vecs[PARAMETER_SUM1]->add(*vecs[PARAMETER_VALUE], 1.0f); @@ -99,7 +100,8 @@ protected: class AverageSparseOptimizer : public AverageOptimizer { public: AverageSparseOptimizer(const OptimizationConfig& optConfig, - ParameterOptimizer* optimizer, bool useParameterApply) + ParameterOptimizer* optimizer, + bool useParameterApply) : AverageOptimizer(optConfig, optimizer, useParameterApply) {} virtual void init(size_t numRows, const ParameterConfig* config) { @@ -114,9 +116,11 @@ public: AverageOptimizer::finishBatch(); timer_++; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const; - void catchUpWith(const VectorPtr vecs[], const ParameterConfig& paraConfig, + void catchUpWith(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const; virtual TraverseCallback startCatchUpWith() const; virtual void finishCatchUpWith() { diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index bb46a51d1e..a9be07d062 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "paddle/utils/Flags.h" @@ -71,13 +70,15 @@ void SparseMomentumParameterOptimizer::update(const VectorPtr vecs[], tau_ * alpha_ * gamma_ * learningRate_); vecs[PARAMETER_VALUE]->add(*vecs[PARAMETER_MOMENTUM_UT], tau_ / beta_ + 1.0 / alpha_, - *vecs[PARAMETER_MOMENTUM_VT], 1.0 / beta_); + *vecs[PARAMETER_MOMENTUM_VT], + 1.0 / beta_); } else { - vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], - learningRate_ * paraConfig.learning_rate(), paraConfig.momentum(), - applyDecay_ ? paraConfig.decay_rate() : 0); + vecs[PARAMETER_VALUE]->sgdUpdate(*vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], + learningRate_ * paraConfig.learning_rate(), + paraConfig.momentum(), + applyDecay_ ? paraConfig.decay_rate() : 0); } } @@ -90,7 +91,8 @@ SparseMomentumParameterOptimizer::needSpecialTraversal( // 2. Note that \tau * u_t + v_t = \beta \theta_t, therefore: // u_t should be rescaled to u_t/alpha_ // v_t should be reset to \theta_t - return [this](const VectorPtr vecs[], const ParameterConfig& config, + return [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { vecs[PARAMETER_MOMENTUM_UT]->divScalar(alpha_); vecs[PARAMETER_MOMENTUM_VT]->assign(*vecs[PARAMETER_VALUE]); @@ -120,10 +122,12 @@ void AdagradParameterOptimizer::update(const VectorPtr vecs[], vecs[PARAMETER_LEARNING_RATE]->add(optConfig_.ada_epsilon()); vecs[PARAMETER_LEARNING_RATE]->invSqrt(*vecs[PARAMETER_LEARNING_RATE]); - vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], - *vecs[PARAMETER_LEARNING_RATE], learningRate_ * config.learning_rate(), - config.momentum(), applyDecay_ ? config.decay_rate() : 0); + vecs[PARAMETER_VALUE]->sgdUpdate(*vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], + *vecs[PARAMETER_LEARNING_RATE], + learningRate_ * config.learning_rate(), + config.momentum(), + applyDecay_ ? config.decay_rate() : 0); } ParameterOptimizer::TraverseCallback @@ -132,7 +136,8 @@ AdagradParameterOptimizer::needSpecialTraversal( if (numUpdates_ % kMaxNumAccumulates == 0) { // Move the sum to a different buffer to avoid loss of precision // due to too many sums. - return [this](const VectorPtr vecs[], const ParameterConfig& config, + return [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { vecs[PARAMETER_GRADIENT_SQURESUM]->add( *vecs[PARAMETER_GRADIENT_SQURESUM1]); @@ -148,24 +153,29 @@ void AdaDeltaParameterOptimizer::update(const VectorPtr vecs[], size_t sparseId) const { CHECK(sparseId == -1LU) << "Sparse update is not supported"; // E(g_t^2) = \rou * E(g_{t-1}^2) + (1-\rou) * g^2 - vecs[PARAMETER_GRADIENT_SQURESUM]->decayAddSquare(*vecs[PARAMETER_GRADIENT], - rou_, 1.0f - rou_); + vecs[PARAMETER_GRADIENT_SQURESUM]->decayAddSquare( + *vecs[PARAMETER_GRADIENT], rou_, 1.0f - rou_); // learn_rate = sqrt( ( E(dx_{t-1}^2) + epsilon ) / ( E(g_t^2) + epsilon ) ) vecs[PARAMETER_LEARNING_RATE]->dotDiv(*vecs[PARAMETER_GRADIENT_SQURESUM1], *vecs[PARAMETER_GRADIENT_SQURESUM], - epsilon_, epsilon_); + epsilon_, + epsilon_); vecs[PARAMETER_LEARNING_RATE]->sqrt(); // E(dx_t^2) = \rou * E(dx_{t-1}^2) + (1-\rou) * (-g*learn_rate)^2 vecs[PARAMETER_GRADIENT_SQURESUM1]->decayAddSquareMul( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_LEARNING_RATE], rou_, + *vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_LEARNING_RATE], + rou_, 1.0f - rou_); - vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], - *vecs[PARAMETER_LEARNING_RATE], learningRate_ * config.learning_rate(), - config.momentum(), applyDecay_ ? config.decay_rate() : 0); + vecs[PARAMETER_VALUE]->sgdUpdate(*vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], + *vecs[PARAMETER_LEARNING_RATE], + learningRate_ * config.learning_rate(), + config.momentum(), + applyDecay_ ? config.decay_rate() : 0); } void RMSPropParameterOptimizer::update(const VectorPtr vecs[], @@ -185,12 +195,13 @@ void RMSPropParameterOptimizer::update(const VectorPtr vecs[], // For the first time update, make the sum be the current square // so that the initial estimation of E(g_t^2) will not be too small. vecs[PARAMETER_GRADIENT_SQURESUM]->decayAddSquare( - *vecs[PARAMETER_GRADIENT], accumulatedRou, + *vecs[PARAMETER_GRADIENT], + accumulatedRou, firstTime ? 1.0f : 1.0f - rou_); // E(g_t) = \rou * E(g_{t-1}) + (1-\rou) * g - vecs[PARAMETER_GRADIENT_SQURESUM1]->add(*vecs[PARAMETER_GRADIENT], - accumulatedRou, 1.0f - rou_); + vecs[PARAMETER_GRADIENT_SQURESUM1]->add( + *vecs[PARAMETER_GRADIENT], accumulatedRou, 1.0f - rou_); // learn_rate = 1/sqrt( ( E(g_t^2) - (E(g_t))^2 + epsilon ) // Basiclly if the sign of the gradient changes more often, @@ -201,10 +212,12 @@ void RMSPropParameterOptimizer::update(const VectorPtr vecs[], vecs[PARAMETER_LEARNING_RATE]->add(optConfig_.ada_epsilon()); vecs[PARAMETER_LEARNING_RATE]->invSqrt(*vecs[PARAMETER_LEARNING_RATE]); - vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], - *vecs[PARAMETER_LEARNING_RATE], learningRate_ * config.learning_rate(), - config.momentum(), applyDecay_ ? config.decay_rate() : 0); + vecs[PARAMETER_VALUE]->sgdUpdate(*vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], + *vecs[PARAMETER_LEARNING_RATE], + learningRate_ * config.learning_rate(), + config.momentum(), + applyDecay_ ? config.decay_rate() : 0); } void DecayedAdagradParameterOptimizer::update(const VectorPtr vecs[], @@ -224,7 +237,8 @@ void DecayedAdagradParameterOptimizer::update(const VectorPtr vecs[], // For the first time update, make the sum be the current square // so that the initial estimation of E(g_t^2) will not be too small. vecs[PARAMETER_GRADIENT_SQURESUM]->decayAddSquare( - *vecs[PARAMETER_GRADIENT], accumulatedRou, + *vecs[PARAMETER_GRADIENT], + accumulatedRou, firstTime ? 1.0f : 1.0f - rou_); // learn_rate = 1/sqrt( ( E(g_t^2) + epsilon ) @@ -234,10 +248,12 @@ void DecayedAdagradParameterOptimizer::update(const VectorPtr vecs[], vecs[PARAMETER_LEARNING_RATE]->add(*vecs[PARAMETER_GRADIENT_SQURESUM]); vecs[PARAMETER_LEARNING_RATE]->invSqrt(*vecs[PARAMETER_LEARNING_RATE]); - vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], - *vecs[PARAMETER_LEARNING_RATE], learningRate_ * config.learning_rate(), - config.momentum(), applyDecay_ ? config.decay_rate() : 0); + vecs[PARAMETER_VALUE]->sgdUpdate(*vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], + *vecs[PARAMETER_LEARNING_RATE], + learningRate_ * config.learning_rate(), + config.momentum(), + applyDecay_ ? config.decay_rate() : 0); } void AdamParameterOptimizer::update(const VectorPtr vecs[], @@ -290,7 +306,6 @@ void AdamaxParameterOptimizer::update(const VectorPtr vecs[], theta->add(*theta, 1.0, *g, -learningRate); } - void OptimizerWithGradientClipping::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { diff --git a/paddle/parameter/FirstOrderOptimizer.h b/paddle/parameter/FirstOrderOptimizer.h index ad5f480976..a9a2ffdd41 100644 --- a/paddle/parameter/FirstOrderOptimizer.h +++ b/paddle/parameter/FirstOrderOptimizer.h @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterOptimizer.h" @@ -31,21 +30,22 @@ public: virtual void startBatch(int64_t numSamplesProcessed) { learningRate_ = calcLearningRate(numSamplesProcessed, pass_); } - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const { (void)sparseId; - real torch_learningRate = optConfig_.learning_method() == "torch_momentum" ? - 1.0 - paraConfig.momentum() : 1.0; + real torch_learningRate = optConfig_.learning_method() == "torch_momentum" + ? 1.0 - paraConfig.momentum() + : 1.0; vecs[PARAMETER_VALUE]->sgdUpdate( - *vecs[PARAMETER_GRADIENT], *vecs[PARAMETER_MOMENTUM], + *vecs[PARAMETER_GRADIENT], + *vecs[PARAMETER_MOMENTUM], learningRate_ * paraConfig.learning_rate() * - (firstTime_ ? 1.0 : torch_learningRate), + (firstTime_ ? 1.0 : torch_learningRate), paraConfig.momentum(), applyDecay_ ? paraConfig.decay_rate() : 0); } - virtual void finishBatch() { - firstTime_ = false; - } + virtual void finishBatch() { firstTime_ = false; } }; // SGD optimization with sparse support. @@ -71,7 +71,8 @@ public: const OptimizationConfig& optConfig); virtual void init(size_t numRows, const ParameterConfig* config); virtual void startBatch(int64_t numSamplesProcessed); - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const; virtual TraverseCallback needSpecialTraversal( const ParameterConfig& config) const; @@ -111,7 +112,8 @@ public: (void)numSamplesProcessed; ++numUpdates_; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; virtual TraverseCallback needSpecialTraversal( const ParameterConfig& config) const; @@ -141,7 +143,8 @@ public: learningRate_ = calcLearningRate(numSamplesProcessed, pass_); } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; protected: @@ -173,7 +176,8 @@ public: } virtual void finishBatch() { timer_++; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; protected: @@ -214,7 +218,8 @@ public: } virtual void finishBatch() { timer_++; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; protected: @@ -251,7 +256,8 @@ public: virtual void finishBatch() { ++step_; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; protected: @@ -280,7 +286,8 @@ public: virtual void finishBatch() { ++step_; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; protected: @@ -301,7 +308,8 @@ public: // learningRate required by regularizer learningRate_ = calcLearningRate(numSamplesProcessed, pass_); } - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const { vecs[PARAMETER_VALUE]->add(*vecs[PARAMETER_GRADIENT], optConfig_.delta_add_rate()); @@ -314,7 +322,8 @@ public: explicit DummyOptimizer(const OptimizationConfig& optConfig) : ParameterOptimizer(optConfig) {} - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, size_t sparseId) const {} }; @@ -344,7 +353,8 @@ public: const ParameterConfig& config) const { return optimizer_->needSpecialTraversal(config); } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; virtual void setNoDecay() { optimizer_->setNoDecay(); } diff --git a/paddle/parameter/LearningRateScheduler.cpp b/paddle/parameter/LearningRateScheduler.cpp index ce045ebf05..a7412500cc 100644 --- a/paddle/parameter/LearningRateScheduler.cpp +++ b/paddle/parameter/LearningRateScheduler.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "LearningRateScheduler.h" #include "paddle/utils/StringUtil.h" diff --git a/paddle/parameter/LearningRateScheduler.h b/paddle/parameter/LearningRateScheduler.h index 74fb848fab..e987c3dcde 100644 --- a/paddle/parameter/LearningRateScheduler.h +++ b/paddle/parameter/LearningRateScheduler.h @@ -12,7 +12,6 @@ WITHOUT 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 "TrainerConfig.pb.h" @@ -20,9 +19,10 @@ limitations under the License. */ namespace paddle { // NOLINTNEXTLINES_4 -#define REGISTER_LEARNING_RATE_SCHEDULER(__type_name, __class_name) \ - static InitFunction __reg_type_##__type_name([]() { \ - LearningRateScheduler::registrar_.registerClass<__class_name>(#__type_name); \ +#define REGISTER_LEARNING_RATE_SCHEDULER(__type_name, __class_name) \ + static InitFunction __reg_type_##__type_name([]() { \ + LearningRateScheduler::registrar_.registerClass<__class_name>( \ + #__type_name); \ }) class LearningRateScheduler { diff --git a/paddle/parameter/OptimizerFunctions.cpp b/paddle/parameter/OptimizerFunctions.cpp index 5adcf86efd..6fd7964347 100644 --- a/paddle/parameter/OptimizerFunctions.cpp +++ b/paddle/parameter/OptimizerFunctions.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "AverageOptimizer.h" #include "FirstOrderOptimizer.h" #include "OptimizerWithRegularizer.h" @@ -22,19 +21,22 @@ namespace paddle { // creator for AverageOptimizer ParameterOptimizer* sgdOptimizerCreate(const OptimizationConfig& optConfig, const ParameterConfig& paraConfig, - bool isParameterSparse, bool inPserver) { + bool isParameterSparse, + bool inPserver) { ParameterOptimizer* optimizer = OptimizerWithRegularizer::create( optConfig, paraConfig, isParameterSparse, inPserver); - return AverageOptimizer::create(optConfig, optimizer, isParameterSparse, - inPserver /*useParameterApply*/); + return AverageOptimizer::create( + optConfig, optimizer, isParameterSparse, inPserver /*useParameterApply*/); } std::vector sgdOptimizerGetTypes( const OptimizationConfig& optConfig, bool inPserver) { std::unique_ptr optimizer; - optimizer.reset(AverageOptimizer::create( - optConfig, ParameterOptimizer::create(optConfig, inPserver), - false /*isParameterSparse*/, inPserver)); + optimizer.reset( + AverageOptimizer::create(optConfig, + ParameterOptimizer::create(optConfig, inPserver), + false /*isParameterSparse*/, + inPserver)); CHECK(optimizer) << "fail to create optimizer: " << optConfig.learning_method(); return optimizer->getParameterTypes(); diff --git a/paddle/parameter/OptimizerFunctions.h b/paddle/parameter/OptimizerFunctions.h index 9592658224..a5f8b2c569 100644 --- a/paddle/parameter/OptimizerFunctions.h +++ b/paddle/parameter/OptimizerFunctions.h @@ -12,7 +12,6 @@ WITHOUT 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 "FirstOrderOptimizer.h" @@ -25,7 +24,8 @@ namespace paddle { */ ParameterOptimizer* sgdOptimizerCreate(const OptimizationConfig& optConfig, const ParameterConfig& paraConfig, - bool isParameterSparse, bool inPserver); + bool isParameterSparse, + bool inPserver); /* * Get the parameter types needed for the specific optimization diff --git a/paddle/parameter/OptimizerWithRegularizer.cpp b/paddle/parameter/OptimizerWithRegularizer.cpp index 0da27a51c6..5381e7bef3 100644 --- a/paddle/parameter/OptimizerWithRegularizer.cpp +++ b/paddle/parameter/OptimizerWithRegularizer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "OptimizerWithRegularizer.h" namespace paddle { @@ -24,7 +23,8 @@ OptimizerWithRegularizerEveryNumBatches::needSpecialTraversal( if (isRegularizationBatch(config)) { callbacks.emplace_back( - [this](const VectorPtr vecs[], const ParameterConfig& config, + [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { this->doTraversal(vecs, config); }); } @@ -39,8 +39,8 @@ void OptimizerWithRegularizerEveryNumBatches::doTraversal( const VectorPtr vecs[], const ParameterConfig& config) const { int32_t base = std::max(baseTimer_, (timer_ + 1 - config.num_batches_regularization())); - regularizer_->update(vecs, config, optimizer_->getLearningRate(), base, - timer_ + 1); + regularizer_->update( + vecs, config, optimizer_->getLearningRate(), base, timer_ + 1); } ParameterOptimizer::TraverseCallback @@ -53,7 +53,8 @@ OptimizerWithRegularizerEveryNumBatches::startCatchUpWith() const { if (baseTimer_ < timer_) { callbacks.emplace_back( - [this](const VectorPtr vecs[], const ParameterConfig& config, + [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { this->catchUpWith(vecs, config, sparseId); }); } @@ -61,11 +62,15 @@ OptimizerWithRegularizerEveryNumBatches::startCatchUpWith() const { } void OptimizerWithRegularizerEveryNumBatches::catchUpWith( - const VectorPtr vecs[], const ParameterConfig& config, + const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const { int32_t base = timer_ - timer_ % config.num_batches_regularization(); - regularizer_->update(vecs, config, optimizer_->getLearningRate(), - std::max(base, baseTimer_), timer_); + regularizer_->update(vecs, + config, + optimizer_->getLearningRate(), + std::max(base, baseTimer_), + timer_); } void OptimizerWithRegularizerSparse::init(size_t numRows, @@ -83,8 +88,11 @@ void OptimizerWithRegularizerSparse::update(const VectorPtr vecs[], optimizer_->update(vecs, config, sparseId); // para W(t0) -> W(t+1) CHECK_LT(sparseId, t0Vec_.size()); - regularizer_->update(vecs, config, optimizer_->getLearningRate(), - t0Vec_[sparseId], timer_ + 1); + regularizer_->update(vecs, + config, + optimizer_->getLearningRate(), + t0Vec_[sparseId], + timer_ + 1); t0Vec_[sparseId] = timer_ + 1; } @@ -98,7 +106,8 @@ OptimizerWithRegularizerSparse::startCatchUpWith() const { if (timer_ > 0) { callbacks.emplace_back( - [this](const VectorPtr vecs[], const ParameterConfig& config, + [this](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { this->catchUpWith(vecs, config, sparseId); }); } @@ -110,18 +119,20 @@ void OptimizerWithRegularizerSparse::catchUpWith(const VectorPtr vecs[], size_t sparseId) const { // para W(t0) -> W(t+1) CHECK_LT(sparseId, t0Vec_.size()); - regularizer_->update(vecs, config, optimizer_->getLearningRate(), - t0Vec_[sparseId], timer_); + regularizer_->update( + vecs, config, optimizer_->getLearningRate(), t0Vec_[sparseId], timer_); } // factory method to create instance of OptimizerWithRegularizer ParameterOptimizer* OptimizerWithRegularizer::create( - const OptimizationConfig& optConfig, const ParameterConfig& paraConfig, - bool isParameterSparse, bool inPserver) { + const OptimizationConfig& optConfig, + const ParameterConfig& paraConfig, + bool isParameterSparse, + bool inPserver) { ParameterOptimizer* optimizer = ParameterOptimizer::create(optConfig, inPserver); if (paraConfig.gradient_clipping_threshold() > 0.0f && - !dynamic_cast(optimizer)) { + !dynamic_cast(optimizer)) { optimizer = new OptimizerWithGradientClipping(optConfig, optimizer); } Regularizer* regularizer = @@ -157,23 +168,23 @@ ParameterOptimizer* OptimizerWithRegularizer::create( } // normal optimizer->setNoDecay(); - return new OptimizerWithRegularizerEveryNumBatches(optConfig, optimizer, - regularizer); + return new OptimizerWithRegularizerEveryNumBatches( + optConfig, optimizer, regularizer); } if (isParameterSparse) { - CHECK(paraConfig.momentum() == 0.0f) - << "Parameter cannot support momentum if it's sparse."; + CHECK(paraConfig.momentum() == 0.0f) + << "Parameter cannot support momentum if it's sparse."; optimizer->setNoDecay(); - return new OptimizerWithRegularizerSparse(optConfig, optimizer, - regularizer); + return new OptimizerWithRegularizerSparse( + optConfig, optimizer, regularizer); } // dense if (paraConfig.decay_rate_l1() == 0.0f || - dynamic_cast(optimizer)) { + dynamic_cast(optimizer)) { return optimizer; } CHECK(paraConfig.momentum() == 0.0f) - << "Parameter cannot support momentum if it use L1 decay."; + << "Parameter cannot support momentum if it use L1 decay."; optimizer->setNoDecay(); return new OptimizerWithRegularizer(optConfig, optimizer, regularizer); } diff --git a/paddle/parameter/OptimizerWithRegularizer.h b/paddle/parameter/OptimizerWithRegularizer.h index b8b2d5b84d..ebe23c7397 100644 --- a/paddle/parameter/OptimizerWithRegularizer.h +++ b/paddle/parameter/OptimizerWithRegularizer.h @@ -12,7 +12,6 @@ WITHOUT 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 "FirstOrderOptimizer.h" @@ -24,7 +23,8 @@ class OptimizerWithRegularizer : public ParameterOptimizer { public: static ParameterOptimizer* create(const OptimizationConfig& optConfig, const ParameterConfig& paraConfig, - bool isParameterSparse, bool inPserver); + bool isParameterSparse, + bool inPserver); OptimizerWithRegularizer(const OptimizationConfig& optConfig, ParameterOptimizer* optimizer, @@ -60,7 +60,8 @@ public: return optimizer_->needSpecialTraversal(config); } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const { optimizer_->update(vecs, config, sparseId); regularizer_->update(vecs, config, optimizer_->getLearningRate(), 0, 1); @@ -94,7 +95,8 @@ public: baseTimer_ = 0; } - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const { optimizer_->update(vecs, config, sparseId); } @@ -103,7 +105,8 @@ public: const ParameterConfig& config) const; void doTraversal(const VectorPtr vecs[], const ParameterConfig& config) const; - void catchUpWith(const VectorPtr vecs[], const ParameterConfig& config, + void catchUpWith(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; virtual TraverseCallback startCatchUpWith() const; @@ -130,9 +133,11 @@ public: virtual void init(size_t numRows, const ParameterConfig* config); - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; - void catchUpWith(const VectorPtr vecs[], const ParameterConfig& config, + void catchUpWith(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) const; virtual TraverseCallback startCatchUpWith() const; virtual void finishCatchUpWith() { diff --git a/paddle/parameter/ParallelParameter.cpp b/paddle/parameter/ParallelParameter.cpp index 19cbdab1c8..99b20a59ca 100644 --- a/paddle/parameter/ParallelParameter.cpp +++ b/paddle/parameter/ParallelParameter.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include @@ -152,7 +151,8 @@ void SyncParameter::minorUpdate(real learnRate) { gradSem_->post(); } -AsyncParameter::AsyncParameter(TrainerRole role, int asyncCount, +AsyncParameter::AsyncParameter(TrainerRole role, + int asyncCount, ParameterPtr localParam) : ParallelParameter(role, localParam) { asyncCount_ = asyncCount; diff --git a/paddle/parameter/ParallelParameter.h b/paddle/parameter/ParallelParameter.h index 882033af63..2b65321fe2 100644 --- a/paddle/parameter/ParallelParameter.h +++ b/paddle/parameter/ParallelParameter.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -47,17 +46,17 @@ const int UPDATE_TYPE_NUM = 32; * TrainRole denotes the role of current training, different roles have * different jobs. * - * control, major, minor are three kinds of role to support mutiple GPUs + * control, major, minor are three kinds of role to support mutiple GPUs * parallel SGD training. SM on GPU card has two groups, each group * consist of a major and a minor. * * @param single single GPU card single thread training. - * + * * * @param control current parameter updates via control role, * not participate in real training. control role is - * responsible for merging all major's gradient and - * update parameter value. + * responsible for merging all major's gradient and + * update parameter value. * * @param major major role paticipates in real training, when local * gradient is ready, merge its corresponding minor's @@ -83,7 +82,8 @@ typedef void (ParallelParameter::*UpdateFunction)(real learnRate); class ParallelParameter { public: - static ParallelParameterPtr create(TrainerRole role, ParameterPtr localParam, + static ParallelParameterPtr create(TrainerRole role, + ParameterPtr localParam, int asyncCount = 1); ParallelParameter(TrainerRole role, ParameterPtr localParam) { @@ -135,7 +135,7 @@ protected: }; /** - * this class is designed for multi-threading training. + * this class is designed for multi-threading training. * * "Synchronous" means multiple GPUs calculate 1/4 mini-Batch, * but will get only one gradient @@ -209,14 +209,14 @@ public: * When asynchronous training, update strategy including slave and master. * * slave: If in range asyncCount, adopting self-update method. - * If beyond asyncCount, waiting for master to update. + * If beyond asyncCount, waiting for master to update. */ void slaveUpdate(real learnRate); /** * When asynchronous training, update strategy including slave and master. * - * master: it only polls slaves, do not training data. + * master: it only polls slaves, do not training data. * If slave's gradient is ready, fetch it. * Update master's parameter, then copy it into * corresponding slave. @@ -227,7 +227,7 @@ public: private: /** * When asynchronous training, every aysnc trainer needs to - * accumulate a number of batch gradient. + * accumulate a number of batch gradient. * * gradientAccum_ is used to save the sum of gradients. */ diff --git a/paddle/parameter/Parameter.cpp b/paddle/parameter/Parameter.cpp index 64d72ae740..7e37bf225b 100644 --- a/paddle/parameter/Parameter.cpp +++ b/paddle/parameter/Parameter.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include "paddle/math/MathUtils.h" #include "AverageOptimizer.h" @@ -27,11 +26,13 @@ limitations under the License. */ #include "hl_gpu.h" #include "paddle/utils/CommandLineParser.h" -P_DEFINE_int32(enable_grad_share, (100 * 1024 * 1024), +P_DEFINE_int32(enable_grad_share, + (100 * 1024 * 1024), "threshold for enable gradient parameter share for batch " "multi-cpu training"); P_DEFINE_int32( - grad_share_block_num, 64, + grad_share_block_num, + 64, "block number of gradient parameter share for batch multi-cpu training"); namespace paddle { @@ -95,13 +96,12 @@ void Parameter::randomize(const VectorPtr& value, real initial_max = config.initial_mean() + config.initial_std(); value->uniform(initial_min, initial_max); VLOG(1) << config.name() << ": initial_min=" << initial_min - << ", initial_max=" << initial_max; + << ", initial_max=" << initial_max; } else if (PARAMETER_INIT_NORMAL == config.initial_strategy()) { /* Initialize the parameters randomly */ value->randnorm(config.initial_mean(), config.initial_std()); - VLOG(1) << config.name() - << ": initial_mean=" << config.initial_mean() - << ", initial_std=" << config.initial_std(); + VLOG(1) << config.name() << ": initial_mean=" << config.initial_mean() + << ", initial_std=" << config.initial_std(); } else { LOG(FATAL) << "not supported initial_strategy: " << config.initial_strategy(); @@ -116,12 +116,18 @@ void Parameter::randomize() { if (config_.is_sparse()) { if (format_ == SPARSE_CSC) { sparseRand(intBufs_[PARAMETER_COLS]->getData(), - intBufs_[PARAMETER_ROWS]->getData(), config_.size(), - config_.dims(1) + 1, config_.dims(0), useGpu_); + intBufs_[PARAMETER_ROWS]->getData(), + config_.size(), + config_.dims(1) + 1, + config_.dims(0), + useGpu_); } else { sparseRand(intBufs_[PARAMETER_ROWS]->getData(), - intBufs_[PARAMETER_COLS]->getData(), config_.size(), - config_.dims(0) + 1, config_.dims(1), useGpu_); + intBufs_[PARAMETER_COLS]->getData(), + config_.size(), + config_.dims(0) + 1, + config_.dims(1), + useGpu_); } } setValueUpdated(); @@ -152,7 +158,7 @@ bool Parameter::isValueShared() { bool Parameter::isGradSparseUpdate() const { return !useGpu_ && !isStatic() && - (config_.sparse_update() || config_.sparse_remote_update()); + (config_.sparse_update() || config_.sparse_remote_update()); } void Parameter::setMat(ParameterType pType, int matType) { @@ -180,30 +186,42 @@ void Parameter::setMat(ParameterType pType, int matType) { CHECK_EQ(width + 1, intBufs_[PARAMETER_COLS]->getSize()); CHECK_EQ(size, intBufs_[PARAMETER_ROWS]->getSize()); } - mats_[pType] = Matrix::createSparseMatrix( - bufs_[pType]->getData(), intBufs_[PARAMETER_ROWS]->getData(), - intBufs_[PARAMETER_COLS]->getData(), height, width, - bufs_[pType]->getSize(), FLOAT_VALUE, format_, false, useGpu_); + mats_[pType] = + Matrix::createSparseMatrix(bufs_[pType]->getData(), + intBufs_[PARAMETER_ROWS]->getData(), + intBufs_[PARAMETER_COLS]->getData(), + height, + width, + bufs_[pType]->getSize(), + FLOAT_VALUE, + format_, + false, + useGpu_); } } else if (matType == MAT_NORMAL_SHARED) { CHECK_EQ(height * width, bufs_[pType]->getSize()); size_t blockNum = 0; CHECK(isGradShared(&blockNum)); mats_[pType] = std::make_shared( - blockNum, std::dynamic_pointer_cast( - bufs_[pType]->getMemoryHandle()), - height, width); + blockNum, + std::dynamic_pointer_cast( + bufs_[pType]->getMemoryHandle()), + height, + width); } else if (matType == MAT_VALUE_SHARED) { CHECK_EQ(height * width, bufs_[pType]->getSize()); mats_[pType] = std::make_shared( std::dynamic_pointer_cast( - bufs_[pType]->getMemoryHandle()), height, width); + bufs_[pType]->getMemoryHandle()), + height, + width); } else if (matType == MAT_SPARSE_ROW_IDS) { CHECK_EQ(height * width, bufs_[pType]->getSize()); mats_[pType] = std::make_shared( std::dynamic_pointer_cast( bufs_[pType]->getMemoryHandle()), - height, width); + height, + width); } else if (matType == MAT_SPARSE_ROW) { auto valueMat = std::dynamic_pointer_cast(mats_[PARAMETER_VALUE]); @@ -214,29 +232,31 @@ void Parameter::setMat(ParameterType pType, int matType) { << " MAT_SPARSE_ROW_PREFETCH or MAT_CACHE_ROW"; indexDict = valueMat->getIndexDictHandle(); } - auto mat = std::make_shared( - nullptr, height, width, - // grad share index with value - indexDict); + auto mat = + std::make_shared(nullptr, + height, + width, + // grad share index with value + indexDict); mats_[pType] = mat; } else if (matType == MAT_CACHE_ROW) { CHECK(isGradSparseUpdate()); - auto mat = std::make_shared( - height, width); + auto mat = std::make_shared(height, width); mats_[pType] = mat; } else if (matType == MAT_SPARSE_ROW_PREFETCH_FULL_SIZE || matType == MAT_SPARSE_ROW_PREFETCH) { auto mat = std::make_shared( bufs_[pType] ? std::dynamic_pointer_cast( - bufs_[pType]->getMemoryHandle()) : nullptr, - height, width, + bufs_[pType]->getMemoryHandle()) + : nullptr, + height, + width, nullptr, // indexDictHandle getGlobalSyncThreadPool()); mats_[pType] = mat; } else if (matType == MAT_SPARSE_ROW_AUTO_GROW) { CHECK(isGradSparseUpdate()); - mats_[pType] = std::make_shared( - height, width); + mats_[pType] = std::make_shared(height, width); } else { LOG(FATAL) << "Unsupported mat type" << matType; } @@ -252,30 +272,43 @@ SparsePrefetchRowCpuMatrix* Parameter::getPrefetchMatrix() { } void Parameter::updateWithGradient(real learningRate) { - sgdUpdate(learningRate * config_.learning_rate(), config_.momentum(), - config_.decay_rate(), bufs_[PARAMETER_VALUE].get(), - bufs_[PARAMETER_GRADIENT].get(), bufs_[PARAMETER_MOMENTUM].get()); + sgdUpdate(learningRate * config_.learning_rate(), + config_.momentum(), + config_.decay_rate(), + bufs_[PARAMETER_VALUE].get(), + bufs_[PARAMETER_GRADIENT].get(), + bufs_[PARAMETER_MOMENTUM].get()); } -void Parameter::updateWithGradient(real learningRate, MatrixPtr gradMat, - IVectorPtr t0, int currentTime, bool fini) { +void Parameter::updateWithGradient(real learningRate, + MatrixPtr gradMat, + IVectorPtr t0, + int currentTime, + bool fini) { SparseRowCpuMatrix* sparseMat = dynamic_cast(gradMat.get()); CHECK(sparseMat); CHECK_EQ(config_.momentum(), 0.0f) << "not support momentum in sparse input sgd"; bool useL1 = (config_.decay_rate_l1() != 0.0f); - sparseMat->sgdUpdate(*bufs_[PARAMETER_VALUE], *t0, - learningRate * config_.learning_rate(), currentTime, + sparseMat->sgdUpdate(*bufs_[PARAMETER_VALUE], + *t0, + learningRate * config_.learning_rate(), + currentTime, useL1 ? config_.decay_rate_l1() : config_.decay_rate(), - useL1, fini); + useL1, + fini); } -void Parameter::updateWithGradient(real learningRate, VectorPtr gradVec, +void Parameter::updateWithGradient(real learningRate, + VectorPtr gradVec, bool normalUpdate) { if (normalUpdate) { - sgdUpdate(learningRate * config_.learning_rate(), config_.momentum(), - config_.decay_rate(), bufs_[PARAMETER_VALUE].get(), gradVec.get(), + sgdUpdate(learningRate * config_.learning_rate(), + config_.momentum(), + config_.decay_rate(), + bufs_[PARAMETER_VALUE].get(), + gradVec.get(), bufs_[PARAMETER_MOMENTUM].get()); } else { size_t size = gradVec->getSize(); @@ -361,7 +394,7 @@ bool Parameter::load(const std::string& filename) { return true; } LOG(FATAL) << "unsupported load_missing_parameter_strategy: " - << FLAGS_load_missing_parameter_strategy; + << FLAGS_load_missing_parameter_strategy; return false; } return load(fs); @@ -372,8 +405,8 @@ bool Parameter::load(std::istream& s) { Header header; CHECK(s.read(reinterpret_cast(&header), sizeof(header))) << "Fail to read parameter " << getName(); - CHECK_EQ(header.version, kFormatVersion) - << "Incorrect format version: " << header.version; + CHECK_EQ(header.version, kFormatVersion) << "Incorrect format version: " + << header.version; CHECK_EQ(header.size, getSize()) << "The size (" << header.size << ") in the file does not match the size " << "(" << getSize() << ") of the parameter: " << getName(); @@ -382,7 +415,7 @@ bool Parameter::load(std::istream& s) { CHECK(s.read(reinterpret_cast(vec.getData()), header.size * sizeof(real))); - auto & tmp = *bufs_[PARAMETER_VALUE].get(); + auto& tmp = *bufs_[PARAMETER_VALUE].get(); if (typeid(tmp) == typeid(GpuVector)) { bufs_[PARAMETER_VALUE]->copyFrom(vec); } @@ -393,7 +426,11 @@ bool Parameter::load(std::istream& s) { auto height = config_.dims(0); auto width = config_.dims(1); auto mat = Matrix::create(vec.getData(), height, width); - CpuSparseMatrix sparseMat(height, width, 0, FLOAT_VALUE, format_, + CpuSparseMatrix sparseMat(height, + width, + 0, + FLOAT_VALUE, + format_, /*trans*/ false); sparseMat.copyFrom(*mat, HPPL_STREAM_DEFAULT); auto nnz = sparseMat.getElementCnt(); @@ -423,11 +460,11 @@ bool Parameter::load(std::istream& s) { s.read(reinterpret_cast(rows.getData()), rowSize * sizeof(int))); CHECK( s.read(reinterpret_cast(cols.getData()), colSize * sizeof(int))); - auto & paramRows = *intBufs_[PARAMETER_ROWS].get(); + auto& paramRows = *intBufs_[PARAMETER_ROWS].get(); if (typeid(paramRows) == typeid(GpuIVector)) { intBufs_[PARAMETER_ROWS]->copyFrom(rows); } - auto & paramCols = *intBufs_[PARAMETER_COLS].get(); + auto& paramCols = *intBufs_[PARAMETER_COLS].get(); if (typeid(paramCols) == typeid(GpuIVector)) { intBufs_[PARAMETER_COLS]->copyFrom(cols); } @@ -457,8 +494,8 @@ void Parameter::exec(ExecFunc func) { func(this->getBufs()); } else { // multi thread VectorPtr* vecs = Parameter::getTlsTempBufs(); - auto interval = calcSplitArrayInterval(this->getSize(), (size_t)tid, - numThreads, 8LU /*for avx*/); + auto interval = calcSplitArrayInterval( + this->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); for (size_t i = 0; i < (size_t)NUM_PARAMETER_TYPES; ++i) { if (bufs_[i]) { vecs[i]->subVecFrom(*bufs_[i], interval); diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index ff251fe89f..1c159d669a 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -52,7 +51,6 @@ struct Segment { int64_t beginPos; // beginning position in the local value or grad buffer }; - class Parameter; typedef std::shared_ptr ParameterPtr; @@ -129,8 +127,7 @@ public: if (config_.dims_size() == 2) { if (matType == MAT_NORMAL || matType == MAT_NORMAL_SHARED || matType == MAT_SPARSE_ROW_PREFETCH_FULL_SIZE || - matType == MAT_VALUE_SHARED || - matType == MAT_SPARSE_ROW_IDS) { + matType == MAT_VALUE_SHARED || matType == MAT_SPARSE_ROW_IDS) { bufs_[type] = Vector::createParallelVector(config_.size(), useGpu_); bufs_[type]->zeroMem(); } else { @@ -161,7 +158,8 @@ public: } } - void enableSharedType(ParameterType type, VectorPtr vec, + void enableSharedType(ParameterType type, + VectorPtr vec, MatrixPtr mat = nullptr) { if (!bufs_[type] && !mats_[type]) { bufs_[type] = vec; @@ -235,13 +233,17 @@ public: * * @see SparseRowCpuMatrix::sgdUpdate for more information. */ - void updateWithGradient(real learningRate, MatrixPtr gradMat, IVectorPtr t0, - int currentTime, bool fini = false); + void updateWithGradient(real learningRate, + MatrixPtr gradMat, + IVectorPtr t0, + int currentTime, + bool fini = false); /** * This function is used to calculate multiple gpus, but only as a candidate */ - void updateWithGradient(real learningRate, VectorPtr grad, + void updateWithGradient(real learningRate, + VectorPtr grad, bool normalUpdate = true); /** diff --git a/paddle/parameter/ParameterOptimizer.cpp b/paddle/parameter/ParameterOptimizer.cpp index 164b50c4d2..2a71d6aee4 100644 --- a/paddle/parameter/ParameterOptimizer.cpp +++ b/paddle/parameter/ParameterOptimizer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #include diff --git a/paddle/parameter/ParameterOptimizer.h b/paddle/parameter/ParameterOptimizer.h index 8c76674340..21a148333c 100644 --- a/paddle/parameter/ParameterOptimizer.h +++ b/paddle/parameter/ParameterOptimizer.h @@ -12,7 +12,6 @@ WITHOUT 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 "LearningRateScheduler.h" @@ -32,8 +31,8 @@ namespace paddle { */ class ParameterOptimizer { public: - typedef std::function + typedef std::function TraverseCallback; public: @@ -69,35 +68,35 @@ public: (void)numSamplesProcessed; } - /** - * following hooks useful for sparse update, - * because the traversal in block costs. - * called by Trainer after update and before finishBatch - * e.g. Trainer call like this: - * - * @code - * startBatch(); - * if (dense) { - * update(blockVec); - * } else {//sparse - * for (row : rows_in_block) {update(rowVec)} - * } - * auto callback = needSpecialTraversal(); - * if (callback) { - * // do traverse, maybe multi-thread - * if (dense) { - * callback(); - * } else {//sparse - * for (row : all_rows_in_block) {callback();} - * } - * } - * finishBatch(); - * @endcode - * - * @return callback if need traverse, - * else return nullptr. - * It should be no state change. - */ + /** + * following hooks useful for sparse update, + * because the traversal in block costs. + * called by Trainer after update and before finishBatch + * e.g. Trainer call like this: + * + * @code + * startBatch(); + * if (dense) { + * update(blockVec); + * } else {//sparse + * for (row : rows_in_block) {update(rowVec)} + * } + * auto callback = needSpecialTraversal(); + * if (callback) { + * // do traverse, maybe multi-thread + * if (dense) { + * callback(); + * } else {//sparse + * for (row : all_rows_in_block) {callback();} + * } + * } + * finishBatch(); + * @endcode + * + * @return callback if need traverse, + * else return nullptr. + * It should be no state change. + */ virtual TraverseCallback needSpecialTraversal( const ParameterConfig& config) const { return nullptr; @@ -112,47 +111,48 @@ public: * with its gradient in PARAMETER_GRADIENT. sparseId is row id, * when sparseId set, update is sparse, each time one row. */ - virtual void update(const VectorPtr vecs[], const ParameterConfig& config, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId = -1LU) const = 0; - /** - * following hooks catch up with current time for sparse update, - * In the beginning, call startCatchUpWith() and check return. - * In the end, call finishCatchUpWith() to finish state. - * callback do the actual works, can call many times for sparse data. - * e.g. Trainer call like this: - * - * @code - * auto callback = startCatchUpWith(); - * if (callback) { - * // do catch up with, maybe multi-thread - * if (dense) { - * callback(); - * } else {//sparse - * for (row : rows_in_block) {callback();} - * } - * // finish catch up with, main thread - * finishCatchUpWith(); - * } - * @endcode - * - * @return callback if need catch up with, - * else return nullptr. - * It should be no state change. - */ + /** + * following hooks catch up with current time for sparse update, + * In the beginning, call startCatchUpWith() and check return. + * In the end, call finishCatchUpWith() to finish state. + * callback do the actual works, can call many times for sparse data. + * e.g. Trainer call like this: + * + * @code + * auto callback = startCatchUpWith(); + * if (callback) { + * // do catch up with, maybe multi-thread + * if (dense) { + * callback(); + * } else {//sparse + * for (row : rows_in_block) {callback();} + * } + * // finish catch up with, main thread + * finishCatchUpWith(); + * } + * @endcode + * + * @return callback if need catch up with, + * else return nullptr. + * It should be no state change. + */ virtual TraverseCallback startCatchUpWith() const { return nullptr; } virtual void finishCatchUpWith() {} - /** - * following two hooks used by averager, - * apply to final parameter value (PARAMETER_VALUE or PARAMETER_APPLY). - * - * restore() will restore orginal value if it apply to PARAMETER_VALUE. - * Caller must ensure it's catched up with current time before apply. - * - * Use returned callback same way as callback returned by - * ParameterOptimizer::needSpecialTraversal() - */ + /** + * following two hooks used by averager, + * apply to final parameter value (PARAMETER_VALUE or PARAMETER_APPLY). + * + * restore() will restore orginal value if it apply to PARAMETER_VALUE. + * Caller must ensure it's catched up with current time before apply. + * + * Use returned callback same way as callback returned by + * ParameterOptimizer::needSpecialTraversal() + */ virtual TraverseCallback apply() { return nullptr; } virtual TraverseCallback restore() { return nullptr; } @@ -180,7 +180,8 @@ protected: static TraverseCallback composeCallbacks( const TraverseCallbackVec& callbacks) { if (callbacks.size() > 1LU) { - return [callbacks](const VectorPtr vecs[], const ParameterConfig& config, + return [callbacks](const VectorPtr vecs[], + const ParameterConfig& config, size_t sparseId) { for (auto callback : callbacks) { callback(vecs, config, sparseId); diff --git a/paddle/parameter/ParameterUpdateFunctions.cpp b/paddle/parameter/ParameterUpdateFunctions.cpp index 679e3bf89b..510ec5bf48 100644 --- a/paddle/parameter/ParameterUpdateFunctions.cpp +++ b/paddle/parameter/ParameterUpdateFunctions.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Logging.h" #ifdef __AVX__ #include @@ -23,8 +22,13 @@ limitations under the License. */ namespace paddle { -void sgdUpdateCpu(real learningRate, real momentum, real decayRate, size_t size, - real* value, const real* grad, real* momentumVec) { +void sgdUpdateCpu(real learningRate, + real momentum, + real decayRate, + size_t size, + real* value, + const real* grad, + real* momentumVec) { decayRate *= learningRate; for (size_t i = 0; i < size; ++i) { momentumVec[i] = momentum * momentumVec[i] - learningRate * grad[i] - @@ -33,8 +37,12 @@ void sgdUpdateCpu(real learningRate, real momentum, real decayRate, size_t size, } } -void sgdUpdate(real learningRate, real momentum, real decayRate, Vector* value, - Vector* grad, Vector* momentumVec) { +void sgdUpdate(real learningRate, + real momentum, + real decayRate, + Vector* value, + Vector* grad, + Vector* momentumVec) { size_t size = value->getSize(); real* val = value->getData(); real* grd = grad->getData(); @@ -48,8 +56,12 @@ void sgdUpdate(real learningRate, real momentum, real decayRate, Vector* value, } } -void sgdUpdateAvx(float learningRate, float momentum, float decayRate, - size_t size, float* value, const float* _grad, +void sgdUpdateAvx(float learningRate, + float momentum, + float decayRate, + size_t size, + float* value, + const float* _grad, float* momentumVec) { #ifdef __AVX__ float* grad = const_cast(_grad); // the gradient is not modified @@ -86,18 +98,36 @@ void sgdUpdateAvx(float learningRate, float momentum, float decayRate, std::function loopFun; learningRate *= -1; - lr = _mm256_set_ps(learningRate, learningRate, learningRate, learningRate, - learningRate, learningRate, learningRate, learningRate); + lr = _mm256_set_ps(learningRate, + learningRate, + learningRate, + learningRate, + learningRate, + learningRate, + learningRate, + learningRate); if (0 != momentum) { - mom = _mm256_set_ps(momentum, momentum, momentum, momentum, momentum, - momentum, momentum, momentum); + mom = _mm256_set_ps(momentum, + momentum, + momentum, + momentum, + momentum, + momentum, + momentum, + momentum); } decayRate *= learningRate; if (0 != decayRate) { - dr = _mm256_set_ps(decayRate, decayRate, decayRate, decayRate, decayRate, - decayRate, decayRate, decayRate); + dr = _mm256_set_ps(decayRate, + decayRate, + decayRate, + decayRate, + decayRate, + decayRate, + decayRate, + decayRate); } auto gradMulFun = [&](void) { diff --git a/paddle/parameter/ParameterUpdateFunctions.h b/paddle/parameter/ParameterUpdateFunctions.h index 59eb25656e..2d98030bd2 100644 --- a/paddle/parameter/ParameterUpdateFunctions.h +++ b/paddle/parameter/ParameterUpdateFunctions.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/TypeDefs.h" @@ -31,14 +30,27 @@ namespace paddle { * momentum = 0 or decayRate = 0 are specially handled to avoid unnecessary * computation. */ -void sgdUpdate(real learningRate, real momentum, real decayRate, Vector* value, - Vector* grad, Vector* momentumVec); - -void sgdUpdateCpu(real learningRate, real momentum, real decayRate, size_t size, - real* value, const real* grad, real* momentumVec); - -void sgdUpdateAvx(float learningRate, float momentum, float decayRate, - size_t size, float* value, const float* grad, +void sgdUpdate(real learningRate, + real momentum, + real decayRate, + Vector* value, + Vector* grad, + Vector* momentumVec); + +void sgdUpdateCpu(real learningRate, + real momentum, + real decayRate, + size_t size, + real* value, + const real* grad, + real* momentumVec); + +void sgdUpdateAvx(float learningRate, + float momentum, + float decayRate, + size_t size, + float* value, + const float* grad, float* momentumVec); } // namespace paddle diff --git a/paddle/parameter/ParameterUpdaterBase.cpp b/paddle/parameter/ParameterUpdaterBase.cpp index e3f1d54037..e706742053 100644 --- a/paddle/parameter/ParameterUpdaterBase.cpp +++ b/paddle/parameter/ParameterUpdaterBase.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include "paddle/utils/Logging.h" #include "ParameterUpdaterBase.h" diff --git a/paddle/parameter/ParameterUpdaterBase.h b/paddle/parameter/ParameterUpdaterBase.h index f16e183515..ffd2980261 100644 --- a/paddle/parameter/ParameterUpdaterBase.h +++ b/paddle/parameter/ParameterUpdaterBase.h @@ -12,7 +12,6 @@ WITHOUT 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 "Parameter.h" diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 02a352920c..7d85a32c0c 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterUpdaterHook.h" #include @@ -155,7 +154,8 @@ private: std::hash intHasher_; }; -static WeakKVCache, IParameterUpdaterHook, +static WeakKVCache, + IParameterUpdaterHook, StringIntPairHasher> g_hookCache_; /** diff --git a/paddle/parameter/ParameterUpdaterHook.h b/paddle/parameter/ParameterUpdaterHook.h index 1c132a7338..553282bcaa 100644 --- a/paddle/parameter/ParameterUpdaterHook.h +++ b/paddle/parameter/ParameterUpdaterHook.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include diff --git a/paddle/parameter/Regularizer.cpp b/paddle/parameter/Regularizer.cpp index bc7de3ca04..a9bddc1596 100644 --- a/paddle/parameter/Regularizer.cpp +++ b/paddle/parameter/Regularizer.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include "paddle/utils/Flags.h" #include "Regularizer.h" @@ -21,8 +20,9 @@ namespace paddle { Regularizer* Regularizer::get(const std::vector& types, const ParameterConfig& paraConfig) { - bool useLearningRateVec = std::find(types.begin(), types.end(), - PARAMETER_LEARNING_RATE) != types.end(); + bool useLearningRateVec = + std::find(types.begin(), types.end(), PARAMETER_LEARNING_RATE) != + types.end(); if (paraConfig.decay_rate_l1() > 0.0f && paraConfig.decay_rate() > 0.0f) { // use L1 and L2 if (useLearningRateVec) { diff --git a/paddle/parameter/Regularizer.h b/paddle/parameter/Regularizer.h index 8c9eb49ab6..5baaccc00d 100644 --- a/paddle/parameter/Regularizer.h +++ b/paddle/parameter/Regularizer.h @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterUpdaterBase.h" @@ -22,7 +21,8 @@ namespace paddle { // Regularizer function for parameter, e.g. L1/L2 class Regularizer { public: - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, real learningRate, // learningrate from optimizer int t0, // last occurence time int t) const = 0; // current time @@ -34,8 +34,11 @@ public: // L1 Regularizer, |w|_1 class L1Regularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL1(learningRate * paraConfig.learning_rate(), paraConfig.decay_rate_l1() * (t - t0)); } @@ -43,8 +46,11 @@ class L1Regularizer : public Regularizer { // L1 Lr Regularizer class L1LrRegularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL1(*vecs[PARAMETER_LEARNING_RATE], learningRate * paraConfig.learning_rate(), paraConfig.decay_rate_l1() * (t - t0)); @@ -53,8 +59,11 @@ class L1LrRegularizer : public Regularizer { // L2 Regularizer, |w|_2^2 class L2Regularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL2(learningRate * paraConfig.learning_rate(), paraConfig.decay_rate() * (t - t0)); } @@ -62,8 +71,11 @@ class L2Regularizer : public Regularizer { // L2 Lr Regularizer class L2LrRegularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL2(*vecs[PARAMETER_LEARNING_RATE], learningRate * paraConfig.learning_rate(), paraConfig.decay_rate() * (t - t0)); @@ -72,8 +84,11 @@ class L2LrRegularizer : public Regularizer { // L1 + L2 Regularizer, |w|_1 + |w|_2^2 class L1L2Regularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL1(learningRate * paraConfig.learning_rate(), paraConfig.decay_rate_l1() * (t - t0)); vecs[PARAMETER_VALUE]->applyL2(learningRate * paraConfig.learning_rate(), @@ -83,8 +98,11 @@ class L1L2Regularizer : public Regularizer { // L1 + L2 Lr Regularizer class L1L2LrRegularizer : public Regularizer { - virtual void update(const VectorPtr vecs[], const ParameterConfig& paraConfig, - real learningRate, int t0, int t) const { + virtual void update(const VectorPtr vecs[], + const ParameterConfig& paraConfig, + real learningRate, + int t0, + int t) const { vecs[PARAMETER_VALUE]->applyL1(*vecs[PARAMETER_LEARNING_RATE], learningRate * paraConfig.learning_rate(), paraConfig.decay_rate_l1() * (t - t0)); diff --git a/paddle/parameter/Weight.cpp b/paddle/parameter/Weight.cpp index ed02355c01..c138010607 100644 --- a/paddle/parameter/Weight.cpp +++ b/paddle/parameter/Weight.cpp @@ -60,14 +60,20 @@ Weight::Weight(size_t height, size_t width, ParameterPtr param, size_t offset) { // weight_ if (vPtr) { - weight_ = Matrix::create(vPtr->getData() + offset, height, width, - /* trans */ false, param->useGpu()); + weight_ = Matrix::create(vPtr->getData() + offset, + height, + width, + /* trans */ false, + param->useGpu()); } // weightGrad if (gPtr) { - weightGrad_ = Matrix::create(gPtr->getData() + offset, height, width, - /* trans */ false, param->useGpu()); + weightGrad_ = Matrix::create(gPtr->getData() + offset, + height, + width, + /* trans */ false, + param->useGpu()); } parameter_ = param; diff --git a/paddle/parameter/tests/test_common.cpp b/paddle/parameter/tests/test_common.cpp index 1a22abf7cf..1a64fe3352 100644 --- a/paddle/parameter/tests/test_common.cpp +++ b/paddle/parameter/tests/test_common.cpp @@ -12,7 +12,6 @@ WITHOUT 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 @@ -38,8 +37,8 @@ protected: CommonTest() : testStat_("test") {} virtual ~CommonTest() {} virtual void SetUp() { - const size_t buffSize[] = {100, 128, 500, 1024, - 4096, 10240, 102400, 1000000}; + const size_t buffSize[] = { + 100, 128, 500, 1024, 4096, 10240, 102400, 1000000}; sizeVec_.resize(8); memcpy(&sizeVec_[0], &buffSize[0], 8 * sizeof(size_t)); valueUint_.resize(4); @@ -54,8 +53,10 @@ protected: learningRate_ = 1.0; } - void test_sgdUpadate(real* gradientBuffer, real* valueBuffer, - real* momentumBuffer, size_t size); + void test_sgdUpadate(real* gradientBuffer, + real* valueBuffer, + real* momentumBuffer, + size_t size); virtual void TreaDown() { LOG(INFO) << "All Test Finished."; } @@ -66,8 +67,10 @@ protected: StatSet testStat_; }; -void CommonTest::test_sgdUpadate(real* gradientBuffer, real* valueBuffer, - real* momentumBuffer, size_t size) { +void CommonTest::test_sgdUpadate(real* gradientBuffer, + real* valueBuffer, + real* momentumBuffer, + size_t size) { // sgdUpdateAvx has no double version yet #if defined(__AVX__) && !defined(PADDLE_TYPE_DOUBLE) real valueSum1 = 0, valueSum2 = 0, momSum1 = 0, momSum2 = 0; @@ -85,8 +88,13 @@ void CommonTest::test_sgdUpadate(real* gradientBuffer, real* valueBuffer, gettimeofday(&t, NULL); } REGISTER_TIMER("avxTimer", 0); - sgdUpdateAvx(learningRate_, arg.first, arg.second, size, valueBuffer, - gradientBuffer, momentumBuffer); + sgdUpdateAvx(learningRate_, + arg.first, + arg.second, + size, + valueBuffer, + gradientBuffer, + momentumBuffer); } for (size_t i = 0; i < size; i++) { valueSum1 += valueBuffer[i]; @@ -98,8 +106,13 @@ void CommonTest::test_sgdUpadate(real* gradientBuffer, real* valueBuffer, } { REGISTER_TIMER("cpuTimer", 0); - sgdUpdateCpu(learningRate_, arg.first, arg.second, size, valueTmp, - gradTmp, momentumTmp); + sgdUpdateCpu(learningRate_, + arg.first, + arg.second, + size, + valueTmp, + gradTmp, + momentumTmp); } for (size_t i = 0; i < size; i++) { valueSum2 += valueTmp[i]; @@ -126,10 +139,10 @@ TEST_F(CommonTest, sgdUpdate) { for (auto& size : sizeVec_) { real *gradientBuffer, *valueBuffer, *momentumBuffer; CHECK_EQ(posix_memalign((void**)&gradientBuffer, 32, sizeof(real) * size), - 0); + 0); CHECK_EQ(posix_memalign((void**)&valueBuffer, 32, sizeof(real) * size), 0); CHECK_EQ(posix_memalign((void**)&momentumBuffer, 32, sizeof(real) * size), - 0); + 0); for (size_t i = 0; i < size; i++) { gradientBuffer[i] = 1.0; @@ -141,7 +154,8 @@ TEST_F(CommonTest, sgdUpdate) { << "-------------------------"; test_sgdUpadate(&gradientBuffer[alignHeader[i]], &valueBuffer[alignHeader[i]], - &momentumBuffer[alignHeader[i]], size - alignHeader[i]); + &momentumBuffer[alignHeader[i]], + size - alignHeader[i]); } free(gradientBuffer); free(valueBuffer); @@ -173,16 +187,16 @@ TEST_F(CommonTest, barrierStat) { SyncThreadPool pool(threadNum); -#define TEST_BARRIER_RANDOM(statName, numConnThreads, ...) \ - pool.exec([&](int tid, size_t numThreads) { \ - struct timeval time; \ - gettimeofday(&time, nullptr); \ - uint64_t usec = timeToMicroSecond(time); \ - std::srand(usec); \ - auto value = std::rand() % 100000; \ - usleep(value); \ - REGISTER_SLOW_NODES_PROBE(globalStat, statName, numConnThreads, tid, \ - __VA_ARGS__); \ +#define TEST_BARRIER_RANDOM(statName, numConnThreads, ...) \ + pool.exec([&](int tid, size_t numThreads) { \ + struct timeval time; \ + gettimeofday(&time, nullptr); \ + uint64_t usec = timeToMicroSecond(time); \ + std::srand(usec); \ + auto value = std::rand() % 100000; \ + usleep(value); \ + REGISTER_SLOW_NODES_PROBE( \ + globalStat, statName, numConnThreads, tid, __VA_ARGS__); \ }); for (auto i = 0; i < 10; i++) { @@ -202,11 +216,11 @@ TEST_F(CommonTest, barrierStat) { globalStat.reset(); // use it to test accurate barrier gap -#define TEST_BARRIER(statName, numConnThreads, ...) \ - pool.exec([&](int tid, size_t numThreads) { \ - usleep(tid * 10000); \ - REGISTER_SLOW_NODES_PROBE(globalStat, statName, numConnThreads, tid, \ - __VA_ARGS__); \ +#define TEST_BARRIER(statName, numConnThreads, ...) \ + pool.exec([&](int tid, size_t numThreads) { \ + usleep(tid * 10000); \ + REGISTER_SLOW_NODES_PROBE( \ + globalStat, statName, numConnThreads, tid, __VA_ARGS__); \ }); for (auto i = 0; i < 10; i++) { diff --git a/paddle/pserver/BaseClient.cpp b/paddle/pserver/BaseClient.cpp index df4daca9bf..ff83970ab1 100644 --- a/paddle/pserver/BaseClient.cpp +++ b/paddle/pserver/BaseClient.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include "paddle/utils/Stat.h" diff --git a/paddle/pserver/BaseClient.h b/paddle/pserver/BaseClient.h index f1c4c9eb37..3a501172b7 100644 --- a/paddle/pserver/BaseClient.h +++ b/paddle/pserver/BaseClient.h @@ -62,7 +62,10 @@ public: /// send data to server, support only synchronize template - void putData(int clientId, SendDataType type, DataType* datas, size_t size, + void putData(int clientId, + SendDataType type, + DataType* datas, + size_t size, DataUpdateMode mode) { synchronize(SYNC_DATA); sendData(clientId, type, mode, datas, size); @@ -71,16 +74,23 @@ public: } template - void putOwnData(int clientId, SendDataType type, DataType* datas, + void putOwnData(int clientId, + SendDataType type, + DataType* datas, size_t size) { putData(clientId, type, datas, size, DATA_UPDATE_MODE_SET_OWN); } template - void getAllData(int clientId, SendDataType type, DataType* datas, + void getAllData(int clientId, + SendDataType type, + DataType* datas, size_t size) { - sendData(clientId, type, DATA_UPDATE_MODE_GET_ALL, - reinterpret_cast(NULL), 0); + sendData(clientId, + type, + DATA_UPDATE_MODE_GET_ALL, + reinterpret_cast(NULL), + 0); recvData(); size_t dataOffset = 0; for (auto& recvMem : recvDataMems_) { @@ -100,7 +110,10 @@ public: * The results are saved in recvBuf of rootId client */ template - void reduce(DataType* sendBuf, DataType* recvBuf, size_t size, int clientId, + void reduce(DataType* sendBuf, + DataType* recvBuf, + size_t size, + int clientId, int rootId) { putOwnData(clientId, DATA_REDUCE_SUM, sendBuf, size); if (rootId == clientId) { @@ -147,8 +160,12 @@ protected: void finishThreads(); template - void prepareData(int clientId, SendDataType type, DataUpdateMode updateMode, - DataType* datas, size_t size, SendJob* sendJob) { + void prepareData(int clientId, + SendDataType type, + DataUpdateMode updateMode, + DataType* datas, + size_t size, + SendJob* sendJob) { sendJob->parallelDataRequests.resize(serviceNum_); sendJob->parallelInputIovs.resize(serviceNum_); for (int i = 0; i < serviceNum_; ++i) { @@ -192,8 +209,11 @@ protected: * synchronization in metric learning. */ template - void sendData(int clientId, SendDataType type, DataUpdateMode updateMode, - DataType* datas, size_t size) { + void sendData(int clientId, + SendDataType type, + DataUpdateMode updateMode, + DataType* datas, + size_t size) { SendJobPtr sendJob = std::make_shared(); prepareData(clientId, type, updateMode, datas, size, sendJob.get()); for (int i = 0; i < threadNum_; ++i) { @@ -210,7 +230,8 @@ protected: /// send request, and recv responses template - void multiCall(const char* funcName, const ProtoIn& request, + void multiCall(const char* funcName, + const ProtoIn& request, std::vector* responses) { responses->resize(clients_.size()); size_t numClients = clients_.size(); diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index ff2875fc70..1830170a16 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include #include @@ -32,19 +31,22 @@ limitations under the License. */ #include "RDMANetwork.h" /// quick ack can reduce the latency of small message -P_DEFINE_bool(small_messages, false, +P_DEFINE_bool(small_messages, + false, "if message size is small, recommend set it True to enable quick " "ack and no delay"); /// reasonable sock_send_buf_size can control the traffic injected into switch /// network. Injecting too many data into traffic could cause packets loss which /// cause long latency and degrade the efficiency of communication. -P_DEFINE_int32(sock_send_buf_size, 1024 * 1024 * 40, +P_DEFINE_int32(sock_send_buf_size, + 1024 * 1024 * 40, "restrict sock send buff size, can reduce network congestion if " "set carefully"); /// reasonable size can hold bursted packets and reduce packets loss -P_DEFINE_int32(sock_recv_buf_size, 1024 * 1024 * 40, +P_DEFINE_int32(sock_recv_buf_size, + 1024 * 1024 * 40, "restrict sock recv buff size"); namespace paddle { @@ -174,7 +176,8 @@ void SocketServer::tcpServer() { if (!addr_.empty()) { server = gethostbyname(addr_.c_str()); PCHECK(server) << "ERROR, no such host: " << addr_; - bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, + bcopy((char *)server->h_addr, + (char *)&serv_addr.sin_addr.s_addr, server->h_length); } else { serv_addr.sin_addr.s_addr = INADDR_ANY; @@ -347,29 +350,32 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { struct sockaddr_in serv_addr; struct hostent *server; - int errRet; // temp for gethostbyname_r + int errRet; // temp for gethostbyname_r /// Create a socket point int sockfd = socket(AF_INET, SOCK_STREAM, 0); PCHECK(sockfd >= 0) << "ERROR opening socket"; #if defined(__OSX__) || defined(__APPLE__) - server = getipnodebyname(serverAddr.c_str(), AF_INET, AI_DEFAULT, &errRet); - CHECK_NE(HOST_NOT_FOUND, errRet) - << "ERROR, no such host: " << serverAddr << " ret = " << errRet; - CHECK(server) << "getipnodebyname error!"; + server = getipnodebyname(serverAddr.c_str(), AF_INET, AI_DEFAULT, &errRet); + CHECK_NE(HOST_NOT_FOUND, errRet) << "ERROR, no such host: " << serverAddr + << " ret = " << errRet; + CHECK(server) << "getipnodebyname error!"; #else - struct hostent hostinfo; - char buf[1024]; // temp for gethostbyname_r - CHECK_EQ(0, gethostbyname_r(serverAddr.c_str(), &hostinfo, buf, sizeof(buf), - &server, &errRet)) - << "ERROR, no such host: " << serverAddr << " ret = " << errRet; - CHECK(server) << "gethostbyname_r error!"; + struct hostent hostinfo; + char buf[1024]; // temp for gethostbyname_r + CHECK_EQ( + 0, + gethostbyname_r( + serverAddr.c_str(), &hostinfo, buf, sizeof(buf), &server, &errRet)) + << "ERROR, no such host: " << serverAddr << " ret = " << errRet; + CHECK(server) << "gethostbyname_r error!"; #endif bzero((char *)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; - bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, + bcopy((char *)server->h_addr, + (char *)&serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(serverPort); @@ -421,7 +427,8 @@ void SocketClient::RdmaClient(const std::string &serverAddr, int serverPort) { * * @note responsible for building one connection to specified pserver port */ -SocketClient::SocketClient(const std::string &serverAddr, int serverPort, +SocketClient::SocketClient(const std::string &serverAddr, + int serverPort, enum ChannelType channelType) { if (channelType == F_RDMA) RdmaClient(serverAddr, serverPort); diff --git a/paddle/pserver/LightNetwork.h b/paddle/pserver/LightNetwork.h index 0d6d6bf6b7..b7d7bc7902 100644 --- a/paddle/pserver/LightNetwork.h +++ b/paddle/pserver/LightNetwork.h @@ -12,7 +12,6 @@ WITHOUT 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 "SocketChannel.h" @@ -39,9 +38,9 @@ class SocketWorker; * in child class of socketserver. */ class SocketServer : public Thread { - // rdmaCpu controls the cpu affinity of RDMA server daemon, - // which could benifit performance. rdmaCpu = -1 means TCP - // is used instead of RDMA transport. + // rdmaCpu controls the cpu affinity of RDMA server daemon, + // which could benifit performance. rdmaCpu = -1 means TCP + // is used instead of RDMA transport. public: SocketServer(const std::string& addr, int port, int rdmaCpu); ~SocketServer(); @@ -91,7 +90,6 @@ protected: bool stopping_; }; - /** * @brief class for holding one connection from one trainer * @@ -165,7 +163,8 @@ private: */ class SocketClient { public: - SocketClient(const std::string& serverAddr, int serverPort, + SocketClient(const std::string& serverAddr, + int serverPort, enum ChannelType channelType); SocketChannel* getChannel() { return channel_.get(); } diff --git a/paddle/pserver/ParameterClient2.cpp b/paddle/pserver/ParameterClient2.cpp index d0e5352c82..28cc0ae2dd 100644 --- a/paddle/pserver/ParameterClient2.cpp +++ b/paddle/pserver/ParameterClient2.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterClient2.h" @@ -27,7 +26,8 @@ P_DEFINE_int32(parallel_thread_num, 1, "Thread number for parameter send"); namespace paddle { template -void copyToRepeatedField(google::protobuf::RepeatedField* dest, const T* src, +void copyToRepeatedField(google::protobuf::RepeatedField* dest, + const T* src, size_t size) { dest->Clear(); dest->Reserve(size); @@ -46,11 +46,10 @@ void copyToRepeatedField(const std::vector& src, ParameterClient2::ParameterClient2(bool separate, int port, int numPorts) : BaseClient(separate, numPorts), port_(port) { #ifndef PADDLE_DISABLE_TIMER - forwardbackwordTime_ = 0; + forwardbackwordTime_ = 0; #endif } - int ParameterClient2::calcParameterBlockSize( const std::vector& parameters, size_t serviceNum) { size_t totalSize = 0; @@ -89,8 +88,8 @@ bool ParameterClient2::init(const std::vector& parameters) { for (auto& para : parameters) { /// set block size for each parameter para->getConfig().set_parameter_block_size( - para->getConfig().sparse_remote_update() ? - para->getConfig().dims(1) : denseBlockSize); + para->getConfig().sparse_remote_update() ? para->getConfig().dims(1) + : denseBlockSize); } for (auto& para : parameters) { @@ -107,7 +106,7 @@ bool ParameterClient2::init(const std::vector& parameters) { allSegments_.push_back(segments); if (para->getConfig().sparse_remote_update()) { CHECK_EQ(para->getConfig().parameter_block_size(), - para->getConfig().dims(1)) + para->getConfig().dims(1)) << "For sparse remote update parameter," << " block size is the width of each row."; } @@ -152,7 +151,8 @@ void ParameterClient2::destroy() { clients_.clear(); } -void ParameterClient2::sendParallel(int tid, size_t numThreads, +void ParameterClient2::sendParallel(int tid, + size_t numThreads, ParameterType recvParameterType) { int numMyClients = divup(serviceNum_ - tid, numThreads); @@ -163,7 +163,8 @@ void ParameterClient2::sendParallel(int tid, size_t numThreads, /// at the same time so that they will not flood data to the same /// pserver. i = calcClientId(i, serviceNum_); - clients_[i].send("sendParameter", sendJob_.parallelRequests[i], + clients_[i].send("sendParameter", + sendJob_.parallelRequests[i], sendJob_.parallelInputIovs[i]); /// clear large structure @@ -204,10 +205,15 @@ void ParameterClient2::sendParallel(int tid, size_t numThreads, } void ParameterClient2::prepareSendData( - ParameterUpdateMode updateMode, ParameterType parameterType, - const std::vector& parameterSegments, int64_t numSamples, - real cost, bool sendBackParameter, ParameterType sendBackParameterType, - BatchStatus batchStatus, SendJob* sendJob) { + ParameterUpdateMode updateMode, + ParameterType parameterType, + const std::vector& parameterSegments, + int64_t numSamples, + real cost, + bool sendBackParameter, + ParameterType sendBackParameterType, + BatchStatus batchStatus, + SendJob* sendJob) { sendJob->parallelRequests.resize(serviceNum_); sendJob->parallelInputIovs.resize(serviceNum_); @@ -247,11 +253,11 @@ void ParameterClient2::prepareSendData( const auto prefetchMat = parameter->getPrefetchMatrix(); CHECK(prefetchMat != nullptr) << "prefetchMat is nullptr"; auto sendMat = dynamic_cast( - parameter->getMat(parameterType).get()); + parameter->getMat(parameterType).get()); CHECK(sendMat != nullptr) << "sendMat is nullptr"; syncThreadPool_->exec([&](int tid, size_t numThreads) { - const auto &localIndices = prefetchMat->getLocalIndices(); + const auto& localIndices = prefetchMat->getLocalIndices(); /// num of sparse rows size_t nLocalBlocks = localIndices.size(); uint64_t beginDim = 0; @@ -278,17 +284,17 @@ void ParameterClient2::prepareSendData( if (sendingPara) { sendJob->parallelInputIovs[serverId].push_back( - {sendMat->getLocalRow(row), sizeof(real) * (size_t) blockSize}); + {sendMat->getLocalRow(row), sizeof(real) * (size_t)blockSize}); /// detect sparse parameter distribution sparseDistribution_->probeDistribution(serverId, - sizeof(real) * blockSize); + sizeof(real) * blockSize); } } }); } else { /// parameter set for dense and sparse - real* buf = sendingPara ? - parameter->getBuf(parameterType)->getPoint(0) : nullptr; + real* buf = + sendingPara ? parameter->getBuf(parameterType)->getPoint(0) : nullptr; uint64_t endDim = 0; for (uint64_t beginDim = 0; beginDim < paraSize; beginDim = endDim) { endDim = std::min(beginDim + blockSize, paraSize); @@ -302,8 +308,8 @@ void ParameterClient2::prepareSendData( block->set_begin_pos(beginDim); block->set_block_size(endDim - beginDim); if (buf) { - sendJob->parallelInputIovs[serverId].push_back({buf + beginDim, - sizeof(real) * ((size_t) (endDim - beginDim))}); + sendJob->parallelInputIovs[serverId].push_back( + {buf + beginDim, sizeof(real) * ((size_t)(endDim - beginDim))}); } } } @@ -313,13 +319,23 @@ void ParameterClient2::prepareSendData( } void ParameterClient2::sendAndReceiveParameter( - ParameterUpdateMode updateMode, ParameterType parameterType, - const std::vector& parameterSegments, int64_t numSamples, - real cost, bool sendBackParameter, ParameterType sendBackParameterType, + ParameterUpdateMode updateMode, + ParameterType parameterType, + const std::vector& parameterSegments, + int64_t numSamples, + real cost, + bool sendBackParameter, + ParameterType sendBackParameterType, ParameterType recvParameterType) { - prepareSendData(updateMode, parameterType, parameterSegments, numSamples, - cost, sendBackParameter, sendBackParameterType, - /*batchStatus = */ BATCH_START_AND_FINISH, &sendJob_); + prepareSendData(updateMode, + parameterType, + parameterSegments, + numSamples, + cost, + sendBackParameter, + sendBackParameterType, + /*batchStatus = */ BATCH_START_AND_FINISH, + &sendJob_); syncThreadPool_->exec([&](int tid, size_t numThreads) { this->sendParallel(tid, numThreads, recvParameterType); @@ -327,12 +343,22 @@ void ParameterClient2::sendAndReceiveParameter( } void ParameterClient2::sendParameter( - ParameterUpdateMode updateMode, ParameterType parameterType, - const std::vector& parameterSegments, int64_t numSamples, - real cost, bool sendBackParameter, BatchStatus batchStatus) { + ParameterUpdateMode updateMode, + ParameterType parameterType, + const std::vector& parameterSegments, + int64_t numSamples, + real cost, + bool sendBackParameter, + BatchStatus batchStatus) { SendJobPtr sendJob = std::make_shared(); - prepareSendData(updateMode, parameterType, parameterSegments, numSamples, - cost, sendBackParameter, PARAMETER_VALUE, batchStatus, + prepareSendData(updateMode, + parameterType, + parameterSegments, + numSamples, + cost, + sendBackParameter, + PARAMETER_VALUE, + batchStatus, sendJob.get()); for (int i = 0; i < threadNum_; i++) { @@ -360,10 +386,12 @@ void ParameterClient2::send(int threadId) { /// pserver. i = calcClientId(i, serviceNum_); if (recvJob->parallelRequests.size()) { - clients_[i].send("sendParameter", recvJob->parallelRequests[i], + clients_[i].send("sendParameter", + recvJob->parallelRequests[i], recvJob->parallelInputIovs[i]); } else { - clients_[i].send("sendData", recvJob->parallelDataRequests[i], + clients_[i].send("sendData", + recvJob->parallelDataRequests[i], recvJob->parallelInputIovs[i]); } } @@ -586,12 +614,13 @@ void PreparedOperations::addOperationHelper(Operation* op, CpuMatrixPtr mat) { ProtoMatrix& pmat = *op->add_matrices(); pmat.set_num_cols(mat->getWidth()); pmat.set_num_rows(mat->getHeight()); - copyToRepeatedField(pmat.mutable_values(), mat->getData(), - pmat.num_cols() * pmat.num_rows()); + copyToRepeatedField( + pmat.mutable_values(), mat->getData(), pmat.num_cols() * pmat.num_rows()); } void ParameterClient2::doOperation(PreparedOperations& ops, - bool waitForGradient, bool sendBackGradient, + bool waitForGradient, + bool sendBackGradient, bool releasePass) { std::vector responses; ops.request_.set_wait_for_gradient(waitForGradient); @@ -666,7 +695,8 @@ void ParameterClient2::doOperation(PreparedOperations& ops, CHECK_EQ(rmat->getWidth(), (size_t)mat.num_cols()); CpuMatrixPtr amat = std::make_shared(const_cast(mat.values().data()), - rmat->getHeight(), rmat->getWidth()); + rmat->getHeight(), + rmat->getWidth()); rmat->add(*amat); } } @@ -700,14 +730,17 @@ void ParameterClient2::vectorAddMult(PServerVector u, PServerVector v, real a) { doOperation(ops, false, false); } -void ParameterClient2::vectorAddMultInto(PServerVector u, PServerVector v, - PServerVector w, real a) { +void ParameterClient2::vectorAddMultInto(PServerVector u, + PServerVector v, + PServerVector w, + real a) { PreparedOperations ops; ops.addOperation(PSERVER_OP_au_bv_cw, v, w, u, (real)1, a, (real)0); doOperation(ops, false, false); } -void ParameterClient2::vectorScaleInto(PServerVector u, PServerVector v, +void ParameterClient2::vectorScaleInto(PServerVector u, + PServerVector v, real a) { PreparedOperations ops; ops.addOperation(PSERVER_OP_au_bv, v, u, a, (real)0); diff --git a/paddle/pserver/ParameterClient2.h b/paddle/pserver/ParameterClient2.h index 7a4085ad82..af8dd41ec4 100644 --- a/paddle/pserver/ParameterClient2.h +++ b/paddle/pserver/ParameterClient2.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -190,8 +189,8 @@ protected: }; struct ParameterSegments { - std::string name; // name of the parameter - size_t id; // id of the parameter + std::string name; // name of the parameter + size_t id; // id of the parameter }; /** @@ -225,7 +224,8 @@ public: * connections the parameter client maintains. */ ParameterClient2(bool separate = false, - int port = FLAGS_port, int numPorts = FLAGS_ports_num); + int port = FLAGS_port, + int numPorts = FLAGS_ports_num); ~ParameterClient2(); @@ -255,14 +255,14 @@ public: * client[recvParameterType] * @note Only parameterType will be sent. */ - void sendAndReceiveParameter( - ParameterUpdateMode updateMode, - ParameterType parameterType, - const std::vector& segments, - int64_t numSamples, - real cost, bool sendBackParameter, - ParameterType sendBackParameterType, - ParameterType recvParameterType); + void sendAndReceiveParameter(ParameterUpdateMode updateMode, + ParameterType parameterType, + const std::vector& segments, + int64_t numSamples, + real cost, + bool sendBackParameter, + ParameterType sendBackParameterType, + ParameterType recvParameterType); /** * @brief Sends all parameters to parameter servers, and receives the response @@ -276,8 +276,13 @@ public: bool sendBackParameter, ParameterType sendBackParameterType = PARAMETER_VALUE, ParameterType recvParameterType = PARAMETER_VALUE) { - sendAndReceiveParameter(updateMode, parameterType, allSegments_, numSamples, - cost, sendBackParameter, sendBackParameterType, + sendAndReceiveParameter(updateMode, + parameterType, + allSegments_, + numSamples, + cost, + sendBackParameter, + sendBackParameterType, recvParameterType); } @@ -302,29 +307,41 @@ public: void sendParameter(ParameterUpdateMode updateMode, ParameterType parameterType, const std::vector& segments, - int64_t numSamples, real cost, bool sendBackParameter, + int64_t numSamples, + real cost, + bool sendBackParameter, BatchStatus batchStatus); void recvParameter(); /** - * Sends all parameters to parameter servers, recvParameter() have to be invoked + * Sends all parameters to parameter servers, recvParameter() have to be + * invoked * afterwards. * * @note This function is non-blocking. This means that if parameter should * not changes between this call and recvParameter() */ void sendParameter(ParameterUpdateMode updateMode, - ParameterType parameterType, int64_t numSamples, real cost, - bool sendBackParameter, BatchStatus batchStatus) { - sendParameter(updateMode, parameterType, allSegments_, numSamples, cost, - sendBackParameter, batchStatus); + ParameterType parameterType, + int64_t numSamples, + real cost, + bool sendBackParameter, + BatchStatus batchStatus) { + sendParameter(updateMode, + parameterType, + allSegments_, + numSamples, + cost, + sendBackParameter, + batchStatus); } /// Get all parameters from parameter servers void getParameter(ParameterType recvParameterType = PARAMETER_VALUE, ParameterType sendBackParameterType = PARAMETER_VALUE) { - sendAndReceiveParameter(PSERVER_UPDATE_MODE_GET_PARAM, PARAMETER_VALUE, + sendAndReceiveParameter(PSERVER_UPDATE_MODE_GET_PARAM, + PARAMETER_VALUE, 0, // numSamples = 0 0, // cost = 0 true, // sendBackParameter = true @@ -341,12 +358,14 @@ public: 0, // numSamples = 0 0, // cost = 0 true, // sendBackParameter = true - sendBackParameterType, recvParameterType); + sendBackParameterType, + recvParameterType); } /// Set all parameters on parameter servers using the local parameters void setParameter() { - sendAndReceiveParameter(PSERVER_UPDATE_MODE_SET_PARAM, PARAMETER_VALUE, + sendAndReceiveParameter(PSERVER_UPDATE_MODE_SET_PARAM, + PARAMETER_VALUE, 0, // numSamples = 0 0, // cost = 0 false); // sendBackParameter = false @@ -356,7 +375,8 @@ public: * means do not sending local parameters */ void setParameterZero() { - sendAndReceiveParameter(PSERVER_UPDATE_MODE_SET_PARAM_ZERO, PARAMETER_VALUE, + sendAndReceiveParameter(PSERVER_UPDATE_MODE_SET_PARAM_ZERO, + PARAMETER_VALUE, 0, // numSamples = 0 0, // cost = 0 false); // sendBackParameter = false @@ -401,15 +421,18 @@ public: * @param[in] If true, and if all clients call waitPassFinish, signal all * clients finish the pass. */ - void doOperation(PreparedOperations& ops, bool waitForGradient, - bool sendBackParameter, bool releasePass = true); + void doOperation(PreparedOperations& ops, + bool waitForGradient, + bool sendBackParameter, + bool releasePass = true); /** * Set the configuration of pserver, including parameter config and * optimization config */ void setConfig(const OptimizationConfig& optConfig, - const std::string& saveDir = "", bool isSparseServer = false); + const std::string& saveDir = "", + bool isSparseServer = false); /// Return true if all pservers are in the given status bool inStatus(PServerStatus status); @@ -454,7 +477,9 @@ public: void vectorAddMult(PServerVector u, PServerVector v, real a); /// u = v + w * a - void vectorAddMultInto(PServerVector u, PServerVector v, PServerVector w, + void vectorAddMultInto(PServerVector u, + PServerVector v, + PServerVector w, real a); /// u = v * a void vectorScaleInto(PServerVector u, PServerVector v, real a); @@ -491,7 +516,8 @@ public: protected: template - void multiCall(const char* funcName, const ProtoIn& request, + void multiCall(const char* funcName, + const ProtoIn& request, std::vector* responses) { responses->resize(clients_.size()); size_t numClients = clients_.size(); @@ -511,10 +537,12 @@ private: * to all pservers. it is called under one SyncThreadPool. it * supports to use N thread to control M connections. the receiving * actions can be started until all sending action to all connections - * owned by current thread are finished. Different connections controlled + * owned by current thread are finished. Different connections + * controlled * by different threads can transfer data asynchronously. */ - void sendParallel(int tid, size_t numThreads, + void sendParallel(int tid, + size_t numThreads, ParameterType recvParameterType); /// sending thread routine for asynchronously send data void send(int threadId); @@ -535,9 +563,12 @@ private: ParameterUpdateMode updateMode, ParameterType parameterType, // client send type const std::vector& parameterSegments, - int64_t numSamples, real cost, bool sendBackParameter, + int64_t numSamples, + real cost, + bool sendBackParameter, ParameterType sendBackParameterType, // send back type in pserver - BatchStatus batchStatus, SendJob* sendJob); + BatchStatus batchStatus, + SendJob* sendJob); /// start necessary threads for threadPool void initThreads(); diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index 960fca2853..b7f999f8b1 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -31,10 +31,12 @@ limitations under the License. */ #include "paddle/utils/GlobalConstants.h" P_DEFINE_int32(pserver_num_threads, 1, "number of threads for sync op exec"); -P_DEFINE_double(async_lagged_ratio_min, 1.0, +P_DEFINE_double(async_lagged_ratio_min, + 1.0, "control config_.async_lagged_grad_discard_ratio() min value"); P_DEFINE_double( - async_lagged_ratio_default, 1.5, + async_lagged_ratio_default, + 1.5, "if async_lagged_grad_discard_ratio is not set in trainer_config.conf" "use it as defalut value"); @@ -47,7 +49,8 @@ const std::string ParameterServer2::kRetMsgInvalidVectorHandle = const std::string ParameterServer2::kRetMsgUnknownOperation = "Unknown operation"; -ParameterServer2::ParameterServer2(const std::string& addr, int port, +ParameterServer2::ParameterServer2(const std::string& addr, + int port, int rdmaCpu) : ProtoServer(addr, port, rdmaCpu), dataSize_(0), @@ -59,12 +62,12 @@ ParameterServer2::ParameterServer2(const std::string& addr, int port, allClientPassFinish_(false), serverId_(-1), batchId_(-1) { - /** - * register function for remote client calling, these functions - * will be mapped to a data structure for quick looking up. each - * request from trainer can contains one function name to indicate - * remote action. this architecture looks like rpc style for pserver. - */ + /** + * register function for remote client calling, these functions + * will be mapped to a data structure for quick looking up. each + * request from trainer can contains one function name to indicate + * remote action. this architecture looks like rpc style for pserver. + */ REGISTER_SERVICE_FUNCTION_EX(ParameterServer2, sendParameter); REGISTER_SERVICE_FUNCTION_EX(ParameterServer2, sendData); REGISTER_SERVICE_FUNCTION(ParameterServer2, setConfig); @@ -150,12 +153,12 @@ void ParameterServer2::setConfig(const SetConfigRequest& request, mkDir(request.save_dir().c_str()); } - for (const auto& config : request.param_configs()) { - CHECK(!configMap_.count(config.para_id())) - << "Duplicated parameter name: " << config.name(); - configMap_[config.para_id()] = config; - CHECK_EQ(config.sparse_remote_update(), isSparseServer_); - } + for (const auto& config : request.param_configs()) { + CHECK(!configMap_.count(config.para_id())) + << "Duplicated parameter name: " << config.name(); + configMap_[config.para_id()] = config; + CHECK_EQ(config.sparse_remote_update(), isSparseServer_); + } config_ = request.opt_config(); if (config_.algorithm() == TrainAlgorithm::AsyncSGD) { @@ -267,9 +270,9 @@ void ParameterServer2::setParameter(const SendParameterRequest& request, if (!request.blocks().size()) { LOG(WARNING) - << "--ports_num or --ports_num_for_sparse might be too large, " - << "or total dense parameter size or sparse parameters size " - << "might be too small, this psever doesn't store any parameter."; + << "--ports_num or --ports_num_for_sparse might be too large, " + << "or total dense parameter size or sparse parameters size " + << "might be too small, this psever doesn't store any parameter."; return; } @@ -339,8 +342,8 @@ void ParameterServer2::setParameter(const SendParameterRequest& request, << "width : " << width; } info.optimizer->init(1, info.config); - usedSegments_.push_back(std::make_pair(offsets[i], - offsets[i] + request.blocks(i).block_size())); + usedSegments_.push_back(std::make_pair( + offsets[i], offsets[i] + request.blocks(i).block_size())); } mergeSegments(&usedSegments_); @@ -364,15 +367,18 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, std::vector* outputBuffers) { VLOG(1) << "pserver: addGradient"; - /// forwardbackward delta from all trainers - /// indicate the fluctuation caused by forwardbackward. +/// forwardbackward delta from all trainers +/// indicate the fluctuation caused by forwardbackward. #ifndef PADDLE_METRIC_LEARNING // @TODO(yanfei): // add support tuning forwardbackward balance for metric learning if (!numPassFinishClients_) { REGISTER_BARRIER_DELTA_SERVER_SET( - *statSet_, "forwardbackwardDelta", FLAGS_num_gradient_servers, - request.trainer_id(), request.forwardbackward_time(), + *statSet_, + "forwardbackwardDelta", + FLAGS_num_gradient_servers, + request.trainer_id(), + request.forwardbackward_time(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } #endif @@ -390,14 +396,19 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, /// barrier fluctuation caused by network and previous forwardbackward if (!numPassFinishClients_) { REGISTER_BARRIER_TIMER_SERVER_SET( - *statSet_, "handleReqBegin", FLAGS_num_gradient_servers, - request.trainer_id(), (*handleRequestBegin_), + *statSet_, + "handleReqBegin", + FLAGS_num_gradient_servers, + request.trainer_id(), + (*handleRequestBegin_), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } if (!numPassFinishClients_) { REGISTER_BARRIER_TIMER_SERVER( - *statSet_, "addGradBegin", FLAGS_num_gradient_servers, + *statSet_, + "addGradBegin", + FLAGS_num_gradient_servers, request.trainer_id(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -414,8 +425,8 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, int64_t blockId = getBlockId(block); CHECK_GE(blockId, 0) << "Only existing parameter block is allowed: " - << " id=" << block.para_id() - << " block id=" << block.block_id(); + << " id=" << block.para_id() + << " block id=" << block.block_id(); Buffer buffer = inputBuffers[bufferIndex]; ++bufferIndex; @@ -438,7 +449,9 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, if (!numPassFinishClients_) { REGISTER_BARRIER_TIMER_SERVER( - *statSet_, "addGradCoreFinish", FLAGS_num_gradient_servers, + *statSet_, + "addGradCoreFinish", + FLAGS_num_gradient_servers, request.trainer_id(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -453,7 +466,9 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, /// numPassFinishClients_ means some trainer has entered finishPass if (!numPassFinishClients_) { REGISTER_SLOW_NODES_PROBE( - *statSet_, "SLOW_NODES", FLAGS_num_gradient_servers, + *statSet_, + "SLOW_NODES", + FLAGS_num_gradient_servers, request.trainer_id(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -463,7 +478,9 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, /// if wait pass finish does not start, do check if (!numPassFinishClients_) { - CHECK_BARRIER_TIMER(*statSet_, "SLOW_NODES", FLAGS_num_gradient_servers, + CHECK_BARRIER_TIMER(*statSet_, + "SLOW_NODES", + FLAGS_num_gradient_servers, isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -471,7 +488,9 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, /// can indicate the fluctation caused by computation at pserver. if (!numPassFinishClients_) { REGISTER_BARRIER_TIMER_SERVER( - *statSet_, "paraReady", FLAGS_num_gradient_servers, + *statSet_, + "paraReady", + FLAGS_num_gradient_servers, request.trainer_id(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -481,7 +500,8 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, { /// total time except overhead of network. REGISTER_TIMER_DYNAMIC_SET("sendParaNoRecvNoSend", - timeToMicroSecond(*addGradBegin_), -1, + timeToMicroSecond(*addGradBegin_), + -1, *statSet_); } } @@ -609,7 +629,8 @@ void ParameterServer2::asyncSGD(const SendParameterRequest& request, << " block id=" << block.block_id(); int64_t blockId = getBlockId(block); CHECK_GE(blockId, 0) << "Only existing parameter block is allowed: " - << " id=" << block.para_id() << " block id=" << block.block_id(); + << " id=" << block.para_id() + << " block id=" << block.block_id(); Buffer buffer = inputBuffers[bufferIndex]; ++bufferIndex; @@ -730,10 +751,11 @@ void ParameterServer2::sendBackParameter(const ParameterBlock& block, int64_t offset = getBlockOffset(block); CHECK_GE(offset, 0) << "Only existing parameter block is allowed: " - << " id=" << block.para_id() << " block id=" << block.block_id(); + << " id=" << block.para_id() + << " block id=" << block.block_id(); real* valueBuffer = vectors_[parameterType]->getPoint(offset); - outputBuffers->push_back({valueBuffer, (size_t) block.block_size()}); + outputBuffers->push_back({valueBuffer, (size_t)block.block_size()}); } void ParameterServer2::sendBackParameter(const ParameterBlock& block, @@ -749,7 +771,8 @@ void ParameterServer2::sendBackParameter(const ParameterBlock& block, int64_t offset = getBlockOffset(block); CHECK_GE(offset, 0) << "Only existing parameter block is allowed: " - << " id=" << block.para_id() << " block id=" << block.block_id(); + << " id=" << block.para_id() + << " block id=" << block.block_id(); size_t size = buffer->size; real* valueBuffer = vectors_[parameterType]->getPoint(offset); @@ -759,8 +782,11 @@ void ParameterServer2::sendBackParameter(const ParameterBlock& block, } void ParameterServer2::sendBackParameterSparse( - const ParameterBlock& block, int parameterType, - SendParameterResponse* response, Buffer* buffer, size_t width, + const ParameterBlock& block, + int parameterType, + SendParameterResponse* response, + Buffer* buffer, + size_t width, std::vector* outputBuffers) { ParameterBlock* returnBlock = response->add_blocks(); returnBlock->set_para_id(block.para_id()); @@ -769,7 +795,8 @@ void ParameterServer2::sendBackParameterSparse( returnBlock->set_block_size(block.block_size()); int64_t offset = getBlockOffset(block); CHECK_GE(offset, 0) << "Only existing parameter block is allowed: " - << " id=" << block.para_id() << " block id=" << block.block_id(); + << " id=" << block.para_id() + << " block id=" << block.block_id(); real* valueBuffer = vectors_[parameterType]->getPoint(offset); CHECK_EQ(buffer->size, width); @@ -781,7 +808,7 @@ void ParameterServer2::readAllBlocks( MsgReader* msgReader, std::vector* buffers) { auto& buffer = *readWriteBuffer_; size_t numBlocks = msgReader->getNumBlocks(); - buffer.resizeWithAlignHints(msgReader->getTotalLength()/sizeof(real), + buffer.resizeWithAlignHints(msgReader->getTotalLength() / sizeof(real), numBlocks); std::vector bufs(numBlocks); buffers->clear(); @@ -861,7 +888,9 @@ void ParameterServer2::sendParameter(const SendParameterRequest& request, /// indicates network flucatuation for big message. if (!numPassFinishClients_) { REGISTER_BARRIER_TIMER_SERVER( - *statSet_, "sendParamFinish", FLAGS_num_gradient_servers, + *statSet_, + "sendParamFinish", + FLAGS_num_gradient_servers, request.trainer_id(), isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); } @@ -871,13 +900,15 @@ void ParameterServer2::sendParameter(const SendParameterRequest& request, /// total time including overhead of network. REGISTER_TIMER_DYNAMIC_SET("sendParaTotal", timeToMicroSecond(*handleRequestBegin_), - -1, *statSet_); + -1, + *statSet_); } /// all time exhausted in pserverServer except recieve network. { /// total time except overhead of network receive REGISTER_TIMER_DYNAMIC_SET("sendParaNoRecv", - timeToMicroSecond(*addGradBegin_), -1, + timeToMicroSecond(*addGradBegin_), + -1, *statSet_); } } @@ -1007,36 +1038,42 @@ void ParameterServer2::clearUnusedSegments(CpuVector* vec) { return; } memset(data, 0, sizeof(real) * usedSegments_[0].first); - memset(data + usedSegments_.back().second, 0, + memset(data + usedSegments_.back().second, + 0, sizeof(real) * (size_ - usedSegments_.back().second)); size_t n = size_ - usedSegments_.back().second; for (size_t i = 1; i < usedSegments_.size(); ++i) { memset( - data + usedSegments_[i - 1].second, 0, + data + usedSegments_[i - 1].second, + 0, sizeof(real) * (usedSegments_[i].first - usedSegments_[i - 1].second)); n += usedSegments_[i].first - usedSegments_[i - 1].second; } } void ParameterServer2::parallelExecForEachBlock(ExecFunc func) { - SyncThreadPool::execHelper(syncThreadPool_.get(), [&](int tid, - size_t numThreads) { - int64_t numBlocks = blockIdMap_.size(); - VectorPtr* vecs = Parameter::getTlsTempBufs(); - for (int64_t blockId = tid; blockId < numBlocks; blockId += numThreads) { - func(blockId, vecs); - } - }); + SyncThreadPool::execHelper(syncThreadPool_.get(), + [&](int tid, size_t numThreads) { + int64_t numBlocks = blockIdMap_.size(); + VectorPtr* vecs = Parameter::getTlsTempBufs(); + for (int64_t blockId = tid; blockId < numBlocks; + blockId += numThreads) { + func(blockId, vecs); + } + }); } void ParameterServer2::blockTraverse( - BlockInfo& info, const ParameterConfig& config, int64_t offset, size_t size, + BlockInfo& info, + const ParameterConfig& config, + int64_t offset, + size_t size, const VectorPtr vecs[], const ParameterOptimizer::TraverseCallback& callback) { /// setup sub bufs for (const auto type : info.optimizer->getParameterTypes()) { - vecs[type]->subVecFrom(*vectors_[type], offset, size); + vecs[type]->subVecFrom(*vectors_[type], offset, size); } callback(vecs, config, config.sparse_remote_update() ? 0 : -1LU); } @@ -1064,10 +1101,10 @@ void ParameterServer2::op_SGD(const Operation& operation, info.optimizer->startBatch(numSamplesProcessed_); for (const auto type : info.optimizer->getParameterTypes()) { - vecs[type]->subVecFrom(*vectors_[type], offset, size); + vecs[type]->subVecFrom(*vectors_[type], offset, size); } - info.optimizer->update(vecs, config, - config.sparse_remote_update() ? 0 : -1LU); + info.optimizer->update( + vecs, config, config.sparse_remote_update() ? 0 : -1LU); vecs[PARAMETER_GRADIENT]->zeroMem(); if (auto callback = info.optimizer->needSpecialTraversal(config)) { diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index ceb1ad69e9..ccaea42e7d 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -55,7 +54,6 @@ namespace paddle { // computation causes big optmization latency, the GPU may be required by // pserver. - /** * Client interface for the parameter server * @@ -189,9 +187,10 @@ protected: */ constexpr static size_t AlignElementCount = AlignBytes / sizeof(T); - static_assert( - AlignElementCount == (AlignElementCount & -AlignElementCount) - || AlignBytes > sizeof(T), "AlignElementCount should be exp of 2"); + static_assert(AlignElementCount == + (AlignElementCount & -AlignElementCount) || + AlignBytes > sizeof(T), + "AlignElementCount should be exp of 2"); /** * @brief Resize Buffer, with block count that will be allocated. Each block @@ -205,7 +204,7 @@ protected: } else { //! at most, we need such elements in buffer to make sure each block is //! aligned. - this->resize(size + alignBlockCount* (AlignElementCount - 1)); + this->resize(size + alignBlockCount * (AlignElementCount - 1)); } } @@ -224,8 +223,8 @@ protected: curOffset_ += blockSize; if (!IsTLargerThanAlign) { - curOffset_ = (curOffset_ + AlignElementCount - 1) & - ~(AlignElementCount -1); + curOffset_ = + (curOffset_ + AlignElementCount - 1) & ~(AlignElementCount - 1); } return r; } @@ -369,7 +368,8 @@ public: /** * @brief send config to pserver * - * @note it can help pserver to understand the configuration for optimization, + * @note it can help pserver to understand the configuration for + * optimization, * logging control, duplicated initialization, etc. */ void setConfig(const SetConfigRequest& request, @@ -545,17 +545,17 @@ protected: std::vector* buffers); const ParameterConfig& getParameterConfig(const ParameterBlock& block) { - CHECK_LT(block.para_id(), -1UL) - << "invalid parameter id:" << block.para_id(); + CHECK_LT(block.para_id(), -1UL) << "invalid parameter id:" + << block.para_id(); const auto it = configMap_.find(block.para_id()); - CHECK(it != configMap_.end()) - << "can not find parameter id: " << block.para_id(); + CHECK(it != configMap_.end()) << "can not find parameter id: " + << block.para_id(); return it->second; } /// it implictly check blockOffsetMap_ while retrieving blockId const ParameterConfig& getParameterConfig(int64_t blockId) const { - CHECK(blockId >= 0 && blockId < (int64_t) blockInfos_.size()) + CHECK(blockId >= 0 && blockId < (int64_t)blockInfos_.size()) << "block idx out of range, id: " << blockId << " info size: " << blockInfos_.size(); return *(blockInfos_[blockId].config); @@ -614,7 +614,8 @@ protected: * vectors_[parameterType] directly * for dense with sync-sgd */ - void sendBackParameter(const ParameterBlock& block, int parameterType, + void sendBackParameter(const ParameterBlock& block, + int parameterType, SendParameterResponse* response, std::vector* outputBuffers); @@ -627,16 +628,20 @@ protected: * to buffer->base. * for dense with async-sgd */ - void sendBackParameter(const ParameterBlock& block, int parameterType, - SendParameterResponse* response, Buffer* buffer, + void sendBackParameter(const ParameterBlock& block, + int parameterType, + SendParameterResponse* response, + Buffer* buffer, std::vector* outputBuffers); /** * @brief prepare data for sending back * * @note specified for sparse */ - void sendBackParameterSparse(const ParameterBlock& block, int parameterType, - SendParameterResponse* response, Buffer* buffer, + void sendBackParameterSparse(const ParameterBlock& block, + int parameterType, + SendParameterResponse* response, + Buffer* buffer, size_t width, std::vector* outputBuffers); @@ -648,8 +653,11 @@ protected: */ typedef std::function ExecFunc; void parallelExecForEachBlock(ExecFunc func); - void blockTraverse(BlockInfo& info, const ParameterConfig& config, - int64_t offset, size_t size, const VectorPtr vecs[], + void blockTraverse(BlockInfo& info, + const ParameterConfig& config, + int64_t offset, + size_t size, + const VectorPtr vecs[], const ParameterOptimizer::TraverseCallback& callback); public: diff --git a/paddle/pserver/ProtoServer.cpp b/paddle/pserver/ProtoServer.cpp index 0ce06ddf91..2f6d911a01 100644 --- a/paddle/pserver/ProtoServer.cpp +++ b/paddle/pserver/ProtoServer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ProtoServer.h" namespace paddle { @@ -42,8 +41,8 @@ void ProtoServer::handleRequest(std::unique_ptr msgReader, void ProtoServer::registerServiceFunctionImp(const std::string& funcName, ServiceFunction func) { - CHECK(!nameToFuncMap_.count(funcName)) - << "Duplicated registration: " << funcName; + CHECK(!nameToFuncMap_.count(funcName)) << "Duplicated registration: " + << funcName; nameToFuncMap_[funcName] = func; } diff --git a/paddle/pserver/ProtoServer.h b/paddle/pserver/ProtoServer.h index 86e7158683..cf08e24ff3 100644 --- a/paddle/pserver/ProtoServer.h +++ b/paddle/pserver/ProtoServer.h @@ -12,7 +12,6 @@ WITHOUT 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 "LightNetwork.h" @@ -23,17 +22,17 @@ limitations under the License. */ namespace paddle { - /** - * - * It implements the rpc framework, which launchs one thread for each - * connection. Here define one parameter server as single TCP server - * binding on single port. All connections share single tcp ProtoServer - * object, each connection handles all requests from specified trainer - * within single worker thread. - * to accelerate bandwidth efficiency and harness multicore for pserver - * optimization to reduce pserver latency, you could launch more port - * for single NIC hardward with --port=N(N>1) for small cluster job. - */ +/** + * + * It implements the rpc framework, which launchs one thread for each + * connection. Here define one parameter server as single TCP server + * binding on single port. All connections share single tcp ProtoServer + * object, each connection handles all requests from specified trainer + * within single worker thread. + * to accelerate bandwidth efficiency and harness multicore for pserver + * optimization to reduce pserver latency, you could launch more port + * for single NIC hardward with --port=N(N>1) for small cluster job. + */ class ProtoServer : public SocketServer { public: /// rdmaCpu controls the cpu affinity of RDMA server daemon, @@ -84,7 +83,8 @@ public: template void registerServiceFunctionEx( const std::string& funcName, - std::function msgReader, + std::function msgReader, ProtoResponseCallbackEx callback)> func); protected: @@ -120,7 +120,8 @@ protected: class ProtoClient : public SocketClient { public: - ProtoClient(const std::string& serverAddr, int serverPort, + ProtoClient(const std::string& serverAddr, + int serverPort, enum ChannelType channelType = F_TCP) : SocketClient(serverAddr, serverPort, channelType) {} @@ -133,7 +134,8 @@ public: * @note iov provides additional blocks which need to be written to the * communication channel */ - void send(const char* funcName, const google::protobuf::MessageLite& proto, + void send(const char* funcName, + const google::protobuf::MessageLite& proto, const std::vector& iov = std::vector()); /** @@ -148,7 +150,8 @@ public: /// combines send() and recv() std::unique_ptr sendAndRecv( - const char* funcName, const google::protobuf::MessageLite& protoIn, + const char* funcName, + const google::protobuf::MessageLite& protoIn, google::protobuf::MessageLite* protoOut) { send(funcName, protoIn); return recv(protoOut); @@ -156,8 +159,10 @@ public: /// combines send() and recv() std::unique_ptr sendAndRecv( - const char* funcName, const google::protobuf::MessageLite& protoIn, - const std::vector& iov, google::protobuf::MessageLite* protoOut) { + const char* funcName, + const google::protobuf::MessageLite& protoIn, + const std::vector& iov, + google::protobuf::MessageLite* protoOut) { send(funcName, protoIn, iov); return recv(protoOut); } @@ -172,52 +177,62 @@ struct service_arg_type { }; template -struct service_arg_type, - Arg2)> { +struct service_arg_type, + Arg2)> { typedef Arg1 _1; }; /// register a service function to the ProtoServer /// This should only be used within a member function of className -#define REGISTER_SERVICE_FUNCTION(className, funcName) \ - registerServiceFunction< \ - service_arg_type::_1>( \ - #funcName, std::bind(&className::funcName, this, std::placeholders::_1, \ - std::placeholders::_2)) +#define REGISTER_SERVICE_FUNCTION(className, funcName) \ + registerServiceFunction< \ + service_arg_type::_1>( \ + #funcName, \ + std::bind(&className::funcName, \ + this, \ + std::placeholders::_1, \ + std::placeholders::_2)) /// register a service function to the ProtoServer /// This should only be used within a member function of className -#define REGISTER_SERVICE_FUNCTION_EX(className, funcName) \ - registerServiceFunctionEx< \ - service_arg_type::_1>( \ - #funcName, std::bind(&className::funcName, this, std::placeholders::_1, \ - std::placeholders::_2, std::placeholders::_3)) +#define REGISTER_SERVICE_FUNCTION_EX(className, funcName) \ + registerServiceFunctionEx< \ + service_arg_type::_1>( \ + #funcName, \ + std::bind(&className::funcName, \ + this, \ + std::placeholders::_1, \ + std::placeholders::_2, \ + std::placeholders::_3)) /// create wrapper function for parameter server high level function and /// register the wrapper function into function mapping. template void ProtoServer::registerServiceFunctionEx( const std::string& funcName, - std::function msgReader, + std::function msgReader, ProtoResponseCallbackEx callback)> func) { - auto f = - [func](std::unique_ptr msgReader, ResponseCallback callback) { - ProtoIn request; - std::string str(msgReader->getNextBlockLength(), 0); - msgReader->readNextBlock(&str[0]); - CHECK(request.ParseFromString(str)); - auto pcob = [callback](const google::protobuf::MessageLite& response, - const std::vector& outputIovs) { - std::string out; - CHECK(response.SerializeToString(&out)); - std::vector iovs; - iovs.push_back({&out[0], out.size()}); - iovs.insert(iovs.end(), outputIovs.begin(), outputIovs.end()); - callback(iovs); - }; - - func(request, std::move(msgReader), pcob); - }; + auto f = [func](std::unique_ptr msgReader, + ResponseCallback callback) { + ProtoIn request; + std::string str(msgReader->getNextBlockLength(), 0); + msgReader->readNextBlock(&str[0]); + CHECK(request.ParseFromString(str)); + auto pcob = [callback](const google::protobuf::MessageLite& response, + const std::vector& outputIovs) { + std::string out; + CHECK(response.SerializeToString(&out)); + std::vector iovs; + iovs.push_back({&out[0], out.size()}); + iovs.insert(iovs.end(), outputIovs.begin(), outputIovs.end()); + callback(iovs); + }; + + func(request, std::move(msgReader), pcob); + }; registerServiceFunctionImp(funcName, f); } @@ -226,24 +241,24 @@ template void ProtoServer::registerServiceFunction( const std::string& funcName, std::function func) { - auto f = - [func](std::unique_ptr msgReader, ResponseCallback callback) { - ProtoIn request; - std::string str(msgReader->getNextBlockLength(), 0); - msgReader->readNextBlock(&str[0]); - CHECK(request.ParseFromString(str)); - msgReader.reset(); - - auto pcob = [callback](const google::protobuf::MessageLite& response) { - std::string out; - CHECK(response.SerializeToString(&out)); - std::vector iovs; - iovs.push_back({&out[0], out.size()}); - callback(iovs); - }; - - func(request, pcob); - }; + auto f = [func](std::unique_ptr msgReader, + ResponseCallback callback) { + ProtoIn request; + std::string str(msgReader->getNextBlockLength(), 0); + msgReader->readNextBlock(&str[0]); + CHECK(request.ParseFromString(str)); + msgReader.reset(); + + auto pcob = [callback](const google::protobuf::MessageLite& response) { + std::string out; + CHECK(response.SerializeToString(&out)); + std::vector iovs; + iovs.push_back({&out[0], out.size()}); + callback(iovs); + }; + + func(request, pcob); + }; registerServiceFunctionImp(funcName, f); } diff --git a/paddle/pserver/RDMANetwork.h b/paddle/pserver/RDMANetwork.h index 05b845b68a..4e492a3afd 100644 --- a/paddle/pserver/RDMANetwork.h +++ b/paddle/pserver/RDMANetwork.h @@ -76,7 +76,7 @@ inline sxi_sock* accept(sxi_socket* s) { inline sockaddr_in* getSourceAddress(sxi_sock* sock) { #ifndef PADDLE_DISABLE_RDMA - return reinterpret_cast(&sock->sa); + return reinterpret_cast(&sock->sa); #else PROMPT_ERR(); #endif @@ -98,7 +98,6 @@ inline int close(sxi_sock* sock) { #endif } - inline void init() { #ifndef PADDLE_DISABLE_RDMA sxi_module_init(); @@ -155,6 +154,5 @@ inline sxi_sock* connect(sxi_socket* socket, const char* url) { #endif } - } // namespace rdma } // namespace paddle diff --git a/paddle/pserver/SocketChannel.cpp b/paddle/pserver/SocketChannel.cpp index 20295d7cdc..4ebc47d326 100644 --- a/paddle/pserver/SocketChannel.cpp +++ b/paddle/pserver/SocketChannel.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "SocketChannel.h" #include @@ -35,7 +34,6 @@ namespace paddle { #define UIO_MAXIOV 512 #endif - SocketChannel::~SocketChannel() { if (tcpRdma_ == F_TCP) close(tcpSocket_); @@ -81,8 +79,12 @@ size_t SocketChannel::write(const void* buf, size_t size) { } template -static size_t readwritev(IOFunc iofunc, SocketType socket, iovec* iovs, - int iovcnt, int maxiovs, const std::string& peerName) { +static size_t readwritev(IOFunc iofunc, + SocketType socket, + iovec* iovs, + int iovcnt, + int maxiovs, + const std::string& peerName) { int curIov = 0; size_t total = 0; @@ -123,25 +125,40 @@ static size_t readwritev(IOFunc iofunc, SocketType socket, iovec* iovs, return size; } - /// rdma::readv and rdma::writev can take advantage of RDMA blocking offload /// transfering size_t SocketChannel::writev(const std::vector& iovs) { if (tcpRdma_ == F_TCP) - return readwritev(::writev, tcpSocket_, const_cast(&iovs[0]), - iovs.size(), UIO_MAXIOV, peerName_); + return readwritev(::writev, + tcpSocket_, + const_cast(&iovs[0]), + iovs.size(), + UIO_MAXIOV, + peerName_); else - return readwritev(rdma::writev, rdmaSocket_, const_cast(&iovs[0]), - iovs.size(), MAX_VEC_SIZE, peerName_); + return readwritev(rdma::writev, + rdmaSocket_, + const_cast(&iovs[0]), + iovs.size(), + MAX_VEC_SIZE, + peerName_); } size_t SocketChannel::readv(std::vector* iovs) { if (tcpRdma_ == F_TCP) - return readwritev(::readv, tcpSocket_, const_cast(&(*iovs)[0]), - iovs->size(), UIO_MAXIOV, peerName_); + return readwritev(::readv, + tcpSocket_, + const_cast(&(*iovs)[0]), + iovs->size(), + UIO_MAXIOV, + peerName_); else - return readwritev(rdma::readv, rdmaSocket_, const_cast(&(*iovs)[0]), - iovs->size(), MAX_VEC_SIZE, peerName_); + return readwritev(rdma::readv, + rdmaSocket_, + const_cast(&(*iovs)[0]), + iovs->size(), + MAX_VEC_SIZE, + peerName_); } void SocketChannel::writeMessage(const std::vector& userIovs) { @@ -157,8 +174,8 @@ void SocketChannel::writeMessage(const std::vector& userIovs) { std::vector iovs; iovs.reserve(userIovs.size() + 2); iovs.push_back({&header, sizeof(header)}); - iovs.push_back({&iovLengths[0], static_cast( - sizeof(iovLengths[0]) * header.numIovs)}); + iovs.push_back({&iovLengths[0], + static_cast(sizeof(iovLengths[0]) * header.numIovs)}); iovs.insert(iovs.end(), userIovs.begin(), userIovs.end()); header.totalLength = 0; diff --git a/paddle/pserver/SocketChannel.h b/paddle/pserver/SocketChannel.h index fb9ac2e1dc..472b37a122 100644 --- a/paddle/pserver/SocketChannel.h +++ b/paddle/pserver/SocketChannel.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" diff --git a/paddle/pserver/SparseParameterDistribution.cpp b/paddle/pserver/SparseParameterDistribution.cpp index 31682c158e..2085b22a95 100644 --- a/paddle/pserver/SparseParameterDistribution.cpp +++ b/paddle/pserver/SparseParameterDistribution.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include "paddle/utils/Logging.h" @@ -21,19 +20,24 @@ limitations under the License. */ #include "SparseParameterDistribution.h" -P_DEFINE_bool(check_sparse_distribution_in_pserver, false, +P_DEFINE_bool(check_sparse_distribution_in_pserver, + false, "check whether sparse parameter exhibts balanced distribution at " "all pservers"); -P_DEFINE_bool(show_check_sparse_distribution_log, false, +P_DEFINE_bool(show_check_sparse_distribution_log, + false, "show logs details for sparse parameter distribution in pserver"); -P_DEFINE_int32(check_sparse_distribution_batches, 100, +P_DEFINE_int32(check_sparse_distribution_batches, + 100, "run sparse parameter distribution check for N batches"); P_DEFINE_double( - check_sparse_distribution_ratio, 0.6, + check_sparse_distribution_ratio, + 0.6, "if parameters dispatched to different pservers exhibit unbalanced " " distribution for check_sparse_distribution_ratio * " " check_sparse_distribution_batches times, crash program"); -P_DEFINE_double(check_sparse_distribution_unbalance_degree, 2.0, +P_DEFINE_double(check_sparse_distribution_unbalance_degree, + 2.0, "the ratio of maximum data size and minimun data size for " "different pserver"); diff --git a/paddle/pserver/test/SocketTest.cpp b/paddle/pserver/test/SocketTest.cpp index 260aed0083..24c90f1078 100644 --- a/paddle/pserver/test/SocketTest.cpp +++ b/paddle/pserver/test/SocketTest.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/Util.h" #include @@ -184,7 +183,8 @@ SocketClient::SocketClient(const std::string& serverAddr, int serverPort) { bzero((char*)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; - bcopy((char*)server->h_addr, (char*)&serv_addr.sin_addr.s_addr, + bcopy((char*)server->h_addr, + (char*)&serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(serverPort); diff --git a/paddle/pserver/test/test_ParameterServer2.cpp b/paddle/pserver/test/test_ParameterServer2.cpp index c9722f1212..eb813e92d6 100644 --- a/paddle/pserver/test/test_ParameterServer2.cpp +++ b/paddle/pserver/test/test_ParameterServer2.cpp @@ -27,7 +27,9 @@ P_DEFINE_int32(server_cpu, 0, "assign server cpu"); class ParameterServer2Tester : public ParameterServer2 { public: - ParameterServer2Tester(std::string serverAddr, int port, int rdmaCpu = -1, + ParameterServer2Tester(std::string serverAddr, + int port, + int rdmaCpu = -1, bool sepSendAndRecv = false) : ParameterServer2(serverAddr, port, rdmaCpu), client_(sepSendAndRecv) {} virtual ~ParameterServer2Tester() {} @@ -63,7 +65,7 @@ public: } size_t id = 0; - for (auto ¶ : parameters_) { + for (auto& para : parameters_) { para->setID(id++); } @@ -560,8 +562,8 @@ TEST(ParameterServer2, sendData) { std::unique_ptr g_server2; std::unique_ptr g_server3; if (FLAGS_rdma_tcp == "rdma") { - g_server1.reset(new ParameterServer2Tester(FLAGS_server_addr, FLAGS_port, - FLAGS_server_cpu)); + g_server1.reset(new ParameterServer2Tester( + FLAGS_server_addr, FLAGS_port, FLAGS_server_cpu)); g_server1->start(); g_server2.reset(new ParameterServer2Tester( FLAGS_server_addr, FLAGS_port + 1, FLAGS_server_cpu + 1)); @@ -604,8 +606,8 @@ int main(int argc, char** argv) { FLAGS_num_gradient_servers = 2; if (FLAGS_rdma_tcp == "rdma") { - g_server.reset(new ParameterServer2Tester(FLAGS_server_addr, FLAGS_port, - FLAGS_server_cpu)); + g_server.reset(new ParameterServer2Tester( + FLAGS_server_addr, FLAGS_port, FLAGS_server_cpu)); } else { g_server.reset(new ParameterServer2Tester(FLAGS_server_addr, FLAGS_port)); } diff --git a/paddle/pserver/test/test_ProtoServer.cpp b/paddle/pserver/test/test_ProtoServer.cpp index 065d6b3396..79d1f2743a 100644 --- a/paddle/pserver/test/test_ProtoServer.cpp +++ b/paddle/pserver/test/test_ProtoServer.cpp @@ -126,9 +126,11 @@ TEST(ProtoServer, extended) { GetStatusResponse response; { REGISTER_TIMER("sendAndRecv"); - auto msgReader = client->sendAndRecv( - "getStatusEx", request, {{cpuGrad.getData(), (size_t)dataSize}}, - &response); + auto msgReader = + client->sendAndRecv("getStatusEx", + request, + {{cpuGrad.getData(), (size_t)dataSize}}, + &response); EXPECT_EQ(msgReader->getNumBlocks(), (size_t)1); EXPECT_EQ(msgReader->getNextBlockLength(), (size_t)dataSize); diff --git a/paddle/trainer/ParamUtil.cpp b/paddle/trainer/ParamUtil.cpp index bb309a5497..2be9cd6223 100644 --- a/paddle/trainer/ParamUtil.cpp +++ b/paddle/trainer/ParamUtil.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ParamUtil.h" #include @@ -48,8 +47,6 @@ ParameterUtil::ParameterUtil( pUpdater_ = parameterUpdater; } - - bool ParameterUtil::loadParameters(int passId, bool local, bool remote) { constexpr int kBufLen = 100; char buf[kBufLen]; @@ -60,8 +57,9 @@ bool ParameterUtil::loadParameters(int passId, bool local, bool remote) { return true; } -void ParameterUtil::loadParametersWithPath(const std::string& dir, - bool local, bool remote) { +void ParameterUtil::loadParametersWithPath(const std::string &dir, + bool local, + bool remote) { if (local) { gserver_->loadParameters(dir); } @@ -98,7 +96,7 @@ void ParameterUtil::saveParameters(int passId, int passInnerId) { mkDir(saveDir.c_str()); if (!intConfig_->load_save_param_pserver_) { pUpdater_->getParametersRemote(true /*full parameter*/, - true /*after apply*/); + true /*after apply*/); } gserver_->saveParameters(saveDir); @@ -117,9 +115,13 @@ void ParameterUtil::saveParameters(int passId, int passInnerId) { void ParameterUtil::deleteParameters(int passId, int passInnerId) { constexpr int kBufLen = 100; char buf[kBufLen]; - const std::string& saveDir = config_->getSaveDir(); + const std::string &saveDir = config_->getSaveDir(); if (passInnerId > 0) { - snprintf(buf, kBufLen, "%s/pass-%05d-%03d", saveDir.c_str(), passId, + snprintf(buf, + kBufLen, + "%s/pass-%05d-%03d", + saveDir.c_str(), + passId, passInnerId); } else { snprintf(buf, kBufLen, "%s/pass-%05d", saveDir.c_str(), passId); @@ -129,8 +131,7 @@ void ParameterUtil::deleteParameters(int passId, int passInnerId) { rmDir(buf); } - -void ParameterUtil::saveConfigWithPath(const std::string& path) { +void ParameterUtil::saveConfigWithPath(const std::string &path) { std::string src; // save config in some path if (!intConfig_->config_.empty()) { diff --git a/paddle/trainer/ParamUtil.h b/paddle/trainer/ParamUtil.h index cfb637a3ed..3923941c3d 100644 --- a/paddle/trainer/ParamUtil.h +++ b/paddle/trainer/ParamUtil.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -37,14 +36,14 @@ namespace paddle { struct ParameterUtilConfig { DISABLE_COPY(ParameterUtilConfig); - ParameterUtilConfig(bool save_only_one, int saving_period, + ParameterUtilConfig(bool save_only_one, + int saving_period, bool load_save_parameters_in_pserver, - std::string config): - save_only_one_(save_only_one), - saving_period_(saving_period), - load_save_param_pserver_(load_save_parameters_in_pserver), - config_(config) { - } + std::string config) + : save_only_one_(save_only_one), + saving_period_(saving_period), + load_save_param_pserver_(load_save_parameters_in_pserver), + config_(config) {} bool save_only_one_; int saving_period_; @@ -52,7 +51,6 @@ struct ParameterUtilConfig { std::string config_; }; - /** * ParameterUtil * Utility class for loading and saving parameters @@ -80,8 +78,9 @@ public: bool loadParameters(int passId, bool local = true, bool remote = false); /// load parameters given path info - void loadParametersWithPath(const std::string& dir, bool local = true, - bool remote = false); + void loadParametersWithPath(const std::string &dir, + bool local = true, + bool remote = false); /// Save parameter to dist for pass passId /// passInnerId means saving times in one pass, some users want to @@ -97,14 +96,14 @@ public: void deleteParameters(int passId, int passInnerId = 0); /// save config given path info - void saveConfigWithPath(const std::string& path); + void saveConfigWithPath(const std::string &path); /** * Try to load parameter from config. * @return true if can load from trainer config. */ inline bool tryLoadParametersFromConfig() { - auto& c = config_->getConfig(); + auto &c = config_->getConfig(); if (!c.init_model_path().empty()) { loadParametersWithPath(c.init_model_path()); return true; diff --git a/paddle/trainer/ParameterUpdater.cpp b/paddle/trainer/ParameterUpdater.cpp index ef2b1443d9..6001a0b391 100644 --- a/paddle/trainer/ParameterUpdater.cpp +++ b/paddle/trainer/ParameterUpdater.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ParameterUpdater.h" #include "paddle/utils/Logging.h" @@ -30,7 +29,8 @@ SgdUpdaterWithCpuAverager::SgdUpdaterWithCpuAverager( CHECK(FLAGS_use_gpu && optConfig.do_average_in_cpu()); averager_.reset(AverageOptimizer::create(optConfig, new DummyOptimizer(optConfig), - false /*sparse*/, true /*apply*/)); + false /*sparse*/, + true /*apply*/)); updateWorker_.addJob([]() { hl_set_device(FLAGS_gpu_id); }); } diff --git a/paddle/trainer/ParameterUpdater.h b/paddle/trainer/ParameterUpdater.h index 854e6a45d8..b83b4cf55e 100644 --- a/paddle/trainer/ParameterUpdater.h +++ b/paddle/trainer/ParameterUpdater.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Thread.h" @@ -69,7 +68,8 @@ public: ParameterUpdater::init(parameters); optimizer_->init(parameters_.size(), nullptr); // check no L1 decay in parameter configs - CHECK(std::find_if(parameters.begin(), parameters.end(), + CHECK(std::find_if(parameters.begin(), + parameters.end(), [](const ParameterPtr& para) { return para->getConfig().decay_rate_l1() > 0.0f; }) == parameters.end()) @@ -146,7 +146,6 @@ protected: para->getBuf(PARAMETER_GRADIENT)->zeroMem(); } - std::unique_ptr optimizer_; /** @@ -163,10 +162,10 @@ class SgdCpuUpdater : public SgdLocalUpdater, public Deprecated { public: explicit SgdCpuUpdater(const OptimizationConfig& optConfig) : SgdLocalUpdater(optConfig), - Deprecated("SgdCpuUpdater is used only in recursive neural network, " - "and recursive neural network is deprecated in paddle. " - "Use it all by your own.") - {} + Deprecated( + "SgdCpuUpdater is used only in recursive neural network, " + "and recursive neural network is deprecated in paddle. " + "Use it all by your own.") {} /** * @brief update all parameter on finish batch. diff --git a/paddle/trainer/RemoteParameterUpdater.cpp b/paddle/trainer/RemoteParameterUpdater.cpp index 3a5c2a3517..d83bb5b10a 100644 --- a/paddle/trainer/RemoteParameterUpdater.cpp +++ b/paddle/trainer/RemoteParameterUpdater.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "RemoteParameterUpdater.h" #include "Trainer.h" #include "paddle/utils/Stat.h" @@ -31,7 +30,8 @@ const std::string RemoteParameterUpdater::kAverage = "average"; const std::string RemoteParameterUpdater::kElasticAverage = "elastic_average"; RemoteParameterUpdater::RemoteParameterUpdater( - const OptimizationConfig& config, int expectedPassCount, + const OptimizationConfig& config, + int expectedPassCount, std::unique_ptr&& localUpdater) : config_(config), localUpdater_(std::move(localUpdater)), @@ -94,8 +94,8 @@ void RemoteParameterUpdater::init(std::vector& parameters) { parameterClient_->getParameter(); copyParametersToDevice(PARAMETER_VALUE); } - if (FLAGS_trainer_id == 0 && (config_.algorithm() - != TrainAlgorithm::AsyncSGD)) { + if (FLAGS_trainer_id == 0 && + (config_.algorithm() != TrainAlgorithm::AsyncSGD)) { startController(); useApplyInPserver_ = useApplyInPserver(config_); } @@ -241,7 +241,9 @@ void RemoteParameterUpdater::finishBatch(real cost) { { REGISTER_TIMER("sendAndRecv_dense"); - parameterClient_->sendAndReceiveParameter(mode, sendType, batchSize_, + parameterClient_->sendAndReceiveParameter(mode, + sendType, + batchSize_, 0, // cost = 0 sendBackParameter); } @@ -356,7 +358,8 @@ void RemoteParameterUpdater::restore() { } ConcurrentRemoteParameterUpdater::ConcurrentRemoteParameterUpdater( - OptimizationConfig config, int passCount, + OptimizationConfig config, + int passCount, std::unique_ptr&& localUpdater) : RemoteParameterUpdater(config, passCount, std::move(localUpdater)) { sendThread_.reset(new std::thread([this]() { this->send(); })); @@ -423,7 +426,10 @@ void ConcurrentRemoteParameterUpdater::send(Parameter* para) { std::vector paraSegment; if (para == NULL) { parameterClient_->sendParameter( - mode, sendType, paraSegment, batchSize_, + mode, + sendType, + paraSegment, + batchSize_, 0, // cost=0 true, // sendBackParameter = true batchStatus_); // batchStatus_ = BATCH_FINISH @@ -440,7 +446,10 @@ void ConcurrentRemoteParameterUpdater::send(Parameter* para) { copySingleParaFromDevice(para, sendType); hl_stream_synchronize(kDeviceToHostStream); } - parameterClient_->sendParameter(mode, sendType, paraSegment, batchSize_, + parameterClient_->sendParameter(mode, + sendType, + paraSegment, + batchSize_, 0, // cost=0 true, // sendBackParameter = true batchStatus_); @@ -589,14 +598,14 @@ SparseRemoteParameterUpdater::SparseRemoteParameterUpdater( void SparseRemoteParameterUpdater::init(std::vector& parameters) { ParameterUpdater::init(parameters); - parameterClient_.reset(new ParameterClient2(false, - FLAGS_port + FLAGS_ports_num, FLAGS_ports_num_for_sparse)); + parameterClient_.reset(new ParameterClient2( + false, FLAGS_port + FLAGS_ports_num, FLAGS_ports_num_for_sparse)); parameterClient_->init(parameters_); parameterClient_->setTrainerId(FLAGS_trainer_id); if (FLAGS_trainer_id == 0) { - parameterClient_->setConfig(config_, FLAGS_save_dir, - true /*is_sparse_server*/); + parameterClient_->setConfig( + config_, FLAGS_save_dir, true /*is_sparse_server*/); if (parameters[0]->isFullSize()) { parameterClient_->setParameter(); } else { // init in pserver @@ -615,9 +624,8 @@ void SparseRemoteParameterUpdater::startController() { } void SparseRemoteParameterUpdater::controller() { - ParameterClient2 client(false, - FLAGS_port + FLAGS_ports_num, - FLAGS_ports_num_for_sparse); + ParameterClient2 client( + false, FLAGS_port + FLAGS_ports_num, FLAGS_ports_num_for_sparse); client.init(parameters_); while (true) { @@ -679,7 +687,9 @@ void SparseRemoteParameterUpdater::finishBatch(real cost) { ParameterType sendType = PARAMETER_GRADIENT; REGISTER_TIMER("sendSparseParam"); - parameterClient_->sendAndReceiveParameter(mode, sendType, batchSize_, + parameterClient_->sendAndReceiveParameter(mode, + sendType, + batchSize_, 0, // cost = 0 false); // sendBackParameter @@ -823,6 +833,6 @@ void SparseRemoteParameterUpdaterComposite::init( std::vector> -ParameterUpdaterCreators::constructors_; + ParameterUpdaterCreators::constructors_; } // namespace paddle diff --git a/paddle/trainer/RemoteParameterUpdater.h b/paddle/trainer/RemoteParameterUpdater.h index be273e9ef7..a40884724c 100644 --- a/paddle/trainer/RemoteParameterUpdater.h +++ b/paddle/trainer/RemoteParameterUpdater.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -56,7 +55,8 @@ namespace paddle { class RemoteParameterUpdater : public ParameterUpdater { public: RemoteParameterUpdater( - const OptimizationConfig& config, int expectedPpassCount, + const OptimizationConfig& config, + int expectedPpassCount, std::unique_ptr&& localUpdater = nullptr); ~RemoteParameterUpdater() { if (controllerThread_) { @@ -180,7 +180,8 @@ protected: class ConcurrentRemoteParameterUpdater : public RemoteParameterUpdater { public: ConcurrentRemoteParameterUpdater( - OptimizationConfig config, int expectedPassCount, + OptimizationConfig config, + int expectedPassCount, std::unique_ptr&& localUpdater); ~ConcurrentRemoteParameterUpdater(); @@ -264,7 +265,8 @@ private: class SparseRemoteParameterUpdater : public ParameterUpdater { public: SparseRemoteParameterUpdater(const OptimizationConfig& config, - int expectedPassCount, bool testing); + int expectedPassCount, + bool testing); ~SparseRemoteParameterUpdater() { if (controllerThread_) { controllerThread_->join(); @@ -345,7 +347,9 @@ public: * @note use syncThreadPool to synchronize these two updaters */ SparseRemoteParameterUpdaterComposite( - const OptimizationConfig& config, int expectedPassCount, bool testing, + const OptimizationConfig& config, + int expectedPassCount, + bool testing, std::unique_ptr&& normalUpdater) { updaters_.resize(NUMBER_UPDATERS); updaters_[UPDATER_SPARSE_REMOTE].reset( @@ -373,11 +377,11 @@ public: */ static void addCreator( const std::function& creator) { // NOLINT explicit move closing ) in this line + bool, // isLocal + size_t // numPasses + )>& creator) { // NOLINT explicit move closing ) in this line // for readability constructors_.push_back(creator); } @@ -395,7 +399,7 @@ public: const OptimizationConfig& optConfig, bool isLocal, size_t numPasses) { - for (auto & c : constructors_) { + for (auto& c : constructors_) { if (auto updater = c(algo, optConfig, isLocal, numPasses)) { return updater; } @@ -406,7 +410,7 @@ public: private: static std::vector> - constructors_; + constructors_; }; } // namespace paddle diff --git a/paddle/trainer/Tester.cpp b/paddle/trainer/Tester.cpp index d3b88019fa..30e92682ba 100644 --- a/paddle/trainer/Tester.cpp +++ b/paddle/trainer/Tester.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Tester.h" #include @@ -37,38 +36,33 @@ limitations under the License. */ namespace paddle { -Tester::Tester(const std::shared_ptr &config, - std::unique_ptr &&intconfig, - const GradientMachinePtr &gradientMachine, - const std::shared_ptr ¶meterUpdater, - std::shared_ptr testDataProvider): - config_(config), - intconfig_(std::move(intconfig)), - gradientMachine_(gradientMachine), - parameterUpdater_(parameterUpdater), - testDataProvider_(testDataProvider) { - testEvaluator_.reset(gradientMachine_ ->makeEvaluator()); +Tester::Tester(const std::shared_ptr& config, + std::unique_ptr&& intconfig, + const GradientMachinePtr& gradientMachine, + const std::shared_ptr& parameterUpdater, + std::shared_ptr testDataProvider) + : config_(config), + intconfig_(std::move(intconfig)), + gradientMachine_(gradientMachine), + parameterUpdater_(parameterUpdater), + testDataProvider_(testDataProvider) { + testEvaluator_.reset(gradientMachine_->makeEvaluator()); if (intconfig_->distributeTest) { testParameterClient_.reset(new ParameterClient2(true)); } if (testParameterClient_) { - testParameterClient_->init( - gradientMachine_->getParameters()); + testParameterClient_->init(gradientMachine_->getParameters()); } std::unique_ptr paramConfig( - new ParameterUtilConfig( - intconfig_->saveOnlyOne, - intconfig_->savingPeriod, - intconfig_->loadsaveParametersInPserver, - intconfig_->config)); + new ParameterUtilConfig(intconfig_->saveOnlyOne, + intconfig_->savingPeriod, + intconfig_->loadsaveParametersInPserver, + intconfig_->config)); paramUtil_.reset(new ParameterUtil( - config_, - std::move(paramConfig), - gradientMachine_, - parameterUpdater_)); + config_, std::move(paramConfig), gradientMachine_, parameterUpdater_)); } void Tester::startTestPeriod() { @@ -83,10 +77,10 @@ void Tester::startTestPeriod() { } } -void Tester::testOneDataBatch( - const DataBatch& dataBatch, std::vector* outArgs) { - testContext_.cost += forwardOneBatch( - dataBatch, testEvaluator_.get(), outArgs); +void Tester::testOneDataBatch(const DataBatch& dataBatch, + std::vector* outArgs) { + testContext_.cost += + forwardOneBatch(dataBatch, testEvaluator_.get(), outArgs); testContext_.numSamples += dataBatch.getSize(); } @@ -158,8 +152,8 @@ int64_t Tester::testOneBatchById(int64_t batchId) { return actualBatchSize; } -real Tester::forwardOneBatch(const DataBatch &dataBatch, - Evaluator *evaluator, +real Tester::forwardOneBatch(const DataBatch& dataBatch, + Evaluator* evaluator, std::vector* pOutArgs) { auto& outArgs = *pOutArgs; const std::vector& inArgs = dataBatch.getStreams(); @@ -180,7 +174,8 @@ real Tester::forwardOneBatch(const DataBatch &dataBatch, featMatrices.resize(numOutputs); for (size_t i = 0; i < numOutputs; ++i) { featMatrices[i] = Matrix::create(outArgs[i].value->getHeight(), - outArgs[i].value->getWidth(), false, + outArgs[i].value->getWidth(), + false, false); // CPU data buffer featMatrices[i]->copyFrom(*(outArgs[i].value), HPPL_STREAM_DEFAULT); } @@ -222,20 +217,19 @@ real Tester::forwardOneBatch(const DataBatch &dataBatch, return Argument::sumCosts(outArgs); } - void Tester::testOnePassBatch(int passId) { stats_.reset(); const std::vector inArgs; gradientMachine_->forward(inArgs, nullptr, PASS_TEST); - int64_t num; real cost; + int64_t num; + real cost; gradientMachine_->getStats(cost, num); - stats_ += std::pair {num, cost}; + stats_ += std::pair{num, cost}; gradientMachine_->onPassEnd(); LOG(INFO) << " Pass=" << passId << " " << stats_.getStats(false); } - void Tester::testOnePass(int passId) { stats_.reset(); int64_t batchId = 0; @@ -265,7 +259,6 @@ void Tester::testOnePass(int passId) { } } - void Tester::test() { CHECK(testDataProvider_) << "TestData is not specified"; testDataProvider_->setSkipShuffle(); @@ -281,33 +274,32 @@ void Tester::test() { intconfig_->testPass = 0; intconfig_->numPasses = modelList.size(); intconfig_->savingPeriod = 1; - CHECK_EQ(intconfig_->testWait, 0) << - "--test_wait must be 0 for evaluation"; + CHECK_EQ(intconfig_->testWait, 0) << "--test_wait must be 0 for evaluation"; } else if (!initModelPath.empty()) { modelList.push_back(initModelPath); intconfig_->testPass = 0; intconfig_->numPasses = 1; intconfig_->savingPeriod = 1; - CHECK_EQ(intconfig_->testWait, 0) << - "--test_wait must be 0 for evaluation"; + CHECK_EQ(intconfig_->testWait, 0) << "--test_wait must be 0 for evaluation"; } for (int i = intconfig_->testPass; i < intconfig_->numPasses; ++i) { int passId = i; if (passId % intconfig_->savingPeriod == 0) { if (intconfig_->testWait) { - while (paramUtil_->loadParameters(passId, - true /*local*/, true /*remote*/) == false) { + while (paramUtil_->loadParameters( + passId, true /*local*/, true /*remote*/) == false) { LOG(INFO) << "Waiting for parameters of pass " << passId; sleep(60); // sleep 60s } } else { if (modelList.size() == 0) { - CHECK_EQ(paramUtil_->loadParameters(passId, - true /*local*/, true /*remote*/), true); + CHECK_EQ(paramUtil_->loadParameters( + passId, true /*local*/, true /*remote*/), + true); } else { - paramUtil_->loadParametersWithPath(modelList[i], - true /*local*/, true /*remote*/); + paramUtil_->loadParametersWithPath( + modelList[i], true /*local*/, true /*remote*/); } } if (IGradientMachineMode::trainWholeDataInOneBatch(intconfig_->mode)) { @@ -326,9 +318,8 @@ void Tester::test() { gradientMachine_->finish(); } - void Tester::printOutput(const std::vector& outArgs, - std::ostream& os) { + std::ostream& os) { size_t numOutputs = outArgs.size(); size_t numIns = outArgs[0].getBatchSize(); if (cpuMat_.size() != numOutputs || cpuVec_.size() != numOutputs) { @@ -346,11 +337,13 @@ void Tester::printOutput(const std::vector& outArgs, } else if (dynamic_cast(outArgs[i].value.get())) { auto sparseMat = dynamic_cast(outArgs[i].value.get()); - cpuMat_[i] = Matrix::createSparseMatrix( - sparseMat->getHeight(), sparseMat->getWidth(), - sparseMat->getElementCnt(), sparseMat->getValueType(), - sparseMat->format_, false, /* trans */ - false); /* useGpu */ + cpuMat_[i] = Matrix::createSparseMatrix(sparseMat->getHeight(), + sparseMat->getWidth(), + sparseMat->getElementCnt(), + sparseMat->getValueType(), + sparseMat->format_, + false, /* trans */ + false); /* useGpu */ hl_stream_t stream = HPPL_STREAM_DEFAULT; cpuMat_[i]->copyFrom(*sparseMat, stream); } else { diff --git a/paddle/trainer/Tester.h b/paddle/trainer/Tester.h index 671ffc5220..a9de9fe208 100644 --- a/paddle/trainer/Tester.h +++ b/paddle/trainer/Tester.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -49,10 +48,10 @@ public: * for getting parameter from parameter-server. * @param testDataProvider Test data provider. */ - Tester(const std::shared_ptr &config, - std::unique_ptr &&intconfig, - const GradientMachinePtr &gradientMachine, - const std::shared_ptr ¶meterUpdater, + Tester(const std::shared_ptr& config, + std::unique_ptr&& intconfig, + const GradientMachinePtr& gradientMachine, + const std::shared_ptr& parameterUpdater, std::shared_ptr testDataProvider); /** @@ -83,13 +82,11 @@ public: Evaluator* evaluator, std::vector* outArgs); - /** * performance the full pass of test given test data provider */ void test(); - protected: std::shared_ptr testParameterClient_; std::shared_ptr config_; diff --git a/paddle/trainer/TesterConfig.h b/paddle/trainer/TesterConfig.h index d5e644ce61..90267e68d7 100644 --- a/paddle/trainer/TesterConfig.h +++ b/paddle/trainer/TesterConfig.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" diff --git a/paddle/trainer/ThreadParameterUpdater.cpp b/paddle/trainer/ThreadParameterUpdater.cpp index d0fda1b625..cc22851d8e 100644 --- a/paddle/trainer/ThreadParameterUpdater.cpp +++ b/paddle/trainer/ThreadParameterUpdater.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "ThreadParameterUpdater.h" #include "paddle/utils/Logging.h" @@ -45,7 +44,8 @@ void SgdThreadUpdater::init(std::vector& parameters) { optimizers_.resize(maxId + 1); for (auto& para : parameters_) { int pid = para->getID(); - optimizers_[pid].reset(sgdOptimizerCreate(config_, para->getConfig(), + optimizers_[pid].reset(sgdOptimizerCreate(config_, + para->getConfig(), para->isGradSparseUpdate(), false /*inPserver*/)); size_t numRows = para->isGradSparseUpdate() ? para->getConfig().dims(0) : 0; @@ -91,8 +91,10 @@ void SgdThreadUpdater::updateImpl(Parameter* para) { } void SgdThreadUpdater::threadTraverse( - const ParameterOptimizer::TraverseCallback& callback, int tid, - size_t numThreads, Parameter* para) { + const ParameterOptimizer::TraverseCallback& callback, + int tid, + size_t numThreads, + Parameter* para) { VectorPtr* vecs = Parameter::getTlsTempBufs(); if (para->isGradSparseUpdate()) { size_t height = para->getConfig().dims(0); @@ -106,8 +108,8 @@ void SgdThreadUpdater::threadTraverse( } } else { // dense // setup sub bufs - auto interval = calcSplitArrayInterval(para->getSize(), (size_t)tid, - numThreads, 8LU /*for avx*/); + auto interval = calcSplitArrayInterval( + para->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); for (auto type : parameterTypes_) { vecs[type]->subVecFrom(*para->getBuf(type), interval); } @@ -150,7 +152,7 @@ void SgdThreadUpdater::traverse(GetTraverseCallback getTraverseCallback) { } else if (hasCpuPara) { getGlobalSyncThreadPool()->exec(cpuTraverse); } else if (hasGpuPara) { - gpuTraverse(0, 0); + gpuTraverse(0, 0); } } @@ -168,9 +170,8 @@ void SgdThreadUpdater::catchUpWith() { void SgdThreadUpdater::apply() { catchUpWith(); - traverse([this](Parameter* para) { - return optimizers_[para->getID()]->apply(); - }); + traverse( + [this](Parameter* para) { return optimizers_[para->getID()]->apply(); }); } void SgdThreadUpdater::restore() { @@ -205,9 +206,9 @@ void SgdThreadUpdater::finishBatch(real cost) { } } -void SgdThreadUpdater::threadUpdateSparse( - int tid, size_t numThreads, Parameter* para) { - +void SgdThreadUpdater::threadUpdateSparse(int tid, + size_t numThreads, + Parameter* para) { int pid = para->getID(); ParameterOptimizer* optimizer = optimizers_[pid].get(); VectorPtr* vecs = Parameter::getTlsTempBufs(); @@ -216,10 +217,10 @@ void SgdThreadUpdater::threadUpdateSparse( size_t width = para->getConfig().dims(1); if (dynamic_cast( - para->getMat(PARAMETER_GRADIENT).get())) { + para->getMat(PARAMETER_GRADIENT).get())) { // From MultiGradientMachine SparseRowIdsCpuMatrix* mainMat = dynamic_cast( - para->getMat(PARAMETER_GRADIENT).get()); + para->getMat(PARAMETER_GRADIENT).get()); std::vector& sparseIds = mainMat->getIds(tid); for (auto id : sparseIds) { @@ -232,16 +233,16 @@ void SgdThreadUpdater::threadUpdateSparse( } sparseIds.clear(); } else if (dynamic_cast( - para->getMat(PARAMETER_GRADIENT).get())) { + para->getMat(PARAMETER_GRADIENT).get())) { // From NeuralNetwork SparseRowCpuMatrix* mainMat = dynamic_cast( - para->getMat(PARAMETER_GRADIENT).get()); + para->getMat(PARAMETER_GRADIENT).get()); std::vector& localIndices = mainMat->getIndexDictHandle()->localIndices; - auto interval = calcSplitArrayInterval( - localIndices.size(), tid, numThreads); + auto interval = + calcSplitArrayInterval(localIndices.size(), tid, numThreads); for (size_t i = interval.first; i < interval.second; ++i) { auto id = localIndices[i]; real* row = mainMat->getLocalRow(i); @@ -261,12 +262,11 @@ void SgdThreadUpdater::threadUpdateSparse( CHECK_EQ(numThreads, 1UL); mainMat->clearIndices(); } else { - auto & m = *para->getMat(PARAMETER_GRADIENT).get(); + auto& m = *para->getMat(PARAMETER_GRADIENT).get(); LOG(FATAL) << "Internal error: " << para->getName() << " " << typeid(m).name(); } - if (auto callback = optimizer->needSpecialTraversal(para->getConfig())) { for (size_t i = tid; i < height; i += numThreads) { // setup sub bufs @@ -278,14 +278,15 @@ void SgdThreadUpdater::threadUpdateSparse( } } -void SgdThreadUpdater::threadUpdateDense(int tid, size_t numThreads, +void SgdThreadUpdater::threadUpdateDense(int tid, + size_t numThreads, Parameter* para) { int pid = para->getID(); ParameterOptimizer* optimizer = optimizers_[pid].get(); VectorPtr* vecs = Parameter::getTlsTempBufs(); - auto interval = calcSplitArrayInterval(para->getSize(), (size_t)tid, - numThreads, 8LU /*for avx*/); + auto interval = calcSplitArrayInterval( + para->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); // setup sub bufs for (auto type : parameterTypes_) { diff --git a/paddle/trainer/ThreadParameterUpdater.h b/paddle/trainer/ThreadParameterUpdater.h index d8a7a5dd4f..5a5e3f1d4b 100644 --- a/paddle/trainer/ThreadParameterUpdater.h +++ b/paddle/trainer/ThreadParameterUpdater.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -26,7 +25,6 @@ limitations under the License. */ #include #include - namespace paddle { /** @@ -45,14 +43,12 @@ public: explicit SgdThreadUpdater(const OptimizationConfig& optConfig); virtual ~SgdThreadUpdater() {} - // Use the startPass() function of the base optimizer. virtual void startPass(); // Use the finishPass() function of the base optimizer. virtual bool finishPass(real cost); - virtual void init(std::vector& parameters); virtual PassType startBatch(int64_t batchSize); // Call finishBatch for each optimizer. @@ -78,9 +74,11 @@ protected: void threadUpdateDense(int tid, size_t numThreads, Parameter* para); // The update function for after update operations, such as averager. void threadTraverse(const ParameterOptimizer::TraverseCallback& callback, - int tid, size_t numThreads, Parameter* para); + int tid, + size_t numThreads, + Parameter* para); typedef std::function - GetTraverseCallback; + GetTraverseCallback; void traverse(GetTraverseCallback getTraverseCallback); }; diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 7fc48dd1fb..8a5162912e 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Trainer.h" #include @@ -40,7 +39,8 @@ limitations under the License. */ #include "TrainerConfigHelper.h" P_DEFINE_string(config, "", "Trainer config file"); -P_DEFINE_int32(test_period, 0, +P_DEFINE_int32(test_period, + 0, "Run test every so many train batches." " 0 for testing after each pass." " If not 0, test log_period batches." @@ -49,23 +49,28 @@ P_DEFINE_int32(test_period, 0, P_DEFINE_bool(local, true, "Train in local mode or not"); P_DEFINE_bool( - test_all_data_in_one_period, false, + test_all_data_in_one_period, + false, "true will test all data in one test peroid." "Otherwise test (batch_size * log_peroid) data in one test period."); -P_DEFINE_int32(average_test_period, 0, +P_DEFINE_int32(average_test_period, + 0, "Do test on average parameter every so" " many batches. MUST be devided by FLAGS_log_period." " Default 0 means do not test average parameter"); P_DEFINE_int32(saving_period, 1, "Save parameteres every so many passes"); -P_DEFINE_int64(saving_period_by_batches, 0, +P_DEFINE_int64(saving_period_by_batches, + 0, "Save parameters every so many batches in one pass"); P_DEFINE_string(save_dir, "", "Directory for saving model parameter"); -P_DEFINE_int32(start_pass, 0, +P_DEFINE_int32(start_pass, + 0, "Start training from this pass. " "Will load parameter from the previous pass"); -P_DEFINE_int32(test_pass, -1, +P_DEFINE_int32(test_pass, + -1, "Will load parameter start from this pass to test"); P_DEFINE_int32(test_wait, 0, "Waiting for pass parameter if not exist"); P_DEFINE_bool(with_cost, true, "enable cost layer or not"); @@ -73,17 +78,21 @@ P_DEFINE_bool(distribute_test, false, "test in distribute mode"); P_DEFINE_int32(num_passes, 100, "train for so many passes"); -P_DEFINE_string(config_args, "", +P_DEFINE_string(config_args, + "", "arguments passed to config file." "Format: key1=value1,key2=value2"); -P_DEFINE_bool(save_only_one, false, +P_DEFINE_bool(save_only_one, + false, "Save only parameters in last pass, remove previous."); P_DEFINE_string(feat_file, "", "File name of extracted feature."); -P_DEFINE_string(predict_output_dir, "", +P_DEFINE_string(predict_output_dir, + "", "Directory that saves the predicted results of output layers"); -P_DEFINE_string(model_list, "", +P_DEFINE_string(model_list, + "", "File that saves the model list when evaluation"); namespace paddle { @@ -98,11 +107,11 @@ void Trainer::init(int argc, char** argv) { init(config); } -void Trainer::init(const std::shared_ptr &config, +void Trainer::init(const std::shared_ptr& config, bool testing, - const std::shared_ptr &gradientMachine, - const std::shared_ptr &dataProvider, - const std::shared_ptr &testDataProvider) { + const std::shared_ptr& gradientMachine, + const std::shared_ptr& dataProvider, + const std::shared_ptr& testDataProvider) { this->stats_ = std::make_shared(); config_ = config; @@ -156,13 +165,16 @@ void Trainer::init(const std::shared_ptr &config, LOG(INFO) << "trainer mode: Testing"; } } else if (IGradientMachineMode::tryGetMode( - (int*)&mode_, config_->getOptConfig().algorithm(), - FLAGS_trainer_count, - FLAGS_local, FLAGS_use_gpu)) { + (int*)&mode_, + config_->getOptConfig().algorithm(), + FLAGS_trainer_count, + FLAGS_local, + FLAGS_use_gpu)) { LOG(INFO) << "Custom trainer mode."; } else if ((config_->getOptConfig().algorithm() == TrainAlgorithm::SGD || - config_->getOptConfig().algorithm() == TrainAlgorithm::AsyncSGD) - && useSparseUpdater) { + config_->getOptConfig().algorithm() == + TrainAlgorithm::AsyncSGD) && + useSparseUpdater) { mode_ = GradientMachine::kSgdSparseCpuTraining; LOG(INFO) << "trainer mode: SgdSparseCpuTraining"; } else { @@ -171,26 +183,26 @@ void Trainer::init(const std::shared_ptr &config, } // initialize trainer internal - trainerInternal_.init(config_, gradientMachine, + trainerInternal_.init(config_, + gradientMachine, TrainerInternalConfig::createFromMode(mode_), - stats_, testing); + stats_, + testing); std::unique_ptr paramConfig( - new ParameterUtilConfig(FLAGS_save_only_one, - FLAGS_saving_period, - FLAGS_loadsave_parameters_in_pserver, - FLAGS_config)); + new ParameterUtilConfig(FLAGS_save_only_one, + FLAGS_saving_period, + FLAGS_loadsave_parameters_in_pserver, + FLAGS_config)); paramUtil_.reset( - new paddle::ParameterUtil( - config_, - std::move(paramConfig), - trainerInternal_.getGradientMachine(), - trainerInternal_.getParameterUpdater())); - + new paddle::ParameterUtil(config_, + std::move(paramConfig), + trainerInternal_.getGradientMachine(), + trainerInternal_.getParameterUpdater())); - bool gpuData = FLAGS_use_gpu && (!FLAGS_parallel_nn) && - (!IGradientMachineMode::dataMustInCpu(mode_, - FLAGS_trainer_count)); + bool gpuData = + FLAGS_use_gpu && (!FLAGS_parallel_nn) && + (!IGradientMachineMode::dataMustInCpu(mode_, FLAGS_trainer_count)); dataProvider_ = dataProvider; if (!dataProvider_ && config_->hasDataConfig()) { @@ -244,12 +256,14 @@ void Trainer::init(const std::shared_ptr &config, } else if (!config_->getConfig().init_model_path().empty() && (FLAGS_local || FLAGS_trainer_id == 0)) { paramUtil_->loadParametersWithPath( - config_->getConfig().init_model_path(), - false /*local*/, true /*remote*/); + config_->getConfig().init_model_path(), + false /*local*/, + true /*remote*/); } else if (config_->getConfig().start_pass() > 0 && (FLAGS_local || FLAGS_trainer_id == 0)) { CHECK(paramUtil_->loadParameters(config_->getConfig().start_pass() - 1, - false /*local*/, true /*remote*/)); + false /*local*/, + true /*remote*/)); } else { trainerInternal_.getParameterUpdater()->randParametersRemote(); } @@ -277,9 +291,8 @@ void Trainer::train(size_t numPasses) { finishTrain(); } - static double genPerturbation(real* d, real* grad, size_t dim) { - auto & reng = ThreadLocalRandomEngine::get(); + auto& reng = ThreadLocalRandomEngine::get(); std::uniform_real_distribution dist(-1, 1); double gradNorm = 0, dNorm = 0; for (size_t i = 0; i < dim; ++i) { @@ -390,9 +403,7 @@ void Trainer::startTrain() { trainerInternal_.getGradientMachine()->start(*config_, dataProvider_); } -void Trainer::finishTrain() { - trainerInternal_.getGradientMachine()->finish(); -} +void Trainer::finishTrain() { trainerInternal_.getGradientMachine()->finish(); } void Trainer::startTrainPass() { stats_->reset(); @@ -421,9 +432,8 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { if (FLAGS_prev_batch_state) { trainerInternal_.getGradientMachine()->getState(trainState_); } - trainPassContext_.avgTestCost += - tester_->forwardOneBatch( - dataBatch, averageEvaluator_.get(), &forwardOutput_); + trainPassContext_.avgTestCost += tester_->forwardOneBatch( + dataBatch, averageEvaluator_.get(), &forwardOutput_); if (FLAGS_prev_batch_state) { trainerInternal_.getGradientMachine()->setState(trainState_); } @@ -434,16 +444,16 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { { REGISTER_TIMER("TrainBatch"); trainerInternal_.trainOneBatch( - trainPassContext_.batchId, dataBatch, &forwardOutput_); + trainPassContext_.batchId, dataBatch, &forwardOutput_); } if (averageEvaluator_ && - trainPassContext_.batchId % FLAGS_average_test_period - == FLAGS_average_test_period - 1) { + trainPassContext_.batchId % FLAGS_average_test_period == + FLAGS_average_test_period - 1) { averageEvaluator_->finish(); LOG(INFO) << " Averaged parameter:" - << " cost=" << trainPassContext_.avgTestCost - / trainPassContext_.numAvgTests + << " cost=" + << trainPassContext_.avgTestCost / trainPassContext_.numAvgTests << " Eval: " << *averageEvaluator_; trainPassContext_.numAvgTests = 0; trainPassContext_.avgTestCost = 0; @@ -463,15 +473,15 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { } if (FLAGS_saving_period_by_batches > 0 && - trainPassContext_.batchId - > FLAGS_saving_period_by_batches * trainPassContext_.passInnerId && + trainPassContext_.batchId > + FLAGS_saving_period_by_batches * trainPassContext_.passInnerId && 0 == FLAGS_trainer_id) { trainerInternal_.getParameterUpdater()->catchUpWith(); if (testDataProvider_) { tester_->testOnePeriod(); } - paramUtil_->saveParametersOnePass( - trainPassContext_.passId, trainPassContext_.passInnerId); + paramUtil_->saveParametersOnePass(trainPassContext_.passId, + trainPassContext_.passInnerId); ++trainPassContext_.passInnerId; } } @@ -482,8 +492,8 @@ void Trainer::finishTrainPass() { return; } - trainerInternal_.finishTrainPass( - trainPassContext_.passId, trainPassContext_.batchId); + trainerInternal_.finishTrainPass(trainPassContext_.passId, + trainPassContext_.batchId); FOR_TIMING(globalStat.setThreadInfo(true)); FOR_TIMING(globalStat.printAllStatus()); @@ -493,8 +503,8 @@ void Trainer::finishTrainPass() { tester_->testOnePeriod(); } - if (trainPassContext_.passId % FLAGS_saving_period == 0 - && FLAGS_trainer_id == 0) { + if (trainPassContext_.passId % FLAGS_saving_period == 0 && + FLAGS_trainer_id == 0) { paramUtil_->saveParametersOnePass(trainPassContext_.passId); } ++trainPassContext_.passId; @@ -526,8 +536,8 @@ void Trainer::trainOnePassBatch(int passId) { const std::vector inArgs; { REGISTER_TIMER("onePass"); - trainerInternal_.getGradientMachine()->forwardBackward(inArgs, nullptr, - PASS_TRAIN, nullptr); + trainerInternal_.getGradientMachine()->forwardBackward( + inArgs, nullptr, PASS_TRAIN, nullptr); } real cost = .0; @@ -537,8 +547,7 @@ void Trainer::trainOnePassBatch(int passId) { trainerInternal_.getGradientMachine()->onPassEnd(); - bool accepted = - trainerInternal_.getParameterUpdater()->finishPass(cost); + bool accepted = trainerInternal_.getParameterUpdater()->finishPass(cost); globalStat.setThreadInfo(true); globalStat.printAllStatus(); @@ -559,11 +568,12 @@ void Trainer::trainOnePassBatch(int passId) { } } -real Trainer::calcGradient(const DataBatch& dataBatch, const Vector& value, +real Trainer::calcGradient(const DataBatch& dataBatch, + const Vector& value, Vector& gradient) { CHECK_EQ(value.getSize(), gradient.getSize()); std::vector& parameters = - trainerInternal_.getGradientMachine()->getParameters(); + trainerInternal_.getGradientMachine()->getParameters(); clearGradient(); @@ -584,8 +594,8 @@ real Trainer::calcGradient(const DataBatch& dataBatch, const Vector& value, std::vector inArgs = dataBatch.getStreams(); std::vector outArgs; - trainerInternal_.getGradientMachine()->forwardBackward(inArgs, &outArgs, - PASS_TRAIN); + trainerInternal_.getGradientMachine()->forwardBackward( + inArgs, &outArgs, PASS_TRAIN); real cost = Argument::sumCosts(outArgs); offset = 0; @@ -612,15 +622,14 @@ void Trainer::clearGradient() { int Trainer::getBatchSize() { return config_->getOptConfig().batch_size(); } void Trainer::createTester() { - tester_.reset(new paddle::Tester(config_, createTesterConfig(), + tester_.reset(new paddle::Tester(config_, + createTesterConfig(), trainerInternal_.getGradientMachine(), trainerInternal_.getParameterUpdater(), testDataProvider_)); } -void Trainer::test() { - tester_->test(); -} +void Trainer::test() { tester_->test(); } std::unique_ptr Trainer::createTesterConfig() { TesterConfig* conf = new TesterConfig; @@ -648,7 +657,5 @@ std::unique_ptr Trainer::createTesterConfig() { return std::unique_ptr(conf); } -ParameterUtil* Trainer::getParameterUtilPtr() { - return paramUtil_.get(); -} +ParameterUtil* Trainer::getParameterUtilPtr() { return paramUtil_.get(); } } // namespace paddle diff --git a/paddle/trainer/Trainer.h b/paddle/trainer/Trainer.h index 7762722456..899607c7c0 100644 --- a/paddle/trainer/Trainer.h +++ b/paddle/trainer/Trainer.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -66,18 +65,17 @@ public: * @param testDataProvider Test Data Provider. null if create from config. */ virtual void init( - const std::shared_ptr &config, + const std::shared_ptr& config, bool testing = false, - const std::shared_ptr &gradientMachine = nullptr, - const std::shared_ptr &dataProvider = nullptr, - const std::shared_ptr &testDataProvider = nullptr); + const std::shared_ptr& gradientMachine = nullptr, + const std::shared_ptr& dataProvider = nullptr, + const std::shared_ptr& testDataProvider = nullptr); /** * Initialize Trainer from command line flags. */ void init(int argc, char** argv); - /** * Train until num_passes reached. * One pass means neural network train through all training data. @@ -108,7 +106,8 @@ public: * TODO(yuyang18): I think this method is deprecated and buggy. Should it be * removed? */ - real calcGradient(const DataBatch& dataBatch, const Vector& value, + real calcGradient(const DataBatch& dataBatch, + const Vector& value, Vector& gradient); /** @@ -207,12 +206,12 @@ protected: // parameter util std::unique_ptr paramUtil_; - #ifdef PADDLE_METRIC_LEARNING +#ifdef PADDLE_METRIC_LEARNING MetricTrainer trainerInternal_; - #else +#else // trainer Internal TrainerInternal trainerInternal_; - #endif +#endif }; } // namespace paddle diff --git a/paddle/trainer/TrainerConfigHelper.cpp b/paddle/trainer/TrainerConfigHelper.cpp index 98197e7988..ee5b1e0a9c 100644 --- a/paddle/trainer/TrainerConfigHelper.cpp +++ b/paddle/trainer/TrainerConfigHelper.cpp @@ -29,9 +29,8 @@ P_DECLARE_bool(with_gpu); P_DECLARE_bool(parallel_nn); P_DECLARE_string(config_args); - -const char* kConfigParserModuleName = "paddle.trainer.config_parser"; -const char* kConfigParserFuncName = "parse_config_and_serialize"; +const char *kConfigParserModuleName = "paddle.trainer.config_parser"; +const char *kConfigParserFuncName = "parse_config_and_serialize"; namespace paddle { @@ -40,12 +39,10 @@ struct TrainerConfigHelperPrivate { }; TrainerConfigHelper::TrainerConfigHelper(const std::string &configFilePath) - :m(new TrainerConfigHelperPrivate()) { + : m(new TrainerConfigHelperPrivate()) { std::ostringstream configArgs; - configArgs << "trainer_id=" << FLAGS_trainer_id - << ",local=" << FLAGS_local - << ",with_cost=" << FLAGS_with_cost - << ",use_gpu=" << FLAGS_use_gpu + configArgs << "trainer_id=" << FLAGS_trainer_id << ",local=" << FLAGS_local + << ",with_cost=" << FLAGS_with_cost << ",use_gpu=" << FLAGS_use_gpu << ",parallel_nn=" << FLAGS_parallel_nn << ",cudnn_version=" << hl_get_cudnn_lib_version(); if (!FLAGS_config_args.empty()) { @@ -54,31 +51,26 @@ TrainerConfigHelper::TrainerConfigHelper(const std::string &configFilePath) VLOG(3) << "Parsing trainer config " << configFilePath; std::string configProtoStr = - callPythonFunc(kConfigParserModuleName, kConfigParserFuncName, + callPythonFunc(kConfigParserModuleName, + kConfigParserFuncName, {configFilePath, configArgs.str()}); CHECK(m->conf.ParseFromString(configProtoStr)); } -TrainerConfigHelper::TrainerConfigHelper(const TrainerConfig& config) - :m(new TrainerConfigHelperPrivate()) { +TrainerConfigHelper::TrainerConfigHelper(const TrainerConfig &config) + : m(new TrainerConfigHelperPrivate()) { m->conf = config; } - TrainerConfigHelper::~TrainerConfigHelper() { if (m) { delete m; } } -const TrainerConfig & -TrainerConfigHelper::getConfig() const { - return m->conf; -} +const TrainerConfig &TrainerConfigHelper::getConfig() const { return m->conf; } -TrainerConfig& TrainerConfigHelper::getMutableConfig() { - return m->conf; -} +TrainerConfig &TrainerConfigHelper::getMutableConfig() { return m->conf; } const OptimizationConfig &TrainerConfigHelper::getOptConfig() const { return m->conf.opt_config(); @@ -173,8 +165,7 @@ std::string TrainerConfigHelper::getConfigName(bool *ok) const { } else if (!m->conf.init_model_path().empty()) { retv = getConfigNameFromPath(m->conf.init_model_path()); } else if (m->conf.start_pass() >= 1) { - retv = getConfigNameFromPassId(m->conf.start_pass(), - m->conf.save_dir()); + retv = getConfigNameFromPassId(m->conf.start_pass(), m->conf.save_dir()); } if (ok) { @@ -191,8 +182,8 @@ std::shared_ptr TrainerConfigHelper::createFromFlags() { } else if (!FLAGS_init_model_path.empty()) { configPath = getConfigNameFromPath(FLAGS_init_model_path); } else if (FLAGS_start_pass >= 1) { - configPath = getConfigNameFromPassId(FLAGS_start_pass - 1, - FLAGS_init_model_path); + configPath = + getConfigNameFromPassId(FLAGS_start_pass - 1, FLAGS_init_model_path); } else { return nullptr; } diff --git a/paddle/trainer/TrainerConfigHelper.h b/paddle/trainer/TrainerConfigHelper.h index d3ad1eeeb4..d206849641 100644 --- a/paddle/trainer/TrainerConfigHelper.h +++ b/paddle/trainer/TrainerConfigHelper.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -27,7 +26,6 @@ struct TrainerConfigHelperPrivate; class ModelConfig; class DataConfig; - /** * @brief TrainerConfig Helper. A class wrap protobuf's TrainerConfig Object, * simplize the usage for TrainerConfig. @@ -46,7 +44,7 @@ public: * @brief Ctor, Create a TrainerConfig from config file * @param configFilePath Config file path. */ - explicit TrainerConfigHelper(const std::string &configFilePath); + explicit TrainerConfigHelper(const std::string& configFilePath); explicit TrainerConfigHelper(const TrainerConfig& config); /** @@ -106,7 +104,6 @@ public: */ bool hasTestDataConfig() const; - /** * @brief Update trainer config from command line flags. * Override config's (save_dir, init_model_path, start_pass) if command @@ -114,7 +111,6 @@ public: */ void updateConfigFromFlags(); - /** * @brief Disable optimization's sparse remote update. */ @@ -125,13 +121,10 @@ public: */ void disableRemoteSparseUpdaterForEachParams(); - /** * @brief implicit conversion. */ - inline operator const TrainerConfig&() const { - return this->getConfig(); - } + inline operator const TrainerConfig&() const { return this->getConfig(); } /** * @brief implicit conversion. @@ -143,16 +136,12 @@ public: /** * @brief implicit conversion. */ - inline operator const DataConfig&() const { - return this->getDataConfig(); - } + inline operator const DataConfig&() const { return this->getDataConfig(); } /** * @brief implicit conversion. */ - inline operator const ModelConfig&() const { - return this->getModelConfig(); - } + inline operator const ModelConfig&() const { return this->getModelConfig(); } /** * @brief Get mutable optimization config. diff --git a/paddle/trainer/TrainerInternal.cpp b/paddle/trainer/TrainerInternal.cpp index e23e42927c..b1c3bf26d2 100644 --- a/paddle/trainer/TrainerInternal.cpp +++ b/paddle/trainer/TrainerInternal.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "TrainerInternal.h" #include @@ -37,30 +36,31 @@ limitations under the License. */ namespace paddle { -void TrainerInternal::init(const std::shared_ptr &config, - const GradientMachinePtr &gradientMachine, - std::unique_ptr &&intconfig, - const std::shared_ptr &stats, +void TrainerInternal::init(const std::shared_ptr& config, + const GradientMachinePtr& gradientMachine, + std::unique_ptr&& intconfig, + const std::shared_ptr& stats, bool testing) { - config_ = config; - intconfig_ = std::move(intconfig); - stats_ = stats; + config_ = config; + intconfig_ = std::move(intconfig); + stats_ = stats; - //! in training will use parameter updater definitly. - //! But only use parameter in testing mode when some parameter in pserver. - if (!testing || (config_->getOptConfig().use_sparse_remote_updater() && + //! in training will use parameter updater definitly. + //! But only use parameter in testing mode when some parameter in pserver. + if (!testing || (config_->getOptConfig().use_sparse_remote_updater() && intconfig_->loadsave_parameters_in_pserver)) { - createParameterUpdater(testing); - } + createParameterUpdater(testing); + } - gradientMachine_ = gradientMachine; - if (!gradientMachine) { - CHECK(config_->getConfig().has_model_config()) - << "Missing model_config in trainer_config"; - gradientMachine_.reset(GradientMachine::create( - config_->getConfig().model_config(), intconfig_->mode, - parameterUpdater_->getParameterTypes())); - } + gradientMachine_ = gradientMachine; + if (!gradientMachine) { + CHECK(config_->getConfig().has_model_config()) + << "Missing model_config in trainer_config"; + gradientMachine_.reset( + GradientMachine::create(config_->getConfig().model_config(), + intconfig_->mode, + parameterUpdater_->getParameterTypes())); + } } void TrainerInternal::trainOneBatch(int64_t batchId, @@ -96,8 +96,8 @@ void TrainerInternal::trainOneBatch(int64_t batchId, parameterUpdater_->getParametersRemote(); } - UpdateCallback updateCallback = - [this, showStats, ¶Stats](Parameter* para) { + UpdateCallback updateCallback = [this, showStats, ¶Stats]( + Parameter* para) { if (showStats) { //! @TODO(yuyang18) Show stats is actually a ParameterHook, refactor // it @@ -116,8 +116,8 @@ void TrainerInternal::trainOneBatch(int64_t batchId, timer.start(); #endif REGISTER_TIMER("forwardBackward"); - forwardBackwardBatch(inArgs, *outArgs, passType, updateCallback, - doPipelineUpdate); + forwardBackwardBatch( + inArgs, *outArgs, passType, updateCallback, doPipelineUpdate); #ifndef PADDLE_DISABLE_TIMER timer.stop(); parameterUpdater_->setForwardbackwardTime(timer.get()); @@ -147,7 +147,7 @@ void TrainerInternal::trainOneBatch(int64_t batchId, gradientMachine_->eval(evaluator_); } - *stats_ += { actualBatchSize, cost }; + *stats_ += {actualBatchSize, cost}; { REGISTER_TIMER("finishBatch"); parameterUpdater_->finishBatch(cost); @@ -162,12 +162,11 @@ void TrainerInternal::trainOneBatch(int64_t batchId, if (intconfig_->dot_period > 0) { std::cerr << std::endl; } - LOG(INFO) << " Batch=" << batchId + 1 << " " - << *stats_ + LOG(INFO) << " Batch=" << batchId + 1 << " " << *stats_ << " Eval: " << *evaluator_ << " CurrentEval: " << *currentEvaluator_; } else if (intconfig_->dot_period > 0 && - (batchId + 1) % intconfig_->dot_period == 0) { + (batchId + 1) % intconfig_->dot_period == 0) { std::cerr << "."; } } @@ -179,13 +178,13 @@ void TrainerInternal::finishTrainPass(int passId, int batchId) { gradientMachine_->onPassEnd(); parameterUpdater_->finishPass(); evaluator_->finish(); - LOG(INFO) << " Pass=" << passId << " Batch=" << batchId - << " " << stats_->getStats(false /*without current cost*/) + LOG(INFO) << " Pass=" << passId << " Batch=" << batchId << " " + << stats_->getStats(false /*without current cost*/) << " Eval: " << *evaluator_; } -void TrainerInternal::showParameterStats(const std::vector& - paraStats) { +void TrainerInternal::showParameterStats( + const std::vector& paraStats) { std::vector& parameters = gradientMachine_->getParameters(); for (auto& parameter : parameters) { SetDevice device(parameter->getDeviceId()); @@ -218,18 +217,21 @@ void TrainerInternal::showParameterStats(const std::vector& void TrainerInternal::createParameterUpdater(bool testing) { const std::string& alg = config_->getOptConfig().algorithm(); parameterUpdater_.reset(ParameterUpdaterCreators::tryCreateUpdater( - alg, config_->getOptConfig(), intconfig_->local, - intconfig_->num_passes)); - if (parameterUpdater_) { return; } + alg, config_->getOptConfig(), intconfig_->local, intconfig_->num_passes)); + if (parameterUpdater_) { + return; + } if (!intconfig_->local) { if (testing && config_->getOptConfig().use_sparse_remote_updater()) { std::unique_ptr localUpdater; localUpdater.reset( new SgdLocalUpdater(config_->getOptConfig())); // do nothing - parameterUpdater_.reset(new SparseRemoteParameterUpdaterComposite( - config_->getOptConfig(), intconfig_->num_passes, testing, - std::move(localUpdater))); + parameterUpdater_.reset( + new SparseRemoteParameterUpdaterComposite(config_->getOptConfig(), + intconfig_->num_passes, + testing, + std::move(localUpdater))); } else { if (GradientMachine::kSgdSparseCpuTraining == intconfig_->mode && !intconfig_->use_old_updater) { @@ -251,21 +253,18 @@ void TrainerInternal::createParameterUpdater(bool testing) { } localUpdater.reset( - intconfig_->use_old_updater + intconfig_->use_old_updater ? new RemoteParameterUpdater( - *config_, - intconfig_->num_passes, - std::move(localUpdater)) + *config_, intconfig_->num_passes, std::move(localUpdater)) : new ConcurrentRemoteParameterUpdater( - *config_, - intconfig_->num_passes, - std::move(localUpdater))); - + *config_, intconfig_->num_passes, std::move(localUpdater))); if (config_->getOptConfig().use_sparse_remote_updater()) { - localUpdater.reset(new SparseRemoteParameterUpdaterComposite( - *config_, intconfig_->num_passes, testing, - std::move(localUpdater))); + localUpdater.reset( + new SparseRemoteParameterUpdaterComposite(*config_, + intconfig_->num_passes, + testing, + std::move(localUpdater))); } this->parameterUpdater_ = std::move(localUpdater); @@ -282,8 +281,7 @@ void TrainerInternal::createParameterUpdater(bool testing) { } else if (intconfig_->use_gpu && config_->getOptConfig().do_average_in_cpu() && config_->getOptConfig().average_window() > 0) { - parameterUpdater_.reset( - new SgdUpdaterWithCpuAverager(*config_)); + parameterUpdater_.reset(new SgdUpdaterWithCpuAverager(*config_)); } else { parameterUpdater_.reset(new SgdLocalUpdater(*config_)); } @@ -294,10 +292,10 @@ void TrainerInternal::createParameterUpdater(bool testing) { } void TrainerInternal::forwardBackwardBatch(const std::vector& inArgs, - std::vector& outArgs, - PassType& passType, - UpdateCallback updateCallback, - bool doPipelineUpdate) { + std::vector& outArgs, + PassType& passType, + UpdateCallback updateCallback, + bool doPipelineUpdate) { gradientMachine_->forwardBackward( inArgs, &outArgs, passType, doPipelineUpdate ? updateCallback : nullptr); } diff --git a/paddle/trainer/TrainerInternal.h b/paddle/trainer/TrainerInternal.h index 3a53aa1d17..962d53a30e 100644 --- a/paddle/trainer/TrainerInternal.h +++ b/paddle/trainer/TrainerInternal.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -28,7 +27,6 @@ limitations under the License. */ #include "TrainerConfigHelper.h" #include "TrainerInternalConfig.h" - namespace paddle { /** @@ -40,12 +38,10 @@ public: struct ParaStat { real maxAbsGrad; real avgAbsGrad; - ParaStat() :maxAbsGrad(.0), avgAbsGrad(.0){ - } + ParaStat() : maxAbsGrad(.0), avgAbsGrad(.0) {} }; - TrainerInternal() { - } + TrainerInternal() {} /** * Intializes trainer internal class @@ -55,10 +51,10 @@ public: * @param stats training stats * @param testing if it is in testing phase */ - void init(const std::shared_ptr &config, - const GradientMachinePtr &machine, - std::unique_ptr &&intconfig, - const std::shared_ptr &stats, + void init(const std::shared_ptr& config, + const GradientMachinePtr& machine, + std::unique_ptr&& intconfig, + const std::shared_ptr& stats, bool testing); virtual ~TrainerInternal() {} @@ -94,7 +90,7 @@ public: /** * getGradientMachine */ - inline const GradientMachinePtr & getGradientMachine() const { + inline const GradientMachinePtr& getGradientMachine() const { return gradientMachine_; } @@ -109,17 +105,13 @@ public: * setCurrentEvaluator * @param eval evaluator to set */ - inline void setCurrentEvaluator(Evaluator* eval) { - currentEvaluator_ = eval; - } + inline void setCurrentEvaluator(Evaluator* eval) { currentEvaluator_ = eval; } /** * setEvaluator * @param eval evaluator to set */ - inline void setEvaluator(Evaluator* eval) { - evaluator_ = eval; - } + inline void setEvaluator(Evaluator* eval) { evaluator_ = eval; } /** * forwardBackwardBatch diff --git a/paddle/trainer/TrainerInternalConfig.cpp b/paddle/trainer/TrainerInternalConfig.cpp index 4a829a4df9..0dc74cb3b3 100644 --- a/paddle/trainer/TrainerInternalConfig.cpp +++ b/paddle/trainer/TrainerInternalConfig.cpp @@ -14,7 +14,8 @@ limitations under the License. */ #include "TrainerInternalConfig.h" -P_DEFINE_int32(show_parameter_stats_period, 0, +P_DEFINE_int32(show_parameter_stats_period, + 0, "Whether to show parameter stats during training"); P_DEFINE_int32(dot_period, 1, "Print '.' every so many batches"); diff --git a/paddle/trainer/TrainerInternalConfig.h b/paddle/trainer/TrainerInternalConfig.h index 9b59143bad..b7bfd29abd 100644 --- a/paddle/trainer/TrainerInternalConfig.h +++ b/paddle/trainer/TrainerInternalConfig.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include "paddle/utils/Util.h" @@ -94,9 +93,7 @@ public: * @brief get all processed samples' number * @return all processed samples' number */ - inline int64_t getNumProcessed() const { - return this->numProcessed_; - } + inline int64_t getNumProcessed() const { return this->numProcessed_; } /** * @brief same function as addCost. But it is simple to invoke. @@ -111,7 +108,7 @@ public: * @param p a pair of parameter, first is numProcessed, second is cost. * @return *this */ - inline TrainerStats& operator += (const std::pair& p) { + inline TrainerStats& operator+=(const std::pair& p) { this->addCost(p.first, p.second); return *this; } @@ -121,9 +118,7 @@ public: * * reset stat when constructed. */ - inline TrainerStats() { - this->reset(); - } + inline TrainerStats() { this->reset(); } /** * @brief show stats to ostream. @@ -137,7 +132,7 @@ public: os << "samples=" << this->getNumProcessed() << " AvgCost=" << this->getAvgCost(); if (withCurrentCost) { - os << " CurrentCost=" << this->getCurrentAvgCost(); + os << " CurrentCost=" << this->getCurrentAvgCost(); } } diff --git a/paddle/trainer/TrainerMain.cpp b/paddle/trainer/TrainerMain.cpp index a486cc383a..e23e745d99 100644 --- a/paddle/trainer/TrainerMain.cpp +++ b/paddle/trainer/TrainerMain.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include #include "paddle/utils/PythonUtil.h" #include "paddle/utils/StringUtil.h" @@ -34,7 +33,7 @@ P_DECLARE_string(rdma_tcp); using namespace paddle; // NOLINT int main(int argc, char** argv) { - // write logs instantly (never buffer log messages) +// write logs instantly (never buffer log messages) #ifdef PADDLE_USE_GLOG FLAGS_logbuflevel = -1; #endif diff --git a/paddle/trainer/tests/picojson.h b/paddle/trainer/tests/picojson.h index a0b5c2274b..cb657d219e 100644 --- a/paddle/trainer/tests/picojson.h +++ b/paddle/trainer/tests/picojson.h @@ -409,7 +409,8 @@ inline std::string value::to_str() const { case number_type: { char buf[256]; double tmp; - SNPRINTF(buf, sizeof(buf), + SNPRINTF(buf, + sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", @@ -532,7 +533,8 @@ void value::_serialize(Iter oi, int indent) const { ++indent; } for (object::const_iterator i = u_.object_->begin(); - i != u_.object_->end(); ++i) { + i != u_.object_->end(); + ++i) { if (i != u_.object_->begin()) { *oi++ = ','; } @@ -983,7 +985,9 @@ inline std::string parse(value& out, Iter& pos, const Iter& last) { } template -inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, +inline Iter _parse(Context& ctx, + const Iter& first, + const Iter& last, std::string* err) { input in(first, last); if (!_parse(ctx, in) && err != NULL) { @@ -1003,7 +1007,9 @@ inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, } template -inline Iter parse(value& out, const Iter& first, const Iter& last, +inline Iter parse(value& out, + const Iter& first, + const Iter& last, std::string* err) { default_parse_context ctx(&out); return _parse(ctx, first, last, err); @@ -1017,8 +1023,10 @@ inline std::string parse(value& out, const std::string& s) { inline std::string parse(value& out, std::istream& is) { std::string err; - parse(out, std::istreambuf_iterator(is.rdbuf()), - std::istreambuf_iterator(), &err); + parse(out, + std::istreambuf_iterator(is.rdbuf()), + std::istreambuf_iterator(), + &err); return err; } diff --git a/paddle/trainer/tests/test_Compare.cpp b/paddle/trainer/tests/test_Compare.cpp index 735c5a5b27..03312f9e47 100644 --- a/paddle/trainer/tests/test_Compare.cpp +++ b/paddle/trainer/tests/test_Compare.cpp @@ -52,8 +52,8 @@ void calcGradient(bool useGpu, comData& Data) { vector& inArgs = dataBatch.getStreams(); trainer.getGradientMachine()->start(trainer.getConfig(), nullptr); for (int i = 0; i < 2; ++i) { - trainer.getGradientMachine()->forwardBackward(inArgs, &Data.outArgs, - PASS_TRAIN); + trainer.getGradientMachine()->forwardBackward( + inArgs, &Data.outArgs, PASS_TRAIN); } trainer.getGradientMachine()->finish(); } diff --git a/paddle/trainer/tests/test_CompareSparse.cpp b/paddle/trainer/tests/test_CompareSparse.cpp index 311dd333a1..a7c6862ce3 100644 --- a/paddle/trainer/tests/test_CompareSparse.cpp +++ b/paddle/trainer/tests/test_CompareSparse.cpp @@ -23,7 +23,7 @@ using namespace paddle; // NOLINT using namespace std; // NOLINT static const string& configFile1 = - "trainer/tests/sample_trainer_config_qb_rnn.conf"; + "trainer/tests/sample_trainer_config_qb_rnn.conf"; P_DECLARE_bool(use_gpu); P_DECLARE_string(config); @@ -38,8 +38,9 @@ P_DECLARE_bool(local); P_DECLARE_bool(use_old_updater); P_DECLARE_bool(parallel_nn); P_DECLARE_string(config_args); -P_DEFINE_double(max_diff_ratio, 0.0f, - "max diff ratio allowed for parameters value"); +P_DEFINE_double(max_diff_ratio, + 0.0f, + "max diff ratio allowed for parameters value"); int gNumDevices = 0; @@ -53,8 +54,7 @@ std::vector trainerOnePassTest(const string& configFile, FLAGS_config_args = sparseUpdate ? "sparse_update=1" : "sparse_update=0"; LOG(INFO) << " useGpu=" << useGpu << " trainerCount=" << trainerCount - << " configFile=" << configFile - << " sparseUpdate=" << sparseUpdate; + << " configFile=" << configFile << " sparseUpdate=" << sparseUpdate; srand(FLAGS_seed); *ThreadLocalRand::getSeed() = FLAGS_seed; ThreadLocalRandomEngine::get().seed(FLAGS_seed); @@ -91,8 +91,12 @@ std::vector& getDenseParameters() { return denseParameters; } -void checkBuffer(real* A, const char* desA, real* B, const char* desB, - size_t len, double maxDiffRatio) { +void checkBuffer(real* A, + const char* desA, + real* B, + const char* desB, + size_t len, + double maxDiffRatio) { double maxDiff = 0; double maxValue = 0; for (size_t i = 0; i < len; ++i) { @@ -101,10 +105,8 @@ void checkBuffer(real* A, const char* desA, real* B, const char* desB, maxDiff = std::max(maxDiff, diff); } EXPECT_LE(maxDiff / maxValue, maxDiffRatio); - LOG(INFO) << " maxDiff=" << maxDiff - << " maxValue=" << maxValue - << " maxDiff/maxValue=" << maxDiff / maxValue - << "\n\n"; + LOG(INFO) << " maxDiff=" << maxDiff << " maxValue=" << maxValue + << " maxDiff/maxValue=" << maxDiff / maxValue << "\n\n"; } void compareValue(const vector& parametersA, @@ -125,8 +127,12 @@ void compareValue(const vector& parametersA, LOG(INFO) << "\n\n----------- PARAMETER_VALUE: " << parameterA->getName() << " ; size : " << paraA.getSize() << " ------------"; - checkBuffer(paraA.getData(), "para_A", paraB.getData(), "para_B", - paraA.getSize(), maxDiffRatio); + checkBuffer(paraA.getData(), + "para_A", + paraB.getData(), + "para_B", + paraA.getSize(), + maxDiffRatio); } } @@ -172,8 +178,7 @@ TEST(compareSparse, multiGradientMachine) { if (useGpu) continue; #endif FLAGS_parallel_nn = useGpu; - LOG(INFO) << " local=" << local - << " useGpu=" << useGpu; + LOG(INFO) << " local=" << local << " useGpu=" << useGpu; int trainerCount = useGpu ? numGpu : 2; std::vector parameters = trainerOnePassTest(configFile1, true, trainerCount, useGpu); @@ -197,8 +202,7 @@ TEST(compareSparse, NeuralNetwork) { if (useGpu) continue; #endif FLAGS_parallel_nn = useGpu; - LOG(INFO) << " local=" << local - << " useGpu=" << useGpu; + LOG(INFO) << " local=" << local << " useGpu=" << useGpu; int trainerCount = 1; std::vector parameters = trainerOnePassTest(configFile1, true, trainerCount, useGpu); diff --git a/paddle/trainer/tests/test_CompareTwoNets.cpp b/paddle/trainer/tests/test_CompareTwoNets.cpp index d1057f2aea..81320da6ac 100644 --- a/paddle/trainer/tests/test_CompareTwoNets.cpp +++ b/paddle/trainer/tests/test_CompareTwoNets.cpp @@ -32,10 +32,12 @@ P_DECLARE_string(nics); P_DEFINE_string(config_file_a, "", "config of one network to compare"); P_DEFINE_string(config_file_b, "", "config of another network to compare"); -P_DEFINE_bool(need_high_accuracy, false, +P_DEFINE_bool(need_high_accuracy, + false, "whether need to run in double accuracy"); P_DEFINE_double( - max_diff_ratio, 0.0f, + max_diff_ratio, + 0.0f, "max diff ratio allowed for outputs and parameters (value/gradient)"); P_DECLARE_bool(thread_local_rand_use_global_seed); P_DECLARE_int32(seed); @@ -71,14 +73,18 @@ void calcGradient(ComData& data, const string configFile) { vector& inArgs = dataBatch.getStreams(); trainer.getGradientMachine()->start(trainer.getConfig(), nullptr); - trainer.getGradientMachine()->forwardBackward(inArgs, &data.outArgs, - PASS_TRAIN); + trainer.getGradientMachine()->forwardBackward( + inArgs, &data.outArgs, PASS_TRAIN); trainer.getGradientMachine()->finish(); } -void checkBuffer(real* A, const char* desA, real* B, const char* desB, - size_t len, size_t width = 1) { +void checkBuffer(real* A, + const char* desA, + real* B, + const char* desB, + size_t len, + size_t width = 1) { int nNum = 0; real maxVal = 0; for (size_t i = 0; i < len; ++i) { @@ -90,8 +96,8 @@ void checkBuffer(real* A, const char* desA, real* B, const char* desB, maxDiff = std::max(maxDiff, diff); if (diff > maxVal * FLAGS_max_diff_ratio) { nNum++; - VLOG(1) << "Row: " << i / width << ", " << desA << " : " << A[i] - << " " << desB << " : " << B[i] << " diff=" << diff; + VLOG(1) << "Row: " << i / width << ", " << desA << " : " << A[i] << " " + << desB << " : " << B[i] << " diff=" << diff; } } EXPECT_EQ(0, nNum); @@ -114,8 +120,12 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n--------------------------------" << " Check Network Output_" << i << ":" << " -------------------------------------\n"; - checkBuffer(matA.getData(), "network A output", matB.getData(), - "network B output", matA.getElementCnt(), matA.getWidth()); + checkBuffer(matA.getData(), + "network A output", + matB.getData(), + "network B output", + matA.getElementCnt(), + matA.getWidth()); } vector& parametersA = comDataA.parameters; @@ -136,7 +146,10 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n\n----------- PARAMETER_VALUE: " << parameterA->getName() << " ; size : " << paraA.getSize() << " ------------"; - checkBuffer(paraA.getData(), "Network A", paraB.getData(), "Network B", + checkBuffer(paraA.getData(), + "Network A", + paraB.getData(), + "Network B", paraA.getSize()); CpuVector gradA(*parameterA->getBuf(PARAMETER_GRADIENT)); @@ -144,7 +157,10 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n\n----------- PARAMETER_GRADIENT: " << parameterA->getName() << " ; size : " << gradA.getSize() << " -----------"; - checkBuffer(gradA.getData(), "Network A", gradB.getData(), "Network B", + checkBuffer(gradA.getData(), + "Network A", + gradB.getData(), + "Network B", gradA.getSize()); } } diff --git a/paddle/trainer/tests/test_CompareTwoOpts.cpp b/paddle/trainer/tests/test_CompareTwoOpts.cpp index 2c44da43fc..a52f2fa7e7 100644 --- a/paddle/trainer/tests/test_CompareTwoOpts.cpp +++ b/paddle/trainer/tests/test_CompareTwoOpts.cpp @@ -32,11 +32,13 @@ P_DECLARE_string(nics); P_DEFINE_string(config_file_a, "", "config of one network to compare"); P_DEFINE_string(config_file_b, "", "config of another network to compare"); -P_DEFINE_bool(need_high_accuracy, true, +P_DEFINE_bool(need_high_accuracy, + true, "whether need to run in double accuracy (recommended)"); P_DEFINE_double( - max_diff_ratio, 0.0f, - "max diff ratio allowed for outputs and parameters (value/gradient)"); + max_diff_ratio, + 0.0f, + "max diff ratio allowed for outputs and parameters (value/gradient)"); struct ComData { vector outArgs; @@ -62,8 +64,12 @@ void calcGradient(ComData& data, const string configFile) { trainer.train(); } -void checkBuffer(real* A, const char* desA, real* B, const char* desB, - size_t len, size_t width = 1) { +void checkBuffer(real* A, + const char* desA, + real* B, + const char* desB, + size_t len, + size_t width = 1) { int nNum = 0; for (size_t i = 0; i < len; ++i) { real diff = fabs(A[i] - B[i]); @@ -94,8 +100,12 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n--------------------------------" << " Check Network Output_" << i << ":" << " -------------------------------------\n"; - checkBuffer(matA.getData(), "network A output", matB.getData(), - "network B output", matA.getElementCnt(), matA.getWidth()); + checkBuffer(matA.getData(), + "network A output", + matB.getData(), + "network B output", + matA.getElementCnt(), + matA.getWidth()); } vector& parametersA = comDataA.parameters; @@ -116,7 +126,10 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n\n----------- PARAMETER_VALUE: " << parameterA->getName() << " ; size : " << paraA.getSize() << " ------------"; - checkBuffer(paraA.getData(), "Network A", paraB.getData(), "Network B", + checkBuffer(paraA.getData(), + "Network A", + paraB.getData(), + "Network B", paraA.getSize()); CpuVector gradA(*parameterA->getBuf(PARAMETER_GRADIENT)); @@ -124,7 +137,10 @@ void compareGradient(ComData& comDataA, ComData& comDataB) { LOG(INFO) << "\n\n----------- PARAMETER_GRADIENT: " << parameterA->getName() << " ; size : " << gradA.getSize() << " -----------"; - checkBuffer(gradA.getData(), "Network A", gradB.getData(), "Network B", + checkBuffer(gradA.getData(), + "Network A", + gradB.getData(), + "Network B", gradA.getSize()); } } diff --git a/paddle/trainer/tests/test_Prediction.cpp b/paddle/trainer/tests/test_Prediction.cpp index 1c7f93666b..6db33439b3 100644 --- a/paddle/trainer/tests/test_Prediction.cpp +++ b/paddle/trainer/tests/test_Prediction.cpp @@ -20,7 +20,8 @@ limitations under the License. */ P_DECLARE_string(config); P_DECLARE_string(config_args); -P_DEFINE_string(merger, "./paddle_merge_model", +P_DEFINE_string(merger, + "./paddle_merge_model", "path to paddle_merge_model binary"); using namespace paddle; // NOLINT @@ -120,8 +121,10 @@ TEST(GradientMachine, create) { rand() / (real)RAND_MAX; // NOLINT TODO(yuyang): use rand_r } } - MatrixPtr input = Matrix::create(numSamples, inputDim, - /* trans */ false, FLAGS_use_gpu); + MatrixPtr input = Matrix::create(numSamples, + inputDim, + /* trans */ false, + FLAGS_use_gpu); input->copyFrom(cpuInput); inArgs[0].value = input; gradientMachine1->forward(inArgs, &outArgs, PASS_TEST); @@ -139,8 +142,8 @@ TEST(GradientMachine, create) { gradientMachine3->forward(inArgs, &outArgs2, PASS_TEST); out2.copyFrom(*outArgs2[0].value); - checkBuffer(out1.getData(), out2.getData(), - out2.getHeight() * out2.getWidth()); + checkBuffer( + out1.getData(), out2.getData(), out2.getHeight() * out2.getWidth()); cmd = " rm -rf " + modelDir + "/*"; LOG(INFO) << "cmd " << cmd; diff --git a/paddle/trainer/tests/test_PyDataProviderWrapper.cpp b/paddle/trainer/tests/test_PyDataProviderWrapper.cpp index 49332b877d..e53291386c 100644 --- a/paddle/trainer/tests/test_PyDataProviderWrapper.cpp +++ b/paddle/trainer/tests/test_PyDataProviderWrapper.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #ifndef PADDLE_NO_PYTHON #include #include diff --git a/paddle/trainer/tests/test_Trainer.cpp b/paddle/trainer/tests/test_Trainer.cpp index ad2a715ef8..900c05af85 100644 --- a/paddle/trainer/tests/test_Trainer.cpp +++ b/paddle/trainer/tests/test_Trainer.cpp @@ -33,7 +33,9 @@ P_DECLARE_string(config); P_DECLARE_int32(gpu_id); P_DECLARE_bool(allow_only_one_model_on_one_gpu); -void checkGradientTest(const string& configFile, bool useGpu, bool parallel, +void checkGradientTest(const string& configFile, + bool useGpu, + bool parallel, int trainerCount = 1) { FLAGS_use_gpu = useGpu; FLAGS_parallel_nn = parallel; @@ -94,7 +96,7 @@ TEST(checkGradient, multi) { TEST(checkGradient, hsigmoid) { checkGradientTest(configFile2, false, false); } TEST(checkGradient, chunk) { -#if defined(__APPLE__) || defined (__OSX__) +#if defined(__APPLE__) || defined(__OSX__) EXPECT_EQ(0, system("python trainer/tests/gen_proto_data.py")); #else EXPECT_EQ(0, system("python2 trainer/tests/gen_proto_data.py")); diff --git a/paddle/trainer/tests/test_TrainerOnePass.cpp b/paddle/trainer/tests/test_TrainerOnePass.cpp index 4554b94485..da2954d166 100644 --- a/paddle/trainer/tests/test_TrainerOnePass.cpp +++ b/paddle/trainer/tests/test_TrainerOnePass.cpp @@ -41,12 +41,13 @@ public: } }; - - int gNumDevices = 0; -void trainerOnePassTest(const string& configFile, bool useGpu, bool parallel, - int trainerCount = 1, double averageWindow = 0.0f, +void trainerOnePassTest(const string& configFile, + bool useGpu, + bool parallel, + int trainerCount = 1, + double averageWindow = 0.0f, bool doAverageInCpu = false) { FLAGS_use_gpu = useGpu; FLAGS_parallel_nn = parallel; @@ -164,13 +165,13 @@ double checkRemoteParameterUpdater(TrainerForTest& trainer) { const vector& inArgs = dataBatch.getStreams(); vector outArgs; - UpdateCallback updateCallback = - [parameterUpdater, parameterCheck](Parameter* para) { - parameterCheck[para->getID()] - ->getBuf(PARAMETER_GRADIENT) - ->copyFrom(*para->getBuf(PARAMETER_GRADIENT)); - parameterUpdater->update(para); - }; + UpdateCallback updateCallback = [parameterUpdater, + parameterCheck](Parameter* para) { + parameterCheck[para->getID()] + ->getBuf(PARAMETER_GRADIENT) + ->copyFrom(*para->getBuf(PARAMETER_GRADIENT)); + parameterUpdater->update(para); + }; parameterUpdater->startPass(); parameterUpdaterCheck->startPass(); @@ -178,8 +179,8 @@ double checkRemoteParameterUpdater(TrainerForTest& trainer) { for (int i = 0; i < config.opt_config().num_batches_per_get_parameter() * 2; ++i) { PassType passType = parameterUpdater->startBatch(actualBatchSize); - gradientMachine->forwardBackward(inArgs, &outArgs, passType, - updateCallback); + gradientMachine->forwardBackward( + inArgs, &outArgs, passType, updateCallback); parameterUpdater->finishBatch(0); parameterUpdaterCheck->startBatch(actualBatchSize); @@ -191,7 +192,7 @@ double checkRemoteParameterUpdater(TrainerForTest& trainer) { double sum = 0.0f; for (size_t i = 0; i != parameters.size(); ++i) { - real* v1, *v2; + real *v1, *v2; CpuVector trainerPara(parameters[i]->getSize()); trainerPara.copyFrom(*parameters[i]->getBuf(PARAMETER_VALUE)); if (!FLAGS_use_gpu) { @@ -217,8 +218,10 @@ double checkRemoteParameterUpdater(TrainerForTest& trainer) { return sum; } -void checkRemoteParameterUpdaterTest(const string& configFile, bool useGpu, - bool parallel, int trainerCount = 1, +void checkRemoteParameterUpdaterTest(const string& configFile, + bool useGpu, + bool parallel, + int trainerCount = 1, bool useOldUpdater = false, int num_batches_per_get_parameter = 1) { FLAGS_use_gpu = useGpu; diff --git a/paddle/trainer/tests/test_recurrent_machine_generation.cpp b/paddle/trainer/tests/test_recurrent_machine_generation.cpp index fcee318d16..49e8a97ad0 100644 --- a/paddle/trainer/tests/test_recurrent_machine_generation.cpp +++ b/paddle/trainer/tests/test_recurrent_machine_generation.cpp @@ -51,8 +51,10 @@ void checkOutput(const string& expRetFile) { } } -void prepareInArgs(vector& inArgs, const size_t batchSize, - bool useGpu, bool hasSubseq) { +void prepareInArgs(vector& inArgs, + const size_t batchSize, + bool useGpu, + bool hasSubseq) { inArgs.clear(); // sentence id Argument sentId; @@ -87,7 +89,9 @@ void prepareInArgs(vector& inArgs, const size_t batchSize, inArgs.emplace_back(dummyInput); } -void testGeneration(const string& configFile, bool useGpu, bool hasSubseq, +void testGeneration(const string& configFile, + bool useGpu, + bool hasSubseq, const string& expRetFile) { FLAGS_use_gpu = useGpu; auto config = std::make_shared(configFile); @@ -114,8 +118,10 @@ TEST(RecurrentGradientMachine, test_generation) { #else const auto useGpuConfs = {true, false}; #endif - auto testGen = [&](const string& configFile, bool hasSubseq, - const string& expRetFile, bool beam_search) { + auto testGen = [&](const string& configFile, + bool hasSubseq, + const string& expRetFile, + bool beam_search) { FLAGS_config_args = beam_search ? "beam_search=1" : "beam_search=0"; for (auto useGpu : useGpuConfs) { testGeneration(configFile, useGpu, hasSubseq, expRetFile); @@ -126,7 +132,9 @@ TEST(RecurrentGradientMachine, test_generation) { // In hierarchical RNN, beam search and one way search are only in inner-RNN, // outer-RNN will concat the generated inner-results (first for beam search) // from inner-RNN. Thus, they have the same outer-results. - testGen(NEST_CONFIG_FILE, true, expectFile + ".nest", + testGen(NEST_CONFIG_FILE, + true, + expectFile + ".nest", false); // no beam search testGen(NEST_CONFIG_FILE, true, expectFile + ".nest", true); // beam search } diff --git a/paddle/utils/BarrierStat.cpp b/paddle/utils/BarrierStat.cpp index f083ef3982..82c5b84e59 100644 --- a/paddle/utils/BarrierStat.cpp +++ b/paddle/utils/BarrierStat.cpp @@ -20,17 +20,19 @@ limitations under the License. */ #include "paddle/utils/BarrierStat.h" #include "paddle/utils/Flags.h" -P_DEFINE_bool(log_barrier_abstract, true, +P_DEFINE_bool(log_barrier_abstract, + true, "if true, show abstract of barrier performance"); -P_DEFINE_int32(log_barrier_lowest_nodes, 5, +P_DEFINE_int32(log_barrier_lowest_nodes, + 5, "how many lowest node will be logged"); -P_DEFINE_bool(log_barrier_show_log, false, // for performance tuning insight +P_DEFINE_bool(log_barrier_show_log, + false, // for performance tuning insight "if true, always show barrier abstract even with little gap"); namespace paddle { -std::ostream &operator<<(std::ostream &output, - const BarrierStatBase &stat) { +std::ostream &operator<<(std::ostream &output, const BarrierStatBase &stat) { if (FLAGS_log_barrier_abstract) { std::lock_guard guard(stat.lock_); stat.showAbstract(output); @@ -144,7 +146,8 @@ void BarrierEndStat::showAbstract(std::ostream &output) const { // duplicate freq info std::vector outputAbstract = abstract_; - std::sort(outputAbstract.begin(), outputAbstract.end(), + std::sort(outputAbstract.begin(), + outputAbstract.end(), [](const struct Abstract &a, const struct Abstract &b) { return a.freq > b.freq; }); @@ -280,7 +283,8 @@ void BarrierDeltaStat::showAbstract(std::ostream &output) const { // duplicate freq info std::vector outputAbstract = abstract_; - std::sort(outputAbstract.begin(), outputAbstract.end(), + std::sort(outputAbstract.begin(), + outputAbstract.end(), [](const struct Abstract &a, const struct Abstract &b) { return a.freq > b.freq; }); diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h index add1093758..661340ad27 100644 --- a/paddle/utils/BarrierStat.h +++ b/paddle/utils/BarrierStat.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -305,44 +304,44 @@ private: // nodes. // end barrier -#define __REGISTER_BARRIER_TIMER_SERVER(set, statName, numConnThreads, \ - trainerId, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - struct timeval cur; \ - gettimeofday(&cur, nullptr); \ - __stat->updateStat(cur, trainerId); \ - } \ +#define __REGISTER_BARRIER_TIMER_SERVER( \ + set, statName, numConnThreads, trainerId, ...) \ + do { \ + if (numConnThreads > 2) { \ + std::string internalName = \ + std::string(statName) + std::string(__VA_ARGS__); \ + BarrierStatPtr __stat = \ + (set).getStat(numConnThreads, internalName, BARRIER_END); \ + struct timeval cur; \ + gettimeofday(&cur, nullptr); \ + __stat->updateStat(cur, trainerId); \ + } \ } while (0); // end barrier with user-defined timer -#define __REGISTER_BARRIER_TIMER_SERVER_SET(set, statName, numConnThreads, \ - trainerId, cur, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - __stat->updateStat(cur, trainerId); \ - } \ +#define __REGISTER_BARRIER_TIMER_SERVER_SET( \ + set, statName, numConnThreads, trainerId, cur, ...) \ + do { \ + if (numConnThreads > 2) { \ + std::string internalName = \ + std::string(statName) + std::string(__VA_ARGS__); \ + BarrierStatPtr __stat = \ + (set).getStat(numConnThreads, internalName, BARRIER_END); \ + __stat->updateStat(cur, trainerId); \ + } \ } while (0); // delta barrier -#define __REGISTER_BARRIER_DELTA_SERVER_SET(set, statName, numConnThreads, \ - trainerId, delta, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_DELTA); \ - __stat->updateStat(delta, trainerId); \ - } \ +#define __REGISTER_BARRIER_DELTA_SERVER_SET( \ + set, statName, numConnThreads, trainerId, delta, ...) \ + do { \ + if (numConnThreads > 2) { \ + std::string internalName = \ + std::string(statName) + std::string(__VA_ARGS__); \ + BarrierStatPtr __stat = \ + (set).getStat(numConnThreads, internalName, BARRIER_DELTA); \ + __stat->updateStat(delta, trainerId); \ + } \ } while (0); // check end barrier @@ -374,10 +373,10 @@ private: */ // try to capture which trainer is slowest node in sync-sgd at pserver. -#define REGISTER_SLOW_NODES_PROBE(set, statName, numConnThreads, trainerId, \ - ...) \ - __REGISTER_BARRIER_TIMER_SERVER((set), statName, numConnThreads, trainerId, \ - __VA_ARGS__) +#define REGISTER_SLOW_NODES_PROBE( \ + set, statName, numConnThreads, trainerId, ...) \ + __REGISTER_BARRIER_TIMER_SERVER( \ + (set), statName, numConnThreads, trainerId, __VA_ARGS__) // try to check if all threads or trainers have passed barriers for data // accuracy. #define CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ @@ -385,12 +384,12 @@ private: #ifdef PADDLE_DISABLE_TIMER -#define REGISTER_BARRIER_TIMER_SERVER(set, statName, numConnThreads, \ - trainerId, ...) -#define REGISTER_BARRIER_TIMER_SERVER_SET(set, statName, numConnThreads, \ - trainerId, cur, ...) -#define REGISTER_BARRIER_DELTA_SERVER_SET(set, statName, numConnThreads, \ - trainerId, cur, ...) +#define REGISTER_BARRIER_TIMER_SERVER( \ + set, statName, numConnThreads, trainerId, ...) +#define REGISTER_BARRIER_TIMER_SERVER_SET( \ + set, statName, numConnThreads, trainerId, cur, ...) +#define REGISTER_BARRIER_DELTA_SERVER_SET( \ + set, statName, numConnThreads, trainerId, cur, ...) #else @@ -398,10 +397,10 @@ private: * sensing barrier time distribution for all parallelization threads. * it provides low API for slow node check(REGISTER_SLOW_NODES_PROBE) */ -#define REGISTER_BARRIER_TIMER_SERVER(set, statName, numConnThreads, \ - trainerId, ...) \ - __REGISTER_BARRIER_TIMER_SERVER((set), statName, numConnThreads, trainerId, \ - __VA_ARGS__) +#define REGISTER_BARRIER_TIMER_SERVER( \ + set, statName, numConnThreads, trainerId, ...) \ + __REGISTER_BARRIER_TIMER_SERVER( \ + (set), statName, numConnThreads, trainerId, __VA_ARGS__) /* * sensing barrier time distribution for all parallelization threads. @@ -410,18 +409,18 @@ private: * time distribution * for receiving data. */ -#define REGISTER_BARRIER_TIMER_SERVER_SET(set, statName, numConnThreads, \ - trainerId, cur, ...) \ - __REGISTER_BARRIER_TIMER_SERVER_SET((set), statName, numConnThreads, \ - trainerId, cur, __VA_ARGS__) +#define REGISTER_BARRIER_TIMER_SERVER_SET( \ + set, statName, numConnThreads, trainerId, cur, ...) \ + __REGISTER_BARRIER_TIMER_SERVER_SET( \ + (set), statName, numConnThreads, trainerId, cur, __VA_ARGS__) // try to capture time delta from all trainers, such as forwardBackward time // which implies // computation fluctuation -#define REGISTER_BARRIER_DELTA_SERVER_SET(set, statName, numConnThreads, \ - trainerId, delta, ...) \ - __REGISTER_BARRIER_DELTA_SERVER_SET((set), statName, numConnThreads, \ - trainerId, delta, __VA_ARGS__) +#define REGISTER_BARRIER_DELTA_SERVER_SET( \ + set, statName, numConnThreads, trainerId, delta, ...) \ + __REGISTER_BARRIER_DELTA_SERVER_SET( \ + (set), statName, numConnThreads, trainerId, delta, __VA_ARGS__) #endif // DISABLE_TIMER } // namespace paddle diff --git a/paddle/utils/ClassRegistrar.h b/paddle/utils/ClassRegistrar.h index 0c7747ac77..ee58ccb2ad 100644 --- a/paddle/utils/ClassRegistrar.h +++ b/paddle/utils/ClassRegistrar.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -63,16 +62,16 @@ public: // Create a class instance of type @type using args BaseClass* createByType(const std::string& type, CreateArgs... args) { ClassCreator creator; - CHECK(mapGet(type, creatorMap_, &creator)) - << "Unknown class type: " << type; + CHECK(mapGet(type, creatorMap_, &creator)) << "Unknown class type: " + << type; return creator(args...); } template inline void forEachType(T callback) { - for (auto it = creatorMap_.begin(); it != creatorMap_.end(); ++it) { - callback(it->first); - } + for (auto it = creatorMap_.begin(); it != creatorMap_.end(); ++it) { + callback(it->first); + } } protected: diff --git a/paddle/utils/CommandLineParser.cpp b/paddle/utils/CommandLineParser.cpp index 8edcad5747..307e304bb0 100644 --- a/paddle/utils/CommandLineParser.cpp +++ b/paddle/utils/CommandLineParser.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "CommandLineParser.h" #ifndef PADDLE_USE_GFLAGS #include "paddle/utils/StringUtil.h" @@ -31,7 +30,6 @@ static constexpr int kStatusOK = 0; static constexpr int kStatusInvalid = 1; static constexpr int kStatusNotFound = 2; - /** * \brief: Convert a string to any type value. * @@ -48,13 +46,16 @@ template <> bool StringToValue(const std::string& content, bool* value) { std::string tmp = content; - std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](char in) -> char { - if (in <= 'Z' && in >= 'A') { - return in - ('Z' - 'z'); - } else { - return in; - } - }); // tolower. + std::transform(tmp.begin(), + tmp.end(), + tmp.begin(), + [](char in) -> char { + if (in <= 'Z' && in >= 'A') { + return in - ('Z' - 'z'); + } else { + return in; + } + }); // tolower. if (tmp == "true" || tmp == "1") { *value = true; @@ -121,20 +122,16 @@ int ParseArgument(const std::string& argument, std::string* extraInfo) { * parse '--flag_name', '-flag_name' as true; '--noflag_name', '-noflag_name' as * false */ -static int ParseBoolArgumentExtra( - const std::string& argument, std::string* extraInfo) { +static int ParseBoolArgumentExtra(const std::string& argument, + std::string* extraInfo) { (void)(extraInfo); // unused extraInfo, just make api same. //! @warning: The order and content of prefixes is DESIGNED for parsing //! command line. The length of prefixes are 1, 2, 3, 4. The parse logic takes //! use of this fact. DO NOT CHANGE IT without reading how to parse command //! below. - static const std::vector > prefixes = { - {"-", true}, - {"--", true}, - {"-no", false}, - {"--no", false} - }; + static const std::vector> prefixes = { + {"-", true}, {"--", true}, {"-no", false}, {"--no", false}}; for (flags_internal::CommandLineFlagRegistry::Command& command : flags_internal::CommandLineFlagRegistry::Instance()->commands) { @@ -153,7 +150,6 @@ static int ParseBoolArgumentExtra( return kStatusNotFound; } - /** * \brief: Print command line arguments' usage with type T. */ @@ -170,12 +166,9 @@ static void PrintTypeUsage() { } } -template +template static void PrintTypeUsages() { - int unused[] = { - 0, - (PrintTypeUsage(), 0) ... - }; + int unused[] = {0, (PrintTypeUsage(), 0)...}; (void)(unused); } /** @@ -190,7 +183,8 @@ static void PrintUsageAndExit(const char* argv0) { /** * \brief: Print the error flags, usage, and exit. */ -static void PrintParseError(const std::string& name, const char* actualInput, +static void PrintParseError(const std::string& name, + const char* actualInput, const char* arg0) { std::cerr << "Parse command flag " << name << " error! User input is " << actualInput << std::endl; @@ -211,7 +205,7 @@ void ParseCommandLineFlags(int* argc, char** argv, bool withHelp) { PrintParseError(extra, argv[i], argv[0]); \ } - ParseArgumentWithType(bool); // NOLINT + ParseArgumentWithType(bool); // NOLINT ParseArgumentWithType(int32_t); ParseArgumentWithType(double); // NOLINT ParseArgumentWithType(int64_t); diff --git a/paddle/utils/CommandLineParser.h b/paddle/utils/CommandLineParser.h index d18675ffa3..c46567913e 100644 --- a/paddle/utils/CommandLineParser.h +++ b/paddle/utils/CommandLineParser.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #ifndef PADDLE_USE_GFLAGS #include "DisableCopy.h" @@ -72,7 +71,8 @@ struct CommandLineFlagRegister { * \param [inout] val: The command line argument instance, FLAGS_xxx. * \param [in] desc: The command line helper message. */ - CommandLineFlagRegister(const std::string& name, T* val, + CommandLineFlagRegister(const std::string& name, + T* val, const std::string desc) { CommandLineFlagRegistry::Instance()->commands.push_back( {name, val, desc, *val}); @@ -83,7 +83,8 @@ struct CommandLineFlagRegister { * \brief: Define a command line arguments. * * \param type: The variable type, such as int, double, etc. - * \param name: The variable name. The command line argument is '--name', the variable + * \param name: The variable name. The command line argument is '--name', the + *variable *is 'FLAGS_name' * \param default_value: The default value of command line argument. * \param text: The description in command line argument. diff --git a/paddle/utils/CustomStackTrace.cpp b/paddle/utils/CustomStackTrace.cpp index 232a478ecd..8740fe662e 100644 --- a/paddle/utils/CustomStackTrace.cpp +++ b/paddle/utils/CustomStackTrace.cpp @@ -12,12 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "CustomStackTrace.h" #include "CommandLineParser.h" #include -P_DEFINE_bool(layer_stack_error_only_current_thread, +P_DEFINE_bool( + layer_stack_error_only_current_thread, true, "Dump current thread or whole process layer stack when signal error " "occurred. true means only dump current thread layer stack"); @@ -33,21 +33,23 @@ void installLayerStackTracer() { if (!gLayerStackTrace.empty()) { size_t curTid = -1UL; std::hash hasher; - gLayerStackTrace.dump([&curTid, &hasher](std::thread::id tid, - bool* isForwarding, - const std::string& layerName) { - if (curTid != hasher(tid)) { - if (curTid != -1UL) { - std::cerr << std::endl; - } - curTid = hasher(tid); - std::cerr << "Thread [" << tid << "] "; - if (isForwarding) { - std::cerr << (*isForwarding ? "Forwarding ": "Backwarding "); - } - } - std::cerr << layerName << ", "; - }, FLAGS_layer_stack_error_only_current_thread); + gLayerStackTrace.dump( + [&curTid, &hasher](std::thread::id tid, + bool* isForwarding, + const std::string& layerName) { + if (curTid != hasher(tid)) { + if (curTid != -1UL) { + std::cerr << std::endl; + } + curTid = hasher(tid); + std::cerr << "Thread [" << tid << "] "; + if (isForwarding) { + std::cerr << (*isForwarding ? "Forwarding " : "Backwarding "); + } + } + std::cerr << layerName << ", "; + }, + FLAGS_layer_stack_error_only_current_thread); std::cerr << std::endl; } std::cerr.write(data, sz); diff --git a/paddle/utils/CustomStackTrace.h b/paddle/utils/CustomStackTrace.h index 774c4db2b9..878e14eb5f 100644 --- a/paddle/utils/CustomStackTrace.h +++ b/paddle/utils/CustomStackTrace.h @@ -24,13 +24,13 @@ limitations under the License. */ namespace paddle { /** - * A ThreadLocal stack for tracing train/test process. - * (More details of ThreadLocal can be find + * A ThreadLocal stack for tracing train/test process. + * (More details of ThreadLocal can be find * in the comments of ThreadLocal class.) - * + * * For example. * @code{.cpp} - * + * * paddle::CustomStackTrace stack; * for (auto& layer : layers){ * stack.push(layer->getName()); @@ -48,7 +48,7 @@ namespace paddle { * @endcode */ template -class CustomStackTrace{ +class CustomStackTrace { public: /** * @brief Pop out an item from the top of the stack if item == top. @@ -87,7 +87,6 @@ public: return true; } - /** * @brief DumpCallback Type. It will be invoked many times by dump method. * @@ -96,8 +95,8 @@ public: * The third parameter is the item in stack. */ typedef std::function DumpCallback; + bool* /*isPushing*/, + const T& /*item*/)> DumpCallback; /** * Dump all thread stack, and all stack will be cleared. @@ -160,25 +159,23 @@ private: * @brief Get thread local stack reference. */ std::stack& stack() { - return this->getThreadLocal(this->logStack_, - this->stackBuffers_); + return this->getThreadLocal(this->logStack_, this->stackBuffers_); } /** * @brief Get thread local pushing flag. */ bool& pushing() { - return this->getThreadLocal(this->isPushing_, - this->pushingBuffers_); + return this->getThreadLocal(this->isPushing_, this->pushingBuffers_); } private: mutable std::mutex mtx_; - std::unordered_map* > stackBuffers_; - std::unordered_map pushingBuffers_; + std::unordered_map*> stackBuffers_; + std::unordered_map pushingBuffers_; ThreadLocal isPushing_; - ThreadLocal > logStack_; + ThreadLocal> logStack_; }; extern CustomStackTrace gLayerStackTrace; diff --git a/paddle/utils/DisableCopy.h b/paddle/utils/DisableCopy.h index 964daa237b..e991c07cdf 100644 --- a/paddle/utils/DisableCopy.h +++ b/paddle/utils/DisableCopy.h @@ -12,13 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once /** * Disable copy macro. */ -#define DISABLE_COPY(CLASS_NAME)\ - CLASS_NAME(CLASS_NAME &&) = delete; \ +#define DISABLE_COPY(CLASS_NAME) \ + CLASS_NAME(CLASS_NAME &&) = delete; \ CLASS_NAME(const CLASS_NAME &other) = delete; \ - CLASS_NAME& operator=(const CLASS_NAME &other) = delete + CLASS_NAME &operator=(const CLASS_NAME &other) = delete diff --git a/paddle/utils/Excepts.cpp b/paddle/utils/Excepts.cpp index 9123508fc7..b2fad3ac9d 100644 --- a/paddle/utils/Excepts.cpp +++ b/paddle/utils/Excepts.cpp @@ -27,28 +27,28 @@ int feenableexcept(unsigned int excepts) { static fenv_t fenv; unsigned int new_excepts = excepts & FE_ALL_EXCEPT, old_excepts; - if ( fegetenv (&fenv) ) return -1; + if (fegetenv(&fenv)) return -1; old_excepts = fenv.__control & FE_ALL_EXCEPT; // unmask fenv.__control &= ~new_excepts; - fenv.__mxcsr &= ~(new_excepts << 7); + fenv.__mxcsr &= ~(new_excepts << 7); - return ( fesetenv (&fenv) ? -1 : old_excepts ); + return (fesetenv(&fenv) ? -1 : old_excepts); } int fedisableexcept(unsigned int excepts) { static fenv_t fenv; unsigned int new_excepts = excepts & FE_ALL_EXCEPT, old_excepts; - if ( fegetenv (&fenv) ) return -1; + if (fegetenv(&fenv)) return -1; old_excepts = fenv.__control & FE_ALL_EXCEPT; // mask fenv.__control |= new_excepts; - fenv.__mxcsr |= new_excepts << 7; + fenv.__mxcsr |= new_excepts << 7; - return ( fesetenv (&fenv) ? -1 : old_excepts ); + return (fesetenv(&fenv) ? -1 : old_excepts); } #endif diff --git a/paddle/utils/Flags.cpp b/paddle/utils/Flags.cpp index b2b5a5949e..6fae24e1b5 100644 --- a/paddle/utils/Flags.cpp +++ b/paddle/utils/Flags.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Flags.h" #ifdef PADDLE_ONLY_CPU @@ -22,7 +21,8 @@ P_DEFINE_bool(use_gpu, true, "Whether to use GPU for training"); #endif P_DEFINE_bool( - parallel_nn, false, + parallel_nn, + false, "Whether to use multi-threads to calculate one neural network." "If it was set false, use gpu_id specify which gpu core to use" "(the device property in the trainer config file will be ingored)." @@ -32,39 +32,48 @@ P_DEFINE_int32(trainer_count, 1, "Defined how many trainers to train"); P_DEFINE_int32(gpu_id, 0, "Which gpu core to use"); P_DEFINE_int32(port, 20134, "Listening port for pserver"); P_DEFINE_int32(data_server_port, 21134, "Listening port for dserver"); -P_DEFINE_int32(ports_num, 1, +P_DEFINE_int32(ports_num, + 1, "The ports number for parameter send," " increment based on default port number"); -P_DEFINE_int32(ports_num_for_sparse, 0, +P_DEFINE_int32(ports_num_for_sparse, + 0, "The ports number for parameter send," " increment based on default (port + ports_num)"); P_DEFINE_string(nics, "xgbe0,xgbe1", "network device name for pservers"); P_DEFINE_string(rdma_tcp, "tcp", "use rdma or tcp rdma transport protocol"); P_DEFINE_int32( - trainer_id, 0, + trainer_id, + 0, "For distributed training, each trainer must be given an unique id" " ranging from 0 to num_trainers-1. Trainer 0 is the master" " trainer"); P_DEFINE_int32(num_gradient_servers, 1, "number of gradient servers"); P_DEFINE_string(comment, "", "A string for commenting this training task"); -P_DEFINE_string(load_missing_parameter_strategy, "fail", +P_DEFINE_string(load_missing_parameter_strategy, + "fail", "which operation to take on load model fails. support " "fail/rand/zero only."); P_DEFINE_int32(log_period, 100, "Log progress every so many batches"); -P_DEFINE_int32(log_period_server, 500, +P_DEFINE_int32(log_period_server, + 500, "Log progress every so many batches at pserver end"); P_DEFINE_double(checkgrad_eps, 1e-5, "parameter change size for checkgrad"); -P_DEFINE_int32(enable_parallel_vector, 0, +P_DEFINE_int32(enable_parallel_vector, + 0, "threshold for enable parallel vector"); -P_DEFINE_bool(loadsave_parameters_in_pserver, false, +P_DEFINE_bool(loadsave_parameters_in_pserver, + false, "load and save parameters in pserver. " "only work while parameter set sparse_remote_update."); -P_DEFINE_int32(beam_size, 1, +P_DEFINE_int32(beam_size, + 1, "Beam size used in generating most probable output sequences."); P_DEFINE_bool(show_layer_stat, false, "show the statistics of each layer"); P_DEFINE_string(predict_file, "", "File name for saving predict result"); P_DEFINE_bool(prev_batch_state, false, "batch is continue with next batch"); -P_DEFINE_string(init_model_path, "", +P_DEFINE_string(init_model_path, + "", "Path of the initial model parameters." "If it was set, start_pass will be ignored."); diff --git a/paddle/utils/Flags.h b/paddle/utils/Flags.h index b23a29eff9..dda60c3f96 100644 --- a/paddle/utils/Flags.h +++ b/paddle/utils/Flags.h @@ -12,7 +12,6 @@ WITHOUT 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 "CommandLineParser.h" diff --git a/paddle/utils/GlobalConstants.cpp b/paddle/utils/GlobalConstants.cpp index 8ed6471e4e..d769cd1ee7 100644 --- a/paddle/utils/GlobalConstants.cpp +++ b/paddle/utils/GlobalConstants.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "GlobalConstants.h" namespace paddle { diff --git a/paddle/utils/GlobalConstants.h b/paddle/utils/GlobalConstants.h index 8818b014f8..4c74c17a50 100644 --- a/paddle/utils/GlobalConstants.h +++ b/paddle/utils/GlobalConstants.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -20,9 +19,9 @@ namespace paddle { namespace enumeration_wrapper { enum PassType { - PASS_TRAIN, // Train pass - PASS_TEST, // Test pass - PASS_GC, // Gradient Check pass + PASS_TRAIN, // Train pass + PASS_TEST, // Test pass + PASS_GC, // Gradient Check pass PASS_METRIC, // pass for generate template output with no drop rate. // pass for metric learning training with metric learning error, only used // when we are doing KNN evaluation. @@ -81,7 +80,7 @@ enum ParameterType { } // namespace enumeration_wrapper //! explicit import enum into paddle namespace. -using namespace enumeration_wrapper; // NOLINT +using namespace enumeration_wrapper; // NOLINT class TrainAlgorithm { public: diff --git a/paddle/utils/Locks.h b/paddle/utils/Locks.h index 1fc0363d34..5990e16570 100644 --- a/paddle/utils/Locks.h +++ b/paddle/utils/Locks.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -26,7 +25,7 @@ namespace paddle { /** * A simple read-write lock. - * The RWlock allows a number of readers or at most one writer + * The RWlock allows a number of readers or at most one writer * at any point in time. * The RWlock disable copy. * @@ -37,7 +36,7 @@ namespace paddle { * * Use lock_shared() to lock on read mode, other thread can get * it by using the same method lock_shared(). - * + * * Unlock: * * Use unlock() to unlock the lock. @@ -68,13 +67,13 @@ protected: }; /** - * The ReadLockGuard is a read mode RWLock - * using RAII management mechanism. + * The ReadLockGuard is a read mode RWLock + * using RAII management mechanism. */ class ReadLockGuard { public: /** - * @brief Construct Function. Lock on rwlock in read mode. + * @brief Construct Function. Lock on rwlock in read mode. */ explicit ReadLockGuard(RWLock& rwlock) : rwlock_(&rwlock) { rwlock_->lock_shared(); @@ -82,7 +81,7 @@ public: /** * @brief Destruct Function. - * @note This method just unlock the read mode rwlock, + * @note This method just unlock the read mode rwlock, * won't destroy the lock. */ ~ReadLockGuard() { rwlock_->unlock(); } @@ -120,16 +119,15 @@ class Semaphore { public: //! Disable copy & assign Semaphore(const Semaphore& other) = delete; - Semaphore& operator= (const Semaphore&& other) = delete; + Semaphore& operator=(const Semaphore&& other) = delete; //! Enable move. - Semaphore(Semaphore&& other): m(std::move(other.m)) { - } + Semaphore(Semaphore&& other) : m(std::move(other.m)) {} public: /** - * @brief Construct Function. - * @param[in] initValue the initial value of the + * @brief Construct Function. + * @param[in] initValue the initial value of the * semaphore, default 0. */ explicit Semaphore(int initValue = 0); @@ -137,22 +135,23 @@ public: ~Semaphore(); /** - * @brief The same as wait(), except if the decrement can not + * @brief The same as wait(), except if the decrement can not * be performed until ts return false install of blocking. - * @param[in] ts an absolute timeout in seconds and nanoseconds + * @param[in] ts an absolute timeout in seconds and nanoseconds * since the Epoch 1970-01-01 00:00:00 +0000(UTC). - * @return ture if the decrement proceeds before ts, + * @return ture if the decrement proceeds before ts, * else return false. */ bool timeWait(struct timespec* ts); /** - * @brief decrement the semaphore. If the semaphore's value is 0, then call blocks. + * @brief decrement the semaphore. If the semaphore's value is 0, then call + * blocks. */ void wait(); /** - * @brief increment the semaphore. If the semaphore's value + * @brief increment the semaphore. If the semaphore's value * greater than 0, wake up a thread blocked in wait(). */ void post(); @@ -178,9 +177,9 @@ public: ~ThreadBarrier(); /** - * @brief . - * If there were count - 1 threads waiting before, - * then wake up all the count - 1 threads and continue run together. + * @brief . + * If there were count - 1 threads waiting before, + * then wake up all the count - 1 threads and continue run together. * Else block the thread until waked by other thread . */ void wait(); @@ -218,12 +217,12 @@ public: /** * @brief wait until pred return ture. - * @tparam Predicate c++ concepts, describes a function object - * that takes a single iterator argument - * that is dereferenced and used to + * @tparam Predicate c++ concepts, describes a function object + * that takes a single iterator argument + * that is dereferenced and used to * return a value testable as a bool. - * @note pred shall not apply any non-constant function - * through the dereferenced iterator. + * @note pred shall not apply any non-constant function + * through the dereferenced iterator. */ template void wait(Predicate pred) { diff --git a/paddle/utils/Logging.cpp b/paddle/utils/Logging.cpp index 9a6b1f2d83..14303bd4c7 100644 --- a/paddle/utils/Logging.cpp +++ b/paddle/utils/Logging.cpp @@ -91,8 +91,8 @@ static inline int env2index(const char* envName, } static bool gLogToStderr = env2bool("PLOG_LOGTOSTDERR", true); -static const std::vector gLevelName = {"INFO", "WARNING", "ERROR", - "FATAL"}; +static const std::vector gLevelName = { + "INFO", "WARNING", "ERROR", "FATAL"}; static int gMinLogLevel = env2int("PLOG_MINLOGLEVEL", env2index("PLOG_MINLOGLEVEL", gLevelName, 0)); @@ -143,11 +143,19 @@ LogMessage::~LogMessage() { this->generateLogMessage(); } void LogMessage::generateLogMessage() { if (!gLogInited) { - fprintf(stderr, "%c %s:%d] %s\n", "IWEF"[severity_], fname_, line_, + fprintf(stderr, + "%c %s:%d] %s\n", + "IWEF"[severity_], + fname_, + line_, str().c_str()); } else { for (auto& fd : gLogFds[this->severity_]) { - dprintf(fd, "%c %s:%d] %s\n", "IWEF"[severity_], fname_, line_, + dprintf(fd, + "%c %s:%d] %s\n", + "IWEF"[severity_], + fname_, + line_, str().c_str()); } } @@ -167,9 +175,7 @@ void initializeLogging(int argc, char** argv) { } namespace logging { -void setMinLogLevel(int level) { - paddle::internal::gMinLogLevel = level; -} +void setMinLogLevel(int level) { paddle::internal::gMinLogLevel = level; } void installFailureFunction(void (*callback)() ATTR_NORETURN) { paddle::internal::gFailureFunctionPtr = callback; @@ -191,13 +197,11 @@ void initializeLogging(int argc, char** argv) { } namespace logging { -void setMinLogLevel(int level) { - FLAGS_minloglevel = level; -} +void setMinLogLevel(int level) { FLAGS_minloglevel = level; } void installFailureFunction(void (*callback)()) { google::InstallFailureFunction(callback); } -void installFailureWriter(void(*callback)(const char*, int)) { +void installFailureWriter(void (*callback)(const char*, int)) { google::InstallFailureWriter(callback); } } // namespace logging diff --git a/paddle/utils/Logging.h b/paddle/utils/Logging.h index 46b6a7feeb..e9029b421f 100644 --- a/paddle/utils/Logging.h +++ b/paddle/utils/Logging.h @@ -32,11 +32,11 @@ limitations under the License. */ /** * Generate Unique Variable Name, Usefully in macro. - * @SEE http://stackoverflow.com/questions/1082192/how-to-generate-random-variable-names-in-c-using-macros + * @SEE + * http://stackoverflow.com/questions/1082192/how-to-generate-random-variable-names-in-c-using-macros */ #define UNIQUE_NAME(base) PP_CAT(base, __LINE__) - namespace paddle { //! Log levels. @@ -175,7 +175,7 @@ void installFailureFunction(void (*callback)() ATTR_NORETURN); * @brief installFailureWriter * @note: not implemented currently. */ -inline void installFailureWriter(void(*callback)(const char*, int)) { +inline void installFailureWriter(void (*callback)(const char*, int)) { (void)(callback); // unused callback. } } // namespace logging @@ -187,7 +187,7 @@ void initializeLogging(int argc, char** argv); namespace logging { void setMinLogLevel(int level); void installFailureFunction(void (*callback)()); -void installFailureWriter(void(*callback)(const char*, int)); +void installFailureWriter(void (*callback)(const char*, int)); } // namespace logging } #endif // PADDLE_USE_GLOG diff --git a/paddle/utils/PythonUtil.cpp b/paddle/utils/PythonUtil.cpp index 90e5093f96..7f17a82522 100644 --- a/paddle/utils/PythonUtil.cpp +++ b/paddle/utils/PythonUtil.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "PythonUtil.h" #include #include @@ -33,7 +32,8 @@ int executeCMD(const char* cmd, char* result) { strncpy(ps, cmd, kExecuteCMDBufLength); if ((ptr = popen(ps, "r")) != NULL) { size_t count = fread(bufPs, 1, kExecuteCMDBufLength, ptr); - memcpy(result, bufPs, + memcpy(result, + bufPs, count - 1); // why count-1: remove the '\n' at the end result[count] = 0; pclose(ptr); @@ -71,15 +71,14 @@ std::string callPythonFunc(const std::string& moduleName, #else - static std::recursive_mutex g_pyMutex; PyGuard::PyGuard() : guard_(g_pyMutex) {} - -static void printPyErrorStack(std::ostream& os, bool withEndl = false, +static void printPyErrorStack(std::ostream& os, + bool withEndl = false, bool withPyPath = true) { - PyObject * ptype, *pvalue, *ptraceback; + PyObject *ptype, *pvalue, *ptraceback; PyErr_Fetch(&ptype, &pvalue, &ptraceback); PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); PyErr_Clear(); @@ -91,10 +90,8 @@ static void printPyErrorStack(std::ostream& os, bool withEndl = false, } PyTracebackObject* obj = (PyTracebackObject*)ptraceback; - os << "Python Error: " << PyString_AsString(PyObject_Str(ptype)) - <<" : " << (pvalue == NULL ? "" - : PyString_AsString( - PyObject_Str(pvalue))); + os << "Python Error: " << PyString_AsString(PyObject_Str(ptype)) << " : " + << (pvalue == NULL ? "" : PyString_AsString(PyObject_Str(pvalue))); if (withEndl) { os << std::endl; } @@ -104,8 +101,8 @@ static void printPyErrorStack(std::ostream& os, bool withEndl = false, } while (obj != NULL) { int line = obj->tb_lineno; - const char* filename = PyString_AsString( - obj->tb_frame->f_code->co_filename); + const char* filename = + PyString_AsString(obj->tb_frame->f_code->co_filename); os << " " << filename << " : " << line; if (withEndl) { os << std::endl; @@ -143,7 +140,8 @@ std::string callPythonFunc(const std::string& moduleName, } PyObjectPtr createPythonClass( - const std::string& moduleName, const std::string& className, + const std::string& moduleName, + const std::string& className, const std::vector& args, const std::map& kwargs) { PyGuard guard; @@ -164,21 +162,18 @@ PyObjectPtr createPythonClass( PyObjectPtr kwargsObjectList(PyDict_New()); for (auto& x : kwargs) { PyObjectPtr pyArg(Py_BuildValue("s#", x.second.c_str(), x.second.length())); - PyDict_SetItemString(kwargsObjectList.get(), x.first.c_str(), - pyArg.release()); + PyDict_SetItemString( + kwargsObjectList.get(), x.first.c_str(), pyArg.release()); } - PyObjectPtr pyInstance(PyInstance_New(pyClass.get(), argsObjectList.release(), - kwargsObjectList.release())); + PyObjectPtr pyInstance(PyInstance_New( + pyClass.get(), argsObjectList.release(), kwargsObjectList.release())); CHECK_PY(pyInstance) << "Create class " << className << " failed."; return pyInstance; } - namespace py { -char* repr(PyObject* obj) { - return PyString_AsString(PyObject_Repr(obj)); -} +char* repr(PyObject* obj) { return PyString_AsString(PyObject_Repr(obj)); } std::string getPyCallStack() { std::ostringstream os; @@ -186,7 +181,7 @@ std::string getPyCallStack() { return os.str(); } -PyObjectPtr import(const std::string &moduleName) { +PyObjectPtr import(const std::string& moduleName) { auto module = PyImport_ImportModule(moduleName.c_str()); CHECK_PY(module) << "Import " << moduleName << "Error"; return PyObjectPtr(module); diff --git a/paddle/utils/PythonUtil.h b/paddle/utils/PythonUtil.h index 00fc177022..65677d9010 100644 --- a/paddle/utils/PythonUtil.h +++ b/paddle/utils/PythonUtil.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #ifndef PADDLE_NO_PYTHON @@ -83,8 +82,7 @@ PyObjectPtr createPythonClass(const std::string& moduleName, const std::vector& args, const std::map& kwargs); -#define CHECK_PY(x)\ - CHECK((x) != nullptr) << ::paddle::py::getPyCallStack() +#define CHECK_PY(x) CHECK((x) != nullptr) << ::paddle::py::getPyCallStack() namespace py { PyObjectPtr import(const std::string& moduleName); @@ -101,13 +99,13 @@ template T castInt(PyObject* obj, bool* ok = nullptr) { if (PyLong_Check(obj)) { if (ok) *ok = true; - return (T) PyLong_AsUnsignedLong(obj); + return (T)PyLong_AsUnsignedLong(obj); } else if (PyInt_Check(obj)) { if (ok) *ok = true; - return (T) PyInt_AsLong(obj); + return (T)PyInt_AsLong(obj); } else { if (ok) *ok = false; - return (T) 0; + return (T)0; } } @@ -116,14 +114,12 @@ T castInt(PyObject* obj, bool* ok = nullptr) { * * Just like toString method in java. */ -char *repr(PyObject* obj); +char* repr(PyObject* obj); /** * Invoke repr of python object. */ -inline char *repr(const PyObjectPtr &obj) { - return repr(obj.get()); -} +inline char* repr(const PyObjectPtr& obj) { return repr(obj.get()); } /** * Get Python Error Stack String. @@ -137,8 +133,7 @@ std::string getPyCallStack(); */ class ObjectHelper { public: - explicit ObjectHelper(const PyObjectPtr& obj): obj_(obj) { - } + explicit ObjectHelper(const PyObjectPtr& obj) : obj_(obj) {} /** * get attribute @@ -211,15 +206,13 @@ public: CHECK(PySequence_Check(seq_)); } - explicit SequenceHelper(PyObject* seq): seq_(seq) { + explicit SequenceHelper(PyObject* seq) : seq_(seq) { CHECK(PySequence_Check(seq_)); } - inline size_t size() const { - return (size_t) PySequence_Size(seq_); - } + inline size_t size() const { return (size_t)PySequence_Size(seq_); } - inline PyObject* operator[] (size_t i) const { + inline PyObject* operator[](size_t i) const { return PySequence_Fast_GET_ITEM(seq_, i); } @@ -260,9 +253,9 @@ private: class DictHelper { public: - explicit DictHelper(PyObject* d): dict_(d) {} + explicit DictHelper(PyObject* d) : dict_(d) {} - explicit DictHelper(const PyObjectPtr& d): dict_(d.get()) {} + explicit DictHelper(const PyObjectPtr& d) : dict_(d.get()) {} void set(const std::string& key, PyObject* item) { PyDict_SetItemString(dict_, key.c_str(), item); @@ -274,17 +267,15 @@ public: void setStringList(const std::string& key, const std::vector& items) { - auto * list = PyList_New(items.size()); - for (size_t i=0; i < items.size(); ++i) { + auto* list = PyList_New(items.size()); + for (size_t i = 0; i < items.size(); ++i) { PyList_SetItem(list, i, PyString_FromString(items[i].c_str())); } this->set(key, list); } private: - inline void checkDict() { - CHECK(PyDict_Check(this->dict_)); - } + inline void checkDict() { CHECK(PyDict_Check(this->dict_)); } PyObject* dict_; }; @@ -298,7 +289,7 @@ inline static bool isCallable(const PyObjectPtr& obj) { */ class CallableHelper { public: - explicit CallableHelper(const PyObjectPtr& obj): obj_(obj) { + explicit CallableHelper(const PyObjectPtr& obj) : obj_(obj) { CHECK(py::isCallable(obj_)); } @@ -308,21 +299,17 @@ public: * reset args, and create new tuple. * @param sz args size. */ - void setArgsSize(size_t sz) { - args.reset(PyTuple_New(sz)); - } + void setArgsSize(size_t sz) { args.reset(PyTuple_New(sz)); } /** * Get args sequence. User can set/get by SequenceHelper. */ - SequenceHelper getArgs() { - return SequenceHelper(args); - } + SequenceHelper getArgs() { return SequenceHelper(args); } /** * Call python method, return an object. */ - PyObject* operator() () { + PyObject* operator()() { PyGuard guard; return PyObject_Call(obj_.get(), args.get(), kwargs.get()); } diff --git a/paddle/utils/Queue.h b/paddle/utils/Queue.h index f952cf5877..58d17e86c4 100644 --- a/paddle/utils/Queue.h +++ b/paddle/utils/Queue.h @@ -142,12 +142,9 @@ public: */ bool waitNotEmptyFor(int seconds) { std::unique_lock lock(queueLock_); - return queueCV_.wait_for( - lock, - std::chrono::seconds(seconds), - [this] { - return numElements_ != 0; - }); + return queueCV_.wait_for(lock, + std::chrono::seconds(seconds), + [this] { return numElements_ != 0; }); } private: @@ -190,7 +187,7 @@ template class BlockingQueue { public: /** - * @brief Construct Function. + * @brief Construct Function. * @param[in] capacity the max numer of elements the queue can have. */ explicit BlockingQueue(size_t capacity) : capacity_(capacity) {} @@ -198,9 +195,9 @@ public: /** * @brief enqueue an element into Queue. * @param[in] x The enqueue element, pass by reference . - * @note This method is thread-safe, and will wake up another thread + * @note This method is thread-safe, and will wake up another thread * who was blocked because of the queue is empty. - * @note If it's size() >= capacity before enqueue, + * @note If it's size() >= capacity before enqueue, * this method will block and wait until size() < capacity. */ void enqueue(const T& x) { @@ -229,7 +226,7 @@ public: /** * Return size of queue. * - * @note This method is thread safe. + * @note This method is thread safe. * The size of the queue won't change until the method return. */ size_t size() { diff --git a/paddle/utils/Stat.h b/paddle/utils/Stat.h index 00e5aaec2b..4051145d92 100644 --- a/paddle/utils/Stat.h +++ b/paddle/utils/Stat.h @@ -93,7 +93,8 @@ public: return ret.first->second; } - BarrierStatPtr getStat(uint16_t numConnThreads, const std::string& name, + BarrierStatPtr getStat(uint16_t numConnThreads, + const std::string& name, BarrierStatType bType); void deleteStat(const std::string& name); @@ -204,8 +205,10 @@ protected: class TimerOnce { public: - TimerOnce(Stat* stat, const char* info = "", - uint64_t threshold = -1, bool autoStart = true, + TimerOnce(Stat* stat, + const char* info = "", + uint64_t threshold = -1, + bool autoStart = true, uint64_t startStamp = 0) : stat_(stat), info_(info), timer_(autoStart), threshold_(threshold) { if (!autoStart) { @@ -261,21 +264,21 @@ inline StatSet& registerTimerArg2(uint64_t threshold = -1, #define REGISTER_TIMER_SET(statName, start, ...) \ static StatPtr __stat = registerTimerArg2(__VA_ARGS__).getStat(statName); \ - TimerOnce __timerOnce(__stat.get(), "", registerTimerArg1(__VA_ARGS__), \ - false, start); + TimerOnce __timerOnce( \ + __stat.get(), "", registerTimerArg1(__VA_ARGS__), false, start); // dynmaic timer, support to discriminate runtime entity, used in pserver -#define REGISTER_TIMER_DYNAMIC(statName, ...) \ - StatPtr __stat = registerTimerArg2(__VA_ARGS__).getStat(statName); \ +#define REGISTER_TIMER_DYNAMIC(statName, ...) \ + StatPtr __stat = registerTimerArg2(__VA_ARGS__).getStat(statName); \ TimerOnce __timerOnce(__stat.get(), "", registerTimerArg1(__VA_ARGS__)); -#define REGISTER_TIMER_DYNAMIC_SET(statName, start, ...) \ - StatPtr __stat = registerTimerArg2(__VA_ARGS__).getStat(statName); \ - TimerOnce __timerOnce(__stat.get(), "", registerTimerArg1(__VA_ARGS__), \ - false, start); +#define REGISTER_TIMER_DYNAMIC_SET(statName, start, ...) \ + StatPtr __stat = registerTimerArg2(__VA_ARGS__).getStat(statName); \ + TimerOnce __timerOnce( \ + __stat.get(), "", registerTimerArg1(__VA_ARGS__), false, start); -#define REGISTER_TIMER_INFO(statName, info) \ - static StatPtr __stat = globalStat.getStat(statName); \ +#define REGISTER_TIMER_INFO(statName, info) \ + static StatPtr __stat = globalStat.getStat(statName); \ TimerOnce __timerOnce(__stat.get(), info, 10 * 1000000LU /*threshold*/); #endif // DISABLE_TIMER diff --git a/paddle/utils/StringUtil.h b/paddle/utils/StringUtil.h index 50301a19be..8b44dad192 100644 --- a/paddle/utils/StringUtil.h +++ b/paddle/utils/StringUtil.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -68,8 +67,6 @@ inline T to(const std::string& s) { return v; } - - } // namespace str #undef DEFINE_STRING_CONVERSION diff --git a/paddle/utils/Thread.h b/paddle/utils/Thread.h index f6c826a1ee..ade0ee496f 100644 --- a/paddle/utils/Thread.h +++ b/paddle/utils/Thread.h @@ -57,7 +57,8 @@ public: void join() { thread_->join(); } /** - * @brief Define what to be done on this thread through override this function. + * @brief Define what to be done on this thread through override this + * function. */ virtual void run() = 0; @@ -155,10 +156,9 @@ public: /** * @brief Construct Function. No thread will be created. */ - SyncThreadPool() - : jobStartBarrier_(0), - jobFinishBarrier_(0) - { LOG(FATAL) << "Not implemented"; } + SyncThreadPool() : jobStartBarrier_(0), jobFinishBarrier_(0) { + LOG(FATAL) << "Not implemented"; + } /** * @brief Construct Fucntion. Create numWorkers of threads in the pool. @@ -191,7 +191,8 @@ public: /** * @brief Execute a job using all the theads in the pool. * @param[in] jobFunc The function to be executed. - * @param[in] ownerFunc Owner thread can do something in owerFunc when job executing. + * @param[in] ownerFunc Owner thread can do something in owerFunc when job + * executing. * @note For the ownerFunc, tid=getNumThreads(). */ void exec(JobFunc jobFunc, JobFunc ownerFunc = nullptr) { @@ -316,7 +317,8 @@ protected: * * Force stop: * - * Use forceStop() to exit forcibly even though there are remaining jobs in the + * Use forceStop() to exit forcibly even though there are remaining jobs in + * the * job queue. */ template @@ -426,7 +428,8 @@ protected: /** * @brief Do the jobs in the job queue sequentianlly * and enqueue the result into the result queue. - * @note A nullptr will be enqueued into the resulte queue, when a worker finished. + * @note A nullptr will be enqueued into the resulte queue, when a worker + * finished. */ virtual void run() { while (true) { @@ -492,7 +495,9 @@ public: } ~AsyncThreadPool() { - if (!stopping_) { stop(); } + if (!stopping_) { + stop(); + } } /** @@ -501,7 +506,7 @@ public: void stop() { stopping_ = true; for (size_t i = 0; i < workers_.size(); i++) { - jobs_.enqueue([]{}); + jobs_.enqueue([] {}); } for (auto& worker : workers_) { worker->join(); @@ -526,7 +531,7 @@ public: * asynchronously. * Call std::future::get() when the execturation result is needed; */ - template + template auto addJob(F&& f, Args&&... args) -> std::future::type> { CHECK(!stopping_) << "AsyncThreadPool is closed"; @@ -535,7 +540,7 @@ public: auto task = std::make_shared>( std::bind(std::forward(f), std::forward(args)...)); auto res = task->get_future(); - jobs_.enqueue([task]{ (*task)(); }); + jobs_.enqueue([task] { (*task)(); }); return res; } @@ -551,15 +556,15 @@ public: * * @note *results* may need to be carefully cleared before *addBatchJobs()*. */ - template - void addBatchJobs(const std::vector &jobs, - std::vector::type> &results) { + template + void addBatchJobs(const std::vector& jobs, + std::vector::type>& results) { typedef typename std::result_of::type T; static_assert(!std::is_same::value, - "should pass a non-void function as job"); + "should pass a non-void function as job"); - std::vector > resFuts; - for (const auto &job : jobs) { + std::vector> resFuts; + for (const auto& job : jobs) { resFuts.emplace_back(addJob(job)); } for (auto& fut : resFuts) { @@ -572,13 +577,16 @@ public: * @tparam F don't need to have a return value. * @param[in] jobs a vector of executable objection. */ - template - void addBatchJobs(const std::vector &jobs) { + template + void addBatchJobs(const std::vector& jobs) { CHECK(!stopping_) << "AsyncThreadPool is closed"; - std::vector > tmpRes; + std::vector> tmpRes; for (const auto& job : jobs) { - tmpRes.emplace_back(addJob([&job]{ job(); return true; })); + tmpRes.emplace_back(addJob([&job] { + job(); + return true; + })); } for (auto& res : tmpRes) { @@ -604,4 +612,4 @@ private: bool stopping_; }; // class AsyncThreadPool -} // namespace paddle +} // namespace paddle diff --git a/paddle/utils/ThreadLocal.cpp b/paddle/utils/ThreadLocal.cpp index 0f948f1029..49d4b15265 100644 --- a/paddle/utils/ThreadLocal.cpp +++ b/paddle/utils/ThreadLocal.cpp @@ -16,7 +16,8 @@ limitations under the License. */ #include "ThreadLocal.h" #include "CommandLineParser.h" -P_DEFINE_bool(thread_local_rand_use_global_seed, false, +P_DEFINE_bool(thread_local_rand_use_global_seed, + false, "Whether to use global seed in thread local rand."); namespace paddle { diff --git a/paddle/utils/ThreadLocal.h b/paddle/utils/ThreadLocal.h index b91e4ad547..06c8b392af 100644 --- a/paddle/utils/ThreadLocal.h +++ b/paddle/utils/ThreadLocal.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -91,9 +90,7 @@ public: /** * Implicit conversion to T* */ - operator T*() { - return get(); - } + operator T*() { return get(); } private: static void dataDestructor(void* p) { delete (T*)p; } diff --git a/paddle/utils/TypeDefs.h b/paddle/utils/TypeDefs.h index e02fd62b53..e8be779bea 100644 --- a/paddle/utils/TypeDefs.h +++ b/paddle/utils/TypeDefs.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once namespace paddle { diff --git a/paddle/utils/Util.cpp b/paddle/utils/Util.cpp index b16d431465..bc727cfa74 100644 --- a/paddle/utils/Util.cpp +++ b/paddle/utils/Util.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Util.h" #include @@ -54,7 +53,8 @@ P_DEFINE_int32(seed, 1, "random number seed. 0 for srand(time)"); #include P_DEFINE_int32(profile_signal, 12, "signal for switch google profiler"); -P_DEFINE_string(profile_data_file, "gperf.prof", +P_DEFINE_string(profile_data_file, + "gperf.prof", "file for storing profile data"); static void profilerSwitch(int signalNumber) { @@ -94,18 +94,18 @@ static void installProfilerSwitch() {} namespace paddle { pid_t getTID() { - #if defined(__APPLE__) || defined(__OSX__) - // syscall is deprecated: first deprecated in macOS 10.12. - // syscall is unsupported; - // syscall pid_t tid = syscall(SYS_thread_selfid); - uint64_t tid; - pthread_threadid_np(NULL, &tid); - #else - #ifndef __NR_gettid - #define __NR_gettid 224 - #endif - pid_t tid = syscall(__NR_gettid); - #endif +#if defined(__APPLE__) || defined(__OSX__) + // syscall is deprecated: first deprecated in macOS 10.12. + // syscall is unsupported; + // syscall pid_t tid = syscall(SYS_thread_selfid); + uint64_t tid; + pthread_threadid_np(NULL, &tid); +#else +#ifndef __NR_gettid +#define __NR_gettid 224 +#endif + pid_t tid = syscall(__NR_gettid); +#endif CHECK_NE((int)tid, -1); return tid; } @@ -126,22 +126,25 @@ void registerInitFunction(std::function func, int priority) { } void runInitFunctions() { - std::call_once(g_onceFlag, []() { - LOG(INFO) << "Calling runInitFunctions"; - if (g_initFuncs) { - std::sort(g_initFuncs->begin(), g_initFuncs->end(), - [](const PriorityFuncPair& x, const PriorityFuncPair& y) { - return x.first > y.first; - }); - for (auto& f : *g_initFuncs) { - f.second(); - } - delete g_initFuncs; - g_initFuncs = nullptr; - } - g_initialized = true; - LOG(INFO) << "Call runInitFunctions done."; - }); + std::call_once( + g_onceFlag, + []() { + LOG(INFO) << "Calling runInitFunctions"; + if (g_initFuncs) { + std::sort(g_initFuncs->begin(), + g_initFuncs->end(), + [](const PriorityFuncPair& x, const PriorityFuncPair& y) { + return x.first > y.first; + }); + for (auto& f : *g_initFuncs) { + f.second(); + } + delete g_initFuncs; + g_initFuncs = nullptr; + } + g_initialized = true; + LOG(INFO) << "Call runInitFunctions done."; + }); } void initMain(int argc, char** argv) { @@ -282,7 +285,7 @@ void mkDir(const char* filename) { } } -void mkDirRecursively(const char *dir) { +void mkDirRecursively(const char* dir) { struct stat sb; if (!stat(dir, &sb)) return; @@ -303,7 +306,6 @@ void loadFileList(const std::string& fileListFileName, } } - double getMemoryUsage() { FILE* fp = fopen("/proc/meminfo", "r"); CHECK(fp) << "failed to fopen /proc/meminfo"; @@ -363,7 +365,9 @@ size_t calculateServiceNum(const std::string& pservers, int ports_num) { return hosts.size() * ports_num; } -void memcpyWithCheck(void* dest, const void* src, size_t num, +void memcpyWithCheck(void* dest, + const void* src, + size_t num, const void* srcEnd) { int minus = (char*)srcEnd - (char*)src - num; CHECK_LE(0, minus) << "memcpyWithCheck: copy " << num diff --git a/paddle/utils/Util.h b/paddle/utils/Util.h index 2adb626c83..ed38f8fa60 100644 --- a/paddle/utils/Util.h +++ b/paddle/utils/Util.h @@ -12,7 +12,6 @@ WITHOUT 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 @@ -47,7 +46,8 @@ limitations under the License. */ */ #define FOR_EACH(iterator_name, container) \ for (auto iterator_name = (container).begin(), e = (container).end(); \ - iterator_name != e; ++iterator_name) + iterator_name != e; \ + ++iterator_name) /** * Loop over the elements in a container in reverse order @@ -60,8 +60,8 @@ limitations under the License. */ */ #define FOR_EACH_R(iterator_name, container) \ for (auto iterator_name = (container).rbegin(), e = (container).rend(); \ - iterator_name != e; ++iterator_name) - + iterator_name != e; \ + ++iterator_name) namespace paddle { @@ -77,11 +77,11 @@ pid_t getTID(); * \f] */ inline constexpr size_t findLastSet(size_t x) { - return std::is_same::value ? - (x ? 8 * sizeof(x) - __builtin_clz(x) : 0) - : (std::is_same::value ? // NOLINT - (x ? 8 * sizeof(x) - __builtin_clzl(x) : 0) - : (x ? 8 * sizeof(x) - __builtin_clzll(x) : 0)); + return std::is_same::value + ? (x ? 8 * sizeof(x) - __builtin_clz(x) : 0) + : (std::is_same::value // NOLINT + ? (x ? 8 * sizeof(x) - __builtin_clzl(x) : 0) + : (x ? 8 * sizeof(x) - __builtin_clzll(x) : 0)); } /** @@ -95,7 +95,6 @@ inline int mod(int a, int b) { return r >= 0 ? r : r + b; } - /** * find the value given a key k from container c. * If the key can be found, the value is stored in *value @@ -120,7 +119,7 @@ static bool contains(const Container& container, const T& val) { /** * pop and get the front element of a container */ -template +template typename Container::value_type pop_get_front(Container& c) { typename Container::value_type v; swap(v, c.front()); @@ -207,7 +206,6 @@ protected: int devId_; }; - /** * Enables direct access to memory allocations on a peer device(d2). * input: @@ -250,7 +248,6 @@ private: bool syncFlag_; }; - inline bool useGpu(int deviceId) { return FLAGS_parallel_nn ? (deviceId >= 0 ? true : false) : FLAGS_use_gpu; } @@ -328,7 +325,9 @@ T readT(char*& p, const char* pEnd) { return v; } -void memcpyWithCheck(void* dest, const void* src, size_t num, +void memcpyWithCheck(void* dest, + const void* src, + size_t num, const void* srcEnd); /** @@ -338,7 +337,6 @@ void memcpyWithCheck(void* dest, const void* src, size_t num, class SyncThreadPool; SyncThreadPool* getGlobalSyncThreadPool(); - namespace path { // directory separator @@ -363,7 +361,8 @@ std::string dirname(const std::string& path); std::string join(const std::string& part1, const std::string& part2); template -std::string join(const std::string& part1, const std::string& part2, +std::string join(const std::string& part1, + const std::string& part2, Args... args) { return join(join(part1, part2), args...); } @@ -392,8 +391,8 @@ public: std::call_once(onceFlag_, [&] { invokeThreadId_ = curThreadId; }); CHECK_EQ(invokeThreadId_, curThreadId) << "This method should invoke in " - "same thread, but first invoked in " << invokeThreadId_ - << " current invoked in " << curThreadId; + "same thread, but first invoked in " + << invokeThreadId_ << " current invoked in " << curThreadId; } private: @@ -447,28 +446,23 @@ private: * @brief The ScopedCallbacks class is a callback invoker when object is * created and destroyed. */ -template +template class ScopedCallbacks { public: - ScopedCallbacks(CallbackType enter, - CallbackType exit, - Args& ... args) - : exit_(std::bind(exit, args...)) { + ScopedCallbacks(CallbackType enter, CallbackType exit, Args&... args) + : exit_(std::bind(exit, args...)) { enter(args...); } ScopedCallbacks(const ScopedCallbacks& other) = delete; - ScopedCallbacks& operator = (const ScopedCallbacks& other) = delete; + ScopedCallbacks& operator=(const ScopedCallbacks& other) = delete; - ~ScopedCallbacks() { - exit_(); - } + ~ScopedCallbacks() { exit_(); } private: std::function exit_; }; - /** * std compatible allocator with memory alignment. * @tparam T type of allocator elements. @@ -537,8 +531,7 @@ public: return nullptr; } if (n > max_size()) { - throw std::length_error( - "AlignAllocator::allocate() - Int Overflow."); + throw std::length_error("AlignAllocator::allocate() - Int Overflow."); } void* r = nullptr; CHECK_EQ(posix_memalign(&r, Alignment * 8, sizeof(T) * n), 0); @@ -558,7 +551,6 @@ private: AlignedAllocator& operator=(const AlignedAllocator&); // disable }; - class Deprecated { public: explicit Deprecated(const std::string& msg = "") { diff --git a/paddle/utils/Version.cpp b/paddle/utils/Version.cpp index b59b78f570..e706983918 100644 --- a/paddle/utils/Version.cpp +++ b/paddle/utils/Version.cpp @@ -12,7 +12,6 @@ WITHOUT 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 "Version.h" #include "Flags.h" @@ -34,18 +33,22 @@ void printVersion(std::ostream& os) { #ifndef PADDLE_VERSION #define PADDLE_VERSION "unknown" #endif - os << "paddle version: " << PADDLE_VERSION << std::endl << std::boolalpha - << "\t" << "withGpu: " << version::isWithGpu() << std::endl - << "\t" << "withAvx: " << version::isWithAvx() << std::endl - << "\t" << "withPyDataProvider: " << version::isWithPyDataProvider() - << std::endl - << "\t" << "withTimer: " << version::isWithTimer() << std::endl - << "\t" << "withFpga: " << version::isWithFpga() << std::endl - << "\t" << "real byte size: "<< version::sizeofReal() << std::endl - << std::endl; + os << "paddle version: " << PADDLE_VERSION << std::endl + << std::boolalpha << "\t" + << "withGpu: " << version::isWithGpu() << std::endl + << "\t" + << "withAvx: " << version::isWithAvx() << std::endl + << "\t" + << "withPyDataProvider: " << version::isWithPyDataProvider() << std::endl + << "\t" + << "withTimer: " << version::isWithTimer() << std::endl + << "\t" + << "withFpga: " << version::isWithFpga() << std::endl + << "\t" + << "real byte size: " << version::sizeofReal() << std::endl + << std::endl; } - void printVersion() { if (FLAGS_version) { printVersion(std::cout); diff --git a/paddle/utils/Version.h b/paddle/utils/Version.h index e6655fa75d..e6c799644e 100644 --- a/paddle/utils/Version.h +++ b/paddle/utils/Version.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include "TypeDefs.h" @@ -35,7 +34,6 @@ namespace paddle { * real byte size: 4 */ - namespace version { /** @@ -44,7 +42,6 @@ namespace version { */ void printVersion(); - void printVersion(std::ostream& os); /** * @brief isWithGpu @@ -75,7 +72,6 @@ constexpr bool isWithPyDataProvider() { #endif } - /** * @brief isWithTimer * @return true if paddle compiled with timer. @@ -116,25 +112,19 @@ constexpr bool isWithFpga() { * @brief sizeofReal * @return return the byte size of real */ -constexpr size_t sizeofReal() { - return sizeof(real); -} +constexpr size_t sizeofReal() { return sizeof(real); } /** * @brief isPaddleUseDouble * @return true if paddle compiled with double precision. */ -constexpr bool isPaddleUseDouble() { - return sizeofReal() == sizeof(double); -} +constexpr bool isPaddleUseDouble() { return sizeofReal() == sizeof(double); } /** * @brief isPaddleUseFloat * @return true if paddle compiled with float precision */ -constexpr bool isPaddleUseFloat() { - return sizeofReal() == sizeof(float); -} +constexpr bool isPaddleUseFloat() { return sizeofReal() == sizeof(float); } } // namespace version diff --git a/paddle/utils/arch/linux/Locks.cpp b/paddle/utils/arch/linux/Locks.cpp index 347ae64c26..93016daeae 100644 --- a/paddle/utils/arch/linux/Locks.cpp +++ b/paddle/utils/arch/linux/Locks.cpp @@ -22,26 +22,19 @@ public: sem_t sem; }; -Semaphore::Semaphore(int initValue): m(new SemaphorePrivate()) { +Semaphore::Semaphore(int initValue) : m(new SemaphorePrivate()) { sem_init(&m->sem, 0, initValue); } -Semaphore::~Semaphore() { - sem_destroy(&m->sem); -} +Semaphore::~Semaphore() { sem_destroy(&m->sem); } bool Semaphore::timeWait(struct timespec* ts) { return (0 == sem_timedwait(&m->sem, ts)); } -void Semaphore::wait() { - sem_wait(&m->sem); -} - -void Semaphore::post() { - sem_post(&m->sem); -} +void Semaphore::wait() { sem_wait(&m->sem); } +void Semaphore::post() { sem_post(&m->sem); } class SpinLockPrivate { public: @@ -51,25 +44,20 @@ public: char padding_[64 - sizeof(pthread_spinlock_t)]; }; -SpinLock::SpinLock():m(new SpinLockPrivate()) {} - +SpinLock::SpinLock() : m(new SpinLockPrivate()) {} SpinLock::~SpinLock() { delete m; } -void SpinLock::lock() { - pthread_spin_lock(&m->lock_); -} +void SpinLock::lock() { pthread_spin_lock(&m->lock_); } -void SpinLock::unlock() { - pthread_spin_unlock(&m->lock_); -} +void SpinLock::unlock() { pthread_spin_unlock(&m->lock_); } class ThreadBarrierPrivate { public: pthread_barrier_t barrier_; }; -ThreadBarrier::ThreadBarrier(int count): m(new ThreadBarrierPrivate()) { +ThreadBarrier::ThreadBarrier(int count) : m(new ThreadBarrierPrivate()) { pthread_barrier_init(&m->barrier_, nullptr, count); } @@ -78,8 +66,6 @@ ThreadBarrier::~ThreadBarrier() { delete m; } -void ThreadBarrier::wait() { - pthread_barrier_wait(&m->barrier_); -} +void ThreadBarrier::wait() { pthread_barrier_wait(&m->barrier_); } } // namespace paddle diff --git a/paddle/utils/arch/osx/Locks.cpp b/paddle/utils/arch/osx/Locks.cpp index b3ec454976..ae563a6afd 100644 --- a/paddle/utils/arch/osx/Locks.cpp +++ b/paddle/utils/arch/osx/Locks.cpp @@ -22,20 +22,16 @@ namespace paddle { class SemaphorePrivate { public: - ~SemaphorePrivate() { - dispatch_release(sem); - } + ~SemaphorePrivate() { dispatch_release(sem); } dispatch_semaphore_t sem; }; -Semaphore::Semaphore(int initValue): m(new SemaphorePrivate()) { +Semaphore::Semaphore(int initValue) : m(new SemaphorePrivate()) { m->sem = dispatch_semaphore_create(initValue); } -Semaphore::~Semaphore() { - delete m; -} +Semaphore::~Semaphore() { delete m; } bool Semaphore::timeWait(timespec *ts) { dispatch_time_t tm = dispatch_walltime(ts, 0); @@ -46,9 +42,7 @@ void Semaphore::wait() { dispatch_semaphore_wait(m->sem, DISPATCH_TIME_FOREVER); } -void Semaphore::post() { - dispatch_semaphore_signal(m->sem); -} +void Semaphore::post() { dispatch_semaphore_signal(m->sem); } class SpinLockPrivate { public: @@ -56,17 +50,15 @@ public: char padding_[64 - sizeof(lock_)]; // Padding to cache line size }; -SpinLock::SpinLock(): m(new SpinLockPrivate()) {} +SpinLock::SpinLock() : m(new SpinLockPrivate()) {} SpinLock::~SpinLock() { delete m; } void SpinLock::lock() { - while (m->lock_.test_and_set(std::memory_order_acquire)) {} -} - -void SpinLock::unlock() { - m->lock_.clear(std::memory_order_release); + while (m->lock_.test_and_set(std::memory_order_acquire)) { + } } +void SpinLock::unlock() { m->lock_.clear(std::memory_order_release); } class ThreadBarrierPrivate { public: @@ -75,7 +67,7 @@ public: int count_; int tripCount_; - inline explicit ThreadBarrierPrivate(int cnt):count_(0), tripCount_(cnt) { + inline explicit ThreadBarrierPrivate(int cnt) : count_(0), tripCount_(cnt) { CHECK_NE(cnt, 0); CHECK_GE(pthread_mutex_init(&mutex_, 0), 0); CHECK_GE(pthread_cond_init(&cond_, 0), 0); @@ -106,7 +98,7 @@ public: } }; -ThreadBarrier::ThreadBarrier(int count): m(new ThreadBarrierPrivate(count)) {} +ThreadBarrier::ThreadBarrier(int count) : m(new ThreadBarrierPrivate(count)) {} ThreadBarrier::~ThreadBarrier() { delete m; } void ThreadBarrier::wait() { m->wait(); } diff --git a/paddle/utils/tests/test_CommandLineParser.cpp b/paddle/utils/tests/test_CommandLineParser.cpp index 9bb6827540..5ecfb2b4f5 100644 --- a/paddle/utils/tests/test_CommandLineParser.cpp +++ b/paddle/utils/tests/test_CommandLineParser.cpp @@ -63,10 +63,15 @@ TEST(CommandLineParser, defaultValue) { } TEST(CommandLineParser, normal) { - char* argv[] = { - cc("test_program"), cc("--i2=32"), cc("--str1=abc"), - cc("--b2=1"), cc("-b1=False"), cc("--d2=.34"), - cc("--d1=0"), cc("--l1=-12345678901234"), cc("-ul2=3212")}; + char* argv[] = {cc("test_program"), + cc("--i2=32"), + cc("--str1=abc"), + cc("--b2=1"), + cc("-b1=False"), + cc("--d2=.34"), + cc("--d1=0"), + cc("--l1=-12345678901234"), + cc("-ul2=3212")}; int argc = sizeof(argv) / sizeof(char*); paddle::ParseCommandLineFlags(&argc, argv); ASSERT_EQ(argc, 1); @@ -104,8 +109,6 @@ int main(int argc, char** argv) { #else -int main(int argc, char** argv) { - return 0; -} +int main(int argc, char** argv) { return 0; } #endif diff --git a/paddle/utils/tests/test_CustomStackTrace.cpp b/paddle/utils/tests/test_CustomStackTrace.cpp index 3e66502147..3bfb381ed9 100644 --- a/paddle/utils/tests/test_CustomStackTrace.cpp +++ b/paddle/utils/tests/test_CustomStackTrace.cpp @@ -22,11 +22,12 @@ limitations under the License. */ P_DEFINE_int32(test_thread_num, 10, "testing thread number"); -void testNormalImpl(const std::function&, - size_t, size_t, - paddle::ThreadBarrier&, - paddle::ThreadBarrier&)>& callback) { +void testNormalImpl( + const std::function&, + size_t, + size_t, + paddle::ThreadBarrier&, + paddle::ThreadBarrier&)>& callback) { paddle::CustomStackTrace tracer; paddle::ThreadBarrier doneBarrier(FLAGS_test_thread_num + 1); paddle::ThreadBarrier startBarrier(FLAGS_test_thread_num + 1); @@ -35,10 +36,13 @@ void testNormalImpl(const std::function> threads; threads.reserve(FLAGS_test_thread_num); - for (int32_t i=0; i < FLAGS_test_thread_num; ++i) { - threads.emplace_back(new std::thread([&tracer, &countDown, &layerSize, - &startBarrier, &doneBarrier, - &callback]{ + for (int32_t i = 0; i < FLAGS_test_thread_num; ++i) { + threads.emplace_back(new std::thread([&tracer, + &countDown, + &layerSize, + &startBarrier, + &doneBarrier, + &callback] { callback(tracer, countDown, layerSize, startBarrier, doneBarrier); })); } @@ -55,18 +59,19 @@ void testNormalImpl(const std::function& tracer, - size_t countDown, size_t layerSize, - paddle::ThreadBarrier& start, paddle::ThreadBarrier& finish){ + size_t countDown, + size_t layerSize, + paddle::ThreadBarrier& start, + paddle::ThreadBarrier& finish) { while (countDown-- > 0) { start.wait(); - for (size_t i=0; i < layerSize; ++i) { + for (size_t i = 0; i < layerSize; ++i) { tracer.push("layer_" + std::to_string(i)); } tracer.pop(""); - for (size_t i=0; i < layerSize; ++i) { + for (size_t i = 0; i < layerSize; ++i) { tracer.pop("layer_" + std::to_string(layerSize - 1 - i)); } finish.wait(); @@ -75,12 +80,14 @@ TEST(CustomStackTrace, normalTrain) { } TEST(CustomStackTrace, normalTest) { - testNormalImpl([] (paddle::CustomStackTrace& tracer, - size_t countDown, size_t layerSize, - paddle::ThreadBarrier& start, paddle::ThreadBarrier& finish){ + testNormalImpl([](paddle::CustomStackTrace& tracer, + size_t countDown, + size_t layerSize, + paddle::ThreadBarrier& start, + paddle::ThreadBarrier& finish) { while (countDown-- > 0) { start.wait(); - for (size_t i=0; i < layerSize; ++i) { + for (size_t i = 0; i < layerSize; ++i) { tracer.push("layer_" + std::to_string(i)); } tracer.clear(); // in forward test, tracer will clear after forward. diff --git a/paddle/utils/tests/test_CustomStackTracePrint.cpp b/paddle/utils/tests/test_CustomStackTracePrint.cpp index c19c98614e..d39a190961 100644 --- a/paddle/utils/tests/test_CustomStackTracePrint.cpp +++ b/paddle/utils/tests/test_CustomStackTracePrint.cpp @@ -18,7 +18,7 @@ limitations under the License. */ int main(int argc, char** argv) { paddle::initMain(argc, argv); - for (size_t i=0; i < 1000; ++i) { + for (size_t i = 0; i < 1000; ++i) { paddle::gLayerStackTrace.push("layer_" + std::to_string(i)); if (i == 998) { throw "Unhandle exception"; diff --git a/paddle/utils/tests/test_Logging.cpp b/paddle/utils/tests/test_Logging.cpp index a9382de6da..9f477fab14 100644 --- a/paddle/utils/tests/test_Logging.cpp +++ b/paddle/utils/tests/test_Logging.cpp @@ -54,7 +54,7 @@ TEST(Logging, Check) { auto pcheckDown = [&] { P_CHECK(a == b); }; ASSERT_DEATH(pcheckDown(), - "F .*test_Logging.cpp:[0-9]+] Check failed: a == b "); + "F .*test_Logging.cpp:[0-9]+] Check failed: a == b "); P_CHECK_LE(a, b); P_CHECK_LT(a, b); @@ -157,8 +157,6 @@ int main(int argc, char** argv) { #else -int main(int, char**) { - return 0; -} +int main(int, char**) { return 0; } #endif diff --git a/paddle/utils/tests/test_SpinLock.cpp b/paddle/utils/tests/test_SpinLock.cpp index ebc84e0f52..77d281962c 100644 --- a/paddle/utils/tests/test_SpinLock.cpp +++ b/paddle/utils/tests/test_SpinLock.cpp @@ -21,17 +21,18 @@ limitations under the License. */ P_DEFINE_int32(test_thread_num, 100, "testing thread number"); -void testNormalImpl(size_t thread_num, const std::function - & callback) { +void testNormalImpl( + size_t thread_num, + const std::function& callback) { paddle::SpinLock mutex; std::vector threads; threads.reserve(thread_num); size_t count = 0; for (size_t i = 0; i < thread_num; ++i) { - threads.emplace_back([&thread_num, &count, &mutex, &callback]{ - callback(thread_num, count, mutex); - }); + threads.emplace_back([&thread_num, &count, &mutex, &callback] { + callback(thread_num, count, mutex); + }); } for (auto& thread : threads) { thread.join(); @@ -41,12 +42,13 @@ void testNormalImpl(size_t thread_num, const std::function } TEST(ThreadSpinLock, normalTest) { - for (auto &thread_num : {10, 30, 50 , 100 , 300, 1000}) { - testNormalImpl(thread_num, [](size_t thread_num, - size_t& count, paddle::SpinLock& mutex){ - std::lock_guard lock(mutex); - ++count; - }); + for (auto& thread_num : {10, 30, 50, 100, 300, 1000}) { + testNormalImpl( + thread_num, + [](size_t thread_num, size_t& count, paddle::SpinLock& mutex) { + std::lock_guard lock(mutex); + ++count; + }); } } diff --git a/paddle/utils/tests/test_StringUtils.cpp b/paddle/utils/tests/test_StringUtils.cpp index b8636709e9..2c699b791f 100644 --- a/paddle/utils/tests/test_StringUtils.cpp +++ b/paddle/utils/tests/test_StringUtils.cpp @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "paddle/utils/StringUtil.h" #include diff --git a/paddle/utils/tests/test_Thread.cpp b/paddle/utils/tests/test_Thread.cpp index bf4e275345..154db5d9c6 100644 --- a/paddle/utils/tests/test_Thread.cpp +++ b/paddle/utils/tests/test_Thread.cpp @@ -20,7 +20,7 @@ using paddle::AsyncThreadPool; // NOLINT TEST(AsyncThreadPool, addJob) { AsyncThreadPool pool(8); - auto a = pool.addJob([]{ return 1; }); + auto a = pool.addJob([] { return 1; }); auto b = pool.addJob([] { return true; }); auto c = pool.addJob([] { return false; }); @@ -36,10 +36,7 @@ TEST(AsyncThreadPool, addBatchJob) { std::vector jobs; for (int i = 0; i < 10000; i++) { - jobs.emplace_back( - [&] { - counter++; - }); + jobs.emplace_back([&] { counter++; }); } pool.addBatchJobs(jobs); @@ -55,13 +52,16 @@ TEST(AsyncThreadPool, multiThreadAddBatchJob) { int counter = 0; const int numMonitors = 300; const int numSlaves = 300; - std::vector moniterJobs(numMonitors, [&] { - std::vector slaveJobs(numSlaves, - [mut, &counter] { - std::lock_guard lk(*mut); - counter++; - }); - levelTwoPool.addBatchJobs(slaveJobs); + std::vector moniterJobs( + numMonitors, + [&] { + std::vector slaveJobs( + numSlaves, + [mut, &counter] { + std::lock_guard lk(*mut); + counter++; + }); + levelTwoPool.addBatchJobs(slaveJobs); }); levelOnePool.addBatchJobs(moniterJobs); ASSERT_EQ(counter, numMonitors * numSlaves); @@ -70,13 +70,10 @@ TEST(AsyncThreadPool, multiThreadAddBatchJob) { TEST(AsyncThreadPool, addBatchJobWithResults) { AsyncThreadPool pool(100); - std::vector > jobs; + std::vector> jobs; const int numJobs = 100; for (int i = 0; i < numJobs; i++) { - jobs.emplace_back( - [i]{ - return i; - }); + jobs.emplace_back([i] { return i; }); } std::vector res; diff --git a/paddle/utils/tests/test_ThreadBarrier.cpp b/paddle/utils/tests/test_ThreadBarrier.cpp index 90bd6c21bc..20b9babd94 100644 --- a/paddle/utils/tests/test_ThreadBarrier.cpp +++ b/paddle/utils/tests/test_ThreadBarrier.cpp @@ -22,42 +22,44 @@ limitations under the License. */ P_DEFINE_int32(test_thread_num, 100, "testing thread number"); -void testNormalImpl(size_t thread_num, - const std::function&, - paddle::ThreadBarrier&)>& callback) { - std::mutex mutex; - std::set tids; - paddle::ThreadBarrier barrier(thread_num); +void testNormalImpl( + size_t thread_num, + const std::function&, + paddle::ThreadBarrier&)>& callback) { + std::mutex mutex; + std::set tids; + paddle::ThreadBarrier barrier(thread_num); - std::vector threads; - threads.reserve(thread_num); - for (size_t i = 0; i < thread_num; ++i) { - threads.emplace_back([&thread_num, &mutex, - &tids, &barrier, &callback]{ - callback(thread_num, mutex, tids, barrier); + std::vector threads; + threads.reserve(thread_num); + for (size_t i = 0; i < thread_num; ++i) { + threads.emplace_back([&thread_num, &mutex, &tids, &barrier, &callback] { + callback(thread_num, mutex, tids, barrier); }); - } + } - for (auto& thread : threads) { - thread.join(); - } + for (auto& thread : threads) { + thread.join(); + } } TEST(ThreadBarrier, normalTest) { - for (auto &thread_num : {10, 30, 50 , 100 , 300, 1000}) { + for (auto& thread_num : {10, 30, 50, 100, 300, 1000}) { testNormalImpl(thread_num, - [](size_t thread_num, std::mutex& mutex, - std::set& tids, - paddle::ThreadBarrier& barrier){ - { - std::lock_guard guard(mutex); - tids.insert(std::this_thread::get_id()); - } - barrier.wait(); - // Check whether all threads reach this point or not - CHECK_EQ(tids.size(), thread_num); - }); + [](size_t thread_num, + std::mutex& mutex, + std::set& tids, + paddle::ThreadBarrier& barrier) { + { + std::lock_guard guard(mutex); + tids.insert(std::this_thread::get_id()); + } + barrier.wait(); + // Check whether all threads reach this point or not + CHECK_EQ(tids.size(), thread_num); + }); } } -- GitLab From 9ea0661a8261aa137c84edc5b7ac76be49e3e289 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 22 Nov 2016 19:13:17 +0800 Subject: [PATCH 0102/1503] clang format off on some cuda .cc file --- paddle/cuda/src/hl_cuda_cublas.cc | 12 ++++-- paddle/cuda/src/hl_cuda_device.cc | 63 ++++++++++++++++++++----------- paddle/cuda/src/hl_cudart_wrap.cc | 25 ++++++------ 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index f82d6c9402..e8ba232d44 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -56,9 +56,14 @@ void *cublas_dso_handle = nullptr; #define DYNAMIC_LOAD_CUBLAS_V2_WRAP(__name) DYNAMIC_LOAD_CUBLAS_WRAP(__name) // include all needed cublas functions in HPPL -#define CUBLAS_BLAS_ROUTINE_EACH(__macro) \ - __macro(cublasSgemv) __macro(cublasDgemv) __macro(cublasSgemm) \ - __macro(cublasDgemm) __macro(cublasSgeam) __macro(cublasDgeam) +// clang-format off +#define CUBLAS_BLAS_ROUTINE_EACH(__macro) \ + __macro(cublasSgemv) \ + __macro(cublasDgemv) \ + __macro(cublasSgemm) \ + __macro(cublasDgemm) \ + __macro(cublasSgeam) \ + __macro(cublasDgeam) \ DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasCreate) DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasDestroy) @@ -81,6 +86,7 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) } /* namespace dynload */ +// clang-format on #ifndef PADDLE_TYPE_DOUBLE #define CUBLAS_GEAM dynload::cublasSgeam #define CUBLAS_GEMV dynload::cublasSgemv diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 85d4860b5b..745be35b56 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -57,10 +57,14 @@ void *curand_dso_handle = nullptr; #endif /* include all needed curand functions in HPPL */ -#define CURAND_RAND_ROUTINE_EACH(__macro) \ - __macro(curandCreateGenerator) __macro(curandSetStream) \ - __macro(curandSetPseudoRandomGeneratorSeed) \ - __macro(curandGenerateUniform) __macro(curandGenerateUniformDouble) +// clang-format off +#define CURAND_RAND_ROUTINE_EACH(__macro) \ + __macro(curandCreateGenerator) \ + __macro(curandSetStream) \ + __macro(curandSetPseudoRandomGeneratorSeed)\ + __macro(curandGenerateUniform) \ + __macro(curandGenerateUniformDouble) +// clang-format on CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) @@ -99,25 +103,38 @@ void *cudart_dso_handle = nullptr; #endif /* include all needed cuda functions in HPPL */ -#define CUDA_ROUTINE_EACH(__macro) \ - __macro(cudaMalloc) __macro(cudaHostAlloc) __macro(cudaFree) \ - __macro(cudaFreeHost) __macro(cudaMemcpy) __macro(cudaMemset) __macro( \ - cudaMemcpyAsync) __macro(cudaSetDevice) __macro(cudaGetDevice) \ - __macro(cudaGetDeviceCount) __macro(cudaGetDeviceProperties) \ - __macro(cudaDeviceSynchronize) __macro(cudaDeviceCanAccessPeer) \ - __macro(cudaDeviceEnablePeerAccess) \ - __macro(cudaStreamCreate) __macro(cudaStreamDestroy) \ - __macro(cudaStreamSynchronize) __macro( \ - cudaStreamWaitEvent) __macro(cudaEventCreate) \ - __macro(cudaEventRecord) __macro(cudaEventQuery) \ - __macro(cudaEventDestroy) __macro( \ - cudaEventSynchronize) \ - __macro(cudaEventElapsedTime) __macro( \ - cudaSetDeviceFlags) \ - __macro(cudaGetLastError) __macro( \ - cudaFuncSetCacheConfig) \ - __macro(cudaRuntimeGetVersion) \ - __macro(cudaGetErrorString) +// clang-format off +#define CUDA_ROUTINE_EACH(__macro) \ + __macro(cudaMalloc) \ + __macro(cudaHostAlloc) \ + __macro(cudaFree) \ + __macro(cudaFreeHost) \ + __macro(cudaMemcpy) \ + __macro(cudaMemset) \ + __macro(cudaMemcpyAsync) \ + __macro(cudaSetDevice) \ + __macro(cudaGetDevice) \ + __macro(cudaGetDeviceCount) \ + __macro(cudaGetDeviceProperties) \ + __macro(cudaDeviceSynchronize) \ + __macro(cudaDeviceCanAccessPeer) \ + __macro(cudaDeviceEnablePeerAccess) \ + __macro(cudaStreamCreate) \ + __macro(cudaStreamDestroy) \ + __macro(cudaStreamSynchronize) \ + __macro(cudaStreamWaitEvent) \ + __macro(cudaEventCreate) \ + __macro(cudaEventRecord) \ + __macro(cudaEventQuery) \ + __macro(cudaEventDestroy) \ + __macro(cudaEventSynchronize) \ + __macro(cudaEventElapsedTime) \ + __macro(cudaSetDeviceFlags) \ + __macro(cudaGetLastError) \ + __macro(cudaFuncSetCacheConfig) \ + __macro(cudaRuntimeGetVersion) \ + __macro(cudaGetErrorString) +// clang-format on CUDA_ROUTINE_EACH(DYNAMIC_LOAD_CUDART_WRAP) diff --git a/paddle/cuda/src/hl_cudart_wrap.cc b/paddle/cuda/src/hl_cudart_wrap.cc index 610b47581c..ff6b830b7a 100644 --- a/paddle/cuda/src/hl_cudart_wrap.cc +++ b/paddle/cuda/src/hl_cudart_wrap.cc @@ -47,17 +47,20 @@ extern void *cudart_dso_handle; } __name; /* struct DynLoad__##__name */ /* include all needed cuda functions in HPPL */ -#define CUDA_ROUTINE_EACH(__macro) \ - __macro(cudaLaunch, cudaError_t) __macro(cudaSetupArgument, cudaError_t) \ - __macro(cudaConfigureCall, cudaError_t) \ - __macro(__cudaRegisterFatBinary, void **) \ - __macro(__cudaUnregisterFatBinary, void) \ - __macro(__cudaRegisterFunction, void) \ - __macro(__cudaRegisterVar, void) \ - __macro(__cudaRegisterManagedVar, void) \ - __macro(__cudaInitModule, char) \ - __macro(__cudaRegisterTexture, void) \ - __macro(__cudaRegisterSurface, void) +// clang-format off +#define CUDA_ROUTINE_EACH(__macro) \ + __macro(cudaLaunch, cudaError_t) \ + __macro(cudaSetupArgument, cudaError_t) \ + __macro(cudaConfigureCall, cudaError_t) \ + __macro(__cudaRegisterFatBinary, void**) \ + __macro(__cudaUnregisterFatBinary, void) \ + __macro(__cudaRegisterFunction, void) \ + __macro(__cudaRegisterVar, void) \ + __macro(__cudaRegisterManagedVar, void) \ + __macro(__cudaInitModule, char) \ + __macro(__cudaRegisterTexture, void) \ + __macro(__cudaRegisterSurface, void) +// clang-format on CUDA_ROUTINE_EACH(DYNAMIC_LOAD_CUDART_WRAP) -- GitLab From a61bf5a65f62b2ef2aec153dc7fd01d22543df87 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 22 Nov 2016 20:51:59 +0800 Subject: [PATCH 0103/1503] Refine quick start index.rst --- doc_cn/demo/quick_start/index.rst | 105 ++++++++++++------------------ 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/doc_cn/demo/quick_start/index.rst b/doc_cn/demo/quick_start/index.rst index 9dabf1f661..08c1c8413b 100644 --- a/doc_cn/demo/quick_start/index.rst +++ b/doc_cn/demo/quick_start/index.rst @@ -40,7 +40,7 @@ PaddlePaddle快速入门教程 ------------ 接下来我们将展示如何用PaddlePaddle训练一个文本分类模型,将 `Amazon电子产品评论数据 `_ 分为好评(正样本)和差评(负样本)两种类别。 -`源代码 `_ 的 ``demo/quick_start`` 目录里提供了该数据的下载脚本和预处理脚本。 +`源代码 `_ 的 ``demo/quick_start`` 目录里提供了该数据的下载脚本和预处理脚本,你只需要在命令行输入以下命令,就能够很方便的完成数据下载和相应的预处理工作。 .. code-block:: bash @@ -51,7 +51,7 @@ PaddlePaddle快速入门教程 向系统传送数据 ============== -Python数据读取脚本 +Python脚本读取数据 ------------------ `DataProvider <../../ui/data_provider/index.html>`_ 是PaddlePaddle负责提供数据的模块。``DataProvider`` 主要职责在于将训练数据传入内存或者显存,让模型能够得到训练更新,其包括两个函数: @@ -146,7 +146,7 @@ Python数据读取脚本 以下是对上述数据加载的解释: - data/train.list,data/test.list: 指定训练数据和测试数据 -- module="dataprovider_bow": 数据处理的Python脚本文件名 +- module="dataprovider_bow": 处理数据的Python脚本文件 - obj="process": 指定生成数据的函数 - args={"dictionary": word_dict}: 额外的参数,这里指定词典 @@ -162,8 +162,8 @@ Python数据读取脚本 :scale: 80% -我们将以基本的逻辑回归网络作为起点,并逐渐展示更加深入的功能。更详细的网络配置连接请参考 `Layer文档 <../../../doc/layer.html>`_ 。 -所有配置都在 `源代码 `_ 的 ``demo/quick_start`` 目录下。 +我们将以最基本的逻辑回归网络作为起点,并逐渐展示更加深入的功能。更详细的网络配置连接请参考 `Layer文档 <../../../doc/layer.html>`_ 。 +所有配置都能在 `源代码 `_ 的 ``demo/quick_start`` 目录下找到。 逻辑回归模型 ------------ @@ -174,7 +174,7 @@ Python数据读取脚本 :align: center :scale: 80% -- 获取利用one-hot vector表示的每个单词,维度是词典大小 +- 获取利用 `one-hot vector `_ 表示的每个单词,维度是词典大小 .. code-block:: python @@ -198,7 +198,7 @@ Python数据读取脚本 classification_cost(input=output, label=label) - - input: 除过data层,每个层都有一个或多个input,多个input以list方式输入 + - input: 除去data层,每个层都有一个或多个input,多个input以list方式输入 - size: 该层神经元个数 - act_type: 激活函数类型 @@ -213,7 +213,7 @@ Python数据读取脚本 词向量模型 ---------- -embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb.py``,词向量模型、 +embedding模型需要稍微改变提供数据的Python脚本,即 ``dataprovider_emb.py``,词向量模型、 卷积模型、时序模型均使用该脚本。其中文本输入类型定义为整数时序类型integer_value_sequence。 .. code-block:: python @@ -232,20 +232,19 @@ embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb. ... # omitted, it is same as the data provider for LR model -该模型依然是使用逻辑回归分类网络的框架, 只是将句子利用连续向量表示替换稀疏 -向量表示, 即对第3步进行替换。句子表示的计算更新为2步: +该模型依然使用逻辑回归分类网络的框架, 只是将句子用连续向量表示替换为用稀疏向量表示, 即对第三步进行替换。句子表示的计算更新为两步: .. image:: NetContinuous.jpg :align: center :scale: 80% -- 利用单词Id查找对应的该单词的连续表示向量(维度为word_dim), 输入N个单词,输出为N个word_dim维度向量 +- 利用单词Id查找该单词对应的连续向量(维度为word_dim), 输入N个单词,输出为N个word_dim维度向量 .. code-block:: python emb = embedding_layer(input=word, size=word_dim) -- 将该句话包含的所有单词向量求平均得到句子的表示 +- 将该句话包含的所有单词向量求平均, 得到句子的表示 .. code-block:: python @@ -264,20 +263,21 @@ embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb. 卷积模型 ----------- -卷积网络是一种特殊的从词向量表示到句子表示的方法, 也就是将词向量模型额步 -骤3-2进行进一步演化, 变为3个新的子步骤。 +卷积网络是一种特殊的从词向量表示到句子表示的方法, 也就是将词向量模型进一步演化为三个新步骤。 .. image:: NetConv.jpg :align: center :scale: 80% -文本卷积分为三个步骤: +文本卷积分可为三个步骤: -1. 获取每个单词左右各k个近邻, 拼接成一个新的向量表示; +1. 首先,从每个单词左右两端分别获取k个相邻的单词, 拼接成一个新的向量; -2. 对该表示进行非线性变换 (例如Sigmoid变换), 成为维度为hidden_dim的新的向量; +2. 其次,对该向量进行非线性变换(例如Sigmoid变换), 使其转变为维度为hidden_dim的新向量; -3. 在每个维度上取出在该句话新的向量集合上该维度的最大值作为最后的句子表示向量。 这3个子步骤可配置为: +3. 最后,对整个新向量集合的每一个维度取最大值来表示最后的句子。 + +这三个步骤可配置为: .. code-block:: python @@ -300,7 +300,7 @@ embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb. :align: center :scale: 80% -时序模型即为RNN模型, 包括简单的RNN模型、GRU模型、LSTM模型等。 +时序模型,也称为RNN模型, 包括简单的RNN模型, GRU模型和LSTM模型等等。 - GRU模型配置: @@ -315,7 +315,7 @@ embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb. lstm = simple_lstm(input=emb, size=lstm_size) -针对本问题,我们采用单层LSTM模型,并使用了Dropout,**效果总结:** +本次试验,我们采用单层LSTM模型,并使用了Dropout,**效果总结:** ===================== =============================== ========================= 网络名称 参数数量 错误率 @@ -326,8 +326,8 @@ embedding模型需要稍微改变数据提供的脚本,即 ``dataprovider_emb. 优化算法 ========= -`优化算法 <../../../doc/ui/trainer_config_helpers_api.html#module-paddle.trainer_config_helpers.optimizers>`_ 包括 -Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优化方法,加了L2正则和梯度截断。 +`优化算法 `_ 包括 +Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优化方法,同时使用了L2正则和梯度截断。 .. code-block:: python @@ -340,13 +340,19 @@ Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优 训练模型 ========= -在完成了数据和网络结构搭建之后, 我们进入到训练部分。 +在数据加载和网络配置完成之后, 我们就可以训练模型了。 .. image:: PipelineTrain.jpg :align: center :scale: 80% -训练脚本:我们将训练的命令行保存在了 ``train.sh`` 文件中。训练时所需设置的主要参数如下: +训练模型,我们只需要运行 ``train.sh`` 训练脚本: + + .. code-block:: bash + + ./train.sh + +``train.sh``中包含了训练模型的基本命令。训练时所需设置的主要参数如下: .. code-block:: bash @@ -357,30 +363,19 @@ Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优 --num_passes=15 \ --use_gpu=false -这里没有介绍多机分布式训练,可以参考 `分布式训练 <../../cluster/index.html>`_ 的demo学习如何进行多机训练。 +这里只简单介绍了单机训练,如何进行分布式训练,可以参考教程 `分布式训练 <../../cluster/index.html>`_ 。 预测 ===== -可以使用训练好的模型评估带有label的验证集,也可以预测没有label的测试集。 +当模型训练好了之后,我们就可以进行预测了。 .. image:: PipelineTest.jpg :align: center :scale: 80% -测试脚本如下,将会测试配置文件中test.list指定的数据。 - - .. code-block:: bash - - paddle train \ - --use_gpu=false \ - --job=test \ - --init_model_path=./output/pass-0000x - -可以参考 `Python API预测 <../../ui/predict/swig_py_paddle.html>`_ -教程,或其他 `demo <../../demo/index.html>`_ 的Python预测过程。也可以通过如下方式预测。 - -预测脚本(``predict.sh``): +之前配置文件中 ``test.list`` 指定的数据将会被测试,这里直接通过预测脚本 ``predict.sh`` 进行预测, +更详细的说明,可以参考 `Python API预测 <../../ui/predict/swig_py_paddle.html>`_ 教程。 .. code-block:: bash @@ -395,8 +390,7 @@ Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优 mv rank-00000 result.txt -这里以 ``output/pass-00003`` 为例进行预测,用户可以根据训练log选择test结果最好的模型来预测。与训练网络配置不同的是:无需label相关的层,指定outputs输出概率层(softmax输出), -指定batch_size=1,数据传输无需label数据,预测数据指定test_list的位置。 +这里以 ``output/pass-00003`` 为例进行预测,用户可以根据训练日志,选择测试结果最好的模型来预测。 预测结果以文本的形式保存在 ``result.txt`` 中,一行为一个样本,格式如下: @@ -405,29 +399,14 @@ Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优 预测ID;ID为0的概率 ID为1的概率 预测ID;ID为0的概率 ID为1的概率 - .. code-block:: python - - is_predict = get_config_arg('is_predict', bool, False) - trn = 'data/train.list' if not is_predict else None - tst = 'data/test.list' if not is_predict else 'data/pred.list' - obj = 'process' if not is_predict else 'process_pre' - batch_size = 128 if not is_predict else 1 - if is_predict: - maxid = maxid_layer(output) - outputs([maxid,output]) - else: - label = data_layer(name="label", size=2) - cls = classification_cost(input=output, label=label) - outputs(cls) - 总体效果总结 ============== -这些流程中的数据下载、网络配置、训练脚本在 ``/demo/quick_start`` 目录,我们在此总 -结上述网络结构在Amazon-Elec测试集(25k)上的效果: +在 ``/demo/quick_start`` 目录下,能够找到这里使用的所有数据, 网络配置, 训练脚本等等。 +对于Amazon-Elec测试集(25k), 如下表格,展示了上述网络模型的训练效果: ===================== =============================== ============= ================================== - 网络名称 参数数量 错误率 配置文件 + 网络名称 参数数量 错误率 配置文件 ===================== =============================== ============= ================================== 逻辑回归模型 252 KB 8.652% trainer_config.lr.py 词向量模型 15 MB 8.484% trainer_config.emb.py @@ -460,15 +439,15 @@ Momentum, RMSProp,AdaDelta,AdaGrad,ADAM,Adamax等,这里采用Adam优 TrainerInternal.cpp:160] Batch=20 samples=2560 AvgCost=0.628761 CurrentCost=0.628761 Eval: classification_error_evaluator=0.304297 CurrentEval: classification_error_evaluator=0.304297 -模型训练会看到这样的日志,详细的参数解释如下面表格: +模型训练会看到类似上面这样的日志信息,详细的参数解释,请参考如下表格: - =========================================== ========================================================== + =========================================== ============================================================== 名称 解释 - =========================================== ========================================================== + =========================================== ============================================================== Batch=20 表示过了20个batch samples=2560 表示过了2560个样本 AvgCost 每个pass的第0个batch到当前batch所有样本的平均cost CurrentCost 当前log_period个batch所有样本的平均cost Eval: classification_error_evaluator 每个pass的第0个batch到当前batch所有样本的平均分类错误率 CurrentEval: classification_error_evaluator 当前log_period个batch所有样本的平均分类错误率 - =========================================== ========================================================== + =========================================== ============================================================== -- GitLab From 1a8fcc00de74ac2d54297472a13fb4cb410dc34d Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 23 Nov 2016 11:01:12 +0800 Subject: [PATCH 0104/1503] fix the code style --- python/paddle/trainer/config_parser.py | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 1d5e0459b4..9db42bf172 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -592,6 +592,7 @@ class DotMulProjection(Projection): def calc_parameter_dims(self, input_size, output_size): return [1, output_size] + # ScalingProjection @config_class class ScalingProjection(Projection): @@ -808,17 +809,18 @@ class BilinearInterp(Cfg): # please refer to the comments in proto/ModelConfig.proto @config_class class Pool(Cfg): - def __init__(self, - pool_type, - channels, - size_x, - size_y=None, - img_width=None, - start=None, - stride=None, # 1 by defalut in protobuf - stride_y=None, - padding=None, # 0 by defalut in protobuf - padding_y=None): + def __init__( + self, + pool_type, + channels, + size_x, + size_y=None, + img_width=None, + start=None, + stride=None, # 1 by defalut in protobuf + stride_y=None, + padding=None, # 0 by defalut in protobuf + padding_y=None): self.add_keys(locals()) @@ -1114,12 +1116,12 @@ def parse_pool(pool, input_layer_name, pool_conf): if pool.padding is not None: pool_conf.padding = pool.padding pool_conf.padding_y = default(pool.padding_y, pool_conf.padding) - pool_conf.output_x = cnn_output_size( - pool_conf.img_size, pool_conf.size_x, pool_conf.padding, - pool_conf.stride, False) - pool_conf.output_y = cnn_output_size( - pool_conf.img_size_y, pool_conf.size_y, pool_conf.padding_y, - pool_conf.stride_y, False) + pool_conf.output_x = cnn_output_size(pool_conf.img_size, pool_conf.size_x, + pool_conf.padding, pool_conf.stride, + False) + pool_conf.output_y = cnn_output_size(pool_conf.img_size_y, pool_conf.size_y, + pool_conf.padding_y, + pool_conf.stride_y, False) def parse_spp(spp, input_layer_name, spp_conf): -- GitLab From 553d6e88adcf103d473a0813da97e288ceb2c45b Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 23 Nov 2016 11:41:48 +0800 Subject: [PATCH 0105/1503] follow comments on compile_options.rst --- .../cmake/compile_options.csv | 26 +++++++-------- .../cmake/compile_options.rst | 32 +++++++------------ 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/doc_cn/build_and_install/cmake/compile_options.csv b/doc_cn/build_and_install/cmake/compile_options.csv index 36a78e8227..12b45eebb2 100644 --- a/doc_cn/build_and_install/cmake/compile_options.csv +++ b/doc_cn/build_and_install/cmake/compile_options.csv @@ -1,14 +1,14 @@ -选项,说明,默认值 -WITH_GPU,是否支持GPU。,取决于是否寻找到CUDA工具链 -WITH_DOUBLE,是否使用双精度浮点数。,否 -WITH_DSO,是否运行时动态加载CUDA动态库,而非静态加载。,是 -WITH_AVX,是否编译含有AVX指令集的PaddlePaddle二进制文件,是 -WITH_PYTHON,是否内嵌PYTHON解释器。该选项方便今后PaddlePaddle移植到没有PYTHON的嵌入式设备上。,是 -WITH_STYLE_CHECK,是否编译时进行代码风格检查,是 -WITH_RDMA,是否开启RDMA,否 -WITH_GLOG,是否开启GLOG。如果不开启,则会使用一个简化版的日志。该选项方便今后PaddlePaddle移植到没有GLOG的嵌入式设备上。,取决于是否寻找到GLOG -WITH_GFLAGS,是否使用GFLAGS。如果不开启,则会使用一个简化版的命令行参数解析器。该选项方便今后PaddlePaddle移植到没有GFLAGS的嵌入式设备上。,取决于是否寻找到GFLAGS -WITH_TIMER,是否开启计时功能。如果开启会导致运行略慢,打印的日志变多,但是方便调试和测Benchmark,否 -WITH_TESTING,是否开启单元测试,取决于是否寻找到GTEST -WITH_DOC,是否编译中英文文档,否 +选项,说明,默认值 +WITH_GPU,是否支持GPU。,取决于是否寻找到CUDA工具链 +WITH_DOUBLE,是否使用双精度浮点数。,否 +WITH_DSO,是否运行时动态加载CUDA动态库,而非静态加载CUDA动态库。,是 +WITH_AVX,是否编译含有AVX指令集的PaddlePaddle二进制文件,是 +WITH_PYTHON,是否内嵌PYTHON解释器。方便今后的嵌入式移植工作。,是 +WITH_STYLE_CHECK,是否编译时进行代码风格检查,是 +WITH_RDMA,是否开启RDMA,否 +WITH_GLOG,是否开启GLOG。如果不开启,则会使用一个简化版的日志,同时方便今后的嵌入式移植工作。,取决于是否寻找到GLOG +WITH_GFLAGS,是否使用GFLAGS。如果不开启,则会使用一个简化版的命令行参数解析器,同时方便今后的嵌入式移植工作。,取决于是否寻找到GFLAGS +WITH_TIMER,是否开启计时功能。如果开启会导致运行略慢,打印的日志变多,但是方便调试和测Benchmark,否 +WITH_TESTING,是否开启单元测试,取决于是否寻找到GTEST +WITH_DOC,是否编译中英文文档,否 WITH_SWIG_PY,是否编译PYTHON的SWIG接口,该接口可用于预测和定制化训练,取决于是否寻找到SWIG \ No newline at end of file diff --git a/doc_cn/build_and_install/cmake/compile_options.rst b/doc_cn/build_and_install/cmake/compile_options.rst index abd7c56288..f345ead2bf 100644 --- a/doc_cn/build_and_install/cmake/compile_options.rst +++ b/doc_cn/build_and_install/cmake/compile_options.rst @@ -1,10 +1,10 @@ PaddlePaddle的编译选项 -===================== +====================== PaddlePaddle的编译选项,包括生成CPU/GPU二进制文件、链接何种BLAS库等。用户可在调用cmake的时候设置它们,详细的cmake使用方法可以参考 `官方文档 `_ 。 Bool型的编译选项 --------------------- +---------------- 用户可在cmake的命令行中,通过使用 ``-D`` 命令设置该类编译选项,例如 .. code-block:: bash @@ -15,10 +15,10 @@ Bool型的编译选项 :widths: 1, 7, 2 :file: compile_options.csv -路径相关的编译选项 --------------------- -BLAS路径相关 -+++++++++++++ +BLAS/CUDA/Cudnn的编译选项 +-------------------------- +BLAS ++++++ PaddlePaddle支持以下任意一种BLAS库:`MKL `_ ,`ATLAS `_ ,`OpenBlAS `_ 和 `REFERENCE BLAS `_ 。 @@ -26,28 +26,18 @@ PaddlePaddle支持以下任意一种BLAS库:`MKL Date: Wed, 23 Nov 2016 15:49:06 +0800 Subject: [PATCH 0106/1503] Remove glossary --- .../rnn}/glossary_rnn.dot | 0 .../rnn}/glossary_rnn_with_memory.dot | 0 doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 124 ++++++++++++------ doc_cn/concepts/glossary.rst | 93 ------------- doc_cn/index.rst | 1 - 5 files changed, 86 insertions(+), 132 deletions(-) rename doc_cn/{concepts => algorithm/rnn}/glossary_rnn.dot (100%) rename doc_cn/{concepts => algorithm/rnn}/glossary_rnn_with_memory.dot (100%) delete mode 100644 doc_cn/concepts/glossary.rst diff --git a/doc_cn/concepts/glossary_rnn.dot b/doc_cn/algorithm/rnn/glossary_rnn.dot similarity index 100% rename from doc_cn/concepts/glossary_rnn.dot rename to doc_cn/algorithm/rnn/glossary_rnn.dot diff --git a/doc_cn/concepts/glossary_rnn_with_memory.dot b/doc_cn/algorithm/rnn/glossary_rnn_with_memory.dot similarity index 100% rename from doc_cn/concepts/glossary_rnn_with_memory.dot rename to doc_cn/algorithm/rnn/glossary_rnn_with_memory.dot diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index 09172c53f7..896dd7ada9 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -4,16 +4,16 @@ 单双层RNN API对比介绍 ##################### -这篇教程主要介绍了\ :ref:`glossary_双层RNN`\ 的API接口。本文中的以\ :ref:`glossary_paddle`\ 的\ :ref:`glossary_双层RNN`\ 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用\ :ref:`glossary_双层RNN`\ 。本文中所有的例子,都只是介绍\ :ref:`glossary_双层RNN`\ 的API接口,并不是使用\ :ref:`glossary_双层RNN`\ 解决实际的问题。如果想要了解\ :ref:`glossary_双层RNN`\ 在具体问题中的使用,请参考\ :ref:`algo_hrnn_demo`\ 。文章中示例所使用的单元测试文件是\ `test_RecurrentGradientMachine.cpp `_\ 。 +这篇教程主要介绍了\ :ref:`glossary_双层RNN`\ 的API接口。本文中的以PaddlePaddle的\ :ref:`glossary_双层RNN`\ 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用\ :ref:`glossary_双层RNN`\ 。本文中所有的例子,都只是介绍\ :ref:`glossary_双层RNN`\ 的API接口,并不是使用\ :ref:`glossary_双层RNN`\ 解决实际的问题。如果想要了解\ :ref:`glossary_双层RNN`\ 在具体问题中的使用,请参考\ :ref:`algo_hrnn_demo`\ 。文章中示例所使用的单元测试文件是\ `test_RecurrentGradientMachine.cpp `_\ 。 示例1:双层RNN,子序列间无Memory ================================ -在\ :ref:`glossary_双层RNN`\ 中的经典情况是将内层的每一个\ :ref:`glossary_Sequence`\ 数据,分别进行序列操作。并且内层的序列操作之间是独立没有依赖的,即不需要使用\ :ref:`glossary_Memory`\ 的。 +在\ :ref:`glossary_双层RNN`\ 中的经典情况是将内层的每一个\ :ref:`glossary_sequence`\ 数据,分别进行序列操作。并且内层的序列操作之间是独立没有依赖的,即不需要使用\ :ref:`glossary_Memory`\ 的。 -在本问题中,单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 的网络配置,都是将每一句分好词后的句子,使用\ :ref:`glossary_lstm`\ 作为\ :ref:`glossary_encoder`\ ,压缩成一个向量。区别是\ :ref:`glossary_RNN`\ 使用两层序列模型,将多句话看成一个整体,同时使用\ :ref:`glossary_encoder`\ 压缩,二者语意上完全一致。这组语意相同的示例配置如下 +在本问题中,单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 的网络配置,都是将每一句分好词后的句子,使用LSTM作为encoder,压缩成一个向量。区别是\ :ref:`glossary_RNN`\ 使用两层序列模型,将多句话看成一个整体,同时使用encoder压缩,二者语意上完全一致。这组语意相同的示例配置如下 -* 单层 \:ref:`glossary_RNN`\: `sequence_layer_group.conf `_ +* 单层\ :ref:`glossary_RNN`\: `sequence_layer_group.conf `_ * :ref:`glossary_双层RNN`\: `sequence_nest_layer_group.conf `_ @@ -22,13 +22,13 @@ 首先,本示例中使用的原始数据如下\: -- 本里中的原始数据一共有10个\ :ref:`glossary_sample`\ 。每个\ :ref:`glossary_sample`\ 由两部分组成,一个label(此处都为2)和一个已经分词后的句子。这个数据也被单层\ :ref:`glossary_RNN`\ 网络直接使用。 +- 本里中的原始数据一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。这个数据也被单层\ :ref:`glossary_RNN`\ 网络直接使用。 .. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg :language: text -- 双层序列数据一共有4个\ :ref:`glossary_sample`\ 。 每个样本间用空行分开,整体数据和原始数据完全一样。而对于双层序列的\ :ref:`glossary_lstm`\ 来说,第一条数据同时\ :ref:`glossary_encode` 两条数据成两个向量。这四条数据同时处理的句子为\ :code:`[2, 3, 2, 3]`\ 。 +- 双层序列数据一共有4个样本。 每个样本间用空行分开,整体数据和原始数据完全一样。而对于双层序列的LSTM来说,第一条数据同时encode两条数据成两个向量。这四条数据同时处理的句子为\ :code:`[2, 3, 2, 3]`\ 。 .. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg.nest :language: text @@ -40,10 +40,10 @@ :lines: 21-39 :linenos: -- 这是普通的单层\ :ref:`glossary_Sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: +- 这是普通的单层\ :ref:`glossary_sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: * :ref:`glossary_DataProvider`\ 共返回两个数据,分别是words和label。即上述代码中的第19行。 - - words是原始数据中的每一句话,所对应的词表index数组。它是integer_value_sequence类型的,即整数数组。words即为这个数据中的单层\ :ref:`glossary_Sequence`\ 。 + - words是原始数据中的每一句话,所对应的词表index数组。它是integer_value_sequence类型的,即整数数组。words即为这个数据中的单层\ :ref:`glossary_sequence`\ 。 - label是原始数据中对于每一句话的分类标签,它是integer_value类型的。 .. literalinclude:: ../../../paddle/gserver/tests/sequenceGen.py @@ -51,17 +51,17 @@ :lines: 42-71 :linenos: -- 这是对于同样的数据,本示例中双层\ :ref:`glossary_Sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: +- 这是对于同样的数据,本示例中双层\ :ref:`glossary_sequence`\ 的\ :ref:`glossary_DataProvider`\ 代码,其说明如下: - :ref:`glossary_DataProvider`\ 共返回两组数据,分别是sentences和labels。即在双层序列的原始数据中,每一组内的所有句子和labels - - sentences是双层\ :ref:`glossary_Sequence`\ 的数据。他内部包括了每组数据中的所有句子,又使用句子中每一个单词的词表index表示每一个句子,故为双层\ :ref:`glossary_Sequence`\ 。类型为 integer_value_sub_sequence 。 - - labels是每组内每一个句子的标签,故而是一个单层\ :ref:`glossary_Sequence`\ 。 + - sentences是双层\ :ref:`glossary_sequence`\ 的数据。他内部包括了每组数据中的所有句子,又使用句子中每一个单词的词表index表示每一个句子,故为双层\ :ref:`glossary_sequence`\ 。类型为 integer_value_sub_sequence 。 + - labels是每组内每一个句子的标签,故而是一个单层\ :ref:`glossary_sequence`\ 。 :ref:`glossary_trainer_config`\ 的模型配置 ------------------------------------------ -首先,我们看一下单层\ :ref:`glossary_RNN`\ 的配置。代码中9-15行即为单层RNN序列的使用代码。这里使用了\ :ref:`glossary_paddle`\ 预定义好的\ :ref:`glossary_RNN`\ 处理函数。在这个函数中,\ :ref:`glossary_RNN`\ 对于每一个\ :ref:`glossary_timestep`\ 通过了一个\ :ref:`glossary_lstm`\ 网络。 +首先,我们看一下单层\ :ref:`glossary_RNN`\ 的配置。代码中9-15行即为单层RNN序列的使用代码。这里使用了PaddlePaddle预定义好的\ :ref:`glossary_RNN`\ 处理函数。在这个函数中,\ :ref:`glossary_RNN`\ 对于每一个\ :ref:`glossary_timestep`\ 通过了一个LSTM网络。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_layer_group.conf :language: python @@ -72,15 +72,15 @@ 其次,我们看一下语义相同的\ :ref:`glossary_双层RNN`\ 的网络配置。 -* :ref:`glossary_paddle`\ 中的许多layer并不在意输入是否是\ :ref:`glossary_Sequence`\ ,例如\ :code:`embedding_layer`\ 。在这些layer中,所有的操作都是针对每一个\ :ref:`glossary_timestep`\ 来进行的。 +* PaddlePaddle中的许多layer并不在意输入是否是\ :ref:`glossary_sequence`\ ,例如\ :code:`embedding_layer`\ 。在这些layer中,所有的操作都是针对每一个\ :ref:`glossary_timestep`\ 来进行的。 -* 在该配置中,7-26行将双层\ :ref:`glossary_Sequence`\ 数据,先变换成单层\ :ref:`glossary_Sequence`\ 数据,在对每一个单层\ :ref:`glossary_Sequence`\ 进行处理。 +* 在该配置中,7-26行将双层\ :ref:`glossary_sequence`\ 数据,先变换成单层\ :ref:`glossary_sequence`\ 数据,在对每一个单层\ :ref:`glossary_sequence`\ 进行处理。 - * 使用\ :code:`recurrent_group`\ 这个函数进行变换,在变换时需要将输入序列传入。由于我们想要的变换是双层\ :ref:`glossary_Sequence`\ => 单层\ :ref:`glossary_Sequence`\ ,所以我们需要将输入数据标记成\ :code:`SubsequenceInput`\ 。 + * 使用\ :code:`recurrent_group`\ 这个函数进行变换,在变换时需要将输入序列传入。由于我们想要的变换是双层\ :ref:`glossary_sequence`\ => 单层\ :ref:`glossary_sequence`\ ,所以我们需要将输入数据标记成\ :code:`SubsequenceInput`\ 。 - * 在本例中,我们将原始数据的每一组,通过\ :code:`recurrent_group`\ 进行拆解,拆解成的每一句话再通过一个\ :ref:`glossary_lstm`\ 网络。这和单层\ :ref:`glossary_RNN`\ 的配置是等价的。 + * 在本例中,我们将原始数据的每一组,通过\ :code:`recurrent_group`\ 进行拆解,拆解成的每一句话再通过一个LSTM网络。这和单层\ :ref:`glossary_RNN`\ 的配置是等价的。 -* 与单层\ :ref:`glossary_RNN`\ 的配置类似,我们只需要知道使用\ :ref:`glossary_lstm` :ref:`glossary_encode`\ 成的最后一个向量。所以对\ :code:`recurrent_group`\ 进行了\ :code:`last_seq`\ 操作。但是,和单层\ :ref:`glossary_RNN`\ 有区别的地方是,我们是对每一个子序列取最后一个元素。于是我们设置\ :code:`agg_level=AggregateLevel.EACH_SEQUENCE`\ 。 +* 与单层\ :ref:`glossary_RNN`\ 的配置类似,我们只需要知道使用LSTM encode成的最后一个向量。所以对\ :code:`recurrent_group`\ 进行了\ :code:`last_seq`\ 操作。但是,和单层\ :ref:`glossary_RNN`\ 有区别的地方是,我们是对每一个子序列取最后一个元素。于是我们设置\ :code:`agg_level=AggregateLevel.EACH_SEQUENCE`\ 。 * 至此,\ :code:`lstm_last`\ 便和单层\ :ref:`glossary_RNN`\ 的配置中的\ :code:`lstm_last`\ 具有相同的结果了。 @@ -93,43 +93,32 @@ 示例2::ref:`glossary_双层RNN`,子序列间有\ :ref:`glossary_Memory` ================================================================== -本示例中,意图使用单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 同时实现一个完全等价的全连接\ :ref:`glossary_RNN`\ 。对于单层\ :ref:`glossary_RNN`\ ,输入数据为一个完整的\ :ref:`glossary_Sequence`\ ,例如\ :code:`[4, 5, 2, 0, 9, 8, 1, 4]`\ 。而对于\ :ref:`glossary_双层RNN`\ ,输入数据为在单层\ :ref:`glossary_RNN`\ 数据里面,任意将一些数据组合成双层\ :ref:`glossary_Sequence`\ ,例如\ :code:`[ [4, 5, 2], [0, 9], [8, 1, 4]]`。 +本示例中,意图使用单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 同时实现一个完全等价的全连接\ :ref:`glossary_RNN`\ 。对于单层\ :ref:`glossary_RNN`\ ,输入数据为一个完整的\ :ref:`glossary_sequence`\ ,例如\ :code:`[4, 5, 2, 0, 9, 8, 1, 4]`\ 。而对于\ :ref:`glossary_双层RNN`\ ,输入数据为在单层\ :ref:`glossary_RNN`\ 数据里面,任意将一些数据组合成双层\ :ref:`glossary_sequence`\ ,例如\ :code:`[ [4, 5, 2], [0, 9], [8, 1, 4]]`。 :ref:`glossary_trainer_config`\ 的模型配置 ------------------------------------------ -本例配置了两个完全等价的全连接\ :ref:`glossary_RNN`\ 。对于单层序列模型的配置如下: +我们选取单双层序列配置中的不同部分,来对比分析两者语义相同的原因。 + +- 单层序列:过了一个很简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全链接。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn.conf :language: python :lines: 36-48 - :linenos: - -在该配置中,名称为\ :code:`rnn_state`\ 的全连接层暂存到了\ :ref:`glossary_Memory`\ 中。这个\ :ref:`glossary_Memory`\ 变量\ :code:`mem`\ 中可以保存到上一个\ :ref:`glossary_timestep`\ 中的全连接层的输出。从而实现一个全连接的\ :ref:`glossary_RNN`\ 。 -以数据\ :code:`[4, 5, 2, 0, 9, 8, 1, 4]`\ 举例,单层\ :ref:`glossary_RNN`\ 的网络图如下\: +- 双层序列,外层memory是一个元素: -.. graphviz:: simple_full_recurrent.dot - -而对于\ :ref:`glossary_双层RNN`\ 来说,等价的网络配置如下\: + - 内层inner_step的recurrent_group和单层序列的几乎一样。除了boot_layer=outer_mem,表示将外层的outer_mem作为内层memory的初始状态。外层outer_step中,outer_mem是一个子句的最后一个向量,即整个双层group是将前一个子句的最后一个向量,作为下一个子句memory的初始状态。 + - 从输入数据上看,单双层序列的句子是一样的,只是双层序列将其又做了子序列划分。因此双层序列的配置中,必须将前一个子句的最后一个元素,作为boot_layer传给下一个子句的memory,才能保证和单层序列的配置中“每一个时间步都用了上一个时间步的输出结果”一致。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn.conf :language: python :lines: 39-66 - :linenos: - :emphasize-lines: 4-6 -- 在该配置中,外层的\ :code:`outer_mem`\ 和内层的\ :code:`inner_mem`\ 两个变量配合,实现了和单层\ :ref:`glossary_RNN`\ 等价的全连接\ :ref:`glossary_RNN`\ 。 +- 双层序列,外层memory是单层序列: - - 外层\ :code:`outer_step`\ 中的\ :code:`outer_mem`\ 会将神经网络中每个子序列的最后一个结果记录下来。即将第18行的\ :code:`last`\ 变量记录下来。 - - 内层\ :code:`inner_step`\ 中的\ :code:`inner_mem`\ 会将神经网络中子序列中的每一个元素的结果记录下来。即将第7行的\ :code:`out`\ 变量记录下来。 - - 内层的\ :code:`inner_mem`\ 初始值是\ :code:`outer_mem`(:code:`boot_layer`)。于是前一个子序列的最后结果,是新的子序列的初试结果。即完成了简单的全连接\ :code:`glossary_RNN`\ 。 - -本例中的\ :ref:`glossary_双层RNN`\ ,以数据\ :code:`[ [4, 5, 2], [0, 9], [8, 1, 4]]`\ 举例,配置图如下\: - -.. graphviz:: simple_full_hierarchical_recurrent.dot - -这里有一点注意事项,Paddle目前实现的\ :ref:`glossary_双层RNN`\ 不完全支持内层\ :ref:`glossary_RNN`\ 的\ :ref:`glossary_Memory`\ 引用外层\ :ref:`glossary_RNN`\ 的某一层序列输入。即\ :code:`inner_mem`的\ :code:`boot_layer`\ 需要是非序列类型的,或者可以是序列类型,但是每个时间步下,序列长度是一致的。从序列类型转换为非序列类型,可以使用\ :code:`pooling_layer`, :code:`last_seq`, :code:`first_seq`\ 等操作进行转换。 + - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 + - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 示例3:双进双出,输入不等长 =========================== @@ -190,3 +179,62 @@ data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 ======================== TBD + + +词汇表 +====== + +.. _glossary_memory: + +Memory +------ + +Memory是PaddlePaddle实现 :ref:`glossary_RNN` 时候使用的一个概念。 :ref:`glossary_RNN` 即时间递归神经网络,通常要求时间步之间具有一些依赖性,即当前时间步下的神经网络依赖前一个时间步神经网络中某一个神经元输出。如下图所示。 + +.. graphviz:: glossary_rnn.dot + +上图中虚线的连接,即是跨越时间步的网络连接。PaddlePaddle在实现 :ref:`glossary_RNN` 的时候,将这种跨越时间步的连接用一个特殊的神经网络单元实现。这个神经网络单元就叫Memory。Memory可以缓存上一个时刻某一个神经元的输出,然后在下一个时间步输入给另一个神经元。使用Memory的 :ref:`glossary_RNN` 实现便如下图所示。 + +.. graphviz:: glossary_rnn_with_memory.dot + +使用这种方式,PaddlePaddle可以比较简单的判断哪些输出是应该跨越时间步的,哪些不是。 + +.. _glossary_timestep: + +时间步 +------ + +参考 :ref:`glossary_sequence` 。 + + +.. _glossary_sequence: + +时间序列 +-------- + +时间序列(time series)是指一系列的特征数据。这些特征数据之间的顺序是有意义的。即特征的数组,而不是特征的集合。而这每一个数组元素,或者每一个系列里的特征数据,即为一个时间步(time step)。值得注意的是,时间序列、时间步的概念,并不真正的和『时间』有关。只要一系列特征数据中的『顺序』是有意义的,即为时间序列的输入。 + +举例说明,例如文本分类中,我们通常将一句话理解成一个时间序列。比如一句话中的每一个单词,会变成词表中的位置。而这一句话就可以表示成这些位置的数组。例如 :code:`[9, 2, 3, 5, 3]` 。 + +关于时间序列(time series)的更详细准确的定义,可以参考 `维基百科页面 Time series `_ 或者 `维基百科中文页面 时间序列 `_ 。 + +另外,Paddle中经常会将时间序列成为 :code:`Sequence` 。他们在Paddle的文档和API中是一个概念。 + +.. _glossary_RNN: + +RNN +--- + +RNN 在PaddlePaddle的文档中,一般表示 :code:`Recurrent neural network`,即时间递归神经网络。详细介绍可以参考 `维基百科页面 Recurrent neural network `_ 或者 `中文维基百科页面 `_ 中关于时间递归神经网络的介绍。 + +RNN 一般在PaddlePaddle中,指对于一个 :ref:`glossary_sequence` 输入数据,每一个时间步之间的神经网络具有一定的相关性。例如,某一个神经元的一个输入为上一个时间步网络中某一个神经元的输出。或者,从每一个时间步来看,神经网络的网络结构中具有有向环结构。 + +.. _glossary_双层RNN: + +双层RNN +------- + +双层RNN顾名思义,即 :ref:`glossary_RNN` 之间有一次嵌套关系。输入数据整体上是一个时间序列,而对于每一个内层特征数据而言,也是一个时间序列。即二维数组,或者数组的数组这个概念。 而双层RNN是可以处理这种输入数据的网络结构。 + +例如,对于段落的文本分类,即将一段话进行分类。我们将一段话看成句子的数组,每个句子又是单词的数组。这便是一种双层RNN的输入数据。而将这个段落的每一句话用lstm编码成一个向量,再对每一句话的编码向量用lstm编码成一个段落的向量。再对这个段落向量进行分类,即为这个双层RNN的网络结构。 + diff --git a/doc_cn/concepts/glossary.rst b/doc_cn/concepts/glossary.rst deleted file mode 100644 index 518712d1fe..0000000000 --- a/doc_cn/concepts/glossary.rst +++ /dev/null @@ -1,93 +0,0 @@ -.. _glossary: - -######################## -Paddle文档中使用的词汇表 -######################## - -.. _glossary_paddle: - -PaddlePaddle ------------- - -TBD - -.. _glossary_encode: - -encode ------- - -参考\ :ref:`glossary_encoder`\ 。 - -.. _glossary_encoder: - -encoder -------- - -TBD - -.. _glossary_sample: - -样本 ----- - -TBD Sample的概念 - -.. _glossary_lstm: - -LSTM ----- - -TBD - -.. _glossary_memory: - -Memory ------- - -Memory是 :ref:`glossary_paddle` 实现 :ref:`glossary_RNN` 时候使用的一个概念。 :ref:`glossary_RNN` 即时间递归神经网络,通常要求时间步之间具有一些依赖性,即当前时间步下的神经网络依赖前一个时间步神经网络中某一个神经元输出。如下图所示。 - -.. graphviz:: glossary_rnn.dot - -上图中虚线的连接,即是跨越时间步的网络连接。:ref:`glossary_paddle` 在实现 :ref:`glossary_RNN` 的时候,将这种跨越时间步的连接用一个特殊的神经网络单元实现。这个神经网络单元就叫Memory。Memory可以缓存上一个时刻某一个神经元的输出,然后在下一个时间步输入给另一个神经元。使用Memory的 :ref:`glossary_RNN` 实现便如下图所示。 - -.. graphviz:: glossary_rnn_with_memory.dot - -使用这种方式,:ref:`glossary_paddle` 可以比较简单的判断哪些输出是应该跨越时间步的,哪些不是。 - -.. _glossary_timestep: - -时间步 ------- - -参考 :ref:`_glossary_Sequence` 。 - -.. _glossary_Sequence: - -时间序列 --------- - -时间序列(time series)是指一系列的特征数据。这些特征数据之间的顺序是有意义的。即特征的数组,而不是特征的集合。而这每一个数组元素,或者每一个系列里的特征数据,即为一个时间步(time step)。值得注意的是,时间序列、时间步的概念,并不真正的和『时间』有关。只要一系列特征数据中的『顺序』是有意义的,即为时间序列的输入。 - -举例说明,例如文本分类中,我们通常将一句话理解成一个时间序列。比如一句话中的每一个单词,会变成词表中的位置。而这一句话就可以表示成这些位置的数组。例如 :code:`[9, 2, 3, 5, 3]` 。 - -关于时间序列(time series)的更详细准确的定义,可以参考 `维基百科页面 Time series `_ 或者 `维基百科中文页面 时间序列 `_ 。 - -另外,Paddle中经常会将时间序列成为 :code:`Sequence` 。他们在Paddle的文档和API中是一个概念。 - -.. _glossary_RNN: - -RNN ---- - -RNN 在 :ref:`glossary_paddle` 的文档中,一般表示 :code:`Recurrent neural network`,即时间递归神经网络。详细介绍可以参考 `维基百科页面 Recurrent neural network `_ 或者 `中文维基百科页面 `_ 中关于时间递归神经网络的介绍。 - -RNN 一般在 :ref:`glossary_paddle` 中,指对于一个 :ref:`glossary_Sequence` 输入数据,每一个时间步之间的神经网络具有一定的相关性。例如,某一个神经元的一个输入为上一个时间步网络中某一个神经元的输出。或者,从每一个时间步来看,神经网络的网络结构中具有有向环结构。 - -.. _glossary_双层RNN: - -双层RNN -------- - -双层RNN顾名思义,即 :ref:`glossary_RNN` 之间有一次嵌套关系。输入数据整体上是一个时间序列,而对于每一个内层特征数据而言,也是一个时间序列。即二维数组,或者数组的数组这个概念。 而双层RNN是可以处理这种输入数据的网络结构。 - -例如,对于段落的文本分类,即将一段话进行分类。我们将一段话看成句子的数组,每个句子又是单词的数组。这便是一种双层RNN的输入数据。而将这个段落的每一句话用lstm编码成一个向量,再对每一句话的编码向量用lstm编码成一个段落的向量。再对这个段落向量进行分类,即为这个双层RNN的网络结构。 diff --git a/doc_cn/index.rst b/doc_cn/index.rst index fef39aa527..68128a74f8 100644 --- a/doc_cn/index.rst +++ b/doc_cn/index.rst @@ -11,7 +11,6 @@ PaddlePaddle文档 * `使用示例 `_ * `模型配置 <../doc/ui/api/trainer_config_helpers/index.html>`_ * `集群训练 `_ -* :ref:`glossary` 开发指南 -------- -- GitLab From ebd725265a0af43a563742eb58901e622a237daa Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 23 Nov 2016 16:14:27 +0800 Subject: [PATCH 0107/1503] Update the data of quick start. --- demo/quick_start/data/README.md | 9 ++++++ demo/quick_start/data/get_data.sh | 15 ++++----- demo/quick_start/data/pred.list | 1 - demo/quick_start/data/pred.txt | 2 -- .../proc_from_raw_data/get_data.sh} | 32 +++++++++++++------ .../proc_from_raw_data}/preprocess.py | 10 ++++-- doc/demo/quick_start/index_en.md | 3 +- doc_cn/demo/quick_start/index.md | 4 +-- 8 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 demo/quick_start/data/README.md delete mode 100644 demo/quick_start/data/pred.list delete mode 100644 demo/quick_start/data/pred.txt rename demo/quick_start/{preprocess.sh => data/proc_from_raw_data/get_data.sh} (68%) rename demo/quick_start/{ => data/proc_from_raw_data}/preprocess.py (95%) diff --git a/demo/quick_start/data/README.md b/demo/quick_start/data/README.md new file mode 100644 index 0000000000..63abcf7ebf --- /dev/null +++ b/demo/quick_start/data/README.md @@ -0,0 +1,9 @@ +This dataset consists of electronics product reviews associated with +binary labels (positive/negative) for sentiment classification. + +The preprocessed data can be downloaded by script `get_data.sh`. +The data was derived from reviews_Electronics_5.json.gz at + +http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz + +If you want to process the raw data, you can use the script `proc_from_raw_data/get_data.sh`. diff --git a/demo/quick_start/data/get_data.sh b/demo/quick_start/data/get_data.sh index f355d63225..952de3f3c8 100755 --- a/demo/quick_start/data/get_data.sh +++ b/demo/quick_start/data/get_data.sh @@ -17,14 +17,11 @@ set -e DIR="$( cd "$(dirname "$0")" ; pwd -P )" cd $DIR -echo "Downloading Amazon Electronics reviews data..." -# http://jmcauley.ucsd.edu/data/amazon/ -wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +# Download the preprocessed data +wget http://paddlepaddle.bj.bcebos.com/demo/quick_start_preprocessed_data/preprocessed_data.tar.gz -echo "Downloading mosesdecoder..." -#https://github.com/moses-smt/mosesdecoder -wget https://github.com/moses-smt/mosesdecoder/archive/master.zip +# Extract package +tar zxvf preprocessed_data.tar.gz -unzip master.zip -rm master.zip -echo "Done." +# Remove compressed package +rm preprocessed_data.tar.gz diff --git a/demo/quick_start/data/pred.list b/demo/quick_start/data/pred.list deleted file mode 100644 index d88b2b6385..0000000000 --- a/demo/quick_start/data/pred.list +++ /dev/null @@ -1 +0,0 @@ -./data/pred.txt diff --git a/demo/quick_start/data/pred.txt b/demo/quick_start/data/pred.txt deleted file mode 100644 index 6ed5f738dd..0000000000 --- a/demo/quick_start/data/pred.txt +++ /dev/null @@ -1,2 +0,0 @@ -the device is cute , but that 's just about all that 's good. the specs are what you 'd expect : it 's a wifi mic , with some noise filter options. the app has the option to upload your baby 's name and photo , which is a cutesy touch. but the app is otherwise unstable and useless unless you upgrade for $ 60 / year.set up involves downloading the app , turning on the mic , switching your phone to the wifi network of the mic , telling the app your wifi settings , switching your wifi back to your home router. the app is then directly connected to your mic.the app is adware ! the main screen says " cry notifications on / off : upgrade to evoz premium and receive a text message of email when your baby is crying " .but the adware points out an important limitation , this monitor is only intended to be used from your home network. if you want to access it remotely , get a webcam. this app would make a lot more sense of the premium features were included with the hardware . -don 't be fooled by my one star rating. if there was a zero , i would have selected it. this product was a waste of my money.it has never worked like the company said it supposed to. i only have one device , an iphone 4gs. after charging the the iphone mid way , the i.sound portable power max 16,000 mah is completely drained. the led light no longer lit up. when plugging the isound portable power max into a wall outlet to charge , it would charge for about 20-30 minutes and then all four battery led indicator lit up showing a full charge. i would leave it on to charge for the full 8 hours or more but each time with the same result upon using. don 't buy this thing. put your money to good use elsewhere . diff --git a/demo/quick_start/preprocess.sh b/demo/quick_start/data/proc_from_raw_data/get_data.sh similarity index 68% rename from demo/quick_start/preprocess.sh rename to demo/quick_start/data/proc_from_raw_data/get_data.sh index c9190e2dd2..9c3e9db248 100755 --- a/demo/quick_start/preprocess.sh +++ b/demo/quick_start/data/proc_from_raw_data/get_data.sh @@ -16,10 +16,23 @@ # 1. size of pos : neg = 1:1. # 2. size of testing set = min(25k, len(all_data) * 0.1), others is traning set. # 3. distinct train set and test set. -# 4. build dict set -e +DIR="$( cd "$(dirname "$0")" ; pwd -P )" +cd $DIR + +# Download data +echo "Downloading Amazon Electronics reviews data..." +# http://jmcauley.ucsd.edu/data/amazon/ +#wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +echo "Downloading mosesdecoder..." +#https://github.com/moses-smt/mosesdecoder +#wget https://github.com/moses-smt/mosesdecoder/archive/master.zip +#unzip master.zip +#rm master.zip +echo "Done." + export LC_ALL=C UNAME_STR=`uname` @@ -29,10 +42,11 @@ else SHUF_PROG='gshuf' fi -mkdir -p data/tmp -python preprocess.py -i data/reviews_Electronics_5.json.gz +# Start preprocess +mkdir -p tmp +python preprocess.py -i reviews_Electronics_5.json.gz # uniq and shuffle -cd data/tmp +cd tmp echo 'uniq and shuffle...' cat pos_*|sort|uniq|${SHUF_PROG}> pos.shuffed cat neg_*|sort|uniq|${SHUF_PROG}> neg.shuffed @@ -53,11 +67,11 @@ cat train.pos train.neg | ${SHUF_PROG} >../train.txt cat test.pos test.neg | ${SHUF_PROG} >../test.txt cd - -echo 'data/train.txt' > data/train.list -echo 'data/test.txt' > data/test.list +echo 'train.txt' > train.list +echo 'test.txt' > test.list # use 30k dict -rm -rf data/tmp -mv data/dict.txt data/dict_all.txt -cat data/dict_all.txt | head -n 30001 > data/dict.txt +rm -rf tmp +mv dict.txt dict_all.txt +cat dict_all.txt | head -n 30001 > dict.txt echo 'preprocess finished' diff --git a/demo/quick_start/preprocess.py b/demo/quick_start/data/proc_from_raw_data/preprocess.py similarity index 95% rename from demo/quick_start/preprocess.py rename to demo/quick_start/data/proc_from_raw_data/preprocess.py index d87fad632a..56c2c5f16c 100755 --- a/demo/quick_start/preprocess.py +++ b/demo/quick_start/data/proc_from_raw_data/preprocess.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -1. (remove HTML before or not)tokensizing +1. Tokenize the words and punctuation 2. pos sample : rating score 5; neg sample: rating score 1-2. Usage: @@ -76,7 +76,11 @@ def tokenize(sentences): sentences : a list of input sentences. return: a list of processed text. """ - dir = './data/mosesdecoder-master/scripts/tokenizer/tokenizer.perl' + dir = './mosesdecoder-master/scripts/tokenizer/tokenizer.perl' + if not os.path.exists(dir): + sys.exit( + "The ./mosesdecoder-master/scripts/tokenizer/tokenizer.perl does not exists." + ) tokenizer_cmd = [dir, '-l', 'en', '-q', '-'] assert isinstance(sentences, list) text = "\n".join(sentences) @@ -104,7 +108,7 @@ def tokenize_batch(id): num_batch, instance, pre_fix = parse_queue.get() if num_batch == -1: ### parse_queue finished tokenize_queue.put((-1, None, None)) - sys.stderr.write("tokenize theread %s finish\n" % (id)) + sys.stderr.write("Thread %s finish\n" % (id)) break tokenize_instance = tokenize(instance) tokenize_queue.put((num_batch, tokenize_instance, pre_fix)) diff --git a/doc/demo/quick_start/index_en.md b/doc/demo/quick_start/index_en.md index 80d816a768..01f6f8ef54 100644 --- a/doc/demo/quick_start/index_en.md +++ b/doc/demo/quick_start/index_en.md @@ -59,12 +59,11 @@ To build your text classification system, your code will need to perform five st ## Preprocess data into standardized format In this example, you are going to use [Amazon electronic product review dataset](http://jmcauley.ucsd.edu/data/amazon/) to build a bunch of deep neural network models for text classification. Each text in this dataset is a product review. This dataset has two categories: “positive” and “negative”. Positive means the reviewer likes the product, while negative means the reviewer does not like the product. -`demo/quick_start` in the [source code](https://github.com/baidu/Paddle) provides scripts for downloading data and preprocessing data as shown below. The data process takes several minutes (about 3 minutes in our machine). +`demo/quick_start` in the [source code](https://github.com/baidu/Paddle) provides script for downloading the preprocessed data as shown below. (If you want to process the raw data, you can use the script `demo/quick_start/data/proc_from_raw_data/get_data.sh`). ```bash cd demo/quick_start ./data/get_data.sh -./preprocess.sh ``` ## Transfer Data to Model diff --git a/doc_cn/demo/quick_start/index.md b/doc_cn/demo/quick_start/index.md index 4d9b24ba85..514b45a487 100644 --- a/doc_cn/demo/quick_start/index.md +++ b/doc_cn/demo/quick_start/index.md @@ -32,13 +32,11 @@ ## 数据格式准备(Data Preparation) 在本问题中,我们使用[Amazon电子产品评论数据](http://jmcauley.ucsd.edu/data/amazon/), -将评论分为好评(正样本)和差评(负样本)两类。[源码](https://github.com/baidu/Paddle)的`demo/quick_start`里提供了数据下载脚本 -和预处理脚本。 +将评论分为好评(正样本)和差评(负样本)两类。[源码](https://github.com/baidu/Paddle)的`demo/quick_start`里提供了下载已经预处理数据的脚本(如果想从最原始的数据处理,可以使用脚本 `./demo/quick_start/data/proc_from_raw_data/get_data.sh`)。 ```bash cd demo/quick_start ./data/get_data.sh -./preprocess.sh ``` ## 数据向模型传送(Transfer Data to Model) -- GitLab From 3bfb898fd812430c205e81f6dcffa05c01d4abdd Mon Sep 17 00:00:00 2001 From: chenguoyan01 Date: Wed, 23 Nov 2016 16:49:03 +0800 Subject: [PATCH 0108/1503] add mfs fullname --- .../k8s/distributed_training_on_kubernetes.md | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md index e07cf6182f..d9ed431ec0 100644 --- a/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md +++ b/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md @@ -1,11 +1,11 @@ -# Paddle on Kubernetes:分布式训练 +# PaddlePaddle on Kubernetes:分布式训练 -前一篇文章介绍了如何在Kubernetes集群上启动一个单机PaddlePaddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式PaddlePaddle训练作业。关于PaddlePaddle的分布式训练,可以参考 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md),本文利用Kubernetes的调度功能与容器编排能力,快速构建PaddlePaddle容器集群,进行分布式训练任务。 +前一篇文章介绍了如何在Kubernetes集群上启动一个单机PaddlePaddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式PaddlePaddle训练作业。关于PaddlePaddle的分布式训练,文章 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)介绍了一种通过SSH远程分发任务,进行分布式训练的方法,与此不同的是,本文将介绍在Kubernetes容器管理平台上快速构建PaddlePaddle容器集群,进行分布式训练的方案。 ## Kubernetes 基本概念 -[*Kubernetes*](http://kubernetes.io/)是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用。在介绍分布式训练之前,需要对[Kubernetes](http://kubernetes.io/)有一个基本的认识,下面先简要介绍一下本文用到的几个Kubernetes概念。 +[*Kubernetes*](http://kubernetes.io/)是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用。Kubernetes可以在物理机或虚拟机上运行,且支持部署到[AWS](http://kubernetes.io/docs/getting-started-guides/aws),[Azure](http://kubernetes.io/docs/getting-started-guides/azure/),[GCE](http://kubernetes.io/docs/getting-started-guides/gce)等多种公有云环境。介绍分布式训练之前,需要对[Kubernetes](http://kubernetes.io/)有一个基本的认识,下面先简要介绍一下本文用到的几个Kubernetes概念。 - [*Node*](http://kubernetes.io/docs/admin/node/) 表示一个Kubernetes集群中的一个工作节点,这个节点可以是物理机或者虚拟机,Kubernetes集群就是由node节点与master节点组成的。 @@ -21,17 +21,19 @@ ### 部署Kubernetes集群 -首先,我们需要拥有一个Kubernetes集群,在这个集群中所有node与pod都可以互相通信。关于Kubernetes集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。本文假设大家能找到几台物理机,并且可以按照官方文档在上面部署Kubernetes。在本文的环境中,Kubernetes集群中所有node都挂载了一个*mfs*(分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。在训练之前,用户将配置与训练数据切分好放在mfs目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。整体的结果图如下: +首先,我们需要拥有一个Kubernetes集群,在这个集群中所有node与pod都可以互相通信。关于Kubernetes集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。本文假设大家能找到几台物理机,并且可以按照官方文档在上面部署Kubernetes。在本文的环境中,Kubernetes集群中所有node都挂载了一个[MFS](http://moosefs.org/)(Moose filesystem,一种分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。关于MFS的安装部署,可以参考[MooseFS documentation](https://moosefs.com/documentation.html)。在训练之前,用户将配置与训练数据切分好放在MFS目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。整体的结构图如下: ![paddle on kubernetes结构图](k8s-paddle-arch.png) +上图描述了一个3节点的分布式训练场景,Kubernetes集群的每个node上都挂载了一个MFS目录,这个目录可以通过volume的形式挂载到容器中。Kubernetes为这次训练创建了3个pod并且调度到了3个node上运行,每个pod包含一个PaddlePaddle容器。在容器创建后,会启动pserver与trainer进程,读取volume中的数据进行这次分布式训练。 + ### 使用 Job 我们使用Kubernetes中的job这个概念来代表一次分布式训练。Job表示一次性作业,在作业完成后,Kubernetes会销毁job产生的容器并且释放相关资源。 在Kubernetes中,可以通过编写一个YAML文件,来描述这个job,在这个文件中,主要包含了一些配置信息,例如PaddlePaddle的节点个数,`paddle pserver`开放的端口个数与端口号,使用的网卡设备等,这些信息通过环境变量的形式传递给容器内的程序使用。 -在一次分布式训练中,用户确定好本次训练需要的PaddlePaddle节点个数,将切分好的训练数据与配置文件上传到mfs共享目录中。然后编写这次训练的job YAML文件,提交给Kubernetes集群创建并开始作业。 +在一次分布式训练中,用户确定好本次训练需要的PaddlePaddle节点个数,将切分好的训练数据与配置文件上传到MFS共享目录中。然后编写这次训练的job YAML文件,提交给Kubernetes集群创建并开始作业。 ### 创建PaddlePaddle节点 @@ -39,14 +41,14 @@ ### 启动训练 -在容器启动后,会通过脚本来启动这次分布式训练,我们知道`paddle train`进程启动时需要知道其他节点的IP地址以及本节点的trainer_id,由于Paddle本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据job name向Kubernetes apiserver查询这个job对应的所有pod信息(Kubernetes默认会在每个容器的环境变量中写入apiserver的地址)。 +在容器启动后,会通过脚本来启动这次分布式训练,我们知道`paddle train`进程启动时需要知道其他节点的IP地址以及本节点的trainer_id,由于PaddlePaddle本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据job name向Kubernetes apiserver查询这个job对应的所有pod信息(Kubernetes默认会在每个容器的环境变量中写入apiserver的地址)。 根据这些pod信息,就可以通过某种方式,为每个pod分配一个唯一的trainer_id。本文把所有pod的IP地址进行排序,将顺序作为每个PaddlePaddle节点的trainer_id。启动脚本的工作流程大致如下: 1. 查询Kubernetes apiserver获取pod信息,根据IP分配trainer_id - 1. 从mfs共享目录中拷贝训练文件到容器内 + 1. 从MFS共享目录中拷贝训练文件到容器内 1. 根据环境变量,解析出`paddle pserver`与`paddle train`的启动参数,启动进程 - 1. 训练时,PaddlePaddle会自动将结果保存在trainer_id为0的节点上,将输出路径设置为mfs目录,保存输出的文件 + 1. 训练时,PaddlePaddle会自动将结果保存在trainer_id为0的节点上,将输出路径设置为MFS目录,保存输出的文件 ## 搭建过程 @@ -160,7 +162,7 @@ docker push your_repo/paddle:mypaddle ### 上传训练文件 -本文使用Paddle官方的[recommendation demo](http://www.paddlepaddle.org/doc/demo/index.html#recommendation)作为这次训练的内容,我们将训练文件与数据放在一个job name命名的目录中,上传到mfs共享存储。完成后mfs上的文件内容大致如下: +本文使用PaddlePaddle官方的[recommendation demo](http://www.paddlepaddle.org/doc/demo/index.html#recommendation)作为这次训练的内容,我们将训练文件与数据放在一个job name命名的目录中,上传到MFS共享存储。完成后MFS上的文件内容大致如下: ```bash [root@paddle-kubernetes-node0 mfs]# tree -d @@ -176,7 +178,7 @@ docker push your_repo/paddle:mypaddle └── recommendation ``` -目录中paddle-cluster-job是本次训练对应的job name,本次训练要求有3个Paddle节点,在paddle-cluster-job/data目录中存放切分好的数据,文件夹0,1,2分别代表3个节点的trainer_id。recommendation文件夹内存放训练文件,output文件夹存放训练结果与日志。 +目录中paddle-cluster-job是本次训练对应的job name,本次训练要求有3个PaddlePaddle节点,在paddle-cluster-job/data目录中存放切分好的数据,文件夹0,1,2分别代表3个节点的trainer_id。recommendation文件夹内存放训练文件,output文件夹存放训练结果与日志。 ### 创建Job @@ -229,7 +231,7 @@ spec: restartPolicy: Never ``` -文件中,`metadata`下的`name`表示这个job的名字。`parallelism,completions`字段表示这个job会同时开启3个Paddle节点,成功训练且退出的pod数目为3时,这个job才算成功结束。然后申明一个存储卷`jobpath`,代表宿主机目录`/home/work/mfs`,在对容器的描述`containers`字段中,将此目录挂载为容器的`/home/jobpath`目录,这样容器的`/home/jobpath`目录就成为了共享存储,放在这个目录里的文件其实是保存到了mfs上。 +文件中,`metadata`下的`name`表示这个job的名字。`parallelism,completions`字段表示这个job会同时开启3个PaddlePaddle节点,成功训练且退出的pod数目为3时,这个job才算成功结束。然后申明一个存储卷`jobpath`,代表宿主机目录`/home/work/mfs`,在对容器的描述`containers`字段中,将此目录挂载为容器的`/home/jobpath`目录,这样容器的`/home/jobpath`目录就成为了共享存储,放在这个目录里的文件其实是保存到了MFS上。 `env`字段表示容器的环境变量,我们将`paddle`运行的一些参数通过这种方式传递到容器内。 @@ -254,7 +256,7 @@ kubectl create -f job.yaml ### 查看输出 -在训练过程中,可以在共享存储上查看输出的日志和模型,例如output目录下就存放了输出结果。注意node_0,node_1,node_2这几个目录表示Paddle节点与trainer_id,并不是Kubernetes中的node概念。 +在训练过程中,可以在共享存储上查看输出的日志和模型,例如output目录下就存放了输出结果。注意node_0,node_1,node_2这几个目录表示PaddlePaddle节点与trainer_id,并不是Kubernetes中的node概念。 ```bash [root@paddle-kubernetes-node0 output]# tree -d -- GitLab From c62d5b14a5c8343e455a21fe7c3cf01d14acd1eb Mon Sep 17 00:00:00 2001 From: zhouti Date: Wed, 23 Nov 2016 17:03:09 +0800 Subject: [PATCH 0109/1503] Added PaddlePaddle on AWS with Kubernetes documentation. --- .../paddlepaddle_on_aws_with_kubernetes.md | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md diff --git a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md new file mode 100644 index 0000000000..e2495055de --- /dev/null +++ b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md @@ -0,0 +1,306 @@ +#PaddlePaddle on AWS with Kubernetes + +##Prerequisites + +You need an Amazon account and your user account needs the following privileges to continue: + +* AmazonEC2FullAccess +* AmazonS3FullAccess +* AmazonRoute53FullAccess +* AmazonRoute53DomainsFullAccess +* AmazonVPCFullAccess +* IAMUserSSHKeys +* IAMFullAccess +* NetworkAdministrator + +If you are not in Unites States, we also recommend creating a jump server instance with default amazon AMI in the same available zone as your cluster, otherwise there will be some issue on creating the cluster. + + +##For people new to Kubernetes and AWS + +If you are new to Kubernetes or AWS and just want to run PaddlePaddle, you can follow these steps to start up a new cluster. + +###AWS Login + +First configure your aws account information: + +``` +aws configure + +``` +Fill in the required fields: + +``` +AWS Access Key ID: YOUR_ACCESS_KEY_ID +AWS Secrete Access Key: YOUR_SECRETE_ACCESS_KEY +Default region name: us-west-2 +Default output format: json + +``` + +###Cluster Start Up +And then type the following command: + +``` +export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash + +``` + + +This process takes about 5 to 10 minutes. + +Once the cluster is up, the IP addresses of your master and node(s) will be printed, as well as information about the default services running in the cluster (monitoring, logging, dns). + +User credentials and security tokens are written in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. + + +``` +[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash +'kubernetes' directory already exist. Should we skip download step and start to create cluster based on it? [Y]/n +Skipping download step. +Creating a kubernetes on aws... +... Starting cluster in us-west-2a using provider aws +... calling verify-prereqs +... calling kube-up +Starting cluster using os distro: jessie +Uploading to Amazon S3 ++++ Staging server tars to S3 Storage: kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel +upload: ../../../tmp/kubernetes.7nMCAR/s3/bootstrap-script to s3://kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script +Uploaded server tars: + SERVER_BINARY_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-server-linux-amd64.tar.gz + SALT_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-salt.tar.gz + BOOTSTRAP_SCRIPT_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script +INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-master 2016-11-22T05:20:41Z AIPAJWBAGNSEHM4CILHDY kubernetes-master / +ROLES arn:aws:iam::525016323257:role/kubernetes-master 2016-11-22T05:20:39Z / AROAJW3VKVVQ5MZSTTJ5O kubernetes-master +ASSUMEROLEPOLICYDOCUMENT 2012-10-17 +STATEMENT sts:AssumeRole Allow +PRINCIPAL ec2.amazonaws.com +INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-minion 2016-11-22T05:20:45Z AIPAIYVABOPWQZZX5EN5W kubernetes-minion / +ROLES arn:aws:iam::525016323257:role/kubernetes-minion 2016-11-22T05:20:43Z / AROAJKDVM7XQNZ4JGVKNO kubernetes-minion +ASSUMEROLEPOLICYDOCUMENT 2012-10-17 +STATEMENT sts:AssumeRole Allow +PRINCIPAL ec2.amazonaws.com +Using SSH key with (AWS) fingerprint: 08:9f:6b:82:3d:b5:ba:a0:f3:db:ab:94:1b:a7:a4:c7 +Creating vpc. +Adding tag to vpc-fad1139d: Name=kubernetes-vpc +Adding tag to vpc-fad1139d: KubernetesCluster=kubernetes +Using VPC vpc-fad1139d +Adding tag to dopt-e43a7180: Name=kubernetes-dhcp-option-set +Adding tag to dopt-e43a7180: KubernetesCluster=kubernetes +Using DHCP option set dopt-e43a7180 +Creating subnet. +Adding tag to subnet-fc16fa9b: KubernetesCluster=kubernetes +Using subnet subnet-fc16fa9b +Creating Internet Gateway. +Using Internet Gateway igw-fc0d9398 +Associating route table. +Creating route table +Adding tag to rtb-bd8512da: KubernetesCluster=kubernetes +Associating route table rtb-bd8512da to subnet subnet-fc16fa9b +Adding route to route table rtb-bd8512da +Using Route Table rtb-bd8512da +Creating master security group. +Creating security group kubernetes-master-kubernetes. +Adding tag to sg-d9280ba0: KubernetesCluster=kubernetes +Creating minion security group. +Creating security group kubernetes-minion-kubernetes. +Adding tag to sg-dc280ba5: KubernetesCluster=kubernetes +Using master security group: kubernetes-master-kubernetes sg-d9280ba0 +Using minion security group: kubernetes-minion-kubernetes sg-dc280ba5 +Creating master disk: size 20GB, type gp2 +Adding tag to vol-04d71a810478dec0d: Name=kubernetes-master-pd +Adding tag to vol-04d71a810478dec0d: KubernetesCluster=kubernetes +Allocated Elastic IP for master: 35.162.175.115 +Adding tag to vol-04d71a810478dec0d: kubernetes.io/master-ip=35.162.175.115 +Generating certs for alternate-names: IP:35.162.175.115,IP:172.20.0.9,IP:10.0.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:kubernetes-master +Starting Master +Adding tag to i-042488375c2ca1e3e: Name=kubernetes-master +Adding tag to i-042488375c2ca1e3e: Role=kubernetes-master +Adding tag to i-042488375c2ca1e3e: KubernetesCluster=kubernetes +Waiting for master to be ready +Attempt 1 to check for master nodeWaiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... + [master running] +Attaching IP 35.162.175.115 to instance i-042488375c2ca1e3e +Attaching persistent data volume (vol-04d71a810478dec0d) to master +2016-11-23T02:14:59.645Z /dev/sdb i-042488375c2ca1e3e attaching vol-04d71a810478dec0d +cluster "aws_kubernetes" set. +user "aws_kubernetes" set. +context "aws_kubernetes" set. +switched to context "aws_kubernetes". +user "aws_kubernetes-basic-auth" set. +Wrote config for aws_kubernetes to /home/ec2-user/.kube/config +Creating minion configuration +Creating autoscaling group + 0 minions started; waiting + 0 minions started; waiting + 0 minions started; waiting + 0 minions started; waiting + 2 minions started; ready +Waiting for cluster initialization. + + This will continually check to see if the API for kubernetes is reachable. + This might loop forever if there was some uncaught error during start + up. + +.......................................................................................................................................................................................................................Kubernetes cluster created. +Sanity checking cluster... +Attempt 1 to check Docker on node @ 35.164.79.249 ...working +Attempt 1 to check Docker on node @ 35.164.83.190 ...working + +Kubernetes cluster is running. The master is running at: + + https://35.162.175.115 + +The user name and password to use is located in /home/ec2-user/.kube/config. + +... calling validate-cluster +Waiting for 2 ready nodes. 0 ready nodes, 2 registered. Retrying. +Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. +Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. +Found 2 node(s). +NAME STATUS AGE +ip-172-20-0-23.us-west-2.compute.internal Ready 54s +ip-172-20-0-24.us-west-2.compute.internal Ready 52s +Validate output: +NAME STATUS MESSAGE ERROR +scheduler Healthy ok +controller-manager Healthy ok +etcd-1 Healthy {"health": "true"} +etcd-0 Healthy {"health": "true"} +Cluster validation succeeded +Done, listing cluster services: + +Kubernetes master is running at https://35.162.175.115 +Elasticsearch is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging +Heapster is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/heapster +Kibana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kibana-logging +KubeDNS is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kube-dns +kubernetes-dashboard is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard +Grafana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana +InfluxDB is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb + +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. + +Kubernetes binaries at /home/ec2-user/kubernetes/cluster/ +You may want to add this directory to your PATH in $HOME/.profile +Installation successful! +``` + + +By default, the script will provision a new VPC and a 4 node k8s cluster in us-west-2a (Oregon) with EC2 instances running on Debian. You can override the variables defined in `/cluster/config-default.sh` to change this behavior as follows: + +``` +export KUBE_AWS_ZONE=us-west-2a +export NUM_NODES=2 +export MASTER_SIZE=m3.medium +export NODE_SIZE=m3.medium +export AWS_S3_REGION=us-west-2a +export AWS_S3_BUCKET=mycompany-kubernetes-artifacts +export KUBE_AWS_INSTANCE_PREFIX=k8s +... + +``` +And then concate the kubernetes binaries directory into PATH: + +``` +export PATH=/platforms/linux/amd64:$PATH + +``` +Now you can use administration tool kubectl to operate the cluster. +By default, kubectl will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. + +For running PaddlePaddle training with Kubernetes on AWS, you can refer to [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md). + + +###Cluster Tear Down +If you want to tear down the running cluster: + +``` +export KUBERNETES_PROVIDER=aws; /cluster/kube-down.sh +``` + +This process takes about 2 to 5 minutes. + +``` +[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; ./kubernetes/cluster/kube-down.sh +Bringing down cluster using provider: aws +Deleting instances in VPC: vpc-fad1139d +Deleting auto-scaling group: kubernetes-minion-group-us-west-2a +Deleting auto-scaling launch configuration: kubernetes-minion-group-us-west-2a +Deleting auto-scaling group: kubernetes-minion-group-us-west-2a +Waiting for instances to be deleted +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +All instances deleted +Releasing Elastic IP: 35.162.175.115 +Deleting volume vol-04d71a810478dec0d +Cleaning up resources in VPC: vpc-fad1139d +Cleaning up security group: sg-d9280ba0 +Cleaning up security group: sg-dc280ba5 +Deleting security group: sg-d9280ba0 +Deleting security group: sg-dc280ba5 +Deleting VPC: vpc-fad1139d +Done +``` + + +## For experts with Kubernetes and AWS + +Sometimes we might need to create or manage the cluster on AWS manually with limited privileges, so here we will explain more on what’s going on with the Kubernetes setup script. + +### Some Presumptions + +* Instances run on Debian, the official IAM, and the filesystem is aufs instead of ext4. +* Kubernetes node use instance storage, no EBS get mounted. Master use a persistent volume for etcd. +* Nodes are running in an Auto Scaling Group on AWS, auto-scaling itself is disabled, but if some node get terminated, it will launch another node instead. +* For networking, we use ip-per-pod model here, each pod get assigned a /24 CIDR. And the whole vpc is a /16 CIDR, No overlay network at this moment, we will add Calico solution later on. +* When you create a service with Type=LoadBalancer, Kubernetes will create and ELB, and create a security group for the ELB. +* Kube-proxy sets up two IAM roles, one for master called kubernetes-master, one for nodes called kubernetes-node. +* All AWS resources are tagged with a tag named "KubernetesCluster", with a value that is the unique cluster-id. + + +###Script Details + +* Create an s3 bucket for binaries and scripts. +* Create two iam roles: kubernetes-master, kubernetes-node. +* Create an AWS SSH key named kubernetes-YOUR_RSA_FINGERPRINT. +* Create a vpc with 172.20.0.0/16 CIDR, and enables dns-support and dns-hostnames options in vpc settings. +* Create Internet gateway, route table, a subnet with CIDR of 172.20.0.0/24, and associate the subnet to the route table. +* Create and configure security group for master and nodes. +* Create an EBS for master, it will be attached after the master node get up. +* Launch the master with fixed ip address 172.20.0.9, and the node is initialized with Salt script, all the components get started as docker containers. +* Create an auto-scaling group, it has the min and max size, it can be changed by using aws api or console, it will auto launch the kubernetes node and configure itself, connect to master, assign an internal CIDR, and the master configures the route table with the assigned CIDR. + + + -- GitLab From ce2a2733ca6ae780a87750f1e8666291b547a6b7 Mon Sep 17 00:00:00 2001 From: gangliao Date: Wed, 23 Nov 2016 17:05:16 +0800 Subject: [PATCH 0110/1503] Fix a wrong link in build doc --- doc/build/build_from_source.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build/build_from_source.md b/doc/build/build_from_source.md index b8f26f431e..e5bb7944ca 100644 --- a/doc/build/build_from_source.md +++ b/doc/build/build_from_source.md @@ -6,7 +6,7 @@ Installing from Sources * [3. Build on Ubuntu](#ubuntu) ## Download and Setup -You can download PaddlePaddle from the [github source](https://github.com/gangliao/Paddle). +You can download PaddlePaddle from the [github source](https://github.com/PaddlePaddle/Paddle). ```bash git clone https://github.com/baidu/Paddle paddle -- GitLab From d8161e384058cd9413993143e105410de986f936 Mon Sep 17 00:00:00 2001 From: gangliao Date: Wed, 23 Nov 2016 17:08:20 +0800 Subject: [PATCH 0111/1503] Update build docs --- doc/build/build_from_source.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build/build_from_source.md b/doc/build/build_from_source.md index e5bb7944ca..e44fa0d38e 100644 --- a/doc/build/build_from_source.md +++ b/doc/build/build_from_source.md @@ -9,7 +9,7 @@ Installing from Sources You can download PaddlePaddle from the [github source](https://github.com/PaddlePaddle/Paddle). ```bash -git clone https://github.com/baidu/Paddle paddle +git clone https://github.com/PaddlePaddle/Paddle paddle cd paddle ``` -- GitLab From 0cac5391a62cbb20a97067b1563df6213a38e1de Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 23 Nov 2016 17:10:27 +0800 Subject: [PATCH 0112/1503] fix swig_py_paddle.rst --- doc_cn/ui/predict/swig_py_paddle.rst | 87 ++++++++++++++++------------ 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/doc_cn/ui/predict/swig_py_paddle.rst b/doc_cn/ui/predict/swig_py_paddle.rst index 012ac4ff6e..f9750d80c9 100644 --- a/doc_cn/ui/predict/swig_py_paddle.rst +++ b/doc_cn/ui/predict/swig_py_paddle.rst @@ -1,38 +1,53 @@ -PaddlePaddle的Python预测接口 -================================== - -PaddlePaddle目前使用Swig对其常用的预测接口进行了封装,使在Python环境下的预测接口更加简单。 -在Python环境下预测结果,主要分为以下几个步骤。 - -* 读入解析训练配置 -* 构造GradientMachine -* 准备数据 -* 预测 - -典型的预测代码如下,使用mnist手写识别作为样例, 完整代码见 -:code:`src_root/doc/ui/predict/predict_sample.py` 。 - -.. literalinclude:: ../../../doc/ui/predict/predict_sample.py - :language: python - :lines: 15-18,90-100,101-104 - -主要的软件包为py_paddle.swig_paddle,这个软件包文档相对完善。可以使用python的 -:code:`help()` 函数查询文档。主要步骤为: - -* 在程序开始阶段,使用 :code:`swig_paddle.initPaddle()` 传入命令行参数初始化 - PaddlePaddle。详细的命令行参数请参考 - `命令行参数 <../cmd_argument/detail_introduction.html>`_ 。 -* 接下来使用 :code:`parse_config()` 解析训练时的配置文件。这里要注意预测数据通常 - 不包含label, 而且预测网络通常直接输出最后一层的结果而不是像训练时一样以cost - layer作为输出,所以用于预测的配置文件要做相应的修改。 -* 使用 :code:`swig_paddle.GradientMachine.createFromConfigproto()` 根据上一步解 - 析好的配置创建神经网络。 -* 创建一个 :code:`DataProviderConverter` 对象converter。 - - swig_paddle接受的原始数据是C++的Matrix,也就是直接写内存的float数组。 - 这个接口并不用户友好。所以,我们提供了一个工具类DataProviderConverter。 - 这个工具类接收和PyDataProvider2一样的输入数据,详情请参考 - `PyDataProvider2文档 <../../../doc/ui/data_provider/pydataprovider2.html>`_ 。 -* 最后使用 :code:`forwardTest()` 直接提取出神经网络Output层的输出结果。典型的输出结果为\: +基于Python的预测 +================ + +Python预测接口 +-------------- + +PaddlePaddle使用swig对常用的预测接口进行了封装,通过编译会生成py_paddle软件包,安装该软件包就可以在python环境下实现模型预测。可以使用python的 ``help()`` 函数查询软件包相关API说明。 + +基于Python的模型预测,主要包括以下五个步骤。 + +1. 初始化PaddlePaddle环境 + 在程序开始阶段,通过调用 ``swig_paddle.initPaddle()`` 并传入相应的命令行参数初始化PaddlePaddle。 +2. 解析模型配置文件 + 初始化之后,可以通过调用 ``parse_config()`` 解析训练模型时用的配置文件。注意预测数据通常不包含label, 同时预测网络通常直接输出最后一层的结果而不是像训练网络一样再接一层cost layer,所以一般需要对训练用的模型配置文件稍作相应修改才能在预测时使用。 +3. 构造paddle.GradientMachine + 通过调用 ``swig_paddle.GradientMachine.createFromConfigproto()`` 传入上一步解析出来的模型配置就可以创建一个 ``GradientMachine``。 +4. 准备预测数据 + swig_paddle中的预测接口的参数是自定义的C++数据类型,py_paddle里面提供了一个工具类 ``DataProviderConverter`` 可以用于接收和PyDataProvider2一样的输入数据并转换成预测接口所需的数据类型。 +5. 模型预测 + 通过调用 ``forwardTest()`` 传入预测数据,直接返回计算结果。 + + +基于Python的预测Demo +-------------------- + +如下是一段使用mnist model来实现手写识别的预测代码。完整的代码见 ``src_root/doc/ui/predict/predict_sample.py`` 。mnist model可以通过 ``src_root\demo\mnist`` 目录下的demo训练出来。 + +.. code-block:: python + + from py_paddle import swig_paddle, DataProviderConverter + from paddle.trainer.PyDataProvider2 import dense_vector + from paddle.trainer.config_parser import parse_config + + TEST_DATA = [...] + + def main(): + conf = parse_config("./mnist_model/trainer_config.py", "") + network = swig_paddle.GradientMachine.createFromConfigProto(conf.model_config) + assert isinstance(network, swig_paddle.GradientMachine) # For code hint. + network.loadParameters("./mnist_model/") + converter = DataProviderConverter([dense_vector(784)]) + inArg = converter(TEST_DATA) + print network.forwardTest(inArg) + + if __name__ == '__main__': + swig_paddle.initPaddle("--use_gpu=0") + main() + + +Demo预测输出如下,其中value即为softmax层的输出。由于TEST_DATA包含两条预测数据,所以输出的value包含两个向量 。 .. code-block:: text @@ -45,4 +60,4 @@ PaddlePaddle目前使用Swig对其常用的预测接口进行了封装,使在P 2.70634608e-08, 3.48565123e-08, 5.25639710e-09, 4.48684503e-08]], dtype=float32)}] -其中,value即为softmax层的输出。由于数据是两条,所以输出的value包含两个向量 。 + -- GitLab From 0bcacea5815b710f3d798307d8906cc49347465b Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Wed, 23 Nov 2016 17:35:35 +0800 Subject: [PATCH 0113/1503] refine ubuntu installation and FAQ doc --- .../install/ubuntu_install.rst | 81 ++++++++----------- doc_cn/faq/index.rst | 58 ++++++------- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/doc_cn/build_and_install/install/ubuntu_install.rst b/doc_cn/build_and_install/install/ubuntu_install.rst index 0fb59e25f6..08d55f98d9 100644 --- a/doc_cn/build_and_install/install/ubuntu_install.rst +++ b/doc_cn/build_and_install/install/ubuntu_install.rst @@ -1,83 +1,66 @@ -使用deb包在Ubuntu上安装PaddlePaddle +Ubuntu部署PaddlePaddle =================================== -PaddlePaddle目前支持使用deb包安装。Paddle的 :code:`deb` 安装包在ubuntu 14.04中正确,但理论上支持其他的 debian 发行版。 +PaddlePaddle提供了deb安装包,并在ubuntu 14.04做了完备测试,理论上也支持其他的debian发行版。 +安装 +------ -PaddlePaddle的ubuntu安装包分为四个版本,他们是 cpu、gpu、cpu-noavx、gpu-noavx 四个版本。其中 noavx 用于不支持AVX指令集的cpu。安装包的下载地址是\: https://github.com/baidu/Paddle/releases/ +安装包的下载地址是\: https://github.com/PaddlePaddle/Paddle/releases +它包含四个版本\: -用户需要先将PaddlePaddle安装包下载到本地,然后执行如下 :code:`gdebi` 命令即可完成安装。 +* cpu版本: 支持主流intel x86处理器平台, 支持avx指令集。 -.. code-block:: shell +* cpu-noavx版本:支持主流intel x86处理器平台,不支持avx指令集。 + +* gpu版本:支持主流intel x86处理器平台,支持nvidia cuda平台,支持avx指令集。 + +* gpu-noavx版本:支持主流intel x86处理器平台,支持nvidia cuda平台,不支持avx指令级。 - gdebi paddle-*-cpu*.deb +下载完相关安装包后,执行: -如果 :code:`gdebi` 没有安装,则需要使用 :code:`sudo apt-get install gdebi`, 来安装 :code:`gdebi` 。 +.. code-block:: shell + sudo apt-get install gdebi + gdebi paddle-*-cpu.deb -或者使用下面一条命令安装. +或者: .. code-block:: shell - dpkg -i paddle-*-cpu*.deb + dpkg -i paddle-*-cpu.deb apt-get install -f + 在 :code:`dpkg -i` 的时候如果报一些依赖未找到的错误是正常的, 在 :code:`apt-get install -f` 里会继续安装 PaddlePaddle。 -需要注意的是,如果使用GPU版本的PaddlePaddle,请安装CUDA 7.5 和CUDNN 5到本地环境中, -并设置好对应的环境变量(LD_LIBRARY_PATH等等)。 - -安装完成后,可以使用命令 :code:`paddle version` 查看安装后的paddle 版本。可能的输出为 +安装完成后,可以使用命令 :code:`paddle version` 查看安装后的paddle 版本: .. literalinclude:: paddle_version.txt 可能遇到的问题 -------------- -libcudart.so/libcudnn.so找不到 -++++++++++++++++++++++++++++++ - -安装完成PaddlePaddle后,运行 :code:`paddle train` 报错\: - -.. code-block:: shell - - 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. - -PaddlePaddle使用运行时动态连接CUDA的so,如果在 LD_LIBRARY_PATH里面找不到这些动态 -库的话,会报寻找不到这些动态库。 - -解决方法很简单,就是将这些动态库加到环境变量里面。比较可能的命令如下。 - -.. code-block:: text - - export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH - -CUDA Driver找不到 -+++++++++++++++++ +如何设置gpu版本运行时cuda环境运行GPU版本 +++++++++++++++++++++++++++++++++++++++++ -运行 :code:`paddle train` 报错\: +如果使用GPU版本的PaddlePaddle,请安装CUDA 7.5 和CUDNN 5到本地环境中,并设置: -.. code-block:: text +.. code-block:: shell + export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/lib:$LD_LIBRARY_PATH + export PATH=/usr/local/cuda/bin:$PATH - F0831 12:39:16.699000 1090 hl_cuda_device.cc:530] Check failed: cudaSuccess == cudaStat (0 vs. 35) Cuda Error: CUDA driver version is insufficient for CUDA runtime version -PaddlePaddle运行时如果没有寻找到cuda的driver,变会报这个错误。解决办法是将cuda -driver添加到LD_LIBRARY_PATH中。比较可能的命令如下。 - -.. code-block:: text - - export LD_LIBRARY_PATH=/usr/lib64:$LD_LIBRARY_PATH +libcudart.so/libcudnn.so找不到 +++++++++++++++++++++++++++++++ -config文件找不到 -++++++++++++++++ +安装完成后,运行 :code:`paddle train` 报错\: -运行 :code:`paddle train` 得到结果\: +.. code-block:: shell -.. code-block:: text + 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. - F0831 20:53:07.525789 1302 TrainerMain.cpp:94] Check failed: config != nullptr no valid config +原因是未设置cuda运行时环境变量,请参考** 设置gpu版本运行时cuda环境** 解决方案。 -PaddlePaddle在运行时找不到对应的config文件,说明命令行参数 :code:`config` 没有设置。 -而这个一般说明PaddlePaddle已经安装完毕了。 \ No newline at end of file diff --git a/doc_cn/faq/index.rst b/doc_cn/faq/index.rst index 3eb0e10ae2..6e1102e552 100644 --- a/doc_cn/faq/index.rst +++ b/doc_cn/faq/index.rst @@ -4,22 +4,18 @@ PaddlePaddle常见问题 .. contents:: -1. 如何减少PaddlePaddle的内存占用 +1. 如何减少内存占用 --------------------------------- -神经网络的训练本身是一个非常消耗内存和显存的工作。经常会消耗数十G的内存和数G的显存。 +神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数十G的内存和数G的显存。 PaddlePaddle的内存占用主要分为如下几个方面\: -* DataProvider缓冲池内存 (只针对内存) -* 神经元激活内存 (针对内存和显存) -* 参数内存 (针对内存和显存) +* DataProvider缓冲池内存(只针对内存) +* 神经元激活内存(针对内存和显存) +* 参数内存 (针对内存和显存) * 其他内存杂项 -这其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等, -这些内存就不考虑如何缩减了。 - -其他的内存的减少方法依次为 - +其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等,暂不考虑在内。 减少DataProvider缓冲池内存 ++++++++++++++++++++++++++ @@ -39,28 +35,28 @@ PyDataProvider使用的是异步加载,同时在内存里直接随即选取数 .. literalinclude:: reduce_min_pool_size.py -这样做可以极大的减少内存占用,并且可能会加速训练过程。 详细文档参考 `这里 +这样做可以极大的减少内存占用,并且可能会加速训练过程,详细文档参考 `这里 <../ui/data_provider/pydataprovider2.html#provider>`_ 。 神经元激活内存 ++++++++++++++ -神经网络在训练的时候,会对每一个激活暂存一些数据,包括激活,參差等等。 +神经网络在训练的时候,会对每一个激活暂存一些数据,如神经元激活值等。 在反向传递的时候,这些数据会被用来更新参数。这些数据使用的内存主要和两个参数有关系, 一是batch size,另一个是每条序列(Sequence)长度。所以,其实也是和每个mini-batch中包含 的时间步信息成正比。 -所以,做法可以有两种。他们是 +所以做法可以有两种: * 减小batch size。 即在网络配置中 :code:`settings(batch_size=1000)` 设置成一个小一些的值。但是batch size本身是神经网络的超参数,减小batch size可能会对训练结果产生影响。 * 减小序列的长度,或者直接扔掉非常长的序列。比如,一个数据集大部分序列长度是100-200, - 但是突然有一个10000长的序列,就很容易导致内存超限。特别是在LSTM等RNN中。 + 但是突然有一个10000长的序列,就很容易导致内存超限,特别是在LSTM等RNN中。 参数内存 ++++++++ PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需要使用不同大小的内存。 -例如如果使用 :code:`adadelta` 算法,则需要使用参数规模大约5倍的内存。 如果参数保存下来的 +例如使用 :code:`adadelta` 算法,则需要使用等于权重参数规模大约5倍的内存。举例,如果参数保存下来的模型目录 文件为 :code:`100M`, 那么该优化算法至少需要 :code:`500M` 的内存。 可以考虑使用一些优化算法,例如 :code:`momentum`。 @@ -68,11 +64,11 @@ PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需 2. 如何加速PaddlePaddle的训练速度 --------------------------------- -PaddlePaddle是神经网络训练平台,加速PaddlePaddle训练有如下几个方面\: +加速PaddlePaddle训练可以考虑从以下几个方面\: * 减少数据载入的耗时 * 加速训练速度 -* 利用更多的计算资源 +* 利用分布式训练驾驭更多的计算资源 减少数据载入的耗时 ++++++++++++++++++ @@ -108,25 +104,20 @@ PaddlePaddle支持Sparse的训练,sparse训练需要训练特征是 :code:`spa 利用更多的计算资源可以分为一下几个方式来进行\: * 单机CPU训练 - * 使用多线程训练。设置命令行参数 :code:`trainer_count`,即可以设置参与训练的线程数量。使用方法为 :code:`paddle train --trainer_count=4` + * 使用多线程训练。设置命令行参数 :code:`trainer_count`。 + * 单机GPU训练 - * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 使用方法为 :code:`paddle train --use_gpu=true` - * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count`。使用 :code:`--use_gpu=True` 开启GPU训练,使用 :code:`trainer_count` 指定显卡数量。使用方法为 :code:`paddle train --use_gpu=true --trainer_count=4` + * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 + * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count` 。 + * 多机训练 - * 使用多机训练的方法也比较简单,需要先在每个节点启动 :code:`paddle pserver`,在使用 :code:`paddle train --pservers=192.168.100.1,192.168.100.2` 来指定每个pserver的ip地址 - * 具体的多机训练方法参考 `多机训练 `_ 文档。 + * 具体的多机训练方法参考 `多机训练文档 <../ui/data_provider/pydataprovider2.html#provider>`_ 。 3. 遇到“非法指令”或者是“illegal instruction” -------------------------------------------- -paddle在进行计算的时候为了提升计算性能,使用了avx指令。部分老的cpu型号无法支持这样的指令。通常来说执行下grep avx /proc/cpuinfo看看是否有输出即可知道是否支持。(另:用此方法部分虚拟机可能检测到支持avx指令但是实际运行会挂掉,请当成是不支持,看下面的解决方案) - -解决办法是\: - -* 使用 NO_AVX的 `安装包 <../build_and_install/index.html>`_ 或者 `Docker image <../build_and_install/install/docker_install.html>`_ -* 或者,使用 :code:`-DWITH_AVX=OFF` 重新编译PaddlePaddle。 - +PaddlePaddle使用avx SIMD指令提高cpu执行效率,因此错误的使用二进制发行版可能会导致这种错误,请选择正确的版本。 4. 如何选择SGD算法的学习率 -------------------------- @@ -158,7 +149,7 @@ paddle在进行计算的时候为了提升计算性能,使用了avx指令。 6. 如何共享参数 --------------- -PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是想要共享的参数使用同样的 :code:`ParamAttr` 对象。 +PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是使得要共享的参数使用同样的 :code:`ParamAttr` 对象。 简单的全连接网络,参数共享的配置示例为\: @@ -208,9 +199,6 @@ PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字 paddle package is already in your PYTHONPATH. But unittest need a clean environment. Please uninstall paddle package before start unittest. Try to 'pip uninstall paddle'. -解决办法是:卸载paddle包 :code:`pip uninstall paddle`。 - -原因是:单元测试使用了一个旧版本的python包,而没有测试到代码中实际修改的python包。即单元测试需要一个干净的环境: +解决办法是: -* 如果paddle包已经在python的site-packages里面了,那么单元测试时使用的paddle包,就是site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。 -* 即便设置了 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 \ No newline at end of file +* 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 -- GitLab From 0c981164902ea322f5573d645d3bd090b0ce5421 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 23 Nov 2016 17:38:20 +0800 Subject: [PATCH 0114/1503] Refining documentation --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 43 +++++-------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index 896dd7ada9..e1a847fc9c 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -115,13 +115,11 @@ :language: python :lines: 39-66 -- 双层序列,外层memory是单层序列: +.. warning:: + PaddlePaddle目前只支持在每一个时间步中,Memory的sequence长度一致的情况。 - - 由于外层每个时间步返回的是一个子句,这些子句的长度往往不等长。因此当外层有is_seq=True的memory时,内层是**无法直接使用**它的,即内层memory的boot_layer不能链接外层的这个memory。 - - 如果内层memory想**间接使用**这个外层memory,只能通过`pooling_layer`、`last_seq`或`first_seq`这三个layer将它先变成一个元素。但这种情况下,外层memory必须有boot_layer,否则在第0个时间步时,由于外层memory没有任何seq信息,因此上述三个layer的前向会报出“**Check failed: input.sequenceStartPositions**”的错误。 - -示例3:双进双出,输入不等长 -=========================== +示例3:双层RNN,输入不等长 +========================== .. role:: red @@ -129,41 +127,22 @@ -**输入不等长** 是指recurrent_group的多个输入在各时刻的长度可以不相等, 但需要指定一个和输出长度一致的input,用 :red:`targetInlink` 表示。参考配置:单层RNN(:code:`sequence_rnn_multi_unequalength_inputs.conf`),双层RNN(:code:`sequence_nest_rnn_multi_unequalength_inputs.conf`) - -读取双层序列的方法 ------------------- - -我们看一下单双层序列的数据组织形式和dataprovider(见 :code:`rnn_data_provider.py` ) - -.. literalinclude:: ../../../paddle/gserver/tests/rnn_data_provider.py - :language: python - :lines: 69-97 - -data2 中有两个样本,每个样本有两个特征, 记fea1, fea2。 +**输入不等长** 是指recurrent_group的多个输入序列,在每个\ :ref:`glossary_timestep`\ 的子序列长度可以不相等。但\ :ref:`glossary_双层RNN`\ 目前需要整体的输出,与某一个输入的序列信息是一致的。使用\ :red:`targetInlink`\ 可以指定和输出序列信息一致。 -- 单层序列:两个样本分别为[[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]] 和 [[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]] -- 双层序列:两个样本分别为 +本例参考配置分别为\ `单层不等长RNN `_\ 和\ `双层不等长RNN `_\ 。 - - **样本1**\:[[[1, 2], [4, 5, 2]], [[5, 4, 1], [3, 1]]]。fea1和fea2都分别有2个子句,fea1=[[1, 2], [4, 5, 2]], fea2=[[5, 4, 1], [3, 1]] - - **样本2**\:[[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]]。fea1和fea2都分别有3个子句, fea1=[[0, 2], [2, 5], [0, 1, 2]], fea2=[[1, 5], [4], [2, 3, 6, 1]]。
    - - **注意**\:每个样本中,各特征的子句数目需要相等。这里说的“双进双出,输入不等长”是指fea1在i时刻的输入的长度可以不等于fea2在i时刻的输入的长度。如对于第1个样本,时刻i=2, fea1[2]=[4, 5, 2],fea2[2]=[3, 1],3≠2。 +本例中对于单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 数据完全相同,对于单层\ :ref:`glossary_RNN`\ 的数据一共有两个样本,他们分别是\ :code:`[1, 2, 4, 5, 2], [5, 4, 1, 3, 1]`\ 和\ :code:`[0, 2, 2, 5, 0, 1, 2], [1, 5, 4, 2, 3, 6, 1]`\ 。对于每一个单层\ :ref:`glossary_RNN`\ 的数据,均有两组特征。在单层数据的基础上,\ :ref:`glossary_双层RNN`\ 数据随意加了一些隔断,例如将第一条数据转化为\ :code:`[[0, 2], [2, 5], [0, 1, 2]],[[1, 5], [4], [2, 3, 6, 1]]`\ 。但是需要注意的是Paddle目前只支持序列数目一样的多输入\ :ref:`glossary_双层RNN`\ 。即两个特征,均有三个子序列。每个子序列长度可以不一致,但是子序列的数目必须一样。 -- 单双层序列中,两个样本的label都分别是0和1 -模型中的配置 ------------- - -单层RNN( :code:`sequence_rnn_multi_unequalength_inputs.conf`)和双层RNN( :code:`v.conf`)两个模型配置达到的效果完全一样,区别只在于输入为单层还是双层序列,现在我们来看它们内部分别是如何实现的。 - -- 单层序列\: +:ref:`glossary_trainer_config`\ 的模型配置 +------------------------------------------ - - 过了一个简单的recurrent_group。每一个时间步,当前的输入y和上一个时间步的输出rnn_state做了一个全连接,功能与示例2中`sequence_rnn.conf`的`step`函数完全相同。这里,两个输入x1,x2分别通过calrnn返回最后时刻的状态。结果得到的encoder1_rep和encoder2_rep分别是单层序列,最后取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 - - 注意到这里recurrent_group输入的每个样本中,fea1和fea2的长度都分别相等,这并非偶然,而是因为recurrent_group要求输入为单层序列时,所有输入的长度都必须相等。 +本例中的配置,使用了单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 使用一个\ :code:`recurrent_group`\ 将两个序列同时过完全连接的\ :ref:`glossary_RNN`\ 。对于单层\ :ref:`glossary_RNN`\ 的code如下。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf :language: python :lines: 41-58 + :linenos: - 双层序列\: -- GitLab From da7c0f1326677ec7c22b6cdbd6595e2f4a1d59a2 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 23 Nov 2016 17:41:20 +0800 Subject: [PATCH 0115/1503] Format sequence_nest_rnn_multi_unequalength*.conf --- ...ce_nest_rnn_multi_unequalength_inputs.conf | 106 ----------------- ...ence_nest_rnn_multi_unequalength_inputs.py | 107 ++++++++++++++++++ ...sequence_rnn_multi_unequalength_inputs.py} | 68 +++++------ .../tests/test_RecurrentGradientMachine.cpp | 19 ++-- 4 files changed, 151 insertions(+), 149 deletions(-) delete mode 100644 paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf create mode 100644 paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py rename paddle/gserver/tests/{sequence_rnn_multi_unequalength_inputs.conf => sequence_rnn_multi_unequalength_inputs.py} (52%) diff --git a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf deleted file mode 100644 index d0b9450f4b..0000000000 --- a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf +++ /dev/null @@ -1,106 +0,0 @@ -#edit-mode: -*- python -*- -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -######################## data source ################################ -define_py_data_sources2(train_list='gserver/tests/Sequence/dummy.list', - test_list=None, - module='rnn_data_provider', - obj='process_unequalength_subseq') - - -settings(batch_size=2, learning_rate=0.01) -######################## network configure ################################ -dict_dim = 10 -word_dim = 8 -hidden_dim = 8 -label_dim = 2 - -speaker1 = data_layer(name="word1", size=dict_dim) -speaker2 = data_layer(name="word2", size=dict_dim) - -emb1 = embedding_layer(input=speaker1, size=word_dim) -emb2 = embedding_layer(input=speaker2, size=word_dim) - -# This hierachical RNN is designed to be equivalent to the simple RNN in -# sequence_rnn_multi_unequalength_inputs.conf - -def outer_step(x1, x2): - outer_mem1 = memory(name = "outer_rnn_state1", size = hidden_dim) - outer_mem2 = memory(name = "outer_rnn_state2", size = hidden_dim) - def inner_step1(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem1) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - def inner_step2(y): - inner_mem = memory(name = 'inner_rnn_state_' + y.name, - size = hidden_dim, - boot_layer = outer_mem2) - out = fc_layer(input = [y, inner_mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'inner_rnn_state_' + y.name) - return out - - encoder1 = recurrent_group( - step = inner_step1, - name = 'inner1', - input = x1) - - encoder2 = recurrent_group( - step = inner_step2, - name = 'inner2', - input = x2) - - sentence_last_state1 = last_seq(input = encoder1, name = 'outer_rnn_state1') - sentence_last_state2_ = last_seq(input = encoder2, name = 'outer_rnn_state2') - - encoder1_expand = expand_layer(input = sentence_last_state1, - expand_as = encoder2) - - return [encoder1_expand, encoder2] - - -encoder1_rep, encoder2_rep = recurrent_group( - name="outer", - step=outer_step, - input=[SubsequenceInput(emb1), SubsequenceInput(emb2)], - targetInlink=emb2) - -encoder1_last = last_seq(input = encoder1_rep) -encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) -context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) - -rep = last_seq(input=context) -prob = fc_layer(size=label_dim, - input=rep, - act=SoftmaxActivation(), - bias_attr=True) - -outputs(classification_cost(input=prob, - label=data_layer(name="label", size=label_dim))) - diff --git a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py new file mode 100644 index 0000000000..1b709a39c4 --- /dev/null +++ b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py @@ -0,0 +1,107 @@ +#edit-mode: -*- python -*- +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +######################## data source ################################ +define_py_data_sources2( + train_list='gserver/tests/Sequence/dummy.list', + test_list=None, + module='rnn_data_provider', + obj='process_unequalength_subseq') + +settings(batch_size=2, learning_rate=0.01) +######################## network configure ################################ +dict_dim = 10 +word_dim = 8 +hidden_dim = 8 +label_dim = 2 + +speaker1 = data_layer(name="word1", size=dict_dim) +speaker2 = data_layer(name="word2", size=dict_dim) + +emb1 = embedding_layer(input=speaker1, size=word_dim) +emb2 = embedding_layer(input=speaker2, size=word_dim) + +# This hierachical RNN is designed to be equivalent to the simple RNN in +# sequence_rnn_multi_unequalength_inputs.conf + + +def outer_step(x1, x2): + outer_mem1 = memory(name="outer_rnn_state1", size=hidden_dim) + outer_mem2 = memory(name="outer_rnn_state2", size=hidden_dim) + + def inner_step1(y): + inner_mem = memory( + name='inner_rnn_state_' + y.name, + size=hidden_dim, + boot_layer=outer_mem1) + out = fc_layer( + input=[y, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='inner_rnn_state_' + y.name) + return out + + def inner_step2(y): + inner_mem = memory( + name='inner_rnn_state_' + y.name, + size=hidden_dim, + boot_layer=outer_mem2) + out = fc_layer( + input=[y, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='inner_rnn_state_' + y.name) + return out + + encoder1 = recurrent_group(step=inner_step1, name='inner1', input=x1) + + encoder2 = recurrent_group(step=inner_step2, name='inner2', input=x2) + + sentence_last_state1 = last_seq(input=encoder1, name='outer_rnn_state1') + sentence_last_state2_ = last_seq(input=encoder2, name='outer_rnn_state2') + + encoder1_expand = expand_layer( + input=sentence_last_state1, expand_as=encoder2) + + return [encoder1_expand, encoder2] + + +encoder1_rep, encoder2_rep = recurrent_group( + name="outer", + step=outer_step, + input=[SubsequenceInput(emb1), SubsequenceInput(emb2)], + targetInlink=emb2) + +encoder1_last = last_seq(input=encoder1_rep) +encoder1_expandlast = expand_layer(input=encoder1_last, expand_as=encoder2_rep) +context = mixed_layer( + input=[ + identity_projection(encoder1_expandlast), + identity_projection(encoder2_rep) + ], + size=hidden_dim) + +rep = last_seq(input=context) +prob = fc_layer( + size=label_dim, input=rep, act=SoftmaxActivation(), bias_attr=True) + +outputs( + classification_cost( + input=prob, label=data_layer( + name="label", size=label_dim))) diff --git a/paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf b/paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.py similarity index 52% rename from paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf rename to paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.py index 28b1cb98cf..4cf7035477 100644 --- a/paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf +++ b/paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.py @@ -16,11 +16,11 @@ from paddle.trainer_config_helpers import * ######################## data source ################################ -define_py_data_sources2(train_list='gserver/tests/Sequence/dummy.list', - test_list=None, - module='rnn_data_provider', - obj='process_unequalength_seq') - +define_py_data_sources2( + train_list='gserver/tests/Sequence/dummy.list', + test_list=None, + module='rnn_data_provider', + obj='process_unequalength_seq') settings(batch_size=2, learning_rate=0.01) ######################## network configure ################################ @@ -38,38 +38,40 @@ emb2 = embedding_layer(input=speaker2, size=word_dim) # This hierachical RNN is designed to be equivalent to the RNN in # sequence_nest_rnn_multi_unequalength_inputs.conf + def step(x1, x2): - def calrnn(y): - mem = memory(name = 'rnn_state_' + y.name, size = hidden_dim) - out = fc_layer(input = [y, mem], - size = hidden_dim, - act = TanhActivation(), - bias_attr = True, - name = 'rnn_state_' + y.name) - return out - - encoder1 = calrnn(x1) - encoder2 = calrnn(x2) - return [encoder1, encoder2] + def calrnn(y): + mem = memory(name='rnn_state_' + y.name, size=hidden_dim) + out = fc_layer( + input=[y, mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='rnn_state_' + y.name) + return out + + encoder1 = calrnn(x1) + encoder2 = calrnn(x2) + return [encoder1, encoder2] + encoder1_rep, encoder2_rep = recurrent_group( - name="stepout", - step=step, - input=[emb1, emb2]) + name="stepout", step=step, input=[emb1, emb2]) -encoder1_last = last_seq(input = encoder1_rep) -encoder1_expandlast = expand_layer(input = encoder1_last, - expand_as = encoder2_rep) -context = mixed_layer(input = [identity_projection(encoder1_expandlast), - identity_projection(encoder2_rep)], - size = hidden_dim) +encoder1_last = last_seq(input=encoder1_rep) +encoder1_expandlast = expand_layer(input=encoder1_last, expand_as=encoder2_rep) +context = mixed_layer( + input=[ + identity_projection(encoder1_expandlast), + identity_projection(encoder2_rep) + ], + size=hidden_dim) rep = last_seq(input=context) -prob = fc_layer(size=label_dim, - input=rep, - act=SoftmaxActivation(), - bias_attr=True) - -outputs(classification_cost(input=prob, - label=data_layer(name="label", size=label_dim))) +prob = fc_layer( + size=label_dim, input=rep, act=SoftmaxActivation(), bias_attr=True) +outputs( + classification_cost( + input=prob, label=data_layer( + name="label", size=label_dim))) diff --git a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp index 80d713dac0..9d86067fb5 100644 --- a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp +++ b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ #include -#include -#include -#include +#include #include #include -#include +#include +#include +#include P_DECLARE_int32(seed); @@ -45,10 +45,9 @@ public: auto p = const_cast(this); auto& params = p->getGradientMachine()->getParameters(); return std::accumulate( - params.begin(), - params.end(), - 0UL, - [](size_t a, const ParameterPtr& p) { return a + p->getSize(); }); + params.begin(), params.end(), 0UL, [](size_t a, const ParameterPtr& p) { + return a + p->getSize(); + }); } }; @@ -148,8 +147,8 @@ TEST(RecurrentGradientMachine, rnn_multi_input) { TEST(RecurrentGradientMachine, rnn_multi_unequalength_input) { for (bool useGpu : {false, true}) { - test("gserver/tests/sequence_rnn_multi_unequalength_inputs.conf", - "gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf", + test("gserver/tests/sequence_rnn_multi_unequalength_inputs.py", + "gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py", 1e-6, useGpu); } -- GitLab From 6aece5060b600f9c86580c5958b6039973a424fa Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 23 Nov 2016 18:06:54 +0800 Subject: [PATCH 0116/1503] Stash --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 11 +-- ...ence_nest_rnn_multi_unequalength_inputs.py | 69 ++++++++----------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index e1a847fc9c..4d29507ca3 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -139,20 +139,21 @@ 本例中的配置,使用了单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 使用一个\ :code:`recurrent_group`\ 将两个序列同时过完全连接的\ :ref:`glossary_RNN`\ 。对于单层\ :ref:`glossary_RNN`\ 的code如下。 -.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.conf +.. literalinclude:: ../../../paddle/gserver/tests/sequence_rnn_multi_unequalength_inputs.py :language: python - :lines: 41-58 + :lines: 42-59 :linenos: - 双层序列\: - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 - - 函数`outer_step`中可以分别处理这两个特征,但我们需要用targetInlink指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 + - 函数`outer_step`中可以分别处理这两个特征,但我们需要用\ :red:`targetInlink`\ 指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 -.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.conf +.. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py :language: python - :lines: 41-89 + :lines: 42-75, 82-89 + :linenos: 示例4:beam_search的生成 ======================== diff --git a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py index 1b709a39c4..bf88d00f2d 100644 --- a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py +++ b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py @@ -1,4 +1,4 @@ -#edit-mode: -*- python -*- +# edit-mode: -*- python -*- # Copyright (c) 2016 Baidu, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,46 +35,37 @@ speaker2 = data_layer(name="word2", size=dict_dim) emb1 = embedding_layer(input=speaker1, size=word_dim) emb2 = embedding_layer(input=speaker2, size=word_dim) -# This hierachical RNN is designed to be equivalent to the simple RNN in -# sequence_rnn_multi_unequalength_inputs.conf - +# This hierarchical RNN is designed to be equivalent to the simple RNN in +# sequence_rnn_multi_unequalength_inputs.conf def outer_step(x1, x2): - outer_mem1 = memory(name="outer_rnn_state1", size=hidden_dim) - outer_mem2 = memory(name="outer_rnn_state2", size=hidden_dim) - - def inner_step1(y): - inner_mem = memory( - name='inner_rnn_state_' + y.name, - size=hidden_dim, - boot_layer=outer_mem1) - out = fc_layer( - input=[y, inner_mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name='inner_rnn_state_' + y.name) - return out - - def inner_step2(y): - inner_mem = memory( - name='inner_rnn_state_' + y.name, - size=hidden_dim, - boot_layer=outer_mem2) - out = fc_layer( - input=[y, inner_mem], - size=hidden_dim, - act=TanhActivation(), - bias_attr=True, - name='inner_rnn_state_' + y.name) - return out - - encoder1 = recurrent_group(step=inner_step1, name='inner1', input=x1) - - encoder2 = recurrent_group(step=inner_step2, name='inner2', input=x2) - - sentence_last_state1 = last_seq(input=encoder1, name='outer_rnn_state1') - sentence_last_state2_ = last_seq(input=encoder2, name='outer_rnn_state2') + index = [0] + + def inner_step(ipt): + index[0] += 1 + i = index[0] + outer_mem = memory(name="outer_rnn_state_%d" % i, size=hidden_dim) + + def inner_step_impl(y): + inner_mem = memory( + name="inner_rnn_state_" + y.name, + size=hidden_dim, + boot_layer=outer_mem) + out = fc_layer( + input=[y, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='inner_rnn_state_' + y.name) + return out + + encoder = recurrent_group( + step=inner_step_impl, name='inner_%d' % i, input=ipt) + last = last_seq(name="outer_rnn_state_%d" % i, input=encoder) + return encoder, last + + _, sentence_last_state1 = inner_step(ipt=x1) + encoder2, _ = inner_step(ipt=x2) encoder1_expand = expand_layer( input=sentence_last_state1, expand_as=encoder2) -- GitLab From 9d377f0996e671f649d9e1fdd40ce327712a5e46 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 23 Nov 2016 19:18:31 +0800 Subject: [PATCH 0117/1503] Update doc and merge run_multi.sh into run.sh for PaddlePaddle. --- benchmark/README.md | 32 +++++++------- benchmark/caffe/image/run_multi.sh | 2 +- benchmark/paddle/image/run.sh | 13 +++++- benchmark/paddle/image/run_multi.sh | 42 ------------------- benchmark/paddle/rnn/run.sh | 12 ++++++ benchmark/paddle/rnn/run_multi.sh | 34 --------------- .../tensorflow/image/alexnet_multi_gpu.py | 1 - .../tensorflow/image/smallnet_mnist_cifar.py | 1 - 8 files changed, 41 insertions(+), 96 deletions(-) delete mode 100755 benchmark/paddle/image/run_multi.sh delete mode 100755 benchmark/paddle/rnn/run_multi.sh diff --git a/benchmark/README.md b/benchmark/README.md index 8d2cf5737d..8b453a7b59 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -7,19 +7,19 @@ Machine: - cuDNN: v5.1 - system: Docker 1.12.1, all platform are tested in docker environment. -Platform: +Platforms: - PaddlePaddle: - Tensorflow: gcr.io/tensorflow/tensorflow:0.11.0rc0-gpu -- Caffe: +- Caffe: kaixhin/cuda-caffe -Several convolutional neural networks and recurrent neural network are used to test. +Several convolutional neural networks and recurrent neural networks are used to test. ## Image ### Benchmark Model -AlexNet, GooleNet and a small network which refer the config of cifar10 in Caffe are used. +AlexNet, GoogleNet and a small network used in Caffe. - [AlexNet](https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet): but the group size is one. @@ -38,9 +38,9 @@ AlexNet, GooleNet and a small network which refer the config of cifar10 in Caffe | TensorFlow | 223 | 364 | 645 | 1235 | | Caffe | 324 | 627 | 1232 | 2513 | -##### Notation +**Notation** -All platforms use cuDnn-v5.1. You might see that caffe is slower, because the workspace limit size is 8 * 1024 * 1024 in Caffe's cuDnn-conv interface. This size is larger in PaddlePaddle and TensorFlow. Caffe will be faster if increasing the workspace limit size. +All platforms use cuDNN-v5.1. We see that caffe is slower in this experiment, because its workspace limit size of cuDNN-conv interface is 8 * 1024 * 1024, which is smaller in PaddlePaddle and TensorFlow. Note that Caffe will be faster if increasing the workspace limit size. - GoogletNet: input - 3 * 224 * 224, Time: ms/batch @@ -59,9 +59,9 @@ All platforms use cuDnn-v5.1. You might see that caffe is slower, because the wo | TensorFlow | 9 | 15 | 28 | 59 | | Caffe | 9.373 | 16.6606 | 31.4797 | 59.719 | -##### Notation +**Notation** -All the tests in caffe use `caffe time` to execute, which is not including the parameter updating process. But the time in PaddlePaddle and TensorFlow contains it. +All the experiments in caffe use `caffe time` to execute, which does not include the time of parameter updating. The time in PaddlePaddle and TensorFlow contains it. But, compared with the total time, the time of parameter updating is relatively little. In Tensorflow, they implement algorithm searching method instead of using the algorithm searching interface in cuDNN. @@ -69,13 +69,13 @@ In Tensorflow, they implement algorithm searching method instead of using the al - AlexNet, ms / batch -| totoal-BatchSize | 128 * 4 | 256 * 4 | +| total-BatchSize | 128 * 4 | 256 * 4 | |------------------|----------| -----------| | PaddlePaddle | 347 | 622 | | TensorFlow | 377 | 675 | | Caffe | 1229 | 2435 | -For example, if `totoal-BatchSize = 128 * 4`, the speed is calculated by +For example, if `total-BatchSize = 128 * 4`, the speedup ratio is calculated by ``` time_at_1gpu_batch_128 * 4 / time_at_4gpu_total_batch_512 @@ -86,9 +86,9 @@ For example, if `totoal-BatchSize = 128 * 4`, the speed is calculated by -- GooleNet, ms / batch +- GoogleNet, ms / batch -| totoal-BatchSize | 128 * 4 | 256 * 4 | +| total-BatchSize | 128 * 4 | 256 * 4 | |-------------------|--------------| ----------- | | PaddlePaddle | 1178 | 2367 | | TensorFlow | 1210 | 2292 | @@ -102,7 +102,7 @@ We use lstm network for text classfication to test benchmark. ### Dataset - [IMDB](http://www.iro.umontreal.ca/~lisa/deep/data/imdb.pkl) -- Sequence legth=100, in fact, PaddlePaddle support training with variable-length sequence. But TensorFlow need to pad, in order to compare, we also pad sequence length to 100 in PaddlePaddle. +- Sequence legth is 100. In fact, PaddlePaddle supports training with variable-length sequence, but TensorFlow needs to pad, we also pad sequence length to 100 in PaddlePaddle in order to compare. - Dictionary size=30000 - Peephole connection is used in `lstmemory` by default in PaddlePaddle. It is also configured in TensorFlow. @@ -110,7 +110,7 @@ We use lstm network for text classfication to test benchmark. #### LSTM in Text Classification -Testing network for different hidden size, batch size with `2 lstm layer + fc` network. +Testing `2 lstm layer + fc` network with different hidden size and batch size. - Batch size = 64, ms / batch @@ -138,7 +138,7 @@ Testing network for different hidden size, batch size with `2 lstm layer + fc` n #### Seq2Seq -The benchmark of sequence-to-sequence network will be add later. +The benchmark of sequence-to-sequence network will be added later. ### Multi GPU: 4 GPUs @@ -165,4 +165,4 @@ The benchmark of sequence-to-sequence network will be add later. #### Seq2Seq -The benchmark of sequence-to-sequence network will be add later. +The benchmark of sequence-to-sequence network will be added later. diff --git a/benchmark/caffe/image/run_multi.sh b/benchmark/caffe/image/run_multi.sh index f72b062c11..9a0a71bc18 100755 --- a/benchmark/caffe/image/run_multi.sh +++ b/benchmark/caffe/image/run_multi.sh @@ -9,7 +9,7 @@ function test() { sed -i "/input: \"data\"/{n;s/^input_dim.*/input_dim: ${batch_per_gpu}/g}" $cfg sed -i "/input: \"label\"/{n;s/^input_dim.*/input_dim: ${batch_per_gpu}/g}" $cfg sed -i "1c\net : \"${cfg}\"" solver.prototxt - caffe train --solver=solver.prototxt -gpu all > logs/${prefix}-4gpu-batch${batch}.log 2>&1 + caffe train --solver=solver.prototxt -gpu 0,1,2,3 > logs/${prefix}-4gpu-batch${batch}.log 2>&1 } if [ ! -d "logs" ]; then diff --git a/benchmark/paddle/image/run.sh b/benchmark/paddle/image/run.sh index 6fccf7854c..a216928835 100755 --- a/benchmark/paddle/image/run.sh +++ b/benchmark/paddle/image/run.sh @@ -1,5 +1,8 @@ set -e +# If use `paddle train` to run, it must use DataProvider to +# pass the data type to PaddlePaddle system. +# And PaddlePaddle requires training set list (train.list), function gen_file() { if [ ! -d "train.txt" ]; then for ((i=1;i<=1024;i++)) @@ -26,7 +29,6 @@ function train() { --log_period=10 \ --test_period=100 \ --config_args=$args \ - --cudnn_dir=/home/dangqingqing/tools/cudnn-5.1/lib64 \ > logs/$prefix-${thread}gpu-$bz.log 2>&1 } @@ -52,3 +54,12 @@ train smallnet_mnist_cifar.py 1 64 smallnet train smallnet_mnist_cifar.py 1 128 smallnet train smallnet_mnist_cifar.py 1 256 smallnet train smallnet_mnist_cifar.py 1 512 smallnet + + +############################ +#========multi-gpus=========# +train alexnet.py 4 512 alexnet +train alexnet.py 4 1024 alexnet + +train googlenet.py 4 512 googlenet +train googlenet.py 4 1024 googlenet diff --git a/benchmark/paddle/image/run_multi.sh b/benchmark/paddle/image/run_multi.sh deleted file mode 100755 index c506668fe0..0000000000 --- a/benchmark/paddle/image/run_multi.sh +++ /dev/null @@ -1,42 +0,0 @@ -set -e - -function gen_file() { - if [ ! -d "train.txt" ]; then - for ((i=1;i<=1024;i++)) - do - echo "train/n09246464/n09246464_38735.jpeg 972" >> train.txt - done - fi - - if [ ! -d "train.list" ]; then - echo "train.txt" > train.list - fi -} - -function train() { - cfg=$1 - thread=$2 - bz=$3 - args="batch_size=$3" - prefix=$4 - paddle train --job=time \ - --config=$cfg \ - --use_gpu=True \ - --trainer_count=$thread \ - --log_period=10 \ - --test_period=100 \ - --config_args=$args \ - > logs/$prefix-${thread}gpu-$bz.log 2>&1 -} - -gen_file -if [ ! -d "logs" ]; then - mkdir logs -fi - -#========multi-gpus=========# -train alexnet.py 4 512 alexnet -train alexnet.py 4 1024 alexnet - -train googlenet.py 4 512 googlenet -train googlenet.py 4 1024 googlenet diff --git a/benchmark/paddle/rnn/run.sh b/benchmark/paddle/rnn/run.sh index 92c6e0b4b4..e9dfeb2e52 100755 --- a/benchmark/paddle/rnn/run.sh +++ b/benchmark/paddle/rnn/run.sh @@ -36,3 +36,15 @@ train rnn.py 1 2 1 1280 128 train rnn.py 1 2 1 256 256 train rnn.py 1 2 1 512 256 train rnn.py 1 2 1 1280 256 + + +#==================multi gpus=====================# +# hidden_size=256, lstm_num=2, different batch size +train rnn.py 4 2 1 256 128 +train rnn.py 4 2 1 256 256 +train rnn.py 4 2 1 256 512 + +# hidden_size=512, lstm_num=4, different batch size +train rnn.py 4 2 1 512 128 +train rnn.py 4 2 1 512 256 +train rnn.py 4 2 1 512 512 diff --git a/benchmark/paddle/rnn/run_multi.sh b/benchmark/paddle/rnn/run_multi.sh deleted file mode 100755 index 50ee469bcd..0000000000 --- a/benchmark/paddle/rnn/run_multi.sh +++ /dev/null @@ -1,34 +0,0 @@ -set -e - -function train() { - cfg=$1 - thread=$2 - args="lstm_num=${3},seq_pad=${4},hidden_size=${5},batch_size=${6}" - paddle train --job=time \ - --config=$cfg \ - --use_gpu=1 \ - --trainer_count=$thread \ - --log_period=10 \ - --test_period=100 \ - --num_passes=1 \ - --feed_data=1 \ - --config_args=$args \ - >logs/rnn-pad${4}-${thread}gpu-lstm${3}-hid${5}-batch${6}.log 2>&1 -} - - -if [ ! -d "logs" ]; then - mkdir logs -fi - -#-----config--gpu--lstm_num--padding--hidden_size--batch_size -#==================multi gpus=====================# -# hidden_size=256, lstm_num=2, different batch size -train rnn.py 4 2 1 256 128 -train rnn.py 4 2 1 256 256 -train rnn.py 4 2 1 256 512 - -# hidden_size=512, lstm_num=4, different batch size -train rnn.py 4 2 1 512 128 -train rnn.py 4 2 1 512 256 -train rnn.py 4 2 1 512 512 diff --git a/benchmark/tensorflow/image/alexnet_multi_gpu.py b/benchmark/tensorflow/image/alexnet_multi_gpu.py index 949ad77f3b..f006fb56af 100644 --- a/benchmark/tensorflow/image/alexnet_multi_gpu.py +++ b/benchmark/tensorflow/image/alexnet_multi_gpu.py @@ -279,7 +279,6 @@ def run_benchmark(): staircase=True) # Create an optimizer that performs gradient descent. - # opt = tf.train.GradientDescentOptimizer(lr) opt = tf.train.MomentumOptimizer(lr, 0.9) # Calculate the gradients for each model tower. diff --git a/benchmark/tensorflow/image/smallnet_mnist_cifar.py b/benchmark/tensorflow/image/smallnet_mnist_cifar.py index b539d1bed0..679dd1ab32 100644 --- a/benchmark/tensorflow/image/smallnet_mnist_cifar.py +++ b/benchmark/tensorflow/image/smallnet_mnist_cifar.py @@ -222,7 +222,6 @@ def run_benchmark(): objective = loss(last_layer, labels) # Compute gradients. - # opt = tf.train.GradientDescentOptimizer(0.001) opt = tf.train.MomentumOptimizer(0.001, 0.9) grads = opt.compute_gradients(objective) global_step = tf.get_variable('global_step', [], -- GitLab From ec0214392152dc88f04b7440b11edbfe0e022d67 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 23 Nov 2016 20:47:04 +0800 Subject: [PATCH 0118/1503] use literalinclude --- doc_cn/ui/predict/swig_py_paddle.rst | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/doc_cn/ui/predict/swig_py_paddle.rst b/doc_cn/ui/predict/swig_py_paddle.rst index f9750d80c9..4c0a0de820 100644 --- a/doc_cn/ui/predict/swig_py_paddle.rst +++ b/doc_cn/ui/predict/swig_py_paddle.rst @@ -25,26 +25,9 @@ PaddlePaddle使用swig对常用的预测接口进行了封装,通过编译会 如下是一段使用mnist model来实现手写识别的预测代码。完整的代码见 ``src_root/doc/ui/predict/predict_sample.py`` 。mnist model可以通过 ``src_root\demo\mnist`` 目录下的demo训练出来。 -.. code-block:: python - - from py_paddle import swig_paddle, DataProviderConverter - from paddle.trainer.PyDataProvider2 import dense_vector - from paddle.trainer.config_parser import parse_config - - TEST_DATA = [...] - - def main(): - conf = parse_config("./mnist_model/trainer_config.py", "") - network = swig_paddle.GradientMachine.createFromConfigProto(conf.model_config) - assert isinstance(network, swig_paddle.GradientMachine) # For code hint. - network.loadParameters("./mnist_model/") - converter = DataProviderConverter([dense_vector(784)]) - inArg = converter(TEST_DATA) - print network.forwardTest(inArg) - - if __name__ == '__main__': - swig_paddle.initPaddle("--use_gpu=0") - main() +.. literalinclude:: ../../../doc/ui/predict/predict_sample.py + :language: python + :lines: 15-18,121-136 Demo预测输出如下,其中value即为softmax层的输出。由于TEST_DATA包含两条预测数据,所以输出的value包含两个向量 。 -- GitLab From 88a7cbd6392540dcef4d84fef1324c0b33c62b0c Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 23 Nov 2016 21:02:00 +0800 Subject: [PATCH 0119/1503] Use empty file list for paddle/image. --- benchmark/.gitignore | 9 +++++++++ benchmark/paddle/image/provider.py | 9 ++++----- benchmark/paddle/image/run.sh | 20 +++----------------- 3 files changed, 16 insertions(+), 22 deletions(-) create mode 100644 benchmark/.gitignore diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 0000000000..7b66e8a5b5 --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1,9 @@ +paddle/image/logs +paddle/image/*.pyc +paddle/image/train.list +paddle/rnn/logs +paddle/rnn/*.pyc +paddle/rnn/imdb.pkl +caffe/image/logs +tensorflow/image/logs +tensorflow/rnn/logs diff --git a/benchmark/paddle/image/provider.py b/benchmark/paddle/image/provider.py index b6bc0e9aa2..1ac47212b5 100644 --- a/benchmark/paddle/image/provider.py +++ b/benchmark/paddle/image/provider.py @@ -20,8 +20,7 @@ def initHook(settings, height, width, color, num_class, **kwargs): @provider( init_hook=initHook, min_pool_size=-1, cache=CacheType.CACHE_PASS_IN_MEM) def process(settings, file_list): - with open(file_list, 'r') as fdata: - for line in fdata: - img = np.random.rand(1, settings.data_size).reshape(-1, 1).flatten() - lab = random.randint(0, settings.num_class) - yield img.tolist(), int(lab) + for i in xrange(1024): + img = np.random.rand(1, settings.data_size).reshape(-1, 1).flatten() + lab = random.randint(0, settings.num_class) + yield img.astype('float32'), int(lab) diff --git a/benchmark/paddle/image/run.sh b/benchmark/paddle/image/run.sh index a216928835..717ed487ba 100755 --- a/benchmark/paddle/image/run.sh +++ b/benchmark/paddle/image/run.sh @@ -1,21 +1,5 @@ set -e -# If use `paddle train` to run, it must use DataProvider to -# pass the data type to PaddlePaddle system. -# And PaddlePaddle requires training set list (train.list), -function gen_file() { - if [ ! -d "train.txt" ]; then - for ((i=1;i<=1024;i++)) - do - echo "train/n09246464/n09246464_38735.jpeg 972" >> train.txt - done - fi - - if [ ! -d "train.list" ]; then - echo "train.txt" > train.list - fi -} - function train() { cfg=$1 thread=$2 @@ -32,7 +16,9 @@ function train() { > logs/$prefix-${thread}gpu-$bz.log 2>&1 } -gen_file +if [ ! -d "train.list" ]; then + echo " " > train.list +fi if [ ! -d "logs" ]; then mkdir logs fi -- GitLab From 0710e67d78746d034fa105f6b59a78a79efad062 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 23 Nov 2016 21:11:44 +0800 Subject: [PATCH 0120/1503] refine ui index.rst --- doc_cn/ui/index.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc_cn/ui/index.rst b/doc_cn/ui/index.rst index 5aba272c62..dd2c845eb7 100644 --- a/doc_cn/ui/index.rst +++ b/doc_cn/ui/index.rst @@ -1,8 +1,9 @@ +######## 用户接口 -======== +######## 数据提供 -'''''''' +======== .. toctree:: :maxdepth: 1 @@ -11,14 +12,19 @@ 命令行参数 -'''''''''' +========== + +.. toctree:: + + cmd/index.rst + * `Use Case <../../doc/ui/cmd_argument/use_case.html>`_ * `Argument Outline <../../doc/ui/cmd_argument/argument_outline.html>`_ * `Detail Description <../../doc/ui/cmd_argument/detail_introduction.html>`_ 预测 -'''' +==== .. toctree:: -- GitLab From 41623561ca39dfd9cabde7f304385d801b635622 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 23 Nov 2016 21:22:35 +0800 Subject: [PATCH 0121/1503] refine paddle_version.rst --- doc_cn/ui/cmd/paddle_version.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc_cn/ui/cmd/paddle_version.rst b/doc_cn/ui/cmd/paddle_version.rst index 0a4f8dd472..a65239b9ef 100644 --- a/doc_cn/ui/cmd/paddle_version.rst +++ b/doc_cn/ui/cmd/paddle_version.rst @@ -1,9 +1,7 @@ paddle version的命令行参数 ========================== -paddle version可以打印出paddle的版本信息和编译的选项。常见的输出格式为 +paddle version用于打印当前的版本信息和相关编译选项。常见的输出格式如下。第一行说明了paddle的版本信息,后面跟着一些主要的编译选项。编译选项的具体意义可以参考 +`编译参数选项文件 <../../build_and_install/cmake/compile_options.html>`_ .. literalinclude:: paddle_version.txt - -其第一行说明了paddle的版本,后面跟着一系列编译参数。这里可以参考paddle的 -`编译参数选项文件 <../../build/cmake/compile_options.html>`_ -- GitLab From 24cfc5ab3cb5ee50ff0efe7c5baf5f7d6436ba7a Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 23 Nov 2016 21:35:27 +0800 Subject: [PATCH 0122/1503] Minor changes for use_concepts.rst. --- doc_cn/concepts/use_concepts.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/doc_cn/concepts/use_concepts.rst b/doc_cn/concepts/use_concepts.rst index c86429f323..13f6410b98 100644 --- a/doc_cn/concepts/use_concepts.rst +++ b/doc_cn/concepts/use_concepts.rst @@ -15,7 +15,7 @@ PaddlePaddle是一个深度学习框架,支持单机模式和多机模式。 系统框图 ======== -下图描述了用户使用框图,PaddlePaddle里链接了Python解释器,trainer进程可以利用这个解释器执行Python脚本,Python脚本里定义了模型配置、训练算法、以及数据读取函数。其中,数据读取程序往往定义在一个单独Python脚本文件里,被称为DataProvider,通常是一个Python函数。模型配置、训练算法通常定义在另一单独Python文件中。下面将分别介绍这两部分。 +下图描述了用户使用框图,PaddlePaddle的trainer进程里内嵌了Python解释器,trainer进程可以利用这个解释器执行Python脚本,Python脚本里定义了模型配置、训练算法、以及数据读取函数。其中,数据读取程序往往定义在一个单独Python脚本文件里,被称为DataProvider,通常是一个Python函数。模型配置、训练算法通常定义在另一单独Python文件中。下面将分别介绍这两部分。 .. graphviz:: @@ -37,17 +37,15 @@ PaddlePaddle是一个深度学习框架,支持单机模式和多机模式。 DataProvider ============ -在不同的应用里,训练数据的格式往往各不相同。因此,为了用户能够灵活的处理数据,我们提供了Python处理数据的接口,称为 `PyDataProvider`_ 。 +DataProvider是PaddlePaddle系统的数据提供器,trainer进程会调用DataProvider函数,将用户的原始数据转换成系统可以识别的数据类型。当所有数据读取完一轮后,DataProvider返回空数据,通知系统一轮数据读取结束,系统每一轮训练开始时会重置DataProvider。需要注意的是,DataProvider是被系统调用,而不是新数据驱动系统,一些随机化噪声添加都应该在DataProvider中完成。 -trainer进程会调用DataProvider函数,将用户的原始数据转换成系统可以识别的数据类型。当所有数据读取完一轮后,DataProvider返回空数据,通知系统一轮数据读取结束,系统每一轮训练开始时会重置DataProvider。需要注意的是,DataProvider是被系统调用,而不是新数据驱动系统,一些随机化噪声添加都应该在DataProvider中完成。 - -在 ``PyDataProvider`` 中,系统C++模块接管了shuffle、处理batch、GPU和CPU通信、双缓冲、异步读取等问题,一些情况下(如:``min_pool_size=0``)需要Python接口里处理shuffle,可以参考 `PyDataProvider`_ 的相关文档继续深入了解。 +在不同的应用里,训练数据的格式往往各不相同。因此,为了用户能够灵活的处理数据,我们提供了Python处理数据的接口,称为 `PyDataProvider`_ 。在 ``PyDataProvider`` 中,系统C++模块接管了shuffle、处理batch、GPU和CPU通信、双缓冲、异步读取等问题,一些情况下(如:``min_pool_size=0``)需要Python接口里处理shuffle,可以参考 `PyDataProvider`_ 的相关文档继续深入了解。 模型配置文件 ============ -模型配置主要包括数据传入接口定义(DataConfig)、优化算法(OptimizationConfig)、网络结构(ModelConfig)。 其中数据传入接口定义与DataProvider的关系是:DataProvider里定义数据读取函数,配置文件的DataConfig里指定DataProvider文件名字、生成数据函数接口,请不要混淆。 +模型配置文件主要包括数据传入接口定义(DataConfig)、优化算法(OptimizationConfig)、网络结构(ModelConfig)。 其中数据传入接口定义与DataProvider的关系是:DataProvider里定义数据读取函数,配置文件的DataConfig里指定DataProvider文件名字、生成数据函数接口,请不要混淆。 一个简单的模型配置文件为: @@ -61,7 +59,7 @@ trainer进程会调用DataProvider函数,将用户的原始数据转换成系 DataConfig ---------- -使用函数 ``define_py_data_sources2`` 配置数据源,后缀 2 是Paddle历史遗留问题,因为Paddle之前使用的PyDataProvider性能问题,重构了一个新的 `PyDataProvider`_ 。 +使用 `PyDataProvider`_ 的函数 ``define_py_data_sources2`` 配置数据源,后缀 2 是Paddle历史遗留问题,因为Paddle之前使用的PyDataProvider性能问题,重构了一个新的 `PyDataProvider`_ 。 ``define_py_data_sources2`` 里通过train_list和test_list指定是训练文件列表和测试文件列表。 如果传入字符串的话,是指一个数据列表文件。这个数据列表文件中包含的是每一个训练或者测试文件的路径。如果传入一个list的话,则会默认生成一个list文件,再传入给train.list或者test.list。 @@ -70,7 +68,7 @@ DataConfig OptimizationConfig ------------------ -通过`settings`_ 接口设置神经网络所使用的训练参数和优化算法,包括学习率、batch_size、优化算法、正则方法等,具体的使用方法请参考 `settings`_ 文档。 +通过`settings`_ 接口设置神经网络所使用的训练参数和 `优化算法`_ ,包括学习率、batch_size、优化算法、正则方法等,具体的使用方法请参考 `settings`_ 文档。 ModelConfig ----------- @@ -150,6 +148,7 @@ PaddlePaddle多机采用经典的 Parameter Server 架构对多个节点的 trai .. _PyDataProvider: ../ui/data_provider/pydataprovider2.html .. _settings: ../../doc/ui/api/trainer_config_helpers/optimizers.html#settings +.. _优化算法: ../../doc/ui/api/trainer_config_helpers/optimizers.html#optimizers .. _trainer_config_helper: ../../doc/ui/api/trainer_config_helpers/index.html .. _data_layer: ../../doc/ui/api/trainer_config_helpers/layers.html#data-layer .. _simple_img_conv_pool: ../../doc/ui/api/trainer_config_helpers/networks.html#simple-img-conv-pool -- GitLab From e9d825c1751d36b56f2a9099cbcf5e6816233447 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 23 Nov 2016 22:52:44 +0800 Subject: [PATCH 0123/1503] refine cmd doc --- doc_cn/ui/cmd/index.rst | 27 ++++++++++++++++----------- doc_cn/ui/cmd/paddle_pserver.rst | 2 -- doc_cn/ui/cmd/paddle_train.rst | 2 -- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/doc_cn/ui/cmd/index.rst b/doc_cn/ui/cmd/index.rst index 6d62180a6a..f975d432c0 100644 --- a/doc_cn/ui/cmd/index.rst +++ b/doc_cn/ui/cmd/index.rst @@ -1,17 +1,22 @@ -命令行参数 -========== +PaddlePaddle的命令行参数 +======================== -安装好的PaddlePaddle脚本包括多条命令,他们是 +安装好PaddlePaddle后,在命令行直接敲击 ``paddle`` 或 ``paddle --help`` 会显示如下一些命令行参数。 -* paddle train即为PaddlePaddle的训练进程。可以使用paddle train完成单机多显卡多线程的训 - 练。也可以和paddle pserver组合使用,完成多机训练。 -* paddle pserver为PaddlePaddle的parameter server进程。负责多机训练中的参数聚合工作。 -* paddle version可以打印出PaddlePaddle的版本和编译时信息。 -* merge_model 可以将PaddlePaddle的模型和配置打包成一个文件。方便部署分发。 -* dump_config 可以将PaddlePaddle的训练模型以proto string的格式打印出来 -* make_diagram 可以使用graphviz对PaddlePaddle的网络模型进行绘制,方便调试使用。 +* ``train`` Start a paddle_trainer + 启动一个PaddlePaddle训练进程。 ``paddle train`` 可以通过命令行参数 ``-local=true`` 启动一个单机的训练进程;也可以和 ``paddle pserver`` 一起使用启动多机的分布式训练进程。 +* ``pserver`` Start a paddle_pserver_main + 在多机分布式训练下启动PaddlePaddle的parameter server进程。 +* ``version`` Print paddle version + 用于打印当前PaddlePaddle的版本和编译选项相关信息。 +* ``merge_model`` Start a paddle_merge_model + 用于将PaddlePaddle的模型参数文件和模型配置文件打包成一个文件,方便做部署分发。 +* ``dump_config`` Dump the trainer config as proto string + 用于将PaddlePaddle的模型配置文件以proto string的格式打印出来。 +* ``make_diagram`` + 使用graphviz对PaddlePaddle的模型配置文件进行绘制。 -更详细的介绍请参考各个命令的命令行参数文档。 +更详细的介绍请参考各命令行参数文档。 .. toctree:: :glob: diff --git a/doc_cn/ui/cmd/paddle_pserver.rst b/doc_cn/ui/cmd/paddle_pserver.rst index 891975c34a..e69de29bb2 100644 --- a/doc_cn/ui/cmd/paddle_pserver.rst +++ b/doc_cn/ui/cmd/paddle_pserver.rst @@ -1,2 +0,0 @@ -paddle pserver的命令行参数 -========================== diff --git a/doc_cn/ui/cmd/paddle_train.rst b/doc_cn/ui/cmd/paddle_train.rst index 87b84f5cbd..e69de29bb2 100644 --- a/doc_cn/ui/cmd/paddle_train.rst +++ b/doc_cn/ui/cmd/paddle_train.rst @@ -1,2 +0,0 @@ -paddle train的命令行参数 -======================== -- GitLab From ed7334d694aa32325697af8a85250fed5d642387 Mon Sep 17 00:00:00 2001 From: Haonan Date: Wed, 23 Nov 2016 11:01:45 -0800 Subject: [PATCH 0124/1503] fix the has_LayerOutput boolean for StaticInput as the input of recurrent_group --- python/paddle/trainer_config_helpers/layers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index d984e84320..9a45a51589 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -2853,11 +2853,11 @@ def recurrent_group(step, :type targetInlink: LayerOutput|SubsequenceInput :param is_generating: If is generating, none of input type should be LayerOutput; - else, for training or testing, one of the input type must + else, for training or testing, one of the input type must be LayerOutput. : type is_generating: bool - + :return: LayerOutput object. :rtype: LayerOutput """ @@ -2905,15 +2905,16 @@ def recurrent_group(step, seq_reversed=reverse, target_inlinkname=targetInlinkName) in_args = [] - has_LayerOutput = True + has_LayerOutput = False for each_input in input: assert is_single_input(each_input) if isinstance(each_input, LayerOutput): in_args.append(each_input) + has_LayerOutput = True elif isinstance(each_input, SubsequenceInput): in_args.append(each_input.input) + has_LayerOutput = True else: - has_LayerOutput = False mem_name = "__%s_memory__" % each_input.input.name mem = memory( name=mem_name, -- GitLab From 0561dd01b874c94525b42bd596bafb8020b547bc Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 24 Nov 2016 10:13:16 +0800 Subject: [PATCH 0125/1503] Update doc and proc_from_raw_data/get_data.sh --- .../data/proc_from_raw_data/get_data.sh | 20 ++++++++++--------- doc/demo/quick_start/index_en.md | 2 +- doc_cn/demo/quick_start/index.md | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/demo/quick_start/data/proc_from_raw_data/get_data.sh b/demo/quick_start/data/proc_from_raw_data/get_data.sh index 9c3e9db248..cd85e26842 100755 --- a/demo/quick_start/data/proc_from_raw_data/get_data.sh +++ b/demo/quick_start/data/proc_from_raw_data/get_data.sh @@ -25,14 +25,17 @@ cd $DIR # Download data echo "Downloading Amazon Electronics reviews data..." # http://jmcauley.ucsd.edu/data/amazon/ -#wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz echo "Downloading mosesdecoder..." -#https://github.com/moses-smt/mosesdecoder -#wget https://github.com/moses-smt/mosesdecoder/archive/master.zip -#unzip master.zip -#rm master.zip -echo "Done." +# https://github.com/moses-smt/mosesdecoder +wget https://github.com/moses-smt/mosesdecoder/archive/master.zip +unzip master.zip +rm master.zip + +################## +# Preprocess data +echo "Preprocess data..." export LC_ALL=C UNAME_STR=`uname` @@ -42,12 +45,11 @@ else SHUF_PROG='gshuf' fi -# Start preprocess mkdir -p tmp python preprocess.py -i reviews_Electronics_5.json.gz # uniq and shuffle cd tmp -echo 'uniq and shuffle...' +echo 'Uniq and shuffle...' cat pos_*|sort|uniq|${SHUF_PROG}> pos.shuffed cat neg_*|sort|uniq|${SHUF_PROG}> neg.shuffed @@ -74,4 +76,4 @@ echo 'test.txt' > test.list rm -rf tmp mv dict.txt dict_all.txt cat dict_all.txt | head -n 30001 > dict.txt -echo 'preprocess finished' +echo 'Done.' diff --git a/doc/demo/quick_start/index_en.md b/doc/demo/quick_start/index_en.md index 01f6f8ef54..11d568b8f9 100644 --- a/doc/demo/quick_start/index_en.md +++ b/doc/demo/quick_start/index_en.md @@ -59,7 +59,7 @@ To build your text classification system, your code will need to perform five st ## Preprocess data into standardized format In this example, you are going to use [Amazon electronic product review dataset](http://jmcauley.ucsd.edu/data/amazon/) to build a bunch of deep neural network models for text classification. Each text in this dataset is a product review. This dataset has two categories: “positive” and “negative”. Positive means the reviewer likes the product, while negative means the reviewer does not like the product. -`demo/quick_start` in the [source code](https://github.com/baidu/Paddle) provides script for downloading the preprocessed data as shown below. (If you want to process the raw data, you can use the script `demo/quick_start/data/proc_from_raw_data/get_data.sh`). +`demo/quick_start` in the [source code](https://github.com/PaddlePaddle/Paddle) provides script for downloading the preprocessed data as shown below. (If you want to process the raw data, you can use the script `demo/quick_start/data/proc_from_raw_data/get_data.sh`). ```bash cd demo/quick_start diff --git a/doc_cn/demo/quick_start/index.md b/doc_cn/demo/quick_start/index.md index 514b45a487..4a6e07ee1f 100644 --- a/doc_cn/demo/quick_start/index.md +++ b/doc_cn/demo/quick_start/index.md @@ -32,7 +32,7 @@ ## 数据格式准备(Data Preparation) 在本问题中,我们使用[Amazon电子产品评论数据](http://jmcauley.ucsd.edu/data/amazon/), -将评论分为好评(正样本)和差评(负样本)两类。[源码](https://github.com/baidu/Paddle)的`demo/quick_start`里提供了下载已经预处理数据的脚本(如果想从最原始的数据处理,可以使用脚本 `./demo/quick_start/data/proc_from_raw_data/get_data.sh`)。 +将评论分为好评(正样本)和差评(负样本)两类。[源码](https://github.com/PaddlePaddle/Paddle)的`demo/quick_start`里提供了下载已经预处理数据的脚本(如果想从最原始的数据处理,可以使用脚本 `./demo/quick_start/data/proc_from_raw_data/get_data.sh`)。 ```bash cd demo/quick_start @@ -141,7 +141,7 @@ PyDataProvider2。 我们将以基本的逻辑回归网络作为起点,并逐渐展示更加深入的功能。更详细的网络配置 连接请参考Layer文档。 -所有配置在[源码](https://github.com/baidu/Paddle)`demo/quick_start`目录,首先列举逻辑回归网络。 +所有配置在[源码](https://github.com/PaddlePaddle/Paddle)`demo/quick_start`目录,首先列举逻辑回归网络。 ### 逻辑回归模型(Logistic Regression) -- GitLab From 94ea8aa6657a3ec936eb4d95b996ddc69d21db3e Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 24 Nov 2016 11:29:58 +0800 Subject: [PATCH 0126/1503] follow comments --- doc_cn/ui/cmd/paddle_version.rst | 2 +- doc_cn/ui/index.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc_cn/ui/cmd/paddle_version.rst b/doc_cn/ui/cmd/paddle_version.rst index a65239b9ef..537c23df75 100644 --- a/doc_cn/ui/cmd/paddle_version.rst +++ b/doc_cn/ui/cmd/paddle_version.rst @@ -1,7 +1,7 @@ paddle version的命令行参数 ========================== -paddle version用于打印当前的版本信息和相关编译选项。常见的输出格式如下。第一行说明了paddle的版本信息,后面跟着一些主要的编译选项。编译选项的具体意义可以参考 +paddle version用于打印当前的版本信息和相关编译选项。常见的输出格式如下。第一行说明了PaddlePaddle的版本信息,后面跟着一些主要的编译选项。编译选项的具体意义可以参考 `编译参数选项文件 <../../build_and_install/cmake/compile_options.html>`_ .. literalinclude:: paddle_version.txt diff --git a/doc_cn/ui/index.rst b/doc_cn/ui/index.rst index dd2c845eb7..8079bd9180 100644 --- a/doc_cn/ui/index.rst +++ b/doc_cn/ui/index.rst @@ -18,9 +18,9 @@ cmd/index.rst -* `Use Case <../../doc/ui/cmd_argument/use_case.html>`_ -* `Argument Outline <../../doc/ui/cmd_argument/argument_outline.html>`_ -* `Detail Description <../../doc/ui/cmd_argument/detail_introduction.html>`_ +* `参数分类 <../../doc/ui/cmd_argument/argument_outline.html>`_ +* `参数描述 <../../doc/ui/cmd_argument/detail_introduction.html>`_ +* `参数用例 <../../doc/ui/cmd_argument/use_case.html>`_ 预测 -- GitLab From 634576128ce3c8075c38dbd7fdf6451ca7885a7f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 24 Nov 2016 12:42:22 +0800 Subject: [PATCH 0127/1503] Done for reviewing docs. --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index 4d29507ca3..a13d4728a9 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -144,17 +144,15 @@ :lines: 42-59 :linenos: -- 双层序列\: - - - 双层RNN中,对输入的两个特征分别求时序上的连续全连接(`inner_step1`和`inner_step2`分别处理fea1和fea2),其功能与示例2中`sequence_nest_rnn.conf`的`outer_step`函数完全相同。不同之处是,此时输入`[SubsequenceInput(emb1), SubsequenceInput(emb2)]`在各时刻并不等长。 - - 函数`outer_step`中可以分别处理这两个特征,但我们需要用\ :red:`targetInlink`\ 指定recurrent_group的输出的格式(各子句长度)只能和其中一个保持一致,如这里选择了和emb2的长度一致。 - - 最后,依然是取encoder1_rep的最后一个时刻和encoder2_rep的所有时刻分别相加得到context。 +而双层序列的代码如下。 .. literalinclude:: ../../../paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py :language: python - :lines: 42-75, 82-89 + :lines: 41-80 :linenos: +在上面代码中,单层和双层序列的使用和示例2中的示例类似,区别是同时处理了两个输入。而对于双层序列,两个输入的子序列长度也并不相同。但是,我们使用了\ :code:`targetInlink`\ 参数设置了外层\ :code:`recurrent_group`\ 的输出格式。所以外层输出的序列形状,和\ :code:`emb2`的序列形状一致。 + 示例4:beam_search的生成 ======================== -- GitLab From dc0dcdac0060a300f74c132d2df6fee90c5d99bd Mon Sep 17 00:00:00 2001 From: zhangjinchao01 Date: Thu, 24 Nov 2016 14:42:43 +0800 Subject: [PATCH 0128/1503] reveise input order and sort bug --- demo/semantic_role_labeling/dataprovider.py | 9 +++++---- demo/semantic_role_labeling/predict.py | 10 +++------- demo/semantic_role_labeling/predict.sh | 2 +- demo/semantic_role_labeling/test.sh | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/demo/semantic_role_labeling/dataprovider.py b/demo/semantic_role_labeling/dataprovider.py index d4c137ef42..2c8e134627 100644 --- a/demo/semantic_role_labeling/dataprovider.py +++ b/demo/semantic_role_labeling/dataprovider.py @@ -25,12 +25,13 @@ def hook(settings, word_dict, label_dict, predicate_dict, **kwargs): #all inputs are integral and sequential type settings.slots = [ integer_value_sequence(len(word_dict)), - integer_value_sequence(len(predicate_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), integer_value_sequence(2), + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(predicate_dict)), + integer_value_sequence(2), integer_value_sequence(len(label_dict)) ] @@ -63,5 +64,5 @@ def process(settings, file_name): label_list = label.split() label_slot = [settings.label_dict.get(w) for w in label_list] - yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot, label_slot + yield word_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, predicate_slot, mark_slot, label_slot diff --git a/demo/semantic_role_labeling/predict.py b/demo/semantic_role_labeling/predict.py index 2761814e18..a7f1e8f81f 100644 --- a/demo/semantic_role_labeling/predict.py +++ b/demo/semantic_role_labeling/predict.py @@ -55,18 +55,14 @@ class Prediction(): slots = [ integer_value_sequence(len_dict), - integer_value_sequence(len_pred), integer_value_sequence(len_dict), integer_value_sequence(len_dict), integer_value_sequence(len_dict), integer_value_sequence(len_dict), integer_value_sequence(len_dict), + integer_value_sequence(len_pred), integer_value_sequence(2) ] - integer_value_sequence(len_dict), integer_value_sequence(len_dict), - integer_value_sequence(len_dict), integer_value_sequence(len_dict), - integer_value_sequence(len_dict), integer_value_sequence(2) - ] self.converter = DataProviderConverter(slots) def load_dict_label(self, dict_file, label_file, predicate_dict_file): @@ -104,8 +100,8 @@ class Prediction(): marks = mark.split() mark_slot = [int(w) for w in marks] - yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot + yield word_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, predicate_slot, mark_slot def predict(self, data_file, output_file): """ diff --git a/demo/semantic_role_labeling/predict.sh b/demo/semantic_role_labeling/predict.sh index d0acdb0bd0..88ab5898f7 100644 --- a/demo/semantic_role_labeling/predict.sh +++ b/demo/semantic_role_labeling/predict.sh @@ -18,7 +18,7 @@ set -e function get_best_pass() { cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ sed -r 'N;s/Test.* cost=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' | \ - sort | head -n 1 + sort -n | head -n 1 } log=train.log diff --git a/demo/semantic_role_labeling/test.sh b/demo/semantic_role_labeling/test.sh index c4ab44f5ca..f9e1bdcd4c 100644 --- a/demo/semantic_role_labeling/test.sh +++ b/demo/semantic_role_labeling/test.sh @@ -18,7 +18,7 @@ set -e function get_best_pass() { cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ sed -r 'N;s/Test.* cost=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' |\ - sort | head -n 1 + sort -n | head -n 1 } log=train.log -- GitLab From e08c4b8ec1d579bf08a644a9f984b5fa1d67b58b Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 24 Nov 2016 16:24:17 +0800 Subject: [PATCH 0129/1503] Revise doc and some scripts for demo. --- demo/image_classification/train.sh | 2 +- demo/sentiment/test.sh | 2 +- doc/demo/quick_start/index_en.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/image_classification/train.sh b/demo/image_classification/train.sh index ed9b5220ff..db0a057bf3 100755 --- a/demo/image_classification/train.sh +++ b/demo/image_classification/train.sh @@ -24,7 +24,7 @@ paddle train \ --test_all_data_in_one_period=1 \ --use_gpu=1 \ --trainer_count=1 \ ---num_passes=200 \ +--num_passes=300 \ --save_dir=$output \ 2>&1 | tee $log diff --git a/demo/sentiment/test.sh b/demo/sentiment/test.sh index 098fbb9138..c8b12a0e89 100755 --- a/demo/sentiment/test.sh +++ b/demo/sentiment/test.sh @@ -17,7 +17,7 @@ set -e function get_best_pass() { cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ sed -r 'N;s/Test.* classification_error_evaluator=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' |\ - sort | head -n 1 + sort -n | head -n 1 } log=train.log diff --git a/doc/demo/quick_start/index_en.md b/doc/demo/quick_start/index_en.md index 80d816a768..659485d9be 100644 --- a/doc/demo/quick_start/index_en.md +++ b/doc/demo/quick_start/index_en.md @@ -477,7 +477,7 @@ The scripts of data downloading, network configurations, and training scrips are Word embedding 15MB 8.484% -trainer_config.bow.py +trainer_config.emb.py -- GitLab From 5590b4a2a8817e8ab3b2f4516e6b6527008b1da7 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 25 Nov 2016 10:34:13 +0800 Subject: [PATCH 0130/1503] Refine hierarchical-layer.rst --- ...chical-layer.md => hierarchical-layer.rst} | 97 ++++++++++++------- 1 file changed, 63 insertions(+), 34 deletions(-) rename doc_cn/algorithm/rnn/{hierarchical-layer.md => hierarchical-layer.rst} (50%) diff --git a/doc_cn/algorithm/rnn/hierarchical-layer.md b/doc_cn/algorithm/rnn/hierarchical-layer.rst similarity index 50% rename from doc_cn/algorithm/rnn/hierarchical-layer.md rename to doc_cn/algorithm/rnn/hierarchical-layer.rst index 519653df08..a9906b8b9c 100644 --- a/doc_cn/algorithm/rnn/hierarchical-layer.md +++ b/doc_cn/algorithm/rnn/hierarchical-layer.rst @@ -1,6 +1,11 @@ -# 支持双层序列作为输入的Layer +########################### +支持双层序列作为输入的Layer +########################### -## 概述 +.. contents:: + +概述 +==== 在自然语言处理任务中,序列是一种常见的数据类型。一个独立的词语,可以看作是一个非序列输入,或者,我们称之为一个0层的序列;由词语构成的句子,是一个单层序列;若干个句子构成一个段落,是一个双层的序列。 @@ -12,55 +17,79 @@ + 单层序列:排成一列的多个元素,每个元素是一个0层序列,元素之间的顺序是重要的输入信息 + 双层序列:排成一列的多个元素,每个元素是一个单层序列,称之为双层序列的一个子序列(subseq),subseq的每个元素是一个0层序列 - 在 PaddlePaddle中,下面这些Layer能够接受双层序列作为输入,完成相应的计算。 -## pooling_layer - -pooling_layer的使用示例如下,详细见配置API。 -```python -seq_pool = pooling_layer(input=layer, - pooling_type=AvgPooling(), - agg_level=AggregateLevel.EACH_SEQUENCE) -``` + +pooling_layer +============== + +pooling_layer 的使用示例如下,详细见 `pooling_layer`_ 配置API。 + +.. code-block:: bash + + seq_pool = pooling_layer(input=layer, + pooling_type=AvgPooling(), + agg_level=AggregateLevel.EACH_SEQUENCE) + - `pooling_type` 目前支持两种,分别是:MaxPooling()和AvgPooling()。 -- `agg_level=AggregateLevel.TIMESTEP`时(默认值): + +- `agg_level=AggregateLevel.TIMESTEP` 时(默认值): + - 作用:双层序列经过运算变成一个0层序列,或单层序列经过运算变成一个0层序列 - 输入:一个双层序列,或一个单层序列 - 输出:一个0层序列,即整个输入序列(单层或双层)的平均值(或最大值) -- `agg_level=AggregateLevel.EACH_SEQUENCE`时: + +- `agg_level=AggregateLevel.EACH_SEQUENCE` 时: + - 作用:一个双层序列经过运算变成一个单层序列 - 输入:必须是一个双层序列 - 输出:一个单层序列,序列的每个元素是原来双层序列每个subseq元素的平均值(或最大值) -## last_seq 和 first_seq +last_seq 和 first_seq +===================== + +last_seq 的使用示例如下( `first_seq`_ 类似),详细见 `last_seq`_ 配置API。 + +.. code-block:: bash + + last = last_seq(input=layer, + agg_level=AggregateLevel.EACH_SEQUENCE) + +- `agg_level=AggregateLevel.TIMESTEP` 时(默认值): -last_seq的使用示例如下(first_seq类似),详细见配置API。 -```python -last = last_seq(input=layer, - agg_level=AggregateLevel.EACH_SEQUENCE) -``` -- `agg_level=AggregateLevel.TIMESTEP`时(默认值): - 作用:一个双层序列经过运算变成一个0层序列,或一个单层序列经过运算变成一个0层序列 - 输入:一个双层序列或一个单层序列 - 输出:一个0层序列,即整个输入序列(双层或者单层)最后一个,或第一个元素。 -- `agg_level=AggregateLevel.EACH_SEQUENCE`时: + +- `agg_level=AggregateLevel.EACH_SEQUENCE` 时: - 作用:一个双层序列经过运算变成一个单层序列 - 输入:必须是一个双层序列 - 输出:一个单层序列,其中每个元素是双层序列中每个subseq最后一个(或第一个)元素。 -## expand_layer +expand_layer +============ + +expand_layer 的使用示例如下,详细见 `expand_layer`_ 配置API。 + +.. code-block:: bash + + expand = expand_layer(input=layer1, + expand_as=layer2, + expand_level=ExpandLevel.FROM_TIMESTEP) + +- `expand_level=ExpandLevel.FROM_TIMESTEP` 时(默认值): -expand_layer的使用示例如下,详细见配置API。 -```python -expand = expand_layer(input=layer1, - expand_as=layer2, - expand_level=ExpandLevel.FROM_TIMESTEP) -``` -- `expand_level=ExpandLevel.FROM_TIMESTEP`时(默认值): - 作用:一个0层序列经过运算扩展成一个单层序列,或者一个双层序列 - - 输入:layer1必须是一个0层序列,是待扩展的数据;layer2可以是一个单层序列,或者是一个双层序列,提供扩展的长度信息 - - 输出:一个单层序列,或一个双层序列,输出序列的类型(双层序列,或单层序列)和序列中含有元素的数目同 layer2一致。若输出是单层序列,单层序列的每个元素(0层序列),都是对layer1元素的拷贝;若输出是双层序列,双层序列每个subseq中每个元素(0层序列),都是对layer1元素的拷贝 -- `expand_level=ExpandLevel.FROM_SEQUENCE`时: + - 输入:layer1必须是一个0层序列,是待扩展的数据;layer2 可以是一个单层序列,或者是一个双层序列,提供扩展的长度信息 + - 输出:一个单层序列或一个双层序列,输出序列的类型(双层序列或单层序列)和序列中含有元素的数目同 layer2 一致。若输出是单层序列,单层序列的每个元素(0层序列),都是对layer1元素的拷贝;若输出是双层序列,双层序列每个subseq中每个元素(0层序列),都是对layer1元素的拷贝 + +- `expand_level=ExpandLevel.FROM_SEQUENCE` 时: + - 作用:一个单层序列经过运算扩展成一个双层序列 - - 输入:layer1必须是一个单层序列,是待扩展的数据;layer2必须是一个双层序列,提供扩展的长度信息 - - 输出:一个双层序列,序列中含有元素的数目同layer2一致。要求单层序列含有元素的数目(0层序列),和双层序列含有subseq 的数目一致。单层序列第i个元素(0层序列),被扩展为一个单层序列,构成了输出双层序列的第i个subseq。 + - 输入:layer1必须是一个单层序列,是待扩展的数据;layer2 必须是一个双层序列,提供扩展的长度信息 + - 输出:一个双层序列,序列中含有元素的数目同 layer2 一致。要求单层序列含有元素的数目(0层序列)和双层序列含有subseq 的数目一致。单层序列第i个元素(0层序列),被扩展为一个单层序列,构成了输出双层序列的第i个 subseq 。 + + +.. _pooling_layer: ../../../doc/ui/api/trainer_config_helpers/layers.html#pooling-layer +.. _last_seq: ../../../doc/ui/api/trainer_config_helpers/layers.html#last-seq +.. _first_seq: ../../../doc/ui/api/trainer_config_helpers/layers.html#first-seq +.. _expand_layer: ../../../doc/ui/api/trainer_config_helpers/layers.html#expand-layer -- GitLab From b3f4e535398c0e9eeaca6846c5d890525f2ed60e Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 25 Nov 2016 15:45:24 +0800 Subject: [PATCH 0131/1503] fix LogActivation is not defined --- python/paddle/trainer_config_helpers/activations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/paddle/trainer_config_helpers/activations.py b/python/paddle/trainer_config_helpers/activations.py index 6261934e1b..eeed18a98a 100644 --- a/python/paddle/trainer_config_helpers/activations.py +++ b/python/paddle/trainer_config_helpers/activations.py @@ -16,7 +16,8 @@ __all__ = [ "TanhActivation", "SigmoidActivation", "SoftmaxActivation", "IdentityActivation", "LinearActivation", 'SequenceSoftmaxActivation', 'ExpActivation', "ReluActivation", "BReluActivation", "SoftReluActivation", - "STanhActivation", "AbsActivation", "SquareActivation", "BaseActivation" + "STanhActivation", "AbsActivation", "SquareActivation", "BaseActivation", + "LogActivation" ] -- GitLab From 64f4e547ae7ee332aae6496edb4eafc051979597 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 17:12:18 +0800 Subject: [PATCH 0132/1503] Refine eng docs structure --- doc/about/index.rst | 10 + doc/algorithm/index.rst | 7 - doc/algorithm/rnn/bi_lstm.jpg | 1 - .../rnn/encoder-decoder-attention-model.png | 1 - doc/{ui => api}/data_provider/index.rst | 0 .../data_provider/pydataprovider2.rst | 0 doc/api/index.md | 14 + doc/{ui => api}/predict/predict_sample.py | 0 doc/{ui => api}/predict/swig_py_paddle_en.rst | 0 .../trainer_config_helpers/activations.rst | 0 .../api/trainer_config_helpers/attrs.rst | 0 .../trainer_config_helpers/data_sources.rst | 0 .../api/trainer_config_helpers/evaluators.rst | 0 .../api/trainer_config_helpers/index.rst | 0 .../api/trainer_config_helpers/layers.rst | 0 .../api/trainer_config_helpers/networks.rst | 0 .../api/trainer_config_helpers/optimizers.rst | 0 .../api/trainer_config_helpers/poolings.rst | 0 doc/cluster/index.rst | 8 - doc/dev/layer.md | 4 - doc/howto/algorithm/index.rst | 7 + doc/{ => howto}/algorithm/rnn/rnn.rst | 0 .../cluster}/cluster_train.md | 2 +- .../cmd_argument/argument_outline.md | 0 .../cmd_argument/detail_introduction.md | 0 doc/howto/cmd_argument/index.md | 5 + doc/{ui => howto}/cmd_argument/use_case.md | 0 doc/{build => howto}/contribute_to_paddle.md | 0 doc/{ => howto}/dev/index.rst | 4 +- doc/howto/dev/layer.md | 5 + .../dev/new_layer/FullyConnected.jpg | Bin doc/{ => howto}/dev/new_layer/new_layer.rst | 0 doc/{ => howto/dev}/source/api.rst | 0 doc/{ => howto/dev}/source/cuda/index.rst | 0 doc/{ => howto/dev}/source/cuda/matrix.rst | 0 doc/{ => howto/dev}/source/cuda/nn.rst | 0 doc/{ => howto/dev}/source/cuda/utils.rst | 0 .../dev}/source/gserver/activations.rst | 0 .../dev}/source/gserver/dataproviders.rst | 0 .../dev}/source/gserver/evaluators.rst | 0 .../dev}/source/gserver/gradientmachines.rst | 0 doc/{ => howto/dev}/source/gserver/index.rst | 0 doc/{ => howto/dev}/source/gserver/layers.rst | 0 .../dev}/source/gserver/neworks.rst | 0 doc/{ => howto/dev}/source/index.rst | 0 doc/{ => howto/dev}/source/math/functions.rst | 0 doc/{ => howto/dev}/source/math/index.rst | 0 doc/{ => howto/dev}/source/math/matrix.rst | 0 doc/{ => howto/dev}/source/math/utils.rst | 0 doc/{ => howto/dev}/source/math/vector.rst | 0 .../dev}/source/parameter/index.rst | 0 .../dev}/source/parameter/optimizer.rst | 0 .../dev}/source/parameter/parameter.rst | 0 .../dev}/source/parameter/updater.rst | 0 doc/{ => howto/dev}/source/pserver/client.rst | 0 doc/{ => howto/dev}/source/pserver/index.rst | 0 .../dev}/source/pserver/network.rst | 0 doc/{ => howto/dev}/source/pserver/server.rst | 0 doc/{ => howto/dev}/source/trainer.rst | 0 .../dev}/source/utils/customStackTrace.rst | 0 doc/{ => howto/dev}/source/utils/enum.rst | 0 doc/{ => howto/dev}/source/utils/index.rst | 0 doc/{ => howto/dev}/source/utils/lock.rst | 0 doc/{ => howto/dev}/source/utils/queue.rst | 0 doc/{ => howto/dev}/source/utils/thread.rst | 0 doc/howto/index.rst | 11 + doc/index.rst | 10 +- doc/introduction/basic_usage/basic_usage.rst | 109 +++++ doc/introduction/basic_usage/parameters.png | Bin 0 -> 44469 bytes .../build_and_install}/build_from_source.md | 0 .../build_and_install}/cmake.png | Bin .../build_and_install}/docker_install.rst | 0 .../build_and_install}/index.rst | 5 +- .../build_and_install}/ubuntu_install.rst | 0 doc/introduction/index.md | 100 ----- doc/introduction/index.rst | 8 + doc/introduction/parameters.png | 1 - .../embedding_model/index.md | 0 .../embedding_model/neural-n-gram-model.png | Bin .../image_classification/cifar.png | Bin .../image_classification.md | 0 .../image_classification.png | Bin .../image_classification/index.rst | 0 .../image_classification/lenet.png | Bin .../image_classification/plot.png | Bin .../imagenet_model/resnet_block.jpg | Bin .../imagenet_model/resnet_model.md | 0 doc/{demo => tutorials}/index.md | 2 +- .../quick_start/NetContinuous_en.png | Bin .../quick_start/NetConv_en.png | Bin .../quick_start/NetLR_en.png | Bin .../quick_start/NetRNN_en.png | Bin .../quick_start/PipelineNetwork_en.jpg | Bin .../quick_start/PipelineTest_en.png | Bin .../quick_start/PipelineTrain_en.png | Bin .../quick_start/Pipeline_en.jpg | Bin .../quick_start/index_en.md | 0 doc/{demo => tutorials}/rec/ml_dataset.md | 0 doc/{demo => tutorials}/rec/ml_regression.rst | 0 .../rec/rec_regression_network.png | Bin .../semantic_role_labeling/curve.jpg | Bin .../semantic_role_labeling/feature.jpg | Bin .../semantic_role_labeling/index.rst | 0 .../semantic_role_labeling/network_arch.png | Bin .../semantic_role_labeling.md | 400 +++++++++--------- .../sentiment_analysis/bi_lstm.jpg | Bin .../sentiment_analysis/index.rst | 0 .../sentiment_analysis/lstm.png | Bin .../sentiment_analysis/sentiment_analysis.md | 0 .../sentiment_analysis/stacked_lstm.jpg | Bin .../encoder-decoder-attention-model.png | Bin .../text_generation/index.rst | 0 .../text_generation/text_generation.md | 0 doc/ui/index.md | 20 - doc/user_guide.rst | 13 - 115 files changed, 380 insertions(+), 367 deletions(-) create mode 100644 doc/about/index.rst delete mode 100644 doc/algorithm/index.rst delete mode 120000 doc/algorithm/rnn/bi_lstm.jpg delete mode 120000 doc/algorithm/rnn/encoder-decoder-attention-model.png rename doc/{ui => api}/data_provider/index.rst (100%) rename doc/{ui => api}/data_provider/pydataprovider2.rst (100%) create mode 100644 doc/api/index.md rename doc/{ui => api}/predict/predict_sample.py (100%) rename doc/{ui => api}/predict/swig_py_paddle_en.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/activations.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/attrs.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/data_sources.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/evaluators.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/index.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/layers.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/networks.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/optimizers.rst (100%) rename doc/{ui => }/api/trainer_config_helpers/poolings.rst (100%) delete mode 100644 doc/cluster/index.rst delete mode 100644 doc/dev/layer.md create mode 100644 doc/howto/algorithm/index.rst rename doc/{ => howto}/algorithm/rnn/rnn.rst (100%) rename doc/{cluster/opensource => howto/cluster}/cluster_train.md (99%) rename doc/{ui => howto}/cmd_argument/argument_outline.md (100%) rename doc/{ui => howto}/cmd_argument/detail_introduction.md (100%) create mode 100644 doc/howto/cmd_argument/index.md rename doc/{ui => howto}/cmd_argument/use_case.md (100%) rename doc/{build => howto}/contribute_to_paddle.md (100%) rename doc/{ => howto}/dev/index.rst (71%) create mode 100644 doc/howto/dev/layer.md rename doc/{ => howto}/dev/new_layer/FullyConnected.jpg (100%) rename doc/{ => howto}/dev/new_layer/new_layer.rst (100%) rename doc/{ => howto/dev}/source/api.rst (100%) rename doc/{ => howto/dev}/source/cuda/index.rst (100%) rename doc/{ => howto/dev}/source/cuda/matrix.rst (100%) rename doc/{ => howto/dev}/source/cuda/nn.rst (100%) rename doc/{ => howto/dev}/source/cuda/utils.rst (100%) rename doc/{ => howto/dev}/source/gserver/activations.rst (100%) rename doc/{ => howto/dev}/source/gserver/dataproviders.rst (100%) rename doc/{ => howto/dev}/source/gserver/evaluators.rst (100%) rename doc/{ => howto/dev}/source/gserver/gradientmachines.rst (100%) rename doc/{ => howto/dev}/source/gserver/index.rst (100%) rename doc/{ => howto/dev}/source/gserver/layers.rst (100%) rename doc/{ => howto/dev}/source/gserver/neworks.rst (100%) rename doc/{ => howto/dev}/source/index.rst (100%) rename doc/{ => howto/dev}/source/math/functions.rst (100%) rename doc/{ => howto/dev}/source/math/index.rst (100%) rename doc/{ => howto/dev}/source/math/matrix.rst (100%) rename doc/{ => howto/dev}/source/math/utils.rst (100%) rename doc/{ => howto/dev}/source/math/vector.rst (100%) rename doc/{ => howto/dev}/source/parameter/index.rst (100%) rename doc/{ => howto/dev}/source/parameter/optimizer.rst (100%) rename doc/{ => howto/dev}/source/parameter/parameter.rst (100%) rename doc/{ => howto/dev}/source/parameter/updater.rst (100%) rename doc/{ => howto/dev}/source/pserver/client.rst (100%) rename doc/{ => howto/dev}/source/pserver/index.rst (100%) rename doc/{ => howto/dev}/source/pserver/network.rst (100%) rename doc/{ => howto/dev}/source/pserver/server.rst (100%) rename doc/{ => howto/dev}/source/trainer.rst (100%) rename doc/{ => howto/dev}/source/utils/customStackTrace.rst (100%) rename doc/{ => howto/dev}/source/utils/enum.rst (100%) rename doc/{ => howto/dev}/source/utils/index.rst (100%) rename doc/{ => howto/dev}/source/utils/lock.rst (100%) rename doc/{ => howto/dev}/source/utils/queue.rst (100%) rename doc/{ => howto/dev}/source/utils/thread.rst (100%) create mode 100644 doc/howto/index.rst create mode 100644 doc/introduction/basic_usage/basic_usage.rst create mode 100644 doc/introduction/basic_usage/parameters.png rename doc/{build => introduction/build_and_install}/build_from_source.md (100%) rename doc/{build => introduction/build_and_install}/cmake.png (100%) rename doc/{build => introduction/build_and_install}/docker_install.rst (100%) rename doc/{build => introduction/build_and_install}/index.rst (80%) rename doc/{build => introduction/build_and_install}/ubuntu_install.rst (100%) delete mode 100644 doc/introduction/index.md create mode 100644 doc/introduction/index.rst delete mode 120000 doc/introduction/parameters.png rename doc/{demo => tutorials}/embedding_model/index.md (100%) rename doc/{demo => tutorials}/embedding_model/neural-n-gram-model.png (100%) rename doc/{demo => tutorials}/image_classification/cifar.png (100%) rename doc/{demo => tutorials}/image_classification/image_classification.md (100%) rename doc/{demo => tutorials}/image_classification/image_classification.png (100%) rename doc/{demo => tutorials}/image_classification/index.rst (100%) rename doc/{demo => tutorials}/image_classification/lenet.png (100%) rename doc/{demo => tutorials}/image_classification/plot.png (100%) rename doc/{demo => tutorials}/imagenet_model/resnet_block.jpg (100%) rename doc/{demo => tutorials}/imagenet_model/resnet_model.md (100%) rename doc/{demo => tutorials}/index.md (96%) rename doc/{demo => tutorials}/quick_start/NetContinuous_en.png (100%) rename doc/{demo => tutorials}/quick_start/NetConv_en.png (100%) rename doc/{demo => tutorials}/quick_start/NetLR_en.png (100%) rename doc/{demo => tutorials}/quick_start/NetRNN_en.png (100%) rename doc/{demo => tutorials}/quick_start/PipelineNetwork_en.jpg (100%) rename doc/{demo => tutorials}/quick_start/PipelineTest_en.png (100%) rename doc/{demo => tutorials}/quick_start/PipelineTrain_en.png (100%) rename doc/{demo => tutorials}/quick_start/Pipeline_en.jpg (100%) rename doc/{demo => tutorials}/quick_start/index_en.md (100%) rename doc/{demo => tutorials}/rec/ml_dataset.md (100%) rename doc/{demo => tutorials}/rec/ml_regression.rst (100%) rename doc/{demo => tutorials}/rec/rec_regression_network.png (100%) rename doc/{demo => tutorials}/semantic_role_labeling/curve.jpg (100%) rename doc/{demo => tutorials}/semantic_role_labeling/feature.jpg (100%) rename doc/{demo => tutorials}/semantic_role_labeling/index.rst (100%) rename doc/{demo => tutorials}/semantic_role_labeling/network_arch.png (100%) rename doc/{demo => tutorials}/semantic_role_labeling/semantic_role_labeling.md (97%) rename doc/{demo => tutorials}/sentiment_analysis/bi_lstm.jpg (100%) rename doc/{demo => tutorials}/sentiment_analysis/index.rst (100%) rename doc/{demo => tutorials}/sentiment_analysis/lstm.png (100%) rename doc/{demo => tutorials}/sentiment_analysis/sentiment_analysis.md (100%) rename doc/{demo => tutorials}/sentiment_analysis/stacked_lstm.jpg (100%) rename doc/{demo => tutorials}/text_generation/encoder-decoder-attention-model.png (100%) rename doc/{demo => tutorials}/text_generation/index.rst (100%) rename doc/{demo => tutorials}/text_generation/text_generation.md (100%) delete mode 100644 doc/ui/index.md delete mode 100644 doc/user_guide.rst diff --git a/doc/about/index.rst b/doc/about/index.rst new file mode 100644 index 0000000000..c70940ca85 --- /dev/null +++ b/doc/about/index.rst @@ -0,0 +1,10 @@ +Credits +======== + +PaddlPaddle is an easy-to-use, efficient, flexible and scalable deep learning platform, +which is originally developed by Baidu scientists and engineers for the purpose of applying deep learning to many products at Baidu. + +PaddlePaddle is now open source but far from complete, which is intended to be built upon, improved, scaled, and extended. +We hope to build an active open source community both by providing feedback and by actively contributing to the source code. + +We owe many thanks to `all contributors and developers `_ of PaddlePaddle! diff --git a/doc/algorithm/index.rst b/doc/algorithm/index.rst deleted file mode 100644 index 6073add3c0..0000000000 --- a/doc/algorithm/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Algorithm Tutorial -================== - -.. toctree:: - :maxdepth: 1 - - rnn/rnn.rst diff --git a/doc/algorithm/rnn/bi_lstm.jpg b/doc/algorithm/rnn/bi_lstm.jpg deleted file mode 120000 index a53296cf80..0000000000 --- a/doc/algorithm/rnn/bi_lstm.jpg +++ /dev/null @@ -1 +0,0 @@ -../../demo/sentiment_analysis/bi_lstm.jpg \ No newline at end of file diff --git a/doc/algorithm/rnn/encoder-decoder-attention-model.png b/doc/algorithm/rnn/encoder-decoder-attention-model.png deleted file mode 120000 index db71321a43..0000000000 --- a/doc/algorithm/rnn/encoder-decoder-attention-model.png +++ /dev/null @@ -1 +0,0 @@ -../../demo/text_generation/encoder-decoder-attention-model.png \ No newline at end of file diff --git a/doc/ui/data_provider/index.rst b/doc/api/data_provider/index.rst similarity index 100% rename from doc/ui/data_provider/index.rst rename to doc/api/data_provider/index.rst diff --git a/doc/ui/data_provider/pydataprovider2.rst b/doc/api/data_provider/pydataprovider2.rst similarity index 100% rename from doc/ui/data_provider/pydataprovider2.rst rename to doc/api/data_provider/pydataprovider2.rst diff --git a/doc/api/index.md b/doc/api/index.md new file mode 100644 index 0000000000..8c4a65e0d5 --- /dev/null +++ b/doc/api/index.md @@ -0,0 +1,14 @@ +# API + +## Data Provider + +* [Introduction](data_provider/index.rst) +* [PyDataProvider2](data_provider/pydataprovider2.rst) + +## Trainer Configuration + +* [Model Config Interface](trainer_config_helpers/index.rst) + +## Predict + +* [Python Prediction API](predict/swig_py_paddle_en.rst) diff --git a/doc/ui/predict/predict_sample.py b/doc/api/predict/predict_sample.py similarity index 100% rename from doc/ui/predict/predict_sample.py rename to doc/api/predict/predict_sample.py diff --git a/doc/ui/predict/swig_py_paddle_en.rst b/doc/api/predict/swig_py_paddle_en.rst similarity index 100% rename from doc/ui/predict/swig_py_paddle_en.rst rename to doc/api/predict/swig_py_paddle_en.rst diff --git a/doc/ui/api/trainer_config_helpers/activations.rst b/doc/api/trainer_config_helpers/activations.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/activations.rst rename to doc/api/trainer_config_helpers/activations.rst diff --git a/doc/ui/api/trainer_config_helpers/attrs.rst b/doc/api/trainer_config_helpers/attrs.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/attrs.rst rename to doc/api/trainer_config_helpers/attrs.rst diff --git a/doc/ui/api/trainer_config_helpers/data_sources.rst b/doc/api/trainer_config_helpers/data_sources.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/data_sources.rst rename to doc/api/trainer_config_helpers/data_sources.rst diff --git a/doc/ui/api/trainer_config_helpers/evaluators.rst b/doc/api/trainer_config_helpers/evaluators.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/evaluators.rst rename to doc/api/trainer_config_helpers/evaluators.rst diff --git a/doc/ui/api/trainer_config_helpers/index.rst b/doc/api/trainer_config_helpers/index.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/index.rst rename to doc/api/trainer_config_helpers/index.rst diff --git a/doc/ui/api/trainer_config_helpers/layers.rst b/doc/api/trainer_config_helpers/layers.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/layers.rst rename to doc/api/trainer_config_helpers/layers.rst diff --git a/doc/ui/api/trainer_config_helpers/networks.rst b/doc/api/trainer_config_helpers/networks.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/networks.rst rename to doc/api/trainer_config_helpers/networks.rst diff --git a/doc/ui/api/trainer_config_helpers/optimizers.rst b/doc/api/trainer_config_helpers/optimizers.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/optimizers.rst rename to doc/api/trainer_config_helpers/optimizers.rst diff --git a/doc/ui/api/trainer_config_helpers/poolings.rst b/doc/api/trainer_config_helpers/poolings.rst similarity index 100% rename from doc/ui/api/trainer_config_helpers/poolings.rst rename to doc/api/trainer_config_helpers/poolings.rst diff --git a/doc/cluster/index.rst b/doc/cluster/index.rst deleted file mode 100644 index 9062f85f98..0000000000 --- a/doc/cluster/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Cluster Train -==================== - -.. toctree:: - :glob: - - opensource/cluster_train.md - internal/index.md diff --git a/doc/dev/layer.md b/doc/dev/layer.md deleted file mode 100644 index 930fb0de1a..0000000000 --- a/doc/dev/layer.md +++ /dev/null @@ -1,4 +0,0 @@ -# Layer Documents - -* [Layer Source Code Document](../source/gserver/layers/index.rst) -* [Layer Python API Document](../ui/api/trainer_config_helpers/index.rst) diff --git a/doc/howto/algorithm/index.rst b/doc/howto/algorithm/index.rst new file mode 100644 index 0000000000..b4ecbc4847 --- /dev/null +++ b/doc/howto/algorithm/index.rst @@ -0,0 +1,7 @@ +Algorithm Configuration +======================= + +.. toctree:: + :maxdepth: 1 + + rnn/rnn.rst diff --git a/doc/algorithm/rnn/rnn.rst b/doc/howto/algorithm/rnn/rnn.rst similarity index 100% rename from doc/algorithm/rnn/rnn.rst rename to doc/howto/algorithm/rnn/rnn.rst diff --git a/doc/cluster/opensource/cluster_train.md b/doc/howto/cluster/cluster_train.md similarity index 99% rename from doc/cluster/opensource/cluster_train.md rename to doc/howto/cluster/cluster_train.md index cb493a88f0..6b68596dc1 100644 --- a/doc/cluster/opensource/cluster_train.md +++ b/doc/howto/cluster/cluster_train.md @@ -9,7 +9,7 @@ In this article, we explain how to run distributed Paddle training jobs on clust 1. Aforementioned scripts use a Python library [fabric](http://www.fabfile.org/) to run SSH commands. We can use `pip` to install fabric: ```bash -pip install fabric + pip install fabric ``` 1. We need to install PaddlePaddle on all nodes in the cluster. To enable GPUs, we need to install CUDA in `/usr/local/cuda`; otherwise Paddle would report errors at runtime. diff --git a/doc/ui/cmd_argument/argument_outline.md b/doc/howto/cmd_argument/argument_outline.md similarity index 100% rename from doc/ui/cmd_argument/argument_outline.md rename to doc/howto/cmd_argument/argument_outline.md diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/howto/cmd_argument/detail_introduction.md similarity index 100% rename from doc/ui/cmd_argument/detail_introduction.md rename to doc/howto/cmd_argument/detail_introduction.md diff --git a/doc/howto/cmd_argument/index.md b/doc/howto/cmd_argument/index.md new file mode 100644 index 0000000000..90472c44cb --- /dev/null +++ b/doc/howto/cmd_argument/index.md @@ -0,0 +1,5 @@ +# Command Line Argument + +* [Use Case](use_case.md) +* [Argument Outline](argument_outline.md) +* [Detailed Descriptions](detail_introduction.md) diff --git a/doc/ui/cmd_argument/use_case.md b/doc/howto/cmd_argument/use_case.md similarity index 100% rename from doc/ui/cmd_argument/use_case.md rename to doc/howto/cmd_argument/use_case.md diff --git a/doc/build/contribute_to_paddle.md b/doc/howto/contribute_to_paddle.md similarity index 100% rename from doc/build/contribute_to_paddle.md rename to doc/howto/contribute_to_paddle.md diff --git a/doc/dev/index.rst b/doc/howto/dev/index.rst similarity index 71% rename from doc/dev/index.rst rename to doc/howto/dev/index.rst index 0468dd492b..876c42e9db 100644 --- a/doc/dev/index.rst +++ b/doc/howto/dev/index.rst @@ -2,8 +2,8 @@ Development Guide ================= .. toctree:: - :maxdepth: 1 + :maxdepth: 2 layer.md new_layer/new_layer.rst - ../source/index.md + source/index.rst diff --git a/doc/howto/dev/layer.md b/doc/howto/dev/layer.md new file mode 100644 index 0000000000..1ce0cc5829 --- /dev/null +++ b/doc/howto/dev/layer.md @@ -0,0 +1,5 @@ +# Layer Documents + +* [Layer Python API](../../api/trainer_config_helpers/index.rst) +* [Layer Source Code](source/gserver/layers.rst) +* [Writing New Layers](new_layer/new_layer.rst) diff --git a/doc/dev/new_layer/FullyConnected.jpg b/doc/howto/dev/new_layer/FullyConnected.jpg similarity index 100% rename from doc/dev/new_layer/FullyConnected.jpg rename to doc/howto/dev/new_layer/FullyConnected.jpg diff --git a/doc/dev/new_layer/new_layer.rst b/doc/howto/dev/new_layer/new_layer.rst similarity index 100% rename from doc/dev/new_layer/new_layer.rst rename to doc/howto/dev/new_layer/new_layer.rst diff --git a/doc/source/api.rst b/doc/howto/dev/source/api.rst similarity index 100% rename from doc/source/api.rst rename to doc/howto/dev/source/api.rst diff --git a/doc/source/cuda/index.rst b/doc/howto/dev/source/cuda/index.rst similarity index 100% rename from doc/source/cuda/index.rst rename to doc/howto/dev/source/cuda/index.rst diff --git a/doc/source/cuda/matrix.rst b/doc/howto/dev/source/cuda/matrix.rst similarity index 100% rename from doc/source/cuda/matrix.rst rename to doc/howto/dev/source/cuda/matrix.rst diff --git a/doc/source/cuda/nn.rst b/doc/howto/dev/source/cuda/nn.rst similarity index 100% rename from doc/source/cuda/nn.rst rename to doc/howto/dev/source/cuda/nn.rst diff --git a/doc/source/cuda/utils.rst b/doc/howto/dev/source/cuda/utils.rst similarity index 100% rename from doc/source/cuda/utils.rst rename to doc/howto/dev/source/cuda/utils.rst diff --git a/doc/source/gserver/activations.rst b/doc/howto/dev/source/gserver/activations.rst similarity index 100% rename from doc/source/gserver/activations.rst rename to doc/howto/dev/source/gserver/activations.rst diff --git a/doc/source/gserver/dataproviders.rst b/doc/howto/dev/source/gserver/dataproviders.rst similarity index 100% rename from doc/source/gserver/dataproviders.rst rename to doc/howto/dev/source/gserver/dataproviders.rst diff --git a/doc/source/gserver/evaluators.rst b/doc/howto/dev/source/gserver/evaluators.rst similarity index 100% rename from doc/source/gserver/evaluators.rst rename to doc/howto/dev/source/gserver/evaluators.rst diff --git a/doc/source/gserver/gradientmachines.rst b/doc/howto/dev/source/gserver/gradientmachines.rst similarity index 100% rename from doc/source/gserver/gradientmachines.rst rename to doc/howto/dev/source/gserver/gradientmachines.rst diff --git a/doc/source/gserver/index.rst b/doc/howto/dev/source/gserver/index.rst similarity index 100% rename from doc/source/gserver/index.rst rename to doc/howto/dev/source/gserver/index.rst diff --git a/doc/source/gserver/layers.rst b/doc/howto/dev/source/gserver/layers.rst similarity index 100% rename from doc/source/gserver/layers.rst rename to doc/howto/dev/source/gserver/layers.rst diff --git a/doc/source/gserver/neworks.rst b/doc/howto/dev/source/gserver/neworks.rst similarity index 100% rename from doc/source/gserver/neworks.rst rename to doc/howto/dev/source/gserver/neworks.rst diff --git a/doc/source/index.rst b/doc/howto/dev/source/index.rst similarity index 100% rename from doc/source/index.rst rename to doc/howto/dev/source/index.rst diff --git a/doc/source/math/functions.rst b/doc/howto/dev/source/math/functions.rst similarity index 100% rename from doc/source/math/functions.rst rename to doc/howto/dev/source/math/functions.rst diff --git a/doc/source/math/index.rst b/doc/howto/dev/source/math/index.rst similarity index 100% rename from doc/source/math/index.rst rename to doc/howto/dev/source/math/index.rst diff --git a/doc/source/math/matrix.rst b/doc/howto/dev/source/math/matrix.rst similarity index 100% rename from doc/source/math/matrix.rst rename to doc/howto/dev/source/math/matrix.rst diff --git a/doc/source/math/utils.rst b/doc/howto/dev/source/math/utils.rst similarity index 100% rename from doc/source/math/utils.rst rename to doc/howto/dev/source/math/utils.rst diff --git a/doc/source/math/vector.rst b/doc/howto/dev/source/math/vector.rst similarity index 100% rename from doc/source/math/vector.rst rename to doc/howto/dev/source/math/vector.rst diff --git a/doc/source/parameter/index.rst b/doc/howto/dev/source/parameter/index.rst similarity index 100% rename from doc/source/parameter/index.rst rename to doc/howto/dev/source/parameter/index.rst diff --git a/doc/source/parameter/optimizer.rst b/doc/howto/dev/source/parameter/optimizer.rst similarity index 100% rename from doc/source/parameter/optimizer.rst rename to doc/howto/dev/source/parameter/optimizer.rst diff --git a/doc/source/parameter/parameter.rst b/doc/howto/dev/source/parameter/parameter.rst similarity index 100% rename from doc/source/parameter/parameter.rst rename to doc/howto/dev/source/parameter/parameter.rst diff --git a/doc/source/parameter/updater.rst b/doc/howto/dev/source/parameter/updater.rst similarity index 100% rename from doc/source/parameter/updater.rst rename to doc/howto/dev/source/parameter/updater.rst diff --git a/doc/source/pserver/client.rst b/doc/howto/dev/source/pserver/client.rst similarity index 100% rename from doc/source/pserver/client.rst rename to doc/howto/dev/source/pserver/client.rst diff --git a/doc/source/pserver/index.rst b/doc/howto/dev/source/pserver/index.rst similarity index 100% rename from doc/source/pserver/index.rst rename to doc/howto/dev/source/pserver/index.rst diff --git a/doc/source/pserver/network.rst b/doc/howto/dev/source/pserver/network.rst similarity index 100% rename from doc/source/pserver/network.rst rename to doc/howto/dev/source/pserver/network.rst diff --git a/doc/source/pserver/server.rst b/doc/howto/dev/source/pserver/server.rst similarity index 100% rename from doc/source/pserver/server.rst rename to doc/howto/dev/source/pserver/server.rst diff --git a/doc/source/trainer.rst b/doc/howto/dev/source/trainer.rst similarity index 100% rename from doc/source/trainer.rst rename to doc/howto/dev/source/trainer.rst diff --git a/doc/source/utils/customStackTrace.rst b/doc/howto/dev/source/utils/customStackTrace.rst similarity index 100% rename from doc/source/utils/customStackTrace.rst rename to doc/howto/dev/source/utils/customStackTrace.rst diff --git a/doc/source/utils/enum.rst b/doc/howto/dev/source/utils/enum.rst similarity index 100% rename from doc/source/utils/enum.rst rename to doc/howto/dev/source/utils/enum.rst diff --git a/doc/source/utils/index.rst b/doc/howto/dev/source/utils/index.rst similarity index 100% rename from doc/source/utils/index.rst rename to doc/howto/dev/source/utils/index.rst diff --git a/doc/source/utils/lock.rst b/doc/howto/dev/source/utils/lock.rst similarity index 100% rename from doc/source/utils/lock.rst rename to doc/howto/dev/source/utils/lock.rst diff --git a/doc/source/utils/queue.rst b/doc/howto/dev/source/utils/queue.rst similarity index 100% rename from doc/source/utils/queue.rst rename to doc/howto/dev/source/utils/queue.rst diff --git a/doc/source/utils/thread.rst b/doc/howto/dev/source/utils/thread.rst similarity index 100% rename from doc/source/utils/thread.rst rename to doc/howto/dev/source/utils/thread.rst diff --git a/doc/howto/index.rst b/doc/howto/index.rst new file mode 100644 index 0000000000..e2d688e186 --- /dev/null +++ b/doc/howto/index.rst @@ -0,0 +1,11 @@ +How to +======= + +.. toctree:: + :maxdepth: 1 + + cmd_argument/index.md + cluster/cluster_train.md + algorithm/index.rst + dev/index.rst + contribute_to_paddle.md \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index 668ad75a90..a7feed5239 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -4,7 +4,9 @@ PaddlePaddle Documentation .. toctree:: :maxdepth: 1 - introduction/index.md - user_guide.rst - dev/index.rst - algorithm/index.rst + introduction/index.rst + tutorials/index.md + howto/index.rst + api/index.rst + about/index.rst + diff --git a/doc/introduction/basic_usage/basic_usage.rst b/doc/introduction/basic_usage/basic_usage.rst new file mode 100644 index 0000000000..dca7a6b1f4 --- /dev/null +++ b/doc/introduction/basic_usage/basic_usage.rst @@ -0,0 +1,109 @@ +Basic Usage +============= + +PaddlePaddle is a deep learning platform open-sourced by Baidu. With PaddlePaddle, you can easily train a classic neural network within a couple lines of configuration, or you can build sophisticated models that provide state-of-the-art performance on difficult learning tasks like sentiment analysis, machine translation, image caption and so on. + +1. A Classic Problem +--------------------- + +Now, to give you a hint of what using PaddlePaddle looks like, let's start with a fundamental learning problem - `simple linear regression `_: you have observed a set of two-dimensional data points of ``X`` and ``Y``, where ``X`` is an explanatory variable and ``Y`` is corresponding dependent variable, and you want to recover the underlying correlation between ``X`` and ``Y``. Linear regression can be used in many practical scenarios. For example, ``X`` can be a variable about house size, and ``Y`` a variable about house price. You can build a model that captures relationship between them by observing real estate markets. + +2. Prepare the Data +-------------------- + +Suppose the true relationship can be characterized as ``Y = 2X + 0.3``, let's see how to recover this pattern only from observed data. Here is a piece of python code that feeds synthetic data to PaddlePaddle. The code is pretty self-explanatory, the only extra thing you need to add for PaddlePaddle is a definition of input data types. + + .. code-block:: python + + # dataprovider.py + from paddle.trainer.PyDataProvider2 import * + import random + + # define data types of input: 2 real numbers + @provider(input_types=[dense_vector(1), dense_vector(1)],use_seq=False) + def process(settings, input_file): + for i in xrange(2000): + x = random.random() + yield [x], [2*x+0.3] + +3. Train a NeuralNetwork +------------------------- + +To recover this relationship between ``X`` and ``Y``, we use a neural network with one layer of linear activation units and a square error cost layer. Don't worry if you are not familiar with these terminologies, it's just saying that we are starting from a random line ``Y' = wX + b`` , then we gradually adapt ``w`` and ``b`` to minimize the difference between ``Y'`` and ``Y``. Here is what it looks like in PaddlePaddle: + + .. code-block:: python + + # trainer_config.py + from paddle.trainer_config_helpers import * + + # 1. read data. Suppose you saved above python code as dataprovider.py + data_file = 'empty.list' + with open(data_file, 'w') as f: f.writelines(' ') + define_py_data_sources2(train_list=data_file, test_list=None, + module='dataprovider', obj='process',args={}) + + # 2. learning algorithm + settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) + + # 3. Network configuration + x = data_layer(name='x', size=1) + y = data_layer(name='y', size=1) + y_predict = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) + cost = regression_cost(input=y_predict, label=y) + outputs(cost) + +Some of the most fundamental usages of PaddlePaddle are demonstrated: + +- The first part shows how to feed data into PaddlePaddle. In general cases, PaddlePaddle reads raw data from a list of files, and then do some user-defined process to get real input. In this case, we only need to create a placeholder file since we are generating synthetic data on the fly. + +- The second part describes learning algorithm. It defines in what ways adjustments are made to model parameters. PaddlePaddle provides a rich set of optimizers, but a simple momentum based optimizer will suffice here, and it processes 12 data points each time. + +- Finally, the network configuration. It usually is as simple as "stacking" layers. Three kinds of layers are used in this configuration: + - **Data Layer**: a network always starts with one or more data layers. They provide input data to the rest of the network. In this problem, two data layers are used respectively for ``X`` and ``Y``. + - **FC Layer**: FC layer is short for Fully Connected Layer, which connects all the input units to current layer and does the actual computation specified as activation function. Computation layers like this are the fundamental building blocks of a deeper model. + - **Cost Layer**: in training phase, cost layers are usually the last layers of the network. They measure the performance of current model, and provide guidence to adjust parameters. + +Now that everything is ready, you can train the network with a simple command line call: + + .. code-block:: bash + + paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 + + +This means that PaddlePaddle will train this network on the synthectic dataset for 30 passes, and save all the models under path ``./output``. You will see from the messages printed out during training phase that the model cost is decreasing as time goes by, which indicates we are getting a closer guess. + + +4. Evaluate the Model +----------------------- + +Usually, a different dataset that left out during training phase should be used to evalute the models. However, we are lucky enough to know the real answer: ``w=2, b=0.3``, thus a better option is to check out model parameters directly. + +In PaddlePaddle, training is just to get a collection of model parameters, which are ``w`` and ``b`` in this case. Each parameter is saved in an individual file in the popular ``numpy`` array format. Here is the code that reads parameters from last pass. + + .. code-block:: python + + import numpy as np + import os + + def load(file_name): + with open(file_name, 'rb') as f: + f.read(16) # skip header for float type. + return np.fromfile(f, dtype=np.float32) + + print 'w=%.6f, b=%.6f' % (load('output/pass-00029/w'), load('output/pass-00029/b')) + # w=1.999743, b=0.300137 + + .. image:: parameters.png + :align: center + +Although starts from a random guess, you can see that value of ``w`` changes quickly towards 2 and ``b`` changes quickly towards 0.3. In the end, the predicted line is almost identical with real answer. + +There, you have recovered the underlying pattern between ``X`` and ``Y`` only from observed data. + + +5. Where to Go from Here +------------------------- + +- `Install and Build <../build_and_install/index.html>`_ +- `Tutorials <../demo/quick_start/index_en.html>`_ +- `Example and Demo <../demo/index.html>`_ diff --git a/doc/introduction/basic_usage/parameters.png b/doc/introduction/basic_usage/parameters.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec67480951e21f0400bce1c34b3108dcd65c18c GIT binary patch literal 44469 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 j{~Ea`=**simple linear regression** : you have observed a set of two-dimensional data points of `X` and `Y`, where `X` is an explanatory variable and `Y` is corresponding dependent variable, and you want to recover the underlying correlation between `X` and `Y`. Linear regression can be used in many practical scenarios. For example, `X` can be a variable about house size, and `Y` a variable about house price. You can build a model that captures relationship between them by observing real estate markets. - -## 2. Prepare the Data - -Suppose the true relationship can be characterized as `Y = 2X + 0.3`, let's see how to recover this pattern only from observed data. Here is a piece of python code that feeds synthetic data to PaddlePaddle. The code is pretty self-explanatory, the only extra thing you need to add for PaddlePaddle is a definition of input data types. - -```python -# dataprovider.py -from paddle.trainer.PyDataProvider2 import * -import random - -# define data types of input: 2 real numbers -@provider(input_types=[dense_vector(1), dense_vector(1)],use_seq=False) -def process(settings, input_file): - for i in xrange(2000): - x = random.random() - yield [x], [2*x+0.3] -``` - -## 3. Train a NeuralNetwork in PaddlePaddle - -To recover this relationship between `X` and `Y`, we use a neural network with one layer of linear activation units and a square error cost layer. Don't worry if you are not familiar with these terminologies, it's just saying that we are starting from a random line `Y' = wX + b` , then we gradually adapt `w` and `b` to minimize the difference between `Y'` and `Y`. Here is what it looks like in PaddlePaddle: - -```python -# trainer_config.py -from paddle.trainer_config_helpers import * - -# 1. read data. Suppose you saved above python code as dataprovider.py -data_file = 'empty.list' -with open(data_file, 'w') as f: f.writelines(' ') -define_py_data_sources2(train_list=data_file, test_list=None, - module='dataprovider', obj='process',args={}) - -# 2. learning algorithm -settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) - -# 3. Network configuration -x = data_layer(name='x', size=1) -y = data_layer(name='y', size=1) -y_predict = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) -cost = regression_cost(input=y_predict, label=y) -outputs(cost) -``` - -Some of the most fundamental usages of PaddlePaddle are demonstrated: - -- The first part shows how to feed data into PaddlePaddle. In general cases, PaddlePaddle reads raw data from a list of files, and then do some user-defined process to get real input. In this case, we only need to create a placeholder file since we are generating synthetic data on the fly. - -- The second part describes learning algorithm. It defines in what ways adjustments are made to model parameters. PaddlePaddle provides a rich set of optimizers, but a simple momentum based optimizer will suffice here, and it processes 12 data points each time. - -- Finally, the network configuration. It usually is as simple as "stacking" layers. Three kinds of layers are used in this configuration: - - **Data Layer**: a network always starts with one or more data layers. They provide input data to the rest of the network. In this problem, two data layers are used respectively for `X` and `Y`. - - **FC Layer**: FC layer is short for Fully Connected Layer, which connects all the input units to current layer and does the actual computation specified as activation function. Computation layers like this are the fundamental building blocks of a deeper model. - - **Cost Layer**: in training phase, cost layers are usually the last layers of the network. They measure the performance of current model, and provide guidence to adjust parameters. - -Now that everything is ready, you can train the network with a simple command line call: - ``` - paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 - ``` - -This means that PaddlePaddle will train this network on the synthectic dataset for 30 passes, and save all the models under path `./output`. You will see from the messages printed out during training phase that the model cost is decreasing as time goes by, which indicates we are getting a closer guess. - - -## 4. Evaluate the Model - -Usually, a different dataset that left out during training phase should be used to evalute the models. However, we are lucky enough to know the real answer: `w=2, b=0.3`, thus a better option is to check out model parameters directly. - -In PaddlePaddle, training is just to get a collection of model parameters, which are `w` and `b` in this case. Each parameter is saved in an individual file in the popular `numpy` array format. Here is the code that reads parameters from last pass. - -```python -import numpy as np -import os - -def load(file_name): - with open(file_name, 'rb') as f: - f.read(16) # skip header for float type. - return np.fromfile(f, dtype=np.float32) - -print 'w=%.6f, b=%.6f' % (load('output/pass-00029/w'), load('output/pass-00029/b')) -# w=1.999743, b=0.300137 -``` - -
    ![](./parameters.png)
    - -Although starts from a random guess, you can see that value of `w` changes quickly towards 2 and `b` changes quickly towards 0.3. In the end, the predicted line is almost identical with real answer. - -There, you have recovered the underlying pattern between `X` and `Y` only from observed data. - - -## 5. Where to Go from Here - -- Build and Installation -- Quick Start -- Example and Demo diff --git a/doc/introduction/index.rst b/doc/introduction/index.rst new file mode 100644 index 0000000000..ff22f05a1b --- /dev/null +++ b/doc/introduction/index.rst @@ -0,0 +1,8 @@ +Introduction +============ + +.. toctree:: + :maxdepth: 2 + + build_and_install/index.rst + basic_usage/basic_usage.rst diff --git a/doc/introduction/parameters.png b/doc/introduction/parameters.png deleted file mode 120000 index f47e74c94f..0000000000 --- a/doc/introduction/parameters.png +++ /dev/null @@ -1 +0,0 @@ -../../doc_cn/introduction/parameters.png \ No newline at end of file diff --git a/doc/demo/embedding_model/index.md b/doc/tutorials/embedding_model/index.md similarity index 100% rename from doc/demo/embedding_model/index.md rename to doc/tutorials/embedding_model/index.md diff --git a/doc/demo/embedding_model/neural-n-gram-model.png b/doc/tutorials/embedding_model/neural-n-gram-model.png similarity index 100% rename from doc/demo/embedding_model/neural-n-gram-model.png rename to doc/tutorials/embedding_model/neural-n-gram-model.png diff --git a/doc/demo/image_classification/cifar.png b/doc/tutorials/image_classification/cifar.png similarity index 100% rename from doc/demo/image_classification/cifar.png rename to doc/tutorials/image_classification/cifar.png diff --git a/doc/demo/image_classification/image_classification.md b/doc/tutorials/image_classification/image_classification.md similarity index 100% rename from doc/demo/image_classification/image_classification.md rename to doc/tutorials/image_classification/image_classification.md diff --git a/doc/demo/image_classification/image_classification.png b/doc/tutorials/image_classification/image_classification.png similarity index 100% rename from doc/demo/image_classification/image_classification.png rename to doc/tutorials/image_classification/image_classification.png diff --git a/doc/demo/image_classification/index.rst b/doc/tutorials/image_classification/index.rst similarity index 100% rename from doc/demo/image_classification/index.rst rename to doc/tutorials/image_classification/index.rst diff --git a/doc/demo/image_classification/lenet.png b/doc/tutorials/image_classification/lenet.png similarity index 100% rename from doc/demo/image_classification/lenet.png rename to doc/tutorials/image_classification/lenet.png diff --git a/doc/demo/image_classification/plot.png b/doc/tutorials/image_classification/plot.png similarity index 100% rename from doc/demo/image_classification/plot.png rename to doc/tutorials/image_classification/plot.png diff --git a/doc/demo/imagenet_model/resnet_block.jpg b/doc/tutorials/imagenet_model/resnet_block.jpg similarity index 100% rename from doc/demo/imagenet_model/resnet_block.jpg rename to doc/tutorials/imagenet_model/resnet_block.jpg diff --git a/doc/demo/imagenet_model/resnet_model.md b/doc/tutorials/imagenet_model/resnet_model.md similarity index 100% rename from doc/demo/imagenet_model/resnet_model.md rename to doc/tutorials/imagenet_model/resnet_model.md diff --git a/doc/demo/index.md b/doc/tutorials/index.md similarity index 96% rename from doc/demo/index.md rename to doc/tutorials/index.md index 289199d496..c845ca229c 100644 --- a/doc/demo/index.md +++ b/doc/tutorials/index.md @@ -1,4 +1,4 @@ -# Examples and demos +# Tutorials There are serveral examples and demos here. ## Image diff --git a/doc/demo/quick_start/NetContinuous_en.png b/doc/tutorials/quick_start/NetContinuous_en.png similarity index 100% rename from doc/demo/quick_start/NetContinuous_en.png rename to doc/tutorials/quick_start/NetContinuous_en.png diff --git a/doc/demo/quick_start/NetConv_en.png b/doc/tutorials/quick_start/NetConv_en.png similarity index 100% rename from doc/demo/quick_start/NetConv_en.png rename to doc/tutorials/quick_start/NetConv_en.png diff --git a/doc/demo/quick_start/NetLR_en.png b/doc/tutorials/quick_start/NetLR_en.png similarity index 100% rename from doc/demo/quick_start/NetLR_en.png rename to doc/tutorials/quick_start/NetLR_en.png diff --git a/doc/demo/quick_start/NetRNN_en.png b/doc/tutorials/quick_start/NetRNN_en.png similarity index 100% rename from doc/demo/quick_start/NetRNN_en.png rename to doc/tutorials/quick_start/NetRNN_en.png diff --git a/doc/demo/quick_start/PipelineNetwork_en.jpg b/doc/tutorials/quick_start/PipelineNetwork_en.jpg similarity index 100% rename from doc/demo/quick_start/PipelineNetwork_en.jpg rename to doc/tutorials/quick_start/PipelineNetwork_en.jpg diff --git a/doc/demo/quick_start/PipelineTest_en.png b/doc/tutorials/quick_start/PipelineTest_en.png similarity index 100% rename from doc/demo/quick_start/PipelineTest_en.png rename to doc/tutorials/quick_start/PipelineTest_en.png diff --git a/doc/demo/quick_start/PipelineTrain_en.png b/doc/tutorials/quick_start/PipelineTrain_en.png similarity index 100% rename from doc/demo/quick_start/PipelineTrain_en.png rename to doc/tutorials/quick_start/PipelineTrain_en.png diff --git a/doc/demo/quick_start/Pipeline_en.jpg b/doc/tutorials/quick_start/Pipeline_en.jpg similarity index 100% rename from doc/demo/quick_start/Pipeline_en.jpg rename to doc/tutorials/quick_start/Pipeline_en.jpg diff --git a/doc/demo/quick_start/index_en.md b/doc/tutorials/quick_start/index_en.md similarity index 100% rename from doc/demo/quick_start/index_en.md rename to doc/tutorials/quick_start/index_en.md diff --git a/doc/demo/rec/ml_dataset.md b/doc/tutorials/rec/ml_dataset.md similarity index 100% rename from doc/demo/rec/ml_dataset.md rename to doc/tutorials/rec/ml_dataset.md diff --git a/doc/demo/rec/ml_regression.rst b/doc/tutorials/rec/ml_regression.rst similarity index 100% rename from doc/demo/rec/ml_regression.rst rename to doc/tutorials/rec/ml_regression.rst diff --git a/doc/demo/rec/rec_regression_network.png b/doc/tutorials/rec/rec_regression_network.png similarity index 100% rename from doc/demo/rec/rec_regression_network.png rename to doc/tutorials/rec/rec_regression_network.png diff --git a/doc/demo/semantic_role_labeling/curve.jpg b/doc/tutorials/semantic_role_labeling/curve.jpg similarity index 100% rename from doc/demo/semantic_role_labeling/curve.jpg rename to doc/tutorials/semantic_role_labeling/curve.jpg diff --git a/doc/demo/semantic_role_labeling/feature.jpg b/doc/tutorials/semantic_role_labeling/feature.jpg similarity index 100% rename from doc/demo/semantic_role_labeling/feature.jpg rename to doc/tutorials/semantic_role_labeling/feature.jpg diff --git a/doc/demo/semantic_role_labeling/index.rst b/doc/tutorials/semantic_role_labeling/index.rst similarity index 100% rename from doc/demo/semantic_role_labeling/index.rst rename to doc/tutorials/semantic_role_labeling/index.rst diff --git a/doc/demo/semantic_role_labeling/network_arch.png b/doc/tutorials/semantic_role_labeling/network_arch.png similarity index 100% rename from doc/demo/semantic_role_labeling/network_arch.png rename to doc/tutorials/semantic_role_labeling/network_arch.png diff --git a/doc/demo/semantic_role_labeling/semantic_role_labeling.md b/doc/tutorials/semantic_role_labeling/semantic_role_labeling.md similarity index 97% rename from doc/demo/semantic_role_labeling/semantic_role_labeling.md rename to doc/tutorials/semantic_role_labeling/semantic_role_labeling.md index e2793b2b34..f5bdf64487 100644 --- a/doc/demo/semantic_role_labeling/semantic_role_labeling.md +++ b/doc/tutorials/semantic_role_labeling/semantic_role_labeling.md @@ -1,200 +1,200 @@ -# Semantic Role labeling Tutorial # - -Semantic role labeling (SRL) is a form of shallow semantic parsing whose goal is to discover the predicate-argument structure of each predicate in a given input sentence. SRL is useful as an intermediate step in a wide range of natural language processing tasks, such as information extraction. automatic document categorization and question answering. An instance is as following [1]: - - [ A0 He ] [ AM-MOD would ][ AM-NEG n’t ] [ V accept] [ A1 anything of value ] from [A2 those he was writing about ]. - -- V: verb -- A0: acceptor -- A1: thing accepted -- A2: accepted-from -- A3: Attribute -- AM-MOD: modal -- AM-NEG: negation - -Given the verb "accept", the chunks in sentence would play certain semantic roles. Here, the label scheme is from Penn Proposition Bank. - -To this date, most of the successful SRL systems are built on top of some form of parsing results where pre-defined feature templates over the syntactic structure are used. This tutorial will present an end-to-end system using deep bidirectional long short-term memory (DB-LSTM)[2] for solving the SRL task, which largely outperforms the previous state-of-the-art systems. The system regards SRL task as the sequence labelling problem. - -## Data Description -The relevant paper[2] takes the data set in CoNLL-2005&2012 Shared Task for training and testing. Accordingto data license, the demo adopts the test data set of CoNLL-2005, which can be reached on website. - -To download and process the original data, user just need to execute the following command: - -```bash -cd data -./get_data.sh -``` -Several new files appear in the `data `directory as follows. -```bash -conll05st-release:the test data set of CoNll-2005 shared task -test.wsj.words:the Wall Street Journal data sentences -test.wsj.props: the propositional arguments -feature: the extracted features from data set -``` - -## Training -### DB-LSTM -Please refer to the Sentiment Analysis demo to learn more about the long short-term memory unit. - -Unlike Bidirectional-LSTM that used in Sentiment Analysis demo, the DB-LSTM adopts another way to stack LSTM layer. First a standard LSTM processes the sequence in forward direction. The input and output of this LSTM layer are taken by the next LSTM layer as input, processed in reversed direction. These two standard LSTM layers compose a pair of LSTM. Then we stack LSTM layers pair after pair to obtain the deep LSTM model. - -The following figure shows a temporal expanded 2-layer DB-LSTM network. -
    -![pic](./network_arch.png) -
    - -### Features -Two input features play an essential role in this pipeline: predicate (pred) and argument (argu). Two other features: predicate context (ctx-p) and region mark (mr) are also adopted. Because a single predicate word can not exactly describe the predicate information, especially when the same words appear more than one times in a sentence. With the predicate context, the ambiguity can be largely eliminated. Similarly, we use region mark mr = 1 to denote the argument position if it locates in the predicate context region, or mr = 0 if does not. These four simple features are all we need for our SRL system. Features of one sample with context size set to 1 is showed as following[2]: -
    -![pic](./feature.jpg) -
    - -In this sample, the coresponding labelled sentence is: - -[ A1 A record date ] has [ AM-NEG n't ] been [ V set ] . - -In the demo, we adopt the feature template as above, consists of : `argument`, `predicate`, `ctx-p (p=-1,0,1)`, `mark` and use `B/I/O` scheme to label each argument. These features and labels are stored in `feature` file, and separated by `\t`. - -### Data Provider - -`dataprovider.py` is the python file to wrap data. `hook()` function is to define the data slots for network. The Six features and label are all IndexSlots. -``` -def hook(settings, word_dict, label_dict, **kwargs): - settings.word_dict = word_dict - settings.label_dict = label_dict - #all inputs are integral and sequential type - settings.slots = [ - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(predicate_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(2), - integer_value_sequence(len(label_dict))] -``` -The corresponding data iterator is as following: -``` -@provider(init_hook=hook, should_shuffle=True, calc_batch_size=get_batch_size, - can_over_batch_size=False, cache=CacheType.CACHE_PASS_IN_MEM) -def process(settings, file_name): - with open(file_name, 'r') as fdata: - for line in fdata: - sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = \ - line.strip().split('\t') - - words = sentence.split() - sen_len = len(words) - word_slot = [settings.word_dict.get(w, UNK_IDX) for w in words] - - predicate_slot = [settings.predicate_dict.get(predicate)] * sen_len - ctx_n2_slot = [settings.word_dict.get(ctx_n2, UNK_IDX)] * sen_len - ctx_n1_slot = [settings.word_dict.get(ctx_n1, UNK_IDX)] * sen_len - ctx_0_slot = [settings.word_dict.get(ctx_0, UNK_IDX)] * sen_len - ctx_p1_slot = [settings.word_dict.get(ctx_p1, UNK_IDX)] * sen_len - ctx_p2_slot = [settings.word_dict.get(ctx_p2, UNK_IDX)] * sen_len - - marks = mark.split() - mark_slot = [int(w) for w in marks] - - label_list = label.split() - label_slot = [settings.label_dict.get(w) for w in label_list] - yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot, label_slot -``` -The `process`function yield 9 lists which are 8 features and label. - -### Neural Network Config -`db_lstm.py` is the neural network config file to load the dictionaries and define the data provider module and network architecture during the training procedure. - -Nine `data_layer` load instances from data provider. Eight features are transformed into embedddings respectively, and mixed by `mixed_layer` . Deep bidirectional LSTM layers extract features for the softmax layer. The objective function is cross entropy of labels. - -### Run Training -The script for training is `train.sh`, user just need to execute: -```bash - ./train.sh -``` -The content in `train.sh`: -``` -paddle train \ - --config=./db_lstm.py \ - --use_gpu=0 \ - --log_period=5000 \ - --trainer_count=1 \ - --show_parameter_stats_period=5000 \ - --save_dir=./output \ - --num_passes=10000 \ - --average_test_period=10000000 \ - --init_model_path=./data \ - --load_missing_parameter_strategy=rand \ - --test_all_data_in_one_period=1 \ -2>&1 | tee 'train.log' -``` - -- \--config=./db_lstm.py : network config file. -- \--use_gpu=false: use CPU to train, set true, if you install GPU version of PaddlePaddle and want to use GPU to train, until now crf_layer do not support GPU -- \--log_period=500: print log every 20 batches. -- \--trainer_count=1: set thread number (or GPU count). -- \--show_parameter_stats_period=5000: show parameter statistic every 100 batches. -- \--save_dir=./output: output path to save models. -- \--num_passes=10000: set pass number, one pass in PaddlePaddle means training all samples in dataset one time. -- \--average_test_period=10000000: do test on average parameter every average_test_period batches -- \--init_model_path=./data: parameter initialization path -- \--load_missing_parameter_strategy=rand: random initialization unexisted parameters -- \--test_all_data_in_one_period=1: test all data in one period - - -After training, the models will be saved in directory `output`. Our training curve is as following: -
    -![pic](./curve.jpg) -
    - -### Run testing -The script for testing is `test.sh`, user just need to execute: -```bash - ./test.sh -``` -The main part in `tesh.sh` -``` -paddle train \ - --config=./db_lstm.py \ - --model_list=$model_list \ - --job=test \ - --config_args=is_test=1 \ -``` - - - \--config=./db_lstm.py: network config file - - \--model_list=$model_list.list: model list file - - \--job=test: indicate the test job - - \--config_args=is_test=1: flag to indicate test - - \--test_all_data_in_one_period=1: test all data in 1 period - - -### Run prediction -The script for prediction is `predict.sh`, user just need to execute: -```bash - ./predict.sh - -``` -In `predict.sh`, user should offer the network config file, model path, label file, word dictionary file, feature file -``` -python predict.py - -c $config_file \ - -w $best_model_path \ - -l $label_file \ - -p $predicate_dict_file \ - -d $dict_file \ - -i $input_file \ - -o $output_file -``` - -`predict.py` is the main executable python script, which includes functions: load model, load data, data prediction. The network model will output the probability distribution of labels. In the demo, we take the label with maximum probability as result. User can also implement the beam search or viterbi decoding upon the probability distribution matrix. - -After prediction, the result is saved in `predict.res`. - -## Reference -[1] Martha Palmer, Dan Gildea, and Paul Kingsbury. The Proposition Bank: An Annotated Corpus of Semantic Roles , Computational Linguistics, 31(1), 2005. - -[2] Zhou, Jie, and Wei Xu. "End-to-end learning of semantic role labeling using recurrent neural networks." Proceedings of the Annual Meeting of the Association for Computational Linguistics. 2015. +# Semantic Role labeling Tutorial # + +Semantic role labeling (SRL) is a form of shallow semantic parsing whose goal is to discover the predicate-argument structure of each predicate in a given input sentence. SRL is useful as an intermediate step in a wide range of natural language processing tasks, such as information extraction. automatic document categorization and question answering. An instance is as following [1]: + + [ A0 He ] [ AM-MOD would ][ AM-NEG n’t ] [ V accept] [ A1 anything of value ] from [A2 those he was writing about ]. + +- V: verb +- A0: acceptor +- A1: thing accepted +- A2: accepted-from +- A3: Attribute +- AM-MOD: modal +- AM-NEG: negation + +Given the verb "accept", the chunks in sentence would play certain semantic roles. Here, the label scheme is from Penn Proposition Bank. + +To this date, most of the successful SRL systems are built on top of some form of parsing results where pre-defined feature templates over the syntactic structure are used. This tutorial will present an end-to-end system using deep bidirectional long short-term memory (DB-LSTM)[2] for solving the SRL task, which largely outperforms the previous state-of-the-art systems. The system regards SRL task as the sequence labelling problem. + +## Data Description +The relevant paper[2] takes the data set in CoNLL-2005&2012 Shared Task for training and testing. Accordingto data license, the demo adopts the test data set of CoNLL-2005, which can be reached on website. + +To download and process the original data, user just need to execute the following command: + +```bash +cd data +./get_data.sh +``` +Several new files appear in the `data `directory as follows. +```bash +conll05st-release:the test data set of CoNll-2005 shared task +test.wsj.words:the Wall Street Journal data sentences +test.wsj.props: the propositional arguments +feature: the extracted features from data set +``` + +## Training +### DB-LSTM +Please refer to the Sentiment Analysis demo to learn more about the long short-term memory unit. + +Unlike Bidirectional-LSTM that used in Sentiment Analysis demo, the DB-LSTM adopts another way to stack LSTM layer. First a standard LSTM processes the sequence in forward direction. The input and output of this LSTM layer are taken by the next LSTM layer as input, processed in reversed direction. These two standard LSTM layers compose a pair of LSTM. Then we stack LSTM layers pair after pair to obtain the deep LSTM model. + +The following figure shows a temporal expanded 2-layer DB-LSTM network. +
    +![pic](./network_arch.png) +
    + +### Features +Two input features play an essential role in this pipeline: predicate (pred) and argument (argu). Two other features: predicate context (ctx-p) and region mark (mr) are also adopted. Because a single predicate word can not exactly describe the predicate information, especially when the same words appear more than one times in a sentence. With the predicate context, the ambiguity can be largely eliminated. Similarly, we use region mark mr = 1 to denote the argument position if it locates in the predicate context region, or mr = 0 if does not. These four simple features are all we need for our SRL system. Features of one sample with context size set to 1 is showed as following[2]: +
    +![pic](./feature.jpg) +
    + +In this sample, the coresponding labelled sentence is: + +[ A1 A record date ] has [ AM-NEG n't ] been [ V set ] . + +In the demo, we adopt the feature template as above, consists of : `argument`, `predicate`, `ctx-p (p=-1,0,1)`, `mark` and use `B/I/O` scheme to label each argument. These features and labels are stored in `feature` file, and separated by `\t`. + +### Data Provider + +`dataprovider.py` is the python file to wrap data. `hook()` function is to define the data slots for network. The Six features and label are all IndexSlots. +``` +def hook(settings, word_dict, label_dict, **kwargs): + settings.word_dict = word_dict + settings.label_dict = label_dict + #all inputs are integral and sequential type + settings.slots = [ + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(predicate_dict)), + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(word_dict)), + integer_value_sequence(len(word_dict)), + integer_value_sequence(2), + integer_value_sequence(len(label_dict))] +``` +The corresponding data iterator is as following: +``` +@provider(init_hook=hook, should_shuffle=True, calc_batch_size=get_batch_size, + can_over_batch_size=False, cache=CacheType.CACHE_PASS_IN_MEM) +def process(settings, file_name): + with open(file_name, 'r') as fdata: + for line in fdata: + sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = \ + line.strip().split('\t') + + words = sentence.split() + sen_len = len(words) + word_slot = [settings.word_dict.get(w, UNK_IDX) for w in words] + + predicate_slot = [settings.predicate_dict.get(predicate)] * sen_len + ctx_n2_slot = [settings.word_dict.get(ctx_n2, UNK_IDX)] * sen_len + ctx_n1_slot = [settings.word_dict.get(ctx_n1, UNK_IDX)] * sen_len + ctx_0_slot = [settings.word_dict.get(ctx_0, UNK_IDX)] * sen_len + ctx_p1_slot = [settings.word_dict.get(ctx_p1, UNK_IDX)] * sen_len + ctx_p2_slot = [settings.word_dict.get(ctx_p2, UNK_IDX)] * sen_len + + marks = mark.split() + mark_slot = [int(w) for w in marks] + + label_list = label.split() + label_slot = [settings.label_dict.get(w) for w in label_list] + yield word_slot, predicate_slot, ctx_n2_slot, ctx_n1_slot, \ + ctx_0_slot, ctx_p1_slot, ctx_p2_slot, mark_slot, label_slot +``` +The `process`function yield 9 lists which are 8 features and label. + +### Neural Network Config +`db_lstm.py` is the neural network config file to load the dictionaries and define the data provider module and network architecture during the training procedure. + +Nine `data_layer` load instances from data provider. Eight features are transformed into embedddings respectively, and mixed by `mixed_layer` . Deep bidirectional LSTM layers extract features for the softmax layer. The objective function is cross entropy of labels. + +### Run Training +The script for training is `train.sh`, user just need to execute: +```bash + ./train.sh +``` +The content in `train.sh`: +``` +paddle train \ + --config=./db_lstm.py \ + --use_gpu=0 \ + --log_period=5000 \ + --trainer_count=1 \ + --show_parameter_stats_period=5000 \ + --save_dir=./output \ + --num_passes=10000 \ + --average_test_period=10000000 \ + --init_model_path=./data \ + --load_missing_parameter_strategy=rand \ + --test_all_data_in_one_period=1 \ +2>&1 | tee 'train.log' +``` + +- \--config=./db_lstm.py : network config file. +- \--use_gpu=false: use CPU to train, set true, if you install GPU version of PaddlePaddle and want to use GPU to train, until now crf_layer do not support GPU +- \--log_period=500: print log every 20 batches. +- \--trainer_count=1: set thread number (or GPU count). +- \--show_parameter_stats_period=5000: show parameter statistic every 100 batches. +- \--save_dir=./output: output path to save models. +- \--num_passes=10000: set pass number, one pass in PaddlePaddle means training all samples in dataset one time. +- \--average_test_period=10000000: do test on average parameter every average_test_period batches +- \--init_model_path=./data: parameter initialization path +- \--load_missing_parameter_strategy=rand: random initialization unexisted parameters +- \--test_all_data_in_one_period=1: test all data in one period + + +After training, the models will be saved in directory `output`. Our training curve is as following: +
    +![pic](./curve.jpg) +
    + +### Run testing +The script for testing is `test.sh`, user just need to execute: +```bash + ./test.sh +``` +The main part in `tesh.sh` +``` +paddle train \ + --config=./db_lstm.py \ + --model_list=$model_list \ + --job=test \ + --config_args=is_test=1 \ +``` + + - \--config=./db_lstm.py: network config file + - \--model_list=$model_list.list: model list file + - \--job=test: indicate the test job + - \--config_args=is_test=1: flag to indicate test + - \--test_all_data_in_one_period=1: test all data in 1 period + + +### Run prediction +The script for prediction is `predict.sh`, user just need to execute: +```bash + ./predict.sh + +``` +In `predict.sh`, user should offer the network config file, model path, label file, word dictionary file, feature file +``` +python predict.py + -c $config_file \ + -w $best_model_path \ + -l $label_file \ + -p $predicate_dict_file \ + -d $dict_file \ + -i $input_file \ + -o $output_file +``` + +`predict.py` is the main executable python script, which includes functions: load model, load data, data prediction. The network model will output the probability distribution of labels. In the demo, we take the label with maximum probability as result. User can also implement the beam search or viterbi decoding upon the probability distribution matrix. + +After prediction, the result is saved in `predict.res`. + +## Reference +[1] Martha Palmer, Dan Gildea, and Paul Kingsbury. The Proposition Bank: An Annotated Corpus of Semantic Roles , Computational Linguistics, 31(1), 2005. + +[2] Zhou, Jie, and Wei Xu. "End-to-end learning of semantic role labeling using recurrent neural networks." Proceedings of the Annual Meeting of the Association for Computational Linguistics. 2015. diff --git a/doc/demo/sentiment_analysis/bi_lstm.jpg b/doc/tutorials/sentiment_analysis/bi_lstm.jpg similarity index 100% rename from doc/demo/sentiment_analysis/bi_lstm.jpg rename to doc/tutorials/sentiment_analysis/bi_lstm.jpg diff --git a/doc/demo/sentiment_analysis/index.rst b/doc/tutorials/sentiment_analysis/index.rst similarity index 100% rename from doc/demo/sentiment_analysis/index.rst rename to doc/tutorials/sentiment_analysis/index.rst diff --git a/doc/demo/sentiment_analysis/lstm.png b/doc/tutorials/sentiment_analysis/lstm.png similarity index 100% rename from doc/demo/sentiment_analysis/lstm.png rename to doc/tutorials/sentiment_analysis/lstm.png diff --git a/doc/demo/sentiment_analysis/sentiment_analysis.md b/doc/tutorials/sentiment_analysis/sentiment_analysis.md similarity index 100% rename from doc/demo/sentiment_analysis/sentiment_analysis.md rename to doc/tutorials/sentiment_analysis/sentiment_analysis.md diff --git a/doc/demo/sentiment_analysis/stacked_lstm.jpg b/doc/tutorials/sentiment_analysis/stacked_lstm.jpg similarity index 100% rename from doc/demo/sentiment_analysis/stacked_lstm.jpg rename to doc/tutorials/sentiment_analysis/stacked_lstm.jpg diff --git a/doc/demo/text_generation/encoder-decoder-attention-model.png b/doc/tutorials/text_generation/encoder-decoder-attention-model.png similarity index 100% rename from doc/demo/text_generation/encoder-decoder-attention-model.png rename to doc/tutorials/text_generation/encoder-decoder-attention-model.png diff --git a/doc/demo/text_generation/index.rst b/doc/tutorials/text_generation/index.rst similarity index 100% rename from doc/demo/text_generation/index.rst rename to doc/tutorials/text_generation/index.rst diff --git a/doc/demo/text_generation/text_generation.md b/doc/tutorials/text_generation/text_generation.md similarity index 100% rename from doc/demo/text_generation/text_generation.md rename to doc/tutorials/text_generation/text_generation.md diff --git a/doc/ui/index.md b/doc/ui/index.md deleted file mode 100644 index 9c1ba27bdc..0000000000 --- a/doc/ui/index.md +++ /dev/null @@ -1,20 +0,0 @@ -# User Interface - -## Data Provider - -* [Introduction](data_provider/index.rst) -* [PyDataProvider2](data_provider/pydataprovider2.rst) - -## API Reference - -* [Model Config Interface](api/trainer_config_helpers/index.md) - -## Command Line Argument - -* [Use Case](cmd_argument/use_case.md) -* [Argument Outline](cmd_argument/argument_outline.md) -* [Detailed Descriptions](cmd_argument/detail_introduction.md) - -## Predict - -* [Python Prediction API](predict/swig_py_paddle_en.rst) diff --git a/doc/user_guide.rst b/doc/user_guide.rst deleted file mode 100644 index d4deb3ca5a..0000000000 --- a/doc/user_guide.rst +++ /dev/null @@ -1,13 +0,0 @@ -User Guide -========== - -.. toctree:: - :maxdepth: 1 - - demo/quick_start/index_en.md - build/index.rst - build/contribute_to_paddle.md - ui/index.md - ui/api/trainer_config_helpers/index.rst - demo/index.md - cluster/index.md -- GitLab From a48f19cf453fae85f192598e813c1ad80b41bc86 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 17:20:00 +0800 Subject: [PATCH 0133/1503] Add symbol link --- doc/howto/algorithm/rnn/bi_lstm.jpg | 1 + doc/howto/algorithm/rnn/encoder-decoder-attention-model.png | 1 + 2 files changed, 2 insertions(+) create mode 120000 doc/howto/algorithm/rnn/bi_lstm.jpg create mode 120000 doc/howto/algorithm/rnn/encoder-decoder-attention-model.png diff --git a/doc/howto/algorithm/rnn/bi_lstm.jpg b/doc/howto/algorithm/rnn/bi_lstm.jpg new file mode 120000 index 0000000000..f8f3b17691 --- /dev/null +++ b/doc/howto/algorithm/rnn/bi_lstm.jpg @@ -0,0 +1 @@ +../../../tutorials/sentiment_analysis/bi_lstm.jpg \ No newline at end of file diff --git a/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png b/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png new file mode 120000 index 0000000000..88a1d3e5ac --- /dev/null +++ b/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png @@ -0,0 +1 @@ +../../../tutorials/text_generation/encoder-decoder-attention-model.png \ No newline at end of file -- GitLab From 80fb9116d1141951d5a9b60801a3f0378a073e96 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 17:24:20 +0800 Subject: [PATCH 0134/1503] Change gpu profiling docs position --- doc/howto/index.rst | 1 + doc/{ => howto}/optimization/gpu_profiling.rst | 0 doc/{ => howto}/optimization/index.rst | 0 doc/{ => howto}/optimization/nvvp1.png | Bin doc/{ => howto}/optimization/nvvp2.png | Bin doc/{ => howto}/optimization/nvvp3.png | Bin doc/{ => howto}/optimization/nvvp4.png | Bin 7 files changed, 1 insertion(+) rename doc/{ => howto}/optimization/gpu_profiling.rst (100%) rename doc/{ => howto}/optimization/index.rst (100%) rename doc/{ => howto}/optimization/nvvp1.png (100%) rename doc/{ => howto}/optimization/nvvp2.png (100%) rename doc/{ => howto}/optimization/nvvp3.png (100%) rename doc/{ => howto}/optimization/nvvp4.png (100%) diff --git a/doc/howto/index.rst b/doc/howto/index.rst index e2d688e186..ed8294b3c1 100644 --- a/doc/howto/index.rst +++ b/doc/howto/index.rst @@ -7,5 +7,6 @@ How to cmd_argument/index.md cluster/cluster_train.md algorithm/index.rst + optimization/index.rst dev/index.rst contribute_to_paddle.md \ No newline at end of file diff --git a/doc/optimization/gpu_profiling.rst b/doc/howto/optimization/gpu_profiling.rst similarity index 100% rename from doc/optimization/gpu_profiling.rst rename to doc/howto/optimization/gpu_profiling.rst diff --git a/doc/optimization/index.rst b/doc/howto/optimization/index.rst similarity index 100% rename from doc/optimization/index.rst rename to doc/howto/optimization/index.rst diff --git a/doc/optimization/nvvp1.png b/doc/howto/optimization/nvvp1.png similarity index 100% rename from doc/optimization/nvvp1.png rename to doc/howto/optimization/nvvp1.png diff --git a/doc/optimization/nvvp2.png b/doc/howto/optimization/nvvp2.png similarity index 100% rename from doc/optimization/nvvp2.png rename to doc/howto/optimization/nvvp2.png diff --git a/doc/optimization/nvvp3.png b/doc/howto/optimization/nvvp3.png similarity index 100% rename from doc/optimization/nvvp3.png rename to doc/howto/optimization/nvvp3.png diff --git a/doc/optimization/nvvp4.png b/doc/howto/optimization/nvvp4.png similarity index 100% rename from doc/optimization/nvvp4.png rename to doc/howto/optimization/nvvp4.png -- GitLab From 5e7e5ccb68205ef0a5ea97389598a0c3ae9e479c Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 17:30:09 +0800 Subject: [PATCH 0135/1503] Change head to About --- doc/about/index.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/about/index.rst b/doc/about/index.rst index c70940ca85..511c154641 100644 --- a/doc/about/index.rst +++ b/doc/about/index.rst @@ -1,5 +1,9 @@ +About +======= + + Credits -======== +-------- PaddlPaddle is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep learning to many products at Baidu. -- GitLab From a7671dee4745cf5f65228148504043d817476c6c Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 25 Nov 2016 17:30:15 +0800 Subject: [PATCH 0136/1503] refine build_docker_image.rst --- doc_cn/build/docker/build_docker_image.rst | 38 ---------------------- doc_cn/build_and_install/index.rst | 5 --- doc_cn/howto/build_docker_image.rst | 35 ++++++++++++++++++++ doc_cn/index.rst | 1 + paddle/scripts/docker/build.sh | 2 +- 5 files changed, 37 insertions(+), 44 deletions(-) delete mode 100644 doc_cn/build/docker/build_docker_image.rst create mode 100644 doc_cn/howto/build_docker_image.rst diff --git a/doc_cn/build/docker/build_docker_image.rst b/doc_cn/build/docker/build_docker_image.rst deleted file mode 100644 index 73409ceaff..0000000000 --- a/doc_cn/build/docker/build_docker_image.rst +++ /dev/null @@ -1,38 +0,0 @@ -构建PaddlePaddle Docker Image -=========================== - -PaddlePaddle的Docker Image构建源码放置在 :code:`${源码根目录}/paddle/scripts/docker/`目录下。 -该Image基于ubuntu 14.04。该目录下有两个文件,Dockerfile和build.sh。其中: - -* Dockerfile是docker image的主要描述文件。描述了Docker image的构建步骤、各种参数和维护 - 人员等等。 -* build.sh是docker image的主要构建步骤。 - -该image的构建在docker 1.12版本测试通过, 低于docker 1.12版本的情况下并没有测试。主要由于旧版本 -的docker可能缺乏 :code:`--build-arg` 参数,从而不能在运行编译命令的时候接受参数。 - -同时,该构建脚本充分考虑了网络不稳定的情况,对于cuda的Toolkit有断点续传和传输速度过小重启下载的 -简单优化。 - -使用脚本构建PaddlePaddle Docker Image -------------------------------------------- - -该脚本的使用方法是,进入该源码目录,执行 :code:`docker build .` 命令。可以使用 - :code:`--build-arg` 传入的配置参数包括: - -* LOWEST\_DL\_SPEED\: 多线程下载过程中,最低线程的下载速度(默认单位是Bytes,可以传入10K, - 10M,或者10G这样的单位)。如果小于这个下载速度,那么这个下载线程将会关闭。所有的下载线程关闭时, - 下载进程会重启。 -* WITH\_GPU\: ON or OFF。是否开启GPU功能。注意,编译PaddlePaddle的GPU版本并不需要一定在具有GPU - 的机器上进行。但是,运行PaddlePaddle的GPU版本一定要在具有CUDA的机器上运行。 - -简单的使用样例为\: - -.. code-block:: bash - - cd ${源码根目录}/paddle/scripts/docker/ - docker build --build-arg LOWEST_DL_SPEED=50K\ - --build-arg WITH_GPU=ON \ - --tag paddle_gpu:latest . - -即可在本地编译出PaddlePaddle的镜像。 diff --git a/doc_cn/build_and_install/index.rst b/doc_cn/build_and_install/index.rst index 2205e28224..48163fb36e 100644 --- a/doc_cn/build_and_install/index.rst +++ b/doc_cn/build_and_install/index.rst @@ -8,9 +8,7 @@ PaddlePaddle提供数个预编译的二进制来进行安装,包括Docker镜 .. toctree:: :maxdepth: 1 - :glob: - 使用Jumbo安装(对内) <../build/internal/install_from_jumbo.rst> install/docker_install.rst install/ubuntu_install.rst @@ -25,8 +23,5 @@ PaddlePaddle提供数个预编译的二进制来进行安装,包括Docker镜 .. toctree:: :maxdepth: 1 - :glob: - 源码下载(对内) <../build/internal/download_paddle_source_zh_cn.rst> - 从源码编译安装(对内) <../build/internal/build_from_source_zh_cn.rst> cmake/index.rst diff --git a/doc_cn/howto/build_docker_image.rst b/doc_cn/howto/build_docker_image.rst new file mode 100644 index 0000000000..16887672f7 --- /dev/null +++ b/doc_cn/howto/build_docker_image.rst @@ -0,0 +1,35 @@ +构建PaddlePaddle的Docker Image +============================== +PaddlePaddle的Docker Image构建源码放置在 ``${源码根目录}/paddle/scripts/docker/`` 目录下。该目录有三类文件: + +- Dockerfile:Docker Image的描述文件,包括构建步骤、各种参数和维护人员等。 + + - 一共维护了12个Dockerfile,Dockerfile.m4是它们的模板。 + - PaddlePaddle中所有的Image都基于ubuntu 14.04。 + +- build.sh:Docker Image的构建脚本,使用方式见下一小节。 +- generate.sh:通过Dockerfile.m4模板生成不同的Dockerfile。 + +使用脚本构建Docker Image +------------------------ + +进入源码目录,执行 ``docker build`` 命令,即可在本地编译出PaddlePaddle的镜像。简单的使用样例为 + +.. code-block:: bash + + cd ${源码根目录}/paddle/scripts/docker/ + docker build --build-arg LOWEST_DL_SPEED=50K\ + --build-arg WITH_GPU=ON \ + --tag paddle_gpu:latest . + +其中,``--build-arg`` 传入的配置参数包括: + +- LOWEST\_DL\_SPEED\: 在多线程下载过程中,设置下线线程的最低速度。 + + - 默认单位是Bytes,但可以传入10K、10M、或10G等这样的单位。 + - 如果小于这个速度,那么这个线程将会关闭。当所有的线程都关闭了,那么下载进程将会重启。 +- WITH\_GPU\: ON or OFF,是否开启GPU功能。注意, + - **编译** PaddlePaddle的GPU版本 **不一定** 要在具有GPU的机器上进行。 + - **运行** PaddlePaddle的GPU版本 **一定** 要在具有GPU的机器上运行。 + +注意:所有Image的构建在Docker 1.12版本测试通过, 低于1.12的版本并没有测试。原因是旧版本可能缺乏 ``--build-arg`` 参数,从而不能在运行编译命令的时候接受参数。 diff --git a/doc_cn/index.rst b/doc_cn/index.rst index f1398206fd..c9f2126c3e 100644 --- a/doc_cn/index.rst +++ b/doc_cn/index.rst @@ -16,6 +16,7 @@ PaddlePaddle文档 -------- * `新写Layer <../doc/dev/new_layer/index.html>`_ * `如何贡献文档 `_ +* `如何构建Docker Image `_ 算法教程 -------- diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index ec5f3bd967..f8322316a5 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -21,7 +21,7 @@ if [ ${WITH_GPU} == 'ON' ]; then fi cd ~ -git clone https://github.com/baidu/Paddle.git paddle +git clone https://github.com/PaddlePaddle/Paddle.git paddle cd paddle git checkout ${GIT_CHECKOUT} mkdir build -- GitLab From 2664286d6adfdd5cc80560bd22371083a3d64bdd Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 25 Nov 2016 18:32:24 +0800 Subject: [PATCH 0137/1503] refine dataprovider related rst --- doc_cn/ui/data_provider/dataprovider.rst | 15 + doc_cn/ui/data_provider/index.rst | 17 - doc_cn/ui/data_provider/mnist_config.py | 1 + doc_cn/ui/data_provider/mnist_provider.py | 22 - doc_cn/ui/data_provider/pydataprovider2.rst | 476 ++++++++---------- .../ui/data_provider/sentimental_provider.py | 15 +- .../data_provider/write_new_dataprovider.rst | 4 - doc_cn/ui/index.rst | 7 +- 8 files changed, 244 insertions(+), 313 deletions(-) create mode 100644 doc_cn/ui/data_provider/dataprovider.rst delete mode 100644 doc_cn/ui/data_provider/index.rst delete mode 100644 doc_cn/ui/data_provider/mnist_provider.py delete mode 100644 doc_cn/ui/data_provider/write_new_dataprovider.rst diff --git a/doc_cn/ui/data_provider/dataprovider.rst b/doc_cn/ui/data_provider/dataprovider.rst new file mode 100644 index 0000000000..e1ad330c29 --- /dev/null +++ b/doc_cn/ui/data_provider/dataprovider.rst @@ -0,0 +1,15 @@ +DataProvider的介绍 +================== + +DataProvider是PaddlePaddle负责提供数据的模块。其作用是将数据传入内存或显存,让神经网络可以进行训练或预测。有两种使用方式: + +- 简单使用:使用Python接口 `PyDataProvider2 `_ 来自定义传数据的过程。 +- 高级使用:如果用户有更复杂的使用,或者需要更高的效率,可以在C++端自定义一个 ``DataProvider`` 。 + +PaddlePaddle需要用户在网络配置(trainer_config.py)中定义使用哪种DataProvider,并且在DataProvider中实现如何访问训练文件列表(train.list)或测试文件列表(test.list)。 + +- train.list和test.list存放在本地(推荐直接存放到训练目录,以相对路径引用)。一般情况下,两者均为纯文本文件,其中每一行对应一个数据文件地址: + + - 如果数据文件存于本地磁盘,则将这些文件的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)写在train.list和test.list中。 + - 地址也可以为hdfs文件路径,或者数据库连接地址等。 +- 如果没有设置test.list,或设置为None,那么在训练过程中不会执行测试操作;否则,会根据命令行参数指定的测试方式,在训练过程中进行测试,从而防止过拟合。 diff --git a/doc_cn/ui/data_provider/index.rst b/doc_cn/ui/data_provider/index.rst deleted file mode 100644 index ec8f8e5dc5..0000000000 --- a/doc_cn/ui/data_provider/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -PaddlePaddle的数据提供(DataProvider)介绍 -======================================== - -数据提供(DataProvider)是PaddlePaddle负责提供数据的模块。其作用是将训练数据传入内存或者显存,让神经网络可以进行训练。简单的使用,用户可以使用Python的 :code:`PyDataProvider` 来自定义传数据的过程。如果有更复杂的使用,或者需要更高的效率,用户也可以在C++端自定义一个 :code:`DataProvider` 。 - -PaddlePaddle需要用户在网络配置(trainer_config.py)中定义使用哪种DataProvider及其参数,训练文件列表(train.list)和测试文件列表(test.list)。 - -其中,train.list和test.list均为本地的两个文件(推荐直接放置到训练目录,以相对路径引用)。如果test.list不设置,或者设置为None,那么在训练过程中,不会执行测试操作。否则,会根据命令行参数指定的测试方式,在训练过程中进行测试,从而防止过拟合。 - -一般情况下,train.list和test.list为纯文本文件,一行对应一个数据文件,数据文件存放在本地磁盘中。将文件的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)写在train.list和test.list中。当然,train.list和test.list也可以放置hdfs文件路径,或者数据库连接地址等等。 - -用户在DataProvider中需要实现如何访问其中每一个文件。DataProvider的具体用法和如何实现一个新的DataProvider,请参考下述文章: - -.. toctree:: - - pydataprovider2.rst - write_new_dataprovider.rst diff --git a/doc_cn/ui/data_provider/mnist_config.py b/doc_cn/ui/data_provider/mnist_config.py index 39becff03b..429338c57f 100644 --- a/doc_cn/ui/data_provider/mnist_config.py +++ b/doc_cn/ui/data_provider/mnist_config.py @@ -5,5 +5,6 @@ define_py_data_sources2( test_list=None, module='mnist_provider', obj='process') + img = data_layer(name='pixel', size=784) label = data_layer(name='label', size=10) diff --git a/doc_cn/ui/data_provider/mnist_provider.py b/doc_cn/ui/data_provider/mnist_provider.py deleted file mode 100644 index 8b828641d5..0000000000 --- a/doc_cn/ui/data_provider/mnist_provider.py +++ /dev/null @@ -1,22 +0,0 @@ -from paddle.trainer.PyDataProvider2 import * - - -# Define a py data provider -@provider(input_types=[dense_vector(28 * 28), integer_value(10)]) -def process(settings, filename): # settings is not used currently. - f = open(filename, 'r') # open one of training file - - for line in f: # read each line - label, pixel = line.split(';') - - # get features and label - pixels_str = pixel.split(' ') - - pixels_float = [] - for each_pixel_str in pixels_str: - pixels_float.append(float(each_pixel_str)) - - # give data to paddle. - yield pixels_float, int(label) - - f.close() # close file diff --git a/doc_cn/ui/data_provider/pydataprovider2.rst b/doc_cn/ui/data_provider/pydataprovider2.rst index 80b40084d8..3455e331da 100644 --- a/doc_cn/ui/data_provider/pydataprovider2.rst +++ b/doc_cn/ui/data_provider/pydataprovider2.rst @@ -1,257 +1,219 @@ -PyDataProvider2的使用 -===================== - -PyDataProvider是PaddlePaddle使用Python提供数据的推荐接口。使用该接口用户可以只关注如何 -从文件中读取每一条数据,而不用关心数据如何传输给PaddlePaddle,数据如何存储等等。该数据 -接口使用多线程读取数据,并提供了简单的Cache功能。 - - -简单的使用场景 --------------- - -这里以MNIST手写识别为例,来说明简单的PyDataProvider如何使用。MNIST是一个包含有 -70,000张灰度图片的数字分类数据集。对于MNIST而言,标签是0-9的数字,而特征即为 -28*28的像素灰度值。这里我们使用简单的文本文件表示MNIST图片,样例数据如下。 - -.. literalinclude:: mnist_train.txt - -其数据使用;间隔,第一段数据为这张图片的label,第二段数据为这个图片的像素值。 -首先我们将这个数据文件(例如文件名是'mnist_train.txt')写入train.list。那么 -train.list即为 - -.. literalinclude:: train.list - -那么对应的dataprovider既为 - -.. literalinclude:: mnist_provider.py - :linenos: - -其中第一行是引入PaddlePaddle的PyDataProvider2包。主要函数是process函数。process函数 -具有两个参数,第一个参数是 settings 。这个参数在这个样例里没有使用,具 -体可以参考 settings 。第二个参数是filename,这个参数被PaddlePaddle进程传入,为 -train.list中的一行(即train.list若干数据文件路径的某一个路径)。 - -:code:`@provider` 是一个Python的 `Decorator `_ -。这行的作用是设置DataProvider的一些属性,并且标记process函数是一个DataProvider。 -如果不了解 `Decorator `_ 是什么也没关系, -只需要知道这只是一个标记属性的方法就可以了。 - -属性 `input_types`_ 是设置这个DataProvider返回什么样的数据。这里设置的是返回一个 -28*28的稠密向量和一个[0-9],10维的整数值。 `input_types`_ 具体可以设置成什么其他格 -式,请参考 `input_types`_ 的文档。 - -process函数是实现数据输入的主函数,在这个函数中,实现了打开文本文件,从文本文件中读取 -每一行,并将每行转换成和 `input_types`_ 一致的特征,并在23行返回给PaddlePaddle进程。需要注意 -的是, 返回的顺序需要和 `input_types`_ 中定义的顺序一致。 - -同时,返回数据在PaddlePaddle中是仅仅返回一条完整的训练样本,并且使用关键词 :code:`yield` 。 -在PyDataProvider中,可以为一个数据文件返回多条训练样本(就像这个样例一样),只需要在 -process函数调用多次 :code:`yield` 即可。 :code:`yield` 是Python的一个关键词,相关的概 -念是 :code:`generator` 。使用这个关键词,可以在一个函数里,多次返回变量。 - -在训练配置里,只需要使用一行代码即可以设置训练引用这个DataProvider。这个设置为 - -.. literalinclude:: mnist_config.py - -这里说明了训练数据是 'train.list',而没有测试数据。引用的DataProvider是 'mnist_provider' -这个模块中的 'process' 函数。 - -同时,根据模型配置文件中 :code:`data_layer` 的名字,用户也可以显式指定返回的数据对应关系。例如: - -.. literalinclude:: mnist_provider.dict.py - :linenos: - -如果用户不指定返回数据的对应关系,那么PaddlePaddle会粗略的根据layer的声明顺序, -来确定对应关系。这个对应关系可能不正确。所以推荐使用显式指定返回值和数据对应关系。 - -至此,简单的PyDataProvider样例就说明完毕了。对于用户来说,讲数据发送给PaddlePaddle,仅仅需要 -知道如何从 **一个文件** 里面读取 **一条** 样本。而PaddlePaddle进程帮助用户做了 - -* 将数据组合成Batch训练 -* Shuffle训练数据 -* 多线程数据读取 -* 缓存训练数据到内存(可选) -* CPU->GPU双缓存 - -是不是很简单呢? - -序列模型数据提供 ----------------- - -序列模型是指数据的某一维度是一个序列形式,即包含时间步信息。所谓时间步信息, -不一定和时间有关系,只是说明数据的顺序是重要的。例如,文本信息就是一个序列 -数据。 - -这里举例的数据是英文情感分类的数据。数据是给一段英文文本,分类成正面情绪和 -负面情绪两类(用0和1表示)。样例数据为 - -.. literalinclude:: sentimental_train.txt - -这里,DataProvider可以是 - -.. literalinclude:: sentimental_provider.py - -这个序列模型比较复杂。主要是增加了初始化机制。其中 :code:`on_init` 函数是使用 -`@provider`_ 中的 `init_hook`_ 配置参数配置给DataProvider的。这个函数会在 -DataProvider创建的时候执行。这个初始化函数具有如下参数: - -* 第一个参数是 settings 对象。 -* 其他参数均使用key word argument形式传入。有部分参数是Paddle自动生成的, - 参考 `init_hook`_ 。这里的 :code:`dictionary` 是从训练配置传入的dict对象。 - 即从单词字符串到单词id的字典。 - -传入这个变量的方式为 - -.. literalinclude:: sentimental_config.py - -这个声明基本上和mnist的样例一致。除了 - -* 在配置中读取了字典 -* 在声明DataProvider的时候传入了dictionary作为参数。 - -在 :code:`on_init` 函数中,配置了 `input_types` 。这个和在 `@provider`_ 中配置 -`input_types` 效果一致,但是在 `on_init` 中配置 `input_types` 是在运行时执行的,所以 -可以根据不同的数据配置不同的输入类型。这里的输入特征是词id的序列,所以将 :code:`seq_type` -设置成了序列(同时,也可以使用 :code:`integer_sequence` 类型来设置)。 - -同时,将字典存入了settings 对象。这个字典可以在 :code:`process` 函数中使用。 :code:`process` -函数中的 settings 和 :code:`on_init` 中的settings 是同一个对象。 - -而在 :code:`process` 函数中,基本的处理逻辑也和mnist逻辑一致。依次返回了文件中的每条数据。 - -至此,基本的PyDataProvider使用介绍完毕了。具体DataProvider还具有什么功能,请参考下节reference。 - -参考(Reference) ---------------- - -@provider -+++++++++ - -:code:`@provider` 是一个Python的 `Decorator`_ ,他可以将某一个函数标记成一个PyDataProvider。它包含的参数有: - -* `input_types`_ 是数据输入格式。具体有哪些格式,参考 `input_types`_ 。 -* should_shuffle 是个DataProvider是不是要做shuffle,如果不设置的话,训练的时候默认shuffle, - 测试的时候默认不shuffle。 -* min_pool_size 是设置DataProvider在内存中最小暂存的数据条数。这个也是PaddlePaddle所能够保证的shuffle粒度。 - 设置成-1的话,会预先读取全部数据到内存中。 -* pool_size 是设置DataProvider在内存中暂存的数据条数。设置成-1的话,即不在乎内存暂存多少条数据。 -* can_over_batch_size 表示是否允许Paddle暂存略微多余pool_size的数据。这样做可以避免很多死锁问题。 - 一般推荐设置成True -* calc_batch_size 传入的是一个函数,这个函数以一条数据为参数,返回batch_size的大小。默认情况下一条数据 - 是一个batch size,但是有时为了计算均衡性,可以将一条数据设置成多个batch size -* cache 是数据缓存的策略,参考 `cache`_ -* init_hook 是初始化时调用的函数,参考 `init_hook`_ -* check 设置成true的话,会根据input_types检查数据的合法性。 -* check_fail_continue 如果设置成true的话,即使在check中数据不合法,也会扔到这条数据,继续训练。 如果 - check是false的话,没有作用。 - -input_types -+++++++++++ - -PaddlePaddle的数据包括四种主要类型,和三种序列模式。其中,四种数据类型是 - -* dense_vector 表示稠密的浮点数向量。 -* sparse_binary_vector 表示稀疏的零一向量,即大部分值为0,有值的位置只能取1 -* sparse_float_vector 表示稀疏的向量,即大部分值为0,有值的部分可以是任何浮点数 -* integer 表示整数标签。 - -而三种序列模式为 - -* SequenceType.NO_SEQUENCE 即不是一条序列 -* SequenceType.SEQUENCE 即是一条时间序列 -* SequenceType.SUB_SEQUENCE 即是一条时间序列,且序列的每一个元素还是一个时间序列。 - -不同的数据类型和序列模式返回的格式不同,列表如下 - -+----------------------+---------------------+-----------------------------------+------------------------------------------------+ -| | NO_SEQUENCE | SEQUENCE | SUB_SEQUENCE | -+======================+=====================+===================================+================================================+ -| dense_vector | [f, f, ...] | [[f, ...], [f, ...], ...] | [[[f, ...], ...], [[f, ...], ...],...] | -+----------------------+---------------------+-----------------------------------+------------------------------------------------+ -| sparse_binary_vector | [i, i, ...] | [[i, ...], [i, ...], ...] | [[[i, ...], ...], [[i, ...], ...],...] | -+----------------------+---------------------+-----------------------------------+------------------------------------------------+ -| sparse_float_vector | [(i,f), (i,f), ...] | [[(i,f), ...], [(i,f), ...], ...] | [[[(i,f), ...], ...], [[(i,f), ...], ...],...] | -+----------------------+---------------------+-----------------------------------+------------------------------------------------+ -| integer_value | i | [i, i, ...] | [[i, ...], [i, ...], ...] | -+----------------------+---------------------+-----------------------------------+------------------------------------------------+ - -其中,f代表一个浮点数,i代表一个整数。 - -init_hook -+++++++++ - -init_hook可以传入一个函数。这个函数在初始化的时候会被调用。这个函数的参数是: - -* 第一个参数是 settings 对象。这个对象和process的第一个参数一致。具有的属性有 - * settings.input_types 设置输入类型。参考 `input_types`_ - * settings.logger 一个logging对象 -* 其他参数都使用key word argument传入。这些参数包括paddle定义的参数,和用户传入的参数。 - * Paddle定义的参数包括: - * is_train bool参数,表示这个DataProvider是训练用的DataProvider或者测试用的 - DataProvider - * file_list 所有文件列表。 - * 用户定义的参数使用args在训练配置中设置。 - -注意,PaddlePaddle保留添加参数的权力,所以init_hook尽量使用 :code:`**kwargs` , 来接受不使用的 -函数来保证兼容性。 - -cache -+++++ - -DataProvider提供了两种简单的Cache策略。他们是 - -* CacheType.NO_CACHE 不缓存任何数据,每次都会从python端读取数据 -* CacheType.CACHE_PASS_IN_MEM 第一个pass会从python端读取数据,剩下的pass会直接从内存里 - 读取数据。 - - -注意事项 --------- - -可能的内存泄露问题 -++++++++++++++++++ - -PaddlePaddle将train.list中的每一行,都传递给process函数,从而生成多个generator。 -即如果train.list中,有100个训练文件,即会生成100个generator。这个本身不是一个很 -严重的问题。 - -但是,如果在训练时,每一条训练数据都是一个文件,并且,训练数据非常多的情况下,就 -会生成多个generator。每个generator在没有调用的时候,是几乎不占内存的。但是,当调 -用过一次的时候,generator便会存下当前的上下文(Context)。而这个Context可能会非常 -大。并且,generator至少调用两次才会知道是否停止。所以,即使在process里面只会有一 -个yield,也需要两次随机选择到同样的generator的时候,才会释放该段内存。 - -.. code-block:: python - - def func(): - yield 0 - - f = func() # 创建generator - tmp = next(f) # 调用一次,返回0 - tmp = next(f) # 调用第二次的时候,才会Stop Iteration - -而如果按顺序调用这些generator就不会出现这个问题。 - -所以最佳实践推荐不要将每一个样本都放入train.list。而是将样本的地址放入另一个文本 -文件,train.list写入那个文本文件的地址。 或者在python generator的上下文中尽量留 -下非常少的变量引用。例如 - -.. code-block:: python - - def real_process(fn): - # ... read from fn - return result # 当函数返回的时候,python可以解除掉内部变量的引用。 - - def process(fn): - yield real_process(fn) - -这个问题是PyDataProvider读数据时候的逻辑问题,基本上不能整体修正。 - - -内存不够用的情况 -++++++++++++++++ - -PyDataProvider2会尽量使用内存。所以如果对于内存比较小的机器,推荐设置 -:code:`pool_size` 变量,而这个变量推荐大于训练的batch size,并且在内存足够 -的情况下越大越好。 - +PyDataProvider2的使用 +===================== + +PyDataProvider2是PaddlePaddle使用Python提供数据的接口。该接口使用多线程读取数据,并提供了简单的Cache功能;同时可以使用户只关注如何从文件中读取每一条数据,而不用关心数据如何传输,如何存储等等。 + +.. contents:: + +MNIST的使用场景 +--------------- + +我们以MNIST手写识别为例,来说明如何使用最简单的PyDataProvider2。 + +样例数据 +++++++++ + +MNIST是一个包含有70,000张灰度图片的数字分类数据集。样例数据 ``mnist_train.txt`` 如下: + +.. literalinclude:: mnist_train.txt + +其中每行数据代表一张图片,行内使用 ``;`` 分成两部分。第一部分是图片的标签,为0-9中的一个数字;第二部分是28*28的图片像素灰度值。 对应的 ``train.list`` 为: + +.. literalinclude:: train.list + +dataprovider的使用 +++++++++++++++++++ + +.. literalinclude:: mnist_provider.dict.py + +- 首先,引入PaddlePaddle的PyDataProvider2包。 +- 其次,定义一个Python的 `Decorator `_ `@provider`_ 。用于将下一行的数据输入函数标记成一个PyDataProvider2,同时设置它的input_types属性。 + + - `input_types`_:设置这个PyDataProvider2返回什么样的数据。本例根据网络配置中 ``data_layer`` 的名字,显式指定返回的是一个28*28维的稠密浮点数向量和一个[0-9]的10维整数标签。 + + .. literalinclude:: mnist_config.py + :lines: 9-10 + + - 注意:如果用户不显示指定返回数据的对应关系,那么PaddlePaddle会根据layer的声明顺序,来确定对应关系。但这个关系可能不正确,所以推荐使用显式指定的方式来设置input_types。 +- 最后,实现数据输入函数(如本例的 ``process`` 函数)。 + + - 该函数的功能是:打开文本文件,读取每一行,将行中的数据转换成与input_types一致的格式,然后返回给PaddlePaddle进程。注意, + + - 返回的顺序需要和input_types中定义的顺序一致。 + - 返回时,必须使用关键词 ``yield`` 。一次yield调用,即返回一条完整的样本。如果想为一个数据文件返回多条样本,只需要在函数中调用多次yield即可(本例中使用for循环进行多次调用)。 + + - 该函数具有两个参数: + + - settings:在本例中没有使用,具体可以参考 `init_hook`_ 中的说明。 + - filename:为 ``train.list`` 或 ``test.list`` 中的一行,即若干数据文件路径的某一个。 + +网络配置中的调用 +++++++++++++++++ + +在网络配置里,只需要一行代码就可以调用这个PyDataProvider2,如, + +.. literalinclude:: mnist_config.py + :lines: 1-7 + +训练数据是 ``train.list`` ,测试数据没有,调用的PyDataProvider2是 ``mnist_provider`` 模块中的 ``process`` 函数。 + +时序模型的使用场景 +------------------ +样例数据 +++++++++ + +时序模型是指数据的某一维度是一个序列形式,即包含时间步信息。所谓时间步信息,不一定和时间有关系,只是说明数据的顺序是重要的。例如,文本信息就是一个序列数据。 + +本例采用英文情感分类的数据,即将一段英文文本数据,分类成正面情绪和负面情绪两类(用0和1表示)。样例数据 ``sentimental_train.txt`` 如下: + +.. literalinclude:: sentimental_train.txt + +dataprovider的使用 +++++++++++++++++++ + +相对MNIST而言,这个dataprovider较复杂,主要原因是增加了初始化机制 `init_hook`_。本例的 ``on_init`` 函数就是根据该机制配置的,它会在dataprovider创建的时候执行。 + +- 其中 ``input_types`` 和在 `@provider`_ 中配置的效果一致。本例中的输入特征是词ID的序列,因此使用 ``integer_value_sequence`` 类型来设置。 +- 将 ``dictionary`` 存入settings对象,在 ``process`` 函数中使用。 dictionary是从网络配置中传入的dict对象,即一个将单词字符串映射到单词ID的字典。 + +.. literalinclude:: sentimental_provider.py + +网络配置中的调用 +++++++++++++++++ + +调用这个PyDataProvider2的方法,基本上和MNIST样例一致,除了 + +* 在配置中需要读取外部字典。 +* 在声明DataProvider的时候传入dictionary作为参数。 + +.. literalinclude:: sentimental_config.py + :emphasize-lines: 12-14 + +小结 +----- + +至此,两个PyDataProvider2的样例就说明完毕了。对用户来说,仅需要知道如何从 **一个文件** 中读取 **一条样本** ,就可以将数据传送给PaddlePaddle了。而PaddlePaddle则会帮用户做以下工作: + +* 将数据组合成Batch进行训练 +* 对训练数据进行Shuffle +* 多线程的数据读取 +* 缓存训练数据到内存(可选) +* CPU->GPU双缓存 + +是不是很简单呢? + +参考(Reference) +--------------- + +@provider ++++++++++ + +``@provider`` 是一个Python的 `Decorator`_ ,可以将某一个函数标记成一个PyDataProvider2。如果不了解 `Decorator`_ 是什么也没关系,只需知道这是一个标记属性的方法就可以了。它包含的属性参数如下: + +* input_types:数据输入格式。具体的格式说明,请参考 `input_types`_ 。 +* should_shuffle:是不是要对数据做Shuffle。训练时默认shuffle,测试时默认不shuffle。 +* min_pool_size:设置内存中最小暂存的数据条数,也是PaddlePaddle所能够保证的shuffle粒度。如果为-1,则会预先读取全部数据到内存中。 +* pool_size: 设置内存中暂存的数据条数。如果为-1(默认),则不在乎内存暂存多少条数据。如果设置,则推荐大于训练时batch size的值,并且在内存足够的情况下越大越好。 +* can_over_batch_size:是否允许暂存略微多余pool_size的数据。由于这样做可以避免很多死锁问题,一般推荐设置成True。 +* calc_batch_size:可以传入一个函数,用于自定义每条数据的batch size(默认为1)。 +* cache: 数据缓存的策略,具体请参考 `cache`_ 。 +* init_hook:初始化时调用的函数,具体请参考 `init_hook`_ 。 +* check:如果为true,会根据input_types检查数据的合法性。 +* check_fail_continue:如果为true,那么当check出数据不合法时,会扔到这条数据,继续训练或预测。(对check=false的情况,没有作用) + +input_types ++++++++++++ + +PaddlePaddle的数据包括四种主要类型,和三种序列模式。 + +四种数据类型: + +* dense_vector:稠密的浮点数向量。 +* sparse_binary_vector:稀疏的01向量,即大部分值为0,但有值的地方必须为1。 +* sparse_float_vector:稀疏的向量,即大部分值为0,但有值的部分可以是任何浮点数。 +* integer:整数标签。 + +三种序列模式: + +* SequenceType.NO_SEQUENCE:不是一条序列 +* SequenceType.SEQUENCE:是一条时间序列 +* SequenceType.SUB_SEQUENCE: 是一条时间序列,且序列的每一个元素还是一个时间序列。 + +不同的数据类型和序列模式返回的格式不同,列表如下: + ++----------------------+---------------------+-----------------------------------+------------------------------------------------+ +| | NO_SEQUENCE | SEQUENCE | SUB_SEQUENCE | ++======================+=====================+===================================+================================================+ +| dense_vector | [f, f, ...] | [[f, ...], [f, ...], ...] | [[[f, ...], ...], [[f, ...], ...],...] | ++----------------------+---------------------+-----------------------------------+------------------------------------------------+ +| sparse_binary_vector | [i, i, ...] | [[i, ...], [i, ...], ...] | [[[i, ...], ...], [[i, ...], ...],...] | ++----------------------+---------------------+-----------------------------------+------------------------------------------------+ +| sparse_float_vector | [(i,f), (i,f), ...] | [[(i,f), ...], [(i,f), ...], ...] | [[[(i,f), ...], ...], [[(i,f), ...], ...],...] | ++----------------------+---------------------+-----------------------------------+------------------------------------------------+ +| integer_value | i | [i, i, ...] | [[i, ...], [i, ...], ...] | ++----------------------+---------------------+-----------------------------------+------------------------------------------------+ + +其中,f代表一个浮点数,i代表一个整数。 + +init_hook ++++++++++ + +init_hook可以传入一个函数。该函数在初始化的时候会被调用,其参数如下: + +* 第一个参数是settings对象,它和数据传入函数的第一个参数(如本例中 ``process`` 函数的 ``settings`` 参数)必须一致。该对象具有以下两个属性: + * settings.input_types:数据输入格式,具体请参考 `input_types`_ 。 + * settings.logger:一个logging对象。 +* 其他参数使用 ``kwargs`` (key word arguments)传入,包括以下两种: + * PaddlePaddle定义的参数: 1)is_train:bool型参数,表示用于训练或预测;2)file_list:所有文件列表。 + * 用户定义的参数:使用args在网络配置中设置。 + +cache ++++++ + +PyDataProvider2提供了两种简单的Cache策略: + +* CacheType.NO_CACHE:不缓存任何数据,每次都会从python端读取数据 +* CacheType.CACHE_PASS_IN_MEM:第一个pass会从python端读取数据,剩下的pass会直接从内存里 + 读取数据。 + + +注意事项 +-------- + +可能的内存泄露问题 +++++++++++++++++++ + +PaddlePaddle将train.list中的每一行都传递给process函数,从而生成多个generator。当训练数据非常多时,就会生成非常多的generator。 + +虽然每个generator在没有调用的时候,是几乎不占内存的;但当调用过一次后,generator便会存下当前的上下文(Context),而这个Context可能会非常大。并且,generator至少需要调用两次才会知道是否停止。所以,即使process函数里面只有一个yield,也需要两次随机选择到相同generator的时候,才会释放该段内存。 + +.. code-block:: python + + def func(): + yield 0 + + f = func() # 创建generator + tmp = next(f) # 调用一次,返回0 + tmp = next(f) # 调用第二次的时候,才会Stop Iteration + +由于顺序调用这些generator不会出现上述问题,因此有两种解决方案: + +1. **最佳推荐**:将样本的地址放入另一个文本文件,train.list写入那个文本文件的地址。即不要将每一个样本都放入train.list。 +2. 在generator的上下文中尽量留下非常少的变量引用,例如 + +.. code-block:: python + + def real_process(fn): + # ... read from fn + return result # 当函数返回的时候,python可以解除掉内部变量的引用。 + + def process(fn): + yield real_process(fn) + +注意:这个问题是PyDataProvider读数据时候的逻辑问题,很难整体修正。 + +内存不够用的情况 +++++++++++++++++ + +PyDataProvider2会尽可能多的使用内存。因此,对于内存较小的机器,推荐使用 ``pool_size`` 变量来设置内存中暂存的数据条。具体请参考 `@provider`_ 中的说明。 + diff --git a/doc_cn/ui/data_provider/sentimental_provider.py b/doc_cn/ui/data_provider/sentimental_provider.py index 0fb0bb88e9..14bd0e05a9 100644 --- a/doc_cn/ui/data_provider/sentimental_provider.py +++ b/doc_cn/ui/data_provider/sentimental_provider.py @@ -8,19 +8,16 @@ def on_init(settings, dictionary, **kwargs): # set input types in runtime. It will do the same thing as # @provider(input_types) will do, but it is set dynamically during runtime. - settings.input_types = [ + settings.input_types = { # The text is a sequence of integer values, and each value is a word id. # The whole sequence is the sentences that we want to predict its # sentimental. - integer_value( - len(dictionary), seq_type=SequenceType), # text input + 'data': integer_value_sequence(len(dictionary)), # text input + 'label': integer_value(2) # label positive/negative + } - # label positive/negative - integer_value(2) - ] - - # save dictionary as settings.dictionary. It will be used in process - # method. + # save dictionary as settings.dictionary. + # It will be used in process method. settings.dictionary = dictionary diff --git a/doc_cn/ui/data_provider/write_new_dataprovider.rst b/doc_cn/ui/data_provider/write_new_dataprovider.rst deleted file mode 100644 index a2495fe663..0000000000 --- a/doc_cn/ui/data_provider/write_new_dataprovider.rst +++ /dev/null @@ -1,4 +0,0 @@ -自定义一个DataProvider -==================== - -TBD \ No newline at end of file diff --git a/doc_cn/ui/index.rst b/doc_cn/ui/index.rst index 8079bd9180..c53ebeefe1 100644 --- a/doc_cn/ui/index.rst +++ b/doc_cn/ui/index.rst @@ -8,8 +8,8 @@ .. toctree:: :maxdepth: 1 - data_provider/index.rst - + data_provider/dataprovider.rst + data_provider/pydataprovider2.rst 命令行参数 ========== @@ -22,9 +22,8 @@ * `参数描述 <../../doc/ui/cmd_argument/detail_introduction.html>`_ * `参数用例 <../../doc/ui/cmd_argument/use_case.html>`_ - 预测 -==== +======= .. toctree:: -- GitLab From aa6c6dc07d65de97e65e050224e4b07d4f5b0f98 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 22:00:20 +0800 Subject: [PATCH 0138/1503] Fix pythonlib can not be found --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af193c27ae..38daa35483 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,12 @@ include(package) find_package(SWIG 2.0) find_package(CUDA QUIET) find_package(Protobuf REQUIRED) -find_package(PythonLibs 2.7 REQUIRED) + +# Set up the versions we know about, in the order we will search. +# Always add the user supplied additional versions to the front. +set(Python_ADDITIONAL_VERSIONS 2.7) find_package(PythonInterp 2.7 REQUIRED) +find_package(PythonLibs 2.7 REQUIRED) find_package(ZLIB REQUIRED) find_package(NumPy REQUIRED) find_package(Threads REQUIRED) -- GitLab From 74c48d14d937df5a457746a6f71eae85d5b792c8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 25 Nov 2016 22:54:23 +0800 Subject: [PATCH 0139/1503] Revert cmake modification in this pr. --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38daa35483..af193c27ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,8 @@ include(package) find_package(SWIG 2.0) find_package(CUDA QUIET) find_package(Protobuf REQUIRED) - -# Set up the versions we know about, in the order we will search. -# Always add the user supplied additional versions to the front. -set(Python_ADDITIONAL_VERSIONS 2.7) -find_package(PythonInterp 2.7 REQUIRED) find_package(PythonLibs 2.7 REQUIRED) +find_package(PythonInterp 2.7 REQUIRED) find_package(ZLIB REQUIRED) find_package(NumPy REQUIRED) find_package(Threads REQUIRED) -- GitLab From aca08255d2c2a9c7ca06ce4c53765b366f66a227 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 25 Nov 2016 22:05:45 -0800 Subject: [PATCH 0140/1503] Allow docker build to build from local Git commit. --- paddle/scripts/docker/Dockerfile.m4 | 40 +++++++++++++++---- paddle/scripts/docker/build.sh | 40 +++++-------------- paddle/scripts/docker/generate.sh | 60 ++++++----------------------- 3 files changed, 53 insertions(+), 87 deletions(-) mode change 100644 => 100755 paddle/scripts/docker/build.sh mode change 100644 => 100755 paddle/scripts/docker/generate.sh diff --git a/paddle/scripts/docker/Dockerfile.m4 b/paddle/scripts/docker/Dockerfile.m4 index e14493ed9e..7f08e66653 100644 --- a/paddle/scripts/docker/Dockerfile.m4 +++ b/paddle/scripts/docker/Dockerfile.m4 @@ -1,12 +1,36 @@ FROM PADDLE_BASE_IMAGE MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 + +# It is good to run apt-get install with Dockerfile RUN directive, +# because if the following invocation to /root/build.sh fails, `docker +# build` wouldn't have to re-install packages after we fix +# /root/build.sh. +RUN apt-get update && \ + apt-get install -y cmake libprotobuf-dev protobuf-compiler git \ + libgoogle-glog-dev libgflags-dev libatlas-dev libatlas3-base g++ m4 python-pip \ + python-protobuf python-numpy python-dev swig openssh-server \ + wget unzip python-matplotlib tar xz-utils bzip2 gzip coreutils \ + sed grep graphviz libjpeg-dev zlib1g-dev doxygen && \ + apt-get clean -y +RUN pip install BeautifulSoup docopt PyYAML pillow \ + 'sphinx>=1.4.0' sphinx_rtd_theme breathe recommonmark + ENV WITH_GPU=PADDLE_WITH_GPU -ENV IS_DEVEL=PADDLE_IS_DEVEL -ENV WITH_DEMO=PADDLE_WITH_DEMO -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF ENV WITH_AVX=PADDLE_WITH_AVX -RUN cd /root/ && bash build.sh + +RUN mkdir /paddle +COPY . /paddle/ +COPY paddle/scripts/docker/build.sh /root/ +RUN /root/build.sh + +RUN echo 'export LD_LIBRARY_PATH=/usr/lib64:${LD_LIBRARY_PATH}' >> /etc/profile +RUN pip install /usr/local/opt/paddle/share/wheels/*.whl +RUN paddle version # print version after build + +# Configure OpenSSH server. c.f. https://docs.docker.com/engine/examples/running_ssh_service +RUN mkdir /var/run/sshd +RUN echo 'root:root' | chpasswd +RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config +RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config +EXPOSE 22 +CMD ["/usr/sbin/sshd", "-D"] diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh old mode 100644 new mode 100755 index ec5f3bd967..8e2e26b6ba --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -7,43 +7,21 @@ function abort(){ trap 'abort' 0 set -e -if [ ${USE_UBUNTU_MIRROR} == "ON" ]; then - sed -i 's#http://archive\.ubuntu\.com/ubuntu/#mirror://mirrors\.ubuntu\.com/mirrors\.txt#g'\ - /etc/apt/sources.list -fi -apt-get update -apt-get install -y cmake libprotobuf-dev protobuf-compiler git \ - libgoogle-glog-dev libgflags-dev libatlas-dev libatlas3-base g++ m4 python-pip\ - python-protobuf python-numpy python-dev swig if [ ${WITH_GPU} == 'ON' ]; then ln -s /usr/lib/x86_64-linux-gnu/libcudnn.so /usr/lib/libcudnn.so fi -cd ~ -git clone https://github.com/baidu/Paddle.git paddle -cd paddle -git checkout ${GIT_CHECKOUT} -mkdir build -cd build -cmake .. -DWITH_DOC=OFF -DWITH_GPU=${WITH_GPU} -DWITH_SWIG_PY=ON\ - -DCUDNN_ROOT=/usr/ -DWITH_STYLE_CHECK=OFF -DWITH_AVX=${WITH_AVX} +mkdir -p /paddle/build # -p means no error if exists +cd /paddle/build +cmake .. \ + -DWITH_DOC=ON \ + -DWITH_GPU=${WITH_GPU} \ + -DWITH_AVX=${WITH_AVX} \ + -DWITH_SWIG_PY=ON \ + -DCUDNN_ROOT=/usr/ \ + -DWITH_STYLE_CHECK=OFF make -j `nproc` -# because durning make install, there are several warning, so set +e, do not cause abort make install -echo 'export LD_LIBRARY_PATH=/usr/lib64:${LD_LIBRARY_PATH}' >> /etc/profile -pip ${PIP_GENERAL_ARGS} install ${PIP_INSTALL_ARGS} /usr/local/opt/paddle/share/wheels/*.whl -paddle version # print version after build -if [ ${WITH_DEMO} == "ON" ]; then - apt-get install -y wget unzip perl python-matplotlib tar xz-utils bzip2 gzip coreutils\ - sed grep graphviz libjpeg-dev zlib1g-dev - pip ${PIP_GENERAL_ARGS} install ${PIP_INSTALL_ARGS} BeautifulSoup docopt \ - PyYAML pillow -fi -if [ ${IS_DEVEL} == "OFF" ]; then # clean build packages. - cd ~ - rm -rf paddle -fi -apt-get clean -y trap : 0 diff --git a/paddle/scripts/docker/generate.sh b/paddle/scripts/docker/generate.sh old mode 100644 new mode 100755 index 2ad7527db1..b808a62ec2 --- a/paddle/scripts/docker/generate.sh +++ b/paddle/scripts/docker/generate.sh @@ -1,60 +1,24 @@ #!/bin/bash + set -e cd `dirname $0` -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=OFF -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=ON\ + +m4 -DPADDLE_WITH_GPU=OFF \ + -DPADDLE_WITH_AVX=ON \ + -DPADDLE_BASE_IMAGE=ubuntu:14.04 \ Dockerfile.m4 > Dockerfile.cpu -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=OFF -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=OFF\ +m4 -DPADDLE_WITH_GPU=OFF \ + -DPADDLE_WITH_AVX=OFF \ + -DPADDLE_BASE_IMAGE=ubuntu:14.04 \ Dockerfile.m4 > Dockerfile.cpu-noavx -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=OFF\ - Dockerfile.m4 > Dockerfile.cpu-noavx-devel - -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=ON\ - Dockerfile.m4 > Dockerfile.cpu-devel - - -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=ON \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=ON\ - Dockerfile.m4 > Dockerfile.cpu-demo - -m4 -DPADDLE_WITH_GPU=OFF -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=ON \ - -DPADDLE_BASE_IMAGE=ubuntu:14.04 -DPADDLE_WITH_AVX=OFF\ - Dockerfile.m4 > Dockerfile.cpu-noavx-demo - - -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=OFF -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ +m4 -DPADDLE_WITH_GPU=ON \ -DPADDLE_WITH_AVX=ON \ - Dockerfile.m4 > Dockerfile.gpu - -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=OFF -DPADDLE_WITH_DEMO=OFF \ -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ - -DPADDLE_WITH_AVX=OFF \ - Dockerfile.m4 > Dockerfile.gpu-noavx - - -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ - -DPADDLE_WITH_AVX=ON \ - Dockerfile.m4 > Dockerfile.gpu-devel + Dockerfile.m4 > Dockerfile.gpu -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=OFF \ - -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ +m4 -DPADDLE_WITH_GPU=ON \ -DPADDLE_WITH_AVX=OFF \ - Dockerfile.m4 > Dockerfile.gpu-noavx-devel - -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=ON \ -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ - -DPADDLE_WITH_AVX=ON \ - Dockerfile.m4 > Dockerfile.gpu-demo - - -m4 -DPADDLE_WITH_GPU=ON -DPADDLE_IS_DEVEL=ON -DPADDLE_WITH_DEMO=ON \ - -DPADDLE_BASE_IMAGE=nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 \ - -DPADDLE_WITH_AVX=OFF \ - Dockerfile.m4 > Dockerfile.gpu-noavx-demo + Dockerfile.m4 > Dockerfile.gpu-noavx -- GitLab From 4e2071f47e488f9f7fd74fbe4af077ffd0edd9da Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 25 Nov 2016 22:06:16 -0800 Subject: [PATCH 0141/1503] Make cmake derive Paddle version from Git tag or Git commit ID --- CMakeLists.txt | 50 +++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af193c27ae..3ad8663578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 2.8) project(paddle CXX C) -set(PADDLE_MAJOR_VERSION 0) -set(PADDLE_MINOR_VERSION 9) -set(PADDLE_PATCH_VERSION 0a0) -set(PADDLE_VERSION ${PADDLE_MAJOR_VERSION}.${PADDLE_MINOR_VERSION}.${PADDLE_PATCH_VERSION}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) @@ -64,31 +60,31 @@ include(check_packages) include(swig) include(coveralls) -# add PaddlePaddle version -if(DEFINED ENV{PADDLE_VERSION}) - add_definitions(-DPADDLE_VERSION=\"$ENV{PADDLE_VERSION}\") +# Set PaddlePaddle version to Git tag name or Git commit ID. +find_package(Git REQUIRED) +execute_process( + COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match --abbrev=0 + WORKING_DIRECTORY ${PROJ_ROOT} + OUTPUT_VARIABLE GIT_TAG_NAME + RESULT_VARIABLE GIT_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +if(NOT ${GIT_RESULT}) + set(PADDLE_VERSION ${GIT_TAG_NAME}) else() - if(EXISTS ${PROJ_ROOT}/.svn/) - find_package(Subversion REQUIRED) - if(SUBVERSION_FOUND) - Subversion_WC_INFO(${PROJ_ROOT} Project) - add_definitions(-DPADDLE_VERSION=${Project_WC_REVISION}) - endif() - elseif(EXISTS ${PROJ_ROOT}/.git/) - find_package(Git REQUIRED) - execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%H - WORKING_DIRECTORY ${PROJ_ROOT} - OUTPUT_VARIABLE GIT_SHA1 - RESULT_VARIABLE GIT_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT ${GIT_RESULT}) - add_definitions(-DPADDLE_VERSION=\"${GIT_SHA1}\") - else() - message(WARNING "Cannot add paddle version from git tag") - endif() - endif() + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%H + WORKING_DIRECTORY ${PROJ_ROOT} + OUTPUT_VARIABLE GIT_SHA1 + RESULT_VARIABLE GIT_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT ${GIT_RESULT}) + set(PADDLE_VERSION ${GIT_SHA1}) + else() + set(PADDLE_VERSION "unknown") + message(WARNING "Cannot add paddle version from git tag") + endif() endif() +add_definitions(-DPADDLE_VERSION=\"${PADDLE_VERSION}\") if(NOT WITH_GPU) -- GitLab From 88802d15cd2a5953efba978040efca86242ba1a2 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 25 Nov 2016 22:10:38 -0800 Subject: [PATCH 0142/1503] Remove template execution results from Dockerfile.m4 --- paddle/scripts/docker/Dockerfile.cpu | 12 ------------ paddle/scripts/docker/Dockerfile.cpu-demo | 12 ------------ paddle/scripts/docker/Dockerfile.cpu-devel | 12 ------------ paddle/scripts/docker/Dockerfile.cpu-noavx | 12 ------------ paddle/scripts/docker/Dockerfile.cpu-noavx-demo | 12 ------------ paddle/scripts/docker/Dockerfile.cpu-noavx-devel | 12 ------------ paddle/scripts/docker/Dockerfile.gpu | 12 ------------ paddle/scripts/docker/Dockerfile.gpu-demo | 12 ------------ paddle/scripts/docker/Dockerfile.gpu-devel | 12 ------------ paddle/scripts/docker/Dockerfile.gpu-noavx | 12 ------------ paddle/scripts/docker/Dockerfile.gpu-noavx-demo | 12 ------------ paddle/scripts/docker/Dockerfile.gpu-noavx-devel | 12 ------------ 12 files changed, 144 deletions(-) delete mode 100644 paddle/scripts/docker/Dockerfile.cpu delete mode 100644 paddle/scripts/docker/Dockerfile.cpu-demo delete mode 100644 paddle/scripts/docker/Dockerfile.cpu-devel delete mode 100644 paddle/scripts/docker/Dockerfile.cpu-noavx delete mode 100644 paddle/scripts/docker/Dockerfile.cpu-noavx-demo delete mode 100644 paddle/scripts/docker/Dockerfile.cpu-noavx-devel delete mode 100644 paddle/scripts/docker/Dockerfile.gpu delete mode 100644 paddle/scripts/docker/Dockerfile.gpu-demo delete mode 100644 paddle/scripts/docker/Dockerfile.gpu-devel delete mode 100644 paddle/scripts/docker/Dockerfile.gpu-noavx delete mode 100644 paddle/scripts/docker/Dockerfile.gpu-noavx-demo delete mode 100644 paddle/scripts/docker/Dockerfile.gpu-noavx-devel diff --git a/paddle/scripts/docker/Dockerfile.cpu b/paddle/scripts/docker/Dockerfile.cpu deleted file mode 100644 index 69b8363b7a..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=OFF -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.cpu-demo b/paddle/scripts/docker/Dockerfile.cpu-demo deleted file mode 100644 index ccbd183ee3..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu-demo +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=ON -ENV WITH_DEMO=ON -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.cpu-devel b/paddle/scripts/docker/Dockerfile.cpu-devel deleted file mode 100644 index 36460384f3..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu-devel +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=ON -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.cpu-noavx b/paddle/scripts/docker/Dockerfile.cpu-noavx deleted file mode 100644 index fa3b7427b0..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu-noavx +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=OFF -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.cpu-noavx-demo b/paddle/scripts/docker/Dockerfile.cpu-noavx-demo deleted file mode 100644 index 61315f762d..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu-noavx-demo +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=ON -ENV WITH_DEMO=ON -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.cpu-noavx-devel b/paddle/scripts/docker/Dockerfile.cpu-noavx-devel deleted file mode 100644 index 7636531199..0000000000 --- a/paddle/scripts/docker/Dockerfile.cpu-noavx-devel +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=OFF -ENV IS_DEVEL=ON -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu b/paddle/scripts/docker/Dockerfile.gpu deleted file mode 100644 index 1e023ae281..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=OFF -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu-demo b/paddle/scripts/docker/Dockerfile.gpu-demo deleted file mode 100644 index 92b0dca402..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu-demo +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=ON -ENV WITH_DEMO=ON -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu-devel b/paddle/scripts/docker/Dockerfile.gpu-devel deleted file mode 100644 index fb6f351fd2..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu-devel +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=ON -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=ON -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu-noavx b/paddle/scripts/docker/Dockerfile.gpu-noavx deleted file mode 100644 index 7567e62025..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu-noavx +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=OFF -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu-noavx-demo b/paddle/scripts/docker/Dockerfile.gpu-noavx-demo deleted file mode 100644 index ac52484c5c..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu-noavx-demo +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=ON -ENV WITH_DEMO=ON -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh diff --git a/paddle/scripts/docker/Dockerfile.gpu-noavx-devel b/paddle/scripts/docker/Dockerfile.gpu-noavx-devel deleted file mode 100644 index 19202f306b..0000000000 --- a/paddle/scripts/docker/Dockerfile.gpu-noavx-devel +++ /dev/null @@ -1,12 +0,0 @@ -FROM nvidia/cuda:7.5-cudnn5-devel-ubuntu14.04 -MAINTAINER PaddlePaddle Dev Team -COPY build.sh /root/ -ENV GIT_CHECKOUT=v0.9.0a0 -ENV WITH_GPU=ON -ENV IS_DEVEL=ON -ENV WITH_DEMO=OFF -ENV PIP_INSTALL_ARGS "" -ENV PIP_GENERAL_ARGS "" -ENV USE_UBUNTU_MIRROR OFF -ENV WITH_AVX=OFF -RUN cd /root/ && bash build.sh -- GitLab From 74bcf34f2db8e594731bbd613fb50ce2d69e3bed Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 25 Nov 2016 23:10:08 -0800 Subject: [PATCH 0143/1503] Update document --- doc/build/docker_install.rst | 129 +++++++++++------------------------ 1 file changed, 41 insertions(+), 88 deletions(-) diff --git a/doc/build/docker_install.rst b/doc/build/docker_install.rst index e95de35f4d..93e6f6dd7e 100644 --- a/doc/build/docker_install.rst +++ b/doc/build/docker_install.rst @@ -1,71 +1,39 @@ -Docker installation guide -========================== +Using and Building Docker Images +================================ -PaddlePaddle provide the `Docker `_ image. `Docker`_ is a lightweight container utilities. The performance of PaddlePaddle in `Docker`_ container is basically as same as run it in a normal linux. The `Docker`_ is a very convenient way to deliver the binary release for linux programs. +We release PaddlePaddle in the form of `Docker `_ images on `dockerhub.com `_. Running as Docker containers is currently the only officially-supported way to running PaddlePaddle. -.. note:: +Run Docker images +----------------- - The `Docker`_ image is the recommended way to run PaddlePaddle +For each version of PaddlePaddle, we release 4 variants of Docker images: -PaddlePaddle Docker images --------------------------- ++-----------------+-------------+-------+ +| | CPU AVX | GPU | ++=================+=============+=======+ +| cpu | yes | no | ++-----------------+-------------+-------+ +| cpu-noavx | no | no | ++-----------------+-------------+-------+ +| gpu | yes | yes | ++-----------------+-------------+-------+ +| gpu-noavx | no | yes | ++-----------------+-------------+-------+ -There are 12 `images `_ for PaddlePaddle, and the name is :code:`paddle-dev/paddle`, tags are\: - - -+-----------------+------------------+------------------------+-----------------------+ -| | normal | devel | demo | -+=================+==================+========================+=======================+ -| CPU | cpu-latest | cpu-devel-latest | cpu-demo-latest | -+-----------------+------------------+------------------------+-----------------------+ -| GPU | gpu-latest | gpu-devel-latest | gpu-demo-latest | -+-----------------+------------------+------------------------+-----------------------+ -| CPU WITHOUT AVX | cpu-noavx-latest | cpu-devel-noavx-latest | cpu-demo-noavx-latest | -+-----------------+------------------+------------------------+-----------------------+ -| GPU WITHOUT AVX | gpu-noavx-latest | gpu-devel-noavx-latest | gpu-demo-noavx-latest | -+-----------------+------------------+------------------------+-----------------------+ - -And the three columns are: - -* normal\: The docker image only contains binary of PaddlePaddle. -* devel\: The docker image contains PaddlePaddle binary, source code and essential build environment. -* demo\: The docker image contains the dependencies to run PaddlePaddle demo. - -And the four rows are: - -* CPU\: CPU Version. Support CPU which has :code:`AVX` instructions. -* GPU\: GPU Version. Support GPU, and cpu has :code:`AVX` instructions. -* CPU WITHOUT AVX\: CPU Version, which support most CPU even doesn't have :code:`AVX` instructions. -* GPU WITHOUT AVX\: GPU Version, which support most CPU even doesn't have :code:`AVX` instructions. - -User can choose any version depends on machine. The following script can help you to detect your CPU support :code:`AVX` or not. +The following command line detects if your CPU supports :code:`AVX`. .. code-block:: bash - + if cat /proc/cpuinfo | grep -q avx ; then echo "Support AVX"; else echo "Not support AVX"; fi -If the output is :code:`Support AVX`, then you can choose the AVX version of PaddlePaddle, otherwise, you need select :code:`noavx` version of PaddlePaddle. For example, the CPU develop version of PaddlePaddle is :code:`paddle-dev/paddle:cpu-devel-latest`. -The PaddlePaddle images don't contain any entry command. You need to write your entry command to use this image. See :code:`Remote Access` part or just use following command to run a :code:`bash` +Once we determine the proper variant, we can cope with the Docker image tag name by appending the version number. For example, the following command runs the AVX-enabled image of the most recent version: .. code-block:: bash - docker run -it paddledev/paddle:cpu-latest /bin/bash + docker run -it --rm paddledev/paddle:cpu-latest /bin/bash - -Download and Run Docker images ------------------------------- - -You have to install Docker in your machine which has linux kernel version 3.10+ first. You can refer to the official guide https://docs.docker.com/engine/installation/ for further information. - -You can use :code:`docker pull ` to download images first, or just launch a container with :code:`docker run` \: - -.. code-block:: bash - - docker run -it paddledev/paddle:cpu-latest - - -If you want to launch container with GPU support, you need to set some environment variables at the same time: +To run a GPU-enabled image, you need to install CUDA and let Docker knows about it: .. code-block:: bash @@ -73,50 +41,35 @@ If you want to launch container with GPU support, you need to set some environme export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:gpu-latest +The default entry point of all our Docker images starts the OpenSSH server. To run PaddlePaddle and to expose OpenSSH port to 2202 on the host computer: -Some notes for docker ---------------------- - -Performance -+++++++++++ - -Since Docker is based on the lightweight virtual containers, the CPU computing performance maintains well. And GPU driver and equipments are all mapped to the container, so the GPU computing performance would not be seriously affected. - -If you use high performance nic, such as RDMA(RoCE 40GbE or IB 56GbE), Ethernet(10GbE), it is recommended to use config "-net = host". - - - +.. code-block:: bash -Remote access -+++++++++++++ + docker run -d -p 2202:22 paddledev/paddle:cpu-latest +Then we can login to the container using username :code:`root` and password :code:`root`: -If you want to enable ssh access background, you need to build an image by yourself. Please refer to official guide https://docs.docker.com/engine/reference/builder/ for further information. +.. code-block:: bash -Following is a simple Dockerfile with ssh: + ssh -p 2202 root@localhost -.. literalinclude:: ../../doc_cn/build_and_install/install/paddle_ssh.Dockerfile -Then you can build an image with Dockerfile and launch a container: +Build Docker images +------------------- -.. code-block:: bash +Developers might want to build Docker images from their local commit or from a tagged version. Suppose that your local repo is at :code:`~/work/Paddle`, the following steps builds a cpu variant from your current work: - # cd into Dockerfile directory - docker build . -t paddle_ssh - # run container, and map host machine port 8022 to container port 22 - docker run -d -p 8022:22 --name paddle_ssh_machine paddle_ssh +.. code-block:: bash -Now, you can ssh on port 8022 to access the container, username is root, password is also root: + cd ~/Paddle + ./paddle/scripts/docker/generates.sh # Use m4 to generate Dockerfiles for each variant. + docker build -t paddle:latest -f ./paddle/scripts/docker/Dockerfile.cpu -.. code-block:: bash +As a release engineer, you might want to build Docker images for a certain version and publish them to dockerhub.com. You can do this by switching to the right Git tag, or create a new tag, before running `docker build`. For example, the following commands build Docker images for v0.9.0: - ssh -p 8022 root@YOUR_HOST_MACHINE - -You can stop and delete the container as following: - -.. code-block:: bash +.. code-block:: bash - # stop - docker stop paddle_ssh_machine - # delete - docker rm paddle_ssh_machine + cd ~/Paddle + git checkout tags/v0.9.0 + ./paddle/scripts/docker/generates.sh # Use m4 to generate Dockerfiles for each variant. + docker build -t paddle:cpu-v0.9.0 -f ./paddle/scripts/docker/Dockerfile.cpu -- GitLab From a3a7e76c7fdce9a75d6e6b5a57c3f189b05bd3b7 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sat, 26 Nov 2016 20:38:56 -0800 Subject: [PATCH 0144/1503] Respond to Helin's comments --- doc/build/docker_install.rst | 20 +++++++++++++------- paddle/scripts/docker/Dockerfile.m4 | 3 ++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/doc/build/docker_install.rst b/doc/build/docker_install.rst index 93e6f6dd7e..5f272aabd7 100644 --- a/doc/build/docker_install.rst +++ b/doc/build/docker_install.rst @@ -20,22 +20,28 @@ For each version of PaddlePaddle, we release 4 variants of Docker images: | gpu-noavx | no | yes | +-----------------+-------------+-------+ -The following command line detects if your CPU supports :code:`AVX`. +We run the following command on Linux to check if the CPU supports :code:`AVX`. -.. code-block:: bash +.. code-block:: bash + + if cat /proc/cpuinfo | grep -i avx; then echo Yes; else echo No; fi + +On Mac OS X, we need to run + +.. code-block:: bash - if cat /proc/cpuinfo | grep -q avx ; then echo "Support AVX"; else echo "Not support AVX"; fi + sysctl -a | grep machdep.cpu.leaf7_features Once we determine the proper variant, we can cope with the Docker image tag name by appending the version number. For example, the following command runs the AVX-enabled image of the most recent version: -.. code-block:: bash +.. code-block:: bash docker run -it --rm paddledev/paddle:cpu-latest /bin/bash To run a GPU-enabled image, you need to install CUDA and let Docker knows about it: -.. code-block:: bash +.. code-block:: bash export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') @@ -43,13 +49,13 @@ To run a GPU-enabled image, you need to install CUDA and let Docker knows about The default entry point of all our Docker images starts the OpenSSH server. To run PaddlePaddle and to expose OpenSSH port to 2202 on the host computer: -.. code-block:: bash +.. code-block:: bash docker run -d -p 2202:22 paddledev/paddle:cpu-latest Then we can login to the container using username :code:`root` and password :code:`root`: -.. code-block:: bash +.. code-block:: bash ssh -p 2202 root@localhost diff --git a/paddle/scripts/docker/Dockerfile.m4 b/paddle/scripts/docker/Dockerfile.m4 index 7f08e66653..f2822acdde 100644 --- a/paddle/scripts/docker/Dockerfile.m4 +++ b/paddle/scripts/docker/Dockerfile.m4 @@ -4,7 +4,8 @@ MAINTAINER PaddlePaddle Dev Team # It is good to run apt-get install with Dockerfile RUN directive, # because if the following invocation to /root/build.sh fails, `docker # build` wouldn't have to re-install packages after we fix -# /root/build.sh. +# /root/build.sh. For more about Docker build cache, please refer to +# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/build-cache. RUN apt-get update && \ apt-get install -y cmake libprotobuf-dev protobuf-compiler git \ libgoogle-glog-dev libgflags-dev libatlas-dev libatlas3-base g++ m4 python-pip \ -- GitLab From 7573205c686477a5251d646aa905c177803366de Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 28 Nov 2016 12:46:24 +0800 Subject: [PATCH 0145/1503] follow comments on config_parser --- proto/ModelConfig.proto.m4 | 8 ++++---- python/paddle/trainer/config_parser.py | 16 ++++++++-------- .../configs/protostr/img_trans_layers.protostr | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/proto/ModelConfig.proto.m4 b/proto/ModelConfig.proto.m4 index 2270679e90..ac32c9c5fb 100644 --- a/proto/ModelConfig.proto.m4 +++ b/proto/ModelConfig.proto.m4 @@ -78,10 +78,10 @@ message ConvConfig { required uint32 stride_y = 12; // if not set, use output_x - optional uint32 output_y = 13 [default = 0]; + optional uint32 output_y = 13; // if not set, use img_size - optional uint32 img_size_y = 14 [default = 0]; + optional uint32 img_size_y = 14; } message PoolConfig { @@ -161,10 +161,10 @@ message NormConfig { optional bool blocked = 8; // if not set, use output_x - optional uint32 output_y = 9 [default = 0]; + optional uint32 output_y = 9; // if not set, use img_size - optional uint32 img_size_y = 10 [default = 0]; + optional uint32 img_size_y = 10; } message BlockExpandConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index f8c916068d..f917ead680 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1066,7 +1066,7 @@ def cnn_output_size(img_size, filter_size, padding, stride, caffe_mode): return 1 + int(math.ceil(output)) -#calcualte image_size based on output_size for convolution. +#calcualte image_size based on output_size for de-convolution (ConvTransLayer). #It is the reverse function of cnn_output_size def cnn_image_size(output_size, filter_size, padding, stride, caffe_mode): img_size = (output_size - 1) * stride + filter_size - 2 * padding @@ -1075,7 +1075,7 @@ def cnn_image_size(output_size, filter_size, padding, stride, caffe_mode): return img_size -def set_img_size(input_layer_name, channels): +def get_img_size(input_layer_name, channels): input = g_layer_map[input_layer_name] img_pixels = input.size / channels img_size = input.width if input.width > 0 else int(img_pixels**0.5) @@ -1110,7 +1110,7 @@ def parse_pool(pool, input_layer_name, pool_conf): pool_conf.stride_y = default(pool.stride_y, pool_conf.stride) pool_conf.img_size, pool_conf.img_size_y = \ - set_img_size(input_layer_name, pool.channels) + get_img_size(input_layer_name, pool.channels) config_assert(not pool.start, "start is deprecated in pooling.") @@ -1137,7 +1137,7 @@ def parse_spp(spp, input_layer_name, spp_conf): def parse_image(image, input_layer_name, image_conf): image_conf.channels = image.channels image_conf.img_size, image_conf.img_size_y = \ - set_img_size(input_layer_name, image_conf.channels) + get_img_size(input_layer_name, image_conf.channels) def parse_norm(norm, input_layer_name, norm_conf): @@ -1152,7 +1152,7 @@ def parse_norm(norm, input_layer_name, norm_conf): norm_conf.blocked = norm.blocked norm_conf.img_size, norm_conf.img_size_y = \ - set_img_size(input_layer_name, norm.channels) + get_img_size(input_layer_name, norm.channels) norm_conf.output_x = norm_conf.img_size norm_conf.output_y = norm_conf.img_size_y if norm.norm_type in ['cmrnorm-projection']: @@ -1177,7 +1177,7 @@ def parse_conv(conv, input_layer_name, conv_conf, num_filters, trans=False): if not trans: conv_conf.filter_channels = conv.channels / conv.groups conv_conf.img_size, conv_conf.img_size_y = \ - set_img_size(input_layer_name, conv.channels) + get_img_size(input_layer_name, conv.channels) conv_conf.output_x = cnn_output_size( conv_conf.img_size, conv_conf.filter_size, conv_conf.padding, conv_conf.stride, conv_conf.caffe_mode) @@ -1187,11 +1187,11 @@ def parse_conv(conv, input_layer_name, conv_conf, num_filters, trans=False): else: conv_conf.filter_channels = num_filters / conv.groups conv_conf.output_x, conv_conf.output_y = \ - set_img_size(input_layer_name, conv.channels) + get_img_size(input_layer_name, conv.channels) conv_conf.img_size = cnn_image_size( conv_conf.output_x, conv_conf.filter_size, conv_conf.padding, conv_conf.stride, conv_conf.caffe_mode) - conv_conf.img_size_y = cnn_output_size( + conv_conf.img_size_y = cnn_image_size( conv_conf.output_y, conv_conf.filter_size_y, conv_conf.padding_y, conv_conf.stride_y, conv_conf.caffe_mode) 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 ac1e2adff5..cd310bd13b 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 @@ -27,7 +27,7 @@ layers { padding_y: 1 stride_y: 1 output_y: 227 - img_size_y: 198 + img_size_y: 256 } } bias_parameter_name: "___conv_0__.wbias" -- GitLab From a1143460c602fcde55357f7d65b48f7c31fe1be1 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Mon, 28 Nov 2016 12:53:35 +0800 Subject: [PATCH 0146/1503] follow comments: refine ubuntu install doc --- .../build_and_install/install/ubuntu_install.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc_cn/build_and_install/install/ubuntu_install.rst b/doc_cn/build_and_install/install/ubuntu_install.rst index 08d55f98d9..e48e4932ac 100644 --- a/doc_cn/build_and_install/install/ubuntu_install.rst +++ b/doc_cn/build_and_install/install/ubuntu_install.rst @@ -1,7 +1,7 @@ Ubuntu部署PaddlePaddle =================================== -PaddlePaddle提供了deb安装包,并在ubuntu 14.04做了完备测试,理论上也支持其他的debian发行版。 +PaddlePaddle提供了ubuntu 14.04 deb安装包。 安装 ------ @@ -10,13 +10,13 @@ PaddlePaddle提供了deb安装包,并在ubuntu 14.04做了完备测试,理 它包含四个版本\: -* cpu版本: 支持主流intel x86处理器平台, 支持avx指令集。 +* cpu版本: 支持主流x86处理器平台, 使用了avx指令集。 -* cpu-noavx版本:支持主流intel x86处理器平台,不支持avx指令集。 +* cpu-noavx版本:支持主流x86处理器平台,没有使用avx指令集。 -* gpu版本:支持主流intel x86处理器平台,支持nvidia cuda平台,支持avx指令集。 +* gpu版本:支持主流x86处理器平台,支持nvidia cuda平台,使用了avx指令集。 -* gpu-noavx版本:支持主流intel x86处理器平台,支持nvidia cuda平台,不支持avx指令级。 +* gpu-noavx版本:支持主流x86处理器平台,支持nvidia cuda平台,没有使用avx指令集。 下载完相关安装包后,执行: @@ -43,7 +43,7 @@ PaddlePaddle提供了deb安装包,并在ubuntu 14.04做了完备测试,理 可能遇到的问题 -------------- -如何设置gpu版本运行时cuda环境运行GPU版本 +如何设置CUDA环境运行GPU版本 ++++++++++++++++++++++++++++++++++++++++ 如果使用GPU版本的PaddlePaddle,请安装CUDA 7.5 和CUDNN 5到本地环境中,并设置: @@ -62,5 +62,5 @@ libcudart.so/libcudnn.so找不到 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. -原因是未设置cuda运行时环境变量,请参考** 设置gpu版本运行时cuda环境** 解决方案。 +原因是未设置cuda运行时环境变量,请参考**如何设置CUDA环境运行GPU版本** 。 -- GitLab From bb97b47ee7057e4dfbb0dbc8ffa90a6704f28202 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Mon, 28 Nov 2016 12:56:30 +0800 Subject: [PATCH 0147/1503] add blank char to fix tiny rst doc --- doc_cn/build_and_install/install/ubuntu_install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_cn/build_and_install/install/ubuntu_install.rst b/doc_cn/build_and_install/install/ubuntu_install.rst index e48e4932ac..372ad6a944 100644 --- a/doc_cn/build_and_install/install/ubuntu_install.rst +++ b/doc_cn/build_and_install/install/ubuntu_install.rst @@ -62,5 +62,5 @@ libcudart.so/libcudnn.so找不到 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. -原因是未设置cuda运行时环境变量,请参考**如何设置CUDA环境运行GPU版本** 。 +原因是未设置cuda运行时环境变量,请参考 **如何设置CUDA环境运行GPU版本** 。 -- GitLab From f63bdc80a1f781abafd9b4d922050b3a2412dcf1 Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Mon, 28 Nov 2016 13:18:42 +0800 Subject: [PATCH 0148/1503] follow comments: more clean doc --- .../install/ubuntu_install.rst | 21 +++++++------------ doc_cn/faq/index.rst | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/doc_cn/build_and_install/install/ubuntu_install.rst b/doc_cn/build_and_install/install/ubuntu_install.rst index 372ad6a944..4500d6e0b0 100644 --- a/doc_cn/build_and_install/install/ubuntu_install.rst +++ b/doc_cn/build_and_install/install/ubuntu_install.rst @@ -43,24 +43,19 @@ PaddlePaddle提供了ubuntu 14.04 deb安装包。 可能遇到的问题 -------------- -如何设置CUDA环境运行GPU版本 -++++++++++++++++++++++++++++++++++++++++ - -如果使用GPU版本的PaddlePaddle,请安装CUDA 7.5 和CUDNN 5到本地环境中,并设置: - -.. code-block:: shell - export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/lib:$LD_LIBRARY_PATH - export PATH=/usr/local/cuda/bin:$PATH - - libcudart.so/libcudnn.so找不到 ++++++++++++++++++++++++++++++ 安装完成后,运行 :code:`paddle train` 报错\: -.. code-block:: shell +.. code-block:: shell - 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. + 0831 12:36:04.151525 1085 hl_dso_loader.cc:70] Check failed: nullptr != *dso_handle For Gpu version of PaddlePaddle, it couldn't find CUDA library: libcudart.so Please make sure you already specify its path.Note: for training data on Cpu using Gpu version of PaddlePaddle,you must specify libcudart.so via LD_LIBRARY_PATH. + +原因是未设置cuda运行时环境变量。 如果使用GPU版本的PaddlePaddle,请安装CUDA 7.5 和CUDNN 5到本地环境中,并设置: + +.. code-block:: shell -原因是未设置cuda运行时环境变量,请参考 **如何设置CUDA环境运行GPU版本** 。 + export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/lib:$LD_LIBRARY_PATH + export PATH=/usr/local/cuda/bin:$PATH diff --git a/doc_cn/faq/index.rst b/doc_cn/faq/index.rst index 6e1102e552..551430eb41 100644 --- a/doc_cn/faq/index.rst +++ b/doc_cn/faq/index.rst @@ -7,7 +7,7 @@ PaddlePaddle常见问题 1. 如何减少内存占用 --------------------------------- -神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数十G的内存和数G的显存。 +神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数10GB的内存和数GB的显存。 PaddlePaddle的内存占用主要分为如下几个方面\: * DataProvider缓冲池内存(只针对内存) -- GitLab From d6b0b5cb103c83c592bed8ce642250b33b09a8c3 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 28 Nov 2016 15:33:01 +0800 Subject: [PATCH 0149/1503] note on difference between sparse_vector and dense_vector --- doc_cn/ui/data_provider/pydataprovider2.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc_cn/ui/data_provider/pydataprovider2.rst b/doc_cn/ui/data_provider/pydataprovider2.rst index 3455e331da..001e0884be 100644 --- a/doc_cn/ui/data_provider/pydataprovider2.rst +++ b/doc_cn/ui/data_provider/pydataprovider2.rst @@ -155,6 +155,8 @@ PaddlePaddle的数据包括四种主要类型,和三种序列模式。 其中,f代表一个浮点数,i代表一个整数。 +注意:sparse_binary_vector和sparse_float_vector中的元素,可以有None;但dense_vector和integer中的元素,不能出现None。 + init_hook +++++++++ -- GitLab From ddb948a1131466348dc9d30deea42853ebbaae48 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 28 Nov 2016 16:26:42 +0800 Subject: [PATCH 0150/1503] refine doc_cn/ui/index.rst --- doc_cn/ui/cmd/dump_config.rst | 0 doc_cn/ui/cmd/index.rst | 25 ++++++++----------------- doc_cn/ui/cmd/make_diagram.rst | 0 doc_cn/ui/cmd/merge_model.rst | 0 doc_cn/ui/cmd/paddle_pserver.rst | 0 doc_cn/ui/cmd/paddle_train.rst | 0 doc_cn/ui/cmd/paddle_version.rst | 7 ------- doc_cn/ui/index.rst | 8 +++++--- doc_cn/ui/predict/swig_py_paddle.rst | 8 ++++---- 9 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 doc_cn/ui/cmd/dump_config.rst delete mode 100644 doc_cn/ui/cmd/make_diagram.rst delete mode 100644 doc_cn/ui/cmd/merge_model.rst delete mode 100644 doc_cn/ui/cmd/paddle_pserver.rst delete mode 100644 doc_cn/ui/cmd/paddle_train.rst delete mode 100644 doc_cn/ui/cmd/paddle_version.rst diff --git a/doc_cn/ui/cmd/dump_config.rst b/doc_cn/ui/cmd/dump_config.rst deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc_cn/ui/cmd/index.rst b/doc_cn/ui/cmd/index.rst index f975d432c0..31a8b8a79f 100644 --- a/doc_cn/ui/cmd/index.rst +++ b/doc_cn/ui/cmd/index.rst @@ -1,29 +1,20 @@ -PaddlePaddle的命令行参数 -======================== +命令 +==== -安装好PaddlePaddle后,在命令行直接敲击 ``paddle`` 或 ``paddle --help`` 会显示如下一些命令行参数。 +安装好PaddlePaddle后,在命令行直接敲击 ``paddle`` 或 ``paddle --help`` 会显示如下一些命令。 * ``train`` Start a paddle_trainer 启动一个PaddlePaddle训练进程。 ``paddle train`` 可以通过命令行参数 ``-local=true`` 启动一个单机的训练进程;也可以和 ``paddle pserver`` 一起使用启动多机的分布式训练进程。 * ``pserver`` Start a paddle_pserver_main 在多机分布式训练下启动PaddlePaddle的parameter server进程。 * ``version`` Print paddle version - 用于打印当前PaddlePaddle的版本和编译选项相关信息。 + 用于打印当前PaddlePaddle的版本和编译选项相关信息。常见的输出格式如下:1)第一行说明了PaddlePaddle的版本信息;2)第二行开始说明了一些主要的编译选项,具体意义可以参考 `编译参数选项文件 <../../build_and_install/cmake/compile_options.html>`_ 。 + + .. literalinclude:: paddle_version.txt + * ``merge_model`` Start a paddle_merge_model 用于将PaddlePaddle的模型参数文件和模型配置文件打包成一个文件,方便做部署分发。 * ``dump_config`` Dump the trainer config as proto string 用于将PaddlePaddle的模型配置文件以proto string的格式打印出来。 * ``make_diagram`` - 使用graphviz对PaddlePaddle的模型配置文件进行绘制。 - -更详细的介绍请参考各命令行参数文档。 - -.. toctree:: - :glob: - - paddle_train.rst - paddle_pserver.rst - paddle_version.rst - merge_model.rst - dump_config.rst - make_diagram.rst + 使用graphviz对PaddlePaddle的模型配置文件进行绘制。 \ No newline at end of file diff --git a/doc_cn/ui/cmd/make_diagram.rst b/doc_cn/ui/cmd/make_diagram.rst deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc_cn/ui/cmd/merge_model.rst b/doc_cn/ui/cmd/merge_model.rst deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc_cn/ui/cmd/paddle_pserver.rst b/doc_cn/ui/cmd/paddle_pserver.rst deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc_cn/ui/cmd/paddle_train.rst b/doc_cn/ui/cmd/paddle_train.rst deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc_cn/ui/cmd/paddle_version.rst b/doc_cn/ui/cmd/paddle_version.rst deleted file mode 100644 index 537c23df75..0000000000 --- a/doc_cn/ui/cmd/paddle_version.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddle version的命令行参数 -========================== - -paddle version用于打印当前的版本信息和相关编译选项。常见的输出格式如下。第一行说明了PaddlePaddle的版本信息,后面跟着一些主要的编译选项。编译选项的具体意义可以参考 -`编译参数选项文件 <../../build_and_install/cmake/compile_options.html>`_ - -.. literalinclude:: paddle_version.txt diff --git a/doc_cn/ui/index.rst b/doc_cn/ui/index.rst index 8079bd9180..d871ad805f 100644 --- a/doc_cn/ui/index.rst +++ b/doc_cn/ui/index.rst @@ -11,21 +11,23 @@ data_provider/index.rst -命令行参数 -========== +命令及命令行参数 +================ .. toctree:: + :maxdepth: 1 cmd/index.rst +* `参数用例 <../../doc/ui/cmd_argument/use_case.html>`_ * `参数分类 <../../doc/ui/cmd_argument/argument_outline.html>`_ * `参数描述 <../../doc/ui/cmd_argument/detail_introduction.html>`_ -* `参数用例 <../../doc/ui/cmd_argument/use_case.html>`_ 预测 ==== .. toctree:: + :maxdepth: 1 predict/swig_py_paddle.rst diff --git a/doc_cn/ui/predict/swig_py_paddle.rst b/doc_cn/ui/predict/swig_py_paddle.rst index 4c0a0de820..89031dd72f 100644 --- a/doc_cn/ui/predict/swig_py_paddle.rst +++ b/doc_cn/ui/predict/swig_py_paddle.rst @@ -1,8 +1,8 @@ 基于Python的预测 ================ -Python预测接口 --------------- +预测流程 +-------- PaddlePaddle使用swig对常用的预测接口进行了封装,通过编译会生成py_paddle软件包,安装该软件包就可以在python环境下实现模型预测。可以使用python的 ``help()`` 函数查询软件包相关API说明。 @@ -20,8 +20,8 @@ PaddlePaddle使用swig对常用的预测接口进行了封装,通过编译会 通过调用 ``forwardTest()`` 传入预测数据,直接返回计算结果。 -基于Python的预测Demo --------------------- +预测Demo +-------- 如下是一段使用mnist model来实现手写识别的预测代码。完整的代码见 ``src_root/doc/ui/predict/predict_sample.py`` 。mnist model可以通过 ``src_root\demo\mnist`` 目录下的demo训练出来。 -- GitLab From d853a43f67cc91c3b50dbbe04cc0a7853c3fabd6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 28 Nov 2016 16:41:33 +0800 Subject: [PATCH 0151/1503] Refine quick start index.rst --- doc_cn/demo/quick_start/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc_cn/demo/quick_start/index.rst b/doc_cn/demo/quick_start/index.rst index 08c1c8413b..b38b8ca999 100644 --- a/doc_cn/demo/quick_start/index.rst +++ b/doc_cn/demo/quick_start/index.rst @@ -48,6 +48,11 @@ PaddlePaddle快速入门教程 ./data/get_data.sh ./preprocess.sh +数据预处理完成之后,通过配置类似于 ``dataprovider_*.py`` 的数据读取脚本和类似于 ``trainer_config.*.py`` 的训练模型脚本,PaddlePaddle将以设置参数的方式来设置 +相应的数据读取脚本和训练模型脚本。接下来,我们将对这两个步骤给出了详细的解释,你也可以先跳过本文的解释环节,直接进入训练环节, 使用 ``sh train.sh`` 开始训练模型, +查看`train.sh`内容,通过 **自底向上法** (bottom-up approach)来帮助你理解PaddlePaddle的内部运行机制。 + + 向系统传送数据 ============== -- GitLab From b5e36970e29bf2f52cad934466a88b7cf7672499 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 28 Nov 2016 17:04:28 +0800 Subject: [PATCH 0152/1503] all input_types should has None element --- doc_cn/ui/data_provider/pydataprovider2.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc_cn/ui/data_provider/pydataprovider2.rst b/doc_cn/ui/data_provider/pydataprovider2.rst index 001e0884be..3455e331da 100644 --- a/doc_cn/ui/data_provider/pydataprovider2.rst +++ b/doc_cn/ui/data_provider/pydataprovider2.rst @@ -155,8 +155,6 @@ PaddlePaddle的数据包括四种主要类型,和三种序列模式。 其中,f代表一个浮点数,i代表一个整数。 -注意:sparse_binary_vector和sparse_float_vector中的元素,可以有None;但dense_vector和integer中的元素,不能出现None。 - init_hook +++++++++ -- GitLab From 5b4bec43a623e96e725414c8c3166601361b9174 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 28 Nov 2016 17:36:35 +0800 Subject: [PATCH 0153/1503] Add syntax='proto2' when using protobuf 3 --- proto/CMakeLists.txt | 15 ++++++++++++++- proto/DataConfig.proto.m4 | 1 + proto/DataFormat.proto.m4 | 1 + proto/ModelConfig.proto.m4 | 1 + proto/ParameterConfig.proto.m4 | 1 + proto/ParameterService.proto.m4 | 2 +- proto/TrainerConfig.proto.m4 | 1 + 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 461c73f14c..ec68b53d44 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,3 +1,12 @@ +execute_process(COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --version + OUTPUT_VARIABLE PROTOBUF_VERSION) +string(REPLACE "libprotoc " "" PROTOBUF_VERSION ${PROTOBUF_VERSION}) + +set(PROTOBUF_3 OFF) +if (${PROTOBUF_VERSION} VERSION_GREATER "3.0.0" OR ${PROTOBUF_VERSION} VERSION_EQUAL "3.0.0") + set(PROTOBUF_3 ON) +endif() + set(proto_filenames DataConfig.proto DataFormat.proto @@ -11,8 +20,12 @@ set(real_proto_files) # TODO(yuyang18): Some internal proto will also be depended on. # Find a way to automatically calculate all depends. foreach(filename ${proto_filenames}) + set(PROTOBUF_3_FLAGS "") + if (PROTOBUF_3) + set(PROTOBUF_3_FLAGS "-Dproto3") + endif() add_custom_command(OUTPUT ${filename} - COMMAND ${M4_EXECUTABLE} -Dreal=${ACCURACY} -I '${INTERNAL_PROTO_PATH}' + COMMAND ${M4_EXECUTABLE} -Dreal=${ACCURACY} ${PROTOBUF_3_FLAGS} -I '${INTERNAL_PROTO_PATH}' ${PROJ_ROOT}/proto/${filename}.m4 > ${filename} DEPENDS ${PROJ_ROOT}/proto/${filename}.m4 COMMENT "Generate ${filename}") diff --git a/proto/DataConfig.proto.m4 b/proto/DataConfig.proto.m4 index 9862e4e7ef..01d451ff7d 100644 --- a/proto/DataConfig.proto.m4 +++ b/proto/DataConfig.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') package paddle; diff --git a/proto/DataFormat.proto.m4 b/proto/DataFormat.proto.m4 index 556eace5e1..8a4a0be1b3 100644 --- a/proto/DataFormat.proto.m4 +++ b/proto/DataFormat.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') package paddle; diff --git a/proto/ModelConfig.proto.m4 b/proto/ModelConfig.proto.m4 index c835cfd522..68a5eb9dd2 100644 --- a/proto/ModelConfig.proto.m4 +++ b/proto/ModelConfig.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') import "ParameterConfig.proto"; diff --git a/proto/ParameterConfig.proto.m4 b/proto/ParameterConfig.proto.m4 index e8d512445e..26e7c3ef77 100644 --- a/proto/ParameterConfig.proto.m4 +++ b/proto/ParameterConfig.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') package paddle; diff --git a/proto/ParameterService.proto.m4 b/proto/ParameterService.proto.m4 index 189dc1c970..0b3f14a2ee 100644 --- a/proto/ParameterService.proto.m4 +++ b/proto/ParameterService.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') import "ParameterConfig.proto"; import "TrainerConfig.proto"; @@ -20,7 +21,6 @@ package paddle; /** * Various structs for communicating with parameter server */ - enum ParameterUpdateMode { // Set parameter PSERVER_UPDATE_MODE_SET_PARAM = 0;//use local param diff --git a/proto/TrainerConfig.proto.m4 b/proto/TrainerConfig.proto.m4 index 3b0e24f90b..965c9cd393 100644 --- a/proto/TrainerConfig.proto.m4 +++ b/proto/TrainerConfig.proto.m4 @@ -11,6 +11,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +ifdef(`proto3', `syntax = "proto2";') import "DataConfig.proto"; import "ModelConfig.proto"; -- GitLab From 141e2e9856a12e5194ad409798ef0fbddb911dbf Mon Sep 17 00:00:00 2001 From: zhangjinchao01 Date: Mon, 28 Nov 2016 17:44:43 +0800 Subject: [PATCH 0154/1503] revise data download path of srl demo --- demo/semantic_role_labeling/data/get_data.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/demo/semantic_role_labeling/data/get_data.sh b/demo/semantic_role_labeling/data/get_data.sh index 55e33f4685..84aecdd9a9 100644 --- a/demo/semantic_role_labeling/data/get_data.sh +++ b/demo/semantic_role_labeling/data/get_data.sh @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. set -e -wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz -wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/verbDict.txt --no-check-certificate -wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/targetDict.txt --no-check-certificate -wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/wordDict.txt --no-check-certificate -wget https://www.googledrive.com/host/0B7Q8d52jqeI9ejh6Q1RpMTFQT1k/semantic_role_labeling/emb --no-check-certificate +#wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz +wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/verbDict.txt +wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/targetDict.txt +wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/wordDict.txt +wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/emb tar -xzvf conll05st-tests.tar.gz rm conll05st-tests.tar.gz cp ./conll05st-release/test.wsj/words/test.wsj.words.gz . -- GitLab From 2f8947f751d38118be61941588e5141fb5173ec6 Mon Sep 17 00:00:00 2001 From: zhangjinchao01 Date: Mon, 28 Nov 2016 17:47:18 +0800 Subject: [PATCH 0155/1503] del comments --- demo/semantic_role_labeling/data/get_data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/semantic_role_labeling/data/get_data.sh b/demo/semantic_role_labeling/data/get_data.sh index 84aecdd9a9..99487e0d9a 100644 --- a/demo/semantic_role_labeling/data/get_data.sh +++ b/demo/semantic_role_labeling/data/get_data.sh @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. set -e -#wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz +wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/verbDict.txt wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/targetDict.txt wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/wordDict.txt -- GitLab From ff7b4284f41f31b8ee11db67c06c14e3fb51ae12 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 28 Nov 2016 14:56:25 +0800 Subject: [PATCH 0156/1503] Add a auto compare for BaseMatrix --- paddle/math/tests/CMakeLists.txt | 1 + paddle/math/tests/TensorCheck.h | 113 +++++++++++++++++++ paddle/math/tests/TestUtils.h | 149 ++++++++++++++++++++++++++ paddle/math/tests/test_BaseMatrix.cpp | 56 ++++++++++ 4 files changed, 319 insertions(+) create mode 100644 paddle/math/tests/TensorCheck.h create mode 100644 paddle/math/tests/TestUtils.h create mode 100644 paddle/math/tests/test_BaseMatrix.cpp diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index 247be983ba..5ac7888748 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -14,3 +14,4 @@ add_simple_unittest(test_perturbation) add_simple_unittest(test_CpuGpuVector) add_simple_unittest(test_Allocator) add_simple_unittest(test_FPException) +add_simple_unittest(test_BaseMatrix) diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h new file mode 100644 index 0000000000..6ca303cc7c --- /dev/null +++ b/paddle/math/tests/TensorCheck.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include +#include +#include "paddle/math/Matrix.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +namespace autotest { + +class CheckEqual { +public: + inline int operator()(real a, real b) { + if (a != b) { + return 1; + } + + return 0; + } +}; + +class CheckWithErr { +public: + CheckWithErr() { +#ifndef PADDLE_TYPE_DOUBLE + err_ = 1e-5; +#else + err_ = 1e-10; +#endif + } + + inline int operator()(real a, real b) { + if (std::fabs(a - b) > err_) { + if ((std::fabs(a - b) / std::fabs(a)) > (err_ / 10.0f)) { + return 1; + } + } + return 0; + } + +private: + real err_; +}; + +template +void TensorCheck(Check op, const CpuMatrix& matrix1, const CpuMatrix& matrix2) { + CHECK(matrix1.getHeight() == matrix2.getHeight()); + CHECK(matrix1.getWidth() == matrix2.getWidth()); + + int height = matrix1.getHeight(); + int width = matrix1.getWidth(); + const real* data1 = matrix1.getData(); + const real* data2 = matrix2.getData(); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + real a = data1[i * width + j]; + real b = data2[i * width + j]; + count += op(a, b); + } + } + EXPECT_EQ(count, 0) << "There are " << count << " different element."; +} + +template +class CopyToCpu; + +template <> +class CopyToCpu { +public: + explicit CopyToCpu(const CpuMatrix& arg) : arg_(arg) {} + const CpuMatrix& copiedArg() const { return arg_; } + +private: + const CpuMatrix& arg_; +}; + +template <> +class CopyToCpu { +public: + explicit CopyToCpu(const GpuMatrix& arg) + : arg_(arg.getHeight(), arg.getWidth()) { + arg_.copyFrom(arg); + } + CpuMatrix& copiedArg() { return arg_; } + +private: + CpuMatrix arg_; +}; + +template +extern void TensorCheckErr(const Tensor1& tensor1, const Tensor2& tensor2) { + TensorCheck( + CheckWithErr(), + CopyToCpu(tensor1).copiedArg(), + CopyToCpu(tensor2).copiedArg()); +} + +} // namespace autotest + diff --git a/paddle/math/tests/TestUtils.h b/paddle/math/tests/TestUtils.h new file mode 100644 index 0000000000..5c9049cacc --- /dev/null +++ b/paddle/math/tests/TestUtils.h @@ -0,0 +1,149 @@ +/* Copyright (c) 2016 Baidu, Inc. 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. */ + + +/** + * TestUtils.h is used to automatically compare CPU and GPU code is consistent. + * + * Auto compare BaseMatrix member function: + * Use case: + * a. void BaseMatrix::tanh(BaseMatrixT& b); + * Compare method: BaseMatrixCompare<0>(&BaseMatrix::tanh); + * + * b. + * +*/ + +#include +#include "paddle/math/Matrix.h" +#include "TensorCheck.h" + +using namespace paddle; // NOLINT + +namespace autotest { + +template +class ReplaceType { +public: + typedef T1 type; +}; + +template<> +class ReplaceType { +public: + typedef CpuMatrix type; +}; + +template<> +class ReplaceType { +public: + typedef GpuMatrix type; +}; + +// construct a argument +template T construct(int height, int width); +template<> float construct(int height, int width) { return 0.0; } +template<> CpuMatrix construct(int height, int width) { + CpuMatrix a(height, width); + return a; +} +template<> GpuMatrix construct(int height, int width) { + GpuMatrix a(height, width); + return a; +} + +// init a argument +template void init(T& v); +template<> void init(float& v) { v = 0.5; } +template<> void init(CpuMatrix& v) { v.randomizeUniform(); } +template<> void init(GpuMatrix& v) { v.randomizeUniform(); } + +// init a tuple which contains a set of arguments. +template +inline typename std::enable_if::type +initTuple(std::tuple& t){} + +template +inline typename std::enable_if::type +initTuple(std::tuple& t) { + init(std::get(t)); + initTuple(t); +} + +// copy a argument, copy src to dest +template void copy(T1& dest, T2& src); +template<> void copy(float& dest, float& src) { dest = src; } +template<> void copy(GpuMatrix& dest, CpuMatrix& src) { + dest.copyFrom(src); +} + +// copy a tuple, copy src to dest +template +inline typename std::enable_if::type +copyTuple(std::tuple& dest, std::tuple& src) {} + +template +inline typename std::enable_if::type +copyTuple(std::tuple& dest, std::tuple& src) { + copy(std::get(dest), std::get(src)); + copyTuple(dest, src); +} + +// call member function +template +R call(C& obj, R (FC::*f)(FArgs...), Args&&... args) { + return (obj.*f)(args...); +} + +template +void BaseMatrixCompare(R (C::*f)(Args...)) { + for (auto height : {1, 11, 73, 128, 200, 330}) { + for (auto width : {1, 3, 32, 100, 512, 1000, 3210}) { + CpuMatrix obj1(height, width); + GpuMatrix obj2(height, width); + init(obj1); + copy(obj2, obj1); + + auto tuple1 = std::make_tuple( + construct>::type>::type, + CpuMatrix>::type>(height, width)...); + + auto tuple2 = std::make_tuple( + construct>::type>::type, + GpuMatrix>::type>(height, width)...); + + initTuple(tuple1); + copyTuple(tuple2, tuple1); + + call(obj1, f, std::get(tuple1)...); + call(obj2, f, std::get(tuple2)...); + + TensorCheckErr(obj1, obj2); + } + } +} + +} // namespace autotest + +template +void BaseMatrixCompare(R (C::*f)(Args...)) { + static_assert(sizeof...(I) == sizeof...(Args), + "size of parameter packs are not equal"); + + autotest::BaseMatrixCompare(f); +} + diff --git a/paddle/math/tests/test_BaseMatrix.cpp b/paddle/math/tests/test_BaseMatrix.cpp new file mode 100644 index 0000000000..c06580ca82 --- /dev/null +++ b/paddle/math/tests/test_BaseMatrix.cpp @@ -0,0 +1,56 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#ifndef PADDLE_ONLY_CPU +/** + * This test file compares the implementation of CPU and GPU function + * in BaseMatrix.cpp. + */ + +#include +#include "paddle/utils/Util.h" +#include "paddle/math/BaseMatrix.h" +#include "TestUtils.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +TEST(BaseMatrix, apply) { + // member function with no argument + BaseMatrixCompare(&BaseMatrix::neg); + + // If the member function are overloaded, use static_cast to specify which + // member function need be test. + BaseMatrixCompare( + static_cast(&BaseMatrix::exp)); + BaseMatrixCompare( + static_cast(&BaseMatrix::sqrt)); + + // member function with one argument + + BaseMatrixCompare<0>(&BaseMatrix::tanh); + + BaseMatrixCompare<0>( + static_cast(&BaseMatrix::assign)); + BaseMatrixCompare<0>( + static_cast(&BaseMatrix::pow)); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + initMain(argc, argv); + return RUN_ALL_TESTS(); +} + +#endif -- GitLab From 11f97c93b2a0ffa4a6eb23e7af3439e5d5b8c224 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 28 Nov 2016 19:45:54 +0800 Subject: [PATCH 0157/1503] Change explicit code into literalinclude syntax --- doc_cn/demo/quick_start/index.rst | 85 ++++--------------------------- 1 file changed, 11 insertions(+), 74 deletions(-) diff --git a/doc_cn/demo/quick_start/index.rst b/doc_cn/demo/quick_start/index.rst index b38b8ca999..db73cb3f34 100644 --- a/doc_cn/demo/quick_start/index.rst +++ b/doc_cn/demo/quick_start/index.rst @@ -49,7 +49,7 @@ PaddlePaddle快速入门教程 ./preprocess.sh 数据预处理完成之后,通过配置类似于 ``dataprovider_*.py`` 的数据读取脚本和类似于 ``trainer_config.*.py`` 的训练模型脚本,PaddlePaddle将以设置参数的方式来设置 -相应的数据读取脚本和训练模型脚本。接下来,我们将对这两个步骤给出了详细的解释,你也可以先跳过本文的解释环节,直接进入训练环节, 使用 ``sh train.sh`` 开始训练模型, +相应的数据读取脚本和训练模型脚本。接下来,我们将对这两个步骤给出了详细的解释,你也可以先跳过本文的解释环节,直接进入训练模型章节, 使用 ``sh train.sh`` 开始训练模型, 查看`train.sh`内容,通过 **自底向上法** (bottom-up approach)来帮助你理解PaddlePaddle的内部运行机制。 @@ -66,86 +66,23 @@ Python脚本读取数据 ``dataprovider_bow.py`` 文件给出了完整例子: -.. code-block:: python - - from paddle.trainer.PyDataProvider2 import * - - # id of the word not in dictionary - UNK_IDX = 0 - - # initializer is called by the framework during initialization. - # It allows the user to describe the data types and setup the - # necessary data structure for later use. - # `settings` is an object. initializer need to properly fill settings.input_types. - # initializer can also store other data structures needed to be used at process(). - # In this example, dictionary is stored in settings. - # `dictionay` and `kwargs` are arguments passed from trainer_config.lr.py - def initializer(settings, dictionary, **kwargs): - # Put the word dictionary into settings - settings.word_dict = dictionary +.. literalinclude:: ../../../demo/quick_start/dataprovider_bow.py + :language: python + :lines: 21-70 + :linenos: + :emphasize-lines: 8,33 - # setting.input_types specifies what the data types the data provider - # generates. - settings.input_types = [ - # The first input is a sparse_binary_vector, - # which means each dimension of the vector is either 0 or 1. It is the - # bag-of-words (BOW) representation of the texts. - sparse_binary_vector(len(dictionary)), - # The second input is an integer. It represents the category id of the - # sample. 2 means there are two labels in the dataset. - # (1 for positive and 0 for negative) - integer_value(2)] - - # Delaring a data provider. It has an initializer 'data_initialzer'. - # It will cache the generated data of the first pass in memory, so that - # during later pass, no on-the-fly data generation will be needed. - # `setting` is the same object used by initializer() - # `file_name` is the name of a file listed train_list or test_list file given - # to define_py_data_sources2(). See trainer_config.lr.py. - @provider(init_hook=initializer, cache=CacheType.CACHE_PASS_IN_MEM) - def process(settings, file_name): - # Open the input data file. - with open(file_name, 'r') as f: - # Read each line. - for line in f: - # Each line contains the label and text of the comment, separated by \t. - label, comment = line.strip().split('\t') - - # Split the words into a list. - words = comment.split() - - # convert the words into a list of ids by looking them up in word_dict. - word_vector = [settings.word_dict.get(w, UNK_IDX) for w in words] - - # Return the features for the current comment. The first is a list - # of ids representing a 0-1 binary sparse vector of the text, - # the second is the integer id of the label. - yield word_vector, int(label) 配置中的数据加载定义 -------------------- 在模型配置中通过 ``define_py_data_sources2`` 接口来加载数据: -.. code-block:: python - - from paddle.trainer_config_helpers import * - - file = "data/dict.txt" - word_dict = dict() - with open(dict_file, 'r') as f: - for i, line in enumerate(f): - w = line.strip().split()[0] - word_dict[w] = i - # define the data sources for the model. - # We need to use different process for training and prediction. - # For training, the input data includes both word IDs and labels. - # For prediction, the input data only includs word Ids. - define_py_data_sources2(train_list='data/train.list', - test_list='data/test.list', - module="dataprovider_bow", - obj="process", - args={"dictionary": word_dict}) +.. literalinclude:: ../../../demo/quick_start/trainer_config.emb.py + :language: python + :lines: 19-35 + :linenos: + :emphasize-lines: 12 以下是对上述数据加载的解释: -- GitLab From 1f743d381cfb5dc80c25d4787ea1a703eb3ba07d Mon Sep 17 00:00:00 2001 From: wangyanfei01 Date: Mon, 28 Nov 2016 19:55:34 +0800 Subject: [PATCH 0158/1503] Redesign test_period meaning: * always do test on all test data * do test at the end of each pass if test_period=0, otherwise do test if test_period batches passed --- doc/ui/cmd_argument/argument_outline.md | 11 ++---- doc/ui/cmd_argument/detail_introduction.md | 12 +----- doc/ui/cmd_argument/use_case.md | 4 +- paddle/trainer/Tester.cpp | 20 ++-------- paddle/trainer/Tester.h | 2 +- paddle/trainer/TesterConfig.h | 12 +----- paddle/trainer/Trainer.cpp | 46 +++++++++------------- 7 files changed, 29 insertions(+), 78 deletions(-) diff --git a/doc/ui/cmd_argument/argument_outline.md b/doc/ui/cmd_argument/argument_outline.md index bafa5dfef2..013edbc904 100644 --- a/doc/ui/cmd_argument/argument_outline.md +++ b/doc/ui/cmd_argument/argument_outline.md @@ -68,7 +68,7 @@ It looks like there are a lot of arguments. However, most of them are for develo -test_period_while_training +test_period √√ @@ -143,13 +143,8 @@ It looks like there are a lot of arguments. However, most of them are for develo -testing during trainingtest_batches_while_training -√√√< - - - -testing during trainingtest_batches_while_end -√√√< +testing during trainingtest_period +√√ diff --git a/doc/ui/cmd_argument/detail_introduction.md b/doc/ui/cmd_argument/detail_introduction.md index 1f7e406a53..823a226619 100644 --- a/doc/ui/cmd_argument/detail_introduction.md +++ b/doc/ui/cmd_argument/detail_introduction.md @@ -109,8 +109,8 @@ - Load parameter from this pass to test. - type: int32 (default: -1). -* `--test_period_while_training` - - Run test every test_period_while_training batches while doing training. If not 0, test test_batches_while_training batches, if 0, test nothing. +* `--test_period` + - if equal 0, do test on all test data at the end of each pass while if equal non-zero, do test on all test data once each test_period batches passed while training is going on. - type: int32 (default: 0). * `--test_wait` @@ -121,14 +121,6 @@ - File that saves the model list when testing. It was set automatically when using cluster submitting environment after setting model_path. - type: string (default: "", null). -* `--test_batches_while_training` - - Test test_batches_while_training batches if test_batches_while_training != 0 while doing training. If 0, test on all test data. - - type: bool (default: 1000). - -* `--test_batches_while_end` - - Test test_batches_while_end batches if test_batches_while_end != 0 at pass end. If 0, test on all test data. - - type: bool (default: 0). - * `--predict_output_dir` - Directory that saves the layer output. It is configured in Outputs() in network config. Default, this argument is null, meaning save nothing. Specify this directory if you want to save feature map of some layers in testing mode. Note that, layer outputs are values after activation function. - type: string (default: "", null). diff --git a/doc/ui/cmd_argument/use_case.md b/doc/ui/cmd_argument/use_case.md index b243560106..4d7bb33f36 100644 --- a/doc/ui/cmd_argument/use_case.md +++ b/doc/ui/cmd_argument/use_case.md @@ -10,9 +10,7 @@ paddle train \ --config=network_config \ --save_dir=output \ --trainer_count=COUNT \ #(default:1) - --test_period_while_training=M \ #(default:0) - --test_batches_while_training=BATCHES \#(default:1000) - --test_batches_while_end=BATCHES \ #(default:0) + --test_period=M \ #(default:0) --num_passes=N \ #(defalut:100) --log_period=K \ #(default:100) --dot_period=1000 \ #(default:1) diff --git a/paddle/trainer/Tester.cpp b/paddle/trainer/Tester.cpp index f57e09d40a..217b8c60be 100644 --- a/paddle/trainer/Tester.cpp +++ b/paddle/trainer/Tester.cpp @@ -90,20 +90,11 @@ void Tester::testOneDataBatch( testContext_.numSamples += dataBatch.getSize(); } -void Tester::testOnePeriod(bool finishPass) { +void Tester::testOnePeriod() { DataBatch dataBatch; int64_t batchSize = config_->getOptConfig().batch_size(); - bool testAllData = - (!finishPass && !intconfig_->testBatchesWhileTraining) || - (finishPass && !intconfig_->testBatchesWhileEnd); - int batches; - if (testAllData) { - batches = std::numeric_limits::max(); - } else { - batches = finishPass ? - intconfig_->testBatchesWhileEnd : intconfig_->testBatchesWhileTraining; - } + int batches = std::numeric_limits::max(); std::vector outArgs; @@ -115,12 +106,7 @@ void Tester::testOnePeriod(bool finishPass) { if (intconfig_->prevBatchState) { gradientMachine_->resetState(); } - if ((!finishPass && !intconfig_->testBatchesWhileTraining) || - (finishPass && !intconfig_->testBatchesWhileEnd)) { - break; - } else { - num = testDataProvider_->getNextBatch(batchSize, &dataBatch); - } + break; } testOneDataBatch(dataBatch, &outArgs); } diff --git a/paddle/trainer/Tester.h b/paddle/trainer/Tester.h index 21e11422aa..671ffc5220 100644 --- a/paddle/trainer/Tester.h +++ b/paddle/trainer/Tester.h @@ -67,7 +67,7 @@ public: * It is convenience to test small set of data when test data set is large and * is training at same time. */ - void testOnePeriod(bool finishPass = true); + void testOnePeriod(); void startTestPeriod(); void finishTestPeriod(); void testOneDataBatch(const DataBatch& dataBatch, diff --git a/paddle/trainer/TesterConfig.h b/paddle/trainer/TesterConfig.h index b7b550dec7..8392bbcda5 100644 --- a/paddle/trainer/TesterConfig.h +++ b/paddle/trainer/TesterConfig.h @@ -38,17 +38,7 @@ struct TesterConfig { /** * indicate test period */ - int testPeriodWhileTraining; - - /** - * indicate how many batches are used for testing under training - */ - bool testBatchesWhileTraining; - - /** - * indicate how many batches are used for testing at pass end - */ - bool testBatchesWhileEnd; + int testPeriod; /** * indicate whether to save previous batch state diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 507a080cc4..aca896770a 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -42,24 +42,13 @@ limitations under the License. */ P_DEFINE_string(config, "", "Trainer config file"); P_DEFINE_int32(test_period, 0, - "This option was deprecated, use test_period_while_training " - " instead. "); -P_DEFINE_int32(test_period_while_training, 0, - "Run test every test_period_while_training batches." - " If not 0, test test_batches_while_training batches." - " If 0, test nothing."); -P_DEFINE_int32(test_batches_while_training, 1000, - "test test_batches_while_training batches if " - "test_batches_while_training != 0." - " If 0, test on all test data"); -P_DEFINE_int32(test_batches_while_end, 0, - "test test_batches_while_end batches at pass end." - " Always run test at pass end." - " If not 0, test test_batches_while_end batches." - " If 0, test on all test data."); + "if equal 0, do test on all test data at the end of " + "each pass while if equal non-zero, do test on all test " + "data once each test_period batches passed while " + "training is going on"); P_DEFINE_bool(test_all_data_in_one_period, false, - "This option was deprecated, use test_batches_while_training " - "and test_batches_while_end instead"); + "This option was deprecated, since we will always do " + "test on all test set "); P_DEFINE_bool(local, true, "Train in local mode or not"); @@ -467,9 +456,9 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { FOR_TIMING(globalStat.reset()); } - if (testDataProvider_ && FLAGS_test_period_while_training > 0 && - trainPassContext_.batchId % FLAGS_test_period_while_training == 0) { - tester_->testOnePeriod(false); + if (testDataProvider_ && FLAGS_test_period > 0 && + trainPassContext_.batchId % FLAGS_test_period == 0) { + tester_->testOnePeriod(); } if (FLAGS_saving_period_by_batches > 0 && @@ -478,7 +467,7 @@ void Trainer::trainOneDataBatch(DataBatch& dataBatch) { 0 == FLAGS_trainer_id) { trainerInternal_.getParameterUpdater()->catchUpWith(); if (testDataProvider_) { - tester_->testOnePeriod(false); + tester_->testOnePeriod(); } paramUtil_->saveParametersOnePass( trainPassContext_.passId, trainPassContext_.passInnerId); @@ -636,17 +625,18 @@ std::unique_ptr Trainer::createTesterConfig() { TesterConfig* conf = new TesterConfig; if (FLAGS_test_period) { LOG(WARNING) - << "--test_period was deprecated, use --test_period_while_training" - << "--test_batches_while_training --test_batches_while_end instead."; + << "The meaning of --test_period is changed: " + << "if equal 0, do test on all test data at the end of " + << "each pass while if equal non-zero, do test on all test " + << "data once each test_period batches passed while " + << "training is going on"; } if (FLAGS_test_all_data_in_one_period) { LOG(WARNING) - << "--test_all_data_in_one_period was deprecated, use" - << " --test_batches_while_training and --test_batches_while_end instead"; + << "--test_all_data_in_one_period was deprecated, since " + << "we will always do test on all test set "; } - conf->testPeriodWhileTraining = FLAGS_test_period_while_training; - conf->testBatchesWhileTraining = FLAGS_test_batches_while_training; - conf->testBatchesWhileEnd = FLAGS_test_batches_while_end; + conf->testPeriod = FLAGS_test_period; conf->prevBatchState = FLAGS_prev_batch_state; conf->logPeriod = FLAGS_log_period; conf->loadsaveParametersInPserver = FLAGS_loadsave_parameters_in_pserver; -- GitLab From 85e0cd709c1a9efea413975d66af1df62baa9b45 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 28 Nov 2016 20:46:15 +0800 Subject: [PATCH 0159/1503] move some BaseMatrix test from test_matrixCompare.cpp to test_BaseMatrix.cpp --- paddle/math/tests/TensorCheck.h | 89 ++++--- paddle/math/tests/TestUtils.h | 51 +++- paddle/math/tests/test_BaseMatrix.cpp | 229 +++++++++++++++-- paddle/math/tests/test_matrixCompare.cpp | 298 ----------------------- 4 files changed, 300 insertions(+), 367 deletions(-) diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h index 6ca303cc7c..f19933d1f6 100644 --- a/paddle/math/tests/TensorCheck.h +++ b/paddle/math/tests/TensorCheck.h @@ -21,60 +21,29 @@ using namespace std; // NOLINT namespace autotest { -class CheckEqual { +class AssertEqual { public: - inline int operator()(real a, real b) { - if (a != b) { - return 1; - } - - return 0; - } -}; + AssertEqual(real err = 0) : err_(err) {} -class CheckWithErr { -public: - CheckWithErr() { -#ifndef PADDLE_TYPE_DOUBLE - err_ = 1e-5; -#else - err_ = 1e-10; -#endif - } - - inline int operator()(real a, real b) { - if (std::fabs(a - b) > err_) { - if ((std::fabs(a - b) / std::fabs(a)) > (err_ / 10.0f)) { - return 1; + inline bool operator()(real a, real b) { + if (err_ == 0) { + if (a != b) { + return false; + } + } else { + if (std::fabs(a - b) > err_) { + if ((std::fabs(a - b) / std::fabs(a)) > (err_ / 10.0f)) { + return false; + } } } - return 0; + return true; } private: real err_; }; -template -void TensorCheck(Check op, const CpuMatrix& matrix1, const CpuMatrix& matrix2) { - CHECK(matrix1.getHeight() == matrix2.getHeight()); - CHECK(matrix1.getWidth() == matrix2.getWidth()); - - int height = matrix1.getHeight(); - int width = matrix1.getWidth(); - const real* data1 = matrix1.getData(); - const real* data2 = matrix2.getData(); - int count = 0; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - real a = data1[i * width + j]; - real b = data2[i * width + j]; - count += op(a, b); - } - } - EXPECT_EQ(count, 0) << "There are " << count << " different element."; -} - template class CopyToCpu; @@ -101,10 +70,36 @@ private: CpuMatrix arg_; }; -template -extern void TensorCheckErr(const Tensor1& tensor1, const Tensor2& tensor2) { +template +void TensorCheck(AssertEq compare, + const CpuMatrix& matrix1, + const CpuMatrix& matrix2) { + CHECK(matrix1.getHeight() == matrix2.getHeight()); + CHECK(matrix1.getWidth() == matrix2.getWidth()); + + int height = matrix1.getHeight(); + int width = matrix1.getWidth(); + const real* data1 = matrix1.getData(); + const real* data2 = matrix2.getData(); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + real a = data1[i * width + j]; + real b = data2[i * width + j]; + if (!compare(a, b)) { + count++; + } + } + } + EXPECT_EQ(count, 0) << "There are " << count << " different element."; +} + +template +extern void TensorCheck(AssertEq compare, + const Tensor1& tensor1, + const Tensor2& tensor2) { TensorCheck( - CheckWithErr(), + compare, CopyToCpu(tensor1).copiedArg(), CopyToCpu(tensor2).copiedArg()); } diff --git a/paddle/math/tests/TestUtils.h b/paddle/math/tests/TestUtils.h index 5c9049cacc..ffd1134542 100644 --- a/paddle/math/tests/TestUtils.h +++ b/paddle/math/tests/TestUtils.h @@ -107,12 +107,16 @@ R call(C& obj, R (FC::*f)(FArgs...), Args&&... args) { return (obj.*f)(args...); } -template -void BaseMatrixCompare(R (C::*f)(Args...)) { +template +void BaseMatrixCompare(R (C::*f)(Args...), AssertEq compare) { for (auto height : {1, 11, 73, 128, 200, 330}) { - for (auto width : {1, 3, 32, 100, 512, 1000, 3210}) { - CpuMatrix obj1(height, width); - GpuMatrix obj2(height, width); + for (auto width : {1, 3, 32, 100, 512, 1000}) { + CpuMatrix obj1(ApplyCol ? 1 : height, + ApplyRow ? 1 : width); + GpuMatrix obj2(ApplyCol ? 1 : height, + ApplyRow ? 1 : width); init(obj1); copy(obj2, obj1); @@ -132,7 +136,7 @@ void BaseMatrixCompare(R (C::*f)(Args...)) { call(obj1, f, std::get(tuple1)...); call(obj2, f, std::get(tuple2)...); - TensorCheckErr(obj1, obj2); + TensorCheck(compare, obj1, obj2); } } } @@ -144,6 +148,39 @@ void BaseMatrixCompare(R (C::*f)(Args...)) { static_assert(sizeof...(I) == sizeof...(Args), "size of parameter packs are not equal"); - autotest::BaseMatrixCompare(f); +#ifndef PADDLE_TYPE_DOUBLE + autotest::AssertEqual compare(1e-5); +#else + autotest::AssertEqual compare(1e-10); +#endif + + autotest::BaseMatrixCompare(f, compare); +} + +template +void BaseMatrixApplyRow(R (C::*f)(Args...)) { + static_assert(sizeof...(I) == sizeof...(Args), + "size of parameter packs are not equal"); + +#ifndef PADDLE_TYPE_DOUBLE + autotest::AssertEqual compare(1e-3); +#else + autotest::AssertEqual compare(1e-8); +#endif + + autotest::BaseMatrixCompare(f, compare); +} + +template +void BaseMatrixApplyCol(R (C::*f)(Args...)) { + static_assert(sizeof...(I) == sizeof...(Args), + "size of parameter packs are not equal"); + +#ifndef PADDLE_TYPE_DOUBLE + autotest::AssertEqual compare(1e-3); +#else + autotest::AssertEqual compare(1e-8); +#endif + autotest::BaseMatrixCompare(f, compare); } diff --git a/paddle/math/tests/test_BaseMatrix.cpp b/paddle/math/tests/test_BaseMatrix.cpp index c06580ca82..99ab640f48 100644 --- a/paddle/math/tests/test_BaseMatrix.cpp +++ b/paddle/math/tests/test_BaseMatrix.cpp @@ -26,25 +26,224 @@ limitations under the License. */ using namespace paddle; // NOLINT using namespace std; // NOLINT -TEST(BaseMatrix, apply) { - // member function with no argument - BaseMatrixCompare(&BaseMatrix::neg); +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(). + */ +TEST(BaseMatrix, void) { + typedef void (BaseMatrix::*FunctionProto)(); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare(static_cast(&BaseMatrix::function)); + + BASEMATRIXCOMPARE(neg); + BASEMATRIXCOMPARE(exp); + BASEMATRIXCOMPARE(log); + BASEMATRIXCOMPARE(sqrt); + BASEMATRIXCOMPARE(square); + BASEMATRIXCOMPARE(reciprocal); + BASEMATRIXCOMPARE(abs); + BASEMATRIXCOMPARE(sign); + BASEMATRIXCOMPARE(zero); + BASEMATRIXCOMPARE(one); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(real). + */ +TEST(BaseMatrix, real) { + typedef void (BaseMatrix::*FunctionProto)(real); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); + + BASEMATRIXCOMPARE(pow); + BASEMATRIXCOMPARE(subScalar); + BASEMATRIXCOMPARE(mulScalar); + BASEMATRIXCOMPARE(divScalar); + BASEMATRIXCOMPARE(assign); + BASEMATRIXCOMPARE(add); + BASEMATRIXCOMPARE(biggerThanScalar); + BASEMATRIXCOMPARE(downClip); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(real, real). + */ +TEST(BaseMatrix, real_real) { + typedef void (BaseMatrix::*FunctionProto)(real, real); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); + + BASEMATRIXCOMPARE(add); + BASEMATRIXCOMPARE(clip); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&). + */ +TEST(BaseMatrix, BaseMatrix) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); + + BASEMATRIXCOMPARE(assign); + BASEMATRIXCOMPARE(add); + BASEMATRIXCOMPARE(relu); + BASEMATRIXCOMPARE(reluDerivative); + BASEMATRIXCOMPARE(softrelu); + BASEMATRIXCOMPARE(softreluDerivative); + BASEMATRIXCOMPARE(brelu); + BASEMATRIXCOMPARE(breluDerivative); + BASEMATRIXCOMPARE(square); + BASEMATRIXCOMPARE(squareDerivative); + BASEMATRIXCOMPARE(tanh); + BASEMATRIXCOMPARE(tanhDerivative); + + BASEMATRIXCOMPARE(reciprocal); + BASEMATRIXCOMPARE(reciprocalDerivative); + BASEMATRIXCOMPARE(abs); + BASEMATRIXCOMPARE(absDerivative); + BASEMATRIXCOMPARE(sigmoid); + BASEMATRIXCOMPARE(sigmoidDerivative); + BASEMATRIXCOMPARE(expDerivative); + BASEMATRIXCOMPARE(sign); + BASEMATRIXCOMPARE(exp); + BASEMATRIXCOMPARE(log); + BASEMATRIXCOMPARE(sqrt); + BASEMATRIXCOMPARE(dotMul); + BASEMATRIXCOMPARE(dotMulSquare); + BASEMATRIXCOMPARE(dotSquareMul); + + BASEMATRIXCOMPARE(addColVector); + BASEMATRIXCOMPARE(addRowVector); + BASEMATRIXCOMPARE(mulRowVector); + BASEMATRIXCOMPARE(divRowVector); + BASEMATRIXCOMPARE(addP2P); + BASEMATRIXCOMPARE(invSqrt); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&, real). + */ +TEST(BaseMatrix, BaseMatrix_real) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, real); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); - // If the member function are overloaded, use static_cast to specify which - // member function need be test. - BaseMatrixCompare( - static_cast(&BaseMatrix::exp)); - BaseMatrixCompare( - static_cast(&BaseMatrix::sqrt)); + BASEMATRIXCOMPARE(addBias); + BASEMATRIXCOMPARE(add); + BASEMATRIXCOMPARE(sub); + BASEMATRIXCOMPARE(pow); + BASEMATRIXCOMPARE(addScalar); + BASEMATRIXCOMPARE(subScalar); + BASEMATRIXCOMPARE(mulScalar); + BASEMATRIXCOMPARE(divScalar); + BASEMATRIXCOMPARE(scalarDiv); + BASEMATRIXCOMPARE(addSquare); + + BASEMATRIXCOMPARE(isEqualTo); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&, BaseMatrix&). + */ +TEST(BaseMatrix, BaseMatrix_BaseMatrix) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, BaseMatrix&); + #define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); + + BASEMATRIXCOMPARE(softCrossEntropy); + BASEMATRIXCOMPARE(softCrossEntropyBp); + BASEMATRIXCOMPARE(binaryLabelCrossEntropy); + BASEMATRIXCOMPARE(binaryLabelCrossEntropyBp); + BASEMATRIXCOMPARE(sub); + BASEMATRIXCOMPARE(add2); + BASEMATRIXCOMPARE(dotMul); + BASEMATRIXCOMPARE(dotDiv); + BASEMATRIXCOMPARE(logisticRegressionLoss); + BASEMATRIXCOMPARE(logisticRegressionLossBp); + BASEMATRIXCOMPARE(biggerThan); + BASEMATRIXCOMPARE(max); + BASEMATRIXCOMPARE(dotMulSquare); + BASEMATRIXCOMPARE(dotSquareSquare); + + #undef BASEMATRIXCOMPARE +} + +/** + * Test aggregate member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&). + */ +TEST(Aggregate, BaseMatrix) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&); + #define BASEMATRIXAPPLYROW(function) \ + BaseMatrixApplyRow<0>(static_cast(&BaseMatrix::function)); + + #define BASEMATRIXAPPLYCOL(function) \ + BaseMatrixApplyCol<0>(static_cast(&BaseMatrix::function)); + + BASEMATRIXAPPLYROW(maxRows); + BASEMATRIXAPPLYROW(minRows); + + BASEMATRIXAPPLYCOL(sumCols); + BASEMATRIXAPPLYCOL(maxCols); + BASEMATRIXAPPLYCOL(minCols); + + #undef BASEMATRIXAPPLYROW + #undef BASEMATRIXAPPLYCOL +} + +/** + * Test aggregate member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&, BaseMatrix&). + */ +TEST(Aggregate, BaseMatrix_BaseMatrix) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, BaseMatrix&); + #define BASEMATRIXAPPLYROW(function) \ + BaseMatrixApplyRow<0, 1>(static_cast(&BaseMatrix::function)); + + #define BASEMATRIXAPPLYCOL(function) \ + BaseMatrixApplyCol<0, 1>(static_cast(&BaseMatrix::function)); + + BASEMATRIXAPPLYCOL(addDotMulVMM); + + #undef BASEMATRIXAPPLYROW + #undef BASEMATRIXAPPLYCOL +} + +/** + * Test aggregate member functions which prototype is + * void (BaseMatrix::*)(BaseMatrix&, real, real). + */ +TEST(Aggregate, BaseMatrix_real_real) { + typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, real, real); + #define BASEMATRIXAPPLYROW(function) \ + BaseMatrixApplyRow<0, 1, 2>(\ + static_cast(&BaseMatrix::function)); - // member function with one argument + #define BASEMATRIXAPPLYCOL(function) \ + BaseMatrixApplyCol<0, 1, 2>(\ + static_cast(&BaseMatrix::function)); - BaseMatrixCompare<0>(&BaseMatrix::tanh); + BASEMATRIXAPPLYROW(sumRows); + BASEMATRIXAPPLYCOL(sumCols); - BaseMatrixCompare<0>( - static_cast(&BaseMatrix::assign)); - BaseMatrixCompare<0>( - static_cast(&BaseMatrix::pow)); + #undef BASEMATRIXAPPLYROW + #undef BASEMATRIXAPPLYCOL } int main(int argc, char** argv) { diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index ae5bc5a86a..8f96672b88 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -448,125 +448,6 @@ void testMatrixZeroAtOffset(int height, int width) { MatrixCheckEqual(*cpuA, *cpuTest); } -void testMatrixBinaryAdd(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - cpuA->add(*cpuB); - gpuA->add(*gpuB); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); -} - -void testMatrixAssign(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - - cpuA->randomizeUniform(); - gpuA->copyFrom(*cpuA); - cpuA->assign(2.5); - gpuA->assign(2.5); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); -} - -void testMatrixAdd(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - - cpuA->randomizeUniform(); - gpuA->copyFrom(*cpuA); - cpuA->add(2.5); - gpuA->add(2.5); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); -} - -void testMatrixSqrt(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - - cpuA->randomizeUniform(); - gpuA->copyFrom(*cpuA); - cpuA->sqrt(); - gpuA->sqrt(); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixTanhDerivative(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - cpuA->tanhDerivative(*cpuB); - gpuA->tanhDerivative(*gpuB); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixTanh(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - cpuA->tanh(*cpuB); - gpuA->tanh(*gpuB); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixTernarySub(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - - cpuA->sub(*cpuB, *cpuC); - gpuA->sub(*gpuB, *gpuC); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); -} - void testMatrixSumOfSquaresBp(int height, int width) { MatrixPtr cpuA = std::make_shared(height, width); MatrixPtr cpuB = std::make_shared(height, width); @@ -789,18 +670,7 @@ TEST(Matrix, unary) { for (auto width : {1, 3, 32, 100, 512, 1000, 3210}) { VLOG(3) << " height=" << height << " width=" << width; - // applyUnary - testMatrixAssign(height, width); - testMatrixAdd(height, width); - testMatrixSqrt(height, width); - - // applyBinary - testMatrixBinaryAdd(height, width); - testMatrixTanh(height, width); - testMatrixTanhDerivative(height, width); - // applyTernary - testMatrixTernarySub(height, width); testMatrixSumOfSquaresBp(height, width); // asRowVector @@ -931,165 +801,6 @@ TEST(Matrix, softmax) { } } -void testMatrixAddDotMulVMM(int height, int width, int endCol = 0) { - MatrixPtr cpuA = std::make_shared(1, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(1, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - MatrixPtr cpuA1 = std::make_shared(1, width); - MatrixPtr cpuB1 = std::make_shared(height, width); - MatrixPtr cpuC1 = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - cpuA1->copyFrom(*cpuA); - cpuB1->copyFrom(*cpuB); - cpuC1->copyFrom(*cpuC); - - if (!endCol) { - cpuA->addDotMulVMM(*cpuB, *cpuC); - gpuA->addDotMulVMM(*gpuB, *gpuC); - cpuA1->addDotMulVMM2(*cpuB1, *cpuC1); - - MatrixCheckErr(*cpuA, *cpuA1); - } else { - MatrixPtr subCpuA = cpuA->subColMatrix(0, endCol); - MatrixPtr subCpuB = cpuB->subColMatrix(0, endCol); - MatrixPtr subCpuC = cpuC->subColMatrix(0, endCol); - MatrixPtr subGpuA = gpuA->subColMatrix(0, endCol); - MatrixPtr subGpuB = gpuB->subColMatrix(0, endCol); - MatrixPtr subGpuC = gpuC->subColMatrix(0, endCol); - subCpuA->addDotMulVMM(*subCpuB, *subCpuC); - subGpuA->addDotMulVMM(*subGpuB, *subGpuC); - } - - MatrixPtr outputCheck = std::make_shared(1, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixRowSum(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, 1); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, 1); - MatrixPtr gpuB = std::make_shared(height, width); - - MatrixPtr cpuA1 = std::make_shared(height, 1); - MatrixPtr cpuB1 = std::make_shared(height, width); - MatrixPtr gpuA1 = std::make_shared(height, 1); - MatrixPtr gpuB1 = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - cpuA1->copyFrom(*cpuA); - cpuB1->copyFrom(*cpuB); - gpuA1->copyFrom(*cpuA); - gpuB1->copyFrom(*cpuB); - - cpuA->colMerge(*cpuB); - gpuA->colMerge(*gpuB); - - cpuB1->rowSum(*cpuA1); - gpuB1->rowSum(*gpuA1); - - MatrixPtr outputCheck = std::make_shared(height, 1); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); - outputCheck->copyFrom(*gpuA1); - MatrixCheckErr(*cpuA1, *outputCheck); -} - -void testMatrixRowMax(int height, int width, int endCol = 0) { - MatrixPtr cpuA = std::make_shared(height, 1); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, 1); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - - if (!endCol) { - cpuB->rowMax(*cpuA); - gpuB->rowMax(*gpuA); - } else { - MatrixPtr subCpuB = cpuB->subColMatrix(0, endCol); - MatrixPtr subGpuB = gpuB->subColMatrix(0, endCol); - subCpuB->rowMax(*cpuA); - subGpuB->rowMax(*gpuA); - } - - MatrixPtr outputCheck = std::make_shared(height, 1); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixColSum(int height, int width, int endCol = 0) { - MatrixPtr cpuA = std::make_shared(1, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(1, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - - if (!endCol) { - cpuA->accumulateColSum(*cpuB); - gpuA->accumulateColSum(*gpuB); - } else { - MatrixPtr subCpuA = cpuA->subColMatrix(0, endCol); - MatrixPtr subGpuA = gpuA->subColMatrix(0, endCol); - MatrixPtr subCpuB = cpuB->subColMatrix(0, endCol); - MatrixPtr subGpuB = gpuB->subColMatrix(0, endCol); - subCpuA->accumulateColSum(*subCpuB); - subGpuA->accumulateColSum(*subGpuB); - } - - MatrixPtr outputCheck = std::make_shared(1, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixColMax(int height, int width, int endCol = 0) { - MatrixPtr cpuA = std::make_shared(1, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(1, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - - if (!endCol) { - cpuB->colMax(*cpuA); - gpuB->colMax(*gpuA); - } else { - MatrixPtr subCpuA = cpuA->subColMatrix(0, endCol); - MatrixPtr subGpuA = gpuA->subColMatrix(0, endCol); - MatrixPtr subCpuB = cpuB->subColMatrix(0, endCol); - MatrixPtr subGpuB = gpuB->subColMatrix(0, endCol); - subCpuB->colMax(*subCpuA); - subGpuB->colMax(*subGpuA); - } - - MatrixPtr outputCheck = std::make_shared(1, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - void testMatrixCollectBias(int height, int width) { MatrixPtr cpuA = std::make_shared(1, width); MatrixPtr cpuB = std::make_shared(height, width); @@ -1182,13 +893,8 @@ TEST(Matrix, aggregate) { for (auto height : {1, 11, 16, 32, 64, 73, 128, 200, 1024, 2345}) { for (auto width : {1, 9, 16, 32, 64, 100, 512, 1000, 1024, 2453}) { VLOG(3) << " height=" << height << " width=" << width; - testMatrixRowSum(height, width); - testMatrixRowMax(height, width); - testMatrixColSum(height, width); - testMatrixColMax(height, width); testMatrixCollectBias(height, width); testMatrixTernaryRowDotMul(height, width); - testMatrixAddDotMulVMM(height, width); testMatrixSumOfSquares(height, width); testMatrixBinaryClassificationError(height, width); @@ -1203,11 +909,7 @@ TEST(Matrix, aggregate2) { VLOG(3) << " height=" << height << " width=" << width; int endCol = rand() % width; // NOLINT - testMatrixRowMax(height, width, endCol); testMatrixSumOfSquares(height, width, endCol); - testMatrixColSum(height, width, endCol); - testMatrixColMax(height, width, endCol); - testMatrixAddDotMulVMM(height, width, endCol); } } } -- GitLab From 1bac8e60c3dd10f6a7de031d49ef9b9c6f4d1d15 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 28 Nov 2016 21:27:57 +0800 Subject: [PATCH 0160/1503] modify the file name test_matrix.cpp to test_SparseMatrix.cpp --- paddle/math/tests/{test_matrix.cpp => test_SparseMatrix.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paddle/math/tests/{test_matrix.cpp => test_SparseMatrix.cpp} (100%) diff --git a/paddle/math/tests/test_matrix.cpp b/paddle/math/tests/test_SparseMatrix.cpp similarity index 100% rename from paddle/math/tests/test_matrix.cpp rename to paddle/math/tests/test_SparseMatrix.cpp -- GitLab From 1873945dc7fdf33345dccf986c2f1eb02ad2fb24 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 29 Nov 2016 00:53:16 +0800 Subject: [PATCH 0161/1503] Add test_Matrix.cpp for auto compare member functions of class Matrix --- paddle/math/tests/CMakeLists.txt | 5 +- paddle/math/tests/TensorCheck.h | 21 +-- paddle/math/tests/TestUtils.h | 172 ++++++++++++++++------- paddle/math/tests/test_Matrix.cpp | 49 +++++++ paddle/math/tests/test_matrixCompare.cpp | 19 --- 5 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 paddle/math/tests/test_Matrix.cpp diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index d593fe0fa3..893597b158 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -2,7 +2,7 @@ add_simple_unittest(test_ExecViaCpu) add_simple_unittest(test_SIMDFunctions) -add_simple_unittest(test_matrix) +add_simple_unittest(test_SparseMatrix) # TODO(yuyang18): Refactor TestUtil.cpp. Remove this cross module reference. add_unittest(test_matrixCompare @@ -14,5 +14,6 @@ add_simple_unittest(test_perturbation) add_simple_unittest(test_CpuGpuVector) add_simple_unittest(test_Allocator) add_simple_unittest(test_FPException) -add_simple_unittest(test_BaseMatrix) add_simple_unittest(test_GpuProfiler) +add_simple_unittest(test_BaseMatrix) +add_simple_unittest(test_Matrix) diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h index f19933d1f6..d4821314f3 100644 --- a/paddle/math/tests/TensorCheck.h +++ b/paddle/math/tests/TensorCheck.h @@ -37,6 +37,7 @@ public: } } } + return true; } @@ -61,7 +62,7 @@ template <> class CopyToCpu { public: explicit CopyToCpu(const GpuMatrix& arg) - : arg_(arg.getHeight(), arg.getWidth()) { + : arg_(arg.getHeight(), arg.getWidth()) { arg_.copyFrom(arg); } CpuMatrix& copiedArg() { return arg_; } @@ -70,7 +71,7 @@ private: CpuMatrix arg_; }; -template +template void TensorCheck(AssertEq compare, const CpuMatrix& matrix1, const CpuMatrix& matrix2) { @@ -94,15 +95,19 @@ void TensorCheck(AssertEq compare, EXPECT_EQ(count, 0) << "There are " << count << " different element."; } -template +template extern void TensorCheck(AssertEq compare, const Tensor1& tensor1, const Tensor2& tensor2) { - TensorCheck( - compare, - CopyToCpu(tensor1).copiedArg(), - CopyToCpu(tensor2).copiedArg()); + TensorCheck(compare, + CopyToCpu(tensor1).copiedArg(), + CopyToCpu(tensor2).copiedArg()); } -} // namespace autotest +template +void TensorCheck(AssertEq compare, real args1, real args2) { + EXPECT_EQ(compare(args1, args2), true) << "[Test error] args1 = " << args1 + << ", args2 = " << args2; +} +} // namespace autotest diff --git a/paddle/math/tests/TestUtils.h b/paddle/math/tests/TestUtils.h index ffd1134542..96ba2c38c8 100644 --- a/paddle/math/tests/TestUtils.h +++ b/paddle/math/tests/TestUtils.h @@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - /** * TestUtils.h is used to automatically compare CPU and GPU code is consistent. * @@ -21,7 +20,7 @@ limitations under the License. */ * a. void BaseMatrix::tanh(BaseMatrixT& b); * Compare method: BaseMatrixCompare<0>(&BaseMatrix::tanh); * - * b. + * b. * */ @@ -33,102 +32,169 @@ using namespace paddle; // NOLINT namespace autotest { -template +template class ReplaceType { public: typedef T1 type; }; -template<> +template <> class ReplaceType { public: typedef CpuMatrix type; }; -template<> +template <> class ReplaceType { public: typedef GpuMatrix type; }; +template <> +class ReplaceType { +public: + typedef CpuMatrix type; +}; + +template <> +class ReplaceType { +public: + typedef GpuMatrix type; +}; + // construct a argument -template T construct(int height, int width); -template<> float construct(int height, int width) { return 0.0; } -template<> CpuMatrix construct(int height, int width) { +template +T construct(int height, int width); +template <> +float construct(int height, int width) { + return 0.0; +} +template <> +CpuMatrix construct(int height, int width) { CpuMatrix a(height, width); return a; } -template<> GpuMatrix construct(int height, int width) { +template <> +GpuMatrix construct(int height, int width) { GpuMatrix a(height, width); return a; } // init a argument -template void init(T& v); -template<> void init(float& v) { v = 0.5; } -template<> void init(CpuMatrix& v) { v.randomizeUniform(); } -template<> void init(GpuMatrix& v) { v.randomizeUniform(); } +template +void init(T& v); +template <> +void init(float& v) { + v = 0.5; +} +template <> +void init(CpuMatrix& v) { + v.randomizeUniform(); +} +template <> +void init(GpuMatrix& v) { + v.randomizeUniform(); +} // init a tuple which contains a set of arguments. -template -inline typename std::enable_if::type -initTuple(std::tuple& t){} +template +inline typename std::enable_if::type initTuple( + std::tuple& t) {} -template -inline typename std::enable_if::type -initTuple(std::tuple& t) { +template + inline typename std::enable_if < + I::type initTuple(std::tuple& t) { init(std::get(t)); initTuple(t); } // copy a argument, copy src to dest -template void copy(T1& dest, T2& src); -template<> void copy(float& dest, float& src) { dest = src; } -template<> void copy(GpuMatrix& dest, CpuMatrix& src) { +template +void copy(T1& dest, T2& src); +template <> +void copy(float& dest, float& src) { + dest = src; +} +template <> +void copy(GpuMatrix& dest, CpuMatrix& src) { dest.copyFrom(src); } // copy a tuple, copy src to dest -template -inline typename std::enable_if::type -copyTuple(std::tuple& dest, std::tuple& src) {} - -template -inline typename std::enable_if::type -copyTuple(std::tuple& dest, std::tuple& src) { +template +inline typename std::enable_if::type copyTuple( + std::tuple& dest, std::tuple& src) {} + +template + inline typename std::enable_if < + I::type copyTuple(std::tuple& dest, + std::tuple& src) { copy(std::get(dest), std::get(src)); copyTuple(dest, src); } +// Compare output +template +inline typename std::enable_if::type checkTuple( + std::tuple& args1, + std::tuple& args2, + AssertEq compare) {} + +template + inline typename std::enable_if < + I::type checkTuple(std::tuple& args1, + std::tuple& args2, + AssertEq compare) { + TensorCheck(compare, std::get(args1), std::get(args2)); + checkTuple(args1, args2, compare); +} + // call member function template + typename FC, + typename R, + typename... FArgs, + typename... Args> R call(C& obj, R (FC::*f)(FArgs...), Args&&... args) { return (obj.*f)(args...); } -template -void BaseMatrixCompare(R (C::*f)(Args...), AssertEq compare) { +void BaseMatrixCompare(R (C::*f)(Args...), + AssertEq compare, + bool checkArgs = false) { for (auto height : {1, 11, 73, 128, 200, 330}) { for (auto width : {1, 3, 32, 100, 512, 1000}) { - CpuMatrix obj1(ApplyCol ? 1 : height, - ApplyRow ? 1 : width); - GpuMatrix obj2(ApplyCol ? 1 : height, - ApplyRow ? 1 : width); + CpuMatrix obj1(ApplyCol ? 1 : height, ApplyRow ? 1 : width); + GpuMatrix obj2(ApplyCol ? 1 : height, ApplyRow ? 1 : width); init(obj1); copy(obj2, obj1); auto tuple1 = std::make_tuple( - construct>::type>::type, - CpuMatrix>::type>(height, width)...); + construct>::type>::type, + CpuMatrix>::type>(height, width)...); auto tuple2 = std::make_tuple( - construct>::type>::type, - GpuMatrix>::type>(height, width)...); + construct>::type>::type, + GpuMatrix>::type>(height, width)...); initTuple(tuple1); copyTuple(tuple2, tuple1); @@ -137,16 +203,19 @@ void BaseMatrixCompare(R (C::*f)(Args...), AssertEq compare) { call(obj2, f, std::get(tuple2)...); TensorCheck(compare, obj1, obj2); + if (checkArgs) { + checkTuple(tuple1, tuple2, compare); + } } } } } // namespace autotest -template -void BaseMatrixCompare(R (C::*f)(Args...)) { +template +void BaseMatrixCompare(R (C::*f)(Args...), bool checkArgs = false) { static_assert(sizeof...(I) == sizeof...(Args), - "size of parameter packs are not equal"); + "size of parameter packs are not equal"); #ifndef PADDLE_TYPE_DOUBLE autotest::AssertEqual compare(1e-5); @@ -154,13 +223,13 @@ void BaseMatrixCompare(R (C::*f)(Args...)) { autotest::AssertEqual compare(1e-10); #endif - autotest::BaseMatrixCompare(f, compare); + autotest::BaseMatrixCompare(f, compare, checkArgs); } -template +template void BaseMatrixApplyRow(R (C::*f)(Args...)) { static_assert(sizeof...(I) == sizeof...(Args), - "size of parameter packs are not equal"); + "size of parameter packs are not equal"); #ifndef PADDLE_TYPE_DOUBLE autotest::AssertEqual compare(1e-3); @@ -171,10 +240,10 @@ void BaseMatrixApplyRow(R (C::*f)(Args...)) { autotest::BaseMatrixCompare(f, compare); } -template +template void BaseMatrixApplyCol(R (C::*f)(Args...)) { static_assert(sizeof...(I) == sizeof...(Args), - "size of parameter packs are not equal"); + "size of parameter packs are not equal"); #ifndef PADDLE_TYPE_DOUBLE autotest::AssertEqual compare(1e-3); @@ -183,4 +252,3 @@ void BaseMatrixApplyCol(R (C::*f)(Args...)) { #endif autotest::BaseMatrixCompare(f, compare); } - diff --git a/paddle/math/tests/test_Matrix.cpp b/paddle/math/tests/test_Matrix.cpp new file mode 100644 index 0000000000..a4d9d34976 --- /dev/null +++ b/paddle/math/tests/test_Matrix.cpp @@ -0,0 +1,49 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#ifndef PADDLE_ONLY_CPU +/** + * This test file compares the implementation of CPU and GPU function + * in Matrix.cpp. + */ + +#include +#include "paddle/utils/Util.h" +#include "paddle/math/BaseMatrix.h" +#include "TestUtils.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +/** + * Test member functions which prototype is + * void (Matrix::*)(Matrix&). + */ +TEST(BaseMatrix, real) { + typedef void (Matrix::*FunctionProto)(Matrix&); +#define MATRIXCOMPARE(function) \ + BaseMatrixCompare<0>(static_cast(&Matrix::function), true); + + MATRIXCOMPARE(softmax); + +#undef MATRIXCOMPARE +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + initMain(argc, argv); + return RUN_ALL_TESTS(); +} + +#endif diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 8f96672b88..80596abe82 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -693,24 +693,6 @@ TEST(Matrix, unary) { } } -void testMatrixSoftmax(int height, int width) { - MatrixPtr cpuInput = std::make_shared(height, width); - MatrixPtr cpuOutput = std::make_shared(height, width); - MatrixPtr gpuInput = std::make_shared(height, width); - MatrixPtr gpuOutput = std::make_shared(height, width); - - cpuInput->randomizeUniform(); - gpuInput->copyFrom(*cpuInput); - cpuOutput->zero(); - gpuOutput->zero(); - cpuInput->softmax(*cpuOutput); - gpuInput->softmax(*gpuOutput); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuOutput); - MatrixCheckErr(*cpuOutput, *outputCheck); -} - void testSequenceSoftmax(int batchSize) { // forward int inputDim = 1; @@ -793,7 +775,6 @@ TEST(Matrix, softmax) { for (auto width : {1, 32, 100, 512, 1000}) { VLOG(3) << " height=" << height << " width=" << width; - testMatrixSoftmax(height, width); testMatrixSoftmaxBp(height, width); testMatrixSoftmaxThreshold(height, width); } -- GitLab From cf205d0d43a82ef46cd9374218555be339dab5eb Mon Sep 17 00:00:00 2001 From: xutianbing Date: Tue, 22 Nov 2016 14:24:30 -0800 Subject: [PATCH 0162/1503] deepSwap --- paddle/math/BaseMatrix.cu | 6 ++++++ paddle/math/BaseMatrix.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 2f32b3fdd1..a723ef7bc8 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1240,6 +1240,12 @@ void BaseMatrixT::assignAtOffset(BaseMatrixT& b, int64_t columnOffset) { } } +DEFINE_MATRIX_BINARY_OP(DeepSwap, T tmp = a; a = b; b = tmp); +template +void BaseMatrixT::deepSwap(BaseMatrixT& b) { + applyBinary(binary::DeepSwap(), b); +} + template<> void BaseMatrixT::rowDotMul(size_t destCol, BaseMatrixT& b, diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index d41dcee682..dbc217c30f 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -455,6 +455,13 @@ public: */ void assign(T p); + /** + * @code + * swap(this, b) + * @endcode + */ + void deepSwap(BaseMatrixT& b); + /** * @code * this = this + p -- GitLab From c7f96de12e9ce3f32cf8059a583eb9e5ff2b39e3 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Mon, 28 Nov 2016 15:47:16 -0800 Subject: [PATCH 0163/1503] add unit test for deepSwap --- paddle/math/tests/test_matrixCompare.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index ae5bc5a86a..de540dad4c 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -448,6 +448,24 @@ void testMatrixZeroAtOffset(int height, int width) { MatrixCheckEqual(*cpuA, *cpuTest); } +void testMatrixDeepSwap(int height, int width) { + MatrixPtr cpuA = std::make_shared(height, width); + MatrixPtr cpuB = std::make_shared(height, width); + MatrixPtr cpuCopyA = std::make_shared(height, width); + MatrixPtr cpuCopyB = std::make_shared(height, width); + + cpuA->randomizeUniform(); + cpuB->randomizeUniform(); + cpuCopyA->copyFrom(*cpuA); + cpuCopyB->copyFrom(*cpuB); + + // swap matrix cpuA and cpuB + cpuA->deepSwap(*cpuB); + + MatrixCheckEqual(*cpuA, *cpuCopyB); + MatrixCheckEqual(*cpuB, *cpuCopyA); +} + void testMatrixBinaryAdd(int height, int width) { MatrixPtr cpuA = std::make_shared(height, width); MatrixPtr cpuB = std::make_shared(height, width); @@ -480,6 +498,7 @@ void testMatrixAssign(int height, int width) { MatrixCheckEqual(*cpuA, *outputCheck); } + void testMatrixAdd(int height, int width) { MatrixPtr cpuA = std::make_shared(height, width); MatrixPtr gpuA = std::make_shared(height, width); @@ -798,6 +817,7 @@ TEST(Matrix, unary) { testMatrixBinaryAdd(height, width); testMatrixTanh(height, width); testMatrixTanhDerivative(height, width); + testMatrixDeepSwap(height, width); // applyTernary testMatrixTernarySub(height, width); -- GitLab From 5de5453d15eddc4a3eb2e57f1aaf83e571ab55e0 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Mon, 28 Nov 2016 16:25:56 -0800 Subject: [PATCH 0164/1503] add code comments for deepSwap --- paddle/math/BaseMatrix.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index dbc217c30f..ea58c861a3 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -458,6 +458,10 @@ public: /** * @code * swap(this, b) + * example: swap two Matrices + * MatrixPtr cpuA = std::make_shared(height, width); + * MatrixPtr cpuB = std::make_shared(height, width); + * cpuA->deepSwap(*cpuB); * @endcode */ void deepSwap(BaseMatrixT& b); -- GitLab From 1d8d9573225f44eb39a6af187b590b06d45dfb95 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 29 Nov 2016 11:32:43 +0800 Subject: [PATCH 0165/1503] Add `set -e` for paddle boot up script. * error when paddle has a wrong version number. --- paddle/scripts/submit_local.sh.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paddle/scripts/submit_local.sh.in b/paddle/scripts/submit_local.sh.in index 20ea2fedc4..ace2c0dee9 100644 --- a/paddle/scripts/submit_local.sh.in +++ b/paddle/scripts/submit_local.sh.in @@ -29,6 +29,7 @@ function version(){ } function ver2num() { + set -e # convert version to number. if [ -z "$1" ]; then # empty argument printf "%03d%03d%03d%03d%03d" 0 @@ -41,6 +42,7 @@ function ver2num() { printf "%03d%03d%03d%03d%03d" $VERN fi fi + set +e } PADDLE_CONF_HOME="$HOME/.config/paddle" -- GitLab From 7c25d9b8a1f422cdaadf26ac2c23f4b9fac4bc23 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 29 Nov 2016 11:46:49 +0800 Subject: [PATCH 0166/1503] fix style problem in doc_cn/introduction.rst --- doc_cn/build_and_install/index.rst | 5 - doc_cn/introduction/index.rst | 134 +++++++++++++++------------ doc_cn/ui/predict/swig_py_paddle.rst | 22 +++-- 3 files changed, 89 insertions(+), 72 deletions(-) diff --git a/doc_cn/build_and_install/index.rst b/doc_cn/build_and_install/index.rst index 2205e28224..48163fb36e 100644 --- a/doc_cn/build_and_install/index.rst +++ b/doc_cn/build_and_install/index.rst @@ -8,9 +8,7 @@ PaddlePaddle提供数个预编译的二进制来进行安装,包括Docker镜 .. toctree:: :maxdepth: 1 - :glob: - 使用Jumbo安装(对内) <../build/internal/install_from_jumbo.rst> install/docker_install.rst install/ubuntu_install.rst @@ -25,8 +23,5 @@ PaddlePaddle提供数个预编译的二进制来进行安装,包括Docker镜 .. toctree:: :maxdepth: 1 - :glob: - 源码下载(对内) <../build/internal/download_paddle_source_zh_cn.rst> - 从源码编译安装(对内) <../build/internal/build_from_source_zh_cn.rst> cmake/index.rst diff --git a/doc_cn/introduction/index.rst b/doc_cn/introduction/index.rst index f6eb5456c0..c996f5f4ac 100644 --- a/doc_cn/introduction/index.rst +++ b/doc_cn/introduction/index.rst @@ -1,102 +1,114 @@ -# 简介 +简介 +==== PaddlePaddle是源于百度的一个深度学习平台。这份简短的介绍将向你展示如何利用PaddlePaddle来解决一个经典的线性回归问题。 -## 1. 一个经典的任务 +1. 一个经典的任务 +----------------- -我们展示如何用PaddlePaddle解决单变量的线性回归问题。线性回归的输入是一批点`(x, y) `,其中 `y = wx + b + ε`, 而 ε 是一个符合高斯分布的随机变量。线性回归的输出是从这批点估计出来的参数 w 和 b。 +我们展示如何用PaddlePaddle解决 `单变量的线性回归 `_ 问题。线性回归的输入是一批点 `(x, y)` ,其中 `y = wx + b + ε`, 而 ε 是一个符合高斯分布的随机变量。线性回归的输出是从这批点估计出来的参数 `w` 和 `b` 。 一个例子是房产估值。我们假设房产的价格(y)是其大小(x)的一个线性函数,那么我们可以通过收集市场上房子的大小和价格,用来估计线性函数的参数w 和 b。 -## 2. 准备数据 +2. 准备数据 +----------- 假设变量 `x` 和 `y` 的真实关系为: `y = 2x + 0.3 + ε`,这里展示如何使用观测数据来拟合这一线性关系。首先,Python代码将随机产生2000个观测点,作为线性回归的输入。下面脚本符合PaddlePaddle期待的读取数据的Python程序的模式。 -```python -# dataprovider.py -from paddle.trainer.PyDataProvider2 import * -import random +.. code-block:: python -# 定义输入数据的类型: 2个浮点数 -@provider(input_types=[dense_vector(1), dense_vector(1)],use_seq=False) -def process(settings, input_file): - for i in xrange(2000): - x = random.random() - yield [x], [2*x+0.3] -``` + # dataprovider.py + from paddle.trainer.PyDataProvider2 import * + import random -## 3. 训练模型 + # 定义输入数据的类型: 2个浮点数 + @provider(input_types=[dense_vector(1), dense_vector(1)],use_seq=False) + def process(settings, input_file): + for i in xrange(2000): + x = random.random() + yield [x], [2*x+0.3] + +3. 训练模型 +----------- 为了还原 `y = 2x + 0.3`,我们先从一条随机的直线 `y' = wx + b` 开始,然后利用观测数据调整 `w` 和 `b` 使得 `y'` 和 `y` 的差距不断减小,最终趋于接近。这个过程就是模型的训练过程,而 `w` 和 `b` 就是模型的参数,即我们的训练目标。 在PaddlePaddle里,该模型的网络配置如下。 -```python -# trainer_config.py -from paddle.trainer_config_helpers import * - -# 1. 定义数据来源,调用上面的process函数获得观测数据 -data_file = 'empty.list' -with open(data_file, 'w') as f: f.writelines(' ') -define_py_data_sources2(train_list=data_file, test_list=None, - module='dataprovider', obj='process',args={}) - -# 2. 学习算法。控制如何改变模型参数 w 和 b -settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) - -# 3. 神经网络配置 -x = data_layer(name='x', size=1) -y = data_layer(name='y', size=1) -# 线性计算网络层: ȳ = wx + b -ȳ = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) -# 计算误差函数,即 ȳ 和真实 y 之间的距离 -cost = regression_cost(input= ȳ, label=y) -outputs(cost) -``` +.. code-block:: python + + # trainer_config.py + from paddle.trainer_config_helpers import * + + # 1. 定义数据来源,调用上面的process函数获得观测数据 + data_file = 'empty.list' + with open(data_file, 'w') as f: f.writelines(' ') + define_py_data_sources2(train_list=data_file, test_list=None, + module='dataprovider', obj='process',args={}) + + # 2. 学习算法。控制如何改变模型参数 w 和 b + settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) + + # 3. 神经网络配置 + x = data_layer(name='x', size=1) + y = data_layer(name='y', size=1) + # 线性计算网络层: ȳ = wx + b + ȳ = fc_layer(input=x, param_attr=ParamAttr(name='w'), size=1, act=LinearActivation(), bias_attr=ParamAttr(name='b')) + # 计算误差函数,即 ȳ 和真实 y 之间的距离 + cost = regression_cost(input= ȳ, label=y) + outputs(cost) + 这段简短的配置展示了PaddlePaddle的基本用法: -- 第一部分定义了数据输入。一般情况下,PaddlePaddle先从一个文件列表里获得数据文件地址,然后交给用户自定义的函数(例如上面的`process`函数)进行读入和预处理从而得到真实输入。本文中由于输入数据是随机生成的不需要读输入文件,所以放一个空列表(`empty.list`)即可。 +- 第一部分定义了数据输入。一般情况下,PaddlePaddle先从一个文件列表里获得数据文件地址,然后交给用户自定义的函数(例如上面的 `process`函数)进行读入和预处理从而得到真实输入。本文中由于输入数据是随机生成的不需要读输入文件,所以放一个空列表(`empty.list`)即可。 - 第二部分主要是选择学习算法,它定义了模型参数改变的规则。PaddlePaddle提供了很多优秀的学习算法,这里使用一个基于momentum的随机梯度下降(SGD)算法,该算法每批量(batch)读取12个采样数据进行随机梯度计算来更新更新。 - 最后一部分是神经网络的配置。由于PaddlePaddle已经实现了丰富的网络层,所以很多时候你需要做的只是定义正确的网络层并把它们连接起来。这里使用了三种网络单元: + - **数据层**:数据层 `data_layer` 是神经网络的入口,它读入数据并将它们传输到接下来的网络层。这里数据层有两个,分别对应于变量 `x` 和 `y`。 - **全连接层**:全连接层 `fc_layer` 是基础的计算单元,这里利用它建模变量之间的线性关系。计算单元是神经网络的核心,PaddlePaddle支持大量的计算单元和任意深度的网络连接,从而可以拟合任意的函数来学习复杂的数据关系。 - - **回归误差代价层**:回归误差代价层 `regression_cost`是众多误差代价函数层的一种,它们在训练过程作为网络的出口,用来计算模型的误差,是模型参数优化的目标函数。 + - **回归误差代价层**:回归误差代价层 `regression_cost` 是众多误差代价函数层的一种,它们在训练过程作为网络的出口,用来计算模型的误差,是模型参数优化的目标函数。 + +定义了网络结构并保存为 `trainer_config.py` 之后,运行以下训练命令: + +.. code-block:: bash -定义了网络结构并保存为`trainer_config.py`之后,运行以下训练命令: - ``` - paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 - ``` + paddle train --config=trainer_config.py --save_dir=./output --num_passes=30 PaddlePaddle将在观测数据集上迭代训练30轮,并将每轮的模型结果存放在 `./output` 路径下。从输出日志可以看到,随着轮数增加误差代价函数的输出在不断的减小,这意味着模型在训练数据上不断的改进,直到逼近真实解:` y = 2x + 0.3 ` -## 4. 模型检验 +4. 模型检验 +----------- 训练完成后,我们希望能够检验模型的好坏。一种常用的做法是用学习的模型对另外一组测试数据进行预测,评价预测的效果。在这个例子中,由于已经知道了真实答案,我们可以直接观察模型的参数是否符合预期来进行检验。 PaddlePaddle将每个模型参数作为一个numpy数组单独存为一个文件,所以可以利用如下方法读取模型的参数。 -```python -import numpy as np -import os +.. code-block:: python -def load(file_name): - with open(file_name, 'rb') as f: - f.read(16) # skip header for float type. - return np.fromfile(f, dtype=np.float32) + import numpy as np + import os + + def load(file_name): + with open(file_name, 'rb') as f: + f.read(16) # skip header for float type. + return np.fromfile(f, dtype=np.float32) -print 'w=%.6f, b=%.6f' % (load('output/pass-00029/w'), load('output/pass-00029/b')) -# w=1.999743, b=0.300137 -``` -
    ![](./parameters.png)
    + print 'w=%.6f, b=%.6f' % (load('output/pass-00029/w'), load('output/pass-00029/b')) + # w=1.999743, b=0.300137 + +.. image:: ./parameters.png + :align: center + :scale: 80 % 从图中可以看到,虽然 `w` 和 `b` 都使用随机值初始化,但在起初的几轮训练中它们都在快速逼近真实值,并且后续仍在不断改进,使得最终得到的模型几乎与真实模型一致。 -这样,我们用PaddlePaddle解决了单变量线性回归问题, 包括数据输入,模型训练和最后的结果验证。 +这样,我们用PaddlePaddle解决了单变量线性回归问题, 包括数据输入、模型训练和最后的结果验证。 -## 5. 推荐后续阅读 +5. 推荐后续阅读 +--------------- -- 安装/编译:PaddlePaddle的安装与编译文档。 -- 快速入门 :使用商品评论分类任务,系统性的介绍如何一步步改进,最终得到产品级的深度模型。 -- 示例:各种实用案例,涵盖图像、文本、推荐等多个领域。 +- `安装/编译 <../build_and_install/index.html>`_ :PaddlePaddle的安装与编译文档。 +- `快速入门 <../demo/quick_start/index.html>`_ :使用商品评论分类任务,系统性的介绍如何一步步改进,最终得到产品级的深度模型。 +- `示例 <../demo/index.html>`_ :各种实用案例,涵盖图像、文本、推荐等多个领域。 \ No newline at end of file diff --git a/doc_cn/ui/predict/swig_py_paddle.rst b/doc_cn/ui/predict/swig_py_paddle.rst index 89031dd72f..05f25345c5 100644 --- a/doc_cn/ui/predict/swig_py_paddle.rst +++ b/doc_cn/ui/predict/swig_py_paddle.rst @@ -9,15 +9,24 @@ PaddlePaddle使用swig对常用的预测接口进行了封装,通过编译会 基于Python的模型预测,主要包括以下五个步骤。 1. 初始化PaddlePaddle环境 - 在程序开始阶段,通过调用 ``swig_paddle.initPaddle()`` 并传入相应的命令行参数初始化PaddlePaddle。 + + 在程序开始阶段,通过调用 ``swig_paddle.initPaddle()`` 并传入相应的命令行参数初始化PaddlePaddle。 + 2. 解析模型配置文件 - 初始化之后,可以通过调用 ``parse_config()`` 解析训练模型时用的配置文件。注意预测数据通常不包含label, 同时预测网络通常直接输出最后一层的结果而不是像训练网络一样再接一层cost layer,所以一般需要对训练用的模型配置文件稍作相应修改才能在预测时使用。 + + 初始化之后,可以通过调用 ``parse_config()`` 解析训练模型时用的配置文件。注意预测数据通常不包含label, 同时预测网络通常直接输出最后一层的结果而不是像训练网络一样再接一层cost layer,所以一般需要对训练用的模型配置文件稍作相应修改才能在预测时使用。 + 3. 构造paddle.GradientMachine - 通过调用 ``swig_paddle.GradientMachine.createFromConfigproto()`` 传入上一步解析出来的模型配置就可以创建一个 ``GradientMachine``。 + + 通过调用 ``swig_paddle.GradientMachine.createFromConfigproto()`` 传入上一步解析出来的模型配置就可以创建一个 ``GradientMachine``。 + 4. 准备预测数据 - swig_paddle中的预测接口的参数是自定义的C++数据类型,py_paddle里面提供了一个工具类 ``DataProviderConverter`` 可以用于接收和PyDataProvider2一样的输入数据并转换成预测接口所需的数据类型。 + + swig_paddle中的预测接口的参数是自定义的C++数据类型,py_paddle里面提供了一个工具类 ``DataProviderConverter`` 可以用于接收和PyDataProvider2一样的输入数据并转换成预测接口所需的数据类型。 + 5. 模型预测 - 通过调用 ``forwardTest()`` 传入预测数据,直接返回计算结果。 + + 通过调用 ``forwardTest()`` 传入预测数据,直接返回计算结果。 预测Demo @@ -34,7 +43,8 @@ Demo预测输出如下,其中value即为softmax层的输出。由于TEST_DATA .. code-block:: text - [{'id': None, 'value': array([[ 5.53018653e-09, 1.12194102e-05, 1.96644767e-09, + [{'id': None, 'value': array( + [[ 5.53018653e-09, 1.12194102e-05, 1.96644767e-09, 1.43630644e-02, 1.51111044e-13, 9.85625684e-01, 2.08823112e-10, 2.32777140e-08, 2.00186201e-09, 1.15501715e-08], -- GitLab From a36df60a1ce7532aa15fec336512a98bbb79a5d5 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 29 Nov 2016 13:59:21 +0800 Subject: [PATCH 0167/1503] refine dataprovider rst based on comments --- doc_cn/ui/data_provider/dataprovider.rst | 14 ++++---- doc_cn/ui/data_provider/pydataprovider2.rst | 39 +++++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/doc_cn/ui/data_provider/dataprovider.rst b/doc_cn/ui/data_provider/dataprovider.rst index e1ad330c29..e6796429a7 100644 --- a/doc_cn/ui/data_provider/dataprovider.rst +++ b/doc_cn/ui/data_provider/dataprovider.rst @@ -1,15 +1,13 @@ DataProvider的介绍 ================== -DataProvider是PaddlePaddle负责提供数据的模块。其作用是将数据传入内存或显存,让神经网络可以进行训练或预测。有两种使用方式: - -- 简单使用:使用Python接口 `PyDataProvider2 `_ 来自定义传数据的过程。 -- 高级使用:如果用户有更复杂的使用,或者需要更高的效率,可以在C++端自定义一个 ``DataProvider`` 。 +DataProvider是PaddlePaddle负责提供数据的模块。其作用是将数据传入内存或显存,让神经网络可以进行训练或预测。用户可以通过简单使用Python接口 `PyDataProvider2 `_ ,来自定义传数据的过程。如果有更复杂的使用,或者需要更高的效率,用户也可以在C++端自定义一个 ``DataProvider`` 。 PaddlePaddle需要用户在网络配置(trainer_config.py)中定义使用哪种DataProvider,并且在DataProvider中实现如何访问训练文件列表(train.list)或测试文件列表(test.list)。 -- train.list和test.list存放在本地(推荐直接存放到训练目录,以相对路径引用)。一般情况下,两者均为纯文本文件,其中每一行对应一个数据文件地址: - - - 如果数据文件存于本地磁盘,则将这些文件的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)写在train.list和test.list中。 - - 地址也可以为hdfs文件路径,或者数据库连接地址等。 +- train.list和test.list存放在本地(推荐直接存放到训练目录,以相对路径引用)。一般情况下,两者均为纯文本文件,其中每一行对应一个数据文件地址: + + - 如果数据文件存于本地磁盘,这个地址则为它的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)。 + - 地址也可以为hdfs文件路径,或者数据库连接路径等。 + - 由于这个地址会被DataProvider使用,因此,如何解析该地址也是用户自定义DataProvider时需要考虑的地方。 - 如果没有设置test.list,或设置为None,那么在训练过程中不会执行测试操作;否则,会根据命令行参数指定的测试方式,在训练过程中进行测试,从而防止过拟合。 diff --git a/doc_cn/ui/data_provider/pydataprovider2.rst b/doc_cn/ui/data_provider/pydataprovider2.rst index 3455e331da..c0b3286ad5 100644 --- a/doc_cn/ui/data_provider/pydataprovider2.rst +++ b/doc_cn/ui/data_provider/pydataprovider2.rst @@ -1,14 +1,14 @@ PyDataProvider2的使用 ===================== -PyDataProvider2是PaddlePaddle使用Python提供数据的接口。该接口使用多线程读取数据,并提供了简单的Cache功能;同时可以使用户只关注如何从文件中读取每一条数据,而不用关心数据如何传输,如何存储等等。 +PyDataProvider2是PaddlePaddle使用Python提供数据的推荐接口。该接口使用多线程读取数据,并提供了简单的Cache功能;同时可以使用户只关注如何从文件中读取每一条数据,而不用关心数据如何传输,如何存储等等。 .. contents:: MNIST的使用场景 --------------- -我们以MNIST手写识别为例,来说明如何使用最简单的PyDataProvider2。 +我们以MNIST手写识别为例,来说明PyDataProvider2的简单使用场景。 样例数据 ++++++++ @@ -17,7 +17,7 @@ MNIST是一个包含有70,000张灰度图片的数字分类数据集。样例数 .. literalinclude:: mnist_train.txt -其中每行数据代表一张图片,行内使用 ``;`` 分成两部分。第一部分是图片的标签,为0-9中的一个数字;第二部分是28*28的图片像素灰度值。 对应的 ``train.list`` 为: +其中每行数据代表一张图片,行内使用 ``;`` 分成两部分。第一部分是图片的标签,为0-9中的一个数字;第二部分是28*28的图片像素灰度值。 对应的 ``train.list`` 即为这个数据文件的名字: .. literalinclude:: train.list @@ -40,7 +40,8 @@ dataprovider的使用 - 该函数的功能是:打开文本文件,读取每一行,将行中的数据转换成与input_types一致的格式,然后返回给PaddlePaddle进程。注意, - 返回的顺序需要和input_types中定义的顺序一致。 - - 返回时,必须使用关键词 ``yield`` 。一次yield调用,即返回一条完整的样本。如果想为一个数据文件返回多条样本,只需要在函数中调用多次yield即可(本例中使用for循环进行多次调用)。 + - 返回时,必须使用Python关键词 ``yield`` ,相关概念是 ``generator`` 。 + - 一次yield调用,返回一条完整的样本。如果想为一个数据文件返回多条样本,只需要在函数中调用多次yield即可(本例中使用for循环进行多次调用)。 - 该函数具有两个参数: @@ -55,7 +56,20 @@ dataprovider的使用 .. literalinclude:: mnist_config.py :lines: 1-7 -训练数据是 ``train.list`` ,测试数据没有,调用的PyDataProvider2是 ``mnist_provider`` 模块中的 ``process`` 函数。 +训练数据是 ``train.list`` ,没有测试数据,调用的PyDataProvider2是 ``mnist_provider`` 模块中的 ``process`` 函数。 + +小结 ++++++ + +至此,简单的PyDataProvider2样例就说明完毕了。对用户来说,仅需要知道如何从 **一个文件** 中读取 **一条样本** ,就可以将数据传送给PaddlePaddle了。而PaddlePaddle则会帮用户做以下工作: + +* 将数据组合成Batch进行训练 +* 对训练数据进行Shuffle +* 多线程的数据读取 +* 缓存训练数据到内存(可选) +* CPU->GPU双缓存 + +是不是很简单呢? 时序模型的使用场景 ------------------ @@ -89,19 +103,6 @@ dataprovider的使用 .. literalinclude:: sentimental_config.py :emphasize-lines: 12-14 -小结 ------ - -至此,两个PyDataProvider2的样例就说明完毕了。对用户来说,仅需要知道如何从 **一个文件** 中读取 **一条样本** ,就可以将数据传送给PaddlePaddle了。而PaddlePaddle则会帮用户做以下工作: - -* 将数据组合成Batch进行训练 -* 对训练数据进行Shuffle -* 多线程的数据读取 -* 缓存训练数据到内存(可选) -* CPU->GPU双缓存 - -是不是很简单呢? - 参考(Reference) --------------- @@ -167,6 +168,8 @@ init_hook可以传入一个函数。该函数在初始化的时候会被调用 * PaddlePaddle定义的参数: 1)is_train:bool型参数,表示用于训练或预测;2)file_list:所有文件列表。 * 用户定义的参数:使用args在网络配置中设置。 +注意:PaddlePaddle保留添加参数的权力,因此init_hook尽量使用 ``**kwargs`` 来接受不使用的函数以保证兼容性。 + cache +++++ -- GitLab From 5c551f90558955fd8d05a3bee55af6c302ddc0aa Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 29 Nov 2016 15:34:52 +0800 Subject: [PATCH 0168/1503] refine doc structure and revise some words --- doc/about/index.rst | 10 ++--- doc/api/data_provider/index.rst | 4 +- doc/api/data_provider/pydataprovider2.rst | 4 +- doc/api/index.md | 14 ------- doc/api/index.rst | 36 ++++++++++++++++++ doc/api/predict/swig_py_paddle_en.rst | 4 +- doc/api/trainer_config_helpers/attrs.rst | 4 +- doc/api/trainer_config_helpers/index.rst | 14 ------- .../basic_usage/basic_usage.rst | 0 .../basic_usage/parameters.png | Bin .../build_and_install/build_from_source.md | 0 .../build_and_install/cmake.png | Bin .../build_and_install/docker_install.rst | 0 .../build_and_install/index.rst | 0 .../build_and_install/ubuntu_install.rst | 0 doc/{introduction => getstarted}/index.rst | 2 +- doc/howto/algorithm/index.rst | 7 ---- doc/howto/algorithm/rnn/bi_lstm.jpg | 1 - .../rnn/encoder-decoder-attention-model.png | 1 - doc/howto/cluster/cluster_train.md | 2 +- .../arguments.md} | 0 .../detail_introduction.md | 0 .../{cmd_argument => cmd_parameter}/index.md | 4 +- .../use_case.md | 0 doc/howto/contribute_to_paddle.md | 2 +- doc/howto/deep_model/index.rst | 7 ++++ .../{algorithm => deep_model}/rnn/rnn.rst | 4 +- doc/howto/dev/index.rst | 9 ----- doc/howto/dev/layer.md | 5 --- doc/howto/index.rst | 27 ++++++++++--- .../{dev => }/new_layer/FullyConnected.jpg | Bin .../new_layer.rst => new_layer/index.rst} | 6 +-- doc/howto/optimization/index.rst | 4 +- doc/howto/{dev => }/source/api.rst | 0 doc/howto/{dev => }/source/cuda/index.rst | 0 doc/howto/{dev => }/source/cuda/matrix.rst | 0 doc/howto/{dev => }/source/cuda/nn.rst | 0 doc/howto/{dev => }/source/cuda/utils.rst | 0 .../{dev => }/source/gserver/activations.rst | 0 .../source/gserver/dataproviders.rst | 0 .../{dev => }/source/gserver/evaluators.rst | 0 .../source/gserver/gradientmachines.rst | 0 doc/howto/{dev => }/source/gserver/index.rst | 0 doc/howto/{dev => }/source/gserver/layers.rst | 0 .../{dev => }/source/gserver/neworks.rst | 0 doc/howto/{dev => }/source/index.rst | 0 doc/howto/{dev => }/source/math/functions.rst | 0 doc/howto/{dev => }/source/math/index.rst | 0 doc/howto/{dev => }/source/math/matrix.rst | 0 doc/howto/{dev => }/source/math/utils.rst | 0 doc/howto/{dev => }/source/math/vector.rst | 0 .../{dev => }/source/parameter/index.rst | 0 .../{dev => }/source/parameter/optimizer.rst | 0 .../{dev => }/source/parameter/parameter.rst | 0 .../{dev => }/source/parameter/updater.rst | 0 doc/howto/{dev => }/source/pserver/client.rst | 0 doc/howto/{dev => }/source/pserver/index.rst | 0 .../{dev => }/source/pserver/network.rst | 0 doc/howto/{dev => }/source/pserver/server.rst | 0 doc/howto/{dev => }/source/trainer.rst | 0 .../source/utils/customStackTrace.rst | 0 doc/howto/{dev => }/source/utils/enum.rst | 0 doc/howto/{dev => }/source/utils/index.rst | 0 doc/howto/{dev => }/source/utils/lock.rst | 0 doc/howto/{dev => }/source/utils/queue.rst | 0 doc/howto/{dev => }/source/utils/thread.rst | 0 doc/index.rst | 2 +- doc/tutorials/index.md | 2 +- 68 files changed, 92 insertions(+), 83 deletions(-) delete mode 100644 doc/api/index.md create mode 100644 doc/api/index.rst delete mode 100644 doc/api/trainer_config_helpers/index.rst rename doc/{introduction => getstarted}/basic_usage/basic_usage.rst (100%) rename doc/{introduction => getstarted}/basic_usage/parameters.png (100%) rename doc/{introduction => getstarted}/build_and_install/build_from_source.md (100%) rename doc/{introduction => getstarted}/build_and_install/cmake.png (100%) rename doc/{introduction => getstarted}/build_and_install/docker_install.rst (100%) rename doc/{introduction => getstarted}/build_and_install/index.rst (100%) rename doc/{introduction => getstarted}/build_and_install/ubuntu_install.rst (100%) rename doc/{introduction => getstarted}/index.rst (88%) delete mode 100644 doc/howto/algorithm/index.rst delete mode 120000 doc/howto/algorithm/rnn/bi_lstm.jpg delete mode 120000 doc/howto/algorithm/rnn/encoder-decoder-attention-model.png rename doc/howto/{cmd_argument/argument_outline.md => cmd_parameter/arguments.md} (100%) rename doc/howto/{cmd_argument => cmd_parameter}/detail_introduction.md (100%) rename doc/howto/{cmd_argument => cmd_parameter}/index.md (53%) rename doc/howto/{cmd_argument => cmd_parameter}/use_case.md (100%) create mode 100644 doc/howto/deep_model/index.rst rename doc/howto/{algorithm => deep_model}/rnn/rnn.rst (99%) delete mode 100644 doc/howto/dev/index.rst delete mode 100644 doc/howto/dev/layer.md rename doc/howto/{dev => }/new_layer/FullyConnected.jpg (100%) rename doc/howto/{dev/new_layer/new_layer.rst => new_layer/index.rst} (99%) rename doc/howto/{dev => }/source/api.rst (100%) rename doc/howto/{dev => }/source/cuda/index.rst (100%) rename doc/howto/{dev => }/source/cuda/matrix.rst (100%) rename doc/howto/{dev => }/source/cuda/nn.rst (100%) rename doc/howto/{dev => }/source/cuda/utils.rst (100%) rename doc/howto/{dev => }/source/gserver/activations.rst (100%) rename doc/howto/{dev => }/source/gserver/dataproviders.rst (100%) rename doc/howto/{dev => }/source/gserver/evaluators.rst (100%) rename doc/howto/{dev => }/source/gserver/gradientmachines.rst (100%) rename doc/howto/{dev => }/source/gserver/index.rst (100%) rename doc/howto/{dev => }/source/gserver/layers.rst (100%) rename doc/howto/{dev => }/source/gserver/neworks.rst (100%) rename doc/howto/{dev => }/source/index.rst (100%) rename doc/howto/{dev => }/source/math/functions.rst (100%) rename doc/howto/{dev => }/source/math/index.rst (100%) rename doc/howto/{dev => }/source/math/matrix.rst (100%) rename doc/howto/{dev => }/source/math/utils.rst (100%) rename doc/howto/{dev => }/source/math/vector.rst (100%) rename doc/howto/{dev => }/source/parameter/index.rst (100%) rename doc/howto/{dev => }/source/parameter/optimizer.rst (100%) rename doc/howto/{dev => }/source/parameter/parameter.rst (100%) rename doc/howto/{dev => }/source/parameter/updater.rst (100%) rename doc/howto/{dev => }/source/pserver/client.rst (100%) rename doc/howto/{dev => }/source/pserver/index.rst (100%) rename doc/howto/{dev => }/source/pserver/network.rst (100%) rename doc/howto/{dev => }/source/pserver/server.rst (100%) rename doc/howto/{dev => }/source/trainer.rst (100%) rename doc/howto/{dev => }/source/utils/customStackTrace.rst (100%) rename doc/howto/{dev => }/source/utils/enum.rst (100%) rename doc/howto/{dev => }/source/utils/index.rst (100%) rename doc/howto/{dev => }/source/utils/lock.rst (100%) rename doc/howto/{dev => }/source/utils/queue.rst (100%) rename doc/howto/{dev => }/source/utils/thread.rst (100%) diff --git a/doc/about/index.rst b/doc/about/index.rst index 511c154641..8a372d2bc2 100644 --- a/doc/about/index.rst +++ b/doc/about/index.rst @@ -1,14 +1,14 @@ -About +ABOUT ======= - -Credits --------- - PaddlPaddle is an easy-to-use, efficient, flexible and scalable deep learning platform, which is originally developed by Baidu scientists and engineers for the purpose of applying deep learning to many products at Baidu. PaddlePaddle is now open source but far from complete, which is intended to be built upon, improved, scaled, and extended. We hope to build an active open source community both by providing feedback and by actively contributing to the source code. + +Credits +-------- + We owe many thanks to `all contributors and developers `_ of PaddlePaddle! diff --git a/doc/api/data_provider/index.rst b/doc/api/data_provider/index.rst index 3db5b57376..5e7a49d632 100644 --- a/doc/api/data_provider/index.rst +++ b/doc/api/data_provider/index.rst @@ -1,5 +1,5 @@ -DataProvider Introduction -========================= +Introduction +============== DataProvider is a module that loads training or testing data into cpu or gpu memory for the following triaining or testing process. diff --git a/doc/api/data_provider/pydataprovider2.rst b/doc/api/data_provider/pydataprovider2.rst index e105d3be30..b42cbca576 100644 --- a/doc/api/data_provider/pydataprovider2.rst +++ b/doc/api/data_provider/pydataprovider2.rst @@ -1,5 +1,5 @@ -How to use PyDataProvider2 -========================== +PyDataProvider2 +================= We highly recommand users to use PyDataProvider2 to provide training or testing data to PaddlePaddle. The user only needs to focus on how to read a single diff --git a/doc/api/index.md b/doc/api/index.md deleted file mode 100644 index 8c4a65e0d5..0000000000 --- a/doc/api/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# API - -## Data Provider - -* [Introduction](data_provider/index.rst) -* [PyDataProvider2](data_provider/pydataprovider2.rst) - -## Trainer Configuration - -* [Model Config Interface](trainer_config_helpers/index.rst) - -## Predict - -* [Python Prediction API](predict/swig_py_paddle_en.rst) diff --git a/doc/api/index.rst b/doc/api/index.rst new file mode 100644 index 0000000000..ccee7a0f1f --- /dev/null +++ b/doc/api/index.rst @@ -0,0 +1,36 @@ +API +==== + +DataProvider API +---------------- + +.. toctree:: + :maxdepth: 1 + + data_provider/index.rst + data_provider/pydataprovider2.rst + +Model Config API +---------------- + +.. toctree:: + :maxdepth: 1 + + trainer_config_helpers/index.rst + trainer_config_helpers/optimizers.rst + trainer_config_helpers/data_sources.rst + trainer_config_helpers/layers.rst + trainer_config_helpers/activations.rst + trainer_config_helpers/poolings.rst + trainer_config_helpers/networks.rst + trainer_config_helpers/evaluators.rst + trainer_config_helpers/attrs.rst + + +Applications API +---------------- + +.. toctree:: + :maxdepth: 1 + + predict/swig_py_paddle_en.rst \ No newline at end of file diff --git a/doc/api/predict/swig_py_paddle_en.rst b/doc/api/predict/swig_py_paddle_en.rst index b743fc4569..9845cd1607 100644 --- a/doc/api/predict/swig_py_paddle_en.rst +++ b/doc/api/predict/swig_py_paddle_en.rst @@ -1,5 +1,5 @@ -Python Prediction API -===================== +Python Prediction +================== PaddlePaddle offers a set of clean prediction interfaces for python with the help of SWIG. The main steps of predict values in python are: diff --git a/doc/api/trainer_config_helpers/attrs.rst b/doc/api/trainer_config_helpers/attrs.rst index 44919aba90..ac63127bf7 100644 --- a/doc/api/trainer_config_helpers/attrs.rst +++ b/doc/api/trainer_config_helpers/attrs.rst @@ -1,5 +1,5 @@ -Parameter and Extra Layer Attribute -=================================== +Parameter Attributes +======================= .. automodule:: paddle.trainer_config_helpers.attrs :members: diff --git a/doc/api/trainer_config_helpers/index.rst b/doc/api/trainer_config_helpers/index.rst deleted file mode 100644 index 8395eb7571..0000000000 --- a/doc/api/trainer_config_helpers/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Model Config Interface -====================== - -.. toctree:: - :maxdepth: 1 - - optimizers.rst - data_sources.rst - layers.rst - activations.rst - poolings.rst - networks.rst - evaluators.rst - attrs.rst diff --git a/doc/introduction/basic_usage/basic_usage.rst b/doc/getstarted/basic_usage/basic_usage.rst similarity index 100% rename from doc/introduction/basic_usage/basic_usage.rst rename to doc/getstarted/basic_usage/basic_usage.rst diff --git a/doc/introduction/basic_usage/parameters.png b/doc/getstarted/basic_usage/parameters.png similarity index 100% rename from doc/introduction/basic_usage/parameters.png rename to doc/getstarted/basic_usage/parameters.png diff --git a/doc/introduction/build_and_install/build_from_source.md b/doc/getstarted/build_and_install/build_from_source.md similarity index 100% rename from doc/introduction/build_and_install/build_from_source.md rename to doc/getstarted/build_and_install/build_from_source.md diff --git a/doc/introduction/build_and_install/cmake.png b/doc/getstarted/build_and_install/cmake.png similarity index 100% rename from doc/introduction/build_and_install/cmake.png rename to doc/getstarted/build_and_install/cmake.png diff --git a/doc/introduction/build_and_install/docker_install.rst b/doc/getstarted/build_and_install/docker_install.rst similarity index 100% rename from doc/introduction/build_and_install/docker_install.rst rename to doc/getstarted/build_and_install/docker_install.rst diff --git a/doc/introduction/build_and_install/index.rst b/doc/getstarted/build_and_install/index.rst similarity index 100% rename from doc/introduction/build_and_install/index.rst rename to doc/getstarted/build_and_install/index.rst diff --git a/doc/introduction/build_and_install/ubuntu_install.rst b/doc/getstarted/build_and_install/ubuntu_install.rst similarity index 100% rename from doc/introduction/build_and_install/ubuntu_install.rst rename to doc/getstarted/build_and_install/ubuntu_install.rst diff --git a/doc/introduction/index.rst b/doc/getstarted/index.rst similarity index 88% rename from doc/introduction/index.rst rename to doc/getstarted/index.rst index ff22f05a1b..5f2787066e 100644 --- a/doc/introduction/index.rst +++ b/doc/getstarted/index.rst @@ -1,4 +1,4 @@ -Introduction +GET STARTED ============ .. toctree:: diff --git a/doc/howto/algorithm/index.rst b/doc/howto/algorithm/index.rst deleted file mode 100644 index b4ecbc4847..0000000000 --- a/doc/howto/algorithm/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Algorithm Configuration -======================= - -.. toctree:: - :maxdepth: 1 - - rnn/rnn.rst diff --git a/doc/howto/algorithm/rnn/bi_lstm.jpg b/doc/howto/algorithm/rnn/bi_lstm.jpg deleted file mode 120000 index f8f3b17691..0000000000 --- a/doc/howto/algorithm/rnn/bi_lstm.jpg +++ /dev/null @@ -1 +0,0 @@ -../../../tutorials/sentiment_analysis/bi_lstm.jpg \ No newline at end of file diff --git a/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png b/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png deleted file mode 120000 index 88a1d3e5ac..0000000000 --- a/doc/howto/algorithm/rnn/encoder-decoder-attention-model.png +++ /dev/null @@ -1 +0,0 @@ -../../../tutorials/text_generation/encoder-decoder-attention-model.png \ No newline at end of file diff --git a/doc/howto/cluster/cluster_train.md b/doc/howto/cluster/cluster_train.md index 6b68596dc1..1de34a6a99 100644 --- a/doc/howto/cluster/cluster_train.md +++ b/doc/howto/cluster/cluster_train.md @@ -1,4 +1,4 @@ -# Distributed Training +# How to Run Distributed Training In this article, we explain how to run distributed Paddle training jobs on clusters. We will create the distributed version of the single-process training example, [recommendation](https://github.com/baidu/Paddle/tree/develop/demo/recommendation). diff --git a/doc/howto/cmd_argument/argument_outline.md b/doc/howto/cmd_parameter/arguments.md similarity index 100% rename from doc/howto/cmd_argument/argument_outline.md rename to doc/howto/cmd_parameter/arguments.md diff --git a/doc/howto/cmd_argument/detail_introduction.md b/doc/howto/cmd_parameter/detail_introduction.md similarity index 100% rename from doc/howto/cmd_argument/detail_introduction.md rename to doc/howto/cmd_parameter/detail_introduction.md diff --git a/doc/howto/cmd_argument/index.md b/doc/howto/cmd_parameter/index.md similarity index 53% rename from doc/howto/cmd_argument/index.md rename to doc/howto/cmd_parameter/index.md index 90472c44cb..48cf835de1 100644 --- a/doc/howto/cmd_argument/index.md +++ b/doc/howto/cmd_parameter/index.md @@ -1,5 +1,5 @@ -# Command Line Argument +# How to Set Command-line Parameters * [Use Case](use_case.md) -* [Argument Outline](argument_outline.md) +* [Arguments](arguments.md) * [Detailed Descriptions](detail_introduction.md) diff --git a/doc/howto/cmd_argument/use_case.md b/doc/howto/cmd_parameter/use_case.md similarity index 100% rename from doc/howto/cmd_argument/use_case.md rename to doc/howto/cmd_parameter/use_case.md diff --git a/doc/howto/contribute_to_paddle.md b/doc/howto/contribute_to_paddle.md index 1d03eb7362..d1f12c6ab2 100644 --- a/doc/howto/contribute_to_paddle.md +++ b/doc/howto/contribute_to_paddle.md @@ -1,4 +1,4 @@ -# Contribute Code +# How to Contribute Code We sincerely appreciate your contributions. You can use fork and pull request workflow to merge your code. diff --git a/doc/howto/deep_model/index.rst b/doc/howto/deep_model/index.rst new file mode 100644 index 0000000000..06ef443f62 --- /dev/null +++ b/doc/howto/deep_model/index.rst @@ -0,0 +1,7 @@ +How to Configure Deep Models +============================ + +.. toctree:: + :maxdepth: 1 + + rnn/rnn.rst diff --git a/doc/howto/algorithm/rnn/rnn.rst b/doc/howto/deep_model/rnn/rnn.rst similarity index 99% rename from doc/howto/algorithm/rnn/rnn.rst rename to doc/howto/deep_model/rnn/rnn.rst index 01d2caefb5..da29b8efad 100644 --- a/doc/howto/algorithm/rnn/rnn.rst +++ b/doc/howto/deep_model/rnn/rnn.rst @@ -42,7 +42,7 @@ Simple Gated Recurrent Neural Network Recurrent neural network process a sequence at each time step sequentially. An example of the architecture of LSTM is listed below. -.. image:: ./bi_lstm.jpg +.. image:: ../../../tutorials/sentiment_analysis/bi_lstm.jpg :align: center Generally speaking, a recurrent network perform the following operations from :math:`t=1` to :math:`t=T`, or reversely from :math:`t=T` to :math:`t=1`. @@ -101,7 +101,7 @@ Sequence to Sequence Model with Attention ----------------------------------------- We will use the sequence to sequence model with attention as an example to demonstrate how you can configure complex recurrent neural network models. An illustration of the sequence to sequence model with attention is shown in the following figure. -.. image:: ./encoder-decoder-attention-model.png +.. image:: ../../../tutorials/text_generation/encoder-decoder-attention-model.png :align: center In this model, the source sequence :math:`S = \{s_1, \dots, s_T\}` is encoded with a bidirectional gated recurrent neural networks. The hidden states of the bidirectional gated recurrent neural network :math:`H_S = \{H_1, \dots, H_T\}` is called *encoder vector* The decoder is a gated recurrent neural network. When decoding each token :math:`y_t`, the gated recurrent neural network generates a set of weights :math:`W_S^t = \{W_1^t, \dots, W_T^t\}`, which are used to compute a weighted sum of the encoder vector. The weighted sum of the encoder vector is utilized to condition the generation of the token :math:`y_t`. diff --git a/doc/howto/dev/index.rst b/doc/howto/dev/index.rst deleted file mode 100644 index 876c42e9db..0000000000 --- a/doc/howto/dev/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Development Guide -================= - -.. toctree:: - :maxdepth: 2 - - layer.md - new_layer/new_layer.rst - source/index.rst diff --git a/doc/howto/dev/layer.md b/doc/howto/dev/layer.md deleted file mode 100644 index 1ce0cc5829..0000000000 --- a/doc/howto/dev/layer.md +++ /dev/null @@ -1,5 +0,0 @@ -# Layer Documents - -* [Layer Python API](../../api/trainer_config_helpers/index.rst) -* [Layer Source Code](source/gserver/layers.rst) -* [Writing New Layers](new_layer/new_layer.rst) diff --git a/doc/howto/index.rst b/doc/howto/index.rst index ed8294b3c1..41877a64a5 100644 --- a/doc/howto/index.rst +++ b/doc/howto/index.rst @@ -1,12 +1,29 @@ -How to +HOW TO ======= +Usage +------- + .. toctree:: :maxdepth: 1 - cmd_argument/index.md + cmd_parameter/index.md + deep_model/index.rst cluster/cluster_train.md - algorithm/index.rst + +Development +------------ + +.. toctree:: + :maxdepth: 1 + + new_layer/index.rst + contribute_to_paddle.md + +Optimization +------------- + +.. toctree:: + :maxdepth: 1 + optimization/index.rst - dev/index.rst - contribute_to_paddle.md \ No newline at end of file diff --git a/doc/howto/dev/new_layer/FullyConnected.jpg b/doc/howto/new_layer/FullyConnected.jpg similarity index 100% rename from doc/howto/dev/new_layer/FullyConnected.jpg rename to doc/howto/new_layer/FullyConnected.jpg diff --git a/doc/howto/dev/new_layer/new_layer.rst b/doc/howto/new_layer/index.rst similarity index 99% rename from doc/howto/dev/new_layer/new_layer.rst rename to doc/howto/new_layer/index.rst index af8b76a307..922bda5b0d 100644 --- a/doc/howto/dev/new_layer/new_layer.rst +++ b/doc/howto/new_layer/index.rst @@ -1,6 +1,6 @@ -================== -Writing New Layers -================== +======================= +How to Write New Layers +======================= This tutorial will guide you to write customized layers in PaddlePaddle. We will utilize fully connected layer as an example to guide you through the following steps for writing a new layer. diff --git a/doc/howto/optimization/index.rst b/doc/howto/optimization/index.rst index c9e87e0778..e2822a0098 100644 --- a/doc/howto/optimization/index.rst +++ b/doc/howto/optimization/index.rst @@ -1,5 +1,5 @@ -Performance Tuning -================== +How to Tune GPU Performance +=========================== .. toctree:: :maxdepth: 3 diff --git a/doc/howto/dev/source/api.rst b/doc/howto/source/api.rst similarity index 100% rename from doc/howto/dev/source/api.rst rename to doc/howto/source/api.rst diff --git a/doc/howto/dev/source/cuda/index.rst b/doc/howto/source/cuda/index.rst similarity index 100% rename from doc/howto/dev/source/cuda/index.rst rename to doc/howto/source/cuda/index.rst diff --git a/doc/howto/dev/source/cuda/matrix.rst b/doc/howto/source/cuda/matrix.rst similarity index 100% rename from doc/howto/dev/source/cuda/matrix.rst rename to doc/howto/source/cuda/matrix.rst diff --git a/doc/howto/dev/source/cuda/nn.rst b/doc/howto/source/cuda/nn.rst similarity index 100% rename from doc/howto/dev/source/cuda/nn.rst rename to doc/howto/source/cuda/nn.rst diff --git a/doc/howto/dev/source/cuda/utils.rst b/doc/howto/source/cuda/utils.rst similarity index 100% rename from doc/howto/dev/source/cuda/utils.rst rename to doc/howto/source/cuda/utils.rst diff --git a/doc/howto/dev/source/gserver/activations.rst b/doc/howto/source/gserver/activations.rst similarity index 100% rename from doc/howto/dev/source/gserver/activations.rst rename to doc/howto/source/gserver/activations.rst diff --git a/doc/howto/dev/source/gserver/dataproviders.rst b/doc/howto/source/gserver/dataproviders.rst similarity index 100% rename from doc/howto/dev/source/gserver/dataproviders.rst rename to doc/howto/source/gserver/dataproviders.rst diff --git a/doc/howto/dev/source/gserver/evaluators.rst b/doc/howto/source/gserver/evaluators.rst similarity index 100% rename from doc/howto/dev/source/gserver/evaluators.rst rename to doc/howto/source/gserver/evaluators.rst diff --git a/doc/howto/dev/source/gserver/gradientmachines.rst b/doc/howto/source/gserver/gradientmachines.rst similarity index 100% rename from doc/howto/dev/source/gserver/gradientmachines.rst rename to doc/howto/source/gserver/gradientmachines.rst diff --git a/doc/howto/dev/source/gserver/index.rst b/doc/howto/source/gserver/index.rst similarity index 100% rename from doc/howto/dev/source/gserver/index.rst rename to doc/howto/source/gserver/index.rst diff --git a/doc/howto/dev/source/gserver/layers.rst b/doc/howto/source/gserver/layers.rst similarity index 100% rename from doc/howto/dev/source/gserver/layers.rst rename to doc/howto/source/gserver/layers.rst diff --git a/doc/howto/dev/source/gserver/neworks.rst b/doc/howto/source/gserver/neworks.rst similarity index 100% rename from doc/howto/dev/source/gserver/neworks.rst rename to doc/howto/source/gserver/neworks.rst diff --git a/doc/howto/dev/source/index.rst b/doc/howto/source/index.rst similarity index 100% rename from doc/howto/dev/source/index.rst rename to doc/howto/source/index.rst diff --git a/doc/howto/dev/source/math/functions.rst b/doc/howto/source/math/functions.rst similarity index 100% rename from doc/howto/dev/source/math/functions.rst rename to doc/howto/source/math/functions.rst diff --git a/doc/howto/dev/source/math/index.rst b/doc/howto/source/math/index.rst similarity index 100% rename from doc/howto/dev/source/math/index.rst rename to doc/howto/source/math/index.rst diff --git a/doc/howto/dev/source/math/matrix.rst b/doc/howto/source/math/matrix.rst similarity index 100% rename from doc/howto/dev/source/math/matrix.rst rename to doc/howto/source/math/matrix.rst diff --git a/doc/howto/dev/source/math/utils.rst b/doc/howto/source/math/utils.rst similarity index 100% rename from doc/howto/dev/source/math/utils.rst rename to doc/howto/source/math/utils.rst diff --git a/doc/howto/dev/source/math/vector.rst b/doc/howto/source/math/vector.rst similarity index 100% rename from doc/howto/dev/source/math/vector.rst rename to doc/howto/source/math/vector.rst diff --git a/doc/howto/dev/source/parameter/index.rst b/doc/howto/source/parameter/index.rst similarity index 100% rename from doc/howto/dev/source/parameter/index.rst rename to doc/howto/source/parameter/index.rst diff --git a/doc/howto/dev/source/parameter/optimizer.rst b/doc/howto/source/parameter/optimizer.rst similarity index 100% rename from doc/howto/dev/source/parameter/optimizer.rst rename to doc/howto/source/parameter/optimizer.rst diff --git a/doc/howto/dev/source/parameter/parameter.rst b/doc/howto/source/parameter/parameter.rst similarity index 100% rename from doc/howto/dev/source/parameter/parameter.rst rename to doc/howto/source/parameter/parameter.rst diff --git a/doc/howto/dev/source/parameter/updater.rst b/doc/howto/source/parameter/updater.rst similarity index 100% rename from doc/howto/dev/source/parameter/updater.rst rename to doc/howto/source/parameter/updater.rst diff --git a/doc/howto/dev/source/pserver/client.rst b/doc/howto/source/pserver/client.rst similarity index 100% rename from doc/howto/dev/source/pserver/client.rst rename to doc/howto/source/pserver/client.rst diff --git a/doc/howto/dev/source/pserver/index.rst b/doc/howto/source/pserver/index.rst similarity index 100% rename from doc/howto/dev/source/pserver/index.rst rename to doc/howto/source/pserver/index.rst diff --git a/doc/howto/dev/source/pserver/network.rst b/doc/howto/source/pserver/network.rst similarity index 100% rename from doc/howto/dev/source/pserver/network.rst rename to doc/howto/source/pserver/network.rst diff --git a/doc/howto/dev/source/pserver/server.rst b/doc/howto/source/pserver/server.rst similarity index 100% rename from doc/howto/dev/source/pserver/server.rst rename to doc/howto/source/pserver/server.rst diff --git a/doc/howto/dev/source/trainer.rst b/doc/howto/source/trainer.rst similarity index 100% rename from doc/howto/dev/source/trainer.rst rename to doc/howto/source/trainer.rst diff --git a/doc/howto/dev/source/utils/customStackTrace.rst b/doc/howto/source/utils/customStackTrace.rst similarity index 100% rename from doc/howto/dev/source/utils/customStackTrace.rst rename to doc/howto/source/utils/customStackTrace.rst diff --git a/doc/howto/dev/source/utils/enum.rst b/doc/howto/source/utils/enum.rst similarity index 100% rename from doc/howto/dev/source/utils/enum.rst rename to doc/howto/source/utils/enum.rst diff --git a/doc/howto/dev/source/utils/index.rst b/doc/howto/source/utils/index.rst similarity index 100% rename from doc/howto/dev/source/utils/index.rst rename to doc/howto/source/utils/index.rst diff --git a/doc/howto/dev/source/utils/lock.rst b/doc/howto/source/utils/lock.rst similarity index 100% rename from doc/howto/dev/source/utils/lock.rst rename to doc/howto/source/utils/lock.rst diff --git a/doc/howto/dev/source/utils/queue.rst b/doc/howto/source/utils/queue.rst similarity index 100% rename from doc/howto/dev/source/utils/queue.rst rename to doc/howto/source/utils/queue.rst diff --git a/doc/howto/dev/source/utils/thread.rst b/doc/howto/source/utils/thread.rst similarity index 100% rename from doc/howto/dev/source/utils/thread.rst rename to doc/howto/source/utils/thread.rst diff --git a/doc/index.rst b/doc/index.rst index 36a410881a..3555da1dfc 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -4,7 +4,7 @@ PaddlePaddle Documentation .. toctree:: :maxdepth: 1 - introduction/index.rst + getstarted/index.rst tutorials/index.md howto/index.rst api/index.rst diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index c845ca229c..ebf5397391 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -1,4 +1,4 @@ -# Tutorials +# TUTORIALS There are serveral examples and demos here. ## Image -- GitLab From 4d487c6f350b168f5e24094adeacb3c193d5d888 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 29 Nov 2016 07:40:17 +0000 Subject: [PATCH 0169/1503] Integrate warp-ctc as WarpCTCLayer, including unitest and layer interface. --- .gitmodules | 0 CMakeLists.txt | 5 + cmake/util.cmake | 5 + paddle/cuda/CMakeLists.txt | 34 ++- paddle/cuda/include/hl_dso_loader.h | 12 +- paddle/cuda/include/hl_gpu.h | 1 + paddle/cuda/include/hl_sequence.h | 33 +++ paddle/cuda/include/hl_warpctc_wrap.h | 94 +++++++ paddle/cuda/include/stub/hl_sequence_stub.h | 9 + paddle/cuda/src/hl_cuda_sequence.cu | 118 +++++++++ paddle/cuda/src/hl_cudart_wrap.cc | 1 + paddle/cuda/src/hl_dso_loader.cc | 25 +- paddle/cuda/src/hl_warpctc_wrap.cc | 157 +++++++++++ paddle/gserver/layers/WarpCTCLayer.cpp | 229 ++++++++++++++++ paddle/gserver/layers/WarpCTCLayer.h | 65 +++++ paddle/gserver/tests/CMakeLists.txt | 7 + paddle/gserver/tests/test_WarpCTCLayer.cpp | 247 ++++++++++++++++++ proto/ModelConfig.proto.m4 | 2 + python/paddle/trainer/config_parser.py | 21 ++ .../paddle/trainer_config_helpers/layers.py | 79 ++++++ .../protostr/test_cost_layers.protostr | 17 ++ .../tests/configs/test_cost_layers.py | 2 + 22 files changed, 1140 insertions(+), 23 deletions(-) create mode 100644 .gitmodules create mode 100644 paddle/cuda/include/hl_warpctc_wrap.h create mode 100644 paddle/cuda/src/hl_warpctc_wrap.cc create mode 100644 paddle/gserver/layers/WarpCTCLayer.cpp create mode 100644 paddle/gserver/layers/WarpCTCLayer.h create mode 100644 paddle/gserver/tests/test_WarpCTCLayer.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e69de29bb2 diff --git a/CMakeLists.txt b/CMakeLists.txt index af193c27ae..e5e54cc8cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,11 @@ endif() if(NOT WITH_GPU) add_definitions(-DPADDLE_ONLY_CPU) add_definitions(-DHPPL_STUB_FUNC) + + if(WITH_DSO) + add_definitions(-DPADDLE_USE_DSO) + endif(WITH_DSO) + list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS cu) else() if(${CUDA_VERSION_MAJOR} GREATER 6) diff --git a/cmake/util.cmake b/cmake/util.cmake index a8282f0718..11641f6064 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -148,6 +148,11 @@ function(link_paddle_exe TARGET_NAME) target_link_libraries(${TARGET_NAME} rt) endif() endif() + + if(NOT WITH_DSO) + target_link_libraries(${TARGET_NAME} + ${WARPCTC_LIBRARY}) + endif() endfunction() # link_paddle_test diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index 11dbfb54b2..7e45d3d578 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -15,20 +15,29 @@ else() endif() set(CUDA_CXX_WITH_GPU_SOURCES + src/hl_cudart_wrap.cc src/hl_cuda_cublas.cc src/hl_cuda_cudnn.cc - src/hl_cuda_device.cc) + src/hl_cuda_device.cc + ) -set_source_files_properties(${CUDA_CXX_WITH_GPU_SOURCES} - PROPERTIES COMPILE_FLAGS "-D__NVCC__") +if(WITH_GPU) + set(CUDA_CXX_SOURCES + src/hl_dso_loader.cc + src/hl_warpctc_wrap.cc + ${CUDA_CXX_WITH_GPU_SOURCES}) + + set_source_files_properties(${CUDA_CXX_SOURCES} + PROPERTIES COMPILE_FLAGS "-D__NVCC__") +else() + set(CUDA_CXX_SOURCES + src/hl_dso_loader.cc + src/hl_warpctc_wrap.cc) +endif() set_source_files_properties(${AVX_SOURCES} PROPERTIES COMPILE_FLAGS "-mavx") -set(CUDA_DSO_SOURCES - src/hl_dso_loader.cc - src/hl_cudart_wrap.cc) - set(CUDA_CU_SOURCES src/hl_perturbation_util.cu src/hl_cuda_aggregate.cu @@ -44,6 +53,7 @@ set(CUDA_CU_SOURCES set(CUDA_HEADERS include/hl_time.h include/hl_dso_loader.h + include/hl_warpctc_wrap.h include/hl_sequence.h include/hl_cuda_cublas.h include/hl_batch_transpose.h @@ -75,14 +85,14 @@ if(WITH_GPU) cuda_add_library(paddle_cuda ${CUDA_SOURCES} ${CUDA_CU_SOURCES} - ${CUDA_DSO_SOURCES} - ${CUDA_CXX_WITH_GPU_SOURCES}) + ${CUDA_CXX_SOURCES}) else() - add_library(paddle_cuda ${CUDA_SOURCES}) + add_library(paddle_cuda + ${CUDA_SOURCES} + ${CUDA_CXX_SOURCES}) endif() add_style_check_target(paddle_cuda ${CUDA_SOURCES} ${CUDA_HEADERS} - ${CUDA_DSO_SOURCES} - ${CUDA_CXX_WITH_GPU_SOURCES}) + ${CUDA_CXX_SOURCES}) diff --git a/paddle/cuda/include/hl_dso_loader.h b/paddle/cuda/include/hl_dso_loader.h index 1eb9f9ca88..c52066e3d7 100644 --- a/paddle/cuda/include/hl_dso_loader.h +++ b/paddle/cuda/include/hl_dso_loader.h @@ -18,10 +18,6 @@ limitations under the License. */ #include #include #include -#include -#include -#include -#include #include "hl_base.h" /** @@ -56,4 +52,12 @@ void GetCudartDsoHandle(void** dso_handle); */ void GetCurandDsoHandle(void** dso_handle); +/** + * @brief load the DSO of warp-ctc + * + * @param **dso_handle dso handler + * + */ +void GetWarpctcDsoHandle(void** dso_handle); + #endif // HL_DSO_LOADER_H_ diff --git a/paddle/cuda/include/hl_gpu.h b/paddle/cuda/include/hl_gpu.h index 3be0df3b93..6dd6d13212 100644 --- a/paddle/cuda/include/hl_gpu.h +++ b/paddle/cuda/include/hl_gpu.h @@ -25,6 +25,7 @@ limitations under the License. */ #include "hl_sparse.h" #include "hl_lstm.h" #include "hl_sequence.h" +#include "hl_warpctc_wrap.h" #ifdef HPPL_STUB_FUNC #include "stub/hl_cuda_stub.h" diff --git a/paddle/cuda/include/hl_sequence.h b/paddle/cuda/include/hl_sequence.h index bb5124df44..b98d7bdeaf 100644 --- a/paddle/cuda/include/hl_sequence.h +++ b/paddle/cuda/include/hl_sequence.h @@ -172,6 +172,39 @@ extern void hl_sequence2batch_add(real* batch, int batchCount, bool seq2batch); +/** + * @brief Memory copy from sequence to batch, + * while padding all sequences to the same length. + * + * if seq2batch == true + * + * copy from sequence to batch: + * batch[i] = sequence[sequenceStartPositions[i]] + * + * if seq2batch == false + * + * copy from batch to sequence: + * sequence[sequenceStartPositions[i]] = batch[i] + * + * @param[in,out] batch batch matrix. + * @param[in,out] sequence sequence matrix. + * @param[in] sequenceStartPositions index vector. + * @param[in] sequenceWidth width of sequence. + * @param[in] maxSequenceLength maximum length of sequences. + * @param[in] numSequences number of sequences. + * @param[in] normByTimes whether dividing sequence's length. + * @param[in] seq2batch copy direction. + * + */ +extern void hl_sequence2batch_copy_padding(real* batch, + real* sequence, + const int* sequenceStartPositions, + const size_t sequenceWidth, + const size_t maxSequenceLength, + const size_t numSequences, + bool normByTimes, + bool seq2batch); + /** * @brief dst = Op(src), src is sequence. * diff --git a/paddle/cuda/include/hl_warpctc_wrap.h b/paddle/cuda/include/hl_warpctc_wrap.h new file mode 100644 index 0000000000..9d2379a024 --- /dev/null +++ b/paddle/cuda/include/hl_warpctc_wrap.h @@ -0,0 +1,94 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#ifndef HL_WARPCTC_WRAP_H_ +#define HL_WARPCTC_WRAP_H_ + +#include "hl_base.h" +/// #include "hl_cuda.h" +#include "warp-ctc/include/ctc.h" + +typedef ctcStatus_t hl_warpctc_status_t; +typedef ctcOptions hl_warpctc_options_t; + +/** + * @brief Init ctc options. + * + * @param[in] blank blank label used in ctc loss function. + * @param[in] useGpu whether use gpu. + * @param[out] options handle to store cpu or gpu informations. + * + */ +extern void hl_warpctc_init(const size_t blank, + bool useGpu, + hl_warpctc_options_t* options); + +/** + * @brief Compute the connectionist temporal classification loss, + * and optionally compute the gradient with respect to the inputs. + * + * if batchGrad == nullptr + * + * only compute the ctc loss. + * + * if batchGrad != nullptr + * + * compute both ctc loss and gradient. + * + * @param[in] batchInput batch matrix of input probabilities, + * in maxSequenceLength x numSequence x numClasses + * (row-major) format. + * @param[out] batchGrad batch matrix of gradient. + * @param[in] cpuLabels labels always in CPU memory. + * @param[in] cpuLabelLengths length of all labels in CPU memory. + * @param[in] cpuInputLengths length of all sequences in CPU memory. + * @param[in] numClasses number of possible output symbols. + * @param[in] numSequences number of sequence. + * @param[out] cpuCosts cost of each sequence in CPU memory. + * @param[out] workspace workspace to store some temporary results. + * @param[in] options handle to store cpu or gpu informations. + * + */ +extern void hl_warpctc_compute_loss(const real* batchInput, + real* batchGrad, + const int* cpuLabels, + const int* cpuLabelLengths, + const int* cpuInputLengths, + const size_t numClasses, + const size_t numSequences, + real* cpuCosts, + void* workspace, + hl_warpctc_options_t* options); + +/** + * @brief Compute the required workspace size. + * There is no memory allocated operations within warp-ctc. + * + * @param[in] cpuLabelLengths length of all labels in CPU memory. + * @param[in] cpuInputLengths length of all sequences in CPU memory. + * @param[in] numClasses number of possible output symbols. + * @param[in] numSequences number of sequence. + * @param[in] options handle to store cpu or gpu informations. + * @param[out] bytes pointer to a scalar where the memory + * requirement in bytes will be placed. + * + */ +extern void hl_warpctc_get_workspace_size(const int* cpuLabelLengths, + const int* cpuInputLengths, + const size_t numClasses, + const size_t numSequences, + hl_warpctc_options_t* options, + size_t* bytes); + +#endif // HL_WARPCTC_WRAP_H_ diff --git a/paddle/cuda/include/stub/hl_sequence_stub.h b/paddle/cuda/include/stub/hl_sequence_stub.h index 381f0a6f26..3343463a8d 100644 --- a/paddle/cuda/include/stub/hl_sequence_stub.h +++ b/paddle/cuda/include/stub/hl_sequence_stub.h @@ -70,6 +70,15 @@ inline void hl_sequence2batch_add(real* batch, int batchCount, bool seq2batch) {} +inline void hl_sequence2batch_copy_padding(real* batch, + real* sequence, + const int* sequenceStartPositions, + const size_t sequenceWidth, + const size_t maxSequenceLength, + const size_t numSequences, + bool normByTimes, + bool seq2batch) {} + inline void hl_sequence_avg_forward(real* dst, real* src, const int* starts, diff --git a/paddle/cuda/src/hl_cuda_sequence.cu b/paddle/cuda/src/hl_cuda_sequence.cu index 63824eaa4c..0f1d720439 100644 --- a/paddle/cuda/src/hl_cuda_sequence.cu +++ b/paddle/cuda/src/hl_cuda_sequence.cu @@ -447,6 +447,124 @@ void hl_sequence2batch_add(real *batch, CHECK_SYNC("hl_sequence2batch_add failed"); } +template +__global__ +void KeSequence2BatchPadding(real* batch, + real* sequence, + const int* sequenceStartPositions, + const size_t sequenceWidth, + const size_t maxSequenceLength, + const size_t numSequences) { + int batchIdx = blockIdx.y; + int sequenceStart = sequenceStartPositions[batchIdx]; + int sequenceLength = sequenceStartPositions[batchIdx + 1] - sequenceStart; + + int sequenceIdx = blockIdx.x * blockDim.y + threadIdx.y; + int batchBaseIdx = (sequenceIdx * numSequences + batchIdx) * sequenceWidth; + int sequenceBaseIdx = (sequenceStart + sequenceIdx) * sequenceWidth; + + if (sequenceIdx < sequenceLength) { + if (seq2batch) { + /* sequence -> batch */ + if (normByTimes) { + real scale = 1.0f / (real)sequenceLength; + for (int i = threadIdx.x; i < sequenceWidth; i += blockDim.x) { + batch[batchBaseIdx + i] = scale * sequence[sequenceBaseIdx + i]; + } + } else { + for (int i = threadIdx.x; i < sequenceWidth; i += blockDim.x) { + batch[batchBaseIdx + i] = sequence[sequenceBaseIdx + i]; + } + } + } else { + /* batch -> sequence */ + if (normByTimes) { + real scale = 1.0f / (real)sequenceLength; + for (int i = threadIdx.x; i < sequenceWidth; i += blockDim.x) { + sequence[sequenceBaseIdx + i] = scale * batch[batchBaseIdx + i]; + } + } else { + for (int i = threadIdx.x; i < sequenceWidth; i += blockDim.x) { + sequence[sequenceBaseIdx + i] = batch[batchBaseIdx + i]; + } + } + } + } else if (sequenceIdx < maxSequenceLength) { + if (seq2batch) { + /* sequence -> batch */ + for (int i = threadIdx.x; i < sequenceWidth; i += blockDim.x) { + batch[batchBaseIdx + i] = 0; + } + } + } +} + +void hl_sequence2batch_copy_padding(real* batch, + real* sequence, + const int* sequenceStartPositions, + const size_t sequenceWidth, + const size_t maxSequenceLength, + const size_t numSequences, + bool normByTimes, + bool seq2batch) { + CHECK_NOTNULL(batch); + CHECK_NOTNULL(sequence); + CHECK_NOTNULL(sequenceStartPositions); + + if (!normByTimes && numSequences == 1) { + size_t elementCount = maxSequenceLength * sequenceWidth; + if (seq2batch) { + /* sequence -> batch */ + hl_memcpy_device2device(batch, sequence, sizeof(real) * elementCount); + } else { + /* batch -> sequence */ + hl_memcpy_device2device(sequence, batch, sizeof(real) * elementCount); + } + return; + } + + const int CUDA_BLOCK_SIZE = 512; + + /* At least use 32 threads to copy sequenceWidth elements, + and at least 8 elements for each thread. */ + int blockDimX = ((((sequenceWidth + 7) >> 3) + 31) >> 5) << 5; + blockDimX = (blockDimX < CUDA_BLOCK_SIZE) ? blockDimX : CUDA_BLOCK_SIZE; + + int blockDimY = CUDA_BLOCK_SIZE / blockDimX; + dim3 threads(blockDimX, blockDimY); + + int gridDimX = (maxSequenceLength * blockDimX + CUDA_BLOCK_SIZE - 1) / + CUDA_BLOCK_SIZE; + int gridDimY = numSequences; + dim3 grid(gridDimX, gridDimY); + + if (seq2batch) { + /* sequence -> batch */ + if (normByTimes) { + KeSequence2BatchPadding<1, 1><<< grid, threads, 0, STREAM_DEFAULT >>>( + batch, sequence, sequenceStartPositions, + sequenceWidth, maxSequenceLength, numSequences); + } else { + KeSequence2BatchPadding<0, 1><<< grid, threads, 0, STREAM_DEFAULT >>>( + batch, sequence, sequenceStartPositions, + sequenceWidth, maxSequenceLength, numSequences); + } + } else { + /* batch -> sequence */ + if (normByTimes) { + KeSequence2BatchPadding<1, 0><<< grid, threads, 0, STREAM_DEFAULT >>>( + batch, sequence, sequenceStartPositions, + sequenceWidth, maxSequenceLength, numSequences); + } else { + KeSequence2BatchPadding<0, 0><<< grid, threads, 0, STREAM_DEFAULT >>>( + batch, sequence, sequenceStartPositions, + sequenceWidth, maxSequenceLength, numSequences); + } + } + + CHECK_SYNC("hl_sequence2batch_copy_padding failed"); +} + __device__ inline float my_rsqrt(float x) { return rsqrtf(x); } diff --git a/paddle/cuda/src/hl_cudart_wrap.cc b/paddle/cuda/src/hl_cudart_wrap.cc index ff6b830b7a..a95f5557af 100644 --- a/paddle/cuda/src/hl_cudart_wrap.cc +++ b/paddle/cuda/src/hl_cudart_wrap.cc @@ -15,6 +15,7 @@ limitations under the License. */ #ifdef PADDLE_USE_DSO #include +#include #include "hl_dso_loader.h" /** diff --git a/paddle/cuda/src/hl_dso_loader.cc b/paddle/cuda/src/hl_dso_loader.cc index 1a3ce08619..a6ea2a3b9f 100644 --- a/paddle/cuda/src/hl_dso_loader.cc +++ b/paddle/cuda/src/hl_dso_loader.cc @@ -30,6 +30,8 @@ P_DEFINE_string(cuda_dir, "build-in function in cudart already ran before main entry). " "If default, dlopen will search cuda from LD_LIBRARY_PATH"); +P_DEFINE_string(warpctc_dir, "", "Specify path for loading libwarpctc.so."); + static inline std::string join(const std::string& part1, const std::string& part2) { // directory separator @@ -92,27 +94,28 @@ static inline void GetDsoHandleFromSearchPath(const std::string& search_root, *dso_handle = dlopen(dlPath.c_str(), dynload_flags); // if not found, search from default path if (nullptr == *dso_handle) { - LOG(WARNING) << "Failed to find cuda library: " << dlPath; + LOG(WARNING) << "Failed to find dynamic library: " << dlPath << " (" + << dlerror() << ")"; dlPath = dso_name; GetDsoHandleFromDefaultPath(dlPath, dso_handle, dynload_flags); } } - CHECK(nullptr != *dso_handle) << "Failed to find cuda library: " << dlPath - << std::endl + CHECK(nullptr != *dso_handle) << "Failed to find dynamic library: " << dlPath + << " (" << dlerror() << ") \n" << "Please specify its path correctly using " - "one of the following ways: \n" // NOLINT + "one of the following ways: \n" << "Method 1. set cuda and cudnn lib path at " "runtime. " << "http://www.paddlepaddle.org/doc/ui/" "cmd_argument/" - "argument_outline.html \n" // NOLINT + "argument_outline.html \n" << "For instance, issue command: paddle train " "--use_gpu=1 " << "--cuda_dir=/usr/local/cuda/lib64 " "--cudnn_dir=/usr/local/cudnn/lib " - "...\n" // NOLINT + "...\n" << "Method 2. set environment variable " "LD_LIBRARY_PATH on Linux or " @@ -124,7 +127,7 @@ static inline void GetDsoHandleFromSearchPath(const std::string& search_root, "DYLD_LIBRARY_PATH is impossible " << "unless System Integrity Protection (SIP) " "is disabled. However, " - "method 1 " // NOLINT + "method 1 " << "always work well."; } @@ -159,3 +162,11 @@ void GetCurandDsoHandle(void** dso_handle) { GetDsoHandleFromSearchPath(FLAGS_cuda_dir, "libcurand.so", dso_handle); #endif } + +void GetWarpctcDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "libwarpctc.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "libwarpctc.so", dso_handle); +#endif +} diff --git a/paddle/cuda/src/hl_warpctc_wrap.cc b/paddle/cuda/src/hl_warpctc_wrap.cc new file mode 100644 index 0000000000..99db0f242d --- /dev/null +++ b/paddle/cuda/src/hl_warpctc_wrap.cc @@ -0,0 +1,157 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 "hl_warpctc_wrap.h" +#include "hl_dso_loader.h" +#include "paddle/utils/Logging.h" + +namespace dynload { + +std::once_flag warpctc_dso_flag; +void* warpctc_dso_handle = nullptr; + +/** + * The following macro definition can generate structs + * (for each function) to dynamic load warpctc routine + * via operator overloading. When PADDLE_USE_DSO is + * false, you need to add the path of libwarp-ctc.so to + * the linked-libs of paddle or to LD_PRELOAD. + */ +#ifdef PADDLE_USE_DSO +#define DYNAMIC_LOAD_WARPCTC_WRAP(__name, __type) \ + struct DynLoad__##__name { \ + template \ + __type operator()(Args... args) { \ + typedef __type (*warpctcFunc)(Args...); \ + std::call_once( \ + warpctc_dso_flag, GetWarpctcDsoHandle, &warpctc_dso_handle); \ + void* p_##_name = dlsym(warpctc_dso_handle, #__name); \ + return reinterpret_cast(p_##_name)(args...); \ + } \ + } __name; // struct DynLoad__##__name +#else +#define DYNAMIC_LOAD_WARPCTC_WRAP(__name, __type) \ + struct DynLoad__##__name { \ + template \ + __type operator()(Args... args) { \ + return __name(args...); \ + } \ + } __name; // struct DynLoad__##__name +#endif + +// include all needed warp-ctc functions +DYNAMIC_LOAD_WARPCTC_WRAP(get_warpctc_version, int) +DYNAMIC_LOAD_WARPCTC_WRAP(ctcGetStatusString, const char*) +DYNAMIC_LOAD_WARPCTC_WRAP(compute_ctc_loss, hl_warpctc_status_t) +DYNAMIC_LOAD_WARPCTC_WRAP(get_workspace_size, hl_warpctc_status_t) + +#undef DYNAMIC_LOAD_WARPCTC_WRAP + +} /* namespace dynload */ + +#define WARPCTC_GET_VERSION dynload::get_warpctc_version +#define WARPCTC_GET_STATUS_STRING dynload::ctcGetStatusString + +#ifndef PADDLE_TYPE_DOUBLE +#define WARPCTC_COMPUTE_LOSS dynload::compute_ctc_loss +#define WARPCTC_GET_WORKSPACE_SIZE dynload::get_workspace_size +#else +#define WARPCTC_LOG_FATAL \ + LOG(FATAL) << "warp-ctc [version " << g_warpctcVersion \ + << "] Error: not support double precision." +#define WARPCTC_COMPUTE_LOSS(...) WARPCTC_LOG_FATAL(__VA_ARGS__) +#define WARPCTC_GET_WORKSPACE_SIZE(...) WARPCTC_LOG_FATAL(__VA_ARGS__) +#endif + +/** + * Check build-in warp-ctc function using glog and it also + * support << operator for more details error info. + */ +static int g_warpctcVersion = -1; +#define CHECK_WARPCTC(warpctcStat) \ + CHECK_EQ(CTC_STATUS_SUCCESS, warpctcStat) \ + << "warp-ctc [version " << g_warpctcVersion \ + << "] Error: " << WARPCTC_GET_STATUS_STRING(warpctcStat) << " " + +void hl_warpctc_init(const size_t blank, + bool useGpu, + hl_warpctc_options_t* options) { + CHECK_NOTNULL(options); + + g_warpctcVersion = WARPCTC_GET_VERSION(); + + if (useGpu) { +#ifdef __NVCC__ + options->loc = CTC_GPU; + options->stream = STREAM_DEFAULT; +#else + LOG(FATAL) << "[warpctc init] GPU is not enabled."; +#endif + } else { + options->loc = CTC_CPU; + options->num_threads = 1; + } + + options->blank_label = blank; +} + +void hl_warpctc_compute_loss(const real* batchInput, + real* batchGrad, + const int* cpuLabels, + const int* cpuLabelLengths, + const int* cpuInputLengths, + const size_t numClasses, + const size_t numSequences, + real* cpuCosts, + void* workspace, + hl_warpctc_options_t* options) { + CHECK_NOTNULL(batchInput); + CHECK_NOTNULL(cpuLabels); + CHECK_NOTNULL(cpuLabelLengths); + CHECK_NOTNULL(cpuInputLengths); + CHECK_NOTNULL(cpuCosts); + CHECK_NOTNULL(workspace); + CHECK_NOTNULL(options); + + CHECK_WARPCTC(WARPCTC_COMPUTE_LOSS(batchInput, + batchGrad, + cpuLabels, + cpuLabelLengths, + cpuInputLengths, + numClasses, + numSequences, + cpuCosts, + workspace, + *options)); +} + +void hl_warpctc_get_workspace_size(const int* cpuLabelLengths, + const int* cpuInputLengths, + const size_t numClasses, + const size_t numSequences, + hl_warpctc_options_t* options, + size_t* bytes) { + CHECK_NOTNULL(cpuLabelLengths); + CHECK_NOTNULL(cpuInputLengths); + CHECK_NOTNULL(options); + CHECK_NOTNULL(bytes); + + CHECK_WARPCTC(WARPCTC_GET_WORKSPACE_SIZE(cpuLabelLengths, + cpuInputLengths, + numClasses, + numSequences, + *options, + bytes)); +} diff --git a/paddle/gserver/layers/WarpCTCLayer.cpp b/paddle/gserver/layers/WarpCTCLayer.cpp new file mode 100644 index 0000000000..b99e9b9c7a --- /dev/null +++ b/paddle/gserver/layers/WarpCTCLayer.cpp @@ -0,0 +1,229 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 "WarpCTCLayer.h" + +namespace paddle { + +REGISTER_LAYER(warp_ctc, WarpCTCLayer); + +bool WarpCTCLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parament class */ + Layer::init(layerMap, parameterMap); + + CHECK_EQ(inputLayers_.size(), 2UL); + + /* The inputLayers_[0] must be sequence output without softmax */ + numClasses_ = config_.size(); + CHECK_GE(numClasses_, 2UL); + CHECK_EQ(numClasses_, inputLayers_[0]->getSize()); + + blank_ = config_.blank(); + CHECK_GE(blank_, 0UL); + CHECK_LT(blank_, numClasses_); + + normByTimes_ = config_.norm_by_times(); + + // We don't need sequenceStartPositions because each sample of output_ is + // for the cost of one sequence. + setNeedSequenceInfo(false); + + return true; +} + +void WarpCTCLayer::forward(PassType passType) { + Layer::forward(passType); + + const Argument& output = getInput(0); + const Argument& labels = getInput(1); + + CHECK(output.sequenceStartPositions); + CHECK(labels.sequenceStartPositions); + CHECK(labels.ids); + + size_t numSequences = labels.sequenceStartPositions->getSize() - 1; + CHECK_EQ(numSequences, output.sequenceStartPositions->getSize() - 1); + + resizeOutput(numSequences, 1); + + const int* cpuLabelStartPositions = + labels.sequenceStartPositions->getData(false); + const int* cpuOutputStartPositions = + output.sequenceStartPositions->getData(false); + + std::vector cpuLabelLengths(numSequences); + std::vector cpuOutputLengths(numSequences); + for (size_t i = 0; i < numSequences; i++) { + cpuLabelLengths[i] = + cpuLabelStartPositions[i + 1] - cpuLabelStartPositions[i]; + cpuOutputLengths[i] = + cpuOutputStartPositions[i + 1] - cpuOutputStartPositions[i]; + } + + /* Get the maximum sequence length */ + maxSequenceLength_ = 0; + maxSequenceLength_ = *std::max_element( + cpuOutputLengths.data(), cpuOutputLengths.data() + numSequences); + + Matrix::resizeOrCreate(batchValue_, + /* height */ numSequences * maxSequenceLength_, + /* width */ numClasses_, + /* trans */ false, + /* useGpu */ useGpu_); + + Matrix::resizeOrCreate(batchGrad_, + /* height */ numSequences * maxSequenceLength_, + /* width */ numClasses_, + /* trans */ false, + /* useGpu */ useGpu_); + batchGrad_->zeroMem(); + + seq2batchPadding(output.value, batchValue_, output.sequenceStartPositions); + + /* labels always in CPU memory */ + IVector::resizeOrCreate(cpuLabels_, + /* size */ (labels.ids)->getSize(), + /* useGpu */ false); + cpuLabels_->copyFrom(*(labels.ids)); + + /* labels always in CPU memory */ + Matrix::resizeOrCreate(cpuCosts_, + /* width */ numSequences, + /* height */ 1, + /* trans */ false, + /* useGpu */ false); + + /* Init warp-ctc options */ + hl_warpctc_options_t options; + hl_warpctc_init(blank_, useGpu_, &options); + + /* Get the needed workspace size */ + size_t workspaceBytes = 0; + hl_warpctc_get_workspace_size(cpuLabelLengths.data(), + cpuOutputLengths.data(), + numClasses_, + numSequences, + &options, + &workspaceBytes); + CHECK_GT(workspaceBytes, 0UL); + + size_t workspaceLength = workspaceBytes / sizeof(real) + 1; + Vector::resizeOrCreate(workspace_, + /* size */ workspaceLength, + /* useGpu */ useGpu_); + + hl_warpctc_compute_loss(batchValue_->getData(), + batchGrad_->getData(), + cpuLabels_->getData(), + cpuLabelLengths.data(), + cpuOutputLengths.data(), + numClasses_, + numSequences, + cpuCosts_->getData(), + workspace_->getData(), + &options); + + /* Copy the costs */ + output_.value->copyFrom(*cpuCosts_); +} + +void WarpCTCLayer::backward(const UpdateCallback& callback) { + (void)callback; + + const Argument& output = getInput(0); + CHECK(batchGrad_); + + batch2seqPadding( + output.grad, batchGrad_, output.sequenceStartPositions, normByTimes_); +} + +void WarpCTCLayer::seq2batchPadding(const MatrixPtr& seqValue, + MatrixPtr& batchValue, + const ICpuGpuVectorPtr& seqStartPositions) { + size_t numSequences = seqStartPositions->getSize() - 1; + const int* seqStartPositionsData = seqStartPositions->getData(useGpu_); + + real* seqData = seqValue->getData(); + real* batchData = batchValue->getData(); + if (useGpu_) { + hl_sequence2batch_copy_padding(batchData, + seqData, + seqStartPositionsData, + numClasses_, + maxSequenceLength_, + numSequences, + false, + true); + } else { + for (size_t i = 0; i < maxSequenceLength_; i++) { + for (size_t j = 0; j < numSequences; j++) { + size_t sequenceStart = seqStartPositionsData[j]; + size_t sequenceLength = + seqStartPositionsData[j + 1] - seqStartPositionsData[j]; + if (i < sequenceLength) { + memcpy(batchData + (i * numSequences + j) * numClasses_, + seqData + (sequenceStart + i) * numClasses_, + numClasses_ * sizeof(real)); + } else { + memset(batchData + (i * numSequences + j) * numClasses_, + 0, + numClasses_ * sizeof(real)); + } + } + } + } +} + +void WarpCTCLayer::batch2seqPadding(const MatrixPtr& seqValue, + MatrixPtr& batchValue, + const ICpuGpuVectorPtr& seqStartPositions, + bool normByTimes) { + size_t numSequences = seqStartPositions->getSize() - 1; + const int* seqStartPositionsData = seqStartPositions->getData(useGpu_); + + real* seqData = seqValue->getData(); + real* batchData = batchValue->getData(); + if (useGpu_) { + hl_sequence2batch_copy_padding(batchData, + seqData, + seqStartPositionsData, + numClasses_, + maxSequenceLength_, + numSequences, + normByTimes, + false); + } else { + for (size_t i = 0; i < numSequences; i++) { + int sequenceStart = seqStartPositionsData[i]; + int sequenceLength = + seqStartPositionsData[i + 1] - seqStartPositionsData[i]; + for (int j = 0; j < sequenceLength; j++) { + if (normByTimes) { + for (size_t k = 0; k < numClasses_; k++) { + seqData[(sequenceStart + j) * numClasses_ + k] = + batchData[(j * numSequences + i) * numClasses_ + k] / + sequenceLength; + } + } else { + memcpy(seqData + (sequenceStart + j) * numClasses_, + batchData + (j * numSequences + i) * numClasses_, + numClasses_ * sizeof(real)); + } + } + } + } +} + +} // namespace paddle diff --git a/paddle/gserver/layers/WarpCTCLayer.h b/paddle/gserver/layers/WarpCTCLayer.h new file mode 100644 index 0000000000..1b0f5ba267 --- /dev/null +++ b/paddle/gserver/layers/WarpCTCLayer.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 A layer integrating the open-source warp-ctc library + * to compute connectionist + * temporal classification cost. + * + * The config file api is warp_ctc_layer. + */ +class WarpCTCLayer : public Layer { +public: + explicit WarpCTCLayer(const LayerConfig& config) : Layer(config) {} + ~WarpCTCLayer() {} + + virtual bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); + virtual void forward(PassType passType); + virtual void backward(const UpdateCallback& callback); + +protected: + /** + * sequence matrix and batch matrix copy: + * sequence (s0, s0, s0, s0; s1, s1; s2, s2, s2; s3) + * batch (s0, s1, s2, s3; s0, s1, s2, 0; s0, 0, s2, 0; s0, 0, 0, 0) + */ + void seq2batchPadding(const MatrixPtr& seqValue, + MatrixPtr& batchValue, + const ICpuGpuVectorPtr& seqStartPositions); + void batch2seqPadding(const MatrixPtr& seqValue, + MatrixPtr& batchValue, + const ICpuGpuVectorPtr& seqStartPositions, + bool normByTimes); + +protected: + size_t numClasses_; + size_t blank_; + size_t maxSequenceLength_; + bool normByTimes_; + + MatrixPtr batchValue_; + MatrixPtr batchGrad_; + VectorPtr workspace_; + + IVectorPtr cpuLabels_; + MatrixPtr cpuCosts_; +}; + +} // namespace paddle diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index 0651d0b473..5427dc062d 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -62,6 +62,13 @@ add_unittest(test_RecurrentLayer test_RecurrentLayer.cpp TestUtil.cpp) +############### test_WarpCTCLayer ####################### +if(NOT WITH_DOUBLE) + add_unittest(test_WarpCTCLayer + test_WarpCTCLayer.cpp + TestUtil.cpp) +endif() + ############### test_RecurrentGradientMachine ############### # TODO(yuyang18): There is some bug in test_RecurrentGradientMachine # I will fix it. diff --git a/paddle/gserver/tests/test_WarpCTCLayer.cpp b/paddle/gserver/tests/test_WarpCTCLayer.cpp new file mode 100644 index 0000000000..5289c9892c --- /dev/null +++ b/paddle/gserver/tests/test_WarpCTCLayer.cpp @@ -0,0 +1,247 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include +#include +#include "paddle/gserver/layers/Layer.h" +#include "paddle/gserver/layers/DataLayer.h" +#include "paddle/gserver/layers/CTCLayer.h" +#include "paddle/gserver/layers/WarpCTCLayer.h" +#include "ModelConfig.pb.h" + +#include "TestUtil.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +P_DECLARE_bool(use_gpu); + +const real* getData(const Matrix& matrix) { + if (matrix.useGpu()) { + MatrixPtr cpuMatrix = Matrix::create( + matrix.getWidth(), matrix.getHeight(), matrix.isTransposed(), false); + cpuMatrix->copyFrom(matrix); + return cpuMatrix->getData(); + } else { + return matrix.getData(); + } +} + +void checkError(const Matrix& matrix1, const Matrix& matrix2) { + CHECK_EQ(matrix1.getHeight(), matrix2.getHeight()); + CHECK_EQ(matrix1.getWidth(), matrix2.getWidth()); + CHECK_EQ(matrix1.isTransposed(), matrix2.isTransposed()); +#ifndef PADDLE_TYPE_DOUBLE + real err = 1e-3; +#else + real err = 1e-10; +#endif + + int height = matrix1.getHeight(); + int width = matrix1.getWidth(); + + const real* data1 = getData(matrix1); + const real* data2 = getData(matrix2); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (fabs(data1[i * width + j] - data2[i * width + j]) > err) { + count++; + } + } + } + EXPECT_EQ(count, 0) << "There are " << count << " different element."; +} + +void initArgument(size_t batchSize, + int layerSize, + bool useGpu, + Argument& data) { + data.value = Matrix::create(batchSize, layerSize, false, useGpu); + data.grad = Matrix::create(batchSize, layerSize, false, useGpu); + data.value->randomizeUniform(); + data.value->add(-0.5); + /// data.value->sigmoid(*data.value); + data.grad->zeroMem(); + + generateSequenceStartPositions(batchSize, data.sequenceStartPositions); +} + +LayerPtr createDataLayer( + string name, size_t batchSize, int layerSize, bool useGpu, Argument& data) { + LayerConfig layerConfig; + layerConfig.set_name(name); + layerConfig.set_type("data"); + layerConfig.set_size(layerSize); + LayerPtr layer = LayerPtr(new DataLayer(layerConfig)); + + DataLayerPtr dataLayer = std::dynamic_pointer_cast(layer); + dataLayer->setData(data); + dataLayer->forward(PASS_GC); + + /// std::cout << "dataLayer: " << std::endl; + /// (dataLayer->getOutput().value)->print(std::cout); + + return layer; +} + +LayerPtr createLabelLayer(string name, + size_t batchSize, + size_t numClasses, + bool useGpu) { + LayerConfig layerConfig; + layerConfig.set_name(name); + layerConfig.set_type("data"); + layerConfig.set_size(1); + LayerPtr layer = LayerPtr(new DataLayer(layerConfig)); + + Argument data; + data.ids = IVector::create(batchSize, useGpu); + data.ids->rand(numClasses - 1); + + generateSequenceStartPositions(batchSize, data.sequenceStartPositions); + + DataLayerPtr labelLayer = std::dynamic_pointer_cast(layer); + labelLayer->setData(data); + labelLayer->forward(PASS_GC); + + return layer; +} + +LayerPtr createCTCLayer(string name, + size_t numClasses, + bool useGpu, + bool normByTimes, + LayerPtr dataLayer, + LayerPtr labelLayer) { + LayerMap layerMap; + layerMap[dataLayer->getName()] = dataLayer; + layerMap[labelLayer->getName()] = labelLayer; + + ParameterMap parameterMap; + + LayerConfig layerConfig; + layerConfig.set_name(name); + layerConfig.set_type("ctc"); + layerConfig.set_size(numClasses); + layerConfig.set_norm_by_times(normByTimes); + + layerConfig.add_inputs(); + LayerInputConfig& input0 = *(layerConfig.mutable_inputs(0)); + input0.set_input_layer_name(dataLayer->getName()); + + layerConfig.add_inputs(); + LayerInputConfig& input1 = *(layerConfig.mutable_inputs(1)); + input1.set_input_layer_name(labelLayer->getName()); + + LayerPtr layer = LayerPtr(new CTCLayer(layerConfig)); + layerMap[layer->getName()] = layer; + layer->init(layerMap, parameterMap); + + ActivationFunction* softmaxActivation = ActivationFunction::create("softmax"); + + softmaxActivation->forward(dataLayer->getOutput()); + layer->forward(PASS_GC); + + layer->backward(); + softmaxActivation->backward(dataLayer->getOutput()); + + return layer; +} + +LayerPtr createWarpCTCLayer(string name, + size_t numClasses, + bool useGpu, + bool normByTimes, + LayerPtr dataLayer, + LayerPtr labelLayer) { + LayerMap layerMap; + layerMap[dataLayer->getName()] = dataLayer; + layerMap[labelLayer->getName()] = labelLayer; + + ParameterMap parameterMap; + + LayerConfig layerConfig; + layerConfig.set_name(name); + layerConfig.set_type("warp_ctc"); + layerConfig.set_size(numClasses); + layerConfig.set_blank(numClasses - 1); + layerConfig.set_norm_by_times(normByTimes); + + layerConfig.add_inputs(); + LayerInputConfig& input0 = *(layerConfig.mutable_inputs(0)); + input0.set_input_layer_name(dataLayer->getName()); + + layerConfig.add_inputs(); + LayerInputConfig& input1 = *(layerConfig.mutable_inputs(1)); + input1.set_input_layer_name(labelLayer->getName()); + + LayerPtr layer = LayerPtr(new WarpCTCLayer(layerConfig)); + layerMap[layer->getName()] = layer; + layer->init(layerMap, parameterMap); + + layer->forward(PASS_GC); + layer->backward(); + + return layer; +} + +TEST(Layer, WarpCTCLayer) { + for (auto layerSize : {10, 64, 128}) { + for (auto batchSize : {1, 10, 20, 64}) { + for (auto useGpu : {false, true}) { +#ifdef PADDLE_ONLY_CPU + if (useGpu) continue; +#endif + LOG(INFO) << " layerSize=" << layerSize << " batchSize=" << batchSize + << " useGpu=" << useGpu; + + FLAGS_use_gpu = useGpu; + + Argument data0; + initArgument(batchSize, layerSize, useGpu, data0); + + Argument data1; + data1.resizeAndCopyFrom(data0); + + LayerPtr dataLayer0 = + createDataLayer("data", batchSize, layerSize, useGpu, data0); + LayerPtr dataLayer1 = + createDataLayer("data", batchSize, layerSize, useGpu, data1); + + LayerPtr labelLayer = + createLabelLayer("label", batchSize, layerSize, useGpu); + + LayerPtr warpctcLayer = createWarpCTCLayer( + "cost", layerSize, useGpu, false, dataLayer0, labelLayer); + LayerPtr ctcLayer = createCTCLayer( + "cost", layerSize, useGpu, false, dataLayer1, labelLayer); + + /// Check loss + checkError(*(warpctcLayer->getOutput().value), + *(ctcLayer->getOutput().value)); + + /// Check gradients + checkError(*(dataLayer0->getOutput().grad), + *(dataLayer1->getOutput().grad)); + } + } + } +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + initMain(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/proto/ModelConfig.proto.m4 b/proto/ModelConfig.proto.m4 index 68a5eb9dd2..08108a4666 100644 --- a/proto/ModelConfig.proto.m4 +++ b/proto/ModelConfig.proto.m4 @@ -414,6 +414,8 @@ sinclude(`ModelConfigLayer.proto.m4') // to string and reinterpreted in the user's own layer implementation. optional string user_arg = 49; + // For WarpCTCLayer + optional uint32 blank = 50 [default = 0]; } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 9db42bf172..e987ad17d6 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2993,6 +2993,27 @@ class CTCLayer(LayerBase): config_assert(len(self.inputs) == 2, 'CTCLayer must have 2 inputs') +@config_layer('warp_ctc') +class WarpCTCLayer(LayerBase): + def __init__(self, + name, + size, + inputs, + blank=0, + norm_by_times=False, + device=None): + super(WarpCTCLayer, self).__init__( + name, 'warp_ctc', size=size, inputs=inputs, device=device) + self.config.blank = blank + self.config.norm_by_times = norm_by_times + config_assert(len(self.inputs) == 2, 'WarpCTCLayer must have 2 inputs') + input_layer = self.get_input_layer(0) + config_assert( + (input_layer.active_type == '' or + input_layer.active_type == 'linear'), + "Expecting the active_type of input layer to be linear or null") + + @config_layer('recurrent_layer_group') class RecurrentLayerGroup(LayerBase): def __init__(self, name, device=None): diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 9a45a51589..888d48722a 100644 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -91,6 +91,7 @@ __all__ = [ 'linear_comb_layer', 'convex_comb_layer', 'ctc_layer', + 'warp_ctc_layer', 'crf_layer', 'crf_decoding_layer', 'nce_layer', @@ -169,6 +170,7 @@ class LayerType(object): PRINT_LAYER = "print" CTC_LAYER = "ctc" + WARP_CTC_LAYER = "warp_ctc" CRF_LAYER = "crf" CRF_DECODING_LAYER = "crf_decoding" NCE_LAYER = 'nce' @@ -4085,6 +4087,83 @@ def ctc_layer(input, return LayerOutput(name, LayerType.CTC_LAYER, [input, label], size=size) +@wrap_name_default() +@layer_support() +def warp_ctc_layer(input, + label, + size=None, + name=None, + blank=0, + norm_by_times=False, + layer_attr=None): + """ + A layer intergrating the open-source `warp-ctc + ` library, which is used in + `Deep Speech 2: End-toEnd Speech Recognition in English and Mandarin + `, to compute Connectionist Temporal + Classification (CTC) loss. + + More details of CTC can be found by referring to `Connectionist Temporal + Classification: Labelling Unsegmented Sequence Data with Recurrent + Neural Networks `_ + + Note: + - Let num_classes represent the category number. Considering the 'blank' + label needed by CTC, you need to use (num_classes + 1) as the input size. + Thus, the size of both warp_ctc_layer and 'input' layer should be set to + num_classes + 1. + - You can set 'blank' to [0, num_classes - 1], which should be consistent + as that used in your labels. + - As a native 'softmax' activation is interated to the warp-ctc library, + 'linear' activation is expected instead in the 'input' layer. + + The simple usage: + + .. code-block:: python + + ctc = warp_ctc_layer(input=input, + label=label, + size=1001, + blank=1000, + norm_by_times=False) + + :param input: The input layer. + :type input: LayerOutput + :param label: The data layer of label with variable length. + :type label: LayerOutput + :param size: category numbers + 1. + :type size: int + :param name: The name of this layer, which can not specify. + :type name: basestring|None + :param blank: the 'blank' label used in ctc + :type blank: int + :param norm_by_times: Whether to normalization by times. False by default. + :type norm_by_times: bool + :param layer_attr: Extra Layer config. + :type layer_attr: ExtraLayerAttribute|None + :return: LayerOutput object. + :rtype: LayerOutput + """ + assert isinstance(input, LayerOutput) + assert isinstance(label, LayerOutput) + if label.size is not None: + if size is not None: + assert size == label.size + 1 + else: + size = label.size + 1 + Layer( + name=name, + type=LayerType.WARP_CTC_LAYER, + size=size, + blank=blank, + norm_by_times=norm_by_times, + inputs=[input.name, label.name], + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name, LayerType.WARP_CTC_LAYER, parents=[input, label], size=size) + + @wrap_name_default() @wrap_param_attr_default() @layer_support() diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr index f6045fe1f6..10e59e21bc 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr @@ -47,6 +47,20 @@ layers { } norm_by_times: false } +layers { + name: "__warp_ctc_layer_0__" + type: "warp_ctc" + size: 5001 + active_type: "" + inputs { + input_layer_name: "input" + } + inputs { + input_layer_name: "labels" + } + norm_by_times: false + blank: 0 +} layers { name: "crf_label" type: "data" @@ -244,6 +258,7 @@ input_layer_names: "xe-label" input_layer_names: "huber_probs" input_layer_names: "huber_label" output_layer_names: "__ctc_layer_0__" +output_layer_names: "__warp_ctc_layer_0__" output_layer_names: "__crf_layer_0__" output_layer_names: "__rank_cost_0__" output_layer_names: "__lambda_cost_0__" @@ -260,6 +275,7 @@ sub_models { layer_names: "xe-label" layer_names: "__fc_layer_0__" layer_names: "__ctc_layer_0__" + layer_names: "__warp_ctc_layer_0__" layer_names: "crf_label" layer_names: "__crf_layer_0__" layer_names: "left" @@ -289,6 +305,7 @@ sub_models { input_layer_names: "huber_probs" input_layer_names: "huber_label" output_layer_names: "__ctc_layer_0__" + output_layer_names: "__warp_ctc_layer_0__" output_layer_names: "__crf_layer_0__" output_layer_names: "__rank_cost_0__" output_layer_names: "__lambda_cost_0__" diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py index fd979a1e9f..18ff6b48c4 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py @@ -12,6 +12,8 @@ hidden = fc_layer(input=seq_in, size=4) outputs( ctc_layer( input=seq_in, label=labels), + warp_ctc_layer( + input=seq_in, label=labels, blank=0), crf_layer( input=hidden, label=data_layer( name='crf_label', size=4)), -- GitLab From 18645134bd07d7a1658c08102d51af8a5d0195cd Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 29 Nov 2016 16:16:28 +0800 Subject: [PATCH 0170/1503] Follow comments --- doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst | 6 +++--- .../tests/sequence_nest_rnn_multi_unequalength_inputs.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst index a13d4728a9..7ae9f5ef8e 100644 --- a/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst +++ b/doc_cn/algorithm/rnn/hrnn_rnn_api_compare.rst @@ -4,14 +4,14 @@ 单双层RNN API对比介绍 ##################### -这篇教程主要介绍了\ :ref:`glossary_双层RNN`\ 的API接口。本文中的以PaddlePaddle的\ :ref:`glossary_双层RNN`\ 单元测试为示例,用多对效果完全相同的、分别使用单、双层RNN作为网络配置的模型,来讲解如何使用\ :ref:`glossary_双层RNN`\ 。本文中所有的例子,都只是介绍\ :ref:`glossary_双层RNN`\ 的API接口,并不是使用\ :ref:`glossary_双层RNN`\ 解决实际的问题。如果想要了解\ :ref:`glossary_双层RNN`\ 在具体问题中的使用,请参考\ :ref:`algo_hrnn_demo`\ 。文章中示例所使用的单元测试文件是\ `test_RecurrentGradientMachine.cpp `_\ 。 +这篇教程主要介绍了\ :ref:`glossary_双层RNN`\ 的API接口。本文以PaddlePaddle的\ :ref:`glossary_双层RNN`\ 单元测试为示例,用多对效果完全相同的、分别使用单双层RNN作为网络配置的模型,来讲解如何使用\ :ref:`glossary_双层RNN`\ 。本文中所有的例子,都只是介绍\ :ref:`glossary_双层RNN`\ 的API接口,并不是使用\ :ref:`glossary_双层RNN`\ 解决实际的问题。如果想要了解\ :ref:`glossary_双层RNN`\ 在具体问题中的使用,请参考\ :ref:`algo_hrnn_demo`\ 。本文中示例所使用的单元测试文件是\ `test_RecurrentGradientMachine.cpp `_\ 。 示例1:双层RNN,子序列间无Memory ================================ 在\ :ref:`glossary_双层RNN`\ 中的经典情况是将内层的每一个\ :ref:`glossary_sequence`\ 数据,分别进行序列操作。并且内层的序列操作之间是独立没有依赖的,即不需要使用\ :ref:`glossary_Memory`\ 的。 -在本问题中,单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 的网络配置,都是将每一句分好词后的句子,使用LSTM作为encoder,压缩成一个向量。区别是\ :ref:`glossary_RNN`\ 使用两层序列模型,将多句话看成一个整体,同时使用encoder压缩,二者语意上完全一致。这组语意相同的示例配置如下 +在本示例中,单层\ :ref:`glossary_RNN`\ 和\ :ref:`glossary_双层RNN`\ 的网络配置,都是将每一句分好词后的句子,使用LSTM作为encoder,压缩成一个向量。区别是\ :ref:`glossary_RNN`\ 使用两层序列模型,将多句话看成一个整体,同时使用encoder压缩,二者语意上完全一致。这组语意相同的示例配置如下 * 单层\ :ref:`glossary_RNN`\: `sequence_layer_group.conf `_ * :ref:`glossary_双层RNN`\: `sequence_nest_layer_group.conf `_ @@ -22,7 +22,7 @@ 首先,本示例中使用的原始数据如下\: -- 本里中的原始数据一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。这个数据也被单层\ :ref:`glossary_RNN`\ 网络直接使用。 +- 本例中的原始数据一共有10个样本。每个样本由两部分组成,一个label(此处都为2)和一个已经分词后的句子。这个数据也被单层\ :ref:`glossary_RNN`\ 网络直接使用。 .. literalinclude:: ../../../paddle/gserver/tests/Sequence/tour_train_wdseg :language: text diff --git a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py index bf88d00f2d..163fce956e 100644 --- a/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py +++ b/paddle/gserver/tests/sequence_nest_rnn_multi_unequalength_inputs.py @@ -64,8 +64,8 @@ def outer_step(x1, x2): last = last_seq(name="outer_rnn_state_%d" % i, input=encoder) return encoder, last - _, sentence_last_state1 = inner_step(ipt=x1) - encoder2, _ = inner_step(ipt=x2) + encoder1, sentence_last_state1 = inner_step(ipt=x1) + encoder2, sentence_last_state2 = inner_step(ipt=x2) encoder1_expand = expand_layer( input=sentence_last_state1, expand_as=encoder2) -- GitLab From f70fc4a4396e327cd469ab1144916339d9df2601 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 29 Nov 2016 16:49:46 +0800 Subject: [PATCH 0171/1503] move some test from test_matrixCompare.cpp to test_BaseMatrix.cpp and test_Matrix.cpp --- paddle/math/BaseMatrix.cu | 5 - paddle/math/BaseMatrix.h | 2 - paddle/math/tests/TensorCheck.h | 6 + paddle/math/tests/TestUtils.h | 40 +++- paddle/math/tests/test_BaseMatrix.cpp | 107 +++------- paddle/math/tests/test_Matrix.cpp | 21 +- paddle/math/tests/test_matrixCompare.cpp | 244 ----------------------- 7 files changed, 77 insertions(+), 348 deletions(-) diff --git a/paddle/math/BaseMatrix.cu b/paddle/math/BaseMatrix.cu index 2f32b3fdd1..75d76fbc6e 100644 --- a/paddle/math/BaseMatrix.cu +++ b/paddle/math/BaseMatrix.cu @@ -1578,11 +1578,6 @@ void BaseMatrixT::minRows(BaseMatrixT& b) { applyRow(aggregate::min(), b); } -template<> -void BaseMatrixT::sumCols(BaseMatrixT& b) { - applyCol(aggregate::sum(), b); -} - template<> void BaseMatrixT::maxCols(BaseMatrixT& b) { applyCol(aggregate::max(), b); diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index d41dcee682..ce8113a47c 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -1007,8 +1007,6 @@ public: /// calculate the minimum value of each row of the matrix b. void minRows(BaseMatrixT& b); - /// calculate the sum of each column of the matrix b. - void sumCols(BaseMatrixT& b); /// calculate the maximum value of each column of the matrix b. void maxCols(BaseMatrixT& b); /// calculate the minimum value of each column of the matrix b. diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h index d4821314f3..beee9e7c0f 100644 --- a/paddle/math/tests/TensorCheck.h +++ b/paddle/math/tests/TensorCheck.h @@ -110,4 +110,10 @@ void TensorCheck(AssertEq compare, real args1, real args2) { << ", args2 = " << args2; } +template +void TensorCheck(AssertEq compare, size_t args1, size_t args2) { + EXPECT_EQ(args1, args2) << "[Test error] args1 = " << args1 + << ", args2 = " << args2; +} + } // namespace autotest diff --git a/paddle/math/tests/TestUtils.h b/paddle/math/tests/TestUtils.h index 96ba2c38c8..324ecf8017 100644 --- a/paddle/math/tests/TestUtils.h +++ b/paddle/math/tests/TestUtils.h @@ -65,15 +65,24 @@ public: // construct a argument template T construct(int height, int width); + template <> float construct(int height, int width) { return 0.0; } + +template <> +size_t construct(int height, int width) { + size_t offset = std::rand() % (height < width ? height : width); + return offset; +} + template <> CpuMatrix construct(int height, int width) { CpuMatrix a(height, width); return a; } + template <> GpuMatrix construct(int height, int width) { GpuMatrix a(height, width); @@ -83,14 +92,22 @@ GpuMatrix construct(int height, int width) { // init a argument template void init(T& v); + template <> void init(float& v) { v = 0.5; } + +template <> +void init(size_t& v) { + return; +} + template <> void init(CpuMatrix& v) { v.randomizeUniform(); } + template <> void init(GpuMatrix& v) { v.randomizeUniform(); @@ -111,10 +128,17 @@ template // copy a argument, copy src to dest template void copy(T1& dest, T2& src); + template <> void copy(float& dest, float& src) { dest = src; } + +template <> +void copy(size_t& dest, size_t& src) { + dest = src; +} + template <> void copy(GpuMatrix& dest, CpuMatrix& src) { dest.copyFrom(src); @@ -165,8 +189,8 @@ R call(C& obj, R (FC::*f)(FArgs...), Args&&... args) { return (obj.*f)(args...); } -template -void BaseMatrixApplyRow(R (C::*f)(Args...)) { +void BaseMatrixAsColVector(R (C::*f)(Args...)) { static_assert(sizeof...(I) == sizeof...(Args), "size of parameter packs are not equal"); @@ -237,11 +261,11 @@ void BaseMatrixApplyRow(R (C::*f)(Args...)) { autotest::AssertEqual compare(1e-8); #endif - autotest::BaseMatrixCompare(f, compare); + autotest::BaseMatrixCompare(f, compare); } template -void BaseMatrixApplyCol(R (C::*f)(Args...)) { +void BaseMatrixAsRowVector(R (C::*f)(Args...)) { static_assert(sizeof...(I) == sizeof...(Args), "size of parameter packs are not equal"); @@ -250,5 +274,5 @@ void BaseMatrixApplyCol(R (C::*f)(Args...)) { #else autotest::AssertEqual compare(1e-8); #endif - autotest::BaseMatrixCompare(f, compare); + autotest::BaseMatrixCompare(f, compare); } diff --git a/paddle/math/tests/test_BaseMatrix.cpp b/paddle/math/tests/test_BaseMatrix.cpp index 99ab640f48..1d334135a0 100644 --- a/paddle/math/tests/test_BaseMatrix.cpp +++ b/paddle/math/tests/test_BaseMatrix.cpp @@ -24,7 +24,6 @@ limitations under the License. */ #include "TestUtils.h" using namespace paddle; // NOLINT -using namespace std; // NOLINT /** * Test member functions which prototype is @@ -32,8 +31,8 @@ using namespace std; // NOLINT */ TEST(BaseMatrix, void) { typedef void (BaseMatrix::*FunctionProto)(); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(neg); BASEMATRIXCOMPARE(exp); @@ -46,7 +45,7 @@ TEST(BaseMatrix, void) { BASEMATRIXCOMPARE(zero); BASEMATRIXCOMPARE(one); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } /** @@ -55,8 +54,8 @@ TEST(BaseMatrix, void) { */ TEST(BaseMatrix, real) { typedef void (BaseMatrix::*FunctionProto)(real); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(pow); BASEMATRIXCOMPARE(subScalar); @@ -67,7 +66,7 @@ TEST(BaseMatrix, real) { BASEMATRIXCOMPARE(biggerThanScalar); BASEMATRIXCOMPARE(downClip); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } /** @@ -76,13 +75,13 @@ TEST(BaseMatrix, real) { */ TEST(BaseMatrix, real_real) { typedef void (BaseMatrix::*FunctionProto)(real, real); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(add); BASEMATRIXCOMPARE(clip); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } /** @@ -91,8 +90,8 @@ TEST(BaseMatrix, real_real) { */ TEST(BaseMatrix, BaseMatrix) { typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0>(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(assign); BASEMATRIXCOMPARE(add); @@ -129,7 +128,7 @@ TEST(BaseMatrix, BaseMatrix) { BASEMATRIXCOMPARE(addP2P); BASEMATRIXCOMPARE(invSqrt); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } /** @@ -138,8 +137,8 @@ TEST(BaseMatrix, BaseMatrix) { */ TEST(BaseMatrix, BaseMatrix_real) { typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, real); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(addBias); BASEMATRIXCOMPARE(add); @@ -154,7 +153,7 @@ TEST(BaseMatrix, BaseMatrix_real) { BASEMATRIXCOMPARE(isEqualTo); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } /** @@ -163,8 +162,8 @@ TEST(BaseMatrix, BaseMatrix_real) { */ TEST(BaseMatrix, BaseMatrix_BaseMatrix) { typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, BaseMatrix&); - #define BASEMATRIXCOMPARE(function) \ - BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); +#define BASEMATRIXCOMPARE(function) \ + BaseMatrixCompare<0, 1>(static_cast(&BaseMatrix::function)); BASEMATRIXCOMPARE(softCrossEntropy); BASEMATRIXCOMPARE(softCrossEntropyBp); @@ -181,69 +180,25 @@ TEST(BaseMatrix, BaseMatrix_BaseMatrix) { BASEMATRIXCOMPARE(dotMulSquare); BASEMATRIXCOMPARE(dotSquareSquare); - #undef BASEMATRIXCOMPARE +#undef BASEMATRIXCOMPARE } -/** - * Test aggregate member functions which prototype is - * void (BaseMatrix::*)(BaseMatrix&). - */ -TEST(Aggregate, BaseMatrix) { - typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&); - #define BASEMATRIXAPPLYROW(function) \ - BaseMatrixApplyRow<0>(static_cast(&BaseMatrix::function)); - - #define BASEMATRIXAPPLYCOL(function) \ - BaseMatrixApplyCol<0>(static_cast(&BaseMatrix::function)); - - BASEMATRIXAPPLYROW(maxRows); - BASEMATRIXAPPLYROW(minRows); - - BASEMATRIXAPPLYCOL(sumCols); - BASEMATRIXAPPLYCOL(maxCols); - BASEMATRIXAPPLYCOL(minCols); - - #undef BASEMATRIXAPPLYROW - #undef BASEMATRIXAPPLYCOL +// member function without overloaded +TEST(BaseMatrix, Other) { + BaseMatrixCompare<0, 1, 2>(&BaseMatrix::rowScale); + BaseMatrixCompare<0, 1, 2>(&BaseMatrix::rowDotMul); + BaseMatrixCompare<0, 1, 2, 3>(&BaseMatrix::binaryClassificationError); } -/** - * Test aggregate member functions which prototype is - * void (BaseMatrix::*)(BaseMatrix&, BaseMatrix&). - */ -TEST(Aggregate, BaseMatrix_BaseMatrix) { - typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, BaseMatrix&); - #define BASEMATRIXAPPLYROW(function) \ - BaseMatrixApplyRow<0, 1>(static_cast(&BaseMatrix::function)); - - #define BASEMATRIXAPPLYCOL(function) \ - BaseMatrixApplyCol<0, 1>(static_cast(&BaseMatrix::function)); - - BASEMATRIXAPPLYCOL(addDotMulVMM); - - #undef BASEMATRIXAPPLYROW - #undef BASEMATRIXAPPLYCOL -} - -/** - * Test aggregate member functions which prototype is - * void (BaseMatrix::*)(BaseMatrix&, real, real). - */ -TEST(Aggregate, BaseMatrix_real_real) { - typedef void (BaseMatrix::*FunctionProto)(BaseMatrix&, real, real); - #define BASEMATRIXAPPLYROW(function) \ - BaseMatrixApplyRow<0, 1, 2>(\ - static_cast(&BaseMatrix::function)); - - #define BASEMATRIXAPPLYCOL(function) \ - BaseMatrixApplyCol<0, 1, 2>(\ - static_cast(&BaseMatrix::function)); - - BASEMATRIXAPPLYROW(sumRows); - BASEMATRIXAPPLYCOL(sumCols); +TEST(BaseMatrix, Aggregate) { + BaseMatrixAsColVector<0>(&BaseMatrix::maxRows); + BaseMatrixAsColVector<0>(&BaseMatrix::minRows); + BaseMatrixAsColVector<0, 1, 2>(&BaseMatrix::sumRows); - #undef BASEMATRIXAPPLYROW - #undef BASEMATRIXAPPLYCOL + BaseMatrixAsRowVector<0>(&BaseMatrix::maxCols); + BaseMatrixAsRowVector<0>(&BaseMatrix::minCols); + BaseMatrixAsRowVector<0, 1>(&BaseMatrix::addDotMulVMM); + BaseMatrixAsRowVector<0, 1, 2>(&BaseMatrix::sumCols); } int main(int argc, char** argv) { diff --git a/paddle/math/tests/test_Matrix.cpp b/paddle/math/tests/test_Matrix.cpp index a4d9d34976..485e702a66 100644 --- a/paddle/math/tests/test_Matrix.cpp +++ b/paddle/math/tests/test_Matrix.cpp @@ -19,25 +19,20 @@ limitations under the License. */ */ #include -#include "paddle/utils/Util.h" -#include "paddle/math/BaseMatrix.h" #include "TestUtils.h" using namespace paddle; // NOLINT -using namespace std; // NOLINT -/** - * Test member functions which prototype is - * void (Matrix::*)(Matrix&). - */ -TEST(BaseMatrix, real) { - typedef void (Matrix::*FunctionProto)(Matrix&); -#define MATRIXCOMPARE(function) \ - BaseMatrixCompare<0>(static_cast(&Matrix::function), true); +TEST(Matrix, Matrix) { + BaseMatrixCompare<0>(&Matrix::softmax, true); + BaseMatrixCompare<0, 1>(&Matrix::sumOfSquaresBp); +} - MATRIXCOMPARE(softmax); +TEST(Matrix, Aggregate) { + BaseMatrixAsRowVector<0, 1>( + static_cast(&Matrix::collectBias)); -#undef MATRIXCOMPARE + BaseMatrixAsColVector<0, 1>(&Matrix::sumOfSquares); } int main(int argc, char** argv) { diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 80596abe82..86a4a0e5ec 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -448,60 +448,6 @@ void testMatrixZeroAtOffset(int height, int width) { MatrixCheckEqual(*cpuA, *cpuTest); } -void testMatrixSumOfSquaresBp(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - - cpuA->sumOfSquaresBp(*cpuB, *cpuC); - gpuA->sumOfSquaresBp(*gpuB, *gpuC); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixBinaryRowScale(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, 1); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, 1); - - MatrixPtr cpuA1 = std::make_shared(height, width); - MatrixPtr cpuB1 = std::make_shared(height, 1); - MatrixPtr gpuA1 = std::make_shared(height, width); - MatrixPtr gpuB1 = std::make_shared(height, 1); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - cpuA1->copyFrom(*cpuA); - cpuB1->copyFrom(*cpuB); - gpuA1->copyFrom(*cpuA); - gpuB1->copyFrom(*cpuB); - - cpuA->addColVector(*cpuB); - gpuA->addColVector(*gpuB); - cpuA1->addColumnVector(*cpuB1); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); - - MatrixCheckEqual(*cpuA, *cpuA1); -} - void testMatrixAddBias(int height, int width, real scale) { MatrixPtr cpuA = std::make_shared(height, width); MatrixPtr cpuB = std::make_shared(1, width); @@ -521,76 +467,6 @@ void testMatrixAddBias(int height, int width, real scale) { MatrixCheckErr(*cpuA, *outputCheck); } -void testMatrixTernaryRowScale(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - MatrixPtr cpuA1 = std::make_shared(height, width); - MatrixPtr cpuB1 = std::make_shared(height, width); - MatrixPtr cpuC1 = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - cpuA1->copyFrom(*cpuA); - cpuB1->copyFrom(*cpuB); - cpuC1->copyFrom(*cpuC); - - int columnOffset = rand() % width; // NOLINT - - cpuA->rowScale(columnOffset, *cpuB, *cpuC); - gpuA->rowScale(columnOffset, *gpuB, *gpuC); - cpuA1->rowScale2(columnOffset, *cpuB1, *cpuC1); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckEqual(*cpuA, *outputCheck); - - MatrixCheckEqual(*cpuA, *cpuA1); -} - -void testMatrixTernaryRowDotMul(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - - MatrixPtr cpuA1 = std::make_shared(height, width); - MatrixPtr cpuB1 = std::make_shared(height, width); - MatrixPtr cpuC1 = std::make_shared(height, width); - - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - cpuA1->copyFrom(*cpuA); - cpuB1->copyFrom(*cpuB); - cpuC1->copyFrom(*cpuC); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - - int columnOffset = rand() % width; // NOLINT - - cpuA->rowDotMul(columnOffset, *cpuB, *cpuC); - gpuA->rowDotMul(columnOffset, *gpuB, *gpuC); - cpuA1->rowDotMul2(columnOffset, *cpuB1, *cpuC1); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *cpuA1); - MatrixCheckErr(*cpuA, *outputCheck); -} - void testMatrixAddDotMulMMV(int height, int width) { MatrixPtr cpuA = std::make_shared(height, width); MatrixPtr cpuB = std::make_shared(height, width); @@ -670,18 +546,11 @@ TEST(Matrix, unary) { for (auto width : {1, 3, 32, 100, 512, 1000, 3210}) { VLOG(3) << " height=" << height << " width=" << width; - // applyTernary - testMatrixSumOfSquaresBp(height, width); - // asRowVector testMatrixAddBias(height, width, 1.0); testMatrixAddBias(height, width, 3.5); testMatrixAddDotMulMMV(height, width); - // asColVector - testMatrixTernaryRowScale(height, width); - testMatrixBinaryRowScale(height, width); - // sum testMatrixGetSum(height, width); @@ -782,119 +651,6 @@ TEST(Matrix, softmax) { } } -void testMatrixCollectBias(int height, int width) { - MatrixPtr cpuA = std::make_shared(1, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(1, width); - MatrixPtr gpuB = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - - real scale = 1.0f / (rand() % 10); // NOLINT - - cpuA->collectBias(*cpuB, scale); - gpuA->collectBias(*gpuB, scale); - - MatrixPtr outputCheck = std::make_shared(1, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixSumOfSquares(int height, int width, int endCol = 0) { - MatrixPtr cpuA = std::make_shared(height, 1); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, 1); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - - if (!endCol) { - cpuA->sumOfSquares(*cpuB, *cpuC); - gpuA->sumOfSquares(*gpuB, *gpuC); - } else { - MatrixPtr subCpuB = cpuB->subColMatrix(0, endCol); - MatrixPtr subCpuC = cpuC->subColMatrix(0, endCol); - MatrixPtr subGpuB = gpuB->subColMatrix(0, endCol); - MatrixPtr subGpuC = gpuC->subColMatrix(0, endCol); - cpuA->sumOfSquares(*subCpuB, *subCpuC); - gpuA->sumOfSquares(*subGpuB, *subGpuC); - } - - MatrixPtr outputCheck = std::make_shared(height, 1); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); -} - -void testMatrixBinaryClassificationError(int height, int width) { - MatrixPtr cpuA = std::make_shared(height, width); - MatrixPtr cpuB = std::make_shared(height, width); - MatrixPtr cpuC = std::make_shared(height, width); - MatrixPtr gpuA = std::make_shared(height, width); - MatrixPtr gpuB = std::make_shared(height, width); - MatrixPtr gpuC = std::make_shared(height, width); - - MatrixPtr cpuA2 = std::make_shared(height, width); - MatrixPtr cpuB2 = std::make_shared(height, width); - MatrixPtr cpuC2 = std::make_shared(height, width); - - cpuA->randomizeUniform(); - cpuB->randomizeUniform(); - cpuC->randomizeUniform(); - gpuA->copyFrom(*cpuA); - gpuB->copyFrom(*cpuB); - gpuC->copyFrom(*cpuC); - cpuA2->copyFrom(*cpuA); - cpuB2->copyFrom(*cpuB); - cpuC2->copyFrom(*cpuC); - - real scale = 0.5; - int columnOffset = rand() % width; // NOLINT - - cpuA->binaryClassificationError(columnOffset, *cpuB, *cpuC, scale); - gpuA->binaryClassificationError(columnOffset, *gpuB, *gpuC, scale); - cpuA2->binaryClassificationError2(columnOffset, *cpuB2, *cpuC2, scale); - - MatrixPtr outputCheck = std::make_shared(height, width); - outputCheck->copyFrom(*gpuA); - MatrixCheckErr(*cpuA, *outputCheck); - MatrixCheckErr(*cpuA, *cpuA2); -} - -TEST(Matrix, aggregate) { - for (auto height : {1, 11, 16, 32, 64, 73, 128, 200, 1024, 2345}) { - for (auto width : {1, 9, 16, 32, 64, 100, 512, 1000, 1024, 2453}) { - VLOG(3) << " height=" << height << " width=" << width; - testMatrixCollectBias(height, width); - testMatrixTernaryRowDotMul(height, width); - - testMatrixSumOfSquares(height, width); - testMatrixBinaryClassificationError(height, width); - } - } -} - -TEST(Matrix, aggregate2) { - for (auto height : {16, 32, 128, 512, 1024}) { - for (auto width : - {16, 32, 64, 128, 256, 512, 768, 1024, 2048, 3072, 4096}) { - VLOG(3) << " height=" << height << " width=" << width; - - int endCol = rand() % width; // NOLINT - testMatrixSumOfSquares(height, width, endCol); - } - } -} - void testMatrixAddAtOffset(int height, int width1, int width2) { MatrixPtr cpuInput = std::make_shared(height, width1); MatrixPtr cpuOutput = std::make_shared(height, width2); -- GitLab From 1733a777fe4bbf9c8491455c78f06845cc2bc9e5 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 29 Nov 2016 19:36:06 +0800 Subject: [PATCH 0172/1503] Using ApproximatelyEquals for trainer config unittest. * Make float equal works well. * compare 0.01 and 0.009999999999 will return true. --- .../tests/CMakeLists.txt | 4 ++ .../tests/ProtobufEqualMain.cpp | 59 +++++++++++++++++++ .../tests/configs/.gitignore | 2 +- .../tests/configs/file_list.sh | 9 +++ .../tests/configs/generate_protostr.sh | 14 +---- .../tests/configs/run_tests.sh | 30 ++++++++-- 6 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/ProtobufEqualMain.cpp create mode 100755 python/paddle/trainer_config_helpers/tests/configs/file_list.sh diff --git a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt index cf52b06bfe..78505c85e1 100644 --- a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt +++ b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt @@ -4,7 +4,11 @@ add_test(NAME layers_test python ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/layers_test.py WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) +add_paddle_exe(protobuf_equal + ProtobufEqualMain.cpp) + add_test(NAME test_layerHelpers COMMAND ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh + ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal ) diff --git a/python/paddle/trainer_config_helpers/tests/ProtobufEqualMain.cpp b/python/paddle/trainer_config_helpers/tests/ProtobufEqualMain.cpp new file mode 100644 index 0000000000..06f7de9306 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/ProtobufEqualMain.cpp @@ -0,0 +1,59 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include +#include +#include +#include +#include "TrainerConfig.pb.h" + +bool loadPb(google::protobuf::Message* conf, const std::string& filename) { + std::ifstream fin; + fin.open(filename.c_str()); + if (fin.is_open()) { + std::string str((std::istreambuf_iterator(fin)), + std::istreambuf_iterator()); + bool ok = google::protobuf::TextFormat::ParseFromString(str, conf); + fin.close(); + return ok; + } else { + return false; + } +} + +int main(int argc, char** argv) { + std::unique_ptr config1; + std::unique_ptr config2; + if (argc == 3) { + config1.reset(new paddle::ModelConfig()); + config2.reset(new paddle::ModelConfig()); + } else if (argc == 4) { + config1.reset(new paddle::TrainerConfig()); + config2.reset(new paddle::TrainerConfig()); + } + if (!config1 || !config2) { + return 1; + } else if (!loadPb(config1.get(), argv[1])) { + return 2; + } else if (!loadPb(config2.get(), argv[2])) { + return 3; + } else { + if (google::protobuf::util::MessageDifferencer::ApproximatelyEquals( + *config1, *config2)) { + return 0; + } else { + return 4; + } + } +} diff --git a/python/paddle/trainer_config_helpers/tests/configs/.gitignore b/python/paddle/trainer_config_helpers/tests/configs/.gitignore index eb646b4a71..c654bd41b0 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/.gitignore +++ b/python/paddle/trainer_config_helpers/tests/configs/.gitignore @@ -1 +1 @@ -protostr/*.unitest +protostr/*.unittest diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh new file mode 100755 index 0000000000..3f1d99701a --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -0,0 +1,9 @@ +#!/bin/bash +export configs=(test_fc layer_activations projections test_print_layer +test_sequence_pooling test_lstmemory_layer test_grumemory_layer +last_first_seq test_expand_layer test_ntm_layers test_hsigmoid +img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers +test_rnn_group shared_fc shared_lstm test_cost_layers_with_weight +test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops) + +export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh index bb594ac2c2..e55f9bd388 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh @@ -5,24 +5,16 @@ cd `dirname $0` export PYTHONPATH=$PWD/../../../../ protostr=$PWD/protostr - -configs=(test_fc layer_activations projections test_print_layer -test_sequence_pooling test_lstmemory_layer test_grumemory_layer -last_first_seq test_expand_layer test_ntm_layers test_hsigmoid -img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers -test_rnn_group shared_fc shared_lstm test_cost_layers_with_weight -test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops) - -whole_configs=(test_split_datasource) +. file_list.sh for conf in ${configs[*]} do echo "Generating " $conf - python -m paddle.utils.dump_config $conf.py > $protostr/$conf.protostr.unitest + python -m paddle.utils.dump_config $conf.py > $protostr/$conf.protostr.unittest done for conf in ${whole_configs[*]} do echo "Generating " $conf - python -m paddle.utils.dump_config $conf.py "" --whole > $protostr/$conf.protostr.unitest + python -m paddle.utils.dump_config $conf.py "" --whole > $protostr/$conf.protostr.unittest done diff --git a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh index 9683288358..5f6ed91030 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh @@ -9,9 +9,27 @@ files=`ls $protostr | grep -v "unitest"` ./generate_protostr.sh -for file in $files -do - base_protostr=$protostr/$file - new_protostr=$protostr/$file.unitest - diff $base_protostr $new_protostr -u -done +. ./file_list.sh + +if [ -z $1 ]; then + for file in $files + do + base_protostr=$protostr/$file + new_protostr=$protostr/$file.unittest + diff $base_protostr $new_protostr -u + done +else + for file in ${configs[*]} + do + if ! $1 $protostr/$file.protostr $protostr/$file.protostr.unittest; then + diff $protostr/$file.protostr $protostr/$file.protostr.unittest -u + fi + done + + for file in ${whole_configs[*]} + do + if ! $1 $protostr/$file.protostr $protostr/$file.protostr.unittest --whole; then + diff $protostr/$file.protostr $protostr/$file.protostr.unittest -u + fi + done +fi -- GitLab From 1eb9c36fd2a13ff5d267b0280a7f3448290b2c04 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 30 Nov 2016 12:50:14 +0800 Subject: [PATCH 0173/1503] Make ApproximatelyEquals fit for protobuf2 --- CMakeLists.txt | 11 ++++++++++ proto/CMakeLists.txt | 9 -------- .../tests/CMakeLists.txt | 22 ++++++++++++------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d685587a7..7ef9368ef5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,17 @@ include(package) find_package(SWIG 2.0) find_package(CUDA QUIET) find_package(Protobuf REQUIRED) + +# Check protobuf library version. +execute_process(COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --version + OUTPUT_VARIABLE PROTOBUF_VERSION) +string(REPLACE "libprotoc " "" PROTOBUF_VERSION ${PROTOBUF_VERSION}) + +set(PROTOBUF_3 OFF) +if (${PROTOBUF_VERSION} VERSION_GREATER "3.0.0" OR ${PROTOBUF_VERSION} VERSION_EQUAL "3.0.0") + set(PROTOBUF_3 ON) +endif() + find_package(PythonLibs 2.7 REQUIRED) find_package(PythonInterp 2.7 REQUIRED) find_package(ZLIB REQUIRED) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index ec68b53d44..d7f523bc8d 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,12 +1,3 @@ -execute_process(COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --version - OUTPUT_VARIABLE PROTOBUF_VERSION) -string(REPLACE "libprotoc " "" PROTOBUF_VERSION ${PROTOBUF_VERSION}) - -set(PROTOBUF_3 OFF) -if (${PROTOBUF_VERSION} VERSION_GREATER "3.0.0" OR ${PROTOBUF_VERSION} VERSION_EQUAL "3.0.0") - set(PROTOBUF_3 ON) -endif() - set(proto_filenames DataConfig.proto DataFormat.proto diff --git a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt index 78505c85e1..6180b2efbc 100644 --- a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt +++ b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt @@ -4,11 +4,17 @@ add_test(NAME layers_test python ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/layers_test.py WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) -add_paddle_exe(protobuf_equal - ProtobufEqualMain.cpp) - -add_test(NAME test_layerHelpers - COMMAND - ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh - ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal -) +if (PROTOBUF_3) + add_paddle_exe(protobuf_equal + ProtobufEqualMain.cpp) + add_test(NAME test_layerHelpers + COMMAND + ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh + ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal + ) +else() + add_test(NAME test_layerHelpers + COMMAND + ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh + ) +endif() -- GitLab From 63df7ae65d5f003f0166d5a403991b73081db83f Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 30 Nov 2016 12:51:13 +0800 Subject: [PATCH 0174/1503] Refine docker install doc and FAQ for gpu driver --- .../build_and_install/docker_install.rst | 33 +++++++++++++------ .../install/docker_install.rst | 33 ++++++++++++++----- doc_cn/faq/index.rst | 15 +++++++++ 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install.rst b/doc/getstarted/build_and_install/docker_install.rst index e95de35f4d..88d8357304 100644 --- a/doc/getstarted/build_and_install/docker_install.rst +++ b/doc/getstarted/build_and_install/docker_install.rst @@ -56,25 +56,38 @@ The PaddlePaddle images don't contain any entry command. You need to write your Download and Run Docker images ------------------------------ -You have to install Docker in your machine which has linux kernel version 3.10+ first. You can refer to the official guide https://docs.docker.com/engine/installation/ for further information. +Currently, Docker is supported on macOS, Windows and Linux distributions. Please check out `Install Docker Engine `_ to find out much more details. -You can use :code:`docker pull ` to download images first, or just launch a container with :code:`docker run` \: +PaddlePaddle on CPU +..................... -.. code-block:: bash +You can use :code:`docker pull ` to download images, or directly launch a container with :code:`docker run` \: - docker run -it paddledev/paddle:cpu-latest + .. code-block:: bash + docker run -it paddledev/paddle:cpu-latest -If you want to launch container with GPU support, you need to set some environment variables at the same time: +PaddlePaddle on GPU +..................... -.. code-block:: bash +To build GPU version, you will need the following installed: + + .. code-block:: bash + + 1. a CUDA-capable GPU + 2. NVIDIA CUDA Toolkit (available at http://developer.nvidia.com/cuda-downloads) + + +Then, you will need to mount related CUDA driver and library into container. + + .. code-block:: bash - export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" - export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:gpu-latest + export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:gpu-latest -Some notes for docker +Some notes for Docker --------------------- Performance diff --git a/doc_cn/build_and_install/install/docker_install.rst b/doc_cn/build_and_install/install/docker_install.rst index 40339659be..90a5c93709 100644 --- a/doc_cn/build_and_install/install/docker_install.rst +++ b/doc_cn/build_and_install/install/docker_install.rst @@ -60,21 +60,38 @@ mac osx或者是windows机器,请参考 `mac osx的安装文档 `_ 和 `windows 的安装文档 `_ 。 + +启动CPU版Docker镜像 +................... + 您可以使用 :code:`docker pull` 命令预先下载镜像,也可以直接执行 :code:`docker run` 命令运行镜像。执行方法如下: -.. code-block:: bash + .. code-block:: bash + + $ docker run -it paddledev/paddlepaddle:cpu-latest + +即可启动和进入PaddlePaddle的container。 + +启动GPU版Docker镜像 +................... + +首先, 请参考以下链接,在机器上安装CUDA Toolkit。 + + .. code-block:: bash - $ docker run -it paddledev/paddle:cpu-latest + NVIDIA CUDA Toolkit (available at http://developer.nvidia.com/cuda-downloads) -即可启动和进入PaddlePaddle的container。如果运行GPU版本的PaddlePaddle,则需要先将 -cuda相关的Driver和设备映射进container中,脚本类似于 +其次,需要将cuda相关的驱动和设备映射进container中,脚本类似于 -.. code-block:: bash + .. code-block:: bash + + $ export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu - $ export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" - $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:gpu-latest +使用PaddlePaddle +.................. 进入Docker container后,运行 :code:`paddle version` 即可打印出PaddlePaddle的版本和构建 信息。安装完成的PaddlePaddle主体包括三个部分, :code:`paddle` 脚本, python的 diff --git a/doc_cn/faq/index.rst b/doc_cn/faq/index.rst index 551430eb41..838fa651d8 100644 --- a/doc_cn/faq/index.rst +++ b/doc_cn/faq/index.rst @@ -202,3 +202,18 @@ PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字 解决办法是: * 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 + + +9. 运行Docker GPU镜像出现 "CUDA driver version is insufficient" +---------------------------------------------------------------- + +用户在使用PaddlePaddle GPU的Docker镜像的时候,常常出现 `Cuda Error: CUDA driver version is insufficient for CUDA runtime version`, 原因在于没有把机器上CUDA相关的驱动和库映射到容器内部。 +具体的解决方法是: + +.. code-block:: bash + + $ export CUDA_SO="$(\ls usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu + +更多关于Docker的安装与使用, 请参考 `PaddlePaddle Docker 文档 `_ 。 -- GitLab From 9d72cab0a4323a6d96bdc443f9cbac5c5658edbc Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 30 Nov 2016 11:55:00 +0800 Subject: [PATCH 0175/1503] Accelerating image processing for CNN --- CMakeLists.txt | 4 + plugin/opencv/CMakeLists.txt | 39 ++++++ plugin/opencv/DataTransformer.cpp | 179 +++++++++++++++++++++++++ plugin/opencv/DataTransformer.h | 123 +++++++++++++++++ plugin/opencv/PyDecodejpeg.cpp | 173 ++++++++++++++++++++++++ python/paddle/utils/image_multiproc.py | 170 +++++++++++++++++++++++ python/paddle/utils/image_util.py | 31 +++-- 7 files changed, 705 insertions(+), 14 deletions(-) create mode 100644 plugin/opencv/CMakeLists.txt create mode 100644 plugin/opencv/DataTransformer.cpp create mode 100644 plugin/opencv/DataTransformer.h create mode 100644 plugin/opencv/PyDecodejpeg.cpp create mode 100644 python/paddle/utils/image_multiproc.py diff --git a/CMakeLists.txt b/CMakeLists.txt index af193c27ae..40f18f1550 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,3 +195,7 @@ if(WITH_DOC) add_subdirectory(doc) add_subdirectory(doc_cn) endif() + +if(USE_OPENCV) + add_subdirectory(plugin/opencv) +endif() diff --git a/plugin/opencv/CMakeLists.txt b/plugin/opencv/CMakeLists.txt new file mode 100644 index 0000000000..4a253f346a --- /dev/null +++ b/plugin/opencv/CMakeLists.txt @@ -0,0 +1,39 @@ +# use opencv plugin + +project(DeJpeg CXX C) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) +set(DEJPEG_LINKER_LIBS "") + +# opencv +find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc) +include_directories(${OpenCV_INCLUDE_DIRS}) +list(APPEND DEJPEG_LINKER_LIBS ${OpenCV_LIBS}) +message(STATUS "OpenCV found (${OpenCV_CONFIG_PATH})") +add_definitions(-DUSE_OPENCV) + +# boost-python +set(Boost_NO_SYSTEM_PATHS ON) +if (Boost_NO_SYSTEM_PATHS) + set(BOOST_ROOT $ENV{BOOST_ROOT}) + set(Boost_DIR ${BOOST_ROOT}) + set(Boost_INCLUDE_DIR "${BOOST_ROOT}/include") + set(Boost_LIBRARIES "${BOOST_ROOT}/lib/") +endif (Boost_NO_SYSTEM_PATHS) +find_package(Boost 1.46 COMPONENTS python) +include_directories(SYSTEM ${Boost_INCLUDE_DIR}) +link_directories(${Boost_INCLUDE_DIR}) +message(STATUS "Boost found (${Boost_INCLUDE_DIR})") +message(STATUS "Boost found (${Boost_LIBRARIES})") +list(APPEND DEJPEG_LINKER_LIBS ${Boost_LIBRARIES}) + + +file(GLOB DEJPEG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}" "*.h") +file(GLOB DEJPEG_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp") + +set(CMAKE_CXX_FLAGS "-std=c++11 -O3 -fPIC -Wno-unused-parameter") + +add_library(DeJpeg SHARED ${DEJPEG_SOURCES}) +target_link_libraries(DeJpeg ${DEJPEG_LINKER_LIBS}) +set_target_properties(DeJpeg PROPERTIES PREFIX "") diff --git a/plugin/opencv/DataTransformer.cpp b/plugin/opencv/DataTransformer.cpp new file mode 100644 index 0000000000..f4e21db886 --- /dev/null +++ b/plugin/opencv/DataTransformer.cpp @@ -0,0 +1,179 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 "DataTransformer.h" +#include +#include + +DataTransformer::DataTransformer(int threadNum, + int capacity, + bool isTest, + bool isColor, + int cropHeight, + int cropWidth, + int imgSize, + bool isEltMean, + bool isChannelMean, + float* meanValues) + : isTest_(isTest), + isColor_(isColor), + cropHeight_(cropHeight), + cropWidth_(cropWidth), + imgSize_(imgSize), + capacity_(capacity), + prefetchFree_(capacity), + prefetchFull_(capacity) { + fetchCount_ = -1; + scale_ = 1.0; + isChannelMean_ = isChannelMean; + isEltMean_ = isEltMean; + loadMean(meanValues); + + imgPixels_ = cropHeight * cropWidth * (isColor_ ? 3 : 1); + + prefetch_.reserve(capacity); + for (int i = 0; i < capacity; i++) { + auto d = std::make_shared(new float[imgPixels_ * 3], 0); + prefetch_.push_back(d); + memset(prefetch_[i]->first, 0, imgPixels_ * sizeof(float)); + prefetchFree_.enqueue(prefetch_[i]); + } + + numThreads_ = 12; + syncThreadPool_.reset(new SyncThreadPool(numThreads_, false)); +} + +void DataTransformer::loadMean(float* values) { + if (values) { + int c = isColor_ ? 3 : 1; + int sz = isChannelMean_ ? c : cropHeight_ * cropWidth_ * c; + meanValues_ = new float[sz]; + memcpy(meanValues_, values, sz * sizeof(float)); + } +} + +void DataTransformer::startFetching(const char* src, + const int size, + float* trg) { + vector imbuf(src, src + size); + int cvFlag = (isColor_ ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE); + cv::Mat im = cv::imdecode(cv::Mat(imbuf), cvFlag); + if (!im.data) { + LOG(ERROR) << "Could not decode image"; + LOG(ERROR) << im.channels() << " " << im.rows << " " << im.cols; + } + this->transform(im, trg); +} + +int DataTransformer::Rand(int min, int max) { + std::random_device source; + std::mt19937 rng(source()); + std::uniform_int_distribution dist(min, max); + return dist(rng); +} + +void DataTransformer::transform(Mat& cvImgOri, float* target) { + const int imgChannels = cvImgOri.channels(); + const int imgHeight = cvImgOri.rows; + const int imgWidth = cvImgOri.cols; + const bool doMirror = (!isTest_) && Rand(0, 1); + int h_off = 0; + int w_off = 0; + int th = imgHeight; + int tw = imgWidth; + cv::Mat img; + if (imgSize_ > 0) { + if (imgHeight > imgWidth) { + tw = imgSize_; + th = int(double(imgHeight) / imgWidth * tw); + th = th > imgSize_ ? th : imgSize_; + } else { + th = imgSize_; + tw = int(double(imgWidth) / imgHeight * th); + tw = tw > imgSize_ ? tw : imgSize_; + } + cv::resize(cvImgOri, img, cv::Size(tw, th)); + } else { + cv::Mat img = cvImgOri; + } + + cv::Mat cv_cropped_img = img; + if (cropHeight_ && cropWidth_) { + if (!isTest_) { + h_off = Rand(0, th - cropHeight_); + w_off = Rand(0, tw - cropWidth_); + } else { + h_off = (th - cropHeight_) / 2; + w_off = (tw - cropWidth_) / 2; + } + cv::Rect roi(w_off, h_off, cropWidth_, cropHeight_); + cv_cropped_img = img(roi); + } else { + CHECK_EQ(cropHeight_, imgHeight); + CHECK_EQ(cropWidth_, imgWidth); + } + int height = cropHeight_; + int width = cropWidth_; + int top_index; + for (int h = 0; h < height; ++h) { + const uchar* ptr = cv_cropped_img.ptr(h); + int img_index = 0; + for (int w = 0; w < width; ++w) { + for (int c = 0; c < imgChannels; ++c) { + if (doMirror) { + top_index = (c * height + h) * width + width - 1 - w; + } else { + top_index = (c * height + h) * width + w; + } + float pixel = static_cast(ptr[img_index++]); + if (isEltMean_) { + int mean_index = (c * imgHeight + h) * imgWidth + w; + target[top_index] = (pixel - meanValues_[mean_index]) * scale_; + } else { + if (isChannelMean_) { + target[top_index] = (pixel - meanValues_[c]) * scale_; + } else { + target[top_index] = pixel * scale_; + } + } + } + } + } // target: BGR +} + +void DataTransformer::start(vector& data, int* datalen, int* labels) { + auto job = [&](int tid, int numThreads) { + for (int i = tid; i < data.size(); i += numThreads) { + DataTypePtr ret = prefetchFree_.dequeue(); + char* buf = data[i]; + int size = datalen[i]; + ret->second = labels[i]; + this->startFetching(buf, size, ret->first); + prefetchFull_.enqueue(ret); + } + }; + syncThreadPool_->exec(job); + fetchCount_ = data.size(); +} + +void DataTransformer::obtain(float* data, int* label) { + fetchCount_--; + if (fetchCount_ < 0) { + LOG(FATAL) << "Empty data"; + } + DataTypePtr ret = prefetchFull_.dequeue(); + *label = ret->second; + memcpy(data, ret->first, sizeof(float) * imgPixels_); + prefetchFree_.enqueue(ret); +} diff --git a/plugin/opencv/DataTransformer.h b/plugin/opencv/DataTransformer.h new file mode 100644 index 0000000000..c4f04a5878 --- /dev/null +++ b/plugin/opencv/DataTransformer.h @@ -0,0 +1,123 @@ +/* Copyright (c) 2016 Baidu, Inc. 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 +// #define OPENCV_CAN_BREAK_BINARY_COMPATIBILITY +#include +#include +#include +#include + +#include "paddle/utils/Thread.h" + +using namespace std; +using namespace cv; +using namespace paddle; + +/** + * This is an image processing module with OpenCV, such as + * resizing, scaling, mirroring, substracting the image mean... + * + * This class has a double BlockQueue and they shared the same memory. + * It is used to avoid create memory each time. And it also can + * return the data even if the data are processing in multi-threads. + */ +class DataTransformer { +public: + DataTransformer(int threadNum, + int capacity, + bool isTest, + bool isColor, + int cropHeight, + int cropWidth, + int imgSize, + bool isEltMean, + bool isChannelMean, + float* meanValues); + virtual ~DataTransformer() { + if (meanValues_) { + free(meanValues_); + } + } + + /** + * @brief Start multi-threads to transform a list of input data. + * The processed data will be saved in Queue of prefetchFull_. + * + * @param data Data containing the image string to be transformed. + * @param label The label of input image. + */ + void start(vector& data, int* datalen, int* labels); + + /** + * @brief Applies the transformation on one image Mat. + * + * @param img The input img to be transformed. + * @param target target is used to save the transformed data. + */ + void transform(Mat& img, float* target); + + /** + * @brief Decode the image string, then calls transform() function. + * + * @param src The input image string. + * @param size The length of string. + * @param trg trg is used to save the transformed data. + */ + void startFetching(const char* src, const int size, float* trg); + + /** + * @brief Return the transformed data and its label. + */ + void obtain(float* data, int* label); + +private: + int isTest_; + int isColor_; + int cropHeight_; + int cropWidth_; + int imgSize_; + int capacity_; + int fetchCount_; + bool isEltMean_; + bool isChannelMean_; + int numThreads_; + float scale_; + int imgPixels_; + float* meanValues_; + + /** + * Initialize the mean values. + */ + void loadMean(float* values); + + /** + * @brief Generates a random integer from Uniform({min, min + 1, ..., max}). + * @param min The lower bound (inclusive) value of the random number. + * @param max The upper bound (inclusive) value of the random number. + * + * @return + * A uniformly random integer value from ({min, min + 1, ..., max}). + */ + int Rand(int min, int max); + + typedef pair DataType; + typedef std::shared_ptr DataTypePtr; + std::vector prefetch_; + std::unique_ptr syncThreadPool_; + BlockingQueue prefetchFree_; + BlockingQueue prefetchFull_; + +}; // class DataTransformer diff --git a/plugin/opencv/PyDecodejpeg.cpp b/plugin/opencv/PyDecodejpeg.cpp new file mode 100644 index 0000000000..b004d7cad8 --- /dev/null +++ b/plugin/opencv/PyDecodejpeg.cpp @@ -0,0 +1,173 @@ +/* Copyright (c) 2016 Baidu, Inc. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DataTransformer.h" + +using namespace boost::python; +using namespace std; + +/** + * DecodeJpeg is an image processing API for interfacing Python and C++ + * code DataTransformer, which used OpenCV and multi-threads to accelerate + * image processing. + * The Boost Python Library is used to wrap C++ interfaces. + */ + +class DecodeJpeg { +public: + /** + * The constructor will create and nitialize an object of DataTransformer. + */ + DecodeJpeg(int threadNum, + int capacity, + bool isTest, + bool isColor, + int resize_min_size, + int cropSizeH, + int cropSizeW, + PyObject* meanValues) { + int channel = isColor ? 3 : 1; + bool isEltMean = false; + bool isChannelMean = false; + float* mean = NULL; + if (meanValues || meanValues != Py_None) { + if (!PyArray_Check(meanValues)) { + LOG(FATAL) << "Object is not a numpy array"; + } + pyTypeCheck(meanValues); + int size = PyArray_SIZE(meanValues); + isChannelMean = (size == channel) ? true : false; + isEltMean = (size == channel * cropSizeH * cropSizeW) ? true : false; + CHECK(isChannelMean != isEltMean); + mean = (float*)PyArray_DATA(meanValues); + } + tfhandlerPtr_ = std::make_shared(threadNum, + capacity, + isTest, + isColor, + cropSizeH, + cropSizeW, + resize_min_size, + isEltMean, + isChannelMean, + mean); + } + + ~DecodeJpeg() {} + + /** + * @brief This function is used to parse the Python object and convert + * the data to C++ format. Then it called the function of + * DataTransformer to start image processing. + * @param pysrc The input image list with string type. + * @param pylabel The input label of image. + * It's type is numpy.array with int32. + */ + void start(boost::python::list& pysrc, PyObject* pydlen, PyObject* pylabel) { + vector data; + int num = len(pysrc); + for (int t = 0; t < num; ++t) { + char* src = boost::python::extract(pysrc[t]); + data.push_back(src); + } + int* dlen = (int*)PyArray_DATA(pydlen); + int* dlabels = (int*)PyArray_DATA(pylabel); + tfhandlerPtr_->start(data, dlen, dlabels); + } + + /** + * @brief Return one processed data. + * @param pytrg The processed image. + * @param pylabel The label of processed image. + */ + void get(PyObject* pytrg, PyObject* pylab) { + pyWritableCheck(pytrg); + pyWritableCheck(pylab); + pyContinuousCheck(pytrg); + pyContinuousCheck(pylab); + float* data = (float*)PyArray_DATA(pytrg); + int* label = (int*)PyArray_DATA(pylab); + tfhandlerPtr_->obtain(data, label); + } + + /** + * @brief An object of DataTransformer, which is used to call + * the image processing funtions. + */ + std::shared_ptr tfhandlerPtr_; + +private: + /** + * @brief Check whether the type of PyObject is valid or not. + */ + void pyTypeCheck(const PyObject* o) { + int typenum = PyArray_TYPE(o); + + // clang-format off + int type = + typenum == NPY_UBYTE ? CV_8U : + typenum == NPY_BYTE ? CV_8S : + typenum == NPY_USHORT ? CV_16U : + typenum == NPY_SHORT ? CV_16S : + typenum == NPY_INT || typenum == NPY_LONG ? CV_32S : + typenum == NPY_FLOAT ? CV_32F : + typenum == NPY_DOUBLE ? CV_64F : -1; + // clang-format on + + if (type < 0) { + LOG(FATAL) << "toMat: Data type = " << type << " is not supported"; + } + } + + /** + * @brief Check whether the PyObject is writable or not. + */ + void pyWritableCheck(PyObject* o) { CHECK(PyArray_ISWRITEABLE(o)); } + + /** + * @brief Check whether the PyObject is c-contiguous or not. + */ + void pyContinuousCheck(PyObject* o) { CHECK(PyArray_IS_C_CONTIGUOUS(o)); } +}; + +/** + * @brief Initialize the Python interpreter and numpy. + */ +static void initPython() { + Py_Initialize(); + PyOS_sighandler_t sighandler = PyOS_getsig(SIGINT); + import_array(); + PyOS_setsig(SIGINT, sighandler); +} + +/** + * Use Boost.Python to expose C++ interface to Python. + */ +BOOST_PYTHON_MODULE(DeJpeg) { + initPython(); + class_("DecodeJpeg", + init()) + .def("start", &DecodeJpeg::start) + .def("get", &DecodeJpeg::get); +}; diff --git a/python/paddle/utils/image_multiproc.py b/python/paddle/utils/image_multiproc.py new file mode 100644 index 0000000000..ccc0a531a7 --- /dev/null +++ b/python/paddle/utils/image_multiproc.py @@ -0,0 +1,170 @@ +import os, psutil +import cv2 +from paddle.utils.image_util import * +import multiprocessing +import subprocess, signal, sys + + +class CvImageTransfomer(ImageTransformer): + """ + CvImageTransfomer used python-opencv to process image. + """ + + def __init__(self, + min_size=None, + crop_size=None, + transpose=None, + channel_swap=None, + mean=None, + is_train=True, + is_color=True): + ImageTransformer.__init__(self, transpose, channel_swap, mean, is_color) + self.min_size = min_size + self.crop_size = crop_size + self.is_train = is_train + + def cv_resize_fixed_short_side(self, im, min_size): + row, col = im.shape[:2] + scale = min_size / float(min(row, col)) + if row < col: + row = min_size + col = int(round(col * scale)) + col = col if col > min_size else min_size + else: + col = min_size + row = int(round(row * scale)) + row = row if row > min_size else min_size + resized_size = row, col + im = cv2.resize(im, resized_size, interpolation=cv2.INTER_CUBIC) + return im + + def crop_img(self, im): + """ + Return cropped image. + The size of the cropped image is inner_size * inner_size. + im: (H x W x K) ndarrays + """ + row, col = im.shape[:2] + start_h, start_w = 0, 0 + if self.is_train: + start_h = np.random.randint(0, row - self.crop_size + 1) + start_w = np.random.randint(0, col - self.crop_size + 1) + else: + start_h = (row - self.crop_size) / 2 + start_w = (col - self.crop_size) / 2 + end_h, end_w = start_h + self.crop_size, start_w + self.crop_size + if self.is_color: + im = im[start_h:end_h, start_w:end_w, :] + else: + im = im[start_h:end_h, start_w:end_w] + if (self.is_train) and (np.random.randint(2) == 0): + if self.is_color: + im = im[:, ::-1, :] + else: + im = im[:, ::-1] + return im + + def transform(self, im): + im = self.cv_resize_fixed_short_side(im, self.min_size) + im = self.crop_img(im) + # transpose, swap channel, sub mean + im = im.astype('float32') + ImageTransformer.transformer(self, im) + return im + + def load_image_from_string(self, data): + flag = cv2.CV_LOAD_IMAGE_COLOR if self.is_color else cv2.CV_LOAD_IMAGE_GRAYSCALE + im = cv2.imdecode(np.fromstring(data, np.uint8), flag) + return im + + def transform_from_string(self, data): + im = self.load_image_from_string(data) + return self.transform(im) + + +class MultiProcessImageTransfomer(): + def __init__(self, + procnum=10, + capacity=10240, + min_size=None, + crop_size=None, + transpose=None, + channel_swap=None, + mean=None, + is_train=True, + is_color=True): + self.procnum = procnum + self.capacity = capacity + self.size = 0 + self.count = 0 + signal.signal(signal.SIGTERM, self.kill_child_processes) + self.fetch_queue = multiprocessing.Queue(maxsize=capacity) + self.cv_transformer = CvImageTransfomer(min_size, crop_size, transpose, + channel_swap, mean, is_train, + is_color) + + def __del__(self): + try: + for p in self.procs: + p.join() + except Exception as e: + print str(e) + + def reset(self, size): + self.size = size + self.count = 0 + self.procs = [] + + def run_proc(self, data, label): + dlen = len(label) + self.reset(dlen) + for i in xrange(self.procnum): + start = dlen * i / self.procnum + end = dlen * (i + 1) / self.procnum + proc = multiprocessing.Process( + target=self.batch_transfomer, + args=(data[start:end], label[start:end])) + proc.daemon = True + self.procs.append(proc) + for p in self.procs: + p.start() + + def get(self): + """ + Return one processed image. + """ + # block if necessary until an item is available + data, lab = self.fetch_queue.get(block=True) + self.count += 1 + if self.count == self.size: + try: + for p in self.procs: + p.join() + except Exception as e: + print str(e) + return data, lab + + def batch_transfomer(self, data, label): + """ + param data: input data in format of image string + type data: a list of string + label: the label of image + """ + for i in xrange(len(label)): + res = self.cv_transformer.transform_from_string(data[i]) + self.fetch_queue.put((res, int(label[i]))) + + def kill_child_processes(self, signum, frame): + """ + Kill a process's child processes in python. + """ + parent_id = os.getpid() + ps_command = subprocess.Popen( + "ps -o pid --ppid %d --noheaders" % parent_id, + shell=True, + stdout=subprocess.PIPE) + ps_output = ps_command.stdout.read() + retcode = ps_command.wait() + for pid_str in ps_output.strip().split("\n")[:-1]: + os.kill(int(pid_str), signal.SIGTERM) + sys.exit() diff --git a/python/paddle/utils/image_util.py b/python/paddle/utils/image_util.py index b5c6431c06..e6c6b04de0 100644 --- a/python/paddle/utils/image_util.py +++ b/python/paddle/utils/image_util.py @@ -186,29 +186,32 @@ class ImageTransformer: channel_swap=None, mean=None, is_color=True): - self.transpose = transpose - self.channel_swap = None - self.mean = None self.is_color = is_color + self.set_transpose(transpose) + self.set_channel_swap(channel_swap) + self.set_mean(mean) def set_transpose(self, order): - if self.is_color: - assert 3 == len(order) + if order is not None: + if self.is_color: + assert 3 == len(order) self.transpose = order def set_channel_swap(self, order): - if self.is_color: - assert 3 == len(order) + if order is not None: + if self.is_color: + assert 3 == len(order) self.channel_swap = order def set_mean(self, mean): - # mean value, may be one value per channel - if mean.ndim == 1: - mean = mean[:, np.newaxis, np.newaxis] - else: - # elementwise mean - if self.is_color: - assert len(mean.shape) == 3 + if mean is not None: + # mean value, may be one value per channel + if mean.ndim == 1: + mean = mean[:, np.newaxis, np.newaxis] + else: + # elementwise mean + if self.is_color: + assert len(mean.shape) == 3 self.mean = mean def transformer(self, data): -- GitLab From 1d1a04c033e9a63690b39be2573de4747c51f156 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 30 Nov 2016 13:52:48 +0800 Subject: [PATCH 0176/1503] follow comments on rectangle CNN --- paddle/gserver/layers/DataLayer.cpp | 9 +++++++-- python/paddle/trainer/config_parser.py | 7 +++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/paddle/gserver/layers/DataLayer.cpp b/paddle/gserver/layers/DataLayer.cpp index f6f4bf1d9c..67c4923036 100644 --- a/paddle/gserver/layers/DataLayer.cpp +++ b/paddle/gserver/layers/DataLayer.cpp @@ -49,8 +49,13 @@ void DataLayer::copyDataToOutput(Argument& output) { output.ids->copyFrom(*data_.ids); } } - output.setFrameHeight(config_.height()); - output.setFrameWidth(config_.width()); + if (config_.height() && config_.width()) { + output.setFrameHeight(config_.height()); + output.setFrameWidth(config_.width()); + } else { + output.setFrameHeight(data_.getFrameHeight()); + output.setFrameHeight(data_.getFrameHeight()); + } output.cpuSequenceDims = data_.cpuSequenceDims; output.sequenceStartPositions = data_.sequenceStartPositions; output.subSequenceStartPositions = data_.subSequenceStartPositions; diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index f917ead680..0f4839e831 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1931,10 +1931,9 @@ class MaxOutLayer(LayerBase): input_layer = self.get_input_layer(0) maxout_conf = self.config.inputs[0].maxout_conf parse_maxout(self.inputs[0].maxout, input_layer.name, maxout_conf) - self.set_layer_size(g_layer_map[input_layer.name].size / - maxout_conf.groups) - self.set_layer_height_width(g_layer_map[input_layer.name].height, - g_layer_map[input_layer.name].width) + out_channels = maxout_conf.image_conf.channels / maxout_conf.groups + self.set_cnn_layer(name, g_layer_map[input_layer.name].height, + g_layer_map[input_layer.name].width, out_channels) # key: cost type -- GitLab From a342d130cc2f4cc6ad0ee8e1c330e2f4689fcc4f Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 30 Nov 2016 15:27:23 +0800 Subject: [PATCH 0177/1503] remove build of doxyfile --- doc/CMakeLists.txt | 19 +- doc/Doxyfile.in | 2384 -------------------------------------------- doc/conf.py.in | 15 - 3 files changed, 2 insertions(+), 2416 deletions(-) delete mode 100644 doc/Doxyfile.in diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index ef4e9d102d..efcf8b0ad3 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -15,25 +15,11 @@ set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees") # HTML output directory set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html") - -set(PADDLE_DOXYGEN_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/doxygen_xml") - configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" "${BINARY_BUILD_DIR}/conf.py" @ONLY) -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" - "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" - @ONLY - ) - -add_custom_target(paddle_doxygen_docs ALL - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} -) - sphinx_add_target(paddle_docs html ${BINARY_BUILD_DIR} @@ -41,6 +27,5 @@ sphinx_add_target(paddle_docs ${CMAKE_CURRENT_SOURCE_DIR} ${SPHINX_HTML_DIR}) -add_dependencies(paddle_docs - gen_proto_py - paddle_doxygen_docs) +add_dependencies(paddle_docs + gen_proto_py) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in deleted file mode 100644 index a1fc380192..0000000000 --- a/doc/Doxyfile.in +++ /dev/null @@ -1,2384 +0,0 @@ -# Doxyfile 1.8.10 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "paddle" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = 1.0.0 - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = @PADDLE_DOXYGEN_OUTPUT@ - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 2 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = NO - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = NO - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = @PROJ_ROOT@/paddle - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, -# *.vhdl, *.ucf, *.qsf, *.as and *.js. - -FILE_PATTERNS = *.c *.cc *.cpp *.cu *.h *.hpp *.cuh *.ph - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = */x86_64-scm-linux-gnu/* */internals/* */mkl/* */test/* */tests/* */platform/* - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = NO - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /

    *&`6 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

    4p19mkErxY7c=R@>od>0oE2Rk>F)~t=J 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 $9f`6 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 literal 0 HcmV?d00001 -- GitLab From 9b9f8a97c2a13d7b02f4f69e537609364593ce88 Mon Sep 17 00:00:00 2001 From: westeast <510578774@qq.com> Date: Tue, 15 Nov 2016 21:08:31 +0800 Subject: [PATCH 0032/1503] sentiment analysis translation --- doc_cn/demo/index.rst | 2 +- doc_cn/demo/sentiment_analysis/index.rst | 8 + .../sentiment_analysis/sentiment_analysis.md | 324 ++++++++++++++++++ 3 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 doc_cn/demo/sentiment_analysis/index.rst create mode 100644 doc_cn/demo/sentiment_analysis/sentiment_analysis.md diff --git a/doc_cn/demo/index.rst b/doc_cn/demo/index.rst index 71f54bc18f..e15e839f93 100644 --- a/doc_cn/demo/index.rst +++ b/doc_cn/demo/index.rst @@ -9,7 +9,7 @@ 自然语言处理 '''''''''''' -* `情感分析 <../../doc/demo/sentiment_analysis/index.html>`_ +* `情感分析 `_ * `文本生成 <../../doc/demo/text_generation/index.html>`_ * `词性标注 <../../doc/demo/semantic_role_labeling/index.html>`_ diff --git a/doc_cn/demo/sentiment_analysis/index.rst b/doc_cn/demo/sentiment_analysis/index.rst new file mode 100644 index 0000000000..82400b2459 --- /dev/null +++ b/doc_cn/demo/sentiment_analysis/index.rst @@ -0,0 +1,8 @@ +情感分析教程 +=========================== + +.. toctree:: + :maxdepth: 3 + :glob: + + Training Locally \ No newline at end of file diff --git a/doc_cn/demo/sentiment_analysis/sentiment_analysis.md b/doc_cn/demo/sentiment_analysis/sentiment_analysis.md new file mode 100644 index 0000000000..b70f2d5967 --- /dev/null +++ b/doc_cn/demo/sentiment_analysis/sentiment_analysis.md @@ -0,0 +1,324 @@ +# 情感分析教程 + +情感分析有许多应用场景。 一个基本的应用场景是区分给定文本的褒贬两极性,给定的文本可以是一个文档、句子、或者是一个小的文本片段。 一个简单的例子如:把用户在购物网站、旅游网站、团购网站(亚马逊、天猫、淘宝等)上发表的评论分成正面评论和负面评论两类。 + +情感分析也常用于基于大量评论和个人博客来监控社会媒体。 例如,研究人员分析了几个关于消费者信心和政治观点的调查,结果发现它们与同时期的Twitter消息中的情绪词频率相关 [1]。 另一个例子是通过分析每日Twitter博客的文本内容来预测股票变动 [2]。 + +另一方面,抓取产品的用户评论并分析他们的情感,有助于理解用户对不同公司,不同产品,甚至不同竞争对手产品的偏好。 + +本教程将指导您完成长期短期记忆(LSTM)网络的训练过程,以分类来自[大型电影评论数据集](http://ai.stanford.edu/~amaas/data/sentiment/)(有时称为[互联网电影数据库 (IMDB)](http://ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf))的句子的情感 。 此数据集包含电影评论及其相关联的类别标签,即正面和负面。 + +## 数椐准备 + +### IMDB 数椐介绍 + +训练模型之前, 我们需要预处理数椐并构建一个字典。 首先, 你可以使用下面的脚本下载 IMDB 数椐集和[Moses](http://www.statmt.org/moses/)工具, 这是一个基于统计的机器翻译系统. 我们提供了一个数据预处理脚本,它不仅能够处理IMDB数据,还能处理其他用户自定义的数据。 为了使用提前编写的脚本,需要将标记的训练和测试样本移动到另一个路径,这已经在`get_imdb.sh`中完成。 + +``` +cd demo/sentiment/data +./get_imdb.sh +``` +如果数椐获取成功,你将在目录```./demo/sentiment/data```中看到下面的文件: + +``` +aclImdb get_imdb.sh imdb mosesdecoder-master +``` + +* aclImdb: 从外部网站上下载的原始数椐集。 +* imdb: 仅包含训练和测试数椐集。 +* mosesdecoder-master: Moses 工具。 + +IMDB数据集包含25,000个已标注过的高极性电影评论用于训练,25,000个用于测试。负面的评论的得分小于等于4,正面的评论的得大于等于7,总评分10分。 运行完脚本 `./get_imdb.sh`后, 我们可以看到在目录 `aclImdb`中的数椐集的结构如下: + +``` +imdbEr.txt imdb.vocab README test train +``` +* train: 训练数椐集。 +* test : 测试数椐集。 +* imdb.vocab: 字典文件。 +* imdbEr.txt: 字典imdb.vocab中每个切分单词的预期评级。 +* README: 数椐说明文档。 + +测试集和训练集目录包含下面的文件: + +``` +labeledBow.feat neg pos unsup unsupBow.feat urls_neg.txt urls_pos.txt urls_unsup.txt +``` + +* pos: 正面评价样本,包含12,500个txt文件,每个文件是一个电影评论。 +* neg: 负面评价样本,包含12,500个txt文件,每个文件是一个电影评论。 +* unsup: 未标记的评价样本,包含50,000个txt文件。 +* urls_xx.txt: 每个评论的网址。 +* xxBow.feat: 用于统计词频的Bow模型特征。 + +### IMDB 数椐准备 + +在这个例子中,我们只使用已经标注过的训练集和测试集,且默认在训练集上构建字典,而不使用IMDB数椐集中的imdb.vocab做为字典。训练集已经做了随机打乱排序而测试集没有。 Moses 工具中的脚本`tokenizer.perl` 用于切分单单词和标点符号。执行下面的命令就可以预处理数椐。 + +``` +cd demo/sentiment/ +./preprocess.sh +``` +preprocess.sh: + +``` +data_dir="./data/imdb" +python preprocess.py -i data_dir +``` + +* data_dir: 输入数椐所在目录。 +* preprocess.py: 预处理脚本。 + +运行成功后目录`demo/sentiment/data/pre-imdb` 结构如下: + +``` +dict.txt labels.list test.list test_part_000 train.list train_part_000 +``` +* test\_part\_000 and train\_part\_000: 所有标记的测试集和训练集, 训练集已经随机打乱。 +* train.list and test.list: 训练集和测试集文件列表。 +* dict.txt: 利用训练集生成的字典。 +* labels.txt: neg 0, pos 1, 含义:标签0表示负面的评论,标签1表示正面的评论。 + +### 用户自定义数椐预处理 + +如果你执行其它的用情感分析来分类文本的任务,可以按如下的结构来准备数椐. 我们提供了脚本来构建字典和预处理数椐。所以你只用按下面的结构来组织数椐就行了。 + +``` +dataset +|----train +| |----class1 +| | |----text_files +| |----class2 +| | |----text_files +| | ... +|----test +| |----class1 +| | |----text_files +| |----class2 +| | |----text_files +| | ... +``` +* dataset: 一级目录。 +* train, test: 二级目录。 +* class1,class2,...: 三级目录。 +* text_files: 文本格式的实例文件。 + +所有同目录下的文本实例文件都是同级别的。 每个文本文件包含一个或者多个实例,每一行表示一个实例。 为了充分的随机打乱训练集, 在预处理含有多行数椐的文本文件时参数设置稍有不同, 执行`preprocess.sh`脚本时需要加上`-m True`参数。 tokenizer.perl 默认用来切分单记和标点符号,如果你不需要这个操作,在运行`preprocess.sh`时加上`-t False`参数即可。 + +## 训练模型 + +在这步任务中,我们使用了循环神经网络(RNN)的 LSTM 架构来训练情感分析模型。 引入LSTM模型主要是为了克服消失梯度的问题。 LSTM网络类似于具有隐藏层的标准循环神经网络, 但是隐藏层中的每个普通节点被一个记忆单元替换。 每个记忆单元包含四个主要的元素: 输入门, 具有自循环连接的神经元,忘记门和输出门。 更多的细节可以在文献中找到[4]。 LSTM架构的最大优点是它可以在长时间间隔内记忆信息,而没有短时记忆的损失。在有新的单词来临的每一个时间步骤内,存储在记忆单元区块的历史信息被更新用来迭代的学习单词以合理的序列程现。 + +