Back to blog
    GGUF 模型分發:應用商店打包 vs 安裝後下載
    GGUFdeploymentApp Storemodel deliverymobile AIsegment:mobile-builder

    GGUF 模型分發:應用商店打包 vs 安裝後下載

    兩種將 GGUF 模型送達使用者裝置的方式。與應用程式打包以獲得簡單性,或安裝後下載以獲得靈活性。兩種方案的架構、大小限制和最佳實踐。

    EErtas Team·

    您的模型已經微調並匯出為 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 IPA4GB包含所有資源
    iOS OTA 下載200MB行動網路下載限制(使用者可覆寫)
    Android APK150MB不使用 Play Asset Delivery 的情況
    Android AAB150MB 基礎 + 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.

    Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Keep reading