Skip to content

Commit

Permalink
[Faster Transformer] based on new custom op (PaddlePaddle#208)
Browse files Browse the repository at this point in the history
* update

* add python forward

* inference part update

* replace old version custom op

* faster is all you need

* dir update

* delete useless code

* update readme

* update

* add cmake dir

* update according to comments

* update demo

* update readme

* add core_avx.so

* demo update

* add return numpy

* add ndebug

* update cmake

* rename ext_op to ops

* clear helper.h

* clear helper.h

* update dir
  • Loading branch information
FrostML authored Apr 16, 2021
1 parent 0e37041 commit ddf5d36
Show file tree
Hide file tree
Showing 25 changed files with 1,830 additions and 656 deletions.
146 changes: 137 additions & 9 deletions examples/machine_translation/transformer/faster_transformer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 使用环境说明

* 本项目依赖于 PaddlePaddle 2.0.1 及以上版本或适当的 develop 版本
* 本项目依赖于 PaddlePaddle 最新的 develop 版本,可能需要自行编译 PaddlePaddle
* CMake >= 3.10
* CUDA 10.1(需要 PaddlePaddle 框架一致)
* gcc 版本需要与编译 PaddlePaddle 版本一致,比如使用 gcc8.2
Expand All @@ -19,13 +19,18 @@

## 快速开始

我们实现了基于 GPU 的 Faster Transformer 的自定义 op 的接入。接下来,我们将分别介绍基于 Python 动态图和预测库使用 Faster Transformer 自定义 op 的方式,包括 op 的编译与使用。

## Python 动态图使用自定义 op

### 编译自定义OP

自定义 OP 需要将实现的 C++、CUDA 代码编译成动态库,我们提供对应的 CMakeLists.txt ,可以参考使用如下的方式完成编译。同样的自定义 op 编译的说明也可以在自定义 op 对应的路径 `PaddleNLP/paddlenlp/ext_op/` 下面找到。
在 Python 动态图下使用自定义 OP 需要将实现的 C++、CUDA 代码编译成动态库,我们已经提供对应的 CMakeLists.txt ,可以参考使用如下的方式完成编译。同样的自定义 op 编译的说明也可以在自定义 op 对应的路径 `PaddleNLP/paddlenlp/ops/` 下面找到。

#### 克隆 PaddleNLP

首先,因为需要基于当前环境重新编译,当前的 paddlenlp 的 python 包里面并不包含 Faster Transformer 相关 lib,需要克隆一个 PaddleNLP,并重新编译:

``` sh
git clone https://github.com/PaddlePaddle/PaddleNLP.git
```
Expand All @@ -34,12 +39,12 @@ git clone https://github.com/PaddlePaddle/PaddleNLP.git

``` sh
export PYTHONPATH=$PWD/PaddleNLP/:$PYTHONPATH
cd PaddleNLP/paddlenlp/ext_op/
cd PaddleNLP/paddlenlp/ops/
```

#### 编译

编译之前,请确保安装的 PaddlePaddle 的版本需要大于 2.0.1,并且正常可用。
编译之前,请确保安装的 PaddlePaddle 的版本是基于最新的 develop 分支的代码编译,并且正常可用。

编译自定义 OP 可以参照一下步骤:

Expand All @@ -62,7 +67,7 @@ cd ../
举例如下:

``` python
from paddlenlp.ext_op import FasterTransformer
from paddlenlp.ops import FasterTransformer
transformer = FasterTransformer(
src_vocab_size=args.src_vocab_size,
Expand Down Expand Up @@ -120,9 +125,9 @@ tar -zxf tranformer-base-wmt_ende_bpe.tar.gz
# setting visible devices for prediction
export CUDA_VISIBLE_DEVICES=0
export FLAGS_fraction_of_gpu_memory_to_use=0.1
cp -rf ../../../../paddlenlp/ext_op/build/third-party/build/bin/decoding_gemm ./
cp -rf ../../../../paddlenlp/ops/build/third-party/build/bin/decoding_gemm ./
./decoding_gemm 8 4 8 64 38512 32 512 0
python encoder_decoding_predict.py --config ../configs/transformer.base.yaml --decoding-lib ../../../../paddlenlp/ext_op/build/lib/libdecoding_op.so
python encoder_decoding_predict.py --config ../configs/transformer.base.yaml --decoding-lib ../../../../paddlenlp/ops/build/lib/libdecoding_op.so
```
其中,`--config` 选项用于指明配置文件的位置,而 `--decoding-lib` 选项用于指明编译好的 Faster Transformer decoding lib 的位置。
Expand All @@ -138,9 +143,9 @@ float16 与 float32 预测的基本流程相同,不过在使用 float16 的 de
# setting visible devices for prediction
export CUDA_VISIBLE_DEVICES=0
export FLAGS_fraction_of_gpu_memory_to_use=0.1
cp -rf ../../../../paddlenlp/ext_op/build/third-party/build/bin/decoding_gemm ./
cp -rf ../../../../paddlenlp/ops/build/third-party/build/bin/decoding_gemm ./
./decoding_gemm 8 4 8 64 38512 32 512 1
python encoder_decoding_predict.py --config ../configs/transformer.base.yaml --decoding-lib ../../../../paddlenlp/ext_op/build/lib/libdecoding_op.so --use-fp16-decoding
python encoder_decoding_predict.py --config ../configs/transformer.base.yaml --decoding-lib ../../../../paddlenlp/ops/build/lib/libdecoding_op.so --use-fp16-decoding
```
其中,`--config` 选项用于指明配置文件的位置,而 `--decoding-lib` 选项用于指明编译好的 Faster Transformer decoding lib 的位置。
Expand All @@ -166,3 +171,126 @@ perl mosesdecoder/scripts/generic/multi-bleu.perl ~/.paddlenlp/datasets/WMT14end
```
BLEU = 26.89, 58.4/32.6/20.5/13.4 (BP=1.000, ratio=1.010, hyp_len=65166, ref_len=64506)
```
## C++ 预测库使用自定义 op
### 编译自定义OP
在 C++ 预测库使用自定义 OP 需要将实现的 C++、CUDA 代码**以及 C++ 预测的 demo**编译成一个可执行文件。因预测库支持自定义 op 方式与 Python 不同,这个过程将不会产生自定义 op 的动态库,将直接得到可执行文件。我们已经提供对应的 CMakeLists.txt ,可以参考使用如下的方式完成编译。并获取执行 demo。
#### 克隆 PaddleNLP
首先,仍然是需要克隆一个 PaddleNLP:
``` sh
git clone https://github.com/PaddlePaddle/PaddleNLP.git
```
其次,配置环境变量,让我们可以使用当前 clone 的 paddlenlp,并进入到自定义 OP 的路径,准备后续的编译操作:
``` sh
export PYTHONPATH=$PWD/PaddleNLP/:$PYTHONPATH
cd PaddleNLP/paddlenlp/ops/
```
#### 编译
编译之前,请确保安装的 PaddlePaddle 预测库的版本是基于最新的 develop 分支的代码编译,并且正常可用。
编译自定义 OP 可以参照以下步骤:
``` sh
mkdir build
cd build/
cmake .. -DSM=xx -DCMAKE_BUILD_TYPE=Release -DPADDLE_LIB=/path/to/paddle_inference_lib/ -DDEMO=./demo/transformer_e2e.cc -DWITH_STATIC_LIB=OFF -DON_INFER=ON
make -j
cd ../
```
注意:
* `xx` 是指的所用 GPU 的 compute capability。举例来说,可以将之指定为 70(V100) 或是 75(T4)。
* `-DPADDLE_LIB` 需要指明使用的 PaddlePaddle 预测库的路径 `/path/to/paddle_inference_install_dir/`,并且在该路径下,预测库的组织结构满足:
```text
.
├── CMakeCache.txt
├── paddle/
├── include/
└── lib/
├── third_party/
├── cudaerror/
├── install/
└── threadpool/
└── version.txt
```
* `-DDEMO` 说明预测库使用 demo 的位置。
* **当使用预测库的自定义 op 的时候,请务必开启 `-DON_INFER=ON` 选项,否则,不会得到预测库的可执行文件。**
编译完成后,在 `build/bin/` 路径下将会看到 `transformer_e2e` 的一个可执行文件。通过设置对应的设置参数完成执行的过程。
``` sh
cd bin/
./transformer_e2e <batch_size> <gpu_id> <model_directory> <dict_directory> <input_data>
```
### 导出基于 Faster Transformer 自定义 op 的预测库可使用模型文件
我们提供一个已经基于动态图训练好的 base model 的 checkpoint 以供使用,当前 checkpoint 是基于 WMT 英德翻译的任务训练。可以通过[tranformer-base-wmt_ende_bpe](https://paddlenlp.bj.bcebos.com/models/transformers/transformer/tranformer-base-wmt_ende_bpe.tar.gz)下载。
使用 C++ 预测库,首先,我们需要做的是将动态图的 checkpoint 导出成预测库能使用的模型文件和参数文件。可以执行 `export_model.py` 实现这个过程。
``` python
python export_model.py --config ../configs/transformer.base.yaml --decoding-lib ../../../../paddlenlp/ops/src/build/lib/libdecoding_op.so
```
注意:这里的 `libdecoding_op.so` 的动态库是参照前文 **`Python 动态图使用自定义 op`** 编译出来的 lib,当前 **`C++ 预测库使用自定义 op`** 不包含编译的动态库。因此,如果在使用预测库前,还需要额外导出模型,需要编译两次:
* 一次用于获取 Python 动态图下的 lib,用到 Python 端进行模型导出。
* 一次获取编译的基于预测库的可执行文件
执行 `export_model.py` 之后,可以在当前路径的 `infer_model/` 下面看到导出的模型文件:
```text
└── infer_model/
├── transformer.pdiparams
└── transformer.pdmodel
```
### 使用 PaddlePaddle 预测库预测
自定义 op 编译完成后,在 `paddlenlp/ops/build/bin/` 路径下将会看到 `transformer_e2e` 的一个可执行文件。通过设置对应的设置参数完成执行的过程。
``` sh
cd bin/
./transformer_e2e <batch_size> <gpu_id> <model_directory> <dict_directory> <input_data>
```
这里的 `<model_directory>` 即是上文说到导出的 paddle inference 模型。
举例说明:
``` sh
cd bin/
../third-party/build/bin/decoding_gemm 8 5 8 64 38512 256 512 0
./transformer_e2e 8 0 ./infer_model/ /root/.paddlenlp/datasets/WMT14ende/WMT14.en-de/wmt14_ende_data_bpe/vocab_all.bpe.33708 /root/.paddlenlp/datasets/WMT14ende/WMT14.en-de/wmt14_ende_data_bpe/newstest2014.tok.bpe.33708.en
```
其中,`decoding_gemm` 不同参数的意义可以参考 [FasterTransformer 文档](https://github.com/NVIDIA/DeepLearningExamples/tree/master/FasterTransformer/v3.1#execute-the-decoderdecoding-demos)。
## 模型评估
预测完成之后,会生成一份 `predict.txt` 的文件,记录了本次英德翻译的预测结果,可以使用下面的方式进行 BLEU 计算。
评估方式与动态图评估方式相同,预测结果中每行输出是对应行输入的得分最高的翻译,对于使用 BPE 的数据,预测出的翻译结果也将是 BPE 表示的数据,要还原成原始的数据(这里指 tokenize 后的数据)才能进行正确的评估。评估过程具体如下(BLEU 是翻译任务常用的自动评估方法指标):
``` sh
# 还原 predict.txt 中的预测结果为 tokenize 后的数据
sed -r 's/(@@ )|(@@ ?$)//g' predict.txt > predict.tok.txt
# 若无 BLEU 评估工具,需先进行下载
git clone https://github.com/moses-smt/mosesdecoder.git
# 以英德翻译 newstest2014 测试数据为例
perl mosesdecoder/scripts/generic/multi-bleu.perl ~/.paddlenlp/datasets/WMT14ende/WMT14.en-de/wmt14_ende_data/newstest2014.tok.de < predict.tok.txt
```
执行上述操作之后,可以看到类似如下的结果,此处结果是 base model 在 newstest2014 上的 BLEU 结果:
```
BLEU = 26.89, 58.4/32.6/20.5/13.4 (BP=1.000, ratio=1.010, hyp_len=65166, ref_len=64506)
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from paddlenlp.transformers import TransformerModel
from paddlenlp.transformers import position_encoding_init
from paddlenlp.ext_op import FasterTransformer
from paddlenlp.ops import FasterTransformer

sys.path.append("../")
import reader
Expand All @@ -29,7 +29,7 @@ def parse_args():
help="Path of the config file. ")
parser.add_argument(
"--decoding-lib",
default="../../../../paddlenlp/ext_op/build/lib/libdecoding_op.so",
default="../../../../paddlenlp/ops/build/lib/libdecoding_op.so",
type=str,
help="Path of libdecoding_op.so. ")
parser.add_argument(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import sys
import os
import numpy as np
from attrdict import AttrDict
import argparse
import time

import paddle
import paddle.nn as nn
import paddle.nn.functional as F

import yaml
from pprint import pprint

from paddlenlp.transformers import TransformerModel
from paddlenlp.transformers import position_encoding_init
from paddlenlp.ops import FasterTransformer

sys.path.append("../")
import reader


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--config",
default="../configs/transformer.base.yaml",
type=str,
help="Path of the config file. ")
parser.add_argument(
"--decoding-lib",
default="../../../../paddlenlp/ops/src/build/lib/libdecoding_op.so",
type=str,
help="Path of libdecoding_op.so. ")
parser.add_argument(
"--use-fp16-decoding",
action="store_true",
help="Whether to use fp16 decoding to predict. ")
args = parser.parse_args()
return args


def do_predict(args):
paddle.enable_static()
place = "gpu"
place = paddle.set_device(place)

test_program = paddle.static.Program()
startup_program = paddle.static.Program()
with paddle.static.program_guard(test_program, startup_program):
src_word = paddle.static.data(
name="src_word", shape=[None, None], dtype="int64")

# Define model
transformer = FasterTransformer(
src_vocab_size=args.src_vocab_size,
trg_vocab_size=args.trg_vocab_size,
max_length=args.max_length + 1,
n_layer=args.n_layer,
n_head=args.n_head,
d_model=args.d_model,
d_inner_hid=args.d_inner_hid,
dropout=args.dropout,
weight_sharing=args.weight_sharing,
bos_id=args.bos_idx,
eos_id=args.eos_idx,
decoding_strategy="beam_search",
beam_size=args.beam_size,
max_out_len=args.max_out_len,
decoding_lib=args.decoding_lib,
use_fp16_decoding=args.use_fp16_decoding)

finished_seq = transformer(src_word=src_word)

test_program = test_program.clone(for_test=True)

exe = paddle.static.Executor(place)
exe.run(startup_program)

# Load checkpoint.
transformer.export_params(
init_from_params=os.path.join(args.init_from_params,
"transformer.pdparams"),
place=place)

paddle.static.save_inference_model(
os.path.join(args.inference_model_dir, "transformer"),
feed_vars=src_word,
fetch_vars=finished_seq,
executor=exe,
program=test_program)


if __name__ == "__main__":
ARGS = parse_args()
yaml_file = ARGS.config
with open(yaml_file, 'rt') as f:
args = AttrDict(yaml.safe_load(f))
pprint(args)
args.decoding_lib = ARGS.decoding_lib
args.use_fp16_decoding = ARGS.use_fp16_decoding

do_predict(args)
2 changes: 1 addition & 1 deletion paddlenlp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from . import data
from . import datasets
from . import embeddings
from . import ext_op
from . import ops
from . import layers
from . import metrics
from . import models
Expand Down
23 changes: 0 additions & 23 deletions paddlenlp/ext_op/src/CMakeLists.txt

This file was deleted.

Loading

0 comments on commit ddf5d36

Please sign in to comment.