前面都在跟記憶體搏鬥,今天來談純粹的「算力」。
在 1.3GHz 的破板子上跑即時人臉辨識:你有一串 512 維的人臉特徵,要跟資料庫的 5,000 名員工比對。 在 老闆說要10 FPS 的要求下,CPU 每秒得吞下 512×5000×10=2560萬次512×5000×10=2560萬次 的浮點數運算。
為了活下來,我先說服老闆了,最多只能3fps , 畢竟我們要的是辨識,不是畫面流暢.我們的比對模組歷經了三次慘烈的演化。
第一次演化:天真的 Java 苦工
最直覺的作法:在 Java 裡寫個雙重迴圈,算內積(餘弦相似度)。
float bestScore = -1.0f;
for (int i = 0; i < 5000; i++) {
float[] dbFeature = database.get(i);
float score = 0;
for (int j = 0; j < 512; j++) {
score += inputFeature[j] * dbFeature[j];
}
if (score > bestScore) bestScore = score;
}
實驗數據(5000 人臉比對):約 150 毫秒
低階 ARM 晶片上的 JVM 沒有強大的向量化優化。CPU 就像個苦力,老老實實地算完這 250 萬次乘法。比對一次 150ms,畫面頂多 6 FPS,太慢了。聽起來合格了,但是這只是人臉辨識中的一小段,其他處理也要花時間啊
第二次演化:殺雞用牛刀(Java 迴圈 + OpenCV)
Java 太慢?那就交給影像神器 OpenCV 的 C++ 底層來算!
for (Face authFace : database) {
featureDet.put(0, 0, inputFeature);
featureCmp.put(0, 0, authFace.getRgbFeature());
// 呼叫 OpenCV 底層算內積分數
Core.gemm(featureDet, featureCmp, 1.0, new Mat(), 0.0, cvResult);
// ...
}
實驗數據(5000 人臉比對):約 320 毫秒(居然更慢!?)
致命的 JNI 過路費 犯了跨語言呼叫的大忌:在 Java 迴圈裡瘋狂呼叫 JNI。 為了算一筆小小的乘法,程式過海關(JNI Overhead)來回跑了 5,000 趟。就像為了榨 5,000 杯果汁,開卡車一次只載「一顆蘋果」跑 5,000 趟去工廠,過路費早就超過榨果汁的時間了。
最終進化:降維打擊(ACL SDK + ARM NEON)
被教訓後,我們意識到:
- 不能過海關 5000 次:整個迴圈必須搬到 C++。
- OpenCV 太通用:我們需要針對這塊 ARM 晶片量身打造的武器。
於是誕生了最終版 aclsdk,底層改用 ARM 原廠的 Compute Library (ACL)。我們不再一對一單挑了,我們打群架。程式啟動時,5,000 人臉就先塞進 C++ 的超級矩陣(Tensor)裡。
新畫面進來時,只過「一次」海關:
// C++ 底層:一次做完 1 x 5000 的大矩陣乘法
NESFeatureMatcher::copy_to_tensor(*_tensor_a, inputFeature);
// 呼叫 ARM 底層硬體加速 (NEON SIMD) 一擊必殺
sgemm.run();
NESFeatureMatcher::copy_from_tensor(*_tensor_o, results.get());
實驗數據(5000 人臉比對):約 3 毫秒(百倍秒殺)
為何如此狂暴? 除了免除 JNI 過路費,真正的魔法是 ARM NEON SIMD(單指令多資料流)。
補充 simd 為何那麼猛
如果要用最簡單、生活化的方式來總結 SGEMM 和硬體加速,我們可以想像 「一個辦公室員工(CPU)要蓋 1 萬份公文(資料)」 的場景。
1. 傳統寫法 (一般迴圈)
員工走到遙遠的檔案室(主記憶體 RAM),找出一份公文,走回位子,拿起印章蓋下去。 然後再走去檔案室,拿下一份… 結果: 來回跑死,一天只能蓋 100 份。大部分時間都在走路。
2. 快取預讀 (Prefetch) —— 請個小助手
為了解決走路的時間,員工請了一個小助手。 當員工在位子上蓋章的時候,小助手拼命去檔案室把接下來要蓋的公文,一疊一疊搬到員工的桌上(L1 快取)。 結果: 員工再也不用起身了,只要伸手就能拿到下一份公文,動作變快。
3. SIMD (硬體加速) —— 特製連體印章
就算不用走路,一次蓋一份還是太慢。 於是公司配發了一個「特製的連體印章」,一次壓下去可以同時蓋 4 份公文。 結果: 蓋章的速度直接翻了 4 倍。
4. 矩陣分塊 (Tiling) —— 桌面空間管理
雖然小助手一直搬公文,但員工的「桌面空間有限」(L1 快取很小)。如果一次搬 1 萬份過來,會塞爆桌面,公文掉到地上反而更亂。 所以員工規定:「每次只搬剛好鋪滿桌面的數量」。這疊處理到完美結束後,全部收走,再換下一疊剛好鋪滿桌面的數量。 結果: 桌面永遠保持最高效率的運作狀態,絕不混亂。
5. 重新打包 (Packing) —— 事前整理
檔案室裡的公文是照著「年份」排的,但老闆要求你要照著「部門」來蓋章。 如果你一邊蓋章一邊自己翻找,會浪費超多時間。 所以小助手在把公文放到桌上之前,會先在旁邊重新洗牌、排好順序。送到員工桌上時,順序已經完美對齊。 結果: 員工閉著眼睛,拿著特製印章一路往下狂蓋就好,完全不用停下來找。
總結
SGEMM 就是這整套**「頂級辦公室 SOP」**的結合體。
你自己寫的 C++,就像是叫一個很猛的員工去蓋章,但他不懂辦公室政治和 SOP,只會傻傻來回跑。 而 ARM 原廠寫的 SGEMM,是連桌子大小、小助手走路速度、印章尺寸都精算過,設計出來的完美生產線。這就是為什麼它能這麼快的原因!

發佈留言