對于開發(fā)者而言,我們將整個ExecuTorch技術(shù)棧分為兩個階段。首先,我們從一個PyTorch模型開始,這在大多數(shù)情況下是一個torch.in.module。然后我們從中捕獲圖形,并將其lowering并序列化為額外的torch二進(jìn)制文件。這完成了我們的提前編譯階段。然后我們將二進(jìn)制文件放入device并使用ExecuTorch運(yùn)行時來運(yùn)行。
提前編譯主要有三個步驟。首先是導(dǎo)出(export),將給定的模型(如NN模塊或其他可調(diào)用對象)通過PyTorch2的torch export獲取計算圖。在這個模塊內(nèi)部,我們要列出所有正在進(jìn)行的操作的列表,并且這將產(chǎn)生一個exported program,我們將在以后更詳細(xì)地介紹它。
第二步是調(diào)用to edge,它將產(chǎn)生這個edge program,這是大多數(shù)執(zhí)行器用戶運(yùn)行優(yōu)化和轉(zhuǎn)換的入口點(diǎn)。最后一步,我們將使用to_executorch來執(zhí)行,它會將其變?yōu)橐粋€擴(kuò)展名為.pt的二進(jìn)制文件。然后我們將把它傳遞給運(yùn)行時。
所以更近一步,第一步是導(dǎo)出模型,在這一步中,我們將創(chuàng)建這個模型的圖形表示。這個圖形表示是一個fx圖形,你們中的一些人可能對此很熟悉,但它不包含任何Python語義,允許我們在沒有Python運(yùn)行時的環(huán)境下運(yùn)行。這個圖形只包含操作符調(diào)用。所以如果你熟悉fx圖,就沒有像調(diào)用模塊或調(diào)用方法那樣的東西。所以對我們來說,這是一個非常簡單的圖形。導(dǎo)出產(chǎn)生的結(jié)果是一個exported program,它與torch的NN模塊非常相似,
之后生成的graph,我們將其稱為Aten Dialect graph。因此,通過Dialect,我們指的是此exported program的變體,其由其操作符集和一些其他graph屬性定義。所以,通過Aten,我們指的是該圖表僅包含torch.ops.aten操作符。而且,這些操作符僅為functional操作符,意味著沒有副作用或突變。這涉及到約2000個左右的操作符。一些graph屬性是graph包含元數(shù)據(jù),例如指向原始模型的原始堆棧跟蹤指針,還有g(shù)raph內(nèi)每個節(jié)點(diǎn)的輸出data類型和形狀。我們還可以通過這些特殊的高階操作符在特定輸入上表達(dá)動態(tài)性和控制流。如需了解更多信息,您可以在下午聽Torch Expert的演講。
對量化而言,量化實(shí)際上在導(dǎo)出的中間階段運(yùn)行,因為它需要在更高級的opset上工作,這對于訓(xùn)練等一般情況也更安全。因此,工作流程是我們首先導(dǎo)出到Pre-Autograd,此東西訓(xùn)練是安全的,然后運(yùn)行量化,然后降低到之前提到的Aten Dialect,然后傳遞到執(zhí)行器 pipeline 的其余部分。因此,這個API看起來像是準(zhǔn)備PT2E,用戶傳入一個量化器,然后轉(zhuǎn)換PT2E,然后將其傳遞給堆棧的其余部分。所以,量化器是特定于后端實(shí)現(xiàn)的東西,它告訴用戶在特定后端上可以量化什么以及如何對這些進(jìn)行量化。它還公開了方法,允許用戶表達(dá)他們想要如何量化這個后端。
下一步是lower到Edge程序,這是Executorch用戶運(yùn)行他們的目標(biāo)不可知轉(zhuǎn)換的入口點(diǎn)。此時,我們正在處理這個Edge Dialect,其中只包含一組核心的aten運(yùn)算符。大約只有180個運(yùn)算符。因此,如果你是一個新的后端,正在嘗試實(shí)現(xiàn)Executorch,你只需要實(shí)現(xiàn)這180個運(yùn)算符就可以運(yùn)行大多數(shù)模型,而不是之前來自Aten類的大約2000個運(yùn)算符。
此外,這個方言還具有數(shù)據(jù)類型的專門化,這將允許executorch根據(jù)特定的dtype類型構(gòu)建內(nèi)核,以實(shí)現(xiàn)更優(yōu)化的運(yùn)行時。我們還通過將所有標(biāo)量轉(zhuǎn)換為張量來對輸入進(jìn)行歸一化,從而使這些運(yùn)算符內(nèi)核不必隱式地進(jìn)行這種歸一化。
這個邊緣程序的另一個入口是backend delegation,在這里用戶可以選擇在專門的硬件或后端上優(yōu)化和處理部分或全部程序。通過這種方式,他們可以利用這些專用硬件來處理graph的某些部分。對于此,有大約三種工作流程。第一種是分割和delegation graph的部分。或者我們可以delegation整個圖。或者我們可以做兩者的組合,將這個delegation模塊組合到更top的模塊中。
仔細(xì)看這一點(diǎn),第一步是用戶可以傳遞一個特定于后端的patitioner,它將告訴用戶哪些操作符能在特定的后端上運(yùn)行。然后,to_backend API 將根據(jù)這個分區(qū)器對圖進(jìn)行分區(qū),然后lower這些部分為一個較低層的后端模塊。然后將該模塊傳遞給運(yùn)行時,以告訴后端需要運(yùn)行的確切內(nèi)容。它包含后端的ID,告訴我們正在運(yùn)行的后端是哪個,并且包含一組處理過的片段,告訴專用硬件需要運(yùn)行的內(nèi)容。同時,它還包含用于調(diào)試目的的原始模塊。
第二個流程是將整個graph lower到您的backend中,然后直接將其轉(zhuǎn)換為二進(jìn)制文件,然后傳遞給運(yùn)行時在專用硬件上運(yùn)行。第三個流程是我們將完全delegation的模塊組合到其他模塊中,以便在其他地方重用。
最后,我們可以將其轉(zhuǎn)換并保存為ExecuTorch二進(jìn)制文件。因此,用戶可以開始運(yùn)行特定于后端的轉(zhuǎn)換,例如將運(yùn)算符融合到特定的自定義后端運(yùn)算符中。然后,我們將提前運(yùn)行自定義內(nèi)存規(guī)劃過程,以確定此程序需要多少內(nèi)存。為了準(zhǔn)備graph執(zhí)行此操作,我們首先將運(yùn)行一個out variant pass,其目的是把所有運(yùn)算符變成 out variant.這樣做可以使內(nèi)存規(guī)劃變得更加容易,因為對于一個典型的算子(例如add.tensor),它會在內(nèi)核中為輸出的張量分配內(nèi)存。但是如果我們想要進(jìn)行內(nèi)存規(guī)劃,我們可以使用out variant ,它期望傳入一個預(yù)分配的張量,并在內(nèi)核中將結(jié)果張量賦值給這個預(yù)分配的張量。因此,我們可以運(yùn)行內(nèi)存規(guī)劃,非常輕松地計算張量的生命周期,并提前確定這個程序需要多少內(nèi)存,這樣我們就不需要在運(yùn)行時動態(tài)地進(jìn)行內(nèi)存分配。最后,我們可以將它轉(zhuǎn)換為擴(kuò)展名為.pte的二進(jìn)制文件,并將其傳遞給運(yùn)行時。
現(xiàn)在,孟煒將告訴我們運(yùn)行時發(fā)生了什么。希望在完成所有這些步驟后,我們能夠得到一個.pte文件。我們準(zhǔn)備深入探討運(yùn)行時的過程。
到目前為止,我們假設(shè)開發(fā)人員能夠按照Angela介紹的所有步驟進(jìn)行操作。希望就像打個響指,我們就能夠獲取Executorch的二進(jìn)制文件?,F(xiàn)在我們要考慮一些需求,比如我們該如何運(yùn)行這個二進(jìn)制文件。從開發(fā)者的角度來看,他們可能會問的第一個自然問題是:它真的能在我的目標(biāo)設(shè)備上運(yùn)行嗎?其中一些可能有CPU,或者一些可能有…甚至是微控制器。我們需要確保Executorch Runtime能夠在所有這些平臺上編譯和運(yùn)行。
現(xiàn)在我們能夠在目標(biāo)設(shè)備上編譯Executorch Runtime后,開發(fā)者可能會提出一個后續(xù)問題。我的目標(biāo)設(shè)備有一個非常特殊的地方…它包含兩個內(nèi)存緩沖區(qū)。一個速度非??斓苄?,但另一個很慢但非常龐大。Executorch Runtime是否支持這種硬件配置呢?我認(rèn)為這就是提出了我們的第二個需求,我們應(yīng)該能夠支持開發(fā)者想要的定制性,支持我們的開發(fā)人員想要的所有自定義。
好了,現(xiàn)在Executorch程序能夠在目標(biāo)設(shè)備上運(yùn)行了。開發(fā)人員可能并不在意性能問題。請注意,設(shè)備上的AI處于資源受限的環(huán)境中。這意味著效率和性能至關(guān)重要。因此,我們需要確保Executorch Runtime具備高性能。因此,讓我向您介紹一些我們?yōu)闈M足這些要求而所做的工作。
讓我們談?wù)効梢浦残浴N覀內(nèi)绾螡M足可移植性要求?目標(biāo)設(shè)備包括不同的操作系統(tǒng)、不同的體系結(jié)構(gòu),甚至不同的工具鏈。我們解決所有這些要求的方法是隱藏系統(tǒng)級細(xì)節(jié)。我們通過提供一個平臺抽象層來實(shí)現(xiàn)。這一層提供了一組統(tǒng)一的API,用于基本功能,如日志記錄、時鐘,并抽象了許多平臺特定的實(shí)現(xiàn)細(xì)節(jié)。同樣,我們還提供了數(shù)據(jù)加載器和內(nèi)存分配器。訪問Executorch runtime以與操作系統(tǒng)進(jìn)行通信。最后但并非最不重要的是,我們確保我們的Executorch runtime遵循C++11標(biāo)準(zhǔn),這是大多數(shù)工具鏈所接受的。
好的,接下來讓我們談?wù)効啥ㄖ菩?。Executorch 二進(jìn)制文件是編譯和運(yùn)行時之間的唯一橋梁。從這個意義上說,為了支持可定制性,我們必須設(shè)計二進(jìn)制文件的架構(gòu)。它只存儲高級標(biāo)識符。我們只存儲運(yùn)算符名稱和內(nèi)存池ID,但對此沒有任何意見。實(shí)際上,它是在運(yùn)行時上運(yùn)行的內(nèi)核。
這樣,我們?yōu)槎ㄖ崎_啟了很多機(jī)會。讓我們談?wù)剝?nèi)核的可定制性。我想要強(qiáng)調(diào)的一件事是允許用戶帶入他們自己的內(nèi)核。我們提供了一個內(nèi)部的可移植內(nèi)核庫。但它并不旨在優(yōu)化性能。因此,我們允許用戶帶入他們自己的內(nèi)核。注冊自定義內(nèi)核的方法非常簡單,開發(fā)者只需要按照核心 aten 運(yùn)算符的命名約定。然后,他們可以使用一個構(gòu)建工具為它們的庫注冊內(nèi)核。這個庫將幫助將他們的內(nèi)核注冊到 Executorch 運(yùn)行時中。
還有一件事我想提一下,如果開發(fā)者提供了模型級操作符信息,構(gòu)建工具會智能地只注冊必要的信息,這樣我們可以縮小二進(jìn)制文件的大小。
不同的環(huán)境對內(nèi)存有非常特殊的需求,所以我們提供了名為MemoryManager的對象,允許用戶進(jìn)行大量的自定義配置。內(nèi)存管理器由兩個內(nèi)存分配器組成。其中一個用于初始化過程,另一個用于內(nèi)核和委托執(zhí)行過程。除此之外,我們還為張量內(nèi)存分配提供了一系列內(nèi)存緩沖區(qū)。這樣就完成了定制功能。
現(xiàn)在讓我們稍微談?wù)勑阅芤约拔覀內(nèi)绾螡M足性能需求。我們確保我們的Executorch Torch運(yùn)行時在內(nèi)核和委托調(diào)用之間的開銷非常低。這是通過在執(zhí)行之前準(zhǔn)備輸入和輸出張量來實(shí)現(xiàn)的。而用戶只需要付出一次代價,即使他們想多次運(yùn)行該模型。我們確保ExecuteTorch運(yùn)行時的第二個原則是保持一個非常小的二進(jìn)制文件大小以及內(nèi)存占用,這是通過將復(fù)雜邏輯和動態(tài)性推移到提前編譯器中來實(shí)現(xiàn)的,并確保Executorch運(yùn)行時邏輯。
最后但并非最不重要的是,我們還提供了一套非常方便的性能調(diào)試工具,它在這個SDK中。讓我多談一點(diǎn),我們提供了幾個非常有用的API。其中之一是捆綁程序,我們可以將示例輸入綁定到模型中。這樣就能實(shí)現(xiàn)非??焖俚膱?zhí)行。我們還提供調(diào)試和分析工具。分析器能夠?qū)⒔y(tǒng)計數(shù)據(jù)連接到運(yùn)算符上。為我們的程序確定瓶頸非常有幫助。所有這些都提供了Python API,以確保開發(fā)人員能夠輕松使用它們。我談了很多關(guān)于我們運(yùn)行時的組件,但是我們?nèi)绾螌⑺鼈冞B接在一起并確保其正常工作呢?
這是一個圖表。基本上,我們加載Executorch的.pte文件。然后我們進(jìn)行一些初始化,包括加載程序和加載方法。最后,我們可以執(zhí)行它。使用SDK工具來覆蓋整個流程,確保每一步都是正確的。
那么在初始化階段,我們在做什么呢?基本上,我們?yōu)镻yTorch模型的概念創(chuàng)建了C++對象。如您所見,根抽象稱為program,類似于nn.module模塊。一個program可以有多個方法。一個方法可能具有多個操作符,即kernel對象。
當(dāng)我們加載程序時,實(shí)際上我們提供了數(shù)據(jù)加載器接口以便能夠加載二進(jìn)制文件。請注意,我們對文件系統(tǒng)不做任何假設(shè),所以用戶可以自由實(shí)現(xiàn)這個接口并在目標(biāo)設(shè)備上加載程序。加載程序?qū)⑹褂迷摂?shù)據(jù)加載器進(jìn)行二進(jìn)制文件的合法性檢查。同時,用戶也可以在初始化階段提供內(nèi)存管理器。我想要強(qiáng)調(diào)的一點(diǎn)是用戶可以管理自己的內(nèi)存。
初始化的最后一步是調(diào)用加載方法,開發(fā)者需要提供他們想要執(zhí)行的方法名稱,還有內(nèi)存管理器。
最后進(jìn)入到執(zhí)行的階段:這是很簡單的循環(huán)遍歷所有指令,執(zhí)行能夠通過跳轉(zhuǎn)到特定的指令來處理控制流,并且每個指令參數(shù)都是Evalue(wrap all arg types).我們可以把他 unboxex 到不同的基礎(chǔ)類型(tensor,scalar),我們還有一個內(nèi)核運(yùn)行時上下文可以處理一些事情。
你可以查看安卓和ios的演示,github教程可用。到此結(jié)束,謝謝。
審核編輯:劉清
-
二進(jìn)制
+關(guān)注
關(guān)注
2文章
795瀏覽量
41643 -
可穿戴設(shè)備
+關(guān)注
關(guān)注
55文章
3814瀏覽量
167005 -
pytorch
+關(guān)注
關(guān)注
2文章
807瀏覽量
13198
原文標(biāo)題:《PytorchConference2023 翻譯系列》15-PyTorch-Edge-在邊緣設(shè)備上部署AI模型的開發(fā)者之旅
文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論