13 Punkte von GN⁺ 2025-09-30 | 1 Kommentare | Auf WhatsApp teilen
  • Dieser Beitrag erläutert anhand konkreter Beispiele den Versuch und das Design, reinen Python-Code Ahead-of-Time (AOT) zu kompilieren und in plattformübergreifende ausführbare Dateien umzuwandeln
  • Die Kernidee besteht darin, keinen neuen JIT zu bauen und nichts in C++ neu zu schreiben, sondern über eine Pipeline aus symbolischem Tracing → IR → C++-Codegenerierung → Kompilierung für mehrere Targets optimierte Kernel zu erzeugen
  • Mit PEP-484-Typannotationen wird die Typweitergabe initialisiert, und durch KI-Codegenerierung werden Hunderte von C++-Operatoren automatisch implementiert, um breite Library-Aufrufe wie Numpy, OpenCV und PyTorch abzudecken
  • Für dieselbe Python-Funktion werden in großem Umfang verschiedene Implementierungspfade erzeugt und verteilt, und per gemessener Telemetrie wird die schnellste Variante gewählt – eine Strategie der empirischen Performance-Optimierung
  • Ziel ist es, kleine, schnelle und portable Binärdateien bereitzustellen, die nicht von Containern abhängen und als Deployment-Einheit für Server, Desktop, Mobile und Web dienen, also überall ausführbar sind

Foreword

  • Die Einfachheit und Produktivität von Python sind Stärken, doch bei hochlastigen Workloads gibt es Grenzen bei Performance und Portabilität
  • Dieser Beitrag des Gastautors Yusuf Olokoba stellt ein Compiler-Design vor, das schnelle und portable Executables erzeugt, während der ursprüngliche Python-Code erhalten bleibt
  • Der Ansatz zielt darauf ab, Kernel-Optimierung durch eine Pipeline zu erreichen – ohne zusätzlichen JIT oder vollständiges Umschreiben in C++

Introduction

  • Ziel ist es, unverändertes Python vollständig AOT zu kompilieren, sodass es ohne Interpreter läuft, nahe an C/C++-Geschwindigkeit erreicht und auf allen Plattformen ausführbar ist
  • Anders als frühere Ansätze wie Jython, RustPython, Numba, PyTorch oder Mojo setzt dieser Ansatz nicht auf den Austausch von Sprache oder Runtime, sondern auf Code-Transformation und Kernel-Erzeugung
  • Diese kompilierten Python-Funktionen werden bereits auf Tausenden von Geräten pro Monat eingesetzt

Containers Are the Wrong Way to Distribute AI

  • In realen Deployments bringen Container als überladene Payload Interpreter, Pakete und OS-Snapshots mit, was zu Startverzögerungen und Einschränkungen bei der Portabilität führt
  • Die Alternative sind selbstständige ausführbare Dateien, die nur das Modell enthalten, mit kleinerer Größe, schnellerem Start und der Möglichkeit, auf Servern, Desktops, Mobilgeräten und im Web zu laufen
  • Der Kernpunkt ist die Strategie, die Deployment-Einheit von einem OS-Snapshot zu einer eigenständig ausführbaren Binärdatei zu machen

Arm64, Apple, and Unity: How It All Began

  • Beim Übergang Apples zu arm64 diente Unitys Ansatz als Vorbild, bei dem IL2CPP CIL in C++ umwandelt, sodass es für alle Targets kompiliert werden kann
  • Dieselbe Idee soll auf Python angewendet werden, um Codepfade zu schaffen, die überall laufen können

Sketching Out a Python Compiler

  • Das grobe Design besteht aus den Schritten Python-Eingabe → symbolischer Trace (IR) → C++-Generierung → Kompilierung für mehrere Targets
  • Dass von der IR nicht direkt zu Objektcode gegangen wird, sondern C++ als Zwischenartefakt gewählt wird, hat den Grund, Beschleunigungspfade wie CUDA, MLX, TensorRT und AMX möglichst gut zu nutzen
  • Ziel ist ein erweiterbares Design, in das sich hardwareabhängig optimierte Pfade leicht einfügen lassen

Building a Symbolic Tracer for Python

  • Das anfängliche Tracing auf Basis von PyTorch FX stieß an Grenzen, weil es Ausführung erfordert und auf PyTorch-Operationen beschränkt ist
  • Stattdessen wurde ein symbolischer Tracer auf AST-Parsing-Basis gebaut, der Kontrollfluss und Aufrufauflösung in IR überführt
  • Der aktuelle Tracer bietet Funktionen wie statische Analyse, partielle Auswertung und Sandbox-basierte Beobachtung von Live-Werten

Lowering to C++ via Type Propagation

  • Um die Brücke von Pythons dynamischer Typisierung zu C++'s statischer Typisierung zu schlagen, wird Typweitergabe verwendet
  • Sind die Typen der Eingabeargumente gegeben, lassen sich die Typen von Zwischenvariablen anhand der Operatordefinitionen deterministisch ableiten
  • Jede Python-Operation wird auf eine entsprechende C++-Implementierung abgebildet, während sich die Typen über die gesamte Funktion hinweg fortpflanzen

Seeding the Type Propagation Process

  • Als Startpunkt für die Typweitergabe dienen PEP-484-Typannotationen
  • Das steht zwar im Spannungsverhältnis zum Prinzip, den Originalcode nicht zu verändern, wird aber als vertretbarer Kompromiss für eine knappe Schnittstelle und Kompatibilität bewertet
  • Zusätzlich werden Beschränkungen wie eine begrenzte Anzahl von Typen in Funktionssignaturen eingeführt, um eine einfache Consumer-Schnittstelle sicherzustellen

Building a Library of C++ Operators

  • Es müssen nicht alle Funktionen direkt in C++ implementiert werden; nur nicht nachverfolgbare Leaf-Operationen erfordern manuelle oder automatische Implementierung
  • Da viele Python-Programme aus der Kombination einiger weniger Grundoperationen bestehen, ist die abzudeckende Menge an Operatoren relativ klein
  • Mit LLM-basierter Codegenerierung sowie Infrastruktur für Restriktionen, Tests und bedingte Kompilierung wird die Implementierung von Hunderten Funktionen aus Numpy, OpenCV und PyTorch automatisiert

Performance Optimization via Exhaustive Search

  • Aus der Erkenntnis heraus, dass Performance-Optimierung immer empirisch ist, werden sämtliche Implementierungsvarianten erzeugt und per Messvergleich die beste ausgewählt
  • Beispiel: Allein für Resize auf Apple Silicon werden mehrere Pfade wie Accelerate, vImage, Core Image und Metal generiert, und mehrere Binärdateien mit identischer Funktionalität werden verteilt
  • Mit feingranularer Telemetrie werden Latenzen je Pfad gesammelt, und per statistischem Modell wird die schnellste Variante vorhergesagt und ausgewählt
  • Der Effekt für Nutzer ist eine Laufzeiterfahrung, die mit der Zeit automatisch schneller wird

Designing a User Interface for the Compiler

  • Um die Developer Experience auf eine nahezu null steile Lernkurve zu bringen, wird der PEP-318-Dekorator @compile als Interface verwendet
  • Die CLI nutzt den Dekorator als Entrypoint, durchsucht den abhängigen Codegraphen und kompiliert ihn
  • Als Dekoratorargumente werden tag, description, sandbox und metadata angenommen, um Umgebungsreproduktion und Backend-Auswahl zu unterstützen, etwa ONNXRuntime, TensorRT, CoreML, IREE und QNN

Closing Thoughts

  • Ausnahmen, Lambdas, Rekursion und Klassen werden nur teilweise oder gar nicht unterstützt; insbesondere bei komplexen Typen und Higher-Order-Typen muss die Typweitergabe erweitert werden
  • Die Debugging-Erfahrung bleibt eine Herausforderung, da optimierende Kompilierung Symbolinformationen reduziert und damit das Tracing erschwert
  • C++20 mit std::span, concepts und coroutines bildet die zentrale Grundlage, während C++23 mit std::generator, <stdfloat> und <stacktrace> künftig zu Streaming, half/bfloat16 und Exception-Tracing beitragen soll
  • Das Endziel ist, kleine, schnelle und sichere ausführbare Dateien ohne Container als Deployment-Einheit zu etablieren, mit denen sich KI-Workloads wie Embedding und Objekterkennung überall ausführen lassen

1 Kommentare

 
secret3056 2025-09-30

Ich dachte, es wäre so etwas wie APE, aber das ist es wohl nicht.