近日,,微軟亞洲研究院和華為舉辦了 ONNX 合作伙伴研討會(huì),,這是 ONNX 開(kāi)源社區(qū)成立以來(lái)首次在中國(guó)舉辦的活動(dòng)。在研討會(huì)中,,微軟,、Facebook、華為和英特爾等的開(kāi)發(fā)者介紹了他們?cè)?ONNX 上的開(kāi)源貢獻(xiàn)及思考,。
在過(guò)去的一年多中,,ONNX 這種「通用」的神經(jīng)網(wǎng)絡(luò)交換格式已經(jīng)有了很長(zhǎng)遠(yuǎn)的發(fā)展,用不同框架編寫的模型可以在不同的平臺(tái)中流通,。在這次研討會(huì)中,,我們確切地感受到了這一點(diǎn),因?yàn)殚_(kāi)源社區(qū)圍繞著 ONNX 介紹了很多優(yōu)化工具和資源庫(kù),。
微軟上個(gè)月開(kāi)源了 ONNX Runtime,,其專為 ONNX 格式的模型設(shè)計(jì)了高性能推理引擎。Facebook 早兩個(gè)月前開(kāi)源了 ONNXIFI,,其為 ONNX 提供了用于框架集成的接口,,即一組用于加載和執(zhí)行 ONNX 計(jì)算圖的跨平臺(tái) API。更早一些,,英特爾在今年 3 月份就開(kāi)源 nGraph,,它能編譯 ONNX 格式的模型,并在 CPU 或 GPU 等硬件加速模型的運(yùn)行,。
而到了昨天,,微軟又開(kāi)源了 ONNX.JS,它是一種在瀏覽器和 Node.js 上運(yùn)行 ONNX 模型的 JavaScript 庫(kù),。它部署的模型效率非常高,,且能實(shí)現(xiàn)交互式的直觀推理。該開(kāi)源項(xiàng)目給出了圖像分類的交互式演示,,且在 Chrome 瀏覽器和 CPU 下比 TensorFlow.JS 快了近 8 倍,,后文將詳細(xì)介紹這一開(kāi)源庫(kù)。
當(dāng)然除了這些開(kāi)源工作,,ONNX 社區(qū)還有更多的實(shí)踐,,例如如何部署 ONNX 模型到邊緣設(shè)備、如何維護(hù)一個(gè)包羅萬(wàn)象的 ONNX Model Zoo 等,。本文主要從什么是 ONNX,、怎樣用 ONNX,以及如何優(yōu)化 ONNX 三方面看看 ONNX 是不是已經(jīng)引領(lǐng)「框架間的江湖」了,。
什么是 ONNX
很多開(kāi)發(fā)者在玩 GitHub 的時(shí)候都有這樣「悲痛」的經(jīng)歷,,好不容易找到令人眼前一亮的項(xiàng)目,然而發(fā)現(xiàn)它使用我們不熟悉的框架寫成,。其實(shí)我們會(huì)發(fā)現(xiàn)很多優(yōu)秀的視覺(jué)模型是用 Caffe 寫的,,很多新的研究論文是用 PyTorch 寫的,而更多的模型用 TensorFlow 寫成,。因此如果我們要測(cè)試它們就必須擁有對(duì)應(yīng)的框架環(huán)境,,但 ONNX 交換格式令我們?cè)谕画h(huán)境下測(cè)試不同模型有了依靠,。
簡(jiǎn)而言之 ONNX 就是一種框架間的轉(zhuǎn)換格式,例如我們用 TensorFlow 寫的模型可以轉(zhuǎn)換為 ONNX 格式,,并在 Caffe2 環(huán)境下運(yùn)行該模型,。
機(jī)器之心SyncedONNX小程序
項(xiàng)目地址:https://github.com/onnx/onnx
ONNX 定義了一種可擴(kuò)展的計(jì)算圖模型、一系列內(nèi)置的運(yùn)算單元(OP)和標(biāo)準(zhǔn)數(shù)據(jù)類型,。每一個(gè)計(jì)算流圖都定義為由節(jié)點(diǎn)組成的列表,,并構(gòu)建有向無(wú)環(huán)圖。其中每一個(gè)節(jié)點(diǎn)都有一個(gè)或多個(gè)輸入與輸出,,每一個(gè)節(jié)點(diǎn)稱之為一個(gè) OP,。這相當(dāng)于一種通用的計(jì)算圖,不同深度學(xué)習(xí)框架構(gòu)建的計(jì)算圖都能轉(zhuǎn)化為它,。
如下所示,,目前 ONNX 已經(jīng)支持大多數(shù)框架,使用這些框架構(gòu)建的模型可以轉(zhuǎn)換為通用的 ONNX 計(jì)算圖和 OP?,F(xiàn)階段 ONNX 只支持推理,,所以導(dǎo)入的模型都需要在原框架完成訓(xùn)練。
其中 Frameworks 下的框架表示它們已經(jīng)內(nèi)嵌了 ONNX,,開(kāi)發(fā)者可以直接通過(guò)這些框架的內(nèi)置 API 將模型導(dǎo)出為 ONNX 格式,,或采用它們作為推理后端。而 Converters 下的框架并不直接支持 ONNX 格式,,但是可以通過(guò)轉(zhuǎn)換工具導(dǎo)入或?qū)С鲞@些框架的模型,。
其實(shí)并不是所有框架都支持導(dǎo)入和導(dǎo)出 ONNX 格式的模型,有一些并不支持導(dǎo)入 ONNX 格式的模型,,例如 PyTorch 和 Chainer 等,,TensorFlow 的 ONNX 導(dǎo)入同樣也正處于實(shí)驗(yàn)階段。下圖展示了各框架對(duì) ONNX 格式的支持情況:
怎樣使用 ONNX
對(duì)于內(nèi)建了 ONNX 的框架而言,,使用非常簡(jiǎn)單,,只需要調(diào)用 API 導(dǎo)出或?qū)胍延?xùn)練模型就可以了。例如對(duì) PyTorch 而言,,只需要幾個(gè)簡(jiǎn)單的步驟就能完成模型的導(dǎo)出和導(dǎo)入,。簡(jiǎn)單而言,首先加載 torch.onnx 模塊,,然后導(dǎo)出預(yù)訓(xùn)練模型并查看模型結(jié)構(gòu)信息,,最后再將導(dǎo)出的 ONNX 模型加載到另外的框架就能執(zhí)行推理了。
from torch.autograd import Variable
import torch.onnx
import torchvision
dummy_input = Variable(torch.randn(10, 3, 224, 224)).cuda()
model = torchvision.models.alexnet(pretrained=True).cuda()
input_names = [ "actual_input_1" ] + [ "learned_%d" % i for i in range(16) ]
output_names = [ "output1" ]
torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names)
如上所示將導(dǎo)出 ONNX 格式的 AlexNet 模型,,其中"alexnet.onnx"為保存的模型,,input_names、output_names 和 verbose=True 都是為了打印出模型結(jié)構(gòu)信息,。同樣隨機(jī)產(chǎn)生的「圖像」dummy_input 也是為了了解模型結(jié)構(gòu),,因?yàn)槲覀兛梢酝ㄟ^(guò)它理解輸入與每一層具體的參數(shù)維度,。以下展示了 ONNX 輸出的簡(jiǎn)要模型信息:
graph(%actual_input_1 : Float(10, 3, 224, 224)
%learned_0 : Float(64, 3, 11, 11)
%learned_1 : Float(64)
# ---- omitted for brevity ----
%learned_14 : Float(1000, 4096)
%learned_15 : Float(1000)) {
%17 : Float(10, 64, 55, 55) = onnx::Conv[dilations=[1, 1], group=1, kernel_shape=[11, 11], pads=[2, 2, 2, 2], strides=[4, 4]](%actual_input_1, %learned_0, %learned_1), scope: AlexNet/Sequential[features]/Conv2d[0]
%18 : Float(10, 64, 55, 55) = onnx::Relu(%17), scope: AlexNet/Sequential[features]/ReLU[1]
%19 : Float(10, 64, 27, 27) = onnx::MaxPool[kernel_shape=[3, 3], pads=[0, 0, 0, 0], strides=[2, 2]](%18), scope: AlexNet/Sequential[features]/MaxPool2d[2]
# ---- omitted for brevity ----
%output1 : Float(10, 1000) = onnx::Gemm[alpha=1, beta=1, broadcast=1, transB=1](%45, %learned_14, %learned_15), scope: AlexNet/Sequential[classifier]/Linear[6]
return (%output1);
}
其實(shí)我們也可以借助 ONNX 檢查中間表征,不過(guò)這里并不介紹,。后面加載另外一個(gè)框架并執(zhí)行推理同樣非常簡(jiǎn)單,。如下所示,我們可以從 caffe2 中加載 ONNX 的后端,,并將前面保存的模型加載到該后端,從而在新框架下進(jìn)行推理,。這里我們能選擇執(zhí)行推理的硬件,,并直接推理得出輸出結(jié)果。
import caffe2.python.onnx.backend as backend
import numpy as np
import onnx
model = onnx.load("alexnet.onnx")
rep = backend.prepare(model, device="CUDA:0") # or "CPU"
outputs = rep.run(np.random.randn(10, 3, 224, 224).astype(np.float32))
其實(shí)也就兩三行代碼涉及 ONNX 的核心操作,,即導(dǎo)出模型,、加載模型和加載另一個(gè)框架的后端。TensorFlow 或 CNTK 等其它框架的具體 API 可能不一樣,,但主要過(guò)程也就這簡(jiǎn)單的幾步,。
怎樣優(yōu)化 ONNX
前面就已經(jīng)介紹了 Model Zoo、ONNX Runtime 和 ONNX.JS,,現(xiàn)在,,我們可以具體看看它們都是什么,它們?cè)鯓硬拍軒椭覀儍?yōu)化 ONNX 模型的選擇與推理速度,。
Model Zoo
ONNX Model Zoo 包含了一系列預(yù)訓(xùn)練模型,,它們都是 ONNX 格式,且能獲得當(dāng)前最優(yōu)的性能,。因此只要下載這樣的模型,,我們本地不論是 TensorFlow 還是 MXNet,只要是只是能加載模型的框架,,就能運(yùn)行這些預(yù)訓(xùn)練模型,。
項(xiàng)目地址:https://github.com/onnx/models
更重要的是,這個(gè) Model Zoo 不僅有調(diào)用預(yù)訓(xùn)練模型的代碼,,它還為每個(gè)預(yù)訓(xùn)練模型開(kāi)放了對(duì)應(yīng)的訓(xùn)練代碼,。訓(xùn)練和推理代碼都是用 Jupyter Notebook 寫的,數(shù)據(jù)和模型等都有對(duì)應(yīng)的鏈接,。
目前該 Model Zoo 主要從圖像分類,、檢測(cè)與分割、圖像超分辨,、機(jī)器翻譯和語(yǔ)音識(shí)別等 14 個(gè)方向包含 19 種模型,,還有更多的模型還在開(kāi)發(fā)中。如下展示了圖像分類中已經(jīng)完成的模型,,它們都是通用的 ONNX 格式,。
此外在這次的研討會(huì)中,,Model Zoo 的維護(hù)者還和大家討論了目前面臨的問(wèn)題及解決方法,例如目前的預(yù)訓(xùn)練模型主要集中在計(jì)算機(jī)視覺(jué)方面,、ONNX 缺少一些特定的 OP,、權(quán)重計(jì)算圖下載慢等。因此 Model Zoo 接下來(lái)也會(huì)更關(guān)注其它語(yǔ)音和語(yǔ)言等模型,,優(yōu)化整個(gè) GitHub 項(xiàng)目的下載結(jié)構(gòu),。
ONNX Runtime
微軟開(kāi)源的 ONNX Runtime 推理引擎支持 ONNX 中定義的所有運(yùn)算單元,它非常關(guān)注靈活性和推理性能,。因此不論我們的開(kāi)發(fā)環(huán)境是什么,,Runtime 都會(huì)基于各種平臺(tái)與硬件選擇不同的自定義加速器,并希望以最小的計(jì)算延遲和資源占用完成推理,。
文檔地址:https://docs.microsoft.com/en-us/python/api/overview/azure/onnx/intro
ONNX Runtime 可以自動(dòng)調(diào)用各種硬件加速器,,例如英偉達(dá)的 CUDA、TensorRT 和英特爾的 MKL-DNN,、nGraph,。如下所示,ONNX 格式的模型可以傳入到藍(lán)色部分的 Runtime,,并自動(dòng)完成計(jì)算圖分割及并行化處理,,最后我們只需要如橙色所示的輸入數(shù)據(jù)和輸出結(jié)果就行了。
其實(shí)在實(shí)際使用的時(shí)候,,開(kāi)發(fā)者根本不需要考慮藍(lán)色的部分,,不論是編譯還是推理,代碼都賊簡(jiǎn)單,。如下所示,,導(dǎo)入 onnxruntime 模塊后,調(diào)用 InferenceSession() 方法就能導(dǎo)入 ONNX 格式的模型,,并完成上圖一系列復(fù)雜的優(yōu)化,。最后只需要 session.run() 就可以進(jìn)行推理了,所有的優(yōu)化過(guò)程都隱藏了細(xì)節(jié),。
import onnxruntime
session = onnxruntime.InferenceSession("your_model.onnx")
prediction = session.run(None, {"input1": value})
在研討會(huì)中,,開(kāi)發(fā)者表示 Runtime 的目標(biāo)是構(gòu)建高性能推理引擎,它需要利用最好的加速器和完整的平臺(tái)支持,。只需要幾行代碼就能把計(jì)算圖優(yōu)化一遍,,這對(duì) ONNX 格式的模型是個(gè)大福利。
ONNX.JS
ONNX.js 是一個(gè)在瀏覽器上運(yùn)行 ONNX 模型的庫(kù),,它采用了 WebAssembly 和 WebGL 技術(shù),,并在 CPU 或 GPU 上推理 ONNX 格式的預(yù)訓(xùn)練模型。
項(xiàng)目地址:https://github.com/Microsoft/onnxjs
Demo 展示地址:https://microsoft.github.io/onnxjs-demo
通過(guò) ONNX.js,開(kāi)發(fā)者可以直接將預(yù)訓(xùn)練的 ONNX 模型部署到瀏覽器,,這些預(yù)訓(xùn)練模型可以是 Model Zoo 中的,,也可以是自行轉(zhuǎn)換的。部署到瀏覽器有很大的優(yōu)勢(shì),,它能減少服務(wù)器與客戶端之間的信息交流,,并獲得免安裝和跨平臺(tái)的機(jī)器學(xué)習(xí)模型體驗(yàn)。如下所示為部署到網(wǎng)頁(yè)端的 SqueezeNet:
如上若是選擇 GPU,,它會(huì)采用 WebGL 訪問(wèn) GPU,。如果選擇 CPU,那么其不僅會(huì)采用 WebAssembly 以接近原生的速度執(zhí)行模型,,同時(shí)也會(huì)采用 Web Workers 提供的「多線程」環(huán)境來(lái)并行化數(shù)據(jù)處理,。該項(xiàng)目表明,通過(guò)充分利用 WebAssembly 和 Web Workers,,CPU 可以獲得很大的性能提升,。這一點(diǎn)在項(xiàng)目提供的 Benchmarks 中也有相應(yīng)的展示:
以上微軟在 Chrome 和 Edge 瀏覽器中測(cè)試了 ResNet-50 的推理速度,,其中比較顯著的是 CPU 的推理速度,。這主要是因?yàn)?Keras.js 和 TensorFlow.js 在任何瀏覽器中都不支持 WebAssembly。
最后,,從 ONNXIFI 到 ONNX.js,,開(kāi)源社區(qū)已經(jīng)為 ONNX 格式構(gòu)建出眾多的優(yōu)化庫(kù)、轉(zhuǎn)換器和資源,。很多需要支持多框架的場(chǎng)景也都將其作為默認(rèn)的神經(jīng)網(wǎng)絡(luò)格式,,也許以后,ONNX 真的能統(tǒng)一神經(jīng)網(wǎng)絡(luò)之間的江湖,。