# Ernie 3.0 百亿参数模型部署案例
- [1.任务介绍](#1)
- [2.运行环境说明](#2)
- [3.部署步骤](#3)
- [3.1 使用 Docker 镜像](#3.1)
- [3.2 下载 Serving 离线 Wheel 部署包](#3.2)
- [3.3 下载 output.tar.gz 部署包](#3.3)
- [3.4 下载百亿参数问答模型](#3.4)
- [3.5 启动服务](#3.5)
- [3.6 发送客户端请求](#3.6)
- [3.7 关闭服务](#3.7)
- [4.编译方法](#4)
- [4.1 编译 Paddle](#4.1)
- [4.2 编译 Serving](#4.2)
## 1.任务介绍
问答任务(Question-Answering)介绍:是自然语言处理 (NLP) 中一种业务场景,问答任务模型可构建自动回答以自然语言提出的问题。通常,通过查询知识或信息的结构化数据库来构建答案,也可以从非结构化的自然语言文档集合中提取答案。
## 2.运行环境说明
要求:
- 模型大小:20 GB
- 显卡要求:有单机4卡或多机4卡
- 显存要求:12 GB 级以上
- 计算环境:CUDA >= 11.0, cuDNN > 8
- Paddle版本:基于 Paddle 2.3 分支开启多个编译选项,详见 ##4.联编方法
- Serving版本:使用Serving v0.9.0 或 develop 分支联编 Paddle 2.3,详见 ##4.联编方法
## 3.部署步骤:
部署百亿参数模型与部署常规模型相比,大部分步骤是相同的,在启动服务和Client使用上有差异,具体步骤如下:
1. 使用 Docker 镜像
2. 下载 Serving 离线 Wheel 部署包
3. 下载 output.tar.gz 部署包
4. 下载百亿模型
5. 启动服务
6. 发送Client请求
7. 关闭服务
### 3.1 使用 Docker 镜像
首先,拉取 CUDA 11.2 serving 镜像,启动并进入镜像,创建work目录并进入目录。
```shell
docker pull registry.baidubce.com/paddlepaddle/serving:0.9.0-cuda11.2-cudnn8-devel
nvidia-docker run --name serving_dist_demo --network=host -it registry.baidubce.com/paddlepaddle/serving:0.9.0-cuda11.2-cudnn8-devel bash
λ yq01-inf-hic-k8s-a100-aa24-0087 /home mkdir -p work && cd work
```
### 3.2 下载 Serving 离线 Wheel 部署包
安装特定版本 paddle_serving_server 和 paddle_serving_client。特别提示,此版本与 v0.9.0 发布版不同,差异点是集成联编特定选项的 Paddle 推理库,联编方法详见 ##4.联编方法。
```shell
wget https://paddle-serving.bj.bcebos.com/distributed_demos/py37_offline_whl_packages.tar
tar xvf py37_offline_whl_packages.tar
cd py37_offline_whl_packages
## 安装 特定版本 Serving
python3.7 install_local_wheels.py --whl_path paddle_serving_server_gpu-0.0.0.post112-py3-none-any.whl --dep_whl_path py37_offline_whls/serving_dependent_wheels/
python3.7 install_local_wheels.py --whl_path paddle_serving_client-0.0.0-cp37-none-any.whl --dep_whl_path py37_offline_whls/serving_dependent_wheels
## 安装 特定版本 Paddle 2.3
pip3.7 install paddlepaddle-gpu==2.3.0.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html
## 安装 依赖库
pip3.7 install sentencepiece
```
### 3.3 下载 output.tar.gz 部署包
output.tar.gz 部署包包含所有依赖库、代码和运行脚本。进入 output 目录后,运行 get_models.sh 下载百亿模型
```shell
wget https://paddle-serving.bj.bcebos.com/distributed_demos/output.tar.gz
tar zxvf output.tar.gz
cd output
```
目录结构如下:
```
drwxr-xr-x 10 root root 4.0K May 27 08:46 ./
drwxr-xr-x 4 root root 4.0K May 27 08:51 ../
-rw-r--r-- 1 root root 3.0K May 18 03:36 batching.py
drwxr-xr-x 2 root root 4.0K May 17 03:23 bin/
-rw-r--r-- 1 root root 64 May 17 08:38 config_pp1mp4.csv
drwxr-xr-x 2 root root 4.0K May 27 07:14 data/
-rw-r--r-- 1 root root 66 May 17 14:25 dev.txt
drwxr-xr-x 2 root root 4.0K May 27 06:59 dict/
-rw-r--r-- 1 root root 16K May 18 04:06 finetune_args.py
-rwxrwxrwx 1 root root 556 May 27 08:23 get_models.sh*
drwxr-xr-x 2 root root 4.0K May 23 06:25 lib/
-rw-r--r-- 1 root root 2.3M May 20 12:07 libcrypto.so.1.0.0
-rw-r--r-- 1 root root 417K May 20 12:07 libssl.so.1.0.0
-rw-r--r-- 1 root root 277 May 23 06:44 model_conf
drwxr-xr-x 4 root root 4.0K May 17 03:40 nccl_2.3.5/
-rw-r--r-- 1 root root 584 May 27 08:43 ProcessInfo.json
-rw-r--r-- 1 root root 0 May 18 03:37 ps_usr_print_log
drwxr-xr-x 2 root root 4.0K May 18 04:07 __pycache__/
drwxr-xr-x 3 root root 4.0K May 27 07:28 reader/
-rwxr-xr-x 1 root root 1.4K May 17 14:12 start_master_serving.sh*
-rwxrwxrwx 1 root root 195 May 18 02:52 start.sh*
-rwxr-xr-x 1 root root 1.9K May 23 06:43 start_slave_serving.sh*
-rwxrwxrwx 1 root root 281 May 17 13:19 stop.sh*
-rw-r--r-- 1 root root 7.2K May 27 07:29 test_client_while.py
-rw-r--r-- 1 root root 17K May 18 03:36 tokenization.py
drwxr-xr-x 3 root root 4.0K May 17 14:18 utils/
```
### 3.4 下载百亿参数问答模型
运行命令下载模型,建议提前关闭 HTTP 代理。
```
unset http_proxy
unset https_proxy
sh get_models.sh
```
下载模型到 serving_model_pp1mp4 目录,共有12个目录,28个文件。模型已保存出带有feed/fetch 信息的prototxt文件。
```
serving_model_pp1mp4/
├── rank_0
│ ├── serving_client
│ │ ├── serving_client_conf.prototxt
│ │ └── serving_client_conf.stream.prototxt
│ └── serving_server
│ ├── fluid_time_file
│ ├── serving_server_conf.prototxt
│ ├── serving_server_conf.stream.prototxt
│ ├── step_0.pdiparams
│ └── step_0.pdmodel
├── rank_1
│ ├── serving_client
│ │ ├── serving_client_conf.prototxt
│ │ └── serving_client_conf.stream.prototxt
│ └── serving_server
│ ├── fluid_time_file
│ ├── serving_server_conf.prototxt
│ ├── serving_server_conf.stream.prototxt
│ ├── step_0.pdiparams
│ └── step_0.pdmodel
├── rank_2
│ ├── serving_client
│ │ ├── serving_client_conf.prototxt
│ │ └── serving_client_conf.stream.prototxt
│ └── serving_server
│ ├── fluid_time_file
│ ├── serving_server_conf.prototxt
│ ├── serving_server_conf.stream.prototxt
│ ├── step_0.pdiparams
│ └── step_0.pdmodel
└── rank_3
├── serving_client
│ ├── serving_client_conf.prototxt
│ └── serving_client_conf.stream.prototxt
└── serving_server
├── fluid_time_file
├── serving_server_conf.prototxt
├── serving_server_conf.stream.prototxt
├── step_0.pdiparams
└── step_0.pdmodel
12 directories, 28 files
```
### 3.5 启动服务
运行如下命令后台启动 1个 Master Serving 进程 和 4 个 Worker Serving 进程。
```shell
bash start.sh
```
每个 Worker Serving 的启动目录在 work_dir/worker_0/1/2/3 目录下,启动信息输出到 slave_start.log 日志中。Master Serving 在当前目录启动,启动日志信息在 log/master_start.log 日志中。
```
-------------------------------------------
cvs_file: ../../config_pp1mp4.csv
model_path: ../../serving_model_pp1mp4/rank_0/serving_server/
dist_subgraph_idx: 0
serving_port: 9001
work dir: work_dir/worker_0
Starting 0 slave serving backgroud...
Starting done. start log is work_dir/worker_0/slave_start.log
------------------------------------------------------------
cvs_file: ../../config_pp1mp4.csv
model_path: ../../serving_model_pp1mp4/rank_1/serving_server/
dist_subgraph_idx: 1
serving_port: 9002
work dir: work_dir/worker_1
Starting 1 slave serving backgroud...
Starting done. start log is work_dir/worker_1/slave_start.log
------------------------------------------------------------
cvs_file: ../../config_pp1mp4.csv
model_path: ../../serving_model_pp1mp4/rank_2/serving_server/
dist_subgraph_idx: 2
serving_port: 9003
work dir: work_dir/worker_2
Starting 2 slave serving backgroud...
Starting done. start log is work_dir/worker_2/slave_start.log
------------------------------------------------------------
cvs_file: ../../config_pp1mp4.csv
model_path: ../../serving_model_pp1mp4/rank_3/serving_server/
dist_subgraph_idx: 3
serving_port: 9004
work dir: work_dir/worker_3
Starting 3 slave serving backgroud...
Starting done. start log is work_dir/worker_3/slave_start.log
------------------------------------------------------------
-------------------------------------------
SERVING_MASTER_PORT: 2003
SERVING_SLAVE_PORTS: 4
slave address: 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004
-------------------------------------------------------------
Starting master serving backgroud...
Starting done. start log is log/master_start.log
```
以上打印信息表示 Master Serving 和 4个 Worker Serving 启动配置信息,服务已处于启动状态中,需要等待。
由于模型较大加载时间较长,并且 Worker Serving 之间还需要通讯初始化,当所有 Worker 服务启动完成时才能对外提供服务。
当 Worker Serving 的 `serving.Info`日志中出现`C++ Serving service started successfully!`日志表示 Worker Serving 启动成功,可以对外提供服务。
```
I0527 09:06:55.883172 1155 analysis_predictor.cc:1007] ======= optimize end =======
I0527 09:06:55.892191 1155 gen_comm_id_helper.cc:205] Server listening on: 127.0.0.1:9993 successful.
W0527 09:07:39.848902 1155 gpu_context.cc:278] Please NOTE: device: 2, GPU Compute Capability: 8.0, Driver API Version: 11.6, Runtime API Version: 11.2
W0527 09:07:39.856563 1155 gpu_context.cc:306] device: 2, cuDNN Version: 8.1.
I0527 09:07:50.847107 1155 collective_helper.cc:104] nccl communicator of rank 2 in ring 0 has been created on device 2
I0527 09:07:50.847275 1155 task_node.cc:45] Constructing TaskNode for DistModelInf. The TaskNode's id is: 2. And the TaskNode's max_run_time and max_slot_num will be set to 1.
I0527 09:07:50.894994 1155 message_bus.cc:194] Message bus's listen port thread starts successful.
C++ Serving service started successfully!
```
### 3.6 发送客户端请求
运行以下命令启动客户端:
```
python3.7 test_client_while.py --model_prototxt_path serving_model_pp1mp4/rank_0/serving_client/ --serving_port 2003
```
运行结果如下:
```
serving port : 2003
model prototxt path: serving_model_pp1mp4/rank_0/serving_client/
prompt_str: ["%s%s"] multi_choice_prompt:
total-num: 1
examples: [Example(qid='1', text_a='How are you today?', text_b=''), Example(qid='2', text_a='The capital of China is', text_b='')]
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0527 07:29:35.225399 146811 naming_service_thread.cpp:202] brpc::policy::ListNamingService("127.0.0.1:2003"): added 1
[DEBUG] qid: 0 , tokens: ['how', 'are', 'you', 'today', '?'] token_ids: [1, 2248, 1875, 1631, 4014, 35] pos_ids: [0, 1, 2, 3, 4, 5]
[DEBUG] qid: 0 , tokens: ['the', 'capital', 'of', 'china', 'is'] token_ids: [1, 1595, 4460, 1605, 1906, 1706] pos_ids: [0, 1, 2, 3, 4, 5]
running 0 text...
score: -14.7109375 current_tokens: ['[]', 'how', 'are', 'you', 'today', '?', 'how', 'are', 'you', 'today', '?', '[]', '[UNK]', '[UNK]', '[UNK]', '[UNK]', '[UNK]', '[UNK]', '[UNK]']
---------------------------------------------------------------------
running 1 text...
score: -27.109375 current_tokens: ['a', 'country', 'with', 'a', 'long', 'history', 'and', 'a', 'long', 'history', 'of', 'history', '.', '[]', ',', '《', '》', ',', '《']
---------------------------------------------------------------------
```
此时,可以看到服务端计算结果:
```
Q: How are you today?
A: [] how are you, today? how are you today ?[]
Q: The capital of China is
A: a country with a long history and a long history of history.[],《》,《
```
### 3.7 关闭服务
运行 stop.sh 关闭所有进程。
```
bash stop.sh
```
所有进程关闭。
```
start kill serving
grep: warning: GREP_OPTIONS is deprecated; please use an alias or script
grep: warning: GREP_OPTIONS is deprecated; please use an alias or script
760 762 764 766 772 1124 1128 1130 1133 1135 1137 1138 1139 1140 1141
pid:760
pid:762
pid:764
pid:766
pid:772
pid:1124
pid:1128
pid:1130
pid:1133
pid:1135
pid:1137
pid:1138
pid:1139
pid:1140
pid:1141
end kill serving
```
## 4. 编译方法
由于大模型推理不仅要使用推理技术,要需要分布式技术,因此需要开启特定编译选项的 Paddle 和 Serving 才能满足需求。
### 4.1 编译 Paddle
首先,参考飞桨官网[使用Docker编译](https://www.paddlepaddle.org.cn/documentation/docs/zh/install/compile/linux-compile.html#compile_from_docker)步骤完成以下8步:
- 1.选择您希望储存PaddlePaddle的路径
- 2.进入Paddle目录下
- 3.拉取 PaddlePaddle 镜像
- 4.创建并进入已配置好编译环境的Docker容器
- 5.进入Docker后进入paddle目录下
- 6.切换到 `release/2.3` 进行编译
- 7.创建并进入/paddle/build路径下
- 8.使用以下命令安装相关依赖
9.执行 cmake 步骤使用以下命令,开启了多个编译选项,如分布式选项`-DWITH_DISTRIBUTE`
```
cmake .. -DWITH_MKLML=ON -DWITH_MKLDNN=ON -DWITH_GPU=ON -DWITH_TENSORRT=OFF -DWITH_C_API=OFF -DWITH_DISTRIBUTE=ON -DCMAKE_BUILD_TYPE=Release -DWITH_TESTING=OFF -DWITH_STYLE_CHECK=OFF -DWITH_CONTRIB=OFF -DWITH_GRPC=ON -DWITH_BRPC_RDMA=OFF -DWITH_FLUID_ONLY=ON -DWITH_INFERENCE_API_TEST=ON -DWITH_DOC=ON -DCUDA_ARCH_NAME=Auto -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DWITH_PYTHON=ON -DPY_VERSION=3.7 -DWITH_PSCORE=ON
```
10. make -j10 执行编译
编译结果如下:
```
-rw-r--r-- 1 root root 96 Apr 20 07:28 cblas_dummy.c
-rw-r--r-- 1 root root 633K May 26 03:35 CMakeCache.txt
drwxr-xr-x 36 root root 4.0K May 26 03:49 CMakeFiles/
-rw-r--r-- 1 root root 1.8K Apr 20 07:28 cmake_install.cmake
-rw-r--r-- 1 root root 9.5M May 26 03:36 compile_commands.json
-rw-r--r-- 1 root root 387 Apr 20 07:27 detect_cuda_archs.cu
-rw-r--r-- 1 root root 159 May 26 03:35 detect_thrust.cu
-rw-r--r-- 1 root root 58 May 26 03:35 FindNumpyPath.py
-rw-r--r-- 1 root root 2.4K May 26 03:35 includes.txt
-rw-r--r-- 1 root root 1.5K May 26 03:36 libcblas.a
-rw-r--r-- 1 root root 1.5K May 26 03:36 libmkldnn.a
-rw-r--r-- 1 root root 798K May 26 03:35 Makefile
-rw-r--r-- 1 root root 99 Apr 20 07:28 mkldnn_dummy.c
drwxr-xr-x 9 root root 4.0K May 26 03:35 paddle/
drwxr-xr-x 7 root root 4.0K May 26 03:35 python/
drwxr-xr-x 30 root root 4.0K Apr 29 03:10 third_party/
```
动态和静态库保存在 `./paddle/fluid/inference/` 目录下:
```
drwxr-xr-x 5 root root 4.0K May 26 03:42 analysis/
drwxr-xr-x 4 root root 4.0K May 26 03:42 api/
drwxr-xr-x 3 root root 4.0K May 26 03:46 capi_exp/
-rw-r--r-- 1 root root 302 May 26 03:35 check_symbol.cmake
drwxr-xr-x 6 root root 4.0K May 26 03:35 CMakeFiles/
-rw-r--r-- 1 root root 1.8K May 26 03:35 cmake_install.cmake
-rw-r--r-- 1 root root 1.4G May 26 03:46 libpaddle_inference.a
-rw-r--r-- 1 root root 382K May 26 03:41 libpaddle_inference_io.a
-rwxr-xr-x 1 root root 624M May 26 03:47 libpaddle_inference.so*
-rw-r--r-- 1 root root 24K Apr 20 07:28 Makefile
drwxr-xr-x 1420 root root 140K May 25 04:56 paddle_inference.dir/
-rw-r--r-- 1 root root 138 May 26 03:46 paddle_inference_dummy.c
-rw-r--r-- 1 root root 130K Apr 29 06:14 paddle_inference.mri
drwxr-xr-x 5 root root 4.0K May 26 03:40 tensorrt/
drwxr-xr-x 3 root root 4.0K May 26 03:38 utils/
```
### 4.2 编译 Serving
参考 [Paddle Serving源码编译](https://github.com/PaddlePaddle/Serving/blob/v0.9.0/doc/Compile_CN.md) 文档完成编译环境准备、下载代码库和环境变量准备 3个步骤。
下面介绍差异化编译步骤:
**1.Paddle Serving 采用动态库方式联编 Paddle**
首先修改 `Serving/cmake/paddlepaddle.cmake`,联编静态库`libpaddle_inference.a`变更为动态库`libpaddle_inference.so`。
```
ADD_LIBRARY(paddle_inference STATIC IMPORTED GLOBAL)
#SET_PROPERTY(TARGET paddle_inference PROPERTY IMPORTED_LOCATION ${PADDLE_INSTALL_DIR}/lib/libpaddle_inference.a)
SET_PROPERTY(TARGET paddle_inference PROPERTY IMPORTED_LOCATION ${PADDLE_INSTALL_DIR}/lib/libpaddle_inference.so)
if (WITH_ASCEND_CL OR WITH_XPU)
SET_PROPERTY(TARGET paddle_inference PROPERTY IMPORTED_LOCATION ${PADDLE_INSTALL_DIR}/lib/libpaddle_inference.so)
endif()
```
**2.make编译**
设置完成环境变量后,编译paddle-serving-server,其 cmake 和 make 命令与编译示例是一致的。
```
mkdir build_server
cd build_server
cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \
-DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
-DCUDA_TOOLKIT_ROOT_DIR=${CUDA_PATH} \
-DCUDNN_LIBRARY=${CUDNN_LIBRARY} \
-DCUDA_CUDART_LIBRARY=${CUDA_CUDART_LIBRARY} \
-DTENSORRT_ROOT=${TENSORRT_LIBRARY_PATH} \
-DSERVER=ON \
-DWITH_GPU=ON ..
make -j20
cd ..
```
编译目录 build_server 信息如下:
```
-rw-r--r-- 1 root root 85 May 9 05:23 boost_dummy.c
-rw-r--r-- 1 root root 42K Apr 25 08:08 CMakeCache.txt
drwxr-xr-x 19 root root 4.0K May 26 04:09 CMakeFiles/
-rw-r--r-- 1 root root 1.9K Apr 25 08:08 cmake_install.cmake
-rw-r--r-- 1 root root 215K May 9 05:23 compile_commands.json
drwxr-xr-x 10 root root 4.0K May 9 05:23 core/
-rw-r--r-- 1 root root 1.5K May 9 05:23 libboost.a
-rw-r--r-- 1 root root 27K May 9 05:23 Makefile
drwxr-xr-x 4 root root 4.0K May 9 05:23 paddle_inference/
drwxr-xr-x 8 root root 4.0K May 9 05:23 python/
drwxr-xr-x 16 root root 4.0K Apr 25 08:08 third_party/
```
在编译过程中会报错,需要用本地编译生成的Paddle动态库替换下载的Paddle动态库。其中`paddle_build`和`paddle_build`要替换为实际路径。
```
copy -R paddle_build/paddle/fluid/inference/*.so serving_build/third_party/Paddle/src/extern_paddle/paddle/lib/
copy -R paddle_build/paddle/fluid/inference/*.so serving_build/third_party/install/Paddle/lib/
```
再次运行 `make -j10` 继续编译。编译成功后生成二进制文件`serving`:
```
serving_build/core/general-server/serving
```
导出到环境变量中:
```
export SERVING_BIN=${PWD}/core/general-server/serving
```