- Este artículo explica, con un enfoque basado en casos, un intento y diseño para compilar por adelantado (AOT) código Python puro y convertirlo en ejecutables multiplataforma
- La idea central es no crear un nuevo JIT ni reescribir todo en C++, sino producir kernels optimizados mediante una tubería de trazado simbólico → IR → generación de código C++ → compilación para múltiples objetivos
- Usa anotaciones de tipos PEP 484 para iniciar la propagación de tipos, y generación de código con IA para implementar automáticamente cientos de operadores en C++, cubriendo una amplia variedad de llamadas de librerías como Numpy, OpenCV y PyTorch
- Adopta una estrategia de optimización empírica del rendimiento que genera y despliega en masa distintas rutas de implementación para una misma función de Python, y elige la variante más rápida con telemetría medida en producción
- El objetivo es ofrecer binarios portables, pequeños y rápidos que no dependan de contenedores, para usarlos como unidad de despliegue que pueda ejecutarse en cualquier lugar, desde servidor y escritorio hasta móvil y web
Foreword
- La simplicidad y productividad de Python son ventajas, pero existen límites de rendimiento y portabilidad en cargas de trabajo exigentes
- Este texto del autor invitado Yusuf Olokoba presenta el diseño de un compilador para crear ejecutables rápidos y portables manteniendo el Python original
- Es un enfoque que busca lograr optimización de kernels construyendo una tubería sin agregar un JIT ni hacer una reescritura total a C++
Introduction
- La meta es compilar Python sin modificaciones de forma totalmente AOT para que funcione sin intérprete, sea casi tan rápido como C/C++ y pueda ejecutarse en cualquier plataforma
- A diferencia de intentos previos como Jython, RustPython, Numba, PyTorch o Mojo, elige transformación de código y generación de kernels en vez de reemplazar el lenguaje o el runtime
- Estas funciones de Python compiladas ya se usan en miles de dispositivos cada mes
Containers Are the Wrong Way to Distribute AI
- En despliegues reales, los contenedores llevan una carga excesiva —intérprete, paquetes e instantánea del SO—, lo que provoca demoras de arranque y restricciones de portabilidad
- La alternativa son ejecutables autocontenidos que incluyen solo el modelo, con menor tamaño, arranque más rápido y capacidad de ejecución en servidor, escritorio, móvil y web
- La idea de fondo es cambiar la unidad de despliegue: dejar de usar una instantánea del SO y pasar a un binario autoejecutable
Arm64, Apple, and Unity: How It All Began
- Durante la transición de Apple a arm64, se tomó como referencia el caso de Unity, que con IL2CPP convertía CIL a C++ para poder compilar a todos los objetivos
- La visión fue aplicar la misma idea a Python para asegurar rutas de código que puedan correr en cualquier lugar
Sketching Out a Python Compiler
- El diseño general se compone de las etapas entrada en Python → traza simbólica (IR) → generación de C++ → compilación multiobjetivo
- La razón para elegir C++ como producto intermedio en vez de ir directo a código objeto desde el IR es aprovechar al máximo rutas de aceleración como CUDA, MLX, TensorRT y AMX
- El objetivo es obtener un diseño extensible en el que sea fácil insertar la ruta óptima para cada hardware
Building a Symbolic Tracer for Python
- El trazado inicial basado en PyTorch FX tenía limitaciones por la necesidad de ejecución y por estar restringido a operaciones de PyTorch
- En su lugar, se construyó un trazador simbólico basado en análisis de AST que convierte flujo de control y resolución de llamadas a IR
- El trazador actual ofrece funciones como análisis estático, evaluación parcial y observación de valores en vivo basada en sandbox
Lowering to C++ via Type Propagation
- Para tender un puente entre el tipado dinámico de Python y el tipado estático de C++, se usa propagación de tipos
- Si se conocen los tipos de los argumentos de entrada, los tipos de las variables intermedias pueden inferirse de forma determinista según la definición de los operadores
- Cada operación de Python se mapea a su implementación equivalente en C++, y los tipos se propagan a lo largo de toda la función
Seeding the Type Propagation Process
- Para iniciar la propagación de tipos, se usan anotaciones de tipos PEP 484
- Aunque esto entra en tensión con el principio de no modificar el código original, se considera una concesión aceptable por su interfaz concisa y compatibilidad
- También se imponen restricciones, como limitar la cantidad de tipos en la firma de una función, para garantizar una interfaz de consumo simple
Building a Library of C++ Operators
- No hace falta implementar todas las funciones directamente en C++; solo las operaciones hoja que no pueden trazarse requieren implementación manual o automática
- Como gran parte del código Python se compone de una pequeña combinación de operaciones básicas, el conjunto de operadores a cubrir es relativamente pequeño
- Con generación de código basada en LLM e infraestructura de restricciones, pruebas y compilación condicional, se automatiza la implementación de cientos de funciones de Numpy, OpenCV y PyTorch, entre otras
Performance Optimization via Exhaustive Search
- Partiendo de la lección de que la optimización del rendimiento es siempre empírica, se generan todas las variantes posibles de implementación y se elige la óptima mediante comparación con mediciones reales
- Por ejemplo, en Apple Silicon, incluso para resize se generan múltiples rutas como Accelerate, vImage, Core Image y Metal, y se despliegan varios binarios con la misma funcionalidad
- Con telemetría detallada se recopila la latencia por ruta, y un modelo estadístico predice y selecciona la variante más rápida
- En la práctica, esto ofrece al usuario una experiencia de ejecución que se vuelve automáticamente más rápida con el tiempo
Designing a User Interface for the Compiler
- Para que la experiencia de desarrollo tenga una curva de aprendizaje casi nula, se adopta el decorador PEP 318
@compile como interfaz
- El CLI usa el decorador como punto de entrada para recorrer y compilar el grafo de código dependiente
- Los argumentos del decorador aceptan tag, description, sandbox y metadata, con soporte para reproducción de entornos y selección de backend como ONNXRuntime, TensorRT, CoreML, IREE y QNN
Closing Thoughts
- Elementos como excepciones, lambdas, recursión y clases tienen soporte parcial o nulo, y en especial se necesita ampliar la propagación de tipos para tipos compuestos y de orden superior
- La experiencia de depuración sigue siendo un reto, ya que la compilación optimizada reduce la información simbólica y vuelve más difícil el rastreo
std::span, concepts y coroutines de C++20 son bases clave, mientras que std::generator, <stdfloat> y <stacktrace> de C++23 contribuirán a streaming, half/bfloat16 y rastreo de excepciones
- La meta final es establecer una unidad de despliegue para cargas de trabajo de IA como embeddings y detección mediante ejecutables pequeños, rápidos y seguros, sin contenedores y capaces de ejecutarse en cualquier lugar
1 comentarios
Pensé que era algo como APE, pero no lo es.