
Enviando modelos GGUF: bundle en App Store vs descarga post-instalacion
Dos formas de llevar tu modelo GGUF al dispositivo del usuario. Incluirlo con la app para simplicidad, o descargar post-instalacion para flexibilidad. Arquitectura, limites de tamano y mejores practicas para ambos.
Tu modelo esta fine-tuned y exportado a GGUF. Ahora necesitas llevarlo al dispositivo del usuario. Hay dos enfoques fundamentales: incluirlo en el bundle con el binario de la app, o descargarlo despues de la instalacion.
Cada uno tiene compensaciones respecto al tamano de la app, experiencia del usuario, flexibilidad de actualizacion y restricciones de plataforma.
Opcion 1: Incluir en el bundle de la app
Incluye el archivo GGUF en el paquete de la app. El usuario descarga el modelo cuando descarga la app.
Bundle en iOS
Inclusion directa: Agrega el archivo GGUF a tu proyecto Xcode. Se envia dentro del IPA. Accede via Bundle.main:
let modelPath = Bundle.main.path(forResource: "model", ofType: "gguf")!
On Demand Resources (ODR): Etiqueta el modelo como recurso bajo demanda. iOS lo descarga cuando se necesita por primera vez, no al instalar la app. La descarga inicial de la app se mantiene pequena.
let request = NSBundleResourceRequest(tags: ["ai-model"])
request.beginAccessingResources { error in
guard error == nil else { return }
let modelPath = Bundle.main.path(forResource: "model", ofType: "gguf")!
loadModel(at: modelPath)
}
Los archivos ODR son gestionados por iOS y pueden ser purgados bajo presion de almacenamiento. Tu app debe manejar la re-descarga.
Bundle en Android
Assets de APK: Para modelos menores de 150MB, coloca el GGUF en assets/. Copia al almacenamiento interno en el primer lanzamiento para que llama.cpp pueda acceder.
Play Asset Delivery: Para modelos mas grandes, usa el sistema de entrega de assets de Google Play:
// Entrega en tiempo de instalacion (descargado con la app)
// build.gradle.kts
assetPacks += ":model_pack"
Play Asset Delivery soporta tres modos:
- Install-time: Descargado con la app. Lo mas simple pero aumenta la descarga inicial.
- Fast-follow: Descargado inmediatamente despues de la instalacion, en segundo plano.
- On-demand: Descargado cuando la app lo solicita.
Limites de tamano
| Plataforma | Limite | Notas |
|---|---|---|
| iOS IPA | 4GB | Incluye todos los recursos |
| iOS descarga OTA | 200MB | Limite de descarga celular (el usuario puede ignorar) |
| Android APK | 150MB | Sin Play Asset Delivery |
| Android AAB | 150MB base + 2GB assets | Con Play Asset Delivery |
| Pack de Play Asset Delivery | 512MB por pack | Multiples packs permitidos |
Un modelo GGUF 1B Q4 (~600MB) cabe dentro del limite de 4GB de iOS pero excede el umbral OTA celular de 200MB. En Android, requiere Play Asset Delivery.
Un modelo GGUF 3B Q4 (~1.7GB) cabe dentro de los limites superiores de ambas plataformas pero sera una descarga grande.
Pros y contras del bundle
Pros:
- El modelo esta disponible inmediatamente en el primer lanzamiento (sin espera de descarga)
- No se necesita infraestructura CDN
- No se requiere conectividad de red para el primer uso
- Arquitectura mas simple (sin logica de descarga/verificacion/reanudacion)
Contras:
- Aumenta significativamente el tamano de descarga de la app
- Las actualizaciones del modelo requieren una actualizacion completa de la app a traves de la tienda
- Revision de App Store por cada cambio de modelo
- Los usuarios pueden dudar en descargar una app de 600MB-1.7GB
- En iOS, el limite celular de 200MB significa que los usuarios pueden necesitar WiFi para descargar
Opcion 2: Descarga post-instalacion
La app se instala sin el modelo. En el primer lanzamiento (o cuando el usuario accede a la funcion de IA), la app descarga el modelo desde tu CDN.
Flujo de descarga
[App instalada] -> [Usuario abre funcion de IA] -> [Modelo no encontrado]
-> [Mostrar prompt de descarga: "Descargar modelo de IA (1.7GB)?"]
-> [Usuario toca Descargar] -> [Barra de progreso]
-> [Descarga completa] -> [Verificar hash] -> [Modelo listo]
Implementacion en iOS
class ModelDownloader: ObservableObject {
@Published var progress: Double = 0
@Published var isDownloading = false
@Published var isReady = false
private let modelURL = URL(string: "https://cdn.example.com/model.gguf")!
private var modelPath: URL {
FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("model.gguf")
}
func checkModelAvailable() -> Bool {
FileManager.default.fileExists(atPath: modelPath.path)
}
func downloadModel() async throws {
isDownloading = true
let (tempURL, response) = try await URLSession.shared.download(
from: modelURL,
delegate: ProgressDelegate { progress in
Task { @MainActor in self.progress = progress }
}
)
try FileManager.default.moveItem(at: tempURL, to: modelPath)
// Verificar integridad
guard verifyHash(modelPath, expected: expectedSHA256) else {
try FileManager.default.removeItem(at: modelPath)
throw ModelError.corruptedDownload
}
isDownloading = false
isReady = true
}
}
Implementacion en Android
class ModelDownloader(private val context: Context) {
private val modelFile = File(context.filesDir, "model.gguf")
fun isModelAvailable(): Boolean = modelFile.exists()
suspend fun downloadModel(
onProgress: (Float) -> Unit
) = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val request = Request.Builder().url(MODEL_CDN_URL).build()
val response = client.newCall(request).execute()
val body = response.body ?: throw IOException("Respuesta vacia")
val totalBytes = body.contentLength()
var downloadedBytes = 0L
modelFile.outputStream().use { output ->
body.byteStream().use { input ->
val buffer = ByteArray(8192)
var read: Int
while (input.read(buffer).also { read = it } != -1) {
output.write(buffer, 0, read)
downloadedBytes += read
onProgress(downloadedBytes.toFloat() / totalBytes)
}
}
}
// Verificar integridad
val hash = modelFile.sha256()
if (hash != EXPECTED_SHA256) {
modelFile.delete()
throw IOException("Descarga corrupta")
}
}
}
Configuracion de CDN
Aloja el archivo GGUF en un CDN para entrega rapida y confiable:
- AWS CloudFront + S3: Configuracion estandar. ~$0.085/GB de transferencia.
- Cloudflare R2: Sin costos de egreso para descargas. ~$0.015/GB solo almacenamiento.
- Firebase Hosting: Simple para proyectos pequenos. 10GB gratis, luego $0.15/GB.
Ejemplo de costos a 10,000 descargas mensuales de un modelo de 1.7GB:
- CloudFront: ~$1,445/mes
- Cloudflare R2: ~$0.26/mes (solo almacenamiento, sin egreso)
- Firebase: ~$2,550/mes
Los precios sin egreso de Cloudflare R2 lo hacen dramaticamente mas barato para distribucion de modelos.
Soporte de reanudacion
Las descargas grandes seran interrumpidas. Soporta reanudacion:
// iOS: Reanudar descarga interrumpida
let resumeData = try? Data(contentsOf: resumeDataURL)
if let resumeData = resumeData {
downloadTask = session.downloadTask(withResumeData: resumeData)
} else {
downloadTask = session.downloadTask(with: modelURL)
}
Pros y contras de descarga post-instalacion
Pros:
- Descarga inicial de app pequena (instalacion rapida, sin duda por tamano de la tienda)
- Actualizaciones del modelo sin revision de app store (sube nuevo modelo al CDN)
- Puede ofrecer multiples tamanos de modelo (1B para todos, 3B como upgrade)
- Los usuarios solo descargan si usan la funcion de IA
Contras:
- Retraso en primer uso (1-5 minutos para descarga)
- Requiere red para primer uso
- Infraestructura y costo de CDN
- Codigo mas complejo (descarga, verificacion, reanudacion, gestion de almacenamiento)
La recomendacion
| Escenario | Enfoque |
|---|---|
| Modelo 1B, IA es central en la app | Bundle (600MB es aceptable) |
| Modelo 3B, IA es central en la app | Entrega fast-follow / On-demand |
| IA es una funcion opcional | Descarga post-instalacion |
| Modelo se actualiza frecuentemente (mensual) | Descarga post-instalacion |
| Modelo es estable (actualizaciones trimestrales) | Bundle o fast-follow |
| Mercado objetivo tiene internet lento | Bundle |
Para la mayoria de apps: descarga post-instalacion con un prompt de descarga claro. Esto mantiene la descarga inicial de la app pequena, te permite actualizar modelos independientemente, y solo descarga para usuarios que realmente usan la funcion de IA.
Verificacion de integridad
Siempre verifica el archivo del modelo despues de la descarga. Un archivo GGUF corrupto causara crashes durante la inferencia:
func verifyHash(_ fileURL: URL, expected: String) -> Bool {
guard let data = try? Data(contentsOf: fileURL) else { return false }
let hash = SHA256.hash(data: data)
let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
return hashString == expected
}
Gestion de almacenamiento
Los modelos GGUF son grandes. Respeta el almacenamiento del usuario:
- Muestra el tamano del modelo antes de descargar
- Permite eliminar y re-descargar el modelo
- En iOS, excluye el modelo del respaldo de iCloud (puede ser re-descargado)
- Maneja escenarios de almacenamiento bajo correctamente
El modelo en si es lo que hace la diferencia. Un GGUF bien fine-tuned desde una plataforma como Ertas, entregado via bundle o descarga, proporciona IA especifica de dominio que se ejecuta localmente, instantaneamente y a cero costo por uso.
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

On-Device AI Model Size Guide: 1B vs 3B vs 7B for Mobile
How to choose the right model size for your mobile app. Capability breakdown, device requirements, quality benchmarks, and the fine-tuning factor that changes the math.

Quantization for Mobile: Q4, Q5, and Q8 Across Real Devices
A practical guide to GGUF quantization levels for mobile deployment. How Q4, Q5, and Q8 affect model size, speed, quality, and memory usage on iPhones and Android devices.

Llama 3.2 for Mobile Apps: Fine-Tuning and On-Device Deployment
A complete guide to using Meta's Llama 3.2 1B and 3B models in mobile apps. Fine-tuning with LoRA, exporting to GGUF, and deploying on iOS and Android via llama.cpp.