BLAS 層級(the BLAS levels)
/ BLAS rhymes with 'glass' /
數值線性代數由一小組反覆出現的建構元件搭成——向量相加、矩陣乘向量、兩矩陣相乘。與其讓每個程式各自重造(且拙劣地最佳化)這些,社群議定了一份它們的標準目錄,稱為 BLAS(基本線性代數子程式),硬體廠商則為每款晶片出貨手工調校的版本。BLAS 分成三個層級,而層級編號恰恰能預測各運算在真機上能跑多快。
Level 1 是向量對向量運算:像 y = alpha*x + y(一種帶縮放的加法,稱 axpy)或內積。對長度 n 的向量它們做 O(n) 次浮點運算、碰 O(n) 筆資料,故算術強度約每載入一個元素 1 次浮點運算——無可救藥地記憶體受限。Level 2 是矩陣對向量運算,如 y = A*x。它們做 O(n^2) 次浮點運算、搬 O(n^2) 筆資料,仍約每元素 1 次浮點運算——依舊記憶體受限。Level 3 是矩陣對矩陣運算,皇冠是通用矩陣乘法 C = alpha*A*B + beta*C(稱 gemm)。魔法在此:O(n^3) 次浮點運算卻只用 O(n^2) 筆資料,故算術強度隨 n 增長。每個值一旦載入快取,約被重用 n 次。那就是能被分塊、向量化、平行化到逼近機器尖峰的運算。
實務教訓深刻且有點反直覺:要讓數值演算法快,你會想把它盡可能多的工作表達成 level-3 BLAS,即使這意味著以不同順序做相同的浮點運算。這正是為何 LAPACK(更高層的求解器與分解函式庫)會圍繞「在內層迴圈呼叫 gemm 的區塊演算法」重建——分塊 LU 或 Cholesky 把大部分浮點運算花在 level-3 呼叫上,因而繼承其近尖峰速度,而天真課本版本則淹沒在記憶體受限的 level-1、level-2 工作裡。「把它寫成矩陣對矩陣乘法」是高效能數值計算中最有力的啟發法之一。
把一個秩一更新加到矩陣上的三種做法說明了這些層級。逐元素迴圈類似 level-1,以記憶體速度爬行。level-2 呼叫 ger(A = A + x*y^T)較好,但仍約 1 flop/byte。把許多這類更新累積成一個 gemm(A = A + X*Y^T,X、Y 為高瘦矩陣)則是 level-3,跑近尖峰——浮點運算完全相同,但每個載入值的重用次數從 O(1) 升到 O(n)。
Level 1、2、3 為向量、矩陣對向量、矩陣對矩陣;只有 level 3 有足夠重用以達尖峰。
BLAS 是一個介面,不是單一實作——同一個 gemm 呼叫,來自廠商調校函式庫(MKL、OpenBLAS)可能比參考版本快 50 倍,而速度來自分塊與向量化,不是來自做更少浮點運算。永遠連結一個調校過的 BLAS;切勿自己手刻矩陣乘法。