輕鬆調用TensorFlow、PyTorch等多框架,Uber開源深度學習推理引擎Neuropod

近日,Uber ATG開源了深度學習推理引擎Neuropod,對外只提供一個通用接口用於運行深度學習模型,而底層可以調用不同的深度學習框架,如TensorFlow、PyTorch、Caffe2、Keras等。這將有助於企業在異構環境中大規模部署多個DL/ML模型。

優步先進技術團隊(Uber Advanced Technologies Group,ATG),我們利用深度學習來提供安全可靠的自動駕駛技術。利用深度學習,我們可以建立並訓練模型來處理諸如傳感器輸入、識別對象以及預測這些對象可能去的地方等任務。

隨着自動駕駛軟件的發展,我們一直在尋找新的方法來改進模型。有時候,這意味着要試驗不同的深度學習框架。隨着新的深度學習框架的發佈,以及 TensorFlowPyTorch 等現有框架取得的進步,我們希望確保工程師和科學家能夠靈活地使用最適合他們正在研究的問題的工具。

然而不幸的是,要在整個機器學習棧中添加對一個新的深度學習框架的支持,既費資源,又費時間。在 ATG,我們已經花了大量時間來簡化這個過程。今天,我們通過引入 Neuropod 將這些工作成果予以開源。Neuropod 是現有深度學習框架之上的一個抽象層,它提供了一個統一的接口來運行深度學習模型。Neuropod 讓研究人員可以輕鬆地在自己選擇的框架中構建模型,同時也簡化了這些模型的生產化過程。

使用多種深度學習框架

深度學習的發展非常迅速,不同的深度學習框架在不同的任務上有效。因此,在過去的幾年裏,Uber ATG 使用過多種深度學習框架。2016 年,Caffe2 是我們主要的深度學習框架;2017 年初,我們投入了大量工作來集成 TensorFlow。這涉及到與 CUDA 和 cuDNN 的主要集成障礙、Caffe2 和 TensorFlow 的依賴衝突、庫加載問題等。2017 年底,我們開始在 PyTorch 上開發更多的模型。生產這些模型本身就需要大量的工作,而在與 TensorFlow 同時運行時,我們還發現了內存損壞問題,以及其他幾個非常難以調試的問題。然後,TorchScript 發佈了,我們又一次經歷了類似的過程。

圖 1. 多年來,Uber ATG 利用不同的流行深度學習框架,發展了其機器學習方法。

自 2018 年以來,有許多深度學習框架已經開源,包括 Ludwig(由 Uber 創建)、JAXTraxFlaxHaikuRLax,其中許多框架都是過去九個月內發佈的。

即便研究人員可以很容易地試驗新框架,但在我們所有的系統和流程中添加對新深度學習框架的生產支持,也是一項艱鉅的任務。其中有部分是因爲,它需要對基礎設施和工具的每個部分逐一進行集成和優化。

圖 2. 添加對新框架的支持很困難,因爲運行模型的每個基礎設施都需要支持所有框架。這些基礎設施組件可以是度量管道、模型服務或其他生產和測試環境。

在 2018 年末,Uber ATG 開始構建多種模型,嘗試用不同的方式解決同一問題。例如,利用光學雷達進行 3D 目標檢測,可以採用範圍視圖鳥瞰視圖。這兩種方法都是有效的,但各有優缺點。不同團隊構建的模型有時也在不同的框架中實現。

爲了使生產化更容易,我們希望能夠輕鬆地替換解決相同問題的模型,即使它們是在不同的框架中實現的。

我們也會遇到一些其他的情況,比如新的研究將基於PyTorch編寫代碼,但我們想要快速地與 TensorFlow 中的現有模型進行比較。因爲每個模型都有特定於框架的、模型級的度量管道,所以比較就很難做到了。

爲了在新框架中部署模型,我們需要重建模型級的度量管道,在我們所有的系統和流程中對框架進行集成,然後進行額外的優化,以確保我們能在延遲預算範圍內有效地運行模型。

雖然這些步驟看上去很簡單,但諸如上述問題(如內存損壞、依賴衝突等)導致我們耗費大量精力來解決,而不能專注於模型開發。

我們需要一種方法來最大程度地提高研究過程中的靈活性,而不必在過程的其他部分重複工作。

Neuropod 介紹

我們針對這一問題的解決方案是 Neuropod,這是一個開源庫,它能使所有深度學習框架在運行模型時看起來都是一樣的。有了Neuropod,在所有工具和基礎設施中添加對新框架的支持,就像將其添加到 Neuropod 中一樣簡單。

圖3. Neuropod 是一個抽象層,提供了一個統一的接口,可以基於多個框架運行深度學習模型。

目前,Neuropod 支持的框架包括:TensorFlow、PyTorch、Keras 和 TorchScript,同時也可以輕鬆地添加新的框架。

自 2019 年初在內部發布以來,Neuropod 在 Uber 快速部署新模型方面發揮了重要作用。在過去的一年裏,我們已經在 Uber ATG、Uber AI 和 Uber 的核心業務部署了數百個 Neuropod 模型。這些模型包括需求預測模型、預計到達時間(Estimated time of arrival,ETA)預測、Uber Eats 的菜單轉錄以及用於自動駕駛汽車的目標檢測模型。隨着 Neuropod 的開源發佈,我們希望機器學習社區的其他人也會發現它能幫上大忙!

概述

Neuropod 從問題定義的概念開始:模型要解決的“問題”的形式化描述。在這種情況下,問題可能是圖像的語義分割或文本的語言翻譯。通過形式化地定義問題,我們可以將其視爲一個接口,並抽象出具體的實現。每個 Neuropod 模型都實現了一個問題定義。因此,解決相同問題的任何模型都是可互換的,即使它們使用不同的框架。

Neuropod 的工作原理是將現有模型包裝在 Neuropod 包(或簡稱爲“Neuropod”)中。此包包含原始模型以及元數據、測試數據和自定義操作(如果有的話)。

使用 Neuropod,任何模型都能以任何支持的語言執行。例如,如果用戶想基於 C++運行 PyTorch 模型,Neuropod 將在後臺啓動一個 Python 解釋器,並與其通信來運行模型。這麼做是必要的,因爲 PyTorch 模型需要 Python 解釋器才能運行。這一功能允許我們在將 PyTorch 模型轉換爲 TorchScript 之前進行快速測試,而 TorchScript 可以在 C++上直接運行。

Neuropod 目前支持基於Python 和 C++運行模型。但是,爲庫編寫額外的語言綁定也很簡單。例如,Uber 的機器學習平臺 Michelangelo,使用 Neuropod 作爲其核心深度學習模型格式,並實現了 Go 綁定來從 Go 運行其生產模型。

圖 4. Neuropod 提供了特定於框架的封裝 API 和與框架無關的推理 API。

推理概述

在我們深入探討 Neuropod 的工作原理之前,讓我們先看看過去用傳統方法是如何將深度學習模型集成到應用程序中的:

圖 5. 通常,應用程序在整個推理過程中直接與深度學習框架交互。

在上圖中,應用程序在推理過程中的所有部分都是直接與 TensorFlow API 進行交互的。

通過使用 Neuropod,應用程序只與與框架無關的 API 進行交互(下面的所有紫色部分),並且 Neuropod 將這些與框架無關的調用轉換爲對底層框架的調用。我們儘可能使用零拷貝操作來高效地實現這一點。更多細節請參閱下面的“優化”部分。

圖 6. 使用 Neuropod,應用程序可以與框架無關的 API 進行交互,而 Neuropod 與底層框架進行交互。

Neuropod 有一個可插拔的後端層,每個支持的框架都有自己的實現。這使得向 Neuropod 添加新框架變得非常簡單。

使用 Neuropod 進行深度學習

讓我們看一下使用 Neuropod 時的整個深度學習過程,以瞭解它是如何幫助簡化實驗、部署和迭代的。

問題定義

要封裝 Neuropod,我們必須首先創建一個問題定義。如上所述,這是對我們試圖解決的問題的輸入和輸出的規範描述。這個定義包括所有輸入和輸出張量的名稱、數據類型和形狀。例如,2D 目標檢測的問題定義可能是類似下面這樣的:

注意,上面的定義在形狀定義中使用了“符號”(num_classesnum_detections)。符號的每個實例都必須在運行時解析爲相同的值。與僅將形狀元素設置爲 None 相比,這提供了一種更可靠的約束形狀的方式。在上面的例子中,num_detectionsboxesobject_class_probability 必須是相同的。

爲簡單起見,我們將在本文中使用一個更簡單的問題:加法。

上面的代碼段還定義了測試輸入和輸出數據,我們將在下面討論。

生成佔位符模型

一旦定義了問題,我們就可以使用 Neuropod 自動生成一個佔位符模型來實現問題規範。這允許我們在沒有實際模型的情況下開始集成。

生成的模型接受問題規範中描述的輸入,並返回與輸出規範匹配的隨機數據。

構建模型

在建立了問題定義(並可選地生成佔位符模型)之後,我們就可以構建模型了。我們完成了構建和訓練模型的所有正常步驟,但現在我們在該過程的最後添加了一個 Neuropod 導出步驟:

在上面的代碼段中,我們將模型導出爲 Neuropod,同時還提供了可選的測試數據。如果提供了測試數據,庫將在導出後立即對模型進行自檢。

Create_TensorFlow_Neuropod和所有其他封裝程序的選項都有很好的文檔說明。

構建度量管道

現在我們有了自己的模型,就可以用 Python 爲這個問題構建度量管道。與沒有 Neuropod 的情況下做這件事的唯一區別是,我們現在使用 Neuropod Python 庫來運行模型,而不是使用特定於框架的 API。

Neuropod 的文檔包含有關 load_neuropodinfer 的更多詳細信息。

集成

現在,我們可以將模型集成到生產 C++系統中。下面的示例顯示了 Neuropod C++ API 的使用非常簡單,但該庫也支持更復雜的使用,支持高效的零拷貝操作和包裝現有內存。更多細節,請參考 Neuropod 文檔

與沒有 Neuropod 的集成過程不同的是,這個步驟對於每個問題只需執行一次,而不是每個框架執行一次。用戶無需理解 TensorFlow 或者 Torch C++ API 的複雜性,但當研究人員決定要使用哪種框架時,仍然可以提供很大的靈活性。

此外,由於核心 Neuropod 庫是用 C++ 編寫的,因此,我們可以爲其他各種編程語言(包括 Go、Java 等)編寫綁定。

優化

在 Uber ATG,我們對延遲的要求相當嚴格,因此對許多關鍵操作都有零拷貝路徑。我們在分析和優化方面投入了大量工作,現在,Neuropod 可以成爲實現適用於所有模型的推理優化的核心位置。

作爲這項工作的一部分,每個 Neuropod 提交都在持續集成(CI)管道中的以下平臺上進行測試:

  • Mac、Linux、Linux(GPU)
  • Python 的五個版本
  • 每個支持的深度學習框架的五個版本

要了解支持的平臺和框架的最新列表,請查看文檔

Neuropod 還提供了一種使用高性能共享內存橋在工作進程中運行模型的方法,這使得我們可以在不引入顯著延遲損失的情況下,將模型彼此隔離開來。我們將在本文末尾更詳細地討論這一點。

迭代

一旦我們構建並集成了模型的第一個版本,就可以迭代並改進解決方案。

作爲這個過程的一部分,如果我們想嘗試用 TorchScript 模型來代替上面創建的 TensorFlow 模型,那就可以直接替換。

如果沒有 Neuropod,就將需要重新執行之前的許多步驟。有了 Neuropod,任何實現相同問題規範的模型都是可互換的。我們可以重用前面創建的度量管道以及之前做的所有集成工作。

所有運行模型的系統和流程都與框架無關,從而在構建模型時提供了更多的靈活性。Neuropod 讓用戶專注於他們試圖解決的問題,而不是他們用來解決問題的技術。

與問題無關的工具

儘管 Neuropod 是從關注一個“問題”開始的,比如給定圖像的 2D 目標檢測,文本的情感分析等等,但我們可以在 Neuropod 的基礎上構建一個與框架和問題均無關的工具。這讓我們能夠構建通用的基礎設施,可以用於任何模型。

規範輸入構建管道

Neuropod 的一個有趣的、與問題無關的用例是規範輸入構建管道。在 ATG,我們有一個已定義的格式/規範,用於說明如何以張量表示輸入數據。這涵蓋了傳感器數據,包括光學雷達、雷達、相機圖像以及其他信息,如高分辨率地圖等。定義這種標準格式,使得在訓練方面管理超大型數據集變得輕鬆容易。這也使我們能夠快速構建新模型,因爲我們的許多模型都使用這些輸入數據的子集(例如,只對相機和光學雷達進行操作的模型)。

通過將這種通用輸入格式與 Neuropod 結合起來,我們可以構建一個單一的優化輸入構建管道,供我們所有的模型使用,而不管它們是在什麼框架中實現的。

只要每個模型都使用同一組特性的子集,輸入構建器就與問題無關。輸入構建器中的任何優化都有助於改進我們所有的模型。

圖 7. Neuropod 允許我們構建單一的、優化的輸入構建管道,該管道可以與許多模型一起工作,而不必爲每個模型或每個框架構建一個單獨的管道。

模型服務

另一個與問題無關的基礎設施是模型服務。在 Uber ATG,我們有一些模型需要大量的輸入,而且模型本身也很大。對於部分模型,在諸如離線模擬之類的任務中在 CPU 上運行是不可行的。從資源效率的角度來看,在所有集羣機器中包含 GPU 是沒有意義的,因此,我們提供了一種服務,可以讓用戶在遠程 GPU 上運行模型。這與基於 gRPC 的模型服務非常相似。

如果沒有 Neuropod,那麼模型服務平臺就需要擅長遠程運行 Keras、遠程運行 TensorFlow、遠程運行 PyTorch、遠程運行 TorchScript 等。它可能需要實現序列化、反序列化、與 C++ API 的交互,以及對每個框架的優化。所有的應用還需要擅長在本地運行所有這些框架的模型。

因爲在本地和遠程運行有不同的實現,所以整個系統需要關注 2 * # of frameworks 情況。

但是,通過使用 Neuropod,模型服務可以很好地遠程運行 Neuropod,而 Neuropod 可以很好地在多個框架中運行模型。

通過將關注點分開,系統必須關注的案例數量以相機啊的方式 (2 + # framworks) ,而不是以乘積的方式增加。隨着越來越多的框架得到支持,這種差異就變得更加明顯。

不過,我們可以讓它變得更強大。如果我們添加模型服務作爲 Neuropod 後端,任何基礎設施都可以輕鬆地遠程運行模型。

圖 8. 添加模型服務作爲Neuropod後端,允許任何使用 Neuropod 遠程運行模型的應用程序,而無需進行重大更改。

在後臺,它可能看起來像這樣:

圖 9. 應用程序可以使用 Neuropod 將模型執行代理到遠程機器。

這個解決方案不是特定於問題或特定於框架的,而是爲使用 Neuropod 的應用程序提供了更大的靈活性。

進程外執行

圖 10. Neuropod 支持使用低延遲共享內存橋在獨立的工作進程中運行模型。

由於 Uber ATG 的輸入通常很大,並且我們對延遲非常敏感,所以本地的 gRPC 並不是分離模型的理想方法。相反,我們可以使用優化的基於進程外執行(Out-of-process Execution,OPE)的共享內存實現來避免複製數據。這讓我們可以將模型彼此隔離開來,而不會造成嚴重的延遲損失。

這種隔離很重要,因爲在過去,我們已經看到過在統一進程中運行的框架之間存在衝突,包括微妙的 CUDA 錯誤和內存損壞。這些問題都很難追查。

在運行 Python 模型時,這種隔離也有助於提高性能,因爲它能讓用戶避免在所有模型之間共享 GIL。

在單獨的工作進程中運行每個模型還可以啓用“下一步”章節中提到的一些附加功能。

下一步

Neuropod 使 Uber 能夠快速構建和部署新的深度學習模型,但這僅僅是開始。

我們正在積極開展的工作包括:

  1. 版本選擇:該功能使用戶能夠在導出模型時,指定框架所需的版本範圍。例如,一個模型可能需要 TensorFlow 1.13.1,而 Neuropod 將使用正確版本的框架的 OPE 自動運行該模型。這使得用戶能夠在一個應用程序中使用多個框架和每個框架的多個版本。
  2. 封裝操作:這個功能使應用程序能夠指定使用張量“完成”的時間。一旦張量被密封,Neuropod 可以在推理運行之前將數據異步傳輸到正確的目的地(例如本地的 GPU 或輔助進程等)。這有助於用戶並行化數據傳輸和計算。
  3. Dockerized 工作進程:這樣可以在模型之間提供更多的隔離。例如,使用此功能,即使需要不同 CUDA 版本的模型也可以在同一應用程序中運行。

隨着我們繼續通過增強當前功能並引入新功能來擴展 Neuropod 時,我們期待着與開源社區合作來改進這個庫。

Neuropod 在 Uber 的各種深度學習團隊中都非常有用,我們希望它也能對其他人有所幫助。請試用一下吧!

作者介紹:

Vivek Panyam,Uber ATG 高級自動駕駛工程師。在感知團隊工作,領導 Neuropod 的開發。此前,曾帶領團隊將深度學習和機器學習模型集成到自動駕駛車隊中。

原文鏈接:

https://eng.uber.com/introducing-neuropod/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章