`torch.export`: La Nueva API que Revoluciona el Despliegue de Modelos PyTorch

Towardsdatascience

Al embarcarse en un nuevo proyecto de inteligencia artificial o aprendizaje automático, gran parte del enfoque gravita naturalmente hacia tareas monumentales: curar vastos conjuntos de datos, arquitectar modelos sofisticados y asegurar potentes clústeres de GPU para el entrenamiento. Sin embargo, a menudo son los detalles aparentemente menores los que se convierten en obstáculos inesperados, lo que lleva a errores frustrantes y retrasos significativos en la producción. Un ejemplo claro es el traspaso de un modelo entrenado desde el entorno de desarrollo a su contraparte de inferencia. Si bien este paso puede parecer sencillo, la realidad de las diferentes bibliotecas de tiempo de ejecución, configuraciones de hardware y versiones puede convertirlo en un considerable dolor de cabeza. Los desarrolladores deben asegurarse de que la definición del modelo y sus pesos entrenados se carguen correctamente y, lo que es crucial, que su comportamiento permanezca inalterado.

Tradicionalmente, se han empleado dos métodos principales para esta captura y despliegue crítico de modelos. El primero, y más simple, implica guardar solo los pesos del modelo utilizando torch.save. Este enfoque ofrece la máxima flexibilidad, permitiendo optimizaciones específicas de la máquina en el entorno de inferencia. Sin embargo, requiere redefinir explícitamente la arquitectura del modelo en el entorno de despliegue, lo que puede introducir pesadillas de versionado y desajustes de dependencia, especialmente en entornos restringidos donde el control sobre las bibliotecas de tiempo de ejecución es limitado. La separación de la definición y los pesos a menudo se convierte en un terreno fértil para “errores feos”, lo que exige una gestión rigurosa de las versiones.

Durante años, la solución más completa fue TorchScript, que agrupa tanto la definición del modelo como los pesos en una representación de grafo serializable. TorchScript ofrecía dos funcionalidades distintas: torch.jit.script y torch.jit.trace. Scripting realiza un análisis estático del código fuente, capaz de capturar elementos complejos como el flujo de control condicional y las formas de entrada dinámicas. El trazado, por el contrario, registra la ruta de ejecución real de un modelo sobre una entrada de muestra, lo que lo hace menos propenso a ciertas fallas pero incapaz de manejar el comportamiento dinámico. A menudo, se requería una combinación de ambos, pero incluso entonces, TorchScript con frecuencia tenía dificultades con modelos complejos, exigiendo reescrituras de código minuciosas e intrusivas para asegurar la compatibilidad. Nuestros propios experimentos con un modelo generativo de imagen a texto de HuggingFace demostraron esta limitación: mientras que el codificador de entrada fija podía ser trazado, el decodificador de forma dinámica fallaba consistentemente al ser scriptado sin modificaciones significativas al código de la biblioteca subyacente.

Aquí entra torch.export, la nueva solución más robusta de PyTorch para la captura de modelos. Similar a torch.jit.trace, torch.export opera trazando la ejecución del modelo. Sin embargo, mejora significativamente a su predecesor al incorporar soporte para el dinamismo y el flujo de control condicional, superando muchas de las limitaciones históricas de TorchScript. La salida es una representación de grafo intermedia, conocida como Export IR, que puede cargarse y ejecutarse como un programa PyTorch independiente con dependencias mínimas. Una ventaja clave de torch.export es su compatibilidad con torch.compile, lo que permite optimizaciones adicionales sobre la marcha en el entorno de inferencia, una capacidad no extendida a los modelos TorchScript. Esta característica se basa en Torch Dynamo, un componente central de la solución de compilación de grafos de PyTorch.

A pesar de sus potentes capacidades, torch.export sigue siendo una característica prototipo y presenta su propio conjunto de desafíos. Un obstáculo común es la “ruptura de grafo” (graph break), que ocurre cuando la función de exportación encuentra código Python no rastreable. A diferencia de la compilación de modelos, donde PyTorch podría recurrir a la ejecución ansiosa (eager execution), torch.export prohíbe estrictamente las rupturas de grafo, lo que requiere que los desarrolladores reescriban su código para evitarlas. La depuración de grafos exportados también puede ser complicada; si bien se comportan como objetos estándar torch.nn.Module, los depuradores tradicionales no pueden entrar en su función forward compilada. Los problemas a menudo surgen cuando las variables del entorno de exportación se “integran” inadvertidamente en el grafo como constantes, lo que lleva a errores de tiempo de ejecución en diferentes entornos. Por ejemplo, nuestro decodificador exportado falló inicialmente en una GPU debido a referencias de dispositivo de CPU codificadas del entorno de exportación, lo que requirió un “monkey-patching” manual de la biblioteca HuggingFace para resolverlo. Si bien fue efectivo para nuestro modelo de juguete, tales modificaciones intrusivas no son aconsejables para sistemas de producción sin pruebas exhaustivas.

Cuando se probó en nuestro modelo de ejemplo, torch.export capturó con éxito tanto el codificador como el decodificador sin encontrar rupturas de grafo, una mejora significativa sobre TorchScript. La implementación del modelo exportado corregido en una instancia de Amazon EC2 mostró una modesta aceleración del 10.7% en el tiempo de inferencia en comparación con el modelo original. Curiosamente, la aplicación de torch.compile al modelo exportado, aunque prometedora, aumentó inesperadamente el tiempo de ejecución en este escenario específico, lo que subraya la necesidad de una cuidadosa afinación de los parámetros de compilación.

En resumen, torch.export representa un avance convincente en el despliegue de modelos PyTorch. Demuestra un soporte superior para modelos complejos, permitiendo la captura de arquitecturas que anteriormente desconcertaban a TorchScript. Los modelos exportados resultantes son altamente portátiles, capaces de ejecución independiente sin dependencias de paquetes extensas, y son compatibles con potentes optimizaciones específicas de la máquina a través de torch.compile. Sin embargo, como prototipo en rápida evolución, actualmente presenta limitaciones, incluida la posibilidad de que valores específicos del entorno no deseados se integren en el grafo y un conjunto incipiente de herramientas de depuración. A pesar de estas asperezas, torch.export es una mejora sustancial con respecto a las soluciones anteriores, lo que promete enormemente para agilizar la última milla crítica del desarrollo de modelos de IA.