Los modelos de lenguajes grandes (LLM), como Gemma, son poderosos y versátiles. Pueden traducir idiomas, escribir diferentes tipos de contenido de texto y responder preguntas de manera informativa. Sin embargo, implementarlos en producción, especialmente para casos prácticos de transmisión, puede ser un gran desafío.
En esta entrada de blog, se explorará cómo usar dos herramientas de vanguardia, vLLM y Dataflow, para implementar LLM de manera eficiente a escala con un código mínimo. En primer lugar, presentaremos cómo vLLM usa el procesamiento por lotes continuo para servir a los LLM de manera más eficiente. En segundo lugar, describiremos cómo el administrador de modelos de Dataflow simplifica la implementación de vLLM y otros frameworks de modelos grandes.
vLLM es una biblioteca de código abierto diseñada específicamente para inferencia de LLM de gran capacidad de procesamiento y baja latencia. Optimiza el funcionamiento de LLM usando varias técnicas especializadas, incluido el procesamiento por lotes continuo.
Para comprender cómo funciona el procesamiento por lotes continuo, primero veamos cómo los modelos agrupan las entradas de manera tradicional. Las GPU se destacan en el procesamiento paralelo, donde se realizan múltiples cálculos simultáneamente. El procesamiento por lotes le permite a la GPU utilizar todos sus núcleos disponibles para trabajar en un lote completo de datos a la vez, en lugar de procesar cada entrada individualmente. Esto acelera significativamente el proceso de inferencia; a menudo, realizar la inferencia en 8 registros de entrada a la vez consume los mismos recursos que realizarla en uno solo.
El procesamiento por lotes se puede comparar con la cocina de un restaurante: en lugar de preparar cada plato individualmente, el chef puede agrupar pedidos similares y cocinarlos juntos, lo que ahorra tiempo y recursos. Si tiene 8 estufas, se necesita una cantidad similar de tiempo y esfuerzo para hacer un omelet individual u 8.
Sin embargo, hay algunas desventajas en comparación con el procesamiento por lotes tradicional. Lo más importante en este contexto es que el procesamiento por lotes no funciona tan bien cuando no lleva el mismo tiempo realizar la inferencia. Dado que la mayoría de los frameworks no tienen acceso a los mecanismos subyacentes para realizar inferencias ni conocimiento sobre ellos, generalmente, solo esperan a que se completen todas las solicitudes antes de aceptar un nuevo lote. Esto significa que un solo registro lento puede consumir toda la capacidad de una GPU incluso si se han completado los otros registros en el lote, lo que genera trabajos más lentos y costosos.
Cuando se ejecuta la inferencia con un modelo de lenguaje grande (LLM), esperar a que se complete un lote entero puede ser prohibitivamente lento y costoso. Esto se debe a que el tiempo que lleva generar una inferencia para un registro tiene una correlación 1:1 con la longitud del registro. Por ejemplo, si agrupamos las siguientes 2 solicitudes a un LLM:
2. ¿Cuáles son algunas de las diferencias y similitudes culturales entre México y Estados Unidos?
Esperaríamos una respuesta corta a la pregunta 1 y una respuesta larga a la pregunta 2. Sin embargo, debido a que se necesita mucho más tiempo para responder a la pregunta 2, tenemos que esperar a que esa pregunta se complete, lo cual monopoliza nuestra GPU antes de poder devolver cualquiera de los resultados del lote.
El procesamiento por lotes continuo permite que vLLM actualice los lotes mientras la solicitud aún se está ejecutando. Lo hace aprovechando la manera en que los LLM realizan la inferencia: un proceso en el que se genera repetidamente el siguiente token de su respuesta en un bucle. Entonces, al generar la oración “La capital de México es la Ciudad de México”, estamos ejecutando la inferencia 7 veces (una vez por palabra del resultado). En lugar de agrupar las entradas una vez, la técnica de procesamiento por lotes continuo de vLLM le permite volver a procesar un lote cada vez que se ejecuta el LLM, lo que genera un conjunto de tokens para un lote. Puede agregar solicitudes al lote sobre la marcha y devolver resultados tempranos cuando un registro de un lote está completamente hecho.
Se demostró que el procesamiento dinámico por lotes de vLLM y otras optimizaciones mejoran el rendimiento de inferencia de 2 a 4 veces para los LLM populares en algunos casos, lo que lo convierte en una herramienta muy útil para el servicio de modelos. Para obtener más información sobre vLLM, consulta este informe.
La implementación de una instancia de vLLM dentro de una canalización de transmisión puede ser compleja. Tradicionalmente, deberías hacer lo siguiente:
Esto implica una gran cantidad de multiprocesamiento, que puede llevar mucho tiempo, ser propenso a errores y requerir experiencia especializada. También exige una comprensión profunda de la topología subyacente. Generalmente, puedes cambiar esta topología si también deseas probar diferentes configuraciones de máquinas (por ejemplo, para comparar el rendimiento en una máquina de 8 núcleos y una de 16 núcleos).
Afortunadamente, Dataflow simplifica este proceso con su administrador de modelos. Esta función elimina las complejidades de administrar e implementar modelos dentro de una canalización. De forma predeterminada, Dataflow aprovisiona un proceso de worker por núcleo disponible en sus máquinas worker. Estos procesos son responsables del manejo de E/S dentro y fuera del worker, así como de cualquier transformación que se realice en los datos, y operan de forma totalmente independiente. Para la mayoría de las canalizaciones, incluidas las de preparación de datos para casos prácticos de ML, esta es la topología óptima.
Sin embargo, esto no funciona para las canalizaciones que necesitan servir a modelos grandes, como alguno de los de Gemma. No es rentable ni eficiente cargar una copia de modelos grandes en cada proceso porque lo más probable es que las canalizaciones se queden sin memoria. La topología ideal para la mayoría de estas canalizaciones es cargar solo una copia del modelo grande.
El administrador de modelos de Dataflow se creó para permitir a los usuarios controlar el número exacto de copias de un modelo que se implementan en su canalización, independientemente de la topología de la red. Si aplicas la transformación RunInference, Dataflow puede comprender tu intent para crear la topología ideal para tu canalización e implementar el número óptimo de modelos. Lo único que debes hacer es proporcionar algunos parámetros de configuración.
Al usar vLLM, en lugar de cargar un modelo, el administrador de modelos de Dataflow crea una sola instancia de vLLM en un proceso de inferencia dedicado. Los procesos de workers pueden enviar registros de manera eficiente a esta instancia para inferir.
Este administrador de modelos permite a Dataflow aprovechar al máximo el procesamiento por lotes continuo de vLLM; cuando un worker recibe una solicitud entrante, la agrega de forma asíncrona a la cola de solicitudes de vLLM y espera la respuesta, lo que permite a vLLM agrupar dinámicamente tantas solicitudes como pueda.
El administrador de modelos de Dataflow y la transformación RunInference hacen que sea increíblemente fácil incorporar vLLM en tu canalización. Solo debes especificar algunos detalles de configuración y algunas líneas de código. Debido a que Dataflow es capaz de comprender tu intent subyacente, configura todo el resto de la topología de la canalización por ti. En 5 líneas de código, puedes escribir una canalización completa de extremo a extremo para leer tus datos, ejecutarla a través de vLLM y generar un resultado para un receptor.
model_handler = VLLMCompletionsModelHandler('google/gemma-2b')
with beam.Pipeline() as p:
_ = (p | beam.ReadFromSource(<config>)
| RunInference(model_handler) # Envía las indicaciones a vLLM y recibe las respuestas.
| beam.WriteToSink(<config>))
Puedes encontrar la canalización completa y ejecutarla aquí: https://cloud.google.com/dataflow/docs/notebooks/run_inference_vllm
vLLM aumenta significativamente el rendimiento de la inferencia de LLM en las canalizaciones de Dataflow. Para comparar el rendimiento de vLLM con una canalización nueva utilizando lotes de tamaño fijo, ejecutamos 2 canalizaciones con un solo worker con GPU T4. Cada canalización leyó las indicaciones del conjunto de datos P3, las comparó con el modelo google/gemma-2b y registró el resultado.
Para una estrategia de procesamiento por lotes nueva (predeterminada), se necesitaron 59.137 horas de vCPU para procesar 10,000 indicaciones. Para vLLM con procesamiento por lotes continuo, solo se necesitaron 2.481 horas de vCPU para procesar las mismas 10,000 solicitudes. Eso implica un procesamiento 23 veces mejor.
Sin embargo, debemos hacer algunas advertencias: específicamente, no se realizó ningún ajuste en ninguna de las canalizaciones, y la canalización nueva probablemente habría funcionado mucho mejor si se hubiera ajustado para usar lotes más grandes o más uniformes. Dicho esto, eso es parte de la magia de vLLM: con menos de 20 líneas de código y sin trabajo de ajuste, podemos producir una canalización de servicio de LLM de alto rendimiento. Si quisiéramos comparar otro modelo, podríamos hacerlo de manera eficiente cambiando una sola cadena en nuestro controlador de modelos.
Al combinar la potencia de vLLM y Dataflow, puedes implementar y escalar LLM de manera fácil y eficiente para tus apps de streaming. Para obtener más información sobre cómo hacerlo, consulta este notebook de ejemplo: https://cloud.google.com/dataflow/docs/notebooks/run_inference_vllm.
Para obtener más información sobre los modelos de Gemma y algunas de las cosas que puedes hacer con ellos, consulta los documentos de Gemma: https://ai.google.dev/gemma/docs.
Para obtener más información sobre vLLM y algunos de los otros mecanismos que usa para optimizar el servicio, visita los documentos de vLLM: https://docs.vllm.ai/en/latest/.