![]()
在 Android 上實現裝置端 AI 的困難之處不在於撰寫推論呼叫,而是相同的呼叫必須在數千種系統單晶片與驅動程式組合上執行。其中有些能優雅地加速你的模型,有些會在不告知的情況下退回 CPU,還有少數會產生錯誤的數值。有效的策略是在執行階段探測並記住結果。
在 iOS 上,你只需針對單一廠商的少數幾代晶片。而在 Android 上,你必須面對多家晶片廠商、各自多個世代,以及由裝置製造商按照自己的時程所出貨的驅動程式堆疊。兩支搭載相同旗艦晶片的手機,可能因為其中一支使用較舊的 GPU 驅動程式而表現不同。
由此導出的規則是:永遠不要依據裝置型號字串來決定是否加速。依裝置名稱建立的白名單會在一個版本週期內過期,無法涵蓋你出貨後才推出的裝置,而且它們是在有實際測量可用的情況下仍做出的猜測。應該在裝置上實際嘗試一次,並快取結果。
大致上有三種可用的抽象層級,選擇正確的層級主要取決於你需要多少控制權:
哪些加速路徑目前有效、哪些已被棄用而改用廠商 SDK,已改變過多次且仍持續變化。請將你選擇的 delegate 視為建置的參數,而非 Android 的既定事實,並在每次主要平台版本發布時重新檢查。以下的探測程式碼設計成只需修改一行就能更換 delegate。
無論你選擇哪種執行環境,模式都相同:嘗試建構加速的直譯器、執行固定的輸入、與 CPU 參考值比對,並計時兩者。如果建構拋出例外、數值差異超過容忍範圍,或加速路徑實際上並未更快,則使用 CPU。
enum Accel { NNAPI_OR_VENDOR, GPU, CPU }
data class ProbeResult(val accel: Accel, val medianMs: Double, val valid: Boolean)
fun probe(context: Context, modelBytes: ByteBuffer): Accel {
val golden = loadGoldenInputOutput(context) // fixed input + expected output
val results = mutableListOf<ProbeResult>()
// Always establish the CPU reference first: it is the tie-breaker
// for both correctness and speed.
val cpu = timeRun(modelBytes, Accel.CPU, golden)
results += cpu
for (accel in listOf(Accel.NNAPI_OR_VENDOR, Accel.GPU)) {
val r = try {
timeRun(modelBytes, accel, golden)
} catch (t: Throwable) {
// Delegate construction failing is normal, not exceptional.
ProbeResult(accel, Double.MAX_VALUE, valid = false)
}
results += r
}
val best = results
.filter { it.valid }
.minByOrNull { it.medianMs } ?: cpu
// Require a real margin. A 5% win is not worth a second code path.
return if (best.medianMs < cpu.medianMs * 0.8) best.accel else Accel.CPU
}
Enter fullscreen mode
Exit fullscreen mode
在功能首次被使用時,於背景執行緒執行此探測一次——不要在應用程式啟動時執行,因為那時它會與其他所有工作競爭啟動預算。將判斷結果以你的應用程式版本、模型版本與 OS build number 為鍵值持久化儲存,並在這三者任一改變時重新探測。系統更新可能會新增或移除可用的驅動程式,而你的快取答案不能比它活得更久。
探測中正確性這一半是最常被整合忽略的部分,卻也是能抓住最嚴重 bug 類型的那一部分:一個能執行、速度快、但結果錯誤的 delegate。
使用 SoC 識別碼與 OS build 來記錄探測結果。幾週後,這些記錄會成為你自己安裝基礎唯一準確的地圖,其價值遠超過任何公開的相容性矩陣。
與其設計一個必須在所有地方都能運作的體驗,不如定義兩到三個等級,讓探測來為裝置指派等級:
| 等級 | 描述 |
|---|---|
| accelerated | 探測找到有效且明顯更快的路徑。完整功能:更大的模型、更長的上下文、即時處理。 |
| cpu-capable | 沒有加速,但 CPU 推論能在互動預算內完成。較小的模型、批次處理或依需求執行而非即時。 |
| unsupported | CPU 推論太慢,或記憶體不允許載入模型。功能被隱藏或改由遠端提供。隱藏它是誠實的做法;一個需要九秒的功能比沒有功能更糟糕。 |
透過實際測量來決定,意味著隨著你的安裝基礎變化,等級指派仍能保持正確,也意味著在你推出後才發布的新手機能自動獲得良好的體驗。這也為你提供了一個乾淨的地方來放置緊急停止開關:如果某個 delegate 在部分使用者群上被發現有問題,你可以透過遠端旗標將該群組降級到 CPU,而不是推出緊急版本。此模式與 模型與提示的機能旗標 中描述的模式相同。
onTrimMemory。在背景 session 中持續保留模型是導致應用程式被殺掉、然後被怪罪冷啟動變慢的常見原因。如果 unsupported 等級退回至託管模型,則這兩條路徑在呼叫端必須可以互換——Multigrid 在各提供者之間暴露單一 API,並提供每次請求的成本與延遲,這讓遠端分支保持為單一實作,而非針對每個可能路由到的廠商各有一個。
https://dev.to/multigrid/on-device-ai-on-android-delegates-npus-and-fragmentation-2o2p