GGUF 模型分發:應用商店打包 vs 安裝後下載
兩種將 GGUF 模型送達使用者裝置的方式。與應用程式打包以獲得簡單性,或安裝後下載以獲得靈活性。兩種方案的架構、大小限制和最佳實踐。
您的模型已經微調並匯出為 GGUF。現在需要將它送達使用者的裝置。有兩種基本方式:與應用程式二進位檔打包,或安裝後下載。
每種方式在應用程式大小、使用者體驗、更新靈活性和平台限制方面都有不同的取捨。
選項 1:與應用程式打包
將 GGUF 檔案包含在應用程式安裝包中。使用者下載應用程式時同時下載模型。
iOS 打包
直接包含: 將 GGUF 檔案新增到 Xcode 專案中。它隨 IPA 一起分發。透過 Bundle.main 存取:
let modelPath = Bundle.main.path(forResource: "model", ofType: "gguf")!On Demand Resources(ODR): 將模型標記為按需資源。iOS 在首次需要時下載,而非安裝時。初始應用程式下載保持較小。
let request = NSBundleResourceRequest(tags: ["ai-model"])
request.beginAccessingResources { error in
guard error == nil else { return }
let modelPath = Bundle.main.path(forResource: "model", ofType: "gguf")!
loadModel(at: modelPath)
}ODR 檔案由 iOS 管理,在儲存空間緊張時可能被清除。您的應用程式必須處理重新下載。
Android 打包
APK assets: 對於 150MB 以內的模型,將 GGUF 放在 assets/ 中。首次啟動時複製到內部儲存供 llama.cpp 存取。
Play Asset Delivery: 對於更大的模型,使用 Google Play 的資產分發系統:
// 安裝時分發(隨應用程式下載)
// build.gradle.kts
assetPacks += ":model_pack"Play Asset Delivery 支援三種模式:
- Install-time: 隨應用程式下載。最簡單但增加初始下載大小。
- Fast-follow: 安裝後立即在背景下載。
- On-demand: 應用程式請求時才下載。
大小限制
| 平台 | 限制 | 說明 |
|---|---|---|
| iOS IPA | 4GB | 包含所有資源 |
| iOS OTA 下載 | 200MB | 行動網路下載限制(使用者可覆寫) |
| Android APK | 150MB | 不使用 Play Asset Delivery 的情況 |
| Android AAB | 150MB 基礎 + 2GB 資產 | 使用 Play Asset Delivery |
| Play Asset Delivery 包 | 每包 512MB | 允許多個包 |
1B GGUF Q4 模型(約 600MB)在 iOS 的 4GB 限制內,但超過了 200MB 行動網路 OTA 閾值。在 Android 上需要 Play Asset Delivery。
3B GGUF Q4 模型(約 1.7GB)在兩個平台的上限內,但會是一個較大的下載。
打包的優缺點
優點:
- 首次啟動時模型立即可用(無需等待下載)
- 不需要 CDN 基礎設施
- 首次使用不需要網路連線
- 更簡單的架構(無 需下載/驗證/恢復邏輯)
缺點:
- 顯著增加應用程式下載大小
- 模型更新需要透過商店進行完整的應用程式更新
- 每次模型變更都需要應用商店審核
- 使用者可能不願下載 600MB-1.7GB 的應用程式
- 在 iOS 上,200MB 行動網路限制意味著使用者可能需要 WiFi 來下載
選項 2:安裝後下載
應用程式安裝時不包含模型。首次啟動時(或使用者存取 AI 功能時),應用程式從您的 CDN 下載模型。
下載流程
[應用程式已安裝] -> [使用者開啟 AI 功能] -> [未找到模型]
-> [顯示下載提示:「下載 AI 模型(1.7GB)?」]
-> [使用者點擊下載] -> [進度條]
-> [下載完成] -> [驗證雜湊] -> [模型就緒]
iOS 實作
class ModelDownloader: ObservableObject {
@Published var progress: Double = 0
@Published var isDownloading = false
@Published var isReady = false
private let modelURL = URL(string: "https://cdn.example.com/model.gguf")!
private var modelPath: URL {
FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("model.gguf")
}
func checkModelAvailable() -> Bool {
FileManager.default.fileExists(atPath: modelPath.path)
}
func downloadModel() async throws {
isDownloading = true
let (tempURL, response) = try await URLSession.shared.download(
from: modelURL,
delegate: ProgressDelegate { progress in
Task { @MainActor in self.progress = progress }
}
)
try FileManager.default.moveItem(at: tempURL, to: modelPath)
// 驗證完整性
guard verifyHash(modelPath, expected: expectedSHA256) else {
try FileManager.default.removeItem(at: modelPath)
throw ModelError.corruptedDownload
}
isDownloading = false
isReady = true
}
}Android 實作
class ModelDownloader(private val context: Context) {
private val modelFile = File(context.filesDir, "model.gguf")
fun isModelAvailable(): Boolean = modelFile.exists()
suspend fun downloadModel(
onProgress: (Float) -> Unit
) = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val request = Request.Builder().url(MODEL_CDN_URL).build()
val response = client.newCall(request).execute()
val body = response.body ?: throw IOException("Empty response")
val totalBytes = body.contentLength()
var downloadedBytes = 0L
modelFile.outputStream().use { output ->
body.byteStream().use { input ->
val buffer = ByteArray(8192)
var read: Int
while (input.read(buffer).also { read = it } != -1) {
output.write(buffer, 0, read)
downloadedBytes += read
onProgress(downloadedBytes.toFloat() / totalBytes)
}
}
}
// 驗證完整性
val hash = modelFile.sha256()
if (hash != EXPECTED_SHA256) {
modelFile.delete()
throw IOException("Corrupted download")
}
}
}CDN 設定
將 GGUF 檔案託管在 CDN 上以實現快速、可靠的分發:
- AWS CloudFront + S3: 標準方案。約 $0.085/GB 傳輸費用。
- Cloudflare R2: 無出站費用。僅約 $0.015/GB 儲存費用。
- Firebase Hosting: 適合小專案。10GB 免費,之後 $0.15/GB。
以每月 10,000 次下載 1.7GB 模型為例的成本:
- CloudFront:約 $1,445/月
- Cloudflare R2:約 $0.26/月(僅儲存費,無出站費)
- Firebase:約 $2,550/月
Cloudflare R2 的零出站費定價使其在模型分發方面成本大幅降低。
斷點續傳支援
大檔案下載會被中斷。支援斷點續傳:
// iOS:恢復中斷的下載
let resumeData = try? Data(contentsOf: resumeDataURL)
if let resumeData = resumeData {
downloadTask = session.downloadTask(withResumeData: resumeData)
} else {
downloadTask = session.downloadTask(with: modelURL)
}安裝後下載的優缺點
優點:
- 初始應用程式下載小(安裝快,不因商店大小而猶豫)
- 模型更新無需應用商店審核(將新模型推送到 CDN)
- 可提供多種模型大小(1B 給所有人,3B 作為升級選項)
- 使用者只在使用 AI 功能時才下載
缺點:
- 首次使用有延遲(下載需要 1-5 分鐘)
- 首次使用需要網路
- CDN 基礎設施和成本
- 更複雜的程式碼(下載、驗證、斷點續傳、儲存管理)
建議方案
| 場景 | 方案 |
|---|---|
| 1B 模型,AI 是應用程式核心 | 打包(600MB 可接受) |
| 3B 模型,AI 是應用程式核心 | Fast-follow / On-demand 分發 |
| AI 是選用功能 | 安裝後下載 |
| 模型頻繁更新(每月) | 安裝後下載 |
| 模型穩定(每季更新) | 打包或 fast-follow |
| 目標市場網路慢 | 打包 |
對於大多數應用程式:安裝後下載並提供清晰的下載提示。 這樣可以保持初始應用程式下載小,讓您獨立更新模型,並且只為實際使用 AI 功能的使用者下載。
完整性驗證
始終在下載後驗證模型檔案。損壞的 GGUF 檔案會在推理過程中導致當機:
func verifyHash(_ fileURL: URL, expected: String) -> Bool {
guard let data = try? Data(contentsOf: fileURL) else { return false }
let hash = SHA256.hash(data: data)
let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
return hashString == expected
}儲存管理
GGUF 模型檔案很大。尊重使用者的儲存空間:
- 下載前顯示模型大小
- 允許刪除並重新下載模型
- 在 iOS 上,將模型排除在 iCloud 備份之外(可以重新下載)
- 優雅處理低儲存空間場景
模型本身才是關鍵 。透過 Ertas 等平台產生的高品質微調 GGUF,無論透過打包還是下載分發,都能提供在本地即時運行、零使用成本的領域特定 AI。
Ship AI that runs on your users' devices.
Free plan with 30 credits/mo, no card required. Paid plans from $10/mo USD.
Keep reading
Gemma 3 行動端:微調與裝置端部署
如何將 Google 的 Gemma 3 模型用於裝置端行動 AI。模型選擇、LoRA 微調、GGUF 匯出,以及透過 llama.cpp 在 iOS 和 Android 上部署。
Llama 3.2 行動應用:微調與裝置端部署
在行動應用中使用 Meta Llama 3.2 1B 和 3B 模型的完整指南。使用 LoRA 微調、匯出至 GGUF,以及透過 llama.cpp 在 iOS 和 Android 上部署。
從雲端 API 遷移到裝置端 AI:完整指南
將行動應用程式從雲端 AI API 遷移到裝置端推理的分步遷移計畫。資料提取、微調、整合、測試、發布和監控。