- Este texto explica, com foco em casos práticos, uma tentativa e um projeto para compilar antecipadamente (AOT) código Python puro e transformá-lo em executáveis multiplataforma
- A ideia central é não criar um novo JIT nem reescrever tudo em C++, mas gerar kernels otimizados por meio de um pipeline de tracing simbólico → IR → geração de código C++ → compilação para múltiplos alvos
- Usa anotações de tipo do PEP 484 para iniciar a propagação de tipos e geração de código com IA para implementar automaticamente centenas de operadores em C++, cobrindo chamadas amplas de bibliotecas como Numpy, OpenCV e PyTorch
- Para a mesma função Python, adota uma estratégia de otimização de desempenho empírica, com geração e distribuição em massa de vários caminhos de implementação e escolha da variante mais rápida com base em telemetria medida em uso real
- O objetivo é fornecer binários pequenos, rápidos e portáveis, sem dependência de contêineres, como unidade de distribuição para rodar em qualquer lugar, de servidores e desktops até mobile e web
Foreword
- A simplicidade e a produtividade do Python são vantagens, mas há limitações de desempenho e portabilidade em workloads de alta carga
- Este texto do autor convidado Yusuf Olokoba apresenta um projeto de compilador para gerar executáveis rápidos e portáteis mantendo o Python original
- É uma abordagem que busca atingir otimização de kernels com um pipeline sem adicionar JIT nem fazer uma reescrita completa em C++
Introduction
- O objetivo é compilar Python sem modificações de forma totalmente AOT, para que rode sem interpretador, com desempenho próximo de C/C++ e execução em todas as plataformas
- Diferentemente de tentativas anteriores (Jython, RustPython, Numba, PyTorch, Mojo etc.), a escolha aqui não é substituir a linguagem ou o runtime, mas sim fazer transformação de código e geração de kernels
- Essas funções Python compiladas já estão em uso em milhares de dispositivos por mês
Containers Are the Wrong Way to Distribute AI
- Em implantações reais, contêineres trazem payload excessivo — interpretador, pacotes e snapshot do sistema operacional — o que causa atraso na inicialização e restrições de portabilidade
- A alternativa é um executável autossuficiente contendo apenas o modelo, oferecendo tamanho menor, inicialização mais rápida e possibilidade de execução em servidor, desktop, mobile e web
- A ideia central é mudar a unidade de distribuição de um snapshot de SO para um binário autoexecutável
Arm64, Apple, and Unity: How It All Began
- Durante a transição da Apple para arm64, o caso do Unity, que convertia CIL em C++ com IL2CPP para permitir compilação para todos os alvos, serviu como benchmark
- A visão foi aplicar a mesma ideia ao Python para garantir um caminho de código que possa rodar em qualquer lugar
Sketching Out a Python Compiler
- O desenho de alto nível é composto pelas etapas entrada em Python → tracing simbólico (IR) → geração em C++ → compilação multi-target
- O motivo para não ir direto do IR ao código-objeto e escolher C++ como artefato intermediário é aproveitar ao máximo caminhos de aceleração como CUDA, MLX, TensorRT, AMX e outros
- O objetivo é garantir um projeto extensível no qual seja fácil encaixar caminhos ótimos específicos para cada hardware
Building a Symbolic Tracer for Python
- No início, o tracing com base em PyTorch FX tinha limitações por exigir execução e por ficar restrito a operações do PyTorch
- Em vez disso, foi construído um tracer simbólico baseado em parsing de AST, que converte fluxo de controle e resolução de chamadas em IR
- Hoje, o tracer oferece recursos como análise estática, avaliação parcial e observação de valores em tempo de execução com sandbox
Lowering to C++ via Type Propagation
- Para fazer a ponte entre a tipagem dinâmica do Python e a tipagem estática do C++, usa-se propagação de tipos
- Quando os tipos dos argumentos de entrada são fornecidos, os tipos das variáveis intermediárias podem ser inferidos de forma determinística com base nas definições dos operadores
- Cada operação em Python é mapeada para sua implementação correspondente em C++, com os tipos sendo propagados por toda a função
Seeding the Type Propagation Process
- Como ponto de partida da propagação de tipos, são usadas anotações de tipo do PEP 484
- Isso entra em conflito com o princípio de não modificar o código original, mas foi considerado um compromisso aceitável em nome de uma interface concisa e compatibilidade
- Também são impostas restrições, como limites no número de tipos na assinatura da função, para garantir uma interface de consumo simples
Building a Library of C++ Operators
- Não é necessário implementar todas as funções diretamente em C++; apenas as operações folha que não podem ser rastreadas exigem implementação manual ou automática
- Como muito código Python é composto por combinações de poucas operações básicas, o conjunto de operadores a cobrir é relativamente pequeno
- Com geração de código baseada em LLM e uma infraestrutura de restrições, testes e compilação condicional, foi automatizada a implementação de centenas de funções de Numpy, OpenCV, PyTorch e outras bibliotecas
Performance Optimization via Exhaustive Search
- Partindo da lição de que a otimização de desempenho é sempre empírica, a estratégia é gerar todas as variantes de implementação possíveis e escolher a melhor por comparação com medições reais
- Exemplo: no Apple Silicon, até mesmo para resize são gerados vários caminhos, como Accelerate, vImage, Core Image e Metal, com múltiplos binários da mesma funcionalidade sendo distribuídos
- Com telemetria detalhada, coleta-se a latência por caminho, e um modelo estatístico prevê e seleciona a variante mais rápida
- Na prática, isso oferece ao usuário uma experiência de execução que fica automaticamente mais rápida com o tempo
Designing a User Interface for the Compiler
- Para manter a experiência do desenvolvedor com curva de aprendizado próxima de zero, foi adotado como interface o decorator PEP 318
@compile
- A CLI usa o decorator como entrypoint para percorrer e compilar o grafo de código dependente
- Os argumentos do decorator incluem tag, description, sandbox e metadata, com suporte a reprodução de ambiente e especificação de backend (ONNXRuntime, TensorRT, CoreML, IREE, QNN etc.)
Closing Thoughts
- Recursos como exceções, lambdas, recursão e classes têm suporte parcial ou inexistente, e especialmente em tipos compostos e tipos de ordem superior ainda é necessário expandir a propagação de tipos
- A experiência de depuração também é um desafio, já que a compilação otimizada reduz as informações simbólicas e torna o rastreamento mais difícil
std::span, concepts e coroutines do C++20 são bases importantes, enquanto std::generator, <stdfloat> e <stacktrace> do C++23 devem contribuir para streaming, half/bfloat16 e rastreamento de exceções
- O objetivo final é estabelecer, sem contêineres, um executável pequeno, rápido e seguro como unidade de distribuição capaz de rodar workloads de IA, como embedding e detecção, em qualquer lugar
1 comentários
Achei que fosse algo como APE, mas não é.