
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.
Free plan with 30 credits/mo, no card required. Paid plans from $25/mo USD.
Keep reading

IA en apps iOS: Core ML, APIs en la nube y LLMs en el dispositivo comparados
Tres caminos para agregar IA a tu app iOS. Core ML para tareas comunes, APIs en la nube para capacidad completa de LLM y modelos en el dispositivo via llama.cpp para costo y privacidad. Una comparacion practica para desarrolladores Swift.

llama.cpp en Android: guia de integracion con Kotlin
Guia paso a paso para integrar llama.cpp en una app Android con Kotlin. Bindings JNI, aceleracion GPU con Vulkan, carga de modelos y gestion de memoria en el espectro de dispositivos Android.

Pueden los LLMs ejecutarse en iPhones? Benchmarks y rendimiento real
Datos reales de benchmarks para ejecutar LLMs en iPhones via llama.cpp. Velocidades de generacion de tokens, uso de memoria y comportamiento termico en modelos de iPhone desde el iPhone 12 hasta el iPhone 16 Pro.