Paddle Inference原生推理庫


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文檔請參考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. 導出模型文件
    模型部署首先要有部署的模型文件。在模型訓練過程中或者模型訓練結束后,可以通過paddle.jit.save 接口來導出標准化的模型文件。save_inference_model可以根據推理需要的輸入和輸出, 對訓練模型進行剪枝, 去除和推理無關部分, 得到的模型相比訓練時更加精簡, 適合進一步優化和部署。
    用一個簡單的例子來展示下導出模型文件的這一過程。

import numpy as np

import paddle

import paddle.nn 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)

 

    @paddle.jit.to_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 = "example.model/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

該程序運行結束后,會在本目錄中生成一個example.model目錄,目錄中包含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配置推理選項

  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(), input_shape.end(), 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

  auto output_t = predictor->GetOutputHandle(output_names[0]);

  std::vector<int> output_shape = output_t->shape();

  int out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, std::multiplies<int>());

  out_data->resize(out_num);

  output_t->CopyToCpu(out_data->data());

}

以上的程序中CreatePredictor函數對推理過程進行了配置以及創建了Predictor。 Run函數進行了輸入數據的准備,模型推理以及輸出數據的獲取過程。

接下來依次對程序中出現的Config,Predictor,模型輸入,模型輸出做一個詳細的介紹。

使用Config管理推理配置

Config管理Predictor的推理配置,提供了模型路徑設置、推理引擎運行設備選擇以及多種優化推理流程的選項。配置中包括了必選配置以及可選配置。

1. 必選配置

1-1 設置模型和參數路徑

  • 從文件加載:模型文件夾model_dir下有一個模型文件my.pdmodel和一個參數文件my.pdiparams時,傳入模型文件和參數文件路徑。使用方式為:config->SetModel("./model_dir/my.pdmodel", "./model_dir/my.pdiparams");。
  • 內存加載模式:如果模型是從內存加載,可以使用config->SetModelBuffer(model.data(), model.size(), params.data(), params.size())。

2. 可選配置

2-1 加速CPU推理

// 開啟MKLDNN,可加速CPU推理,要求預測庫帶MKLDNN功能。

config->EnableMKLDNN();                           

// 可以設置CPU數學庫線程數math_threads,可加速推理。

// 注意:math_threads * 外部線程數 需要小於總的CPU的核心數目,否則會影響預測性能。

config->SetCpuMathLibraryNumThreads(10);

 

2-2 使用GPU推理

// EnableUseGpu后,模型將運行在GPU上。

// 第一個參數表示預先分配顯存數目,第二個參數表示設備的ID。

config->EnableUseGpu(100, 0);

如果使用的預測lib帶Paddle-TRT子圖功能,可以打開TRT選項進行加速:

// 開啟TensorRT推理,可提升GPU推理性能,需要使用帶TensorRT的推理庫

config->EnableTensorRtEngine(1 << 30           /*workspace_size*/,  

                           batch_size        /*max_batch_size*/,    

                           3                 /*min_subgraph_size*/,

                                    paddle_infer::PrecisionType::kFloat32 /*precision*/,

                           false             /*use_static*/,

                           false             /*use_calib_mode*/);

通過計算圖分析,Paddle可以自動將計算圖中部分子圖融合,並調用NVIDIA的 TensorRT 來進行加速。

2-3 內存/顯存優化

config->EnableMemoryOptim();  // 開啟內存/顯存復用

該配置設置后,在模型圖分析階段會對圖中的變量進行依賴分類,兩兩互不依賴的變量會使用同一塊內存/顯存空間,縮減了運行時的內存/顯存占用(模型較大或batch較大時效果顯著)。

2-4 debug開關

// 該配置設置后,會關閉模型圖分析階段的任何圖優化,預測期間運行同訓練前向代碼一致。

config->SwitchIrOptim(false);

// 該配置設置后,會在模型圖分析的每個階段后保存圖的拓撲信息到.dot文件中,該文件可用graphviz可視化。

config->SwitchIrDebug();

使用Tensor管理輸入/輸出

1. 准備輸入

1-1 獲取模型所有輸入的tensor名字

std::vector<std::string> input_names = predictor->GetInputNames();

1-2 獲取對應名字下的tensor

// 獲取第0個輸入

auto input_t = predictor->GetInputHandle(input_names[0]);

1-3 將數據copy到tensor中

// 在copy前需要設置tensor的shape

input_t->Reshape({batch_size, channels, height, width});

// tensor會根據上述設置的shape從input_data中拷貝對應數目的數據到tensor中。

input_t->CopyFromCpu<float>(input_data /*數據指針*/);

當然也可以用mutable_data獲取tensor的數據指針:

// 參數可為paddle_infer::PlaceType::kGPU, paddle_infer::PlaceType::kCPU

float *input_d = input_t->mutable_data<float>(paddle_infer::PlaceType::kGPU);

2. 獲取輸出
2-1 獲取模型所有輸出的tensor名字

std::vector<std::string> out_names = predictor->GetOutputNames();

2-2 獲取對應名字下的tensor

// 獲取第0個輸出

auto output_t = predictor->GetOutputHandle(out_names[0]);

2-3 將數據copy到tensor中

std::vector<float> out_data;

// 獲取輸出的shpae

std::vector<int> output_shape = output_t->shape();

int out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, std::multiplies<int>());

out_data->resize(out_num);

output_t->CopyToCpu(out_data->data());

可以用data接口獲取tensor的數據指針:

// 參數可為paddle_infer::PlaceType::kGPU, paddle_infer::PlaceType::kCPU

int output_size;

float *output_d = output_t->data<float>(paddle_infer::PlaceType::kGPU, &output_size);

使用Predictor進行高性能推理

Predictor是在模型上執行推理的預測器,根據Config中的配置進行創建。

auto predictor = paddle_infer::CreatePredictor(config);

paddle_infer::CreatePredictor期間首先對模型進行加載,並且將模型轉換為由變量和運算節點組成的計算圖。接下來將進行一系列的圖優化,包括OP的橫向縱向融合,刪除無用節點,內存/顯存優化,以及子圖(Paddle-TRT)的分析,加速推理性能,提高吞吐。

C++ API使用示例

提供一個使用飛槳paddle C++ 預測庫和ResNet50模型進行圖像分類預測的代碼示例,展示預測庫使用的完整流程。

一:獲取Resnet50模型

點擊鏈接下載模型,該模型在imagenet 數據集訓練得到的。

二:樣例編譯

文件resnet50_test.cc 為預測的樣例程序(程序中的輸入為固定值,如果您有opencv或其他方式進行數據讀取的需求,需要對程序進行一定的修改)。
文件CMakeLists.txt 為編譯構建文件。
腳本run_impl.sh 包含了第三方庫、預編譯庫的信息配置。

編譯Resnet50樣例,首先需要對腳本run_impl.sh 文件中的配置進行修改。

1)修改run_impl.sh

打開run_impl.sh,對以下的幾處信息進行修改:

# 根據預編譯庫中的version.txt信息判斷是否將以下三個標記打開

WITH_MKL=ON      

WITH_GPU=ON        

USE_TENSORRT=OFF

 

# 配置預測庫的根目錄

LIB_DIR=${YOUR_LIB_DIR}/paddle_inference_install_dir

 

# 如果上述的WITH_GPU 或 USE_TENSORRT設為ON,請設置對應的CUDA, CUDNN, TENSORRT的路徑。

CUDNN_LIB=/paddle/nvidia-downloads/cudnn_v7.5_cuda10.1/lib64

CUDA_LIB=/paddle/nvidia-downloads/cuda-10.1/lib64

# TENSORRT_ROOT=/paddle/nvidia-downloads/TensorRT-6.0.1.5

運行 sh run_impl.sh, 會在目錄下產生build目錄。

2) 運行樣例

# 進入build目錄

cd build

# 運行樣例

./resnet50_test --model_file=${RESNET_MODEL_PATH}/ResNet/model --params_file=${RESNET_MODEL_PATH}/ResNet/params

運行結束后,程序會將模型結果打印到屏幕,說明運行成功。

C++ API性能調優

在前面預測接口的介紹中,了解到,通過使用Config可以對Predictor進行配置模型運行的信息。對Config中的優化配置進行詳細的介紹。

優化原理
預測主要存在兩個方面的優化,一是預測期間內存/顯存的占用,二是預測花費的時間。

  • 預測期間內存/顯存的占用決定了一個模型在機器上的並行的數量,如果一個任務是包含了多個模型串行執行的過程,內存/顯存的占用也會決定任務是否能夠正常執行(尤其對GPU的顯存來說)。內存/顯存優化增大了模型的並行量,提升了服務吞吐量,同時也保證了任務的正常執行,因此顯的極為重要。
  • 預測的一個重要的指標是模型預測的時間,通過對 kernel 的優化,以及加速庫的使用,能夠充份利用機器資源,使得預測任務高性能運行。

1. 內存/顯存優化
在預測初始化階段,飛槳paddle預測引擎會對模型中的 OP 輸出 Tensor 進行依賴分析,將兩兩互不依賴的 Tensor 在內存/顯存空間上進行復用。

可以通過調用以下接口方式打開內存/顯存優化。

Config config;

config.EnableMemoryOptim();

運行過推理之后,如果想回收推理引擎使用的臨時內存/顯存,降低內存/顯存的使用,可以通過以下接口。

config.TryShrinkMemory();

內存/顯存優化效果

 

 

 

 2. 性能優化

在模型預測期間,飛將預測引擎會對模型中進行一系列的 OP 融合,比如 Conv 和 BN 的融合,Conv 和 Bias、Relu 的融合等。OP 融合不僅能夠減少模型的計算量,同時可以減少 Kernel Launch 的次數,從而能提升模型的性能。

可以通過調用以下接口方式打開 OP 融合優化:

Config config;

config.SwitchIrOptim(true);  // 默認打開

除了通用的 OP 融合優化外,飛槳paddle預測引擎有針對性的對 CPU 以及 GPU 進行了性能優化。

CPU 性能優化

1.對矩陣庫設置多線程

模型在CPU預測期間,大量的運算依托於矩陣庫,如 OpenBlas,MKL。通過設置矩陣庫內部多線程,能夠充分利用 CPU 的計算資源,加速運算性能。

可以通過調用以下接口方式設置矩陣庫內部多線程。

Config config;

// 通常情況下,矩陣內部多線程(num) * 外部線程數量 <= CPU核心數

config->SetCpuMathLibraryNumThreads(num); 

2.使用 MKLDNN 加速

MKLDNN是Intel發布的開源的深度學習軟件包。目前飛槳paddle預測引擎中已經有大量的OP使用MKLDNN加速,包括:Conv,Batch Norm,Activation,Elementwise,Mul,Transpose,Pool2d,Softmax 等。

可以通過調用以下接口方式打開MKLDNN優化。

Config config;

config.EnableMKLDNN(); 

開關打開后,飛槳paddle預測引擎會使用 MKLDNN 加速的 Kernel 運行,從而加速 CPU 的運行性能。

GPU 性能優化

使用 TensorRT 子圖性能優化

TensorRT 是 NVIDIA 發布的一個高性能的深度學習預測庫,飛槳paddle預測引擎采用子圖的形式對 TensorRT 進行了集成。在預測初始化階段,通過對模型分析,將模型中可以使用 TensorRT 運行的 OP 進行標注,同時把這些標記過的且互相連接的 OP 融合成子圖並轉換成一個 TRT OP 。在預測期間,如果遇到 TRT OP ,則調用 TensorRT 庫對該 OP 進行優化加速。

可以通過調用以下接口的方式打開 TensorRT 子圖性能優化:

config->EnableTensorRtEngine(1 << 30      /* workspace_size*/,  

                        batch_size        /* max_batch_size*/,    

                        3                 /* min_subgraph_size*/,

                        paddle_infer::PrecisionType::kFloat32 /* precision*/,

                        false             /* use_static*/,

                        false             /* use_calib_mode*/);

該接口中的參數的詳細介紹如下:

  • workspace_size,類型:int,默認值為1 << 20。指定TensorRT使用的工作空間大小,TensorRT會在該大小限制下篩選合適的kernel執行預測運算,一般可以設置為幾百兆(如1 << 29, 512M)。
  • max_batch_size,類型:int,默認值為1。需要提前設置最大的batch大小,運行時batch大小不得超過此限定值。
  • min_subgraph_size,類型:int,默認值為3。Paddle-TRT 是以子圖的形式運行,為了避免性能損失,當子圖內部節點個數大於min_subgraph_size的時候,才會使用Paddle-TRT運行。
  • precision,類型:enum class Precision {kFloat32 = 0, kHalf, kInt8,};, 默認值為PrecisionType::kFloat32。指定使用TRT的精度,支持FP32(kFloat32),FP16(kHalf),Int8(kInt8)。若需要使用Paddle-TRT int8離線量化校准,需設定precision為 - PrecisionType::kInt8, 且設置use_calib_mode 為true。
  • use_static,類型:bool, 默認值為 false 。如果指定為 true ,在初次運行程序的時候,會將 TRT 的優化信息進行序列化到磁盤上,下次運行時,直接加載優化的序列化信息,而不需要重新生成。
  • use_calib_mode,類型:bool, 默認值為false。若要運行 Paddle-TRT int8 離線量化校准,需要將此選項設置為 true 。

目前 TensorRT 子圖對圖像模型有很好的支持,支持的模型如下

  • 分類:Mobilenetv1/2, ResNet, NasNet, VGG, ResNext, Inception, DPN,ShuffleNet
  • 檢測:SSD,YOLOv3,FasterRCNN,RetinaNet
  • 分割:ICNET,UNET,DeepLabV3,MaskRCNN

基於Python API的推理部署

飛槳paddle提供了高度優化的C++測庫,為了方便使用,也提供了與C++預測庫對應的Python接口。使用Python預測API與C++預測API相似,主要包括Tensor, Config和Predictor,分別對應於C++ API中同名的數據類型。接下來給出更為詳細的介紹。

使用Config管理推理配置

paddle.inference.Config是創建預測引擎的配置,提供了模型路徑設置、預測引擎運行設備選擇以及多種優化預測流程的選項。通過Config,可以指定,預測引擎執行的方式。舉幾個例子,如果希望預測引擎在 CPU 上運行,那么,可以設置disable_gpu()的選項配置,那么在實際執行的時候,預測引擎會在 CPU 上執行。同樣,如果設置switch_ir_optim()為True或是False,則決定了預測引擎是否會自動進行優化。

使用之前,需要創建一個Config的實例,用於完成這些選項的設置。

config = paddle.inference.Config()

Config可以設置的選項,具體如下:

  • set_model(): 設置模型的路徑,model_filename:模型文件名,params_filename:參數文件名。
  • config.set_model(model_filename, params_filename)

既然模型的文件已經配置好了,那么還需要指定的預測引擎是在什么設備上執行的,就是說,需要指定在 CPU 上或是在 GPU 的哪一張卡上預測。

  • enable_use_gpu(): 啟用使用 GPU 的預測方式,並且設置 GPU 初始分配的顯存(單位M)和 Device ID,即是在哪一張 GPU 的卡上執行預測。 需要另外注意的是,Device ID,假設,現在有一台8張顯卡的機器,設定環境變量如下:
  • export CUDA_VISIBLE_DEVICES=1,3,5

那么,在使用enable_use_gpu()設置 Device ID 的時候,可以設置的卡的編號是:0,1,2。0號卡實際代表的是機器上的編號為1的顯卡,而1號卡實際代表的是機器上編號為3的顯卡,同理,2號卡實際代表的是機器上的編號為5的顯卡。

  • gpu_device_id(): 返回使用的 GPU 的 Device ID。
  • disable_gpu(): 該方法從字面上理解是禁用 GPU,即,是使用 CPU 進行預測。

完成了模型的配置,執行預測引擎的設備的設定,還可以進行一些其他的配置。比如:

  • switch_ir_optim(): 打開或是關閉預測引擎的優化,默認是開啟的。設置的方式如下:
  • config.switch_ir_optim(True)
  • config.enable_tensorrt_engine(precision_mode=paddle.inference.PrecisionType.Float32,
  •                             use_calib_mode=True)
  • config.enable_mkldnn()
  • enable_tensorrt_engine(): 啟用TensorRT的引擎進行預測優化。具體的參數信息和上文使用TensorRT子圖性能優化是一樣的。這里提供一個簡單的設置示例。
  • enable_mkldnn(): 開啟 MKLDNN 加速選項,一般是使用 CPU 進行預測的時候,如果機器支持 MKLDNN 可以開啟。
  • disable_glog_info(): 禁用預測中所有的日志信息。

在完成了Config的設置之后,可以通過配置的Config,創建一個用於執行預測引擎的實例,這個實例是基於數據結構Predictor,創建的方式是直接調用paddle.inference.create_predictor()方法。如下:

predictor = paddle.inference.create_predictor(config)

Config示例

首先,如前文所說,設置模型和參數路徑:

config = paddle.inference.Config("./model/model", "./model/params")

使用set_model()方法設置模型和參數路徑方式同上。

其他預測引擎配置選項示例如下:

config.enable_use_gpu(100, 0) # 初始化100M顯存,使用gpu id為0

config.gpu_device_id()        # 返回正在使用的gpu device id

config.disable_gpu()                # 禁用gpu,使用cpu進行預測

config.switch_ir_optim(True)  # 開啟IR優化

config.enable_tensorrt_engine(precision_mode=paddle.inference.PrecisionType.Float32,

                              use_calib_mode=True) # 開啟TensorRT預測,精度為fp32,開啟int8離線量化

config.enable_mkldnn()              # 開啟MKLDNN

最后,根據config得到預測引擎的實例predictor:

predictor = paddle.inference.create_predictor(config)

使用Tensor管理輸入/輸出

Tensor是Predictor的一種輸入/輸出數據結構,下面將用詳細的例子加以講解。

首先,配置好了Config的一個實例config,接下來,需要使用這個config創建一個predictor。

# 創建predictor

predictor = paddle.inference.create_predictor(config)

因為config里面包含了模型的信息,在這里,創建好了predictor之后,實際上已經可以獲取模型的輸入的名稱了。因此,可以通過

input_names = predictor.get_input_names()

獲取模型輸入的名稱。需要注意的是,這里的input_names是一個List[str],存儲着模型所有輸入的名稱。進而,可以通過每一個輸入的名稱,得到Tensor的一個實例,此時,輸入數據的名稱已經和對應的Tensor關聯起來了,無需再另外設置數據的名稱。

input_tensor = predictor.get_input_handle(input_names[0])

得到數據之后,就可以完成對數據的設置了。

fake_input = numpy.random.randn(1, 3, 224, 224).astype("float32")

input_tensor.copy_from_cpu(fake_input)

使用一個copy_from_cpu()方法即可完成設置,數據類型,通過numpy來保證。 在實際的使用過程中,有的用戶會問到,”如果我想將預測引擎在 GPU 上執行怎么辦呢?是否有一個copy_from_gpu()的方法?“ 回答也很簡單,沒有copy_from_gpu()的方法,無論是在 CPU 上執行預測引擎,還是在 GPU 上執行預測引擎,copy_from_cpu()就夠了。

接着,執行預測引擎:

# 運行predictor

predictor.run()

完成預測引擎的執行之后,需要獲得預測的輸出。與設置輸入類似 首先,獲取輸出的名稱 其次,根據輸出的名稱得到Tensor的實例,用來關聯輸出的Tensor。 最后,使用copy_to_cpu()得到輸出的矩陣向量。

output_names = predictor.get_output_names()

output_tensor = predictor.get_output_handle(output_names[0])

output_data = output_tensor.copy_to_cpu() # numpy.ndarray類型

使用Predictor進行高性能推理

class paddle.inference.Predictor

Predictor是運行預測的引擎,由paddle.inference.create_predictor(config)創建。

Predictor示例

import numpy

 

# 引用 paddle inference 預測庫

import paddle.inference as paddle_infer

 

# 創建 config

config = paddle_infer.Config("./model/model", "./model/params")

 

# 根據 config 創建 predictor

predictor = paddle_infer.create_predictor(config)

 

# 獲取輸入 Tensor

input_names = predictor.get_input_names()

input_tensor = predictor.get_input_handle(input_names[0])

 

# 從 CPU 獲取數據,設置到 Tensor 內部

fake_input = numpy.random.randn(1, 3, 224, 224).astype("float32")

input_tensor.copy_from_cpu(fake_input)

 

# 執行預測

predictor.run()

支持方法列表

在這里,先做一個總結。總結下前文介紹的使用預測庫的 Python API 都有哪些方法。

  • Tensor
    • copy_from_cpu(input: numpy.ndarray) -> None
    • copy_to_cpu() -> numpy.ndarray
    • reshape(input: numpy.ndarray|List[int]) -> None
    • shape() -> List[int]
    • set_lod(input: numpy.ndarray|List[List[int]]) -> None
    • lod() -> List[List[int]]
    • type() -> PaddleDType
  • Config
    • set_model(prog_file: str, params_file: str) -> None
    • 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: PrecisionType, use_static: bool, use_calib_mode: bool) -> None
    • enable_mkldnn() -> None
    • disable_glog_info() -> None
    • delete_pass(pass_name: str) -> None
  • Predictor
    • run() -> None
    • get_input_names() -> List[str]
    • get_input_handle(input_name: str) -> Tensor
    • get_output_names() -> List[str]
    • get_output_handle(output_name: str) -> Tensor

Python API使用示例

下面是使用Python API進行推理的一個完整示例。

使用前文提前准備生成的模型example.model,在該路徑下即可找到預測需要的模型文件linear.pdmodel和參數文件linear.pdiparams。

可以ls查看一下對應的路徑下面內容:

ls example.model

linear.pdiparams  linear.pdiparams.info  linear.pdmodel

模型文件准備好了,接下來,可以直接運行下面的代碼得到預測的結果,預測的結果。

最后的output_data就是預測程序返回的結果,output_data是一個numpy.ndarray類型的數組,可以直接獲取其數據的值。output_data作為一個numpy.ndarray,大家需要自定義不同的后處理方式也更為方便。以下的代碼就是使用Python預測API全部的部分。

如下的實例中,打印出了前10個數據。輸出的數據的形狀是[1, 1000],其中1代表的是batch_size的大小,1000是這唯一一個樣本的輸出,有1000個數據。

import numpy as np

# 引用 paddle inference 預測庫

import paddle.inference as paddle_infer

 

def main():

    # 設置Config

    config = set_config()

 

    # 創建Predictor

    predictor = paddle_infer.create_predictor(config)

 

    # 獲取輸入的名稱

    input_names = predictor.get_input_names()

    input_tensor = predictor.get_input_handle(input_names[0])

 

    # 設置輸入

    fake_input = np.random.randn(1,784).astype("float32")

    input_tensor.copy_from_cpu(fake_input)

 

    # 運行predictor

    predictor.run()

 

    # 獲取輸出

    output_names = predictor.get_output_names()

    output_tensor = predictor.get_output_handle(output_names[0])

    output_data = output_tensor.copy_to_cpu() # numpy.ndarray類型

    print("輸出的形狀如下: ")

    print(output_data.shape)

    print("輸出前10個的數據如下: ")

    print(output_data[:10])

 

def set_config():

    config = paddle_infer.Config("./example.model/linear.pdmodel", "./example.model/linear.pdiparams")

    config.disable_gpu()

    return config

 

if __name__ == "__main__":

    main() 

輸出的形狀如下:

(1, 10)

輸出前10個的數據如下:

[[ 2.5663333   0.40451288  1.5446359   0.202711   -1.0881205   1.3942682

  -0.17815694 -0.38273144 -0.10059531 -2.09705   ]]

Python API性能調優

Python 預測 API 的優化與 C++ 預測 API 的優化方法完全一樣。大家在使用的時候,可以參照 C++ 預測 API 的優化說明。唯一存在不同的是,調用的方法的名稱,在這里,做了一個對應的表格供大家查閱。

以上方法在使用 Python 預測 API 的時候,都可以直接使用

config.methods_name()

完成調用與配置。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM