Back to blog
    Busqueda semantica en el dispositivo: busqueda con IA sin servidor
    semantic searchon-device AIembeddingsmobile AIimplementationsegment:mobile-builder

    Busqueda semantica en el dispositivo: busqueda con IA sin servidor

    Como construir busqueda semantica que se ejecuta completamente en el telefono del usuario. Embeddings locales, similaridad vectorial y consultas en lenguaje natural sobre contenido del usuario sin servidor ni API.

    EErtas Team·

    La busqueda por palabras clave falla cuando los usuarios no conocen las palabras exactas. "Ese correo sobre la reunion del presupuesto de la semana pasada" no coincide con un correo con asunto "Revision Financiera Q3." La busqueda semantica entiende significado, no solo palabras clave.

    El enfoque estandar coloca la busqueda semantica en un servidor con una base de datos vectorial. Pero para apps moviles donde el contenido del usuario es local (notas, mensajes, fotos, documentos), enviar ese contenido a un servidor anula el proposito de mantenerlo en el dispositivo.

    La busqueda semantica en el dispositivo mantiene todo local. El modelo de embeddings se ejecuta en el telefono. El indice vectorial vive en almacenamiento local. La consulta de busqueda nunca sale del dispositivo.

    Como funciona la busqueda semantica

    1. Indexacion: Cada pieza de contenido se convierte en un vector de embedding (una lista de numeros que representa su significado) usando un modelo pequeno
    2. Almacenamiento: Los vectores de embedding se almacenan junto al contenido en una base de datos local
    3. Consulta: La consulta de busqueda del usuario se convierte en un vector de embedding usando el mismo modelo
    4. Comparacion: El vector de consulta se compara contra todos los vectores almacenados usando similaridad coseno
    5. Clasificacion: Los resultados se devuelven clasificados por puntuacion de similaridad

    La magia esta en los embeddings. Dos piezas de texto sobre el mismo tema producen vectores similares, incluso si no comparten palabras clave.

    El modelo de embedding

    Los modelos de embedding en el dispositivo son pequenos y rapidos. A diferencia de los LLMs generativos (600MB-1.7GB), los modelos de embedding tipicamente ocupan 20-80MB:

    ModeloTamanoDimensionesVelocidad (iPhone 15)
    all-MiniLM-L6-v223MB384500+ embeddings/seg
    nomic-embed-text-v1.555MB768200+ embeddings/seg
    bge-small-en-v1.533MB384400+ embeddings/seg

    A 200-500 embeddings por segundo, indexar 1,000 notas toma 2-5 segundos. El embedding de consulta es casi instantaneo (menos de 5ms).

    Ejecutando el modelo de embedding

    Puedes ejecutar modelos de embedding via:

    ONNX Runtime Mobile: Soporta modelos de embedding en formato ONNX. Disponible para iOS (via Swift) y Android (via Kotlin). La opcion mas madura para inferencia de embeddings en movil.

    // iOS con ONNX Runtime
    let session = try ORTSession(env: env, modelPath: embeddingModelPath)
    let inputTensor = try ORTValue(tensorData: tokenizedInput, shape: shape)
    let outputs = try session.run(withInputs: ["input_ids": inputTensor])
    let embedding = outputs["embeddings"]!.tensorData()
    

    Modo de embedding de llama.cpp: llama.cpp puede generar embeddings desde modelos GGUF usando la bandera de embedding. Esto te permite usar el mismo motor de inferencia para generacion y embedding.

    Almacenamiento vectorial

    SQLite con extension personalizada

    El enfoque mas simple para movil: almacenar vectores como BLOBs en SQLite y calcular similaridad en codigo de aplicacion.

    // Android: Almacenar embedding
    fun storeEmbedding(db: SQLiteDatabase, contentId: Long, embedding: FloatArray) {
        val blob = ByteBuffer.allocate(embedding.size * 4)
        embedding.forEach { blob.putFloat(it) }
        db.execSQL(
            "INSERT INTO embeddings (content_id, vector) VALUES (?, ?)",
            arrayOf(contentId, blob.array())
        )
    }
    
    // Buscar por similaridad
    fun search(db: SQLiteDatabase, queryEmbedding: FloatArray, limit: Int): List<SearchResult> {
        val cursor = db.rawQuery("SELECT content_id, vector FROM embeddings", null)
        val results = mutableListOf<SearchResult>()
    
        while (cursor.moveToNext()) {
            val blob = cursor.getBlob(1)
            val stored = FloatArray(blob.size / 4)
            ByteBuffer.wrap(blob).asFloatBuffer().get(stored)
    
            val similarity = cosineSimilarity(queryEmbedding, stored)
            results.add(SearchResult(cursor.getLong(0), similarity))
        }
    
        return results.sortedByDescending { it.similarity }.take(limit)
    }
    

    Esto es simple y funciona para colecciones de hasta ~10,000 elementos. Mas alla de eso, el escaneo lineal se vuelve lento.

    SQLite con extension vectorial

    Para colecciones mas grandes, usa una extension vectorial de SQLite que soporte busqueda de vecino mas cercano aproximado (ANN):

    • sqlite-vss: Extension de SQLite que usa Faiss para busqueda vectorial. Soporta iOS y Android.
    • sqlite-vec: Extension de busqueda vectorial ligera disenada para uso embebido.

    Estas extensiones crean un indice sobre los vectores, habilitando busqueda en sub-milisegundos sobre cientos de miles de elementos.

    El pipeline completo

    Paso 1: Indexar contenido

    Cuando el usuario crea o modifica contenido (nota, mensaje, documento), genera y almacena su embedding:

    func indexContent(_ content: Content) async {
        let embedding = await embeddingModel.encode(content.text)
        database.storeEmbedding(contentId: content.id, vector: embedding)
    }
    

    Ejecuta la indexacion en segundo plano. Los usuarios no deberian esperar a que se calculen los embeddings.

    Paso 2: Buscar

    Cuando el usuario ingresa una consulta de busqueda:

    func search(query: String) async -> [Content] {
        let queryEmbedding = await embeddingModel.encode(query)
        let results = database.similaritySearch(queryEmbedding, limit: 10)
        return results.map { fetchContent($0.contentId) }
    }
    

    La busqueda devuelve resultados clasificados por similaridad semantica. "Notas de la reunion del presupuesto" coincide con "Revision Financiera Q3" porque los embeddings capturan la relacion semantica.

    Paso 3: Busqueda hibrida

    Combina busqueda semantica con busqueda por palabras clave para mejores resultados:

    1. Ejecuta busqueda por palabras clave (SQLite FTS5) para coincidencias exactas
    2. Ejecuta busqueda semantica para coincidencias basadas en significado
    3. Combina y elimina duplicados de los resultados
    4. Clasifica por puntuacion combinada (coincidencias exactas con prioridad)

    Esto maneja tanto consultas exactas ("reunion con Juan") como consultas difusas ("ese correo sobre el cronograma del proyecto").

    Presupuesto de rendimiento

    ComponenteAlmacenamientoRAMVelocidad
    Modelo de embedding23-55MB50-100MB durante inferencia200-500 embeddings/seg
    Indice vectorial (10K items, 384d)~15MB~15MBMenos de 5ms por busqueda
    Indice vectorial (100K items, 384d)~150MB~30MB (con indice ANN)Menos de 10ms por busqueda

    Huella adicional total para busqueda semantica: 40-200MB almacenamiento, 65-130MB RAM durante busqueda. Esto es una fraccion de lo que requiere un LLM generativo, haciendolo practico incluso en dispositivos limitados.

    Casos de uso

    Apps de notas

    Busca en todas las notas por significado. "Notas de la reunion de la semana pasada sobre el lanzamiento del producto" encuentra notas relevantes independientemente de las palabras exactas.

    Clientes de correo

    Encuentra correos por tema, no solo por remitente o asunto. "Conversacion sobre la renovacion del contrato" muestra el hilo correcto.

    Apps de fotos

    Combina con subtitulado de imagenes (en el dispositivo) para habilitar busqueda de fotos por texto. "Atardecer en la playa" encuentra fotos coincidentes incluso sin etiquetas manuales.

    Gestores de documentos

    Busca en PDFs, documentos y archivos por contenido y significado.

    Combinando con LLMs en el dispositivo

    La busqueda semantica se combina naturalmente con modelos generativos en el dispositivo. Usa los resultados de busqueda como contexto para el LLM:

    1. El usuario hace una pregunta
    2. La busqueda semantica recupera contenido relevante de sus datos
    3. El LLM genera una respuesta usando el contenido recuperado como contexto

    Esto es RAG en el dispositivo. Sin servidor necesario. Todo el pipeline (embedding, busqueda, generacion) se ejecuta localmente.

    Para el componente generativo, fine-tunea un modelo con tus datos de dominio usando una plataforma como Ertas. El modelo fine-tuned combinado con busqueda semantica local crea un asistente de IA poderoso y completamente privado.

    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