作者:Arm 工程部產(chǎn)品管理總監(jiān) Paul Black
Arm KleidiAI 是一款具有突破性意義的軟件庫,專為提升 Arm CPU 上的人工智能 (AI) 性能而設(shè)計(jì)。在此前發(fā)布的《Arm KleidiAI 助力 AI 框架性能提升》一文中,對(duì) KleidiAI 進(jìn)行了簡(jiǎn)要概述,并附有相關(guān)指南鏈接,其中詳細(xì)說明了在 Linux 環(huán)境中運(yùn)行 KleidiAI 矩陣乘法 (matmul) 微內(nèi)核的分步操作,這份指南內(nèi)容詳實(shí)且極易上手。而本篇內(nèi)容則將探索如何在裸機(jī)環(huán)境中運(yùn)行 KleidiAI 內(nèi)核,并通過測(cè)試多款 C/C++ 編譯器,以確定如何能更高效地生成代碼。
本文將介紹如何在裸機(jī)環(huán)境中運(yùn)行 KleidiAI 微內(nèi)核,并針對(duì)不同編譯器在不同優(yōu)化級(jí)別下的表現(xiàn)進(jìn)行基礎(chǔ)基準(zhǔn)測(cè)試。文中會(huì)用到 Arm Development Studio 的相關(guān)組件,包括固定虛擬平臺(tái) (FVP),以及 Arm Compiler for Embedded (AC6) 的授權(quán)許可。與此同時(shí),還提供了有關(guān)如何查看編譯器已采用(或未采用)的優(yōu)化的相關(guān)信息。
設(shè)置裸機(jī)項(xiàng)目
本文將評(píng)估的三個(gè)編譯器分別是:
Arm Compiler for Embedded,更為人熟知名稱的是 AC6
Arm GNU 工具鏈,即 GCC
新一代 Arm 嵌入式編譯器 Arm Toolchain for Embedded (ATfE)。撰寫本文時(shí),該工具鏈還處于 Beta 測(cè)試階段
為了在裸機(jī)項(xiàng)目中運(yùn)行 KleidiAI 內(nèi)核,可參考 Kleidi 指南中的說明。本文以 Arm Development Studio 中的 C++ 示例項(xiàng)目為基礎(chǔ)進(jìn)行開發(fā):startup_Armv8-Ax1_AC6_CPP是 AC6 版本,startup_Armv8-Ax1_GCC_CPP是 GCC 版本,而 ATfE 的移植版本則包含在 ATfE 測(cè)試版下載包中。這三個(gè)編譯器對(duì)應(yīng)的項(xiàng)目功能相同,但需要對(duì) Makefile 和鏈接腳本進(jìn)行必要的修改。
各工具鏈的修復(fù)和更改
在粘貼 Kleidi 指南中提供的代碼后,需對(duì)這三個(gè)項(xiàng)目進(jìn)行以下簡(jiǎn)單修改以確保正常運(yùn)行:
包含 float.h 頭文件以定義 FLT_MAX
添加 KleidiAI 頭文件的 include 路徑
將架構(gòu)更改為armv8.2-a+dotprod+i8mm
要運(yùn)行此代碼,需要一個(gè)具備 i8mm 擴(kuò)展的 Arm 核心。此擴(kuò)展在 Armv8.2-A 至 Armv8.5-A 架構(gòu)中為可選功能,而在后續(xù)支持高級(jí) SIMD 指令的核心中則為必選,因此 Arm Neoverse V1 是個(gè)不錯(cuò)的選擇。Arm Development Studio 提供了 Neoverse V1 固定虛擬平臺(tái) (FVP),此處所選用的是-C cluster0.NUM_CORES=1 -C bp.secure_memory=false -C cache_state_modelled=0
啟動(dòng)代碼中存在一段用于設(shè)置 SMPEN 的讀-改-寫序列,但這在 Neoverse V1 FVP 上會(huì)引發(fā)問題。由于是復(fù)用 Arm Cortex-A 的啟動(dòng)代碼來適配 Neoverse 核心,因此需要進(jìn)行一些修改,而在本場(chǎng)景中,移除該序列即可解決問題。理想情況下,應(yīng)根據(jù) Neoverse 核心的要求重新審閱啟動(dòng)代碼,但就本次研究而言,確保代碼正常運(yùn)行便已足夠。
添加了一些代碼,用于向矩陣中填充隨機(jī)數(shù)據(jù)。這一步可能并非必需,因?yàn)閮?nèi)存中原本就已填充了重復(fù)的非零模式。
此外,還需要對(duì)各個(gè)項(xiàng)目單獨(dú)做一些修改。示例項(xiàng)目主要是實(shí)現(xiàn)處理器的啟動(dòng),并未考慮在啟動(dòng)后運(yùn)行較為復(fù)雜的負(fù)載任務(wù):
在 ATfE 項(xiàng)目中,RAM 大小被設(shè)為 0x80000,這個(gè)容量過小,會(huì)導(dǎo)致堆與棧發(fā)生沖突。不過此問題很容易解決,因?yàn)榧幢闶?FVP 的默認(rèn)配置,其提供的 RAM 也遠(yuǎn)大于該數(shù)值。因此,我們可以在鏈接腳本中設(shè)置更大的 RAM 大小。
在 GCC 項(xiàng)目中,.init_array 段被分配到 0x80100000 地址,該地址過低,會(huì)與 .eh_frame 段產(chǎn)生沖突。移除這一地址設(shè)置即可解決問題。
至此就能成功在裸機(jī)環(huán)境中使用三款不同的工具鏈運(yùn)行 KleidiAI 內(nèi)核。接下來便可開展性能測(cè)試。
基準(zhǔn)測(cè)試方法和結(jié)果
本次研究中使用了 FVP 的周期計(jì)數(shù)器來作為性能衡量指標(biāo)。雖然它并非完美,但對(duì)于本次研究而言已經(jīng)足夠。由于三款編譯器運(yùn)行的是相同的工作負(fù)載,因此即便存在測(cè)量誤差,其誤差程度和分布位置也會(huì)保持一致。所以,作為一種性能參考指標(biāo),F(xiàn)VP 的周期計(jì)數(shù)完全能滿足本次研究的需求。接著,分別在 -O0、-O1、-O2 和 -O3 這四個(gè)優(yōu)化級(jí)別下,對(duì)三款編譯器的周期計(jì)數(shù)進(jìn)行了測(cè)量,以啟動(dòng)處理器核心、設(shè)置矩陣以及執(zhí)行 KleidiAI 內(nèi)核:

這里有兩個(gè)值得關(guān)注的現(xiàn)象。首先,大部分優(yōu)化效果在 -O1 級(jí)別就已顯現(xiàn)。在 -O2 和 -O3 級(jí)別下雖有小幅提升(其中 GCC 的提升相對(duì)更明顯),但提升幅度遠(yuǎn)不及 -O1 級(jí)別。這并不令人驚訝,因?yàn)?KleidiAI 內(nèi)核本身已通過大量手工編寫的匯編指令進(jìn)行了優(yōu)化,而在 Kleidi 內(nèi)核外添加的代碼既簡(jiǎn)短又簡(jiǎn)單。本文后續(xù)會(huì)深入分析所使用的優(yōu)化手段。
其次,ATfE 的表現(xiàn)似乎明顯快于 AC6 和 GCC。新一代 Arm 嵌入式編譯器能在與 AC6 的對(duì)比中展現(xiàn)出如此優(yōu)勢(shì),固然令人欣喜,但這一性能差距也促使我進(jìn)行更深入的探究。
AC6 和 ATfE 的匯編器、編譯器及 C++ 庫組件均基于 LLVM 構(gòu)建,兩款工具鏈的主要差異體現(xiàn)在鏈接器和 C 庫上(AC6 采用專有版本,ATfE 則使用開源版本)。因此,兩者之間約 20% 的性能差距讓我頗為好奇。我需要確保所有性能數(shù)據(jù)和基準(zhǔn)測(cè)試結(jié)果都能適用于實(shí)際項(xiàng)目,所以必須進(jìn)一步厘清 ATfE 的速度提升究竟源于何處。
深入分析
在這一部分對(duì)性能測(cè)試進(jìn)行了簡(jiǎn)化,但同時(shí)也提升了復(fù)雜度。通過只關(guān)注 -O1 優(yōu)化等級(jí),以此簡(jiǎn)化了測(cè)試,因?yàn)榇蟛糠謨?yōu)化效果都體現(xiàn)在這一級(jí)別。與此同時(shí),通過將代碼分為三個(gè)部分來提高分析的粒度:
啟動(dòng):所有啟動(dòng)代碼,直至進(jìn)入 main ()
準(zhǔn)備:為矩陣分配內(nèi)存,向矩陣填充隨機(jī)數(shù)據(jù)
執(zhí)行:運(yùn)行 Kleidi 內(nèi)核
周期計(jì)數(shù)如下:

從 KleidiAI 內(nèi)核的執(zhí)行耗時(shí)來看,三款編譯器的表現(xiàn)十分接近,ATfE 略領(lǐng)先于 AC6(約 1%),而 GCC 則稍顯落后。在 -O2 和 -O3 級(jí)別下重新運(yùn)行了該測(cè)試,如前文所述,隨著優(yōu)化級(jí)別的提高,GCC 在 -O3 級(jí)別時(shí)小幅反超,這正是高級(jí)別優(yōu)化帶來的提升效果之一。
在準(zhǔn)備階段,ATfE 與 AC6 的表現(xiàn)依然接近,GCC 則仍然落后。同樣在 -O2 和 -O3 級(jí)別下重新測(cè)試后發(fā)現(xiàn),在這些優(yōu)化級(jí)別下,GCC 縮小了部分差距。這似乎表明,不同編譯器會(huì)在不同優(yōu)化級(jí)別中納入特定的優(yōu)化過程。
然而,ATfE 之所以能實(shí)現(xiàn)整體耗時(shí)的大幅縮短,關(guān)鍵提速點(diǎn)其實(shí)在啟動(dòng)階段。我猜測(cè),這可能是因?yàn)?ATfE 所使用的 Picolibc 在 C 庫設(shè)置環(huán)節(jié),比 AC6 采用的 ArmCLib 或 GCC 采用的 newlib 更輕量化。由于 ATfE 的主要提速點(diǎn)在于此,而測(cè)試項(xiàng)目本身的代碼量較少,這就導(dǎo)致初始的性能對(duì)比結(jié)果存在偏差:如果增大工作負(fù)載,啟動(dòng)代碼在整體運(yùn)行時(shí)間中的占比就不會(huì)如此之高了。
分析編譯器優(yōu)化
若要了解 ATfE 采用(或未采用)哪些優(yōu)化過程,可借助編譯器選項(xiàng)-Rpass(或-Rpass-missed)。這兩個(gè)選項(xiàng)后可接=.*(表示所有優(yōu)化過程)或=
快速查看了 ATfE 在 -O0、-O1、-O2 和 -O3 下級(jí)別下的優(yōu)化過程,其結(jié)果如下:
即便在 -O0 級(jí)別,編譯器仍會(huì)對(duì)部分 Arm C 語言擴(kuò)展 (ACLE) 內(nèi)聯(lián)函數(shù)進(jìn)行內(nèi)聯(lián)處理,例如 vaddq_s16(向量加法)。這一點(diǎn)是合理的,因?yàn)檫@類調(diào)用僅對(duì)應(yīng)單條指令,因此在性能(得益于消除函數(shù)調(diào)用開銷)與代碼體積增加(因代碼復(fù)制導(dǎo)致)之間不存在權(quán)衡問題。
在 -O1 級(jí)別,編譯器進(jìn)行了大量的函數(shù)內(nèi)聯(lián),尤其是對(duì)小型函數(shù)(如隨機(jī)數(shù)生成器實(shí)現(xiàn))。此外,若循環(huán)中某些指令或表達(dá)式無需在每次迭代時(shí)重新計(jì)算,編譯器會(huì)將它們提升 (hoist) 到循環(huán)外部。
在 -O2 級(jí)別,編譯器開始進(jìn)行循環(huán)向量化,但部分向量化操作會(huì)推遲到 -O3 級(jí)別。編譯器采用啟發(fā)式算法來權(quán)衡每項(xiàng)優(yōu)化的收益與成本。如同內(nèi)聯(lián)優(yōu)化,在同一優(yōu)化級(jí)別下,不同循環(huán)可能會(huì)采用不同的向量化策略,這一點(diǎn)值得關(guān)注。
在 - O3 級(jí)別,編譯器還會(huì)對(duì)部分循環(huán)進(jìn)行展開。
提升 (hoisting) 機(jī)制值得深入探究。以 KleidiAI 源文件中一段大幅簡(jiǎn)化的代碼為例:
for (size_t dst_row_idx = 0; dst_row_idx < dst_num_rows; ++dst_row_idx) {?
for (size_t dst_byte_idx = 0; dst_byte_idx < dst_num_bytes_per_row; ++dst_byte_idx) {?
const size_t block_idx = dst_byte_idx / block_length_in_bytes;
const size_t nr_idx = block_idx % nr;
const size_t n0_idx = dst_row_idx * nr + nr_idx;
編譯器注意到,在計(jì)算 n0_idx 時(shí),其中的乘法部分無需放在內(nèi)層循環(huán)中,因?yàn)樵趦?nèi)層循環(huán)中,dst_row_idx 和 nr 均為常量:
src/kai_rhs_pack_nxk_qsi4cxp_qs4cxs1s0.c47: remark: hoisting mul [-Rpass=licm]
96 | const size_t n0_idx = dst_row_idx * nr + nr_idx;
| ^
編譯器會(huì)將該乘法操作從內(nèi)層循環(huán)提升 (hoist) 到外層循環(huán),大致如下:
for (size_t dst_row_idx = 0; dst_row_idx < dst_num_rows; ++dst_row_idx) {?
const size_t hoist_temp = dst_row_idx * nr;
for (size_t dst_byte_idx = 0; dst_byte_idx < dst_num_bytes_per_row; ++dst_byte_idx) {?
const size_t block_idx = dst_byte_idx / block_length_in_bytes;
const size_t nr_idx = block_idx % nr;
const size_t n0_idx = hoist_temp + nr_idx;
開發(fā)者也可手動(dòng)進(jìn)行此類優(yōu)化,但這可能會(huì)使代碼變得不夠簡(jiǎn)潔、清晰,難以理解和維護(hù)。編譯器會(huì)考慮這些因素,從而讓開發(fā)者能夠?qū)W⒂诖a功能、清晰度和可維護(hù)性。
ATfE 的 -Rpass 選項(xiàng)輸出包含大量信息,既涉及已應(yīng)用的優(yōu)化過程,也涉及未應(yīng)用的過程。這些信息對(duì)于開發(fā)者而言非常有幫助,能讓開發(fā)者了解編譯器如何優(yōu)化代碼,并指導(dǎo)開發(fā)者對(duì)代碼進(jìn)行調(diào)整,以更好地配合編譯器優(yōu)化。這是一個(gè)龐大的主題,我將在后續(xù)博客中深入探討。
結(jié)論
Arm Development Studio 提供了一套適用于裸機(jī)環(huán)境下 KleidiAI 內(nèi)核實(shí)驗(yàn)的工具,包括便于快速上手的示例項(xiàng)目、用于測(cè)試的 FVP,以及 AC6 的授權(quán)(之后還將包含 ATfEP 的授權(quán))。與所有軟件開發(fā)工作一樣,在評(píng)估編譯器性能等指標(biāo)時(shí),需要考慮采集所有相關(guān)數(shù)據(jù)。在本案例中,很容易輕易得出“用 ATfE 構(gòu)建的項(xiàng)目比用 AC6 構(gòu)建的項(xiàng)目快約 20%”的結(jié)論。ATfE 會(huì)基于每項(xiàng)潛在優(yōu)化的成本與收益做出啟發(fā)式優(yōu)化決策,并提供實(shí)用選項(xiàng)來查看已采用和未采用的優(yōu)化。通過這些選項(xiàng)獲取的信息,可用于調(diào)整代碼,使編譯器能夠?qū)崿F(xiàn)更多優(yōu)化。
-
ARM
+關(guān)注
關(guān)注
135文章
9589瀏覽量
393778 -
內(nèi)核
+關(guān)注
關(guān)注
4文章
1476瀏覽量
43098 -
cpu
+關(guān)注
關(guān)注
68文章
11332瀏覽量
225975 -
人工智能
+關(guān)注
關(guān)注
1821文章
50366瀏覽量
267046
原文標(biāo)題:在裸機(jī) Arm 環(huán)境中運(yùn)行 Arm KleidiAI MatMul 內(nèi)核
文章出處:【微信號(hào):Arm社區(qū),微信公眾號(hào):Arm社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
如何在嵌入式Linux開發(fā)板上配置Qt運(yùn)行環(huán)境
如何在裸機(jī)系統(tǒng)中集成SystemView
《電子發(fā)燒友電子設(shè)計(jì)周報(bào)》聚焦硬科技領(lǐng)域核心價(jià)值 第23期:2025.08.04--2025.08.08
如何在 S7G32 上啟動(dòng) Cortex-M2?
【OK210試用體驗(yàn)】之三裸機(jī)開發(fā)環(huán)境搭建
請(qǐng)問裸機(jī)程序怎么做才可以直接下載到SDRAM中運(yùn)行?
可以將MCUXpresso用于該設(shè)備中M7內(nèi)核的軟件開發(fā),而不是A53內(nèi)核,這是否正確?
如何使用J-Link在A55內(nèi)核上進(jìn)行i.MX93 EVK裸機(jī)調(diào)試?
請(qǐng)問nuc980如何在裸機(jī)程序中實(shí)現(xiàn)nuc980軟件復(fù)位?
請(qǐng)問nuc980如何在裸機(jī)程序中實(shí)現(xiàn)nuc980軟件復(fù)位?
微內(nèi)核與宏內(nèi)核的比較與分析
環(huán)境監(jiān)測(cè)設(shè)備中的FreeRTOS低功耗
程序是如何在 CPU 中運(yùn)行的(二)
如何在裸機(jī)環(huán)境中運(yùn)行KleidiAI微內(nèi)核
評(píng)論