直接在瀏覽器跑 LLM,既兼顧隱私又不用複雜的GPU設定,太完美了吧?
從 WebLLM 到 Transformers.js,前端社群有一群反骨仔吹起一股「邊緣 LLM」的熱潮,可是,當真正將模型落地到使用者的瀏覽器時,第一個面對的考驗就是,WebGPU 真的有比 WASM 快嗎?
這陣子實測的結論比想像中更加戲劇化, 500M 以下的微型模型,WASM 反而快了 12%,但是對於 3B 以上的大模型,WASM 直接變成悲劇,直接讓Chrome撞的頭破血流(Chrome 沒有頭 = Headless Chrome)。
測試環境
| 項目 | 規格 |
|---|---|
| 機器 | MacBook Air M2, 8GB RAM |
| GPU | Apple M2, 8 核心 |
| 磁碟可用空間 | 9.7GB |
| 瀏覽器 | Chrome 136 (Playwright headless) |
| 框架 | WebLLM 0.2.84, @xenova/transformers 2.17.2 |
一開始遇到我的破MBA 8GB 機型的磁碟剩 9.7GB,這直接觸發了瀏覽器 Cache API 的物理配額限制(約為可用空間的 10%,即 ~970MB),這個限制直接決定了前端能載入的模型體積。
Phi-3.5 Mini @ MBA
原定計畫是測試熱門輕量級模型 Phi-3.5-mini-instruct(3.8B 參數),然而在 8GB MacBook Air 上,模型連載入都不行。
Phi-3.5-mini-instruct-q4f16_1-MLC 需佔用約 2,520 MB VRAM,當權重檔案下載到瀏覽器後,就出線 QuotaExceededError,即使換了一些快取的策略,還是都不行:
-
Cache API(預設):QuotaExceededError: Quota exceeded
-
IndexedDB:ArtifactIndexedDBCache failed to fetch
-
OPFS:SecurityError: unsafe file access
在 8GB 記憶體且磁碟空間不足的裝置上,3.8B 模型完全無法運作,瀏覽器儲存配額(Storage Quota)直接被吃滿。
Phi-3.5 Mini @ MStudio
為了確認是否是儲存配額問題,我改用 M2 Max 運行相同的 WebLLM 代碼與 Phi-3.5-mini-instruct-q4f16_1-MLC 模型:
結果:
| 指標 | M2 Max |
|---|---|
| tok/s | 57.4 |
| 準確率 | 90.0%(10 題對 9 題) |
| 總 tokens | 956 |
| 總時間 | 16.66s |
| 模型載入 | 一次成功 |
輸出 57.4 tok/s 速度在中文字的回覆上極度流暢,簡單的丟了一些問題,數學邏輯題,程式碼產出與生物資訊(Variant calling/WES)相關問答回答的也是有模有樣的。
輕薄的Client跑不動前端 LLM,真正的第一道關卡往往是瀏覽器的 Storage Allocator,而非 GPU 本身,那我的 MBA 到底可以跑什麼模型?
1. TinyLlama-1.1B
改用 @xenova/transformers (v2.17.2) 執行 TinyLlama-1.1B-Chat 時,雖然可成功下載權重,但推理的瞬間會拋出錯誤,
RangeError: offset is out of bounds at Uint8Array.set
,餵狗後發現這應該是 舊版 ONNX Runtime Web 處理特定運算子時超越 WASM 記憶體上限的已知issue。
2. SmolLM2-360M
再測試了 SmolLM2-360M-Instruct (q4f16):
速度:38.4 tok/s
聽說底層做了編譯優化,跑起來也是相當的穩定。但 360M 參數推理能力相當有限,回答問題的能力連ChatGPT剛出來時都不如啊… (雖然也知道參數大小差很多,但是已經被LLM寵壞了)
WebGPU vs WASM 效能對比
我內心還是覺得應該要公平比較 WebGPU vs WASM在效能上的差異,因此又找了一些模型在MAC Studio 上來比拼,先用 GPT-2(124M)跑了兩輪。
GPT-2(124M)是 2019 年的模型,答題水準大概長這樣…
Q: What is 2 + 2?
A: The answer is 2 + 2. ← ...
Q: How many legs does a cat have?
A: The answer is no. ← WTF
Q: Write a Python function to add two numbers:
A: def add_number(x, y): return x + y + 1 + 1 + 1 + 1... ← 有 def,但多加了一堆 1
Q: What is the capital of France?
A: The capital of France is the capital of France. ← 跳針
結果
| 後端 | tok/s | 準確率 | 總時間 |
|---|---|---|---|
| WASM | 23.2 | 30% | 16.39s |
| WebGPU | 20.4 | 30% | 18.68s |
在 124M 模型下,WASM 竟然反超 WebGPU 約 12%!
為什麼微型模型下 WASM 會贏?
- 資料傳送Overhead:Token 需要頻繁在 CPU 與 GPU 記憶體間複製,當計算量不夠大時,搬運時間直接侵蝕效能。
- Kernel 啟動成本:每次啟動 GPU Kernel 都有固定開銷,無法被小規模平行運算攤平。
- WASM 的進化:成熟的指令集與 JIT 優化,已足以讓 CPU 高效處理 124M 規模的矩陣運算。
3.8B 模型對比
上面 GPT-2 的結論是「124M 小模型 WASM 贏 12%」,好像就直接打臉我上一篇的農場標題XD TensorFlow.js 快看不到 LiteRT.js 的車尾燈了 ,
那如果換成更大的模型呢?我在 M2 Max上用同一個 Phi-3.5-mini-instruct-q4f16_1 模型分別跑了 WebGPU和 WASM,來看看結果如何。
| 後端 | tok/s | 準確率 | 總 tokens | 總時間 |
|---|---|---|---|---|
| WebGPU | 59.7 | 70%(10 題對 7 題) | 490 | 8.20s |
| WASM | 0.5 | 60%(10 題對 6 題) | 413 | 827s(13.8 分鐘) |
結果看去裡,WASM 慢了 119 倍,總共 10 題要跑將近 14 分鐘,而且單題延遲 72-100 秒,WebGPU 只要 0.8 秒左右。
為什麼差這麼多?合理的推測是因為 WASM 是 CPU 跑,CPU 的記憶體頻寬和算力是固定的 (還是MAC的CPU問題?),而模型從 124M 漲到 3.8B,每單位 token 的計算量跟著漲,但 CPU 的速度不會變。WebGPU 則是把運算丟給 GPU 的 parallel cores,理論上模型越大、GPU 的相對優勢越明顯。
反而像GPT-2 那種 124M 的規模,GPU 的 kernel 啟動和資料傳輸開銷反而吃掉平行化的好處,超過某個臨界點(大概 1B 左右)之後,WebGPU 的優勢就出現了。
又手癢補跑了一顆更大的模型 Qwen2.5-7B-Instruct(7B,q4f16)在 WebGPU 上:
| 指標 | 數值 |
|---|---|
| tok/s | 35.5 |
| 準確率 | 80%(10 題對 8 題) |
| 總 tokens | 643 |
| 總時間 | 18.13s |
7B 的模型在瀏覽器裡能跑到 35.5 tok/s,還能答對 8 題,不過這顆模型的變異較大,跑起來很明顯的卡頓與瀏覽器快往生的感覺,又回頭處理scaling 的部分,Phi-3.5 Mini 在 WebGPU 上把 max_tokens 從 50 拉到 400,準確率從 70% 升到 100%,tok/s 穩定在 59.7-65.2 之間,給模型更多生成空間,答案品質會明顯變好,而且速度幾乎不受影響。
小結
-
小模型不用硬上 WebGPU:若任務僅需 <500M 的微型模型,直接採用 WASM 即可,不僅可以避開 WebGPU 在不同瀏覽器(如舊版 Safari)的相容性問題,也無需撰寫繁複的 Fallback 邏輯。
-
儲存空間是隱形殺手:部署 >1B 模型時,除了關心使用者的顯示卡,務必優先檢查 Storage Quota。使用者硬碟空間不足導致的快取失敗,往往才是用戶體驗崩潰的主因。