PaddleInference原⽣推理库
Paddle Inference原⽣推理库
深度学习⼀般分为训练和推理两个部分,训练是神经⽹络“学习”的过程,主要关注如何搜索和求解模型参数,发现训练数据中的规律,⽣成模型。有了训练好的模型,就要在线上环境中应⽤模型,实现对未知数据做出推理,这个过程在AI领域叫做推理部署。⽤户可以选择如下四种部署应⽤⽅式之⼀:
服务器端⾼性能部署:将模型部署在服务器上,利⽤服务器的⾼性能帮助⽤户处理推理业务。
模型服务化部署:将模型以线上服务的形式部署在服务器或者云端,⽤户通过客户端请求发送需要推理的输⼊内容,服务器或者云通过响应报⽂将推理结果返回给⽤户。
移动端部署:将模型部署在移动端上,例如⼿机或者物联⽹的嵌⼊式端。
Web端部署:将模型部署在⽹页上,⽤户通过⽹页完成推理业务。
本⽂将会为⼤家讲解如何使有飞桨paddle实现服务器端⾼性能部署。
在实际应⽤中,推理部署阶段会⾯临和训练时完全不⼀样的硬件环境,当然也对应着不⼀样的计算性
能要求。因此上线部署可能会遇到各种问题,例如上线部署的硬件环境和训练时不同;推理计算耗时太长, 可能造成服务不可⽤;模型占⽤内存过⾼⽆法上线。但是在实际业务中,训练得到的模型,就是需要能在具体⽣产环境中正确、⾼效地实现推理功能,完成上线部署。对⼯业级部署⽽⾔,条件往往⾮常繁多⽽且苛刻,不是每个深度学习框架都对实际⽣产部署上能有良好的⽀持。⼀款对推理⽀持完善的的框架,会让模型上线⼯作事半功倍。
飞桨paddle作为源于产业实践的深度学习框架,在推理部署能⼒上有特别深厚的积累和打磨,提供了性能强劲、上⼿简单的服务器端推理库Paddle Inference,帮助⽤户摆脱各种上线部署的烦恼。
Paddle Inference是什么
飞桨paddle框架的推理部署能⼒经过多个版本的升级迭代,形成了完善的推理库Paddle Inference。Paddle Inference功能特性丰富,性能优异,针对不同平台不同的应⽤场景进⾏了深度的适配优化,做到⾼吞吐、低时延,保证了飞桨paddle模型在服务器端即训即⽤,快速部署。
主流软硬件环境兼容适配
⽀持服务器端X86 CPU、NVIDIA GPU芯⽚,兼容Linux/macOS/Windows系统。
⽀持飞桨paddle所有模型
⽀持所有飞桨paddle训练产出的模型,真正即训即⽤。
多语⾔环境丰富接⼝可灵活调⽤
⽀持C++, Python, C, Go和R语⾔API, 接⼝简单灵活,20⾏代码即可完成部署。可通过Python API,实现对性能要求不太⾼的场景快速⽀持;通过C++⾼性能接⼝,可与线上系统联编;通过基础的C API可扩展⽀持更多语⾔的⽣产环境。
【性能测⼀测】
通过⽐较ResNet50和BERT模型的训练前向耗时和推理耗时,可以观测到Paddle Inference有显著的加速效果。
说明:测试耗时的⽅法,使⽤相同的输⼊数据先空跑1000次,循环运⾏1000次,每次记录模型运⾏的耗时,最后计算出模型运⾏的平均耗时。
基于Paddle Inference的单机推理部署,即在⼀台机器进⾏推理部署。相⽐Paddle Serving在多机多卡下进⾏推理部署,单机推理部署不产⽣多机通信与调度的时间成本,能够最⼤程度地利⽤机器的Paddle Inference算⼒来提⾼推理部署的性能。对于拥有⾼算⼒机器,已有线上业务系统服务,期望加⼊模型推理作为⼀个⼦模块,且对性能要求较⾼的⽤户,采⽤单机推理部署能够充分利⽤计算资源,
加速部署过程。
Paddle Inference⾼性能实现
内存/显存复⽤提升服务吞吐量
在推理初始化阶段,对模型中的OP输出Tensor 进⾏依赖分析,将两两互不依赖的Tensor在内存/显存空间上进⾏复⽤,进⽽增⼤计算并⾏量,提升服务吞吐量。
细粒度OP横向纵向融合减少计算量
在推理初始化阶段,按照已有的融合模式将模型中的多个OP融合成⼀个OP,减少了模型的计算量的同时,也减少了 Kernel Launch的次数,从⽽能提升推理性能。⽬前Paddle Inference⽀持的融合模式多达⼏⼗个。
内置⾼性能的CPU/GPU Kernel
内置同Intel、Nvidia共同打造的⾼性能kernel,保证了模型推理⾼性能的执⾏。
⼦图集成TensorRT加快GPU推理速度
Paddle Inference采⽤⼦图的形式集成TensorRT,针对GPU推理场景,TensorRT可对⼀些⼦图进⾏优化,包括OP的横向和纵向融合,过滤冗余的OP,并为OP⾃动选择最优的kernel,加快推理速度。
⼦图集成Paddle Lite轻量化推理引擎
Paddle Lite 是飞桨paddle深度学习框架的⼀款轻量级、低框架开销的推理引擎,除了在移动端应⽤外,还可以使⽤服务器进⾏ Paddle Lite 推理。Paddle Inference采⽤⼦图的形式集成 Paddle Lite,以⽅便⽤户在服务器推理原有⽅式上稍加改动,即可开启 Paddle Lite 的推理能⼒,得到更快的推理速度。使⽤ Paddle Lite 可⽀持在百度昆仑等⾼性能AI芯⽚上执⾏推理计算。
⽀持加载PaddleSlim量化压缩后的模型
PaddleSlim是飞桨paddle深度学习模型压缩⼯具,Paddle Inference可联动PaddleSlim,⽀持加载量化、裁剪和蒸馏后的模型并部署,由此减⼩模型存储空间、减少计算占⽤内存、加快模型推理速度。其中在模型量化⽅⾯,Paddle Inference在X86 CPU上做了深度优化,常见分类模型的单线程性能可提升近3倍,ERNIE模型的单线程性能可提升2.68倍。
推理部署实战
场景划分
Paddle Inference应⽤场景,按照API接⼝类型可以分C++, Python, C, Go和R。Python适合直接应⽤,可通过Python API实现性能要求不太⾼的场景的快速⽀持;C++接⼝属于⾼性能接⼝,可与线上系统联编;C接⼝是基于C++,⽤于⽀持更多语⾔的⽣产环境。
不同接⼝的使⽤流程⼀致,但个别操作细节存在差异。其中,⽐较常见的场景是C++和Python。因此本⽂以这两类接⼝为例,介绍如何使⽤Pdddle Inference API进⾏单机服务器的推理预测部署。
推理部署流程
使⽤Paddle Inference进⾏推理部署的流程如下所⽰。详细API⽂档请参考
1. 配置推理选项。Config是飞桨paddle提供的配置管理器API。在使⽤Paddle Inference进⾏推理部署过程中,需要使⽤Config详细地配
置推理引擎参数,包括但不限于在何种设备(CPU/GPU)上部署、加载模型路径、开启/关闭计算图分析优化、使⽤
MKLDNN/TensorRT进⾏部署的加速等。参数的具体设置需要根据实际需求来定。
2. 创建Predictor。Predictor是飞桨paddle提供的推理引擎API。根据设定好的推理,配置Config创建推理引擎Predictor,也就是推理引擎
的⼀个实例。创建期间会进⾏模型加载、分析和优化等⼯作。
3. 准备输⼊数据。准备好待输⼊推理引擎的数据,⾸先获得模型中每个输⼊的名称以及指向该数据块(CPU或GPU上)的指针,再根据
名称将对应的数据块拷贝进Tensor。飞桨paddle采⽤Tensor作为输⼊/输出数据结构,可以减少额外的拷贝,提升推理性能。
4. 调⽤Predictor.Run()执⾏推理。
5. 获取推理输出。与输⼊数据类似,根据输出名称将输出的数据(矩阵向量)由Tensor拷贝⾄(CPU或GPU上)以进⾏后续的处理。
6. 最后,获取输出并不意味着预测过程的结束,在⼀些特别的场景中,单纯的矩阵向量不能明⽩有什么意义。进⼀步地,需要根据向量
本⾝的意义,解析数据,获取实际的输出。举个例⼦,transformer 翻译模型,将字词变成向量输⼊到预测引擎中,⽽预测引擎反馈,仍然是矩阵向量。但是这些矩阵向量是有意义的,利⽤这些向量去翻译结果所对应的句⼦,就完成了使⽤ transformer 翻译的过程。
以上操作的具体使⽤⽅法和⽰例会在下⽂给出。
前提准备
1. 安装或源码编译推理库
使⽤飞桨paddle进⾏推理部署,需要使⽤与当前部署环境⼀致的Paddle推理库。
如果使⽤Python API,只需本地电脑成功安装Paddle,安装⽅法请参考。
如果使⽤C++/C API,需要下载或编译推理库。推荐先从飞桨paddle官⽹下载推理库,下载请点击。如果官⽹提供的推理库版本⽆法满⾜需求,或想要对代码进⾏⾃定义修改,可以采⽤源码编译的⽅式获取推理库,推理库的编译请参考前⽂“源码编译”。
2. 导出模型⽂件
模型部署⾸先要有部署的模型⽂件。在模型训练过程中或者模型训练结束后,可以通过接⼝来导出标准化的模型⽂件。
save_inference_model可以根据推理需要的输⼊和输出, 对训练模型进⾏剪枝, 去除和推理⽆关部分, 得到的模型相⽐训练时更加精简,适合进⼀步优化和部署。
⽤⼀个简单的例⼦来展⽰下导出模型⽂件的这⼀过程。
import numpy as np
import paddle
as nn
import paddle.optimizer as opt
BATCH_SIZE = 16
BATCH_NUM = 4
EPOCH_NUM = 4
IMAGE_SIZE = 784
CLASS_NUM = 10
# define a random dataset
class RandomDataset(paddle.io.Dataset):
def __init__(self, num_samples):
self.num_samples = num_samples
def __getitem__(self, idx):
image = np.random.random([IMAGE_SIZE]).astype('float32')
label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64') return image, label
def __len__(self):
return self.num_samples
class LinearNet(nn.Layer):
def __init__(self):
super(LinearNet, self).__init__()
self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
@_static
def forward(self, x):
return self._linear(x)
def train(layer, loader, loss_fn, opt):
for epoch_id in range(EPOCH_NUM):
for batch_id, (image, label) in enumerate(loader()):
out = layer(image)
loss = loss_fn(out, label)
loss.backward()
opt.step()
opt.clear_grad()
print("Epoch {} batch {}: loss = {}".format(
epoch_id, batch_id, np.mean(loss.numpy())))
# create network
layer = LinearNet()
loss_fn = nn.CrossEntropyLoss()
adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
# create data loader
dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
loader = paddle.io.DataLoader(dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
num_workers=2)
# train
train(layer, loader, loss_fn, adam)
# save
path = "del/linear"
paddle.jit.save(layer, path)
Epoch 0 batch 0: loss = 2.4462146759033203
Epoch 0 batch 1: loss = 2.2266905307769775
Epoch 0 batch 2: loss = 2.4391372203826904
Epoch 0 batch 3: loss = 2.304720163345337
Epoch 1 batch 0: loss = 2.382601022720337
Epoch 1 batch 1: loss = 2.2704334259033203
Epoch 1 batch 2: loss = 1.9981389045715332
Epoch 1 batch 3: loss = 1.9509283304214478
Epoch 2 batch 0: loss = 2.5417778491973877
Epoch 2 batch 1: loss = 2.5323636531829834
Epoch 2 batch 2: loss = 2.3336782455444336
Epoch 2 batch 3: loss = 2.2187507152557373
Epoch 3 batch 0: loss = 2.4967103004455566
Epoch 3 batch 1: loss = 2.406843662261963
Epoch 3 batch 2: loss = 2.668104410171509
Epoch 3 batch 3: loss = 2.6359691619873047
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/utils.py:77: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
return (isinstance(seq, collections.Sequence) and
该程序运⾏结束后,会在本⽬录中⽣成⼀个del⽬录,⽬录中包含linear.pdmodel, linear.pdiparams 两个⽂件,linear.pdmodel⽂件表⽰模型的结构⽂件,linear.pdiparams表⽰所有参数的融合⽂件。
基于C++ API的推理部署
为了简单⽅便地进⾏推理部署,飞桨paddle提供了⼀套⾼度优化的C++ API推理接⼝。下⾯对各主要API使⽤⽅法进⾏详细介绍。API详细介绍
在上述的推理部署流程中,了解到Paddle Inference预测包含了以下⼏个⽅⾯:
配置推理选项
创建predictor
准备模型输⼊
模型推理
获取模型输出
先⽤⼀个简单的程序介绍这⼀过程:
std::unique_ptr<paddle_infer::Predictor> CreatePredictor() {
// 通过Config配置推理选项
random翻译paddle_infer::Config config;
config.SetModel("./resnet50/model",
"./resnet50/params");
config.EnableUseGpu(100, 0);
config.EnableMKLDNN();
config.EnableMemoryOptim();
// 创建Predictor
return paddle_infer::CreatePredictor(config);
}
void Run(paddle_infer::Predictor *predictor,
const std::vector<float>& input,
const std::vector<int>& input_shape,
std::vector<float> *out_data) {
// 准备模型的输⼊
int input_num = std::accumulate(input_shape.begin(), d(), 1, std::multiplies<int>());
auto input_names = predictor->GetInputNames();
auto input_t = predictor->GetInputHandle(input_names[0]);
input_t->Reshape(input_shape);
input_t->CopyFromCpu(input.data());
// 模型推理
CHECK(predictor->Run());
// 获取模型的输出
auto output_names = predictor->GetOutputNames();
// there is only one output of Resnet50
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论