天天看点

ONNX调研一. ONNX生态简介二. ONNX Runtime(ORT) 使用备注

一. ONNX生态简介

ONNX(英语:Open Neural Network Exchange)是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的人工智能框架(如Pytorch、MXNet)可以采用相同格式存储模型数据并交互。 ONNX的规范及代码主要由微软,亚马逊,Facebook和IBM等公司共同开发,以开放源代码的方式托管在Github上。13  目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, MXNet,ML.NET,TensorRT 和 Microsoft CNTK,并且 TensorFlow 也非官方的支持ONNX。

ONNX Runtime(ORT) 是机器学习模型的加速器,具有多平台支持和与硬件特定库集成的灵活接口。ONNX Runtime 可与来自 PyTorch、Tensorflow/Keras、TFLite、scikit-learn 和其他框架的模型一起使用。主要是通过图优化技术来提高模型的性能, 同时也支持CUDA还有一些线程级别的优化。

ONNX调研一. ONNX生态简介二. ONNX Runtime(ORT) 使用备注

image.png

ONNX Go Live ONNX的性能调优和可视化工具,可以帮助开发快速找出最佳的参数配置组合。

调研目的: 提高当前在线推断模型的性能, 最大限度地降低工程的机器成本。

二. ONNX Runtime(ORT) 使用

1. 使用ORT部署传统机器学习模型

第一步: Install ONNX

环境准备, 使用前请保证好了以下软件, 如果没有可以先自行安装:

  1. python并且版本>=python3.7
  2. docker
  3. 安装onnx
pip install onnx
pip install onnxruntime           

复制

第二步: Train Model

训练一个传统的Logistic Regression模型,使用sklearn训练,训练集直接选择sklearn自带的鸢尾花数据集

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from onnx import onnx_ml_pb2

# 数据集内包含 3 类共 150 条记录,每类各 50 个数据,每条记录都有 4 项特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,可以通过这4个特征预测鸢尾花卉属于(iris-setosa, iris-versicolour, iris-virginica)中的哪一品种
iris = load_iris()
X, y = iris.data, iris.target
# 测试集和训练集打散拆分,这里一定要打散,因为原始数据是相对有序的。
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666, test_size=0.3)

# 训练出一个分类器
clr = LogisticRegression(solver='liblinear', multi_class='auto')
clr.fit(X_train, y_train)           

复制

output:

LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='liblinear', tol=0.0001, verbose=0,
                   warm_start=False)           

复制

第三步: Convert To ONNX

使用

skl2onnx

把Scikit-learn模型序列化为ONNX格式,并检查模型文件是否生成正常

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

initial_type = [('float_input', FloatTensorType([1, 4]))]
onx = convert_sklearn(clr, initial_types=initial_type)
with open("logreg_iris.onnx", "wb") as f:
    f.write(onx.SerializeToString())

import onnx
model = onnx.load('logreg_iris.onnx')
print(model)           

复制

output:

ir_version: 8
producer_name: "skl2onnx"
producer_version: "1.10.0"
domain: "ai.onnx"
model_version: 0
doc_string: ""
graph {
  node {
    input: "float_input"
    output: "label"
    output: "probability_tensor"
    name: "LinearClassifier"
    op_type: "LinearClassifier"
    attribute {
     此处内容过多已省略...
 }           

复制

第四步: Direct Predict

使用ONNX Runtime Python API预测该ONNX模型,当前仅使用了测试数据集中的第一条数据。

import onnxruntime as rt
import numpy

initial_type = [('float_input', FloatTensorType([None, 4]))]
sess = rt.InferenceSession("logreg_iris.onnx")
input_name = sess.get_inputs()[0].name
label_name = sess.get_outputs()[0].name
probability_name = sess.get_outputs()[1].name
pred_onx = sess.run([label_name, probability_name], {input_name: X_test[0:1].astype(numpy.float32)})

# print info
print('input_name: ' + input_name)
print('label_name: ' + label_name)
print('probability_name: ' + probability_name)
print(X_test[0])
print(pred_onx)           

复制

output:

input_name: float_input
label_name: output_label
probability_name: output_probability
# 这里输入的是一个4维的向量,分别表示花萼长度、花萼宽度、花瓣长度、花瓣宽度
[5.  3.5 1.3 0.3]
#  品种为iris-setosa
[array([0], dtype=int64), [{0: 0.9112248420715332, 1: 0.08868706971406937, 2: 8.812700252747163e-05}]]           

复制

第五步: Serve Model using ONNX

以HTTP&GRPC 的方式对外提供服务, 使用

mcr.microsoft.com/onnxruntime/server

镜像部署刚才的

logreg_iris.onnx

模型。

首先刚才生成好的模型文件

logreg_iris.onnx

拷贝到/data/www/ai/models 目录下, 然后进行/data/www/ai/models执行:

cd /data/www/ai/models

docker run \
    -it \
    -v "$PWD:/models" \
    -p 9001:8001 \
    -p 50051:50051 \
    --name ort mcr.microsoft.com/onnxruntime/server \
    --model_path="/models/logreg_iris.onnx"           

复制

output:

Version: local_build
Commit ID: default

[2021-10-23 13:28:53.657] [ServerApp] [info] Model path: /models/logreg_iris.onnx
[2021-10-23 13:28:53.660] [ServerApp] [info] [ onnxruntime inference_session.cc:545 Initialize]: Initializing session.

此处内容过多已省略...

SaveInitializedTensors]: Done saving initialized tensors
[2021-10-23 13:28:53.663] [ServerApp] [info] [ onnxruntime inference_session.cc:620 Initialize]: Session successfully initialized.
[2021-10-23 13:28:53.664] [ServerApp] [info] GRPC Listening at: 0.0.0.0:50051
[2021-10-23 13:28:53.664] [ServerApp] [info] Listening at: http://0.0.0.0:8001           

复制

结果分析: 同时启动了一个GRPC和HTTP服务, 其中Http通过一下路由访问

http://<your_ip_address>:<port>/v1/models/<your-model-name>/versions/<your-version>:predict           

复制

也就是

http://127.0.0.1:9001/v1/models/default/versions/1:predict

访问。

注意这里的

<your-model-name>

其实是Commit ID你可以在上面的docker run命令后看到了。

2. 使用ORT部署算法推荐模型

前期准备工作:一个已经训练好的算法推荐模型

第一步: Convert To ONNX

# 进入模型目录
cd /Users/guirong/Desktop/ai/models/

# 环境准备
pip install tensorflow-cpu==2.3.0
pip install tf2onnx

# 转换
python -m tf2onnx.convert \
        --saved-model output/1634309909/ \
        --output output/recommend.onnx \
        --opset 10 \
        --tag serve           

复制

第二步: Serve Model using ONNX

执行如下命令:

docker run \
    -it \
    --rm \
    -v "$PWD:/models" \
    -p 9001:8001 \
    -p 50051:50051 \
    --name ort mcr.microsoft.com/onnxruntime/server \
    --model_path="/models/recommend.onnx"           

复制

需要注意⚠️的是当前目录下必须要有一个models目录并且放置了上一步生成的onnx模型文件。

第三步:Client Test

import onnxruntime as rt
import warnings
import numpy
import json
from array import array
warnings.filterwarnings('ignore')
sess = rt.InferenceSession("recommend.onnx")
input_name0 = sess.get_inputs()[0].name
input_name1 = sess.get_inputs()[1].name
input_name2 = sess.get_inputs()[2].name
input_name3 = sess.get_inputs()[3].name
print(input_name0,input_name1,input_name2,input_name3)

req = {
    'dense_input' : [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
                                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
    'seq_input': [[
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]],
     'sparse_ids_input': [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
                         26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
                         50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
                         74, 75, 76, 77, 78]],
    'sparse_wgt_input': [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
      
}

import datetime
start_time = datetime.datetime.now()
# 输出有9个值, 我们取最后一个pctv_pcvr
pred_onx = sess.run([sess.get_outputs()[8].name,], req,)

end_time = datetime.datetime.now()
print("耗时: {}秒".format(end_time - start_time))

print(sess.get_outputs()[8].name)
print(pred_onx)           

复制

output:

tf_op_layer_pcvr_ctr
[array([[0.00265199, 0.04154155]], dtype=float32)]           

复制

备注

由于ONNX本身没有直接提供免费的服务化的方案, 故没有再进行ORT性能测试.

如果想使用ONNX模型到线上, 建议使用Triton服务化, 个人本地测试并没有获得非常明显的性能提升(和个人模型有关), 所以没再深度研究和使用ONNX, 其实工具/框架带来的提升其实非常有限, 建议先把注意力放到模型结构优化上去, 如果结构没法优化了再使用工具/框架来进一步优化.