
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.
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:
- En Xcode, ve a File, Add Package Dependencies
- Ingresa la URL del repositorio de llama.cpp
- Selecciona la version o branch que deseas
- Importa el modulo
llamaen 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.frameworkyMetalKit.framework - Configura
METAL_COMPILER_FLAGSsi 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
| Configuracion | iPhone 15 Pro, 3B Q4 | iPhone 14, 3B Q4 |
|---|---|---|
| Solo CPU (n_gpu_layers = 0) | 8-12 tok/s | 6-10 tok/s |
| Metal (n_gpu_layers = 99) | 18-25 tok/s | 14-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):
| Dispositivo | RAM total | Presupuesto de app | Disponible 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
didReceiveMemoryWarningdescargando 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
- El modelo carga sin crashes en dispositivos objetivo (prueba en el de menor RAM)
- La aceleracion Metal esta habilitada (verifica con logging de rendimiento)
- El manejador de advertencia de memoria descarga el modelo correctamente
- La integridad del archivo del modelo se verifica despues de la descarga (SHA256)
- El streaming de tokens se muestra fluidamente en la UI
- La generacion puede ser cancelada por el usuario (interrumpir el loop de generacion)
- El modelo se descarga en la transicion a segundo plano
- 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

AI in iOS Apps: CoreML, Cloud APIs, and On-Device LLMs Compared
Three paths to AI in your iOS app. CoreML for Apple's ecosystem, cloud APIs for capability, and on-device LLMs via llama.cpp for cost and privacy. A practical comparison for Swift developers.

Can LLMs Actually Run on iPhones? Benchmarks and Real-World Performance
Real benchmark data for running LLMs on iPhones via llama.cpp. Token generation speeds, memory usage, and thermal behavior across iPhone models from the iPhone 12 to iPhone 16 Pro.

llama.cpp on Android: A Kotlin Integration Guide
Step-by-step guide to integrating llama.cpp into an Android app with Kotlin. JNI bindings, Vulkan GPU acceleration, model loading, and memory management across the Android device spectrum.