Back to blog
    Enviando modelos GGUF: bundle en App Store vs descarga post-instalacion
    GGUFdeploymentApp Storemodel deliverymobile AIsegment:mobile-builder

    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.

    EErtas Team·

    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

    PlataformaLimiteNotas
    iOS IPA4GBIncluye todos los recursos
    iOS descarga OTA200MBLimite de descarga celular (el usuario puede ignorar)
    Android APK150MBSin Play Asset Delivery
    Android AAB150MB base + 2GB assetsCon Play Asset Delivery
    Pack de Play Asset Delivery512MB por packMultiples 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

    EscenarioEnfoque
    Modelo 1B, IA es central en la appBundle (600MB es aceptable)
    Modelo 3B, IA es central en la appEntrega fast-follow / On-demand
    IA es una funcion opcionalDescarga post-instalacion
    Modelo se actualiza frecuentemente (mensual)Descarga post-instalacion
    Modelo es estable (actualizaciones trimestrales)Bundle o fast-follow
    Mercado objetivo tiene internet lentoBundle

    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