Back to blog
    llama.cpp en iOS: guia de integracion con Swift
    llama.cppiOSSwiftintegrationon-device AIMetalsegment:mobile-builder

    llama.cpp en iOS: guia de integracion con Swift

    Guia paso a paso para integrar llama.cpp en una app iOS. Configuracion del proyecto, aceleracion GPU con Metal, carga de modelos, streaming de tokens y gestion de memoria para despliegue en produccion.

    EErtas Team·

    llama.cpp es el motor de inferencia que ejecuta modelos de lenguaje GGUF en hardware Apple. Usa Metal para aceleracion GPU, soporta todos los modelos de iPhone desde el A14 (iPhone 12) en adelante, y genera tokens a 20-50 tokens por segundo dependiendo del tamano del modelo y dispositivo.

    Esta guia cubre la integracion desde la configuracion del proyecto hasta el despliegue en produccion.

    Opciones de integracion

    Opcion 1: Swift Package (Recomendado)

    El repositorio de llama.cpp incluye un Swift Package que puedes agregar directamente a tu proyecto Xcode:

    1. En Xcode, ve a File, Add Package Dependencies
    2. Ingresa la URL del repositorio de llama.cpp
    3. Selecciona la version o branch que deseas
    4. Importa el modulo llama en tus archivos Swift

    Este es el camino de integracion mas simple. El paquete compila llama.cpp como parte de tu build y expone la API C a Swift.

    Opcion 2: Framework pre-compilado

    Compila llama.cpp como un XCFramework e incluyelo como dependencia binaria. Esto evita compilar el codigo fuente C++ en tu proyecto:

    # Compilar el 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
    

    Opcion 3: Wrapper llama.swift

    Wrappers Swift mantenidos por la comunidad proporcionan una API Swift mas idiomatica sobre los bindings C. Estos manejan el boilerplate de bridging y exponen una interfaz mas limpia.

    Configuracion del proyecto

    Requisitos minimos

    • iOS 15.0+ (para shaders de computo Metal)
    • Xcode 15+
    • Un dispositivo fisico para pruebas (el Simulador no soporta computo Metal)

    Configuracion de build

    Agrega el framework Metal a tu proyecto:

    • Enlaza Metal.framework y MetalKit.framework
    • Configura METAL_COMPILER_FLAGS si es necesario para shaders personalizados

    Entitlements

    No se requieren entitlements especiales. llama.cpp se ejecuta en el sandbox normal de la app. El uso de memoria es la preocupacion principal (discutido abajo).

    Cargando un modelo

    import llama
    
    class LlamaEngine {
        private var model: OpaquePointer?
        private var context: OpaquePointer?
    
        func loadModel(at path: String) throws {
            // Parametros del modelo
            var modelParams = llama_model_default_params()
            modelParams.n_gpu_layers = 99 // Descargar todas las capas a Metal
    
            // Cargar el archivo GGUF
            model = llama_load_model_from_file(path, modelParams)
            guard model != nil else {
                throw LlamaError.modelLoadFailed
            }
    
            // Crear contexto de inferencia
            var ctxParams = llama_context_default_params()
            ctxParams.n_ctx = 2048      // Tamano de ventana de contexto
            ctxParams.n_threads = 4     // Hilos CPU (para ops no-Metal)
            ctxParams.n_batch = 512     // Tamano de lote para procesamiento de prompt
    
            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()
        }
    }
    

    Parametros clave

    n_gpu_layers: Configura a 99 (o el conteo real de capas del modelo) para descargar todo a Metal. Esta es la configuracion de rendimiento mas importante.

    n_ctx: El tamano de la ventana de contexto en tokens. Ventanas mas grandes usan mas memoria. 2048 es practico para la mayoria de casos moviles. 4096 si necesitas conversaciones mas largas.

    n_threads: Numero de hilos CPU para operaciones que se ejecutan en CPU. Configura al conteo de nucleos de rendimiento del dispositivo (tipicamente 2-4 en iPhones).

    n_batch: Tokens procesados por lote durante la evaluacion del prompt. Valores mas altos aceleran el procesamiento del prompt pero usan mas memoria. 512 es un buen valor por defecto.

    Generando texto

    Tokenizacion y procesamiento del prompt

    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 "" }
    
            // Tokenizar el prompt
            let promptTokens = tokenize(prompt)
    
            // Crear un lote para procesamiento del 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)
            }
    
            // Procesar el prompt
            llama_decode(ctx, batch)
            llama_batch_free(batch)
    
            // Generar tokens
            var output = ""
            for _ in 0..<maxTokens {
                let logits = llama_get_logits(ctx)
    
                // Muestrear siguiente token
                let token = sampleToken(logits: logits!, temperature: temperature)
    
                // Verificar fin de secuencia
                if llama_token_is_eog(mdl, token) { break }
    
                // Decodificar token a string
                let piece = decodeToken(token)
                output += piece
                onToken(piece)
    
                // Preparar siguiente lote
                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])
        }
    }
    

    Aceleracion GPU con Metal

    La aceleracion Metal es automatica cuando n_gpu_layers esta configurado. llama.cpp compila los shaders Metal en la primera carga (toma 1-2 segundos, se cachea despues).

    Impacto en rendimiento

    ConfiguracioniPhone 15 Pro, 3B Q4iPhone 14, 3B Q4
    Solo 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 proporciona una mejora de 2x en promedio. Siempre habilitalo para produccion.

    Cache de shaders Metal

    La primera vez que llama.cpp se ejecuta en un dispositivo, compila los shaders Metal. Esto agrega 1-2 segundos a la primera carga del modelo. Las cargas posteriores son instantaneas (los shaders se cachean por iOS).

    Gestion de memoria

    Presupuesto de memoria

    iOS da a las apps aproximadamente 50-70% del RAM total del dispositivo antes de activar jetsam (terminacion forzada):

    DispositivoRAM totalPresupuesto de appDisponible para modelo
    iPhone 12 (4GB)4GB~2.5GB~1.5GB
    iPhone 14 (6GB)6GB~3.5GB~2.5GB
    iPhone 15 Pro (8GB)8GB~5GB~3.5GB

    Un modelo 3B Q4 usa ~2.2GB en RAM. En un dispositivo de 6GB, esto deja ~1.3GB para tu app, iOS y otros procesos. Ajustado pero funcional.

    Mejores practicas

    // Verificar memoria disponible antes de cargar
    func canLoadModel(sizeBytes: Int) -> Bool {
        let available = os_proc_available_memory()
        // Dejar 500MB de margen para app y SO
        return available > sizeBytes + 500_000_000
    }
    
    // Manejar advertencias de memoria
    func didReceiveMemoryWarning() {
        engine.unload()
        // Mostrar mensaje "Modelo descargado", ofrecer recargar
    }
    
    • Siempre verifica la memoria disponible antes de cargar
    • Descarga el modelo cuando la funcion de IA no esta activa
    • Maneja didReceiveMemoryWarning descargando el modelo
    • Nunca mantengas el modelo cargado mientras la app esta en segundo plano

    Entrega del modelo

    Incluido en el bundle

    Agrega el archivo GGUF a tu proyecto Xcode como recurso. Accede via Bundle.main:

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

    Para modelos mayores de 200MB, considera usar On Demand Resources para evitar inflar la descarga inicial.

    Descargado

    Descarga el modelo post-instalacion y almacena en el directorio Documents de la app:

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

    Usa descargas en segundo plano de URLSession para archivos grandes. Soporta reanudacion en caso de interrupcion.

    Checklist de produccion

    1. El modelo carga sin crashes en dispositivos objetivo (prueba en el de menor RAM)
    2. La aceleracion Metal esta habilitada (verifica con logging de rendimiento)
    3. El manejador de advertencia de memoria descarga el modelo correctamente
    4. La integridad del archivo del modelo se verifica despues de la descarga (SHA256)
    5. El streaming de tokens se muestra fluidamente en la UI
    6. La generacion puede ser cancelada por el usuario (interrumpir el loop de generacion)
    7. El modelo se descarga en la transicion a segundo plano
    8. La app funciona normalmente cuando el modelo no esta cargado (respaldo gracioso)

    El modelo GGUF fine-tuned es el ingrediente critico. Un modelo base genera respuestas genericas. Un modelo fine-tuned con tus datos de dominio (via una plataforma como Ertas) genera respuestas que coinciden con el proposito y estilo de tu app. La integracion con llama.cpp es la misma en ambos casos.

    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