
GGUF + llama.cpp:在移动应用中部署微调模型
将微调AI模型打包为GGUF文件并在iOS和Android上通过llama.cpp运行的实用指南。包含文件大小、基准测试和集成模式。
你的微调模型在笔记本电脑上运行得很棒。响应速度快,质量正是你需要的,评估结果也很扎实。现在你需要它在50,000台iPhone上运行。
大多数移动开发者到这一步就停下了。将AI模型打包进应用二进制文件听起来很吓人:二进制大小、内存限制、热节流、平台特定的构建系统。但工具已经成熟了。像PocketPal AI这样的项目证明,一个完整的llama.cpp驱动的聊天界面可以在当今的旗舰手机上流畅运行。从微调权重到发布移动应用的路径是确定的、可重复的,比18个月前要容易得多。
本指南将介绍每一个步骤:GGUF格式、量化选择、iOS和Android集成、性能预期和分发策略。
什么是GGUF
GGUF(GGML Unified Format)是由llama.cpp项目维护的单文件模型格式。在GGUF之前,分发一个模型意味着要处理权重、配置、分词器词表和特殊token的多个独立文件。GGUF将所有内容打包成一个具有明确定义头部结构的便携二进制文件。
一个.gguf文件包含什么:
- 模型权重(量化或全精度)
- 架构元数据(层数、注意力头数、上下文长度、rope参数)
- 分词器词表和合并规则
- 特殊token定义(BOS、EOS、填充token)
单文件设计使GGUF非常适合移动部署。你引用一个路径,加载一个文件,推理就开始了。不需要多文件配置。llama.cpp原生读取GGUF,Ollama也使用GGUF作为其内部存储格式。
量化级别详解
量化将模型权重从32-bit或16-bit浮点数压缩为低精度整数。这大幅减少了文件大小和内存占用,代价是适度的质量损失,对于领域特定任务通常难以察觉。
| 格式 | 每权重位数 | 相对FP16的质量 | 说明 |
|---|---|---|---|
| Q4_K_M | 约4.5 bits | 基准下降2-4% | 移动端最佳大小-质量平衡 |
| Q5_K_M | 约5.5 bits | 基准下降1-2% | 连贯性明显更好,大约大20% |
| Q8_0 | 8 bits | 损失可忽略不计 | 接近无损,大小为Q4_K_M的2倍 |
| F16 | 16 bits | 基线 | 对大多数移动目标来说太大 |
Q4_K_M下的具体文件大小:
- Llama 3.2 1B:808 MB
- Llama 3.2 3B:2.02 GB
- Phi-3-mini 3.8B:约2.3 GB
- Llama 3.1 8B:约4.9 GB
对于移动端,Q4_K_M是默认推荐。相比Q5_K_M的质量差异对领域特定微调模型来说很小(因为已经约束了输出分布),而大小节省是显著的。Q8_0保留给桌面端或存储较不受限的离线平板。
1B模型的Q4_K_M版本(808 MB)可以轻松放在任何现代手机上。3B模型(2.02 GB)在旗舰设备上运行良好。8B模型4.9 GB,仅适合作为可下载资源部署在6 GB以上内存的设备上,不适合捆绑在二进制文件中。
llama.cpp在移动端的工作原理
llama.cpp是一个纯C/C++的GGUF模型推理引擎。核心库除C++标准库外没有外部依赖,这就是为什么它几乎能在任何平台上编译。
在加速方面,llama.cpp使用:
- Metal(iOS/macOS)通过Apple的Metal Performance Shaders进行GPU计算
- OpenCL或Vulkan(Android)在Qualcomm、ARM和MediaTek芯片上进行GPU计算
- NEON SIMD内联函数用于ARM CPU矩阵运算
- NNAPI(Android)作为委托路径通往较新芯片组上的硬件NPU
该项目提供示例二进制文件,但对于移动集成,你将其编译为静态库并通过平台绑定调用。官方Swift和Kotlin封装模式都已存在,PocketPal AI项目是两个平台上维护良好的开源参考。
iOS集成
构建设置
llama.cpp使用CMake。对于iOS,你交叉编译一个面向arm64-apple-ios的静态库:
cmake -B build-ios \
-DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \
-DPLATFORM=OS64 \
-DGGML_METAL=ON \
-DBUILD_SHARED_LIBS=OFF \
-DLLAMA_BUILD_TESTS=OFF \
.
cmake --build build-ios --config Release
GGML_METAL=ON标志启用Metal GPU加速。生成的libllama.a和libggml.a静态库链接到你的Xcode项目中。
另外,llama.cpp Swift Package Manager包提供了预构建的XCFramework,位于github.com/ggml-org/llama.cpp,它为你处理CMake步骤。
Swift绑定
SPM包暴露了一个LlamaContext Swift类,带有init(model: URL)初始化器和一个异步complete(prompt: String) -> AsyncStream<String>方法用于流式输出token。
最小集成示例如下:
import llama
class ModelRunner {
private var context: LlamaContext?
func load(modelURL: URL) async throws {
context = try await LlamaContext.createContext(path: modelURL.path)
}
func stream(prompt: String) -> AsyncStream<String> {
guard let ctx = context else { return AsyncStream { $0.finish() } }
return AsyncStream { continuation in
Task {
for await token in ctx.completionStream(text: prompt) {
continuation.yield(token)
}
continuation.finish()
}
}
}
}
iOS内存管理
iOS会在进程超出内存预算时无警告地终止它。关键规则:
- 只加载模型一次,在应用启动时或专用后台线程上。不要每次请求都重新初始化。
- 保守设置
n_ctx。 上下文长度直接决定KV缓存大小。2048-token的上下文比8192-token的内存占用小得多。大多数移动用例不需要超过2048 token。 - 监控内存警告。 实现
applicationDidReceiveMemoryWarning并通过释放KV缓存(调用llama_kv_cache_clear)来响应,而不是卸载整个模型。 - 使用
mmap加载标志。 llama.cpp支持内存映射文件加载(--mmap),让操作系统按需换入换出模型权重。在iOS上这减少了峰值RSS,代价是首个token的延迟略高。
1B Q4_K_M模型(808 MB)加上2048-token KV缓存可以在iPhone 12及以后的设备内存预算内舒适运行。3B模型需要至少6 GB内存的设备(iPhone 15 Pro及以后)。
Android集成
NDK构建
Android使用NDK工具链处理C/C++代码。将llama.cpp作为子模块添加或将其源代码复制到app/src/main/cpp/,然后配置CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(llama_android)
add_subdirectory(llama.cpp)
add_library(llama_jni SHARED jni_bridge.cpp)
target_link_libraries(llama_jni llama ggml android log)
在build.gradle(app模块)中:
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++17"
arguments "-DGGML_OPENMP=OFF", "-DLLAMA_BUILD_TESTS=OFF"
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
对于Android上的GPU加速,传递-DGGML_OPENCL=ON(需要OpenCL头文件)或-DGGML_VULKAN=ON(需要Vulkan SDK)。在Snapdragon设备上,Qualcomm的QNN后端通过-DGGML_QNN=ON提供NPU加速,不过这需要Qualcomm AI SDK。
JNI封装和Kotlin桥接
在jni_bridge.cpp中创建一个轻量的JNI桥接,封装llama.cpp的C API:
extern "C" JNIEXPORT jlong JNICALL
Java_com_yourapp_LlamaWrapper_loadModel(JNIEnv *env, jobject, jstring modelPath) {
const char *path = env->GetStringUTFChars(modelPath, nullptr);
llama_model_params mparams = llama_model_default_params();
llama_model *model = llama_load_model_from_file(path, mparams);
env->ReleaseStringUTFChars(modelPath, path);
return reinterpret_cast<jlong>(model);
}
在Kotlin端,一个轻量的封装类持有原生指针并暴露基于协程的API:
class LlamaWrapper(private val modelPath: String) {
private var modelHandle: Long = 0
fun load() {
modelHandle = loadModel(modelPath)
}
fun complete(prompt: String, onToken: (String) -> Unit) {
generateTokens(modelHandle, prompt, onToken)
}
fun close() {
if (modelHandle != 0L) {
freeModel(modelHandle)
modelHandle = 0
}
}
private external fun loadModel(path: String): Long
private external fun generateTokens(handle: Long, prompt: String, cb: (String) -> Unit)
private external fun freeModel(handle: Long)
companion object {
init { System.loadLibrary("llama_jni") }
}
}
将模型保持在绑定到Application生命周期的单例中,而不是绑定到单个Activity。每次屏幕切换时重建模型会导致明显的延迟和过多的电池消耗。
性能基准
在当前旗舰硬件上运行Q4或Q8量化模型的实际吞吐量:
| 设备 | 模型 | 量化 | Tokens/秒 | 后端 |
|---|---|---|---|---|
| iPhone 17 Pro | 1.5B | INT8 | 136 | NPU (Apple ANE) |
| Galaxy S25 Ultra | 2B | INT8 | 91 | NPU (Google Tensor) |
| iPhone 16 Pro | 1.5B | Q4 | 22(持续) | Metal GPU |
| Snapdragon 8 Elite | 13B | Q4 | 20+ | Hexagon NPU |
| iPhone 15 Pro | 1B | Q4 | 约18 | Metal GPU |
| 中端Android (SD 7s Gen 3) | 1B | Q4 | 8-12 | CPU NEON |
作为参考:人类阅读速度大约为每秒5-7 token。以上列出的每一台旗舰设备在使用1-3B模型时都超过了舒适的阅读速度。即使中端设备在8-12 tok/s下也能满足大多数用例的响应需求。
NPU数字(iPhone 17 Pro 136 tok/s, Galaxy S25 Ultra 91 tok/s)代表了能力的阶跃式提升。在NPU加速路径上,延迟降至CPU基线的大约1-20%, 每万亿次运算的功耗效率也显著高于GPU或CPU推理。
这些数字对UX意味着什么: 在iPhone 16 Pro上22 tok/s的速度下,200-token的回复在10秒内渲染完成。首token延迟(流式输出开始前的时间)通常为200-800毫秒,取决于提示词长度。两者对于大多数应用内助手模式来说都是可接受的。
热管理和电池
持续的LLM推理是移动处理器处理的最密集的热负荷之一。iPhone 16 Pro在持续负载下大约损失44%的吞吐量,因为SoC会降频以保护硬件温度。显示22 tok/s的基准测试在持续推理5-10分钟后可能降至12-14 tok/s。
实用的缓解策略:
限制推理时长。 对于大多数用例,回复在30秒内完成。设置适合你用例的最大token数(n_predict)。这限制了每次请求的热影响。
添加请求间延迟。 对于后台处理任务,在完成之间插入短暂停顿。即使短暂的停顿也能让SoC在下一次推理前散热。
对持续性任务选择更小的模型。 1B模型以更高的吞吐量生成token,产生的热量比3B模型少得多。对于分类、 抽取或格式化任务,较小的模型通常能产生同等质量的结果。
在Android上监控设备温度。 ThermalManager API(Android 10+)以0-6的刻度暴露热状态。注册监听器,并在设备升温时降低推理频率或上下文长度。iOS上没有直接等效的API,但你可以测量吞吐量下降作为替代指标。
电池指南: 1B模型在iPhone 16 Pro上全速持续运行时,SoC功耗大约比基线高2-3瓦。活跃推理会话相对于典型应用使用减少约20-30%的电池续航。对于大多数应用内助手模式(短暂的、用户发起的请求,中间有间歇),影响要小得多。
模型分发策略
如何将GGUF文件送达设备,与如何运行它同等重要。
捆绑在应用二进制中
优点:首次使用零延迟体验,不需要网络,不需要下载UI。 缺点:App Store大小限制。Apple允许不超过200 MB的OTA下载无需用户确认;超过4 GB的应用需要Wi-Fi下载。Google Play有类似的约束。
适用于: 1B Q4_K_M模型(808 MB)作为可下载资源,通过iOS On-Demand Resources或Android App Bundle资源包。模型不在主二进制中,但在安装时自动下载。
首次启动时下载
这是较大模型最常见的模式。应用发布时不包含模型,在首次启动时(或在"设置AI功能"的选择加入界面上)从你的CDN下载GGUF。
实施要点:
- 在iOS上使用
URLSession后台下载任务,这样用户将应用放到后台时下载仍会继续。 - 在Android上使用带
NETWORK_NOT_ROAMING或NETWORK_UNMETERED约束的WorkManager。 - 显示进度并附上清晰的说明("正在下载AI模型,2 GB,建议使用Wi-Fi")。
- 积极缓存下载。如果文件已存在且通过校验和验证,不要重新下载。
- 考虑将GGUF存储在应用的
Application Support目录(iOS)或filesDir(Android ),而非共享位置,以避免低存储时被系统清理。
增量更新
当你发布新的微调版本时,你可能不想每次更新都向每个用户推送2 GB。增量更新模式:
- 仅LoRA适配器: 如果你的更新是在同一基础模型上的新微调,只需发送LoRA适配器文件(通常20-200 MB),在推理时将其加载到冻结的基础模型上。llama.cpp通过
--lora标志或等效的API调用支持LoRA适配器。这比替换完整GGUF要节省带宽得多。 - GGUF差异更新: 对于需要新基础的架构变更,存在可以计算GGUF版本间二进制差异的工具。如果只有部分权重改变,补丁比完整重新下载要小得多。
- 版本标记的CDN路径: 将模型存储在如
/models/v2/model.Q4_K_M.gguf的路径下,并在应用启动时检查版本端点。仅当远程版本更新时才更新本地副本。
llama.cpp的平台替代方案
在提交llama.cpp集成路径之前,评估一下平台托管的API是否满足你的需求。
Apple Foundation Models API(iOS 18.4+ / WWDC 2025)
Apple发布了一个用于端上推理的公开Swift API,目标是其约3B参数的端上模型。该API是高级别的:你用GenerationOptions结构描述任务,接收文本、结构化JSON或工具调用。
优点: 不需要下载或维护模型,由Apple进行硬件优化,极其简单的Swift API,无需担心内存管理。
缺点: 你无法加载自定义微调模型。你被约束在Apple的基础模型及其能力范围内。Android上不可用。对于专业领域,模型质量可能不够。
在以下情况使用Foundation Models API:你的任务足够通用,Apple的基础模型能很好地处理,并且你想快速发布而无需管理模型文件。
在以下情况使用带自定义GGUF的llama.cpp:你需要领域特定的质量、跨平台行为,或对模型精确输出的控制。
Google Gemini Nano(Android, ML Kit GenAI APIs, Google I/O 2025)
Google的ML Kit现在通过托管API暴露了端上Gemini Nano推理。与Apple的方案类似,这运行的是操作系统管理的固定模型,而非自定义模型。
优点: 简单的API,在支持的Pixel和合作伙伴设备上不需要下载,与现有ML Kit模式集成。
缺点: 仅在Pixel 9和部分其他设备上可用。不支持自定义模型。跨设备一致性有限。
对于面向广泛设备且使用自定义微调模型的生产应用,llama.cpp配合GGUF仍然是最可移植的方案。
端到端检查清单
发布前请验证:
- GGUF以适合目标设备层级的量化级别导出(大多数移动目标使用Q4_K_M)
- 文件大小符合你的分发策略(即时OTA低于200 MB,延迟下载低于4 GB)
- 上下文长度(
n_ctx)设为所需最小值,而非模型最大值 - 模型在应用启动时或专用后台队列上加载一次,而非每次请求加载
- 内存警告已处理:卸载完整模型前先清理KV缓存
- 热节流已测试:运行推理10分钟以上并验证持续负载下的输出质量
- 为首次启动的模型分发实现了带进度反馈的后台下载
- 下载的GGUF在尝试加载前进行校验和验证
- Token限制(
n_predict)设为合理上限,以限制最坏情况下的推理时长
使用Ertas开始
以上集成工作假设你已经有了微调的GGUF。微调步骤就是Ertas发挥作用的地方。
上传你的领域数据,通过可视化界面配置训练参数,然后以目标量化级别导出GGUF结果。Ertas处理云GPU计算、数据集格式化和量化导出。你拿到的是一个可以直接放入上述iOS或Android集成的.gguf文件。
移动推理层是开源基础设施。真正的差异化因素是其中的模型:一个理解你的领域、你用户的语言和你产品特定输出需求的模型。这就是微调产生的价值,也是你拥有的部分。
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

Fine-Tuning for App Developers: A Non-ML-Engineer's Guide
A practical guide to fine-tuning AI models for mobile app developers. Learn LoRA, QLoRA, and GGUF export without needing an ML background.

GGUF Explained: The Open Format That Runs AI Anywhere
GGUF is the file format that made running AI models on consumer hardware practical. Here's what it is, how it works, and why every AI builder should understand it.

Shipping GGUF Models: App Store Bundling vs Post-Install Download
Two ways to get your GGUF model onto the user's device. Bundle it with the app for simplicity, or download post-install for flexibility. Architecture, size limits, and best practices for both.