La respuesta corta
La calidad de la recuperación en un producto de búsqueda con IA está limitada por dos cosas: lo bueno que sea el modelo de incrustación y lo barato que resulte ejecutarlo sobre un índice. La primera es una pregunta de investigación; la segunda, de ingeniería, y suele ser la segunda la que mantiene el producto en pie.
El equipo de ingeniería de Perplexity ha publicado con detalle ese segundo lado: la infraestructura de servicio detrás de pplx-embed y de los modelos de ranking que usa en su búsqueda, en Computer y en su plataforma de API. Este texto extrae las lecciones generales de esa pila.
¿Qué es una incrustación?
Una incrustación convierte un texto en una secuencia de números. El objetivo es conservar el significado: dos frases con sentidos parecidos acaban con secuencias parecidas. Así funciona la búsqueda: consultas y documentos caen en el mismo espacio y se recuperan los vecinos más próximos. Eso es lo que la separa de la coincidencia por palabras clave: una consulta de "alquiler de coches" puede encontrar una página sobre "renting de vehículos" que no contiene esas palabras.
El detalle crítico aquí es el tamaño del modelo. Los modelos de incrustación son Transformers pequeños, normalmente por debajo de mil millones de parámetros. Ser pequeños abarata el coste pero crea otro problema: el modelo es tan pequeño que el trabajo que lo rodea puede costar más que el modelo mismo.
Dos tipos de tráfico, un solo motor
Perplexity plantea el servicio de incrustaciones como dos cargas de trabajo, y la diferencia entre ellas lo determina todo:
| Carga | Cuándo | Qué optimiza |
|---|---|---|
| Incrustación por lotes | Al construir o reindexar la base de datos vectorial | Rendimiento: minimizar coste |
| Incrustación en línea | En el momento de la consulta, un texto corto | Latencia: responder rápido |
| Puntuación | Al ordenar documentos tras la búsqueda vectorial | Un equilibrio entre ambas |
La decisión más destacable del equipo: no construyeron un motor de incrustaciones aparte. El razonamiento es técnico y elegante: la incrustación por lotes se parece al prefill limitado por cómputo, mientras que la incrustación en línea, a menudo de unos pocos tokens, se parece al decode limitado por memoria. Los kernels de su pila de modelos de lenguaje ya tenían la forma adecuada, así que los reutilizaron.
Tres servicios: Ivy, Tulip y ROSE
Tres servicios atienden una petición, y la división no es casual:
- Ivy: una pasarela HTTP escrita en Rust. Hace el trabajo del lado de la CPU: análisis de JSON, tokenización, plantillado de entradas, división de lotes. También trocea las peticiones de lote grande y equilibra la carga entre réplicas.
- Tulip: la interfaz del servidor de inferencia. Un servidor gRPC construido con Rust, tokio y tonic; se encarga de la planificación y el agrupamiento antes de enviar al motor.
- ROSE: el motor de inferencia propiamente dicho. Sobre todo Python; contiene los kernels, las capas y las definiciones de modelo, y gestiona los grafos CUDA.
El patrón es generalizable: el trabajo de CPU y el de GPU se separan en servicios distintos, y el lado de la CPU se escribe en un lenguaje de sistemas. En modelos pequeños, la tokenización y el análisis de JSON son lo bastante caros como para dejar la GPU esperando, y por eso no se quedaron en Python.
¿Por qué el planificador es deliberadamente simple?
Tulip atiende las peticiones por orden de llegada: no hay un esquema de prioridades elaborado. No es pereza sino una decisión basada en la medición.
Con las longitudes de secuencia que sirve Perplexity y en modelos de incrustación pequeños, el coste lineal de las capas densas domina sobre el coste cuadrático de la atención. La consecuencia: la latencia es aproximadamente proporcional al número de tokens, no al de secuencias. Una vez que un lote satura la GPU —en torno a 512 tokens en un modelo de menos de mil millones de parámetros— meter más secuencias no mejora la eficiencia.
La regla práctica que se deduce: no optimices un planificador antes de medir tu propia pila. Donde domina el término cuadrático, planificar con astucia compensa; donde domina el lineal, ese mismo esfuerzo se pierde.
El cuello de botella real: lanzar kernels
En lotes pequeños ocurre algo sorprendente: el coste que tiene para la CPU lanzar los kernels de la GPU puede superar el tiempo de cómputo real de la GPU. La GPU termina y espera la siguiente instrucción.
La solución son los grafos CUDA. El modelo entero se captura en un solo grafo, de modo que todos los lanzamientos se reducen a una única llamada al controlador. Como los modelos de incrustación son pequeños, el punto en que el trabajo de GPU supera al coste de lanzamiento llega tarde: miles de tokens y decenas de secuencias.
Pero los grafos tienen un precio: hay que capturar cada configuración por separado. Incluso redondeando los recuentos de tokens a múltiplos de 64 o 256, salen miles de grafos y varios minutos de captura por modelo. La respuesta de Perplexity es la captura diferida: cada configuración se ejecuta una vez de forma normal y se captura en su segundo uso. Eso sacrifica la latencia p99 en el arranque pero reparte minutos de trabajo a lo largo de horas.
No esperar a la espera
El segundo mecanismo es el LazyTensor. Normalmente un paso de inferencia se bloquea hasta que la GPU termina, y la CPU se queda ociosa mientras tanto. En su lugar, un LazyTensor rastrea un búfer de host anclado, una copia asíncrona y un evento CUDA; la función de paso devuelve de inmediato en vez de bloquearse.
El resultado: una tarea en Rust espera el lote N mientras la CPU empieza a preparar el N+1. La ganancia no viene de hardware nuevo sino de llenar el tiempo muerto.
La elección de kernel sigue siendo manual
ROSE admite varios backends de atención para entradas irregulares: FlashInfer 2, FlashInfer 3 y FlashAttention 4. El equipo indica que FlashAttention 4 suele ser más rápido, pero FlashInfer 3 lo supera en modelos basados en Qwen con secuencias muy largas, así que la elección se hace caso por caso.
Un detalle más: al servir un modelo de incrustación, ROSE no instancia ninguna caché KV y despacha a variantes de atención irregular para evitar el relleno. En incrustación no hay un siguiente token que producir; esa pieza heredada de la maquinaria de los modelos de lenguaje solo consumiría memoria aquí.
¿Por qué es tan difícil?
Contra la intuición, la dificultad del servicio de incrustaciones no viene del tamaño del modelo sino de su pequeñez. En un modelo de lenguaje grande la GPU hace tanto trabajo que todo lo que la rodea se pierde en el ruido: unos milisegundos de tokenización son invisibles junto a segundos de generación. Con un modelo de incrustación pequeño la situación se invierte: el modelo termina en microsegundos y cada retraso a su alrededor va directo a la factura.
A eso se suma la escala. En un producto de búsqueda la incrustación se ejecuta dos veces: una sobre miles de millones de documentos al construir el índice y otra en cada consulta. En la primera, un solo punto porcentual de eficiencia se traduce directamente en la factura de hardware; en la segunda, un solo milisegundo se añade directamente a lo que el usuario espera. El mismo modelo tiene dos economías y ambas quieren optimizarse en sentidos opuestos.
¿Contra qué se midió?
La parte que más fácilmente se salta de un texto de ingeniería es cómo se montó la comparación, y es justo lo que determina cuánto significa el resultado.
Perplexity compara contra vLLM 0.22.0 en BF16, con pesos de modelo reales y entradas reales derivadas de evaluaciones. Las ejecuciones de calentamiento verifican que la similitud del coseno no se desvía más de un 0,1 por ciento, es decir, que la ganancia de velocidad no se compró estropeando la salida. Se miden cuatro conjuntos: incrustaciones de baja latencia (lote 1; 128, 512 y 4.096 tokens), puntuación de baja latencia (lotes de 5, 25 y 50 con 512 tokens), incrustaciones de alto rendimiento (lote 100 en cuatro procesos concurrentes) y alta concurrencia (de 1 a 16 peticiones simultáneas, incluida la tokenización de Ivy y la sobrecarga de red).
Ese último paréntesis importa: incluir la red y la tokenización convierte la cifra en un número de producción y no de laboratorio. Una comparación que midiera solo el tiempo de GPU habría ocultado justo los problemas que este texto describe.
Qué llevarse de aquí
Lecciones transferibles para quien construya su propia pila de recuperación:
- Mide primero dónde se va el tiempo. En modelos pequeños el cuello de botella no suele ser el modelo sino el lanzamiento y la preparación de datos a su alrededor.
- Pregunta por la forma antes de construir un motor aparte. Si la incrustación por lotes se parece al prefill y una consulta suelta al decode, tus kernels actuales ya encajan.
- Agrupa por número de tokens, no de secuencias. Pasado el punto de saturación, añadir secuencias no aporta nada.
- No subestimes el lado de la CPU. La tokenización y el análisis pueden dejar la GPU esperando; ese trabajo puede pasar a un lenguaje de sistemas.
- Compara con tus propios datos. Perplexity verifica sus comparaciones con pesos reales y entradas derivadas de evaluaciones, manteniendo la desviación del coseno dentro del 0,1 por ciento.
En resumen
El resumen del relato de Perplexity es este: en el lado de la GPU, la inferencia de incrustaciones ha convergido en gran medida entre motores sobre hardware maduro; la distancia entre motores se ha cerrado. Las ganancias están ahora en el arnés que rodea al modelo: gestión de grafos CUDA, seguimiento asíncrono de resultados y una ruta de peticiones rápida.
Es una observación generalizable para el sector. Elegir el modelo es la parte fácil; lo que mantiene un producto en pie es recoger los milisegundos desperdiciados al ejecutar ese modelo millones de veces al día.
Una advertencia para cerrar: las cifras y decisiones de aquí salen de las mediciones de Perplexity, calibradas a su propia carga de trabajo. Si tus longitudes de secuencia, el tamaño del modelo y el hardware son distintos, también lo serán el punto de saturación y el planificador correcto. Lo transferible no son las cifras sino las preguntas.