算子算力計算


1. 僅基於算力或者算子算法的公司成功率比較低,只有少數頭部企業贏者通吃:反例寒武紀被華為拋棄,正例如英偉達也要持續觀察,即便是正例,也要看到TESLA拋棄它做FSD,蔚來小鵬蠢蠢欲動想做自己的芯片
2. 基於場景看比較容易成功,軟硬結合護城河比較深:比如監控識別的海康大華,自動駕駛的Mobileye、可能成功的地平線。掃地機器人的科沃斯和石頭科技,都是對細分場景理解深刻的。
3.大數據的積累非常重要,自學習的基礎:自動駕駛的路測數據;醫院的CT片、案例等
嵌入式AI框架Tengine的架構、算子定制和引擎推理

本文主要分為以下幾個部分:

1、嵌入式AI面臨的挑戰和Tengine的解決方案

2、Tengine架構解析

3、Tengine API簡介

4、實踐1:Tengine擴展,定制和添加算子

5、實踐2:Tengine在CPU/GPU/NPU/DLA上的推理

Tengine是一個嵌入式AI計算框架,公司的一個核心產品,首先在算力層面做了許多工作,通過與國內眾多芯片廠商建立深度合作關系,采用各種技術方案去充分發揮硬件的計算性能,所以,在全力打造一個AI的算力生態平台,希望通過Tengine,能夠很方便的調用到底層芯片的算力。

其次,提供一系列的產品和工具包,方便算法訓練出來后,面對端側落地時需要解決的各種問題,能夠有一套標准且快速的方法去解決這些問題,加速整個AI產業的落地。這是一個目標,所以未來的Tengine會演化為一個AIoT的開發和部署平台,而不僅僅是一個推理框架。

嵌入式AI面臨的挑戰和Tengine的解決方案

 

 目前,嵌入式AI存在哪些問題呢?首先,可以看到AI對日常生活的滲透變得越來越強烈,可以說AI如電力將無處不在,這個趨勢的形成主要有兩方面的原因,第一是端側計算算力的提升,即CPU的演進和各種各樣NPU的出現;第二是算法本身的進步,可以看到從最早的VGG到Inception v1、v2、v3再到EfficientNet,算法本身也在不斷的輕量化,導致以前只能跑在服務器上的AI應用,現在可以在端側運行。另一個問題是大家對於數據安全和隱私越來越關注,這也導致越來越多人希望AI計算能夠在本地上運行,就盡量不在雲端上運行。對於端側AI,16年是元年,到現在為止還處於一個快速的爆發期。

上面主要介紹前端的應用需求,那具體的產業鏈是什么情況呢?產業的情況目前是非常不友好的,第一體現在硬件的多樣性,AIoT市場硬件的多樣性是天然存在的,現在各種各樣AI加速的IP出現導致多樣化更嚴重。而且隨着AI的進一步應用,以前認為不太可能跑AI的硬件,如MCU等,現在也可以跑一些AI的算法,所以整個硬件平台多樣性越來越嚴重。

第二體現在軟件的多樣性,現在有很多種訓練框架,那訓練框架訓練出來的模型送入嵌入式平台,目前也是處於百花齊放的狀態,各種各樣的框架也很多,訓練框架有的是原生的,也有第三方開發的。所以對於應用開發人員來說,怎樣把一個算法落地到平台上,這中間的過程還是非常長的。

以上是針對應用開發人員,那對算法開發人員,這也是一個很嚴重的問題。訓練好的模型,要把它落地到一個算力有限的嵌入式平台,可能需要對模型做調整,把它的規模減小,還需要做量化及很多工作之后,才能實現落地。如果還想用嵌入式平台上的加速芯片,還需針對芯片做一些調整,可能有些算子不支持,需要做替換,或者定義上有差別。所以,整個的生態是非常不友好的。

在這種多樣性環境下,可以看到整個AI產業鏈工作效率非常低下。對於芯片公司,擅長做算力的提升,但發現如果只做芯片,做簡單的驅動,很多的AI的開發者是沒辦法用起來的,所以它需要投入大量的資源去做上層的開發平台和開發環境,例如國內的華為公司,從芯片到ID環境到部署環境,整個一條產業鏈都做了

算法和應用公司也發現如果不去完成底層硬件的適配,訓練好的一個模型可能在訓練時效果會非常好,真正落地到平台上時,要么性能特別慢,要么精度特別差。所以,需要親自把模型適配並優化好,這樣算法才能真正的應用與落地。覺得整個產業鏈分工非常不明確,效率低下,這也是去試圖改進和解決的問題。

 

 算法和應用公司也發現如果不去完成底層硬件的適配,訓練好的一個模型可能在訓練時效果會非常好,真正落地到平台上時,要么性能特別慢,要么精度特別差。所以,需要親自把模型適配並優化好,這樣算法才能真正的應用與落地。覺得整個產業鏈分工非常不明確,效率低下,這也是去試圖改進和解決的問題。

1)開放,開放主要從兩點去關注。第一是開源,因為希望有更多的人用Tengine,所以做一個開源的措施。第二是模塊化,因為AI的整個軟件和硬件都還在快速的發展,需要提供很好的方式去支持新出現的算法、模型以及硬件,所以以模塊化的方式去構建整個軟件。

2)高效,因為在SOC平台算力永遠是一個瓶頸,所以想讓算法在平台上跑得快、跑得好,對它做優化是不可少的。而,主要從兩個層面做優化,第一是在圖的層面做優化,第二個是實現高性能的計算庫,這個計算庫是完全自主知識產權的。

3)連接,首先是連接算法和芯片,不論算法或者芯片變化,都希望盡可能減少中間對接的成本。接着是,還需要考慮支持多個計算設備的情況,能把多個設備之間的調度和使用連接起來。這是Tengine作為一個推理框架賦能產業時,做的三件事情。

 

 Tengine怎么樣助力AIoT的應用開發呢?上圖展示的是一個標准算法落地的過程。最左邊是算法訓練,然后再通過Inference server對外提供server里的標准工作流程。當需要在SoC上去做時,第一件事是對模型做優化,然后對模型做量化,這時有可能會出現精度和速度的下降情況,還需要把原始的模型去做一些修改,這個過程可能會反復很多次,改完之后,可以把它放到某一個inference engine上,然后在芯片上跑起來。

上面介紹的只是算法模型部分,真正的一個AI應用包含數據的記錄和結果的輸出。上面還可看到一個前處理、后處理的環節,不管是攝像頭來的數據,還是傳感器來的數據,都需要經過一定的前處理,再把數據送入推理引擎,推理引擎的結果也需做一定的后處理,才能夠交給應用程序做進一步的分析和處理。在量化的過程中,有可能會造成一定的精度的損失,對一些精度要求比較高的場景,比如人臉或是支付場景,這種損失還需要盡量避免,所以還需要針對量化去做重訓練。

Tengine為了加速AIoT的落地過程,如上圖藍色框的內容,都是Tengine做一些工作的地方。針對常見的主流的訓練框架,提供了一個量化重訓練的工具包,對於前處理和后處理也提供了專門的算法庫來加速。

Tengine架構解析

下面重點針對Tengine的推進引擎部分做簡單介紹。主要分為以下幾部分,首先是對整體架構的簡單介紹,然后會介紹對訓練框架的支持情況,因為這部分對於落地來說是最關鍵的,最后是計算圖的執行,以及高性能計算庫和配套工具。

 

– Tengine產品架構

Tengine產品架構如上圖所示,最上面是API接口,對於API接口,進行了仔細的研究,因為,認為要去構建一個Tengine的AI應用生態,API的穩定性是第一位的,這樣才能夠保證整個生態能夠持續發展。

下面是模型的轉換層,可以把各種各樣主流的訓練框架模型轉換成Tengine的模型,然后再端上運行,接着,會提供一些配套的工具,包括圖的編譯、壓縮、調優,以及進行仿真的一些工具,還提供了一些常用的算法庫,包括前處理、后處理,以及面向一些特定領域的算法庫。

接着下面是真正的執行層,包括圖的執行、NNIR,NNIR是Tengine的圖的一個表示,包括內存優化、調度和加密都在這里實現。再下面是對操作系統的適配,Tengine 目前已經支持RTOS和Bare-Metal的場景,這些都是為了支持特別低端CPU去做的。在異構計算層,已經能夠去支持CPU、GPU、MCU、NPU等,也會介紹在GPU和DLA上的用法。

– 訓練框架與OS適配

對於訓練框架,現在已經支持TensorFlow、PyTorch、MXNet等;對於主流操作系統,基本上Linux一系列都可以支持,還有RTOS也支持,后續還會把 IOS和Windows加上。

– 模型轉換方案

模型轉換方案本質上是把其它框架模型轉換成Tengine的模型。有兩個辦法做模型轉換,第一個是可以用Tengine提供的轉換工具,自動轉化。第二個需要用戶自己去寫一些代碼,相當於可以用Python腳本把原模型的所有的參數和數據提出來,然后在通過Tengine接口去構建一個Tengine NNIR的圖,最后再保存為Tengine的模型。所以,模型轉換方案,與其它方案不太一樣。第一步是先把所有的模型解析成Tengine的NNIR格式,第二步再保存成Tengine模型。理論上,Tengine NNIR可以直接去執行,也是沒有問題的。所以,利用這個方法還可以做更多的事情。比如可以把Tengine NNIR保存為一個Caffe的模型,這樣就把Tengine作為一個模型之間轉換的工具。做一個好的模型轉換工具很不容易,定義Tengine NNIR時,其實也踩過很多框架之間兼容性的坑,雖然市面上可以看到有各種各樣的轉化工具,其實都會有多多少少的坑在里面。

– Tengine模型可視化工具

Tengine模型轉化完之后,需要關心下模型轉化結果對不對?是不是符合預期?和開源社區的模型可視化工具軟件Netron進行了合作,實現Netron對Tengine模型支持,如果下載最新版本的Netron,已經對Tengine有很好的支持。

 

 

 如上圖所示的一個例子中,是SqueezeNet的最初的幾層,第一個Convolution的屬性,如上圖右邊所示,輸入是一個data,然后有一個filter和一個bias。可以看到,因為Tengine實際上是對着圖做一些優化,所以屬性會多一個activation。activation為0表示是一個ReLU的activation,實際上是把Convolution和ReLU合並在一起。這個工具能很好的展示Tengine模型結束后模型轉化的結果,也是個非常好用的工具。

– 計算圖執行

對於計算圖的執行,所有模型加載過來,不管是其它框架模型還是Tengine模型,都會轉換成Tengine NNIR,這需要去屏蔽各種框架的差異,把兼容性做得非常好。得到Tengine NNIR后,要決定這些節點在哪個設備上執行,如果系統只有一個設備,問題比較容易解決。但當系統存在多個計算設備時,會遇到一些困難,第一個是兩個計算設備可能特點會不一樣,比如其中一個性能比較高,但有些算子不支持,另一個設備性能比較低,但算子支持程度會比較好。采取什么策略去做,切成多個,還是集中在一個上去算?這屬於靜態情況,若是動態情況就更復雜。

假定設備A當前看起來很閑,但當准備把這個任務發給它時,可能會變得很繁忙。所以目前為止,一直沒有特別好的解決方案。這里更接近於OpenVX的方案,OpenVX的方案實際上是所有計算設備需要按需求定一個優先級,得到一個計算圖之后,計算圖會按照優先級去詢問底下設備,能不能支持這個節點。如果有一個設備響應了,這個節點就不會再分配給其它的設備。方案類似,不同點在於,會讓計算圖在所有的計算設備都看一遍,並不只是終結到某一個為止。

 

 

 通過這種方式,可以確定這個圖的哪個節點在哪個設備上去算,確定完之后,根據設備分配的情況,把圖切成多個不同的部分。上面的示意圖是舉的一個例子,實際上是切成4個部分,這4個部分圖之間會有一定的依賴關系,有些圖可以並行執行,有些圖不能並行執行。假定黃色和藍色是可以並行執行的,構成一個執行圖,接下來把這個圖調度到各個設備上去執行,可以看到黃色和藍色的計算圖,可以同時在兩個設備上去跑,這是大概整個計算的一個過程。

– 發揮硬件極致性能的計算

真正在設備上跑時,還需要把算力盡可能發揮出來。對計算庫做了大量的優化,花精力最多的是整個神經網絡里面最耗時的一部分算子包括卷積,全連接層和池化。卷積根據參數的不同,也實現了各種不同的計算模式,比如基於GEMM、Direct和Winograd的,一般情況下,庫的雙向加速比可以達到175%,四線程可以達到300%,對於有些網絡還會更好,四線程將近接近400%。

對於優化,實際上是針對CPU的微架構做優化。在一個完整的網絡運行的情況下,可以看到Convolution MAC利用率可達到70%,基本上是把硬件的性能給用盡了,對Arm的全系列產品都做了很好的適配。

計算庫不僅對FP32有很好的支持,對INT8也做了很好的優化,這樣才能夠充分發掘出硬件的算力,可以看到INT8比FP32基本上能提速50~90%。同時也注意到INT8還是會帶來一些精度損失,計算庫還支持混合精度計算的模式,對於精度損失大的層,可以采用FP32計算,其它可以采用INT8計算,這是一個能夠同時兼顧精度和速度的方案。對於開源版本的FP32的性能,整個的效果是非常不錯的,Squeezenet都不到60毫秒。

– 量化重訓練工具

剛才提到INT8對於端側推理,現在基本上是一個必選項。但INT8的精度的問題一直困擾大家,再看下現在主流的訓練框架,除了TensorFlow有個TensorFlow.Net之外,其它的框架目前都沒有非常完整的一個量化重訓練方案。針對這個問題,做了一個量化重訓練的工具,還針對NPU做了一些改進,因為NPU的芯片廠商對於訓練並不是特別熟悉,所以,希望能夠去改進在NPU上跑的量化精度,讓更多的應用在NPU上去跑起來。

 

 如上圖右邊所示為量化重訓練的結果,可以看到整個效果是非常不錯的。特別是對於MxNet_MobileNet,重訓練后的精度可以提高兩個百分點。

除了Tengine整個配套工具及量化重訓練之外,對於前處理、后處理,也做了不少的工作,實際上有個HCL.Vision的模塊,針對CV的應用做的一個計算庫。這個計算庫與其它CV計算庫不同點,在於若硬件平台上有一些圖像處理的加速,會去調用底層硬件平台接口來處理,若沒有,會用做一個優化的CPU的實現,對外會提供一個統一的接口,這對於開發應用程序就非常方便了。在接口不變的情況下,在一個有硬件加速的平台上就能夠獲得硬件的加速。

Tengine API簡介

下面進入Tengine API的部分,首先介紹下對API設計的想法,后面再介紹下目前API的一些應用。

– 概述

在剛開始做Tengine時,把API放在一個很重要的位置,同時也學習和參考業界一些優秀的軟件方案以及框架。實際上主要參考了Android NN、OpenVX、TensorRT、TensorFlow、MXNet的接口設計原則和思路,最后更偏向於OpenVX。

,有兩個原則,第一個是接口的定義和實現完全無關,這主要基於現在AI的軟件和硬件還處於一個快速發展的情況下來考慮的。如果接口定義會綁定某些實現,會對代碼重構或支持一些新的特性時,帶來很大負擔,所以把接口定義和實現完全拋開。

第二個是要注重接口的穩定性和靈活性,穩定性對於所有的API都非常重要。為什么還特別強調靈活性?與上面類似,未來會怎么樣,不是很清楚,所以要盡可能預留一些接口,去支持未來要出現的功能以及給應用程序對底層有更多的操控權,這樣會更有利於Tengine應用生態的穩定性和長期發展性。

對於硬件這塊,一直考慮怎么樣能夠對Tengine支持的更好。在端側跑一些訓練的模型,可以看得見的趨勢,所以接口也要去支持模型訓練。從本質上來說,Tengine是連接算法和芯片,通過一個標准的接口把芯片的算力提供給應用。這對於實行和訓練的方案,也是很有價值的。最后,看下結果,采用C的接口作為核心的API,然后基於C去封裝一些更好用的API,比如C++ API或者Python API,這兩個API目前已經實現了,規划中還有JS API和Java API,都會基於C API去做,這是整個API體系的一個想法。

– 多芯片和軟件后端支持

在進入AIoT市場時,知道未來一定是有多種AI加速芯片和AI IP的出現,怎樣讓應用程序在不同的芯片或不同的AI加速IP之間遷移,工作量能更少。可以看下,方案是應用程序通過一次性的接口,就能夠調到各種不同的算力。如上圖最左邊可以看到,在一個RK3288或樹莓派Linux上去實現推理的一個用法,調用Tengine模型,右邊第一行是一個MCU的平台,可能需要換一種新的模型就好,換為tiny模型,不再是Tengine的一個模型。如果是NPU,只需要把中間模型的格式給換一下,其它部分基本上都不需要去改動,這樣不管是應用程序的維護,還是學習成本都會很低。

基於,API會發現,對於現在的開發商降低很多復雜度。比如一個應用,希望既能在安卓上跑,又能在Linux上跑時,可以調Tengine接口,都能很好的調用到底層硬件的算力。在安卓下會更復雜,因為Google提出Android NN API的一個接口,在之前並沒有這個接口,假定有一個應用程序,既想調用這個接口,沒在Android NN的平台上,怎么辦呢?

實際上,可以用中間的方案,仍然調用Android NN API,但是通過重新實現Android NN Runtime讓Tengine調底下的一個算力,這樣可以保證用程序,不必擔心在不同平台上需要去切換接口的問題。由上面的場景可以看到對底下的芯片公司價值最大,而芯片公司,只需對Tengine做試配之后,對於各種場景都能夠很好的為應用程序提供一個算力的功能。

– C++ API和Python API

下面介紹下C++ API和Python API,這兩個主要是為了快速簡單去使用,所以會把C API里面很多的功能去掉。看一個典型的用法,分為4步,第一步是加載模型,第二步是設置輸入數據,第三步是run,第4步是取一個接口。

Python API與C++ API類似,只不過變成Python結構下也能調這些東西。

 

  C API相對來說,核心API會比較復雜,可以分為幾大類,第一大類是Tengine的初始化,destroy,接下來是跟Tengine整個NNIR對應,Tengine里面有Graph的概念,有Node的概念,有Tensor的概念。這部分概念跟TensorFlow會更像些。對於Graph,包含Graph創建,Node的創建,所以通過Tengine接口,可以去創建一個圖的,這也是為了支持模型轉換及訓練等功能區做的考慮。接下來還是圖執行的接口,包括Prerun和Postrun,剩下都是一些雜項接口,比如,設置日志的重新項,可以去graph和節點綁定一個執行設備。

下面介紹下用C API去做推理的典型過程,與剛才差不多,可能稍微麻煩一點就是需要主動調一個prerun的接口,prerun接口完成把計算圖綁定一個設備的問題,以及在設備上分配給計算圖做計算所需要資源的過程。接下來是去設Tensor的buffer,然后再去run。

– C API創建圖和節點

首先是創建一個空的圖,空的圖代表什么都沒有,不來自於任何的框架;接下來需要創建一個input節點,input節點需要創建兩個東西,第一個是Node,第二個是Tensor,將Tensor設置為節點的output的Tensor,這一點的思路是和TensorFlow完全一致,所以會與TensorFlow會長得比較像一點;再接下來是創建一個Convolution節點,與上面的input節點類似,首先需要創建一個節點,並指定這個節點的op是一個Convolution;之后要創建這個節點輸出的Tensor;再下面把輸入的Tensor設好,比如說0、1、2,分別設定input_tensor、w_tensor和b_tensor,這三個就構成了這個節點;Convolution還有很多參數,比如kernel size,stride,padding怎么去設?提供了一個接口,可以直接設置節點的屬性,可以通過程序直接設置。

實踐1:Tengine擴展,定制和添加算子

前面已基本上介紹完接口,下面再結合具體的代碼給看下,怎么樣去擴展和定制算子。

– Tengine算子kernel定制

首先是算子kernel的定制,若Tengine里已經實現一個算子,但和目前實現的框架有差別時,需要一個更好的實現,或平台上有一個硬件可以對算子做加速,不希望用,已經做好的,有兩種辦法去解決,第一種是通過C API的custom kernel的接口做替換,但替換時需要指定哪個節點來做,這類接口學習成本可能會低些,不需要了解Tengine添加一個新算子的過程,只需要做計算。

第二種情況是可以用Plugin的實現,通過一個外掛模塊來把算子重新實現,只不過在注冊時,把實現的優先級提高。這樣做完之后,所有關於這個類型算子的實現,都會用算子來做。

先介紹下用Tengine C API去做替換的方式,然后Plugin的方式會在后面一個例子中怎么去添加一個新算子。

 

 Tengine C API里有一個的custom kernel的接口,這個接口最關鍵的數據是custom_kernel_ops,定義了custom kernel需要實現內容,數據結構如上圖所示,第一個是op,op是表明要去做一個convolution,還是要去做一個pooling,在接下來是兩個parameter,一個kernel_param,一個kernel_param_size,是給算子去用的。然后三個最關鍵函數:prerun、run、postrun,傳進去首先是ops,就是ops自身,接下來是input_tensor和output_tensor。其它的參數,需要在parameter里找出來。

這個設置完之后,可以去調set_custom_kernel去替換設備上的實現。現在,可以去看下代碼。

http://oss.zhidx.com/uploads/2020/06/5ed9cb6be1a11_5ed9cb6bd6f9b_5ed9cb6bd6f54_1J.mp4

– Tingine新算子的添加

新算子的添加,對於推理框架也很重要,特別是針對Tensorflow的情況,由於Tensorflow算子既多又復雜,當遇到不支持算子時,推薦先用plugin的模式在Tingine Repo外實現。待測試穩定之后,再提交PR合入到Repo里。

下面以Tensorflow新算子Ceil為例,一共有以下幾件事情:第一是在Tengine NNIR中注冊一個新的算子定義;第二是在Tengine Tensorflow Serializer中注冊算子的模型加載;最后需要在Tengine Executor里面真正實現算子,可以看代碼。

http://oss.zhidx.com/uploads/2020/06/5eda07243f6ca_5eda07243b689_5eda07243b623_2.mp4

實踐2:Tengine在CPU/GPU/NPU/DLA上的推理

下面看怎樣做推理,選了一個MobilenetSSD來做,一共有三個例子,第一個是單獨用一個是 CPU跑,第二個是CPU+GPU配合跑,第三是多設備跑的情況,可以看下MSSD的例子。前面在CPU、GPU下運行的情況,假定是一個NPU或一個DLA時,怎樣能用起來?看一個演示:

http://oss.zhidx.com/uploads/2020/06/5ed9ddf4a53b9_5ed9ddf4a0f98_5ed9ddf4a0f49_4.mp4

由上面可以看出,調用NPU 是一件非常容易的事情,對於學習成本和應用開發速度都有很大的提升。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM