同設(shè)計(jì):有效算力、編譯器與部署實(shí)戰(zhàn))
做了幾代 AI 芯片的軟硬件協(xié)同設(shè)計(jì)之后我最大的一個(gè)感受是“AI 芯片”這四個(gè)字本身就很容易讓人跑偏。做一顆能跑模型的芯片并不難難的是讓模型在真實(shí)負(fù)載下跑出接近標(biāo)稱算力的成績(jī)而這背后幾乎全是軟硬件接口的問(wèn)題。這個(gè)系列前兩篇聊過(guò)宏觀的架構(gòu)輪廓和工具鏈布局這篇“3”想再往下鉆一層把 AI 芯片軟硬件設(shè)計(jì)中最容易被忽略、卻最能決定項(xiàng)目成敗的幾個(gè)環(huán)節(jié)攤開(kāi)講有效算力與峰值算力的差距、架構(gòu)路線取舍、指令集與內(nèi)存模型、編譯器在整個(gè)項(xiàng)目里的位置、芯片回來(lái)之前的軟硬件聯(lián)調(diào)以及部署階段繞不開(kāi)的量化、稀疏化和 Runtime 閉環(huán)。這篇東西不是科普稿也不是給投資人看的 PPT。它更適合真正在做 AI 芯片或準(zhǔn)備做 AI 芯片的人芯片前后端工程師、編譯器/工具鏈開(kāi)發(fā)者、驅(qū)動(dòng)與 Runtime 工程師以及需要做 AI SoC 選型評(píng)估的架構(gòu)決策者。很多內(nèi)容是我在實(shí)際項(xiàng)目里踩過(guò)的坑寫著寫著可能有點(diǎn)直白但確實(shí)是想讓后來(lái)的人少走彎路。1. 有效算力與峰值算力之間的鴻溝AI 芯片性能兌現(xiàn)的真正戰(zhàn)場(chǎng)先拋一個(gè)反直覺(jué)的結(jié)論AI 芯片的賬面算力TOPS在發(fā)布 PPT 上有多漂亮和最終用戶在真實(shí)模型上跑出來(lái)的吞吐量完全是兩碼事。做了三代產(chǎn)品之后我對(duì)團(tuán)隊(duì)里的硬件工程師說(shuō)得最多的一句話是“你們的 KPI 不是 TOPS是用戶在真實(shí)網(wǎng)絡(luò)上的有效算力?!币?yàn)閺挠布O(shè)計(jì)到軟件完全跑起來(lái)中間隔著三道坎片外帶寬夠不夠喂飽計(jì)算陣列、算子形狀能不能貼合硬件的數(shù)據(jù)流、編譯器和驅(qū)動(dòng)有沒(méi)有把指令調(diào)度到最優(yōu)。這三道坎任何一道沒(méi)跨過(guò)去賬面算力就得打個(gè)對(duì)折甚至三折。1.1 賬面算力與實(shí)測(cè)吞吐一個(gè)典型 NPU 的真實(shí)寫照舉一個(gè)很常見(jiàn)的例子。一款設(shè)計(jì)目標(biāo)為 16TOPS INT8 算力的 NPUPE 陣列是 32x64 的 MAC 陣列主頻 1GHz配套 DDR4-3200理論帶寬約 25.6GB/s。這種規(guī)格在規(guī)格表上挺能打但我在類似平臺(tái)上跑過(guò) ResNet-50實(shí)測(cè)吞吐往往只有標(biāo)稱的 40%-60%也就是 6-9TOPS 的有效算力。如果跑 Transformer 類的模型情況只會(huì)更難看因?yàn)?attention 里面的 QKV 投影、softmax、LayerNorm 這些算子形狀極不規(guī)則硬件利用率掉到 30% 以下都常見(jiàn)。差距去哪了很多時(shí)間根本沒(méi)有花在“計(jì)算”上而是花在“等數(shù)據(jù)”上。PE 陣列在片內(nèi)等著一塊塊 weight 和 feature map 從 DDR 里被搬進(jìn)來(lái)片內(nèi) SRAM 容量就那么大搬一次算一會(huì)兒算完再搬下一塊計(jì)算陣列真正滿負(fù)荷工作的時(shí)間占比低得可憐。用開(kāi)車來(lái)類比就是高速路段設(shè)計(jì)時(shí)速 120但上下匝道只有一個(gè)車道你實(shí)際平均時(shí)速可能連 40 都不到——而這個(gè)“匝道”就是 SRAM 容量和 DDR 帶寬共同決定的訪存瓶頸。1.2 內(nèi)存墻堆算力越猛帶寬越先到頭為什么帶寬會(huì)比算力更早卡住用一次簡(jiǎn)單卷積的手工估算就能說(shuō)明白。假設(shè)一個(gè) 3x3 卷積層輸入是 128 通道的 56x56 feature map輸出也是 128 通道。計(jì)算量大約是56x56x128x128x3x3x2約 9 億次 FLOPs。這個(gè)量級(jí)對(duì) 16TOPS 的 NPU 來(lái)說(shuō)理想情況下 0.06 秒就跑完。但如果每個(gè)算子計(jì)算完都把中間結(jié)果寫回 DDR下一層再讀進(jìn)來(lái)那么一次完整的 layer 讀寫就要搬運(yùn) 2x56x56x128 字節(jié)約 1.6MB整個(gè)網(wǎng)絡(luò)幾十上百層累加起來(lái)訪存數(shù)據(jù)量會(huì)膨脹到計(jì)算量的幾十倍。在 25GB/s 的帶寬下光搬數(shù)據(jù)就足夠把推理時(shí)間拖垮。所以做 AI 芯片架構(gòu)選型時(shí)第一優(yōu)先級(jí)不是把 MAC 陣列堆多狠而是把“每層計(jì)算需要從片外拿多少數(shù)據(jù)、中間結(jié)果能不能留在片上”算清楚。真正的優(yōu)化主戰(zhàn)場(chǎng)包括片上 SRAM 容量怎么分配、多級(jí)緩存/片上 Buffer 怎么設(shè)計(jì)、數(shù)據(jù)復(fù)用率能不能提上去以及 DMA 引擎能不能在計(jì)算的同時(shí)把下一塊數(shù)據(jù)預(yù)取進(jìn)來(lái)。這些軟硬件協(xié)同的細(xì)節(jié)才是有效算力的真正來(lái)源。1.3 算子形狀與硬件規(guī)則的錯(cuò)位被低估的第三種損耗帶寬不是唯一瓶頸。深度神經(jīng)網(wǎng)絡(luò)的計(jì)算形狀非常雜1x1 卷積本質(zhì)上是矩陣乘但 3x3 卷積是 im2col 或直接卷積depthwise 卷積每個(gè)通道單獨(dú)處理group conv 又要在通道維度分組attention 里還要做各種 gather/scatter 和動(dòng)態(tài) shape。這些形狀落在固定尺寸的 PE 陣列上時(shí)會(huì)產(chǎn)生大量 padding、補(bǔ)零、低利用率的小算子。我在一次適配 YOLO 系列模型時(shí)就踩過(guò)這個(gè)坑。目標(biāo)檢測(cè)網(wǎng)絡(luò)里有一些很小的分支層比如 1x1 conv 輸出通道只有 16而 PE 陣列一次處理 64 通道結(jié)果就是硬件為了喂?jié)M陣列不得不把多個(gè)不相關(guān)的通道拼在一起算或者干脆空轉(zhuǎn)。這類損耗在純 GEMM 基準(zhǔn)測(cè)試?yán)锿耆床怀鰜?lái)但一跑真實(shí)網(wǎng)絡(luò)就原形畢露。所以做芯片架構(gòu)設(shè)計(jì)時(shí)最忌諱只看理論峰值一定要拿目標(biāo)負(fù)載里的真實(shí)算子分布做一遍映射統(tǒng)計(jì)。硬件是為數(shù)據(jù)和算子服務(wù)的不是為浮點(diǎn)峰值服務(wù)的。2. 架構(gòu)路線的岔路口脈動(dòng)陣列、SIMT 與可重構(gòu)數(shù)組聊完性能差距的來(lái)源必然會(huì)走到架構(gòu)選擇這個(gè)問(wèn)題上。AI 芯片最常見(jiàn)的三種路線——SIMT 風(fēng)格GPU 式、脈動(dòng)陣列風(fēng)格TPU 式、可重構(gòu)數(shù)據(jù)流風(fēng)格CGRA——各有各的脾氣選擇哪條基本決定了后面編譯器、驅(qū)動(dòng)和 Runtime 要付出多大的代價(jià)。很多人以為選型就是看算力密度實(shí)際上選型選的是“軟件棧的未來(lái)成本”。我見(jiàn)過(guò)不止一個(gè)項(xiàng)目最初選了一個(gè)看起來(lái)算力密度最高的架構(gòu)做了一年發(fā)現(xiàn)編譯器根本駕馭不了最后被迫砍掉一半功能。這一節(jié)把三種路線放在一起對(duì)比重點(diǎn)講它們對(duì)軟硬件協(xié)同設(shè)計(jì)的影響。2.1 SIMT 的隱藏延遲策略在 AI 推理場(chǎng)景的錯(cuò)位GPU 式 SIMT 架構(gòu)的思路是用大量線程并行來(lái)隱藏訪存延遲一個(gè) warp 在等內(nèi)存數(shù)據(jù)時(shí)調(diào)度器立刻切換到另一個(gè) warp。這種設(shè)計(jì)對(duì)大規(guī)模并行計(jì)算非常有效訓(xùn)練場(chǎng)景下尤其是這樣大批量、大矩陣GPU 幾乎能把計(jì)算陣列喂得滿滿的。但推理場(chǎng)景往往 batch 很小甚至 batch1。此時(shí) GPU 的大量線程切換機(jī)制、shared memory 同步、warp 調(diào)度器本身的功耗和面積開(kāi)銷反而成了負(fù)擔(dān)。比如做端側(cè)推理時(shí)經(jīng)常出現(xiàn)一個(gè)算子只啟動(dòng)了 8 個(gè)線程做 elementwise 操作但整套調(diào)度器和多級(jí)緩存仍然要全功率待命能效比自然很難看。不是說(shuō) SIMT 不好而是要用對(duì)地方它適合做“大而整齊”的工作不適合“小而雜”的推理負(fù)載。2.2 脈動(dòng)陣列讓數(shù)據(jù)在 PE 之間流動(dòng)換掉隨機(jī)訪存脈動(dòng)陣列是另一種極端把一組 PE 按照規(guī)律連起來(lái)數(shù)據(jù)像流水線一樣從一端流入每個(gè) PE 只和自己的鄰居交換數(shù)據(jù)避免了對(duì)片內(nèi)多端口隨機(jī)讀寫的依賴。這個(gè)思路在能效和面積效率上表現(xiàn)極佳因?yàn)槟悴恍枰o每個(gè) PE 配獨(dú)立的寄存器文件和讀取端口數(shù)據(jù)復(fù)用基本靠流動(dòng)實(shí)現(xiàn)。但脈動(dòng)陣列有它的剛性陣列維度固定每個(gè) PE 的運(yùn)算寬度固定遇到非常規(guī)形狀的算子就需要做大量邊界處理。最典型的是 attention 里的 softmax 和 LayerNorm這些算子計(jì)算非常簡(jiǎn)單但它們?cè)跀?shù)據(jù)流里的位置決定了陣列很難用脈動(dòng)方式覆蓋——你必須把數(shù)據(jù)從陣列里倒騰出來(lái)做這些操作再送回去。所以脈動(dòng)陣列設(shè)計(jì)多了一條潛規(guī)則重點(diǎn)服務(wù) GEMM 類算子其余雜算子交給旁邊的小向量單元。沒(méi)有這條配合整顆芯片的實(shí)際使用率會(huì)很難看。2.3 CGRA 的野心與代價(jià)靈活性的錢從編譯器那里出CGRA粗粒度可重構(gòu)數(shù)組的思路更激進(jìn)配置 PE 之間的互聯(lián)拓?fù)鋪?lái)匹配不同算子。理論上它為每個(gè)算子都能生成最適合的數(shù)據(jù)流結(jié)構(gòu)靈活性比固定脈動(dòng)陣列高得多。但問(wèn)題在于配置空間爆炸式增長(zhǎng)最優(yōu)映射是 NP-hard 級(jí)別的搜索問(wèn)題最終都得靠編譯器和調(diào)度器來(lái)兜底。我接觸過(guò)的 CGRA 項(xiàng)目都有同一個(gè)特點(diǎn)硬件看起來(lái)驚艷軟件團(tuán)隊(duì)苦不堪言。因?yàn)榫幾g器要從巨大的配置空間里找到一個(gè)接近最優(yōu)的解同時(shí)還要保證時(shí)序、端口和 Buffer 沒(méi)有沖突編譯時(shí)間動(dòng)不動(dòng)以小時(shí)計(jì)。對(duì)于面向市場(chǎng)交付的 AI 芯片團(tuán)隊(duì)這幾乎是不可能接受的工程體驗(yàn)?,F(xiàn)實(shí)的做法往往是 CGRA 和固定算子單元混著來(lái)用一小塊可重構(gòu)陣列覆蓋雜算子用固定流程的 MAC 陣列覆蓋主力 GEMM。想明白這一點(diǎn)很多架構(gòu)爭(zhēng)論其實(shí)可以提前畫(huà)上句號(hào)。3. 指令集與內(nèi)存模型NPU 的“憲法”和“土地制度”架構(gòu)路線定了接下來(lái)要解決的是指令集和內(nèi)存模型。這是軟硬件協(xié)同設(shè)計(jì)中最像“立憲”的環(huán)節(jié)指令集規(guī)定了軟件能用哪些能力內(nèi)存模型規(guī)定了數(shù)據(jù)怎么在系統(tǒng)中流動(dòng)。這兩件事定了編譯器和硬件團(tuán)隊(duì)就分頭干活了后面再想改代價(jià)非常大。反復(fù)強(qiáng)調(diào)這個(gè)是因?yàn)樵趯?shí)際項(xiàng)目里指令集設(shè)計(jì)經(jīng)常被壓縮到很短的周期導(dǎo)致后面編譯器團(tuán)隊(duì)叫苦連天。反過(guò)來(lái)內(nèi)存模型選擇失誤的話后期性能調(diào)優(yōu)幾乎沒(méi)有空間。3.1 顯式搬運(yùn)與隱式緩存兩條很難回頭的設(shè)計(jì)路線內(nèi)存模型有兩個(gè)基本選項(xiàng)。第一種是顯式搬運(yùn)模型軟件主要是編譯器負(fù)責(zé)控制 DMA 把數(shù)據(jù)從 DDR 搬進(jìn)片上 SRAM算完了再搬回去類似 DSP 的做法。第二種是隱式緩存模型硬件里做一個(gè)帶 cache 一致性的緩存層次軟件像訪問(wèn)普通內(nèi)存一樣訪問(wèn)數(shù)據(jù)硬件自動(dòng)維護(hù)。顯式搬運(yùn)模型的優(yōu)點(diǎn)是性能確定、可預(yù)測(cè)——你知道每個(gè)時(shí)鐘周期數(shù)據(jù)在哪里編譯器可以做出精確調(diào)度缺點(diǎn)是把大量負(fù)擔(dān)壓在編譯器上工具鏈一旦做得不好就是災(zāi)難。隱式緩存模型軟件寫起來(lái)輕松但硬件要處理緩存命中率、一致性協(xié)議而且遇到不規(guī)則訪存時(shí)性能波動(dòng)非常大沒(méi)法給編譯器一個(gè)穩(wěn)定的調(diào)度依據(jù)。我做設(shè)計(jì)時(shí)更傾向于顯式搬運(yùn)模型因?yàn)?AI 算子的訪存模式在編譯期大部分是可靜態(tài)分析的。數(shù)據(jù)在哪里、什么時(shí)候需要、什么時(shí)候?qū)懟鼐幾g器都能算出來(lái)這正是顯式搬運(yùn)模型的舒適區(qū)。而隱式緩存看著對(duì)軟件友好真實(shí)做下來(lái)容易演變成“硬件團(tuán)隊(duì)省了事性能永遠(yuǎn)到不了位”。3.2 標(biāo)量、向量、矩陣三級(jí)指令NPU 的職責(zé)切分無(wú)論走哪條路線NPU 指令集通常拆成三級(jí)。標(biāo)量指令負(fù)責(zé)控制流、地址計(jì)算、循環(huán)計(jì)數(shù)和同步一個(gè) for 循環(huán)展開(kāi)成幾條標(biāo)量指令告訴下一個(gè)計(jì)算單元“現(xiàn)在該算這組數(shù)據(jù)了”。向量指令負(fù)責(zé)逐元素操作ReLU、sigmoid、elementwise add、normalize 這些它們不是矩陣乘但數(shù)量極多如果沒(méi)有專用向量指令編譯器只能拿矩陣單元硬湊效率非常低。矩陣指令則是整個(gè)芯片的算力核心負(fù)責(zé) GEMM/GEMV通常對(duì)應(yīng)一個(gè)或多個(gè)專用計(jì)算陣列。這三級(jí)的調(diào)度配合很關(guān)鍵。我見(jiàn)過(guò)一個(gè)早期項(xiàng)目沒(méi)有獨(dú)立向量單元所有激活都靠矩陣單元做 matmul 形式計(jì)算結(jié)果是資源利用率低、功耗還高。后來(lái)加了向量單元整個(gè)模型性能提升了 30% 以上。做指令集設(shè)計(jì)時(shí)千萬(wàn)不要只盯著矩陣乘去統(tǒng)計(jì)一下目標(biāo)網(wǎng)絡(luò)里 elementwise 和 reduction 類操作的占比結(jié)果會(huì)讓你吃驚。3.3 寄存器視圖的一致性是軟硬件協(xié)同的第一份合同指令集定好了緊接著就是寄存器視圖。寄存器列表在工程上就是軟硬件團(tuán)隊(duì)之間的“合同”硬件按這個(gè)實(shí)現(xiàn)軟件按這個(gè)編程。但實(shí)際項(xiàng)目里寄存器視圖是最容易產(chǎn)生分歧和 bug 的地方。典型問(wèn)題有三種一是地址對(duì)齊軟件按 32 位寫了某個(gè)控制位硬件實(shí)現(xiàn)時(shí)按字節(jié)處理結(jié)果位錯(cuò)位二是位域更新時(shí)序軟件寫完配置寄存器立即去讀狀態(tài)寄存器但硬件還沒(méi)同步完讀回來(lái)的值還是舊的輪詢永遠(yuǎn)超時(shí)三是中斷狀態(tài)位沒(méi)有在硬件側(cè)做“讀清除”或“寫 1 清除”軟件讀完后狀態(tài)不自動(dòng)拉低中斷風(fēng)暴直接掛死系統(tǒng)。我的建議非常直白用統(tǒng)一的寄存器描述文件例如 IP-XACT 或 SystemRDL作為唯一事實(shí)來(lái)源從它同時(shí)生成 RTL、頭文件和文檔。這樣從源頭消除“文檔和實(shí)現(xiàn)不一致”的問(wèn)題。這個(gè)習(xí)慣一定要在項(xiàng)目第一天建立否則等芯片流片回來(lái)再靠人工對(duì)文檔你會(huì)在驗(yàn)證階段浪費(fèi)掉幾個(gè)月的窗口期。4. 沒(méi)有編譯器就沒(méi)有護(hù)城河從 PyTorch 到芯片上的二進(jìn)制很多做 AI 芯片的團(tuán)隊(duì)把大部分精力放在 RTL 上編譯器團(tuán)隊(duì)反而是后媽養(yǎng)的。在我看過(guò)的項(xiàng)目里凡是這么做的基本都后悔了。AI 芯片的競(jìng)爭(zhēng)力不是裸片面積小、主頻高而是工具鏈能讓用戶模型在上面的有效算力逼近理論值。硬件只是上限編譯器才決定了用戶實(shí)際能摸到什么水位。編譯器不是“把 PyTorch 模型轉(zhuǎn)成二進(jìn)制”這么簡(jiǎn)單。它掩蓋了一整條流水線模型導(dǎo)入、圖優(yōu)化、算子融合、算子映射、指令調(diào)度、內(nèi)存規(guī)劃、代碼生成。每一環(huán)都直接和前面的指令集、內(nèi)存模型、陣列拓?fù)漶詈稀?.1 算子融合把反復(fù)進(jìn)出 DDR 的中間結(jié)果留在片上算子融合是編譯器里收益最明顯、也最見(jiàn)功力的優(yōu)化。舉一個(gè)最簡(jiǎn)單的例子卷積 Bias ReLU 池化。如果不做融合每一層算子都要把中間結(jié)果寫回 DDR然后再讀出來(lái)做下一層一個(gè)字節(jié)的中間結(jié)果來(lái)回要付兩次訪存的錢。融合之后整條鏈在片上 SRAM 里一氣呵成DDR 只需要讀寫最終輸出。實(shí)測(cè)里同一個(gè)模型在融合前后的推理耗時(shí)差距經(jīng)常超過(guò) 30%-50%這個(gè)數(shù)字不是理論推導(dǎo)是我在項(xiàng)目里實(shí)測(cè)出來(lái)的。編譯器團(tuán)隊(duì)要做的是在 IR 層面識(shí)別這種算子鏈然后在后端把它們映射到同一條硬件流水線。為此硬件設(shè)計(jì)階段就要預(yù)留 fusing 的能力片上 Buffer 能不能串聯(lián)、DMA 能不能一次搬多塊數(shù)據(jù)、Ping-Pong 緩存能不能在計(jì)算的同時(shí)預(yù)取下一塊。這些都是軟硬件協(xié)同中的“契約”編譯器單方面做不來(lái)。4.2 靜態(tài)內(nèi)存規(guī)劃與指令調(diào)度替硬件做一張精確時(shí)間表AI 模型編譯期靜態(tài)分析的優(yōu)勢(shì)在于數(shù)據(jù)的生命周期基本是已知的。編譯器可以提前算出來(lái)每一塊 feature map 什么時(shí)候被產(chǎn)生、什么時(shí)候被消費(fèi)、什么時(shí)候可以被回收然后為整個(gè)網(wǎng)絡(luò)排出一張 DDR 與 SRAM 之間的搬運(yùn)時(shí)間表。實(shí)際工程里這一步會(huì)涉及給片上 SRAM 分區(qū)比如配置 buffer 區(qū)、計(jì)算輸入?yún)^(qū)、輸出暫存區(qū)、參數(shù)區(qū)還要為每個(gè) DSP/DMA 通道分配優(yōu)先級(jí)避免同一個(gè)時(shí)間點(diǎn)多個(gè)搬運(yùn)請(qǐng)求搶占帶寬。最簡(jiǎn)單也最見(jiàn)效的技巧是 double buffering計(jì)算第 N 塊數(shù)據(jù)的同時(shí)DMA 預(yù)取第 N1 塊這樣計(jì)算陣列永遠(yuǎn)不餓肚子。但要小心依賴沖突——如果第 N1 塊的數(shù)據(jù)是第 N 塊算出來(lái)的你沒(méi)法預(yù)取只能老老實(shí)實(shí)按順序執(zhí)行編譯器調(diào)度時(shí)要把這類數(shù)據(jù)依賴關(guān)系先算清楚。早期項(xiàng)目里我用過(guò)一個(gè)笨辦法先在周期級(jí)模擬器上測(cè)出每個(gè)算子的跑分再把這些算子作為節(jié)點(diǎn)做 list scheduling。這個(gè)過(guò)程很像排課表每個(gè)算子占用的資源不同數(shù)據(jù)依賴又給它們排了先后誰(shuí)先誰(shuí)后、哪個(gè)資源并行利用調(diào)度器在幾萬(wàn)次約束里找出一個(gè)可行的排程。編譯器做到這一步才叫真正開(kāi)始兌現(xiàn)硬件能力。4.3 給 LLM 做適配的真實(shí)體驗(yàn)頭疼的往往不是 GEMM最近兩年所有 NPU 工具鏈都被一個(gè)現(xiàn)實(shí)逼著做轉(zhuǎn)型給 LLM 做適配。一開(kāi)始很多人以為 LLM 的主角是巨型 GEMM把 GEMV 和 GEMM 優(yōu)化好就足夠了。實(shí)際做下來(lái)你會(huì)發(fā)現(xiàn)最耗時(shí)間的都是“雜活”attention 的 QKV 投影之前要做一堆 reshape 和 transposepadding 和 mask 操作在圖上到處都是softmax 里要對(duì)整行做 max-subtract-exp-sumLayerNorm 要對(duì)均值方差做歸一還有 KV cache 的索引與追加。這些算子單獨(dú)看都不大但數(shù)量多、形狀交互復(fù)雜最容易把編譯器的優(yōu)化徹底打亂。另一個(gè)難啃的點(diǎn)是動(dòng)態(tài) shape。LLM 推理時(shí)序列長(zhǎng)度是動(dòng)態(tài)的KV cache 大小也隨輪次變化這直接挑戰(zhàn)了編譯器“靜態(tài)分析一切”的根基。為了支持動(dòng)態(tài)長(zhǎng)度編譯器不能把所有 buffer 都按最大尺寸靜態(tài)分配否則片上內(nèi)存會(huì)爆炸也不能每一步都重新做地址計(jì)算否則調(diào)度開(kāi)銷會(huì)失控?,F(xiàn)在相對(duì)穩(wěn)妥的做法是分層分塊給批量尺寸做靜態(tài)規(guī)劃給序列長(zhǎng)度留動(dòng)態(tài)余量再用運(yùn)行時(shí)指令搬運(yùn)和地址重計(jì)算來(lái)兜底。這個(gè)方向還在快速演進(jìn)但有一點(diǎn)已經(jīng)非常清楚給 LLM 做適配工具鏈團(tuán)隊(duì)的工作量遠(yuǎn)超想象絕不是“跑通一個(gè) GEMM Kernel”就夠的。5. 芯片還沒(méi)回來(lái)軟件棧也得先轉(zhuǎn)起來(lái)虛擬原型與最小系統(tǒng)調(diào)試說(shuō)完了編譯器接著聊一個(gè)在進(jìn)度壓力下特別容易被擠掉、但真正決定流片后能否快速 bring-up 的環(huán)節(jié)芯片回來(lái)之前的軟硬件聯(lián)調(diào)。流片周期長(zhǎng)芯片從 tapeout 到 sample 回來(lái)往往要幾個(gè)月而你可能需要在芯片回來(lái)當(dāng)天就跑通一個(gè) demo給老板和客戶交代。等樣品到了才開(kāi)始搞軟件棧幾乎必然翻車。這里的經(jīng)典做法是在 RTL 還沒(méi)凍結(jié)之前就搭一套虛擬原型和一個(gè)最小硬件調(diào)試系統(tǒng)讓軟件團(tuán)隊(duì)提前把驅(qū)動(dòng)、Runtime 和關(guān)鍵算法調(diào)通。5.1 虛擬原型把“芯片回來(lái)之后”的軟件工作提前幾個(gè)月虛擬原型就是用 SystemC/TLM 這類方式把 SoC 和 NPU 的關(guān)鍵行為做成寄存器級(jí)/事務(wù)級(jí)模擬模型。它跑不了太大規(guī)模的數(shù)據(jù)但寄存器讀寫、中斷觸發(fā)、DMA 搬運(yùn)這些控制流語(yǔ)義是可以模擬出來(lái)的。有了虛擬原型驅(qū)動(dòng)團(tuán)隊(duì)可以在真實(shí)芯片回來(lái)之前就把設(shè)備枚舉、中斷處理、DMA 配置、寄存器讀寫的代碼寫好并調(diào)通編譯器團(tuán)隊(duì)也可以把編譯產(chǎn)物先灌進(jìn)去做功能驗(yàn)證。必須誠(chéng)實(shí)說(shuō)清楚它的局限虛擬原型不是周期精確的模擬器跑性能分析和功耗分析基本不靠譜。它只適合驗(yàn)證“控制流對(duì)不對(duì)”不適合驗(yàn)證“性能有多快”。所以在用虛擬原型時(shí)我會(huì)單獨(dú)搭一個(gè)輕量的周期級(jí)模擬器專門給編譯器做性能回歸。兩者分開(kāi)用效率最高。5.2 寄存器與中斷的驗(yàn)證盲區(qū)聯(lián)調(diào)時(shí)踩得最深的坑做軟硬件聯(lián)調(diào)這些年我見(jiàn)過(guò)太多了寄存器視圖問(wèn)題在真實(shí)芯片回來(lái)后集中爆發(fā)。最典型的場(chǎng)景軟件驅(qū)動(dòng)往 DMA 控制寄存器里寫完描述符地址就把啟動(dòng)位置 1然而硬件側(cè)是先讀啟動(dòng)位再取描述符地址。如果順序反了第一輪搬運(yùn)拿到的是空描述符DMA 直接卡死中斷永遠(yuǎn)不來(lái)。這類問(wèn)題在 RTL 仿真里往往能被發(fā)現(xiàn)但如果驗(yàn)證環(huán)境只做定向測(cè)試很容易漏掉。另一個(gè)高發(fā)坑是中斷狀態(tài)位不清零。有些硬件把中斷狀態(tài)寄存器設(shè)計(jì)成“只需讀不需要清”但實(shí)際上中斷源會(huì)持續(xù)拉高CPU 進(jìn)入 ISR 后讀了狀態(tài)位中斷信號(hào)還沒(méi)掉一退出又立刻觸發(fā)整個(gè)系統(tǒng)空轉(zhuǎn)在中斷風(fēng)暴里。最有效的工程防護(hù)是在硬件里加一個(gè)測(cè)試模式寄存器允許軟件強(qiáng)制置位或清零中斷源這樣驅(qū)動(dòng)開(kāi)發(fā)時(shí)就可以自主制造和消除中斷而不用每次都求硬件團(tuán)隊(duì)改 RTL。5.3 裸片點(diǎn)亮第一階段最小系統(tǒng)三板斧芯片 sample 回來(lái)以后軟件 bring-up 別急著跑模型。我在多個(gè)項(xiàng)目里驗(yàn)證過(guò)最有效的順序是先過(guò)一個(gè)“最小系統(tǒng)三板斧”第一板斧剪斷一切不必要的上電分支把最小系統(tǒng)需要的電源域、時(shí)鐘域整理干凈確保 CPU 能穩(wěn)定跑起來(lái)、JTAG 能連上。第二板斧確認(rèn)系統(tǒng)時(shí)鐘和復(fù)位很多上電不穩(wěn)的問(wèn)題都表現(xiàn)為寄存器讀回全是 0 或者全是 0xFFFFFFFF跑一遍地址自檢就能定位。第三板斧拿一個(gè)最簡(jiǎn)單的裸機(jī) demo 驗(yàn)證“搬運(yùn)-計(jì)算-寫回”三步往片上 SRAM 寫一塊數(shù)據(jù)讓 DMA 從一個(gè)地址搬到另一個(gè)地址再把結(jié)果讀回來(lái)對(duì)比。另外特別提醒上電初期一定要考慮看門狗的問(wèn)題。我看過(guò)不止一次整個(gè)系統(tǒng)一直在被看門狗復(fù)位軟件怎么查都覺(jué)得硬件壞了最后發(fā)現(xiàn)是默認(rèn)看門狗超時(shí)時(shí)間設(shè)置得太短代碼還沒(méi)跑起來(lái)就又被復(fù)位了。先把看門狗關(guān)掉等最小系統(tǒng)驗(yàn)證通過(guò)再打開(kāi)并配置正確的喂狗策略。這幾步走順之后再把編譯器生成的第一個(gè) model checkpoint 跑起來(lái)這樣的 bring-up 路徑基本不會(huì)出大岔子。6. 部署最后一公里量化、稀疏化與 Runtime 閉環(huán)芯片能跑模型了但離“用戶真正部署”還有最后一公里模型太小、內(nèi)存放不下、延遲下不來(lái)需要量化網(wǎng)絡(luò)里有大量冗余需要稀疏化硬件能力最終要通過(guò) Runtime 和驅(qū)動(dòng)暴露給用戶需要把整個(gè)閉環(huán)打通。這一節(jié)聊的幾個(gè)話題都是我在部署真實(shí)客戶模型時(shí)反復(fù)踩過(guò)坑的地方。6.1 INT8 量化從 FP32 到定點(diǎn)不是丟精度而是重建分布量化本質(zhì)上不是“把小數(shù)點(diǎn)砍掉”而是在目標(biāo)硬件支持的精度空間里重建數(shù)據(jù)的分布。最主流的兩種做法是 PTQ訓(xùn)練后量化和 QAT量化感知訓(xùn)練。PTQ 的優(yōu)點(diǎn)是省事拿一組代表性數(shù)據(jù)統(tǒng)計(jì)每一層激活值的 min/max 或分布直方圖然后用 KL 散度或 MSE 找一個(gè)盡量保住信息的 scale有些硬件還有 zero point 概念。QAT 則是把量化誤差放進(jìn)訓(xùn)練過(guò)程里讓模型自己適應(yīng)低精度表達(dá)精度通常更好但要重新訓(xùn)練成本高。實(shí)際項(xiàng)目里PTQ 最容易炸的位置往往在網(wǎng)絡(luò)的特殊結(jié)構(gòu)上殘差結(jié)構(gòu)的加法會(huì)把兩路量化誤差疊加LayerNorm 對(duì)數(shù)值尺度敏感softmax 的輸入差一點(diǎn)點(diǎn)輸出概率就變化很大。處理經(jīng)驗(yàn)是這些特殊層不要一刀切量化保留 FP32 中間計(jì)算或用混合精度比如用 INT8 做矩陣乘但累加器和最終輸出用 FP32 寫回。這張表格是我的經(jīng)驗(yàn)總結(jié)量化方法工作量精度損失硬件要求動(dòng)態(tài)量化低運(yùn)行期做 scale中等適合大模型粗部署需要硬件支持動(dòng)態(tài) scalePTQ中需要標(biāo)定數(shù)據(jù)集通??煽靥厥鈱右~外處理需要校準(zhǔn)邏輯和 per-channel 支持QAT高要訓(xùn)練最低需要硬件指令集支持偽量化節(jié)點(diǎn)6.2 稀疏化落地跳過(guò)計(jì)算的同時(shí)別把流水線打亂稀疏化在理論上能白賺很多加速比但硬件端實(shí)現(xiàn)起來(lái)很容易“揀了芝麻丟西瓜”。剪枝后的模型參數(shù)變成稀疏矩陣如果硬件還是按密集矩陣去讀那剪枝省下的 FLOPs 全部變成無(wú)效的訪存和等待速度不升反降。所以做一個(gè)高效稀疏加速器的核心是讓硬件能跳過(guò)零值計(jì)算而不是把稀疏數(shù)據(jù)重新填回密集格式。目前工程上最成熟的是結(jié)構(gòu)化稀疏典型如 2:4 稀疏——每 4 個(gè)連續(xù)權(quán)重里最多 2 個(gè)非零。這種規(guī)則讓硬件可以用一個(gè)很小的索引位圖跳過(guò)零值計(jì)算流水線不會(huì)亂。而我強(qiáng)烈建議普通團(tuán)隊(duì)先別碰非結(jié)構(gòu)化稀疏雖然壓縮率高但它會(huì)讓編譯器的數(shù)據(jù)布局、DMA 搬運(yùn)和 PE 調(diào)度復(fù)雜好幾倍后端訪存很容易變成隨機(jī)訪問(wèn)那還不如密集計(jì)算快。6.3 Runtime 與驅(qū)動(dòng)AI 芯片更像一個(gè)“顯式調(diào)度的協(xié)處理器”最后一段是 Runtime 與驅(qū)動(dòng)。和 CPU/GPU 那種“跑指令流”的設(shè)備不同AI 芯片更像一個(gè)需要被顯式調(diào)度的協(xié)處理器Runtime 要在用戶空間分配好片上內(nèi)存、配置好算子序列、把 DMA 描述符提交給內(nèi)核驅(qū)動(dòng)內(nèi)核驅(qū)動(dòng)再負(fù)責(zé) IOMMU 映射、中斷處理和硬件同步。分工上驅(qū)動(dòng)盡量薄、只碰硬件Runtime 盡量厚、負(fù)責(zé)模型級(jí)邏輯和調(diào)度。真正在部署階段會(huì)遇到的麻煩是顯式設(shè)備管理的復(fù)雜度多個(gè)推理任務(wù)并發(fā)時(shí)到底誰(shuí)占用計(jì)算陣列誰(shuí)在等帶寬片上內(nèi)存碎片如何避免高優(yōu)任務(wù)怎么樣能插隊(duì)但不打斷低優(yōu)任務(wù)正在執(zhí)行的 DMA。這些不是 RTOS 概念能直接套的必須針對(duì) NPU 的特性設(shè)計(jì)調(diào)度策略。我的經(jīng)驗(yàn)是先做一個(gè)簡(jiǎn)單的任務(wù)隊(duì)列和優(yōu)先級(jí)搶占模型把穩(wěn)定性跑出來(lái)再談性能優(yōu)化——部署階段的用戶不怕慢一點(diǎn)怕的是動(dòng)不動(dòng)就死鎖或 OOM。寫到這里話題差不多收住了。最后再分享一點(diǎn)個(gè)人的體會(huì)如果讓我重新立項(xiàng)做一款 AI 芯片我會(huì)在第一周就把三件事定下來(lái)——編譯器團(tuán)隊(duì)用的 mock 接口、寄存器描述文件的唯一事實(shí)來(lái)源、以及一個(gè)能跑軟件棧的虛擬原型。硬件確實(shí)決定了算力天花板但能讓用戶實(shí)際拿到的性能貼近天花板軟件棧從第一天開(kāi)始就是主戰(zhàn)場(chǎng)不是可有可無(wú)的附屬件。