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 機型,並根據模型大小和裝置,以每秒 20-50 個 token 的速度生成。

    本指南涵蓋從專案設定到正式環境部署的完整整合流程。

    整合選項

    選項 1:Swift Package(推薦)

    llama.cpp 儲存庫包含一個 Swift Package,您可以直接新增到 Xcode 專案中:

    1. 在 Xcode 中,前往 File,Add Package Dependencies
    2. 輸入 llama.cpp 儲存庫 URL
    3. 選擇您想要的版本或分支
    4. 在 Swift 文件中匯入 llama 模組

    這是最簡單的整合路徑。該套件會在建構時編譯 llama.cpp,並將 C API 暴露給 Swift。

    選項 2:預建置框架

    將 llama.cpp 建構為 XCFramework 並作為二進位相依性包含。這可避免在專案中編譯 C++ 原始碼:

    # 建構框架
    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 框架新增到您的專案:

    • 連結 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 運算的 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 在觸發 jetsam(強制終止)之前,會給應用程式約 50-70% 的裝置總 RAM:

    裝置總 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