vatt'ghern jaskier's ballads

Google 說:同一個模型,只是把運算從 CPU 換成透過 WebGPU 呼叫 GPU、透過 WebNN 呼叫 NPU,速度可以差到 5 到 60 倍——這一切都發生在瀏覽器分頁裡的一段 JavaScript,不用裝任何原生 app。

LiteRT.js:在瀏覽器裡跑 ML 推論

完這篇,你會知道 LiteRT.js 到底是什麼、它背後三個後端各自負責哪塊硬體、Google 那些看起來誇張的加速倍數其實在比較什麼,以及一個 PyTorch 模型要走幾步才會變成瀏覽器裡能呼叫的東西。

上傳、等待、拿結果——多數用到 ML 的網頁都在做這件事

多數會用到 ML 的網頁長這個樣子:使用者上傳一張照片或錄一段語音,瀏覽器把資料送到後端,伺服器上跑真正的模型,算完把結果——一段 JSON、一張新圖——傳回來,畫面才更新。這條路徑對使用者體驗的代價是延遲:上傳、排隊、推論、下載,每一段都要算進使用者等待的時間裡。這條路徑對營運方的代價是伺服器算力帳單——每一次推論都要占用一顆 GPU 或至少一顆 CPU core,流量越大帳單越線性成長。這條路徑對使用者隱私的代價最直接:照片、語音、文件內容,都真實地離開了使用者的裝置,進了一台你控制、但使用者看不到的機器。

Google 在介紹 LiteRT.js 時,把這三個代價講得很白:「enhanced user privacy, zero server costs, and ultra-low latency for real-time experiences」——把推論留在裝置裡,上面那條路徑的三段代價一次砍掉。這不是空話,是這篇文章要解釋的技術動作的直接動機:把原本只能跑在伺服器上的模型,搬進瀏覽器分頁裡執行。

這三段代價,在特定情境下會被放大。一張照片本身可能帶有人臉、環境背景這些個資意味濃厚的畫面;一段語音錄音帶著使用者的聲紋與說話內容;一份掃描文件可能是證件或病歷。這些資料一旦上傳到伺服器,即使事後刪除,也已經在傳輸與暫存的過程裡真實存在過一次——這是「隱私」這個詞在這裡的具體意思,不是抽象的原則。成本這一側也一樣具體:伺服器端要跑推論,通常得準備一台隨時待命的 GPU 或至少一顆專門的 CPU core,不管流量高低都在計費,流量一高,帳單直接跟著線性成長,這正是官方那句「zero server costs」要對付的問題。

點擊切換兩種路徑 · 伺服器往返/裝置本地

雲端伺服器 使用者裝置 目前:資料離開裝置,到伺服器算完,再傳回結果
官方原句:「enhanced user privacy, zero server costs, and ultra-low latency for real-time experiences」——把推論搬進裝置,圖裡那條出去繞一圈的路徑,就變成留在裝置內的短迴圈。

問題是,瀏覽器過去很長一段時間,沒辦法把這件事做好——這正是下一節要講的缺口。

官方那句話裡還有一個詞值得多想一下:「ultra-low latency for real-time experiences」。即時互動的應用——像是即時物件偵測、鏡頭前的即時濾鏡、語音轉文字這一類——延遲預算通常只有幾十毫秒,網路來回的變動性本身就會吃掉這個預算的一大塊:使用者的網路狀況每天、每個地點都不一樣,伺服器排隊的等待時間也隨著同時上線的人數波動。把推論搬到裝置端,這段不確定性直接消失,剩下的只有裝置本身的運算時間,是可以預期、可以量測的。

WASM 撐得住 CPU,GPU 與 NPU 這道門瀏覽器最近才打開

瀏覽器裡能跑 ML 模型,其實已經很多年了——TensorFlow.js 用 WebAssembly 把模型的計算跑在 CPU 上,這件事十年前就存在。問題是 CPU 做矩陣乘法很慢,模型一大,WASM 這條路很快就撞上使用者能忍受的延遲上限。要真的快,得把運算丟給 GPU,或是丟給裝置上專門的神經網路加速器(NPU)——但瀏覽器長年沒有一條乾淨的路徑通到那裡。

WebGL 可以拿來湊合著用:把矩陣運算包裝成貼圖著色器硬塞進去。問題是 WebGL 設計來畫圖,不是設計來做通用計算,能用的資料型別、記憶體存取模式都綁手綁腳,繞這個彎的代價不小。真正對得上號的是 WebGPU:一個把 GPU 的計算著色器(compute shader)直接開放給網頁 JavaScript 呼叫的 API,不用透過畫圖介面繞路,浮點運算可以直接丟給顯示卡。WebGPU 這幾年才在主流瀏覽器裡陸續到位,是「瀏覽器裡真的用得動 GPU」這整套故事能成立的前提——沒有這一步,LiteRT.js 的 GPU 後端根本無處掛載。電腦圖形學裡本來就有 shader 這個概念:一小段跑在 GPU 上、平行處理大量畫面像素的程式。compute shader 把這種平行運算能力從「只能用來畫圖」這件事解放出來,讓它可以拿去跑任何用得上大量平行浮點運算的工作,矩陣乘法正是其中之一——這也是為什麼 WebGPU 而不是 WebGL,才是 ML 推論真正能掛上去的那個 API。

NPU 這一側更晚,而且要先弄清楚 NPU 本身是什麼。NPU(Neural Processing Unit)是專門為神經網路那種大量矩陣乘加運算設計的硬體電路,跟做繪圖用的 GPU、什麼都能算的 CPU 不同,它犧牲通用性換取在特定運算模式下的能耗效率,手機上常聽到的「AI 晶片」多半就是這種東西。這種電路把大量乘加運算單元並排堆疊,跳過 CPU 為了處理各種指令而背負的分支預測、快取階層這些通用性負擔,換來的是同樣運算量下低得多的耗電。WebNN 是把裝置上的這種加速器開放給網頁呼叫的 API,LiteRT.js 官方文件自己也講得清楚:WebNN「currently experimental in Chrome and Edge」——目前只有兩家瀏覽器支援,而且掛著實驗性的標籤,不是所有裝置都吃得到 NPU 那條路。這也是為什麼一套完整的瀏覽器推論方案不能只做一個後端:CPU 永遠在,GPU 靠 WebGPU 這兩年才算成熟,NPU 靠 WebNN 還在半路上——三條路徑的成熟度本來就不一樣,這也解釋了為什麼官方把 WebNN 明確標成實驗性,而不是跟另外兩個一視同仁。

LiteRT.js 是什麼——一個 API,底下三條硬體路徑

LiteRT.js 是 Google 口中「a JavaScript binding of LiteRT for running AI directly inside the web browser」——LiteRT 是 TensorFlow Lite 改的名字,是那套已經在手機、嵌入式裝置上跑了很多年的裝置端推論引擎。LiteRT.js 把這套引擎搬進瀏覽器,官方自己的定位是「a powerful evolution from TensorFlow.js for executing .tflite models」:如果你原本用 TensorFlow.js 跑 .tflite 模型,LiteRT.js 就是同一件事的下一步,不是另起爐灶的新東西。

這個改名本身也值得多解釋一句。TensorFlow Lite 這幾年在 Android、iOS、微控制器這些裝置端場景已經跑得很成熟,LiteRT 是它換了名字之後的樣子,核心的推論引擎、模型格式(.tflite)都是同一套東西延續下來。LiteRT.js 要做的,不是重新發明一套裝置端推論引擎,而是把這套已經在行動裝置上驗證過的引擎,多開一扇瀏覽器窗口讓它跑進去——這也是為什麼官方敢說它是「powerful evolution」而不是「全新產品」:底層的推論邏輯是舊的,新的只是把它接到 WebGPU、WebNN 這些瀏覽器才有的硬體介面上。

寫程式的人只呼叫同一個 API——載入模型、餵資料進去、拿輸出——實際計算會被派去三個後端裡的一個:CPU 走 XNNPACK,Google 自己形容它是「highly optimized library for on-device CPU acceleration」;GPU 走 ML Drift 這套 Google 的 GPU 加速方案,底層透過 WebGPU 呼叫顯示卡;NPU 走 WebNN,目前還在實驗階段,只在 Chrome 與 Edge 可用。三個後端不是給你手動三選一——LiteRT.js 的角色,是把「這個瀏覽器、這台裝置現在能用哪個後端」這件事包起來,同一段呼叫程式碼,在不同瀏覽器、不同硬體上,實際執行的路徑不一樣。對寫程式的人來說,這種設計的好處很直接:不用為每一種瀏覽器、每一種裝置各寫一套判斷邏輯——要不要用 GPU、支不支援 WebNN、不行的話退回 CPU,這些分支都被收進 LiteRT.js 內部,呼叫端看到的永遠是同一個穩定介面。XNNPACK 這條 CPU 路徑也因此不會被 GPU、NPU 的存在淘汰:舊裝置、還沒跟上 WebGPU 的瀏覽器,最後還是得靠它撐住,「能跑」這件事本身就有價值,即使跑得比較慢。

LiteRT.js API(同一段 JS 呼叫)
XNNPACK · CPU
ML Drift + WebGPU · GPU
WebNN · NPU(實驗性)
三層疊在一起只是方便比較——實際執行時,同一次呼叫只會走進其中一條,由瀏覽器與裝置當下能支援哪個後端決定。

這三條路徑各自的定位,攤開來看最清楚——下面這張表把目標硬體、底層技術、目前的成熟度放在一起。

後端 目標硬體 底層技術 瀏覽器支援現況
XNNPACKCPUGoogle 的 CPU 推論加速函式庫所有主流瀏覽器
ML Drift + WebGPUGPUGoogle 的 GPU 加速方案,透過 WebGPU 呼叫顯示卡WebGPU 已在主流瀏覽器到位
WebNNNPU把裝置端神經網路加速器開放給網頁呼叫的 API僅 Chrome、Edge,官方標示為實驗性
官方把 GPU、NPU 這兩條路徑合併報一個數字:比純 CPU 快 5 到 60 倍,實際落在哪裡依裝置而定。

從 PyTorch 到瀏覽器——轉換、量化、部署三步

多數團隊手上的模型是用 PyTorch 訓練出來的,不是原生的 .tflite。LiteRT.js 這條路徑上,第一步是轉換,第二步是可選的量化,第三步才是部署到網頁——下面用分頁把每一步實際在做什麼攤開來看。

起點是一個用 PyTorch 訓練、存成 .pt 或 .pth 的模型檔——這一步還沒有任何 LiteRT 相關的動作發生,模型長什麼樣子完全由訓練團隊自己決定。

官方的說法是「PyTorch models can be converted in a single step」——用 LiteRT Torch 把模型轉成 LiteRT 認得的格式,不用重寫模型定義。下面是概念示意,不是官方逐字指令:

// 概念示意,非官方逐字指令
model = torch.load("model.pt")
litert_model = litert_torch.convert(model)
litert_model.save("model.tflite")

這一步是可選的。AI Edge Quantizer 讓你「configure tailored quantization schemes across different model layers」——不同層可以套用不同的量化精度,不是整個模型只能挑一種。官方只說這樣做「achieves substantial size reductions and performance gains」,沒有給出具體的縮小或加速比例,這裡也就不替它補一個聽起來精確的數字。合理的推測是,不同層對精度損失的敏感度不一樣,這也是為什麼粒度做到逐層,而不是整個模型一刀切。

轉換、(可能)量化過的模型,配上 npm 套件 @litertjs/core 就能在網頁裡載入執行——目前官方標示是「initial release」,還是剛起步的版本。Google 自己示範用的幾個現成模型:YOLO26 做即時物件偵測、Depth Anything V2 把單張照片估成深度圖再轉 3D 點雲、Real-ESRGAN 做 4 倍圖片放大,還有用 EmbeddingGemma 做向量搜尋。

這三步分開設計,其實是把兩件不同的事切開:轉換是「讓模型能跑」,量化是「讓模型跑得更小更快」。轉換幾乎沒有理由跳過——不轉就沒辦法在 LiteRT.js 裡執行;量化則是額外的工程決定,值不值得做,要看模型大小、下載時間、裝置記憶體這些具體限制。這個順序也隱含一個工程判斷:團隊真正要做的決定,是要不要為了下載體積或執行速度,去承受量化可能帶來的精度損失——這個判斷跟模型本身對精度的敏感度、應用場景能接受多少誤差有關,沒有放諸四海皆準的答案。單步轉換本身的價值也不該被略過:對一個原本要手動重寫模型定義才能上線的團隊來說,「一個指令轉換」直接把導入這條路的門檻壓低了一大截,這可能比任何加速倍數都更早決定一個團隊要不要嘗試。

官方那些倍數怎麼來的,以及什麼時候真的值得搬

回到最前面的動機:隱私、免掉伺服器帳單、低延遲。LiteRT.js 官方也給了具體的速度數字,但這兩個數字比較的對象不一樣,得分開看。

第一個數字:官方原句是「LiteRT.js delivers significant speedups」——「outperforming other web runtimes by up to 3x across both CPU and GPU」,這是拿 LiteRT.js 跟其他網頁端推論引擎比,同一顆 CPU 或同一張顯卡上,LiteRT.js 最多快 3 倍。這是 Google 自己的說法,不是第三方獨立測試,「up to」這三個字代表的是上限,不是平均,讀這種官方 benchmark 數字時,這是該養成的習慣。

第二個數字比較的是同一套 LiteRT.js 裡,後端之間的差距:「leveraging the GPU or NPU via WebGPU or WebNN delivers 5-60x speedup compared to standard CPU execution」——GPU 或 NPU 比純 CPU 快 5 到 60 倍。官方緊接著補了一句誠實的話:「Individual user performance may vary based on local GPU capabilities, thermal throttling, and browser driver optimization.」——會拿到 5 倍還是 60 倍,取決於使用者那台裝置的顯卡好不好、有沒有降頻、瀏覽器驅動優不優化,這幾件事開發者一個都管不到。

拖動滑桿看官方 5–60x 範圍落在哪裡 · 裝置條件 1–100

50 / 約 32x
裝置與驅動條件(差 → 好) 相對 CPU 的加速倍數 純 CPU(XNNPACK)=1x GPU/NPU(WebGPU、WebNN)
官方原句:「leveraging the GPU or NPU via WebGPU or WebNN delivers 5-60x speedup compared to standard CPU execution」,並註明「individual user performance may vary based on local GPU capabilities, thermal throttling, and browser driver optimization」——這條線是把那個範圍畫成一條可拖動的直線示意,兩端 5x、60x 是官方數字,中間是內插,不是新的實測。

這也是為什麼「什麼時候值得把推論搬進瀏覽器」不能只看倍數。三個場景比較容易划算:資料本身敏感,不想讓照片、語音、文件內容碰到伺服器;推論頻率高但單次運算量不算太大,伺服器帳單會隨流量線性成長,搬到用戶端等於把這筆成本轉嫁成使用者自己的電費;還有離線場景——訊號差、甚至完全沒有網路的地方,推論不需要等連線,模型跟資料都在裝置裡。前面提到的幾個官方示範模型,剛好各自對應到不同的動機:YOLO26 做即時物件偵測,吃的正是「即時」這個延遲預算;EmbeddingGemma 拿來做向量搜尋,很適合完全離線運作,不需要每次查詢都連回一個向量資料庫;Real-ESRGAN 這種影像放大則是運算量較大的一端,會不會值得搬到裝置端,取決於使用者裝置的 GPU 到底頂不頂得住——這也是為什麼「值不值得」永遠要回到自己的模型與使用情境去問,而不是套用一個通用答案。

反過來,不值得的場景也很具體:模型太大,裝置記憶體撐不住;WebNN 目前只有兩家瀏覽器支援,如果使用者大量分佈在其他瀏覽器上,NPU 這條路直接用不到,只能退回 CPU 或 GPU;@litertjs/core 還是「initial release」,介面與效能特性都可能在未來版本變動,現在導入等於也要跟著它的版本演進走。

把這幾個判準放在一起看,會發現「值不值得」其實是一個工程取捨,而不是一個看倍數就能拍板的決定。伺服器路徑省事——不用煩惱使用者裝置的 GPU 好不好、瀏覽器支不支援 WebGPU——但持續產生帳單,資料也持續離開使用者裝置。瀏覽器路徑省下這兩者,但要吃下裝置效能不一致、瀏覽器支援不齊的現實:同一個模型,在一台新款筆電上可能快得驚人,在一支舊手機的瀏覽器上可能連 WebGPU 都沒有,退回 XNNPACK 純 CPU 執行。這也是為什麼「3x」跟「5-60x」這兩個數字容易被混著讀——前者是跟別的引擎比,後者是自己內部的後端之間比,兩個都不是「你的應用會變快幾倍」的保證值,只是官方自己測出來的參考範圍。

Take-away:LiteRT.js 的本質是一個 JS API,背後接了三條可以各自加速的硬體路徑——要不要導入,不是看官方那句「最多 60 倍」,是看你要不要把使用者的資料留在他自己的裝置裡。