
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.
Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.
Keep reading

On-Device AI Model Size Guide: 1B vs 3B vs 7B for Mobile
How to choose the right model size for your mobile app. Capability breakdown, device requirements, quality benchmarks, and the fine-tuning factor that changes the math.

Quantization for Mobile: Q4, Q5, and Q8 Across Real Devices
A practical guide to GGUF quantization levels for mobile deployment. How Q4, Q5, and Q8 affect model size, speed, quality, and memory usage on iPhones and Android devices.

Llama 3.2 for Mobile Apps: Fine-Tuning and On-Device Deployment
A complete guide to using Meta's Llama 3.2 1B and 3B models in mobile apps. Fine-tuning with LoRA, exporting to GGUF, and deploying on iOS and Android via llama.cpp.