天天看點

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

在過去的一年多中,ONNX 這種「通用」的神經網絡交換格式已經有了很長遠的發展,用不同架構編寫的模型可以在不同的平台中流通。在這次研讨會中,我們确切地感受到了這一點,因為開源社群圍繞着 ONNX 介紹了很多優化工具和資源庫。

微軟上個月開源了 ONNX Runtime,其專為 ONNX 格式的模型設計了高性能推理引擎。Facebook 早兩個月前開源了 ONNXIFI,其為 ONNX 提供了用于架構內建的接口,即一組用于加載和執行 ONNX 計算圖的跨平台 API。更早一些,英特爾在今年 3 月份就開源 nGraph,它能編譯 ONNX 格式的模型,并在 CPU 或 GPU 等硬體加速模型的運作。

而到了昨天,微軟又開源了 ONNX.JS,它是一種在浏覽器和 Node.js 上運作 ONNX 模型的 JavaScript 庫。它部署的模型效率非常高,且能實作互動式的直覺推理。該開源項目給出了圖像分類的互動式示範,且在 Chrome 浏覽器和 CPU 下比 TensorFlow.JS 快了近 8 倍,後文将詳細介紹這一開源庫。

當然除了這些開源工作,ONNX 社群還有更多的實踐,例如如何部署 ONNX 模型到邊緣裝置、如何維護一個包羅萬象的 ONNX Model Zoo 等。本文主要從什麼是 ONNX、怎樣用 ONNX,以及如何優化 ONNX 三方面看看 ONNX 是不是已經引領「架構間的江湖」了。

什麼是 ONNX

很多開發者在玩 GitHub 的時候都有這樣「悲痛」的經曆,好不容易找到令人眼前一亮的項目,然而發現它使用我們不熟悉的架構寫成。其實我們會發現很多優秀的視覺模型是用 Caffe 寫的,很多新的研究論文是用 PyTorch 寫的,而更多的模型用 TensorFlow 寫成。是以如果我們要測試它們就必須擁有對應的架構環境,但 ONNX 交換格式令我們在同一環境下測試不同模型有了依靠。

簡而言之 ONNX 就是一種架構間的轉換格式,例如我們用 TensorFlow 寫的模型可以轉換為 ONNX 格式,并在 Caffe2 環境下運作該模型。

ONNX 定義了一種可擴充的計算圖模型、一系列内置的運算單元(OP)和标準資料類型。每一個計算流圖都定義為由節點組成的清單,并建構有向無環圖。其中每一個節點都有一個或多個輸入與輸出,每一個節點稱之為一個 OP。這相當于一種通用的計算圖,不同深度學習架構建構的計算圖都能轉化為它。

如下所示,目前 ONNX 已經支援大多數架構,使用這些架構建構的模型可以轉換為通用的 ONNX 計算圖和 OP。現階段 ONNX 隻支援推理,是以導入的模型都需要在原架構完成訓練。

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

其中 Frameworks 下的架構表示它們已經内嵌了 ONNX,開發者可以直接通過這些架構的内置 API 将模型導出為 ONNX 格式,或采用它們作為推理後端。而 Converters 下的架構并不直接支援 ONNX 格式,但是可以通過轉換工具導入或導出這些架構的模型。

其實并不是所有架構都支援導入和導出 ONNX 格式的模型,有一些并不支援導入 ONNX 格式的模型,例如 PyTorch 和 Chainer 等,TensorFlow 的 ONNX 導入同樣也正處于實驗階段。下圖展示了各架構對 ONNX 格式的支援情況:

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

怎樣使用 ONNX

對于内建了 ONNX 的架構而言,使用非常簡單,隻需要調用 API 導出或導入已訓練模型就可以了。例如對 PyTorch 而言,隻需要幾個簡單的步驟就能完成模型的導出和導入。簡單而言,首先加載 torch.onnx 子產品,然後導出預訓練模型并檢視模型結構資訊,最後再将導出的 ONNX 模型加載到另外的架構就能執行推理了。

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)      

如上所示将導出 ONNX 格式的 AlexNet 模型,其中"alexnet.onnx"為儲存的模型,input_names、output_names 和 verbose=True 都是為了列印出模型結構資訊。同樣随機産生的「圖像」dummy_input 也是為了了解模型結構,因為我們可以通過它了解輸入與每一層具體的參數次元。以下展示了 ONNX 輸出的簡要模型資訊:

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);
}      

其實我們也可以借助 ONNX 檢查中間表征,不過這裡并不介紹。後面加載另外一個架構并執行推理同樣非常簡單。如下所示,我們可以從 caffe2 中加載 ONNX 的後端,并将前面儲存的模型加載到該後端,進而在新架構下進行推理。這裡我們能選擇執行推理的硬體,并直接推理得出輸出結果。

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))      

其實也就兩三行代碼涉及 ONNX 的核心操作,即導出模型、加載模型和加載另一個架構的後端。TensorFlow 或 CNTK 等其它架構的具體 API 可能不一樣,但主要過程也就這簡單的幾步。

怎樣優化 ONNX

前面就已經介紹了 Model Zoo、ONNX Runtime 和 ONNX.JS,現在,我們可以具體看看它們都是什麼,它們怎樣才能幫助我們優化 ONNX 模型的選擇與推理速度。

Model Zoo

ONNX Model Zoo 包含了一系列預訓練模型,它們都是 ONNX 格式,且能獲得目前最優的性能。是以隻要下載下傳這樣的模型,我們本地不論是 TensorFlow 還是 MXNet,隻要是隻是能加載模型的架構,就能運作這些預訓練模型。

更重要的是,這個 Model Zoo 不僅有調用預訓練模型的代碼,它還為每個預訓練模型開放了對應的訓練代碼。訓練和推理代碼都是用 Jupyter Notebook 寫的,資料和模型等都有對應的連結。

目前該 Model Zoo 主要從圖像分類、檢測與分割、圖像超分辨、機器翻譯和語音識别等 14 個方向包含 19 種模型,還有更多的模型還在開發中。如下展示了圖像分類中已經完成的模型,它們都是通用的 ONNX 格式。

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

此外在這次的研讨會中,Model Zoo 的維護者還和大家讨論了目前面臨的問題及解決方法,例如目前的預訓練模型主要集中在計算機視覺方面、ONNX 缺少一些特定的 OP、權重計算圖下載下傳慢等。是以 Model Zoo 接下來也會更關注其它語音和語言等模型,優化整個 GitHub 項目的下載下傳結構。

ONNX Runtime

微軟開源的 ONNX Runtime 推理引擎支援 ONNX 中定義的所有運算單元,它非常關注靈活性和推理性能。是以不論我們的開發環境是什麼,Runtime 都會基于各種平台與硬體選擇不同的自定義加速器,并希望以最小的計算延遲和資源占用完成推理。

ONNX Runtime 可以自動調用各種硬體加速器,例如英偉達的 CUDA、TensorRT 和英特爾的 MKL-DNN、nGraph。如下所示,ONNX 格式的模型可以傳入到藍色部分的 Runtime,并自動完成計算圖分割及并行化處理,最後我們隻需要如橙色所示的輸入資料和輸出結果就行了。

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

其實在實際使用的時候,開發者根本不需要考慮藍色的部分,不論是編譯還是推理,代碼都賊簡單。如下所示,導入 onnxruntime 子產品後,調用 InferenceSession() 方法就能導入 ONNX 格式的模型,并完成上圖一系列複雜的優化。最後隻需要 session.run() 就可以進行推理了,所有的優化過程都隐藏了細節。

import onnxruntime
session = onnxruntime.InferenceSession("your_model.onnx") 
prediction = session.run(None, {"input1": value})      

在研讨會中,開發者表示 Runtime 的目标是建構高性能推理引擎,它需要利用最好的加速器和完整的平台支援。隻需要幾行代碼就能把計算圖優化一遍,這對 ONNX 格式的模型是個大福利。

ONNX.JS

ONNX.js 是一個在浏覽器上運作 ONNX 模型的庫,它采用了 WebAssembly 和 WebGL 技術,并在 CPU 或 GPU 上推理 ONNX 格式的預訓練模型。

通過 ONNX.js,開發者可以直接将預訓練的 ONNX 模型部署到浏覽器,這些預訓練模型可以是 Model Zoo 中的,也可以是自行轉換的。部署到浏覽器有很大的優勢,它能減少伺服器與用戶端之間的資訊交流,并獲得免安裝和跨平台的機器學習模型體驗。如下所示為部署到網頁端的 SqueezeNet:

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

如上若是選擇 GPU,它會采用 WebGL 通路 GPU。如果選擇 CPU,那麼其不僅會采用 WebAssembly 以接近原生的速度執行模型,同時也會采用 Web Workers 提供的「多線程」環境來并行化資料處理。該項目表明,通過充分利用 WebAssembly 和 Web Workers,CPU 可以獲得很大的性能提升。這一點在項目提供的 Benchmarks 中也有相應的展示:

開源一年多的模型交換格式ONNX,已經一統架構江湖了?

以上微軟在 Chrome 和 Edge 浏覽器中測試了 ResNet-50 的推理速度,其中比較顯著的是 CPU 的推理速度。這主要是因為 Keras.js 和 TensorFlow.js 在任何浏覽器中都不支援 WebAssembly。

最後,從 ONNXIFI 到 ONNX.js,開源社群已經為 ONNX 格式建構出衆多的優化庫、轉換器和資源。很多需要支援多架構的場景也都将其作為預設的神經網絡格式,也許以後,ONNX 真的能統一神經網絡之間的江湖。

繼續閱讀