沒(méi)人告訴你的大規(guī)模部署AI高效流程!
現(xiàn)在有許多關(guān)于 AI 的教程。比如如何進(jìn)行目標(biāo)檢測(cè)、圖像分類、NLP 以及構(gòu)建聊天機(jī)器人等,不勝枚舉。
但當(dāng)我查找如何正確擴(kuò)展 AI 的內(nèi)容時(shí),卻發(fā)現(xiàn)少得可憐。更令人驚訝的是,現(xiàn)有的極少數(shù)資源在反復(fù)強(qiáng)調(diào)相同的幾點(diǎn):
- 用像 TensorFlow 這樣的可擴(kuò)展框架構(gòu)建模型。
- 將其打包到客戶端(TF.js、TF Lite、TF-slim 等)或部署為基于容器的微服務(wù)。
我對(duì)第二點(diǎn)更感興趣,因?yàn)槲乙呀?jīng)開(kāi)發(fā)好了一個(gè)模型,但令我驚訝的是,沒(méi)有任何關(guān)于如何實(shí)現(xiàn)第二點(diǎn)的細(xì)節(jié),而關(guān)于每個(gè)解決方案缺點(diǎn)的信息則更少。研究了幾天并在 Crane.ai 上擴(kuò)展 AI 之后,我整理了一些關(guān)于如何部署這些方法、這些方法的缺點(diǎn)以及如何在低級(jí)別優(yōu)化 TensorFlow 模型的內(nèi)容。
將模型打包到客戶端——這種方法太糟了!
最常用的方法之一是用像 TensorFlow.js、TF Lite 或 TensorFlow Slim 這樣的工具將 AI 打包到你所選擇的客戶端中。我不會(huì)詳細(xì)介紹這些框架如何運(yùn)行,但我會(huì)重點(diǎn)說(shuō)明它們的缺點(diǎn)。
- 計(jì)算能力。部署這些模型的問(wèn)題在于它們需要大量的內(nèi)存(我指的是移動(dòng)應(yīng)用程序或?yàn)g覽器的限制,即 > 1-2GB RAM)。許多手機(jī)都沒(méi)有這樣的計(jì)算能力,而桌面瀏覽器又會(huì)延遲 UI 線程,同時(shí)也降低了用戶的計(jì)算機(jī)速度,要打開(kāi)瀏覽器,還要打開(kāi)風(fēng)扇等等。
- 推斷時(shí)間。當(dāng)你在計(jì)算能力未知的設(shè)備上運(yùn)行模型時(shí),推斷時(shí)間一般也是未知的;這些設(shè)備不是 GPU 驅(qū)動(dòng)的高 RAM、高 CPU 機(jī)器,它們只是在普通計(jì)算機(jī)上運(yùn)行的手機(jī)、瀏覽器和桌面應(yīng)用程序。較大模型的推斷時(shí)間可以輕松超過(guò)一分鐘時(shí)間,而從用戶體驗(yàn)的角度來(lái)看,這并不可行。
- 大文件。不幸的是大多數(shù)模型都存儲(chǔ)在相當(dāng)大的文件中(我指的是數(shù)十、數(shù)百 MB)。因此,加載這些文件速度很慢,需要的內(nèi)存量比較大,也大幅增加了應(yīng)用程序包的大小。
- 不安全。除非你用的是開(kāi)源模型,否則你要相對(duì)保密你的 AI 模型和預(yù)訓(xùn)練檢查點(diǎn)。然而,當(dāng)你將模型打包進(jìn)應(yīng)用程序時(shí),不僅你的推斷代碼容易被反編譯,而且在應(yīng)用程序包里的預(yù)訓(xùn)練檢查點(diǎn)也很容易被竊取。
- 難以更新。如果想要更新你的模型,在客戶端中你有兩個(gè)選擇。要么通過(guò)集中管理器(即 Play Store、App Store 等)發(fā)布更新,這會(huì)導(dǎo)致頻繁的大型更新(對(duì)用戶而言這是很煩人的,而且用戶可以根據(jù)設(shè)置打斷這個(gè)過(guò)程,或者壓根就不開(kāi)啟更新)?;蛘邞?yīng)用程序本身可以獲取新模型的檢查點(diǎn)和元數(shù)據(jù)。后者聽(tīng)起來(lái)要好得多,但是這意味著你可能要在用戶連接不穩(wěn)定的情況下下載 100MB 以上的文件,這可能需要一段時(shí)間,所以你的應(yīng)用程序至少要在后臺(tái)開(kāi)到下載過(guò)程完成,而且會(huì)產(chǎn)生很高的互聯(lián)網(wǎng)輸出成本(這取決于你的云計(jì)算)。
- 缺乏可訓(xùn)練性。針對(duì)新用戶的數(shù)據(jù)訓(xùn)練的模型提供了一定程度的個(gè)性化,同時(shí)提高了準(zhǔn)確率,并建立了核心的高信號(hào)數(shù)據(jù)集。不幸的是大部分設(shè)備缺乏訓(xùn)練模型的計(jì)算能力,即便它們的計(jì)算能力夠了,也無(wú)法將訓(xùn)練效果傳遞到服務(wù)器或其他運(yùn)行該應(yīng)用程序的設(shè)備。
這些缺點(diǎn)使得在客戶端上部署和維護(hù)大型神經(jīng)網(wǎng)絡(luò)幾乎不可能,所以我們從擴(kuò)展模型的備選項(xiàng)中排除這一項(xiàng)。
部署為云端點(diǎn)
圖源:https://xkcd.com/908/
云是可以大規(guī)模部署模型的強(qiáng)大工具。你可以根據(jù)需要定制環(huán)境、容器化應(yīng)用程序、立即水平擴(kuò)展應(yīng)用程序,同時(shí)提供足以和大公司媲美的 SLA 和運(yùn)行時(shí)間。
對(duì)大部分 TensorFlow 模型來(lái)說(shuō),部署流程是相同的:
- 將圖像固化為 Protobuf 二進(jìn)制文件
- 調(diào)整推斷代碼,使它可以處理固化的圖
- 容器化應(yīng)用程序
- 在最上面加上 API 層
第一部分相對(duì)簡(jiǎn)單?!腹袒箞D要用所有命名節(jié)點(diǎn)、權(quán)重、架構(gòu)和檢查點(diǎn)元數(shù)據(jù)創(chuàng)建一個(gè) protobuf 二進(jìn)制文件。這一步可以用多種工具實(shí)現(xiàn),最常用的是 TF 自己的工具,它可以固化任何給定輸出節(jié)點(diǎn)名字的圖。
更多該技術(shù)相關(guān)信息以及實(shí)現(xiàn)參閱: https://www.tensorflow.org/guide/extend/model_files#freezing。
調(diào)整推斷代碼也不難。在大多數(shù)情況下,feed_dict 是不變的,主要區(qū)別在于添加了加載模型的代碼,也許還有輸出節(jié)點(diǎn)的規(guī)范。
容器化也很簡(jiǎn)單——只要在 Dockerfile 中設(shè)置環(huán)境即可。而當(dāng)我們開(kāi)始添加 API 層時(shí),事情就會(huì)變得混亂。通常用這兩種方法:
部署可以運(yùn)行推斷腳本的擴(kuò)展容器。這些容器根據(jù)輸入運(yùn)行腳本,腳本啟動(dòng)一個(gè)會(huì)話并執(zhí)行推斷,再通過(guò)管道返回輸出結(jié)果。這是很有問(wèn)題的:對(duì)大多數(shù)云供應(yīng)商而言添加一個(gè)可以操縱容器和管道進(jìn)出的 API 層并不容易(例如,AWS 有 API 網(wǎng)關(guān),但它并不像你期望的那么方便),而且這種方法是你可以采用的效率最低的方法。這里的問(wèn)題是你在啟動(dòng)容器、分配硬件、啟動(dòng)會(huì)話以及推斷時(shí)損失的寶貴時(shí)間。如果你讓 stdin 開(kāi)著并保持管道輸出,那么你的腳本就會(huì)加速但是會(huì)失去可擴(kuò)展性(現(xiàn)在你已經(jīng)連接到容器的 STDIN,而它無(wú)法接受多個(gè)請(qǐng)求)。
部署運(yùn)行 API 層的擴(kuò)展容器。盡管在架構(gòu)上相似,但由于以下幾個(gè)原因,這種方法效率更高。將 API 層內(nèi)置在容器中,可以緩解之前提出的大多數(shù)問(wèn)題。雖然這需要更多資源,但它已經(jīng)用了最少資源而且沒(méi)有垂直擴(kuò)展;它允許每個(gè)容器保持運(yùn)行狀態(tài),而且由于這種情況下 API 是分散的,因此可以將特定的 stdin/stout 連接到主要的請(qǐng)求路由器上。這意味著省去了啟動(dòng)時(shí)間,可以在服務(wù)多個(gè)請(qǐng)求的同時(shí)維持速度并保證水平擴(kuò)展。可以用負(fù)載平衡器集中容器,并用 Kubernetes 保證近乎 100% 的運(yùn)行時(shí)間并管理集群。這種方式簡(jiǎn)單且有效。
部署集群!
通過(guò)容器集群分散 API 的主要缺點(diǎn)在于計(jì)算成本會(huì)相對(duì)較快地累積起來(lái)。不幸的是這在 AI 中是不可避免的,但有一些方法可以緩解這一問(wèn)題。
- 重復(fù)使用會(huì)話。集群會(huì)根據(jù)負(fù)載成比例地增長(zhǎng)和收縮,因此你的目標(biāo)是最小化執(zhí)行推斷的時(shí)間,使容器可以釋放出來(lái)處理另外的請(qǐng)求。實(shí)現(xiàn)這一想法的方法是初始化 tf.Session 和 tf.Graph 后就將它們存儲(chǔ)起來(lái)并將它們作為全局變量傳遞,以達(dá)到重復(fù)使用 tf.Session 和 tf.Graph 的目的。對(duì) TF 來(lái)說(shuō),這一舉措可以減少啟動(dòng)會(huì)話和構(gòu)建圖的時(shí)間,從而大大提高推斷任務(wù)的速度。即便是單個(gè)容器,這個(gè)方法也是有效的,而且這一技術(shù)被廣泛用于資源再分配最小化和效率最大化。
- 緩存輸入,如果可能的話還要緩存輸出。在 AI 中,動(dòng)態(tài)規(guī)劃范式在 AI 中是最重要的。通過(guò)緩存輸入,你可以節(jié)省預(yù)處理輸入或從遠(yuǎn)程獲得輸入的時(shí)間;通過(guò)緩存輸出,你可以節(jié)省運(yùn)行推斷的時(shí)間。這在 Python 中很容易實(shí)現(xiàn),但你要視自己的情況而定。通常,你的模型會(huì)隨著時(shí)間的推移變得更好,但這會(huì)很大程度上影響你的輸出緩存機(jī)制。我自己的系統(tǒng)用的是我所謂的「80-20」規(guī)則。當(dāng)模型準(zhǔn)確率低于 80% 時(shí),我不會(huì)緩存任何輸出;一旦準(zhǔn)確率到了 80%,就開(kāi)始緩存并設(shè)置為在準(zhǔn)確率到一定值(而不是某個(gè)時(shí)間點(diǎn))的時(shí)候停止緩存。這樣,隨著模型變得越來(lái)越準(zhǔn)確,輸出也會(huì)發(fā)生變化,但是在「80-20」緩存中,性能和速度之間存在的權(quán)衡更少。
使用任務(wù)隊(duì)列。一般需要運(yùn)行或大或小的推斷任務(wù)(在我們的例子中是較大和較小、復(fù)雜和簡(jiǎn)單的圖像)。對(duì) UX 來(lái)說(shuō),使用堆隊(duì)列(heap queue)可能更好,它會(huì)優(yōu)先處理小一些的任務(wù),所以要運(yùn)行簡(jiǎn)單步驟的用戶只要等這一步結(jié)束就行了,而不必等另一個(gè)用戶的更大推斷任務(wù)先完成。(也許你會(huì)想我在這里為什么不用水平擴(kuò)展,你可以這么做但是會(huì)增加計(jì)算成本)。
在帶有任務(wù)隊(duì)列的專用 GPU 上訓(xùn)練模型。訓(xùn)練是一項(xiàng)長(zhǎng)期、困難的任務(wù),它需要大量可用的資源,而且模型在訓(xùn)練過(guò)程中無(wú)法使用。如果你要將每個(gè)交互返回到模型中進(jìn)行訓(xùn)練,請(qǐng)考慮在單獨(dú)的服務(wù)器或 GPU 上運(yùn)行。一旦訓(xùn)練結(jié)束,你就可以將模型(在 AWS 中,你可以將模型 repo 集中在 S3 中)部署到容器中了。
結(jié)論
深思熟慮后,我們提出了一個(gè)大規(guī)模部署 AI 的高效工作流程:
- 固化圖并將推斷封裝在 API 下
- 重復(fù)使用會(huì)話和圖,緩存輸入和輸出
- 用 Docker 容器化應(yīng)用程序(包括 API 層)
- 將大規(guī)模應(yīng)用程序與 Kubernetes 一起部署在你選擇的云上
- 將訓(xùn)練從推斷中分離出來(lái)
- 建立任務(wù)隊(duì)列,將較小的任務(wù)確立為優(yōu)先級(jí)
使用這些技術(shù),你就可以在成本最小、速度和效率最大的情況下大規(guī)模部署 AI。
原文鏈接:https://towardsdatascience.com/scaling-ai-2be294368504
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】































