從光譜走向藍圖
前兩篇導覽已經把舞台搭好。我們看過了為什麼要專用化——Dennard 縮放終結、摩爾定律放緩,你不能再光等一個更快的時脈——也攤開了那道彈性光譜:從通用 CPU,經過GPU與可重組的FPGA,一路下到硬接線的ASIC。現在輪到真正要緊的問題了:當你已經選擇要造一個領域專用架構,你究竟該怎麼設計它?通用 CPU 是為了把任何東西都跑得勉強過得去而造的;而硬體加速器是為了把某一類東西跑得出神入化而造的,而光是這個目標的轉變,就把每一個設計決策都重寫了一遍。
你正在做的核心交換是這樣的。CPU 把多得驚人的一片矽與能量花在不是算術的地方,而是花在彈性上:猜測任何程式可能碰到什麼的快取、賭程式碼往哪個岔路走的分支預測器、在串列寫成的程式碼裡硬找平行的亂序執行機制。這一切都是你在每一道指令上付出的額外開銷,好讓晶片能跑它從沒見過的程式碼。而加速器事先就知道自己的工作量,於是它能把那份開銷刪掉,把省下的電晶體拿去做純粹的計算。專用化說到底,就是拿通用性去換效率——而底下那四條準則,不過就是把省下來的本錢花得有紀律的方法。
那四條準則
在那些真正出貨且勝出的加速器之間,同樣的四個招數反覆出現。它們不是一份你閉著眼照做的食譜;它們是四根槓桿,而功夫在於知道針對你的領域,每一根該拉多用力。把它們讀成一句話:「CPU 在哪裡浪費了力氣,而專家又是怎麼把它討回來的?」
- 採用專屬、由軟體管理的記憶體。CPU 的快取是猜要留什麼,付出標籤比對的開銷,有時還猜錯。加速器改用暫存記憶體(scratchpad)——一塊快速的晶片內記憶體,由編譯器明確地填入與排空,沒有標籤、沒有逐出輪盤、也沒有快取未命中。因為這個領域的存取模式事先就已知,於是是軟體、而非硬體,精確地決定什麼緊貼著算術單元擺。
- 把省下的電晶體花在更多算術單元上。每一顆你沒拿去燒在分支預測或亂序邏輯上的電晶體,都能變成另一個加法器或乘法器。CPU 核心只有寥寥幾條算術通道,而加速器卻能塞進數百、數千條,因為它不再付那筆把它們擠掉的通用性稅。
- 善用這個領域本來就有的平行性。別像 CPU 那樣去撈藏起來的指令層級平行——讓硬體去配合問題免費遞給你的那種平行形狀。如果領域裡滿是彼此獨立的向量運算,就鋪開寬闊的 SIMD 通道;如果是一串彼此獨立的資料項,就造一條管線;如果是矩陣乘法,就造一張二維網格。工作量的結構,就是晶片的結構。
- 採用這個領域能容忍的最低精度。數值精度並不免費——一個 32 位元乘法器,比一個 8 位元的大上許多、也餓上許多。許多領域根本不需要全精度:繪圖、訊號處理與神經網路,全都在降低精度下依然夠準。從 32 位元浮點數掉到 16 位元或 8 位元,能讓你在同樣的面積裡塞進好幾倍的算術單元,也讓每一次記憶體存取搬動好幾倍的數字。
當家範例:深度學習加速器
沒有哪個領域比深度學習更能把這幾條準則展示得淋漓盡致,而 Google 的張量處理單元(TPU)正是教科書範例。把一個神經網路剝到底,幾乎所有的工作都是同一個運算被重複了數十億次:矩陣乘法,也就是一場「乘了再加」的洪流。那是個近乎完美的標靶——運算簡單、每一層都一樣、又壓倒性地平行。於是一個深度學習加速器丟掉了 CPU 的通用性,造出一台幾乎除了乘加之外什麼都不做、卻做得規模龐大的機器。
TPU 的招牌動作是脈動陣列(systolic array)——它是下一篇導覽的主角,所以這裡我們只勾勒它為何契合那幾條準則。想像一張由微小乘加單元排成的方形網格。數字從邊緣流入,步調一致地橫越網格行進,像血液一陣陣搏過心臟(這正是「脈動」的意思)。每個單元把流經它的兩個值相乘、加進累計的總和、再把結果往下傳——於是一個從記憶體載入的數字,被一整列與一整行的單元重複使用,而不必一次又一次重新取回。光是這一招,就直擊整個領域裡那個安靜的大反派:資料搬移。從晶片外的 DRAM 讀一個數字,所耗的能量遠超過你拿它去做的那次乘法,所以搬資料搬得最少的加速器才會勝出,幾乎不管它有多少個乘法器。
而精度,正是第四條準則付出最豐厚紅利的地方。神經網路對雜訊很寬容,所以加速器用 bfloat16 來跑訓練——一種 16 位元浮點數,保留了 32 位元數字那個寬廣的範圍,卻在尾數上少花幾個位元——再用 int8、也就是純粹的 8 位元整數來跑推論。一個 8 位元乘法器的大小與能耗,只是 32 位元的一小部分,於是同一片矽能容下多得多的乘法器,而每一位元組的頻寬能載運四倍多的數值。誠實的但書:這之所以行得通,是因為深度學習容忍得了;你絕不會拿 int8 去跑銀行帳本或物理模擬。低精度是一張領域專用的執照,而不是一個放諸四海的升級。
不只是神經網路
深度學習搶盡了頭條,但加速器比它早了數十年,而且此刻就環繞在你身邊。你口袋裡那支手機,就是它們的一座小型動物園。數位訊號處理器(DSP)是一塊為音訊與無線電那些乘加繁重的數學調校過的晶片——在通話時濾你的人聲、解碼一個 Wi-Fi 訊號——做這些事,它遠比主 CPU 省電。一個視訊編解碼區塊用硬體解碼一條 4K 串流;同樣的事用軟體做會在一小時內榨乾你的電池,這正是它為何掙得了一塊自己專屬的矽。
再舉兩個,看這個點子能伸展得多廣。一個加密引擎用硬體做 AES 加密,於是每一次磁碟寫入、每一條 HTTPS 連線都被加密,卻幾乎不花 CPU——同樣的工作量在軟體裡會既更慢、又是個更肥美的旁路攻擊標靶。而在資料中心裡,一張 SmartNIC 或 DPU 是一張長出了自己的處理器與加速器的網路卡,把封包處理、加密與儲存的雜務接管過去,好讓昂貴的伺服器 CPU 騰出來去跑會付錢的客戶程式碼。不同的領域,同一本戰術手冊:找出一個熱門、穩定、平行的任務,給它專屬的硬體。
加速器到底何時才划算
現在輪到那個誠實的配重,因為專用化並不免費。加速器不是獨居的——它坐落在一塊系統單晶片(SoC)上,與 CPU、GPU、DSP 和記憶體控制器為鄰,全擠在一顆晶粒上。不合加速器的工作仍在 CPU 上跑,而關鍵在於,資料必須在它們之間搬來搬去。如果你把一個小工作丟給加速器,把資料拷過去、把硬體啟動起來、再把結果拷回來的代價,可能讓那點工作本身相形見絀。加速器在數學上可以快十倍,卻依然輸掉,因為數學從一開始就不是瓶頸。
還有第二筆、來得更慢的稅:把它造出來。一個 ASIC 要花一大筆錢、好幾年去設計、製造與驗證,第一顆晶片才會誕生。那筆前期成本,得靠它所跑的工作量來回本——這就是攤提的原理。所以加速器唯有在三個條件全部成立時才掙得回本。工作量必須夠大(好讓每次呼叫的開銷與設計成本,被那個量給相形見絀)、夠穩定(好讓凍在矽裡的硬體,不至於還沒出貨就過時了——這正是為什麼可重組的 FPGA 是給那些仍在變動中的工作量用的)、且夠平行(好讓那一大隊算術單元都有活幹、不致閒著)。
這正是為什麼真正的功夫在於硬體軟體協同設計:晶片與驅動它的軟體是一起設計的,彼此互相塑形。TPU 的 bfloat16 格式、它的暫存記憶體大小、它的指令集,都是為了配合它的編譯器如何鋪排一個神經網路而選的——而那個編譯器之所以被寫出來,就是為了讓那張脈動陣列不斷有東西吃。所以,設計一個領域專用架構,從來不只是畫一條更快的資料路徑。它是那件更難、也更誠實的工作:選出哪一個工作量配得上一塊矽、證明它夠大、夠穩定、夠平行,再把硬體與軟體協同設計起來,好讓你量到的加速,就是你真正領得回去的加速。