Back to blog
    llama.cpp iOS 集成:Swift 开发指南
    llama.cppiOSSwiftintegrationon-device AIMetalsegment:mobile-builder

    llama.cpp iOS 集成:Swift 开发指南

    将 llama.cpp 集成到 iOS 应用的分步指南。项目设置、Metal GPU 加速、模型加载、token 流式输出和生产部署的内存管理。

    EErtas Team·

    llama.cpp 是在 Apple 硬件上运行 GGUF 语言模型的推理引擎。它使用 Metal 进行 GPU 加速,支持从 A14(iPhone 12)开始的所有 iPhone 机型,根据模型大小和设备不同,token 生成速度在每秒 20-50 个之间。

    本指南涵盖从项目设置到生产部署的完整集成流程。

    集成选项

    选项 1:Swift Package(推荐)

    llama.cpp 仓库包含一个 Swift Package,您可以直接添加到 Xcode 项目中:

    1. 在 Xcode 中,前往 File,Add Package Dependencies
    2. 输入 llama.cpp 仓库 URL
    3. 选择所需的版本或分支
    4. 在 Swift 文件中导入 llama 模块

    这是最简单的集成路径。该 package 将 llama.cpp 作为构建的一部分编译,并将 C API 暴露给 Swift。

    选项 2:预编译 Framework

    将 llama.cpp 编译为 XCFramework,作为二进制依赖项包含。这样可以避免在项目中编译 C++ 源代码:

    # 构建 framework
    mkdir build-ios && cd build-ios
    cmake .. -G Xcode \
        -DCMAKE_SYSTEM_NAME=iOS \
        -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 \
        -DLLAMA_METAL=ON \
        -DBUILD_SHARED_LIBS=OFF
    cmake --build . --config Release

    选项 3:llama.swift 封装

    社区维护的 Swift 封装在 C 绑定之上提供了更符合 Swift 风格的 API。这些封装处理了桥接样板代码并暴露了更简洁的接口。

    项目设置

    最低要求

    • iOS 15.0+(Metal 计算着色器需要)
    • Xcode 15+
    • 测试需要物理设备(模拟器不支持 Metal 计算)

    构建设置

    将 Metal framework 添加到项目:

    • 链接 Metal.frameworkMetalKit.framework
    • 如需自定义着色器,设置 METAL_COMPILER_FLAGS

    权限

    无需特殊权限。llama.cpp 在应用的普通沙箱中运行。内存使用是主要关注点(见下文)。

    加载模型

    import llama
    
    class LlamaEngine {
        private var model: OpaquePointer?
        private var context: OpaquePointer?
    
        func loadModel(at path: String) throws {
            // 模型参数
            var modelParams = llama_model_default_params()
            modelParams.n_gpu_layers = 99 // 将所有层卸载到 Metal
    
            // 加载 GGUF 文件
            model = llama_load_model_from_file(path, modelParams)
            guard model != nil else {
                throw LlamaError.modelLoadFailed
            }
    
            // 创建推理上下文
            var ctxParams = llama_context_default_params()
            ctxParams.n_ctx = 2048      // 上下文窗口大小
            ctxParams.n_threads = 4     // CPU 线程数(用于非 Metal 操作)
            ctxParams.n_batch = 512     // 提示处理的批次大小
    
            context = llama_new_context_with_model(model, ctxParams)
            guard context != nil else {
                throw LlamaError.contextCreationFailed
            }
        }
    
        func unload() {
            if let ctx = context {
                llama_free(ctx)
                context = nil
            }
            if let mdl = model {
                llama_free_model(mdl)
                model = nil
            }
        }
    
        deinit {
            unload()
        }
    }

    关键参数

    n_gpu_layers: 设置为 99(或模型的实际层数)以将所有内容卸载到 Metal。这是最重要的性能设置。

    n_ctx: 上下文窗口大小(以 token 为单位)。更大的窗口使用更多内存。2048 对大多数移动端用例来说是实用的。如需更长对话可设为 4096。

    n_threads: 用于在 CPU 上运行操作的线程数。设置为设备的性能核心数量(iPhone 上通常为 2-4 个)。

    n_batch: 提示评估期间每批处理的 token 数。更高的值加速提示处理但使用更多内存。512 是一个好的默认值。

    生成文本

    分词和提示处理

    extension LlamaEngine {
        func generate(
            prompt: String,
            maxTokens: Int = 256,
            temperature: Float = 0.7,
            onToken: @escaping (String) -> Void
        ) -> String {
            guard let ctx = context, let mdl = model else { return "" }
    
            // 对提示进行分词
            let promptTokens = tokenize(prompt)
    
            // 创建提示处理批次
            var batch = llama_batch_init(Int32(promptTokens.count), 0, 1)
            for (i, token) in promptTokens.enumerated() {
                llama_batch_add(&batch, token, Int32(i), [0], i == promptTokens.count - 1)
            }
    
            // 处理提示
            llama_decode(ctx, batch)
            llama_batch_free(batch)
    
            // 生成 token
            var output = ""
            for _ in 0..<maxTokens {
                let logits = llama_get_logits(ctx)
    
                // 采样下一个 token
                let token = sampleToken(logits: logits!, temperature: temperature)
    
                // 检查是否到达序列末尾
                if llama_token_is_eog(mdl, token) { break }
    
                // 将 token 解码为字符串
                let piece = decodeToken(token)
                output += piece
                onToken(piece)
    
                // 准备下一个批次
                var nextBatch = llama_batch_init(1, 0, 1)
                llama_batch_add(&nextBatch, token, Int32(promptTokens.count + output.count), [0], true)
                llama_decode(ctx, nextBatch)
                llama_batch_free(nextBatch)
            }
    
            return output
        }
    
        private func tokenize(_ text: String) -> [llama_token] {
            let maxTokens = Int32(text.utf8.count + 16)
            var tokens = [llama_token](repeating: 0, count: Int(maxTokens))
            let count = llama_tokenize(model, text, Int32(text.utf8.count),
                                       &tokens, maxTokens, true, false)
            return Array(tokens.prefix(Int(count)))
        }
    
        private func decodeToken(_ token: llama_token) -> String {
            var buf = [CChar](repeating: 0, count: 64)
            let len = llama_token_to_piece(model, token, &buf, 64, 0, false)
            return String(cString: Array(buf.prefix(Int(len))) + [0])
        }
    }

    Metal GPU 加速

    当设置了 n_gpu_layers 时,Metal 加速会自动启用。llama.cpp 在首次加载时编译 Metal 着色器(耗时 1-2 秒,之后会缓存)。

    性能影响

    配置iPhone 15 Pro, 3B Q4iPhone 14, 3B Q4
    仅 CPU (n_gpu_layers = 0)8-12 tok/s6-10 tok/s
    Metal (n_gpu_layers = 99)18-25 tok/s14-18 tok/s

    Metal 平均提供 2 倍加速。生产环境务必启用。

    Metal 着色器缓存

    llama.cpp 首次在设备上运行时会编译 Metal 着色器。这会在首次模型加载时增加 1-2 秒。后续加载是即时的(着色器由 iOS 缓存)。

    内存管理

    内存预算

    iOS 大约给应用分配设备总 RAM 的 50-70%,超过后会触发 jetsam(强制终止):

    设备总 RAM应用预算可用于模型
    iPhone 12 (4GB)4GB约 2.5GB约 1.5GB
    iPhone 14 (6GB)6GB约 3.5GB约 2.5GB
    iPhone 15 Pro (8GB)8GB约 5GB约 3.5GB

    3B Q4 模型在 RAM 中占用约 2.2GB。在 6GB 设备上,这为应用、iOS 和其他进程留下约 1.3GB。紧张但可行。

    最佳实践

    // 加载前检查可用内存
    func canLoadModel(sizeBytes: Int) -> Bool {
        let available = os_proc_available_memory()
        // 为应用和系统预留 500MB
        return available > sizeBytes + 500_000_000
    }
    
    // 处理内存警告
    func didReceiveMemoryWarning() {
        engine.unload()
        // 显示"模型已卸载"消息,提供重新加载选项
    }
    • 始终在加载前检查可用内存
    • 当 AI 功能不活跃时卸载模型
    • 通过卸载模型来处理 didReceiveMemoryWarning
    • 应用在后台时绝不保持模型加载状态

    模型分发

    内置打包

    将 GGUF 文件作为资源添加到 Xcode 项目中。通过 Bundle.main 访问:

    let modelPath = Bundle.main.path(forResource: "model", ofType: "gguf")!

    对于超过 200MB 的模型,考虑使用 On Demand Resources 以避免增大初始下载大小。

    下载安装

    安装后下载模型并存储在应用的 Documents 目录中:

    let documentsURL = FileManager.default
        .urls(for: .documentDirectory, in: .userDomainMask)[0]
    let modelURL = documentsURL.appendingPathComponent("model.gguf")

    对大文件使用 URLSession 后台下载。支持中断后恢复下载。

    生产清单

    1. 模型在目标设备上加载不崩溃(在最低 RAM 目标设备上测试)
    2. Metal 加速已启用(通过性能日志验证)
    3. 内存警告处理程序优雅地卸载模型
    4. 下载后验证模型文件完整性(SHA256)
    5. 流式 token 在 UI 中平滑显示
    6. 用户可以取消生成(中断生成循环)
    7. 应用切换到后台时卸载模型
    8. 模型未加载时应用正常运行(优雅降级)

    微调后的 GGUF 模型是关键要素。基础模型生成通用回复。在您的领域数据上微调的模型(通过 Ertas 等平台)生成与应用目的和风格相匹配的回复。llama.cpp 的集成方式无论使用哪个模型都是相同的。

    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