Skip to content

Commit

Permalink
Merge pull request #2 from cryoco/refine-python-api-doc
Browse files Browse the repository at this point in the history
Refine python api doc to be consistent with c++.
  • Loading branch information
cryoco committed May 13, 2020
2 parents 9378280 + a02c61f commit 300fdb1
Showing 1 changed file with 151 additions and 147 deletions.
298 changes: 151 additions & 147 deletions docs/user_guides/inference_python_api.md
Original file line number Diff line number Diff line change
@@ -1,209 +1,213 @@
# Python 预测API介绍
# Python 预测 API介绍

## Python预测相关数据结构
Paddle Inference提供了高度优化的Python 和C++ API预测接口,本篇文档主要介绍Python API,使用C++ API进行预测的文档可以参考[这里](./cxx_api.md)
下面是详细的使用说明。

使用Python预测API与C++预测API相似,主要包括`PaddleTensor`, `PaddleDType`, `AnalysisConfig``PaddlePredictor`,分别对应于C++ API中同名的类型。
使用Python预测API预测包含以下几个主要步骤:

### PaddleTensor
- 配置推理选项
- 创建Predictor
- 准备模型输入
- 模型推理
- 获取模型输出

class paddle.fluid.core.PaddleTensor
我们先从一个简单程序入手,介绍这一流程:

`PaddleTensor`是预测库输入和输出的数据结构,包括以下字段
``` python
def create_predictor():
# 通过AnalysisConfig配置推理选项
config = AnalysisConfig("./resnet50/model", "./resnet50/params")
config.switch_use_feed_fetch_ops(False)
config.enable_use_gpu(100, 0)
config.enable_mkldnn()
config.enable_memory_optim()
predictor = create_paddle_predictor(config)
return predictor

def run(predictor, data):
# 准备模型输入
input_names = predictor.get_input_names()
for i, name in enumerate(input_names):
input_tensor = predictor.get_input_tensor(name)
input_tensor.reshape(data[i].shape)
input_tensor.copy_from_cpu(data[i].copy())

# 执行模型推理
predictor.zero_copy_run()

results = []
# 获取模型输出
output_names = predictor.get_output_names()
for i, name in enumerate(output_names):
output_tensor = predictor.get_output_tensor(name)
output_data = output_tensor.copy_to_cpu()
results.append(output_data)

return results
```

* `name`(str): 指定输入的名称
* `shape`(tuple|list): Tensor的shape
* `data`(numpy.ndarray): Tensor的数据,可在PaddleTensor构造的时候用`numpy.ndarray`直接传入
* `dtype`(PaddleDType): Tensor的类型
* `lod`(List[List[int]]): [LoD](../../../user_guides/howto/basic_concept/lod_tensor.html)信息
以上的程序中`create_predictor `函数对推理过程进行了配置以及创建了Predictor。 `run `函数进行了输入数据的准备、模型推理以及输出数据的获取过程。

`PaddleTensor`包括以下方法
在接下来的部分中,我们会依次对程序中出现的AnalysisConfig,Predictor,模型输入,模型输出进行详细的介绍。

* `as_ndarray`: 返回`data`对应的numpy数组
## 一、推理配置管理器AnalysisConfig
AnalysisConfig管理AnalysisPredictor的推理配置,提供了模型路径设置、推理引擎运行设备选择以及多种优化推理流程的选项。配置中包括了必选配置以及可选配置。

### 1. 必选配置
#### a.设置模型和参数路径
* non-combined形式:模型文件夹`model_dir`下存在一个模型文件和多个参数文件时,传入模型文件夹路径,模型文件名默认为`__model__`。 使用方式为:

#### 代码示例
``` python
tensor = PaddleTensor(name="tensor", data=numpy.array([1, 2, 3], dtype="int32"))
config.set_model("./model_dir")
```
调用`PaddleTensor`的成员字段和方法输出如下:
* combined形式:模型文件夹`model_dir`下只有一个模型文件`model`和一个参数文件`params`时,传入模型文件和参数文件路径。使用方式为:

``` python
>>> tensor.name
'tensor'
>>> tensor.shape
[3]
>>> tensor.dtype
PaddleDType.INT32
>>> tensor.lod
[]
>>> tensor.as_ndarray()
array([1, 2, 3], dtype=int32)
config.set_model("./model_dir/model", "./model_dir/params")
```

* 内存加载模式:如果模型是从内存加载,可以使用:

### PaddleDType
``` python
import os
model_buffer = open('./resnet50/model','rb')
params_buffer = open('./resnet50/params','rb')
model_size = os.fstat(model_buffer.fileno()).st_size
params_size = os.fstat(params_buffer.fileno()).st_size
config.set_model_buffer(model_buffer.read(), model_size, params_buffer.read(), params_size)
```

class paddle.fluid.core.PaddleTensor
关于`non-combined` 以及 `combined`模型介绍,请参照[这里](../introduction/quick_start.md)

`PaddleDType`定义了`PaddleTensor`的数据类型,由传入`PaddleTensor`的numpy数组类型确定,包括以下成员
#### b. 关闭feed与fetch OP
`config.switch_use_feed_fetch_ops(False) # 关闭feed和fetch OP使用,使用ZeroCopy接口必须设置此项`

* `INT64`: 64位整型
* `INT32`: 32位整型
* `FLOAT32`: 32位浮点型
我们用一个小的例子来说明我们为什么要关掉它们。
假设我们有一个模型,模型运行的序列为:
`input -> FEED_OP -> feed_out -> CONV_OP -> conv_out -> FETCH_OP -> output`

### AnalysisConfig
序列中大写字母的`FEED_OP`, `CONV_OP`, `FETCH_OP` 为模型中的OP, 小写字母的`input``feed_out``output` 为模型中的变量。

class paddle.fluid.core.AnalysisConfig
在ZeroCopy模式下,我们通过`predictor.get_input_tensor(input_names[0])`获取的模型输入为`FEED_OP`的输出, 即`feed_out`,我们通过`predictor.get_output_tensor(output_names[0])`接口获取的模型输出为`FETCH_OP`的输入,即`conv_out`,这种情况下,我们在运行期间就没有必要运行feed和fetch OP了,因此需要设置`config.switch_use_feed_fetch_ops(False)`来关闭feed和fetch op。

`AnalysisConfig`是创建预测引擎的配置,提供了模型路径设置、预测引擎运行设备选择以及多种优化预测流程的选项,主要包括以下方法

* `set_model`: 设置模型的路径
* `model_dir`: 返回模型文件夹路径
* `prog_file`: 返回模型文件路径
* `params_file`: 返回参数文件路径
* `enable_use_gpu`: 设置GPU显存(单位M)和Device ID
* `disable_gpu`: 禁用GPU
* `gpu_device_id`: 返回使用的GPU ID
* `switch_ir_optim`: IR优化(默认开启)
* `enable_tensorrt_engine`: 开启TensorRT
* `enable_mkldnn`: 开启MKLDNN
#### 代码示例
设置模型和参数路径有两种形式:
* 当模型文件夹下存在一个模型文件和多个参数文件时,传入模型文件夹路径,模型文件名默认为`__model__`
### 2. 可选配置

#### a. 加速CPU推理

``` python
config = AnalysisConfig("./model")
# 开启MKLDNN,可加速CPU推理,要求预测库带MKLDNN功能。
config.enable_mkldnn()
# 可以设置CPU数学库线程数math_threads,可加速推理。
# 注意:math_threads * 外部线程数 需要小于总的CPU的核心数目,否则会影响预测性能。
config.set_cpu_math_library_num_threads(10)

```
* 当模型文件夹下只有一个模型文件和一个参数文件时,传入模型文件和参数文件路径

#### b. 使用GPU推理

``` python
config = AnalysisConfig("./model/model", "./model/params")
# enable_use_gpu后,模型将运行在GPU上。
# 第一个参数表示预先分配显存数目,第二个参数表示设备的ID。
config.enable_use_gpu(100, 0)
```
使用`set_model`方法设置模型和参数路径方式同上

其他预测引擎配置选项示例如下
如果使用的预测lib带Paddle-TRT子图功能,可以打开TRT选项进行加速:

``` python
config.enable_use_gpu(100, 0) # 初始化100M显存,使用gpu id为0
config.gpu_device_id() # 返回正在使用的gpu id
config.disable_gpu() # 禁用gpu
config.switch_ir_optim(True) # 开启IR优化
config.enable_tensorrt_engine(precision_mode=AnalysisConfig.Precision.Float32,
use_calib_mode=True) # 开启TensorRT预测,精度为fp32,开启int8离线量化
config.enable_mkldnn() # 开启MKLDNN
# 开启TensorRT推理,可提升GPU推理性能,需要使用带TensorRT的推理库
config.enable_tensorrt_engine(1 << 30, # workspace_size
batch_size, # max_batch_size
3, # min_subgraph_size
AnalysisConfig.Precision.Float32, # precision
False, # use_static
False, # use_calib_mode
)
```
通过计算图分析,Paddle可以自动将计算图中部分子图融合,并调用NVIDIA的 TensorRT 来进行加速。
使用Paddle-TensorRT 预测的完整方法可以参考[这里](../optimize/paddle_trt.md)


#### c. 内存/显存优化

### PaddlePredictor

class paddle.fluid.core.PaddlePredictor
``` python
config.enable_memory_optim() # 开启内存/显存复用
```
该配置设置后,在模型图分析阶段会对图中的变量进行依赖分类,两两互不依赖的变量会使用同一块内存/显存空间,缩减了运行时的内存/显存占用(模型较大或batch较大时效果显著)。

`PaddlePredictor`是运行预测的引擎,由`paddle.fluid.core.create_paddle_predictor(config)`创建,主要提供以下方法

* `run`: 输入和返回值均为`PaddleTensor`列表类型,功能为运行预测引擎,返回预测结果
#### d. debug开关

#### 代码示例

``` python
# 设置完AnalysisConfig后创建预测引擎PaddlePredictor
predictor = create_paddle_predictor(config)
# 该配置设置后,会关闭模型图分析阶段的任何图优化,预测期间运行同训练前向代码一致。
config.switch_ir_optim(False)
```

# 设置输入
x = numpy.array([1, 2, 3], dtype="int64")
x_t = fluid.core.PaddleTensor(x)
``` python
# 该配置设置后,会在模型图分析的每个阶段后保存图的拓扑信息到.dot文件中,该文件可用graphviz可视化。
config.switch_ir_debug(True)
```

y = numpy.array([4], dtype = "int64")
y_t = fluid.core.PaddleTensor(y)
## 二、预测器PaddlePredictor

# 运行预测引擎得到结果,返回值是一个PaddleTensor的列表
results = predictor.run([x_t, y_t])
PaddlePredictor 是在模型上执行推理的预测器,根据AnalysisConfig中的配置进行创建。

# 获得预测结果,并应用到自己的应用中
``` python
predictor = create_paddle_predictor(config)
```
## 支持方法列表
* PaddleTensor
* `as_ndarray() -> numpy.ndarray`
* AnalysisConfig
* `set_model(model_dir: str) -> None`
* `set_model(prog_file: str, params_file: str) -> None`
* `model_dir() -> str`
* `prog_file() -> str`
* `params_file() -> str`
* `enable_use_gpu(memory_pool_init_size_mb: int, device_id: int) -> None`
* `gpu_device_id() -> int`
* `switch_ir_optim(x: bool = True) -> None`
* `enable_tensorrt_engine(workspace_size: int = 1 << 20,
max_batch_size: int,
min_subgraph_size: int,
precision_mode: AnalysisConfig.precision,
use_static: bool,
use_calib_mode: bool) -> None`
* `enable_mkldnn() -> None`
* PaddlePredictor
* `run(input: List[PaddleTensor]) -> List[PaddleTensor]`

可参考对应的[C++预测接口](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/pybind/inference_api.cc),其中定义了每个接口的参数和返回值
create_paddle_predictor 期间首先对模型进行加载,并且将模型转换为由变量和运算节点组成的计算图。接下来将进行一系列的图优化,包括OP的横向纵向融合,删除无用节点,内存/显存优化,以及子图(Paddle-TRT)的分析,加速推理性能,提高吞吐。

## 完整使用示例

下面是使用Fluid Python API进行预测的一个完整示例,使用resnet50模型
## 三:输入/输出

下载[resnet50模型](http://paddle-inference-dist.bj.bcebos.com/resnet50_model.tar.gz)并解压,运行如下命令将会调用预测引擎
### 1. 准备输入

``` bash
python resnet50_infer.py --model_file ./model/model --params_file ./model/params --batch_size 2
```

`resnet50_infer.py` 的内容是
#### a. 获取模型所有输入的Tensor名字

``` python
import argparse
import numpy as np

from paddle.fluid.core import PaddleTensor
from paddle.fluid.core import AnalysisConfig
from paddle.fluid.core import create_paddle_predictor


def main():
args = parse_args()
input_names = predictor.get_input_names()
```

# 设置AnalysisConfig
config = AnalysisConfig(args.model_file, args.params_file)
config.disable_gpu()
#### b. 获取对应名字下的Tensor

# 创建PaddlePredictor
predictor = create_paddle_predictor(config)
``` python
# 获取第0个输入
input_tensor = predictor.get_input_tensor(input_names[0])
```

# 设置输入,此处以随机输入为例,用户可自行输入真实数据
inputs = fake_input(args.batch_size)
#### c. 将输入数据copy到Tensor中

# 运行预测引擎
outputs = predictor.run(inputs)
output_num = 512
``` python
# 在copy前需要设置Tensor的shape
input_tensor.reshape((batch_size, channels, height, width))
# Tensor会根据上述设置的shape从input_data中拷贝对应数目的数据。input_data为numpy数组。
input_tensor.copy_from_cpu(input_data)
```

# 获得输出并解析
output = outputs[0]
print(output.name)
output_data = output.as_ndarray() #return numpy.ndarray
assert list(output_data.shape) == [args.batch_size, output_num]
for i in range(args.batch_size):
print(np.argmax(output_data[i]))
### 2. 获取输出
#### a. 获取模型所有输出的Tensor名字

``` python
output_names = predictor.get_output_names()
```

def fake_input(batch_size):
shape = [batch_size, 3, 318, 318]
data = np.random.randn(*shape).astype("float32")
image = PaddleTensor(data)
return [image]
#### b. 获取对应名字下的Tensor

``` python
# 获取第0个输出
output_tensor = predictor.get_output_tensor(ouput_names[0])
```

def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--model_file", type=str, help="model filename")
parser.add_argument("--params_file", type=str, help="parameter filename")
parser.add_argument("--batch_size", type=int, default=1, help="batch size")
#### c. 将数据copy到Tensor中

return parser.parse_args()
``` python
# output_data为numpy数组
output_data = output_tensor.copy_to_cpu()
```


if __name__ == "__main__":
main()
```
## 下一步

看到这里您是否已经对 Paddle Inference 的 Python API 使用有所了解了呢?请访问[这里](https://github.com/PaddlePaddle/Paddle-Inference-Demo/tree/master/python)进行样例测试。

0 comments on commit 300fdb1

Please sign in to comment.