Vor Jahren habe ich eine Laravel-App auf eine Maschine ohne ext-mbstring ausgeliefert, und nichts ging kaputt. Genau das war das Problem. Composer hatte fröhlich symfony/polyfill-mbstring gezogen, jede String-Funktion lief weiter, und unser CSV-Importer war die nächsten acht Monate spürbar langsamer als auf meinem Laptop, weil Userland-PHP Arbeit erledigte, für die eigentlich eine C-Extension da ist. Niemand hat einen Fehler gesehen. Es gab ja auch keinen. Ich erzähle das, weil das PyTorch-Team gerade Version 2.14 veröffentlicht hat, und unter 2.995 Commits von 487 Contributors steckt eine Entscheidung, die ich am liebsten einrahmen und jedem PHP-Maintainer über den Schreibtisch hängen würde.
Darum geht es. PyTorch 2.14 veröffentlicht Wheels für Python 3.15, inklusive des Free-Threaded-Builds, aber torch.compile funktioniert unter 3.15 noch nicht. Statt dich still auf Eager Execution zurückfallen zu lassen, während dein Trainingsschritt doppelt so lange dauert und du rätselst, ob es am Data Loader liegt, wirft es einen Fehler. Du wolltest kompilierten Code, den bekommst du nicht, und hier steht, warum. Das ist das ganze Feature, und ich halte es für das Reifste am ganzen Release.
Noch auffälliger wird das, wenn du den Rest der Release Notes liest, denn viele der großen Performance-Gewinne auf Apple Silicon räumen genau das gegenteilige Muster auf. Lineare Algebra auf dem MPS-Backend ist früher ohne jede Meldung auf die CPU ausgewichen und hat dafür mit Speicherkopien in beide Richtungen bezahlt. In 2.14 laufen SVD, eigh und lstsq für float32 und complex64 nativ auf der GPU, die LU-Zerlegung hat eigene Metal-Kernels mit Beschleunigungen von über 100x bei kleinen gebatchten Matrizen bekommen, und CTC Loss flüchtet nicht mehr auf die CPU. Dazu kam eine Shape beim Single-Token-Decoding, die den schnellen Pfad verfehlt hat und in bf16 und fp16 eine 8,5-fache Verlangsamung gekostet hat. Jeder dieser Fälle ist ein stiller Fallback, den irgendwann jemand mit einem Profiler aufspüren musste. Die Zahlen zeigen dir, wie viel eine höfliche Degradation kosten kann, bevor überhaupt jemand merkt, dass es sie gibt.
Jetzt der faire Einwand, und er ist stark. Fallbacks sind der Grund, warum Software den Kontakt mit echten Maschinen überlebt. Das neue NVGEMM-Matmul-Backend in torch.compile fällt bei jeder Shape, die es nicht beherrscht, auf Triton zurück, und das ist eindeutig richtig: Du hast dich für einen schnelleren Pfad entschieden, und wenn der bei einer schrägen Shape wegfällt, sollte das keinen Run abschießen. Dasselbe gilt für float64 auf Metal, das weiterhin auf der CPU läuft, weil die Hardware keinen 64-Bit-Float-Typ hat. Niemand will ein Modell, das auf einem Mac abstürzt, weil ein Tensor in doppelter Genauigkeit vorliegt. Der Polyfill, der meine fehlende Extension versteckt hat, hat auch eine Produktivseite auf einem Host am Laufen gehalten, über den ich keine Kontrolle hatte. Langsam und funktionierend schlägt schnell und tot, jedenfalls an den meisten Tagen.
Wo verläuft also die Grenze? Ich würde sie bei der Absicht ziehen. Wenn NVGEMM auf Triton zurückfällt, wählt das System zwischen zwei kompilierten Kernels, denen du beiden schon zugestimmt hast, beide innerhalb des Modus, den du angefordert hast. Wäre torch.compile unter 3.15 still zu Eager geworden, hätte das System genau das verworfen, worum du ausdrücklich gebeten hast, und das verdient ein lautes Nein. Im selben Release werden außerdem die TorchScript-Deprecation-Warnungen sichtbar, und das passt ins Bild: Wenn sich der Boden unter dir bewegt, ist es der Job des Tools, das auch zu sagen, selbst wenn das Log eine Weile hässlicher aussieht.
PHP hat beide Arten von Fallback, und wir unterscheiden sie selten. Eine C-Extension durch einen Polyfill zu ersetzen ist in Ordnung, wenn du die Extension nie angefordert hast. Den Opcache-JIT zu laden und ihn dann abgeschaltet zu bekommen, weil irgendeine Extension den Executor überschreibt, ist dagegen ein Fall, in dem du sehr wohl gefragt hast, und PHP 8 gibt da immerhin eine Warnung aus, was ich für den richtigen Instinkt halte. Davon wünsche ich mir mehr im Userland, denn dort gibt es genug Kandidaten: Libraries, die einen langsamen Pfad wählen und darüber schweigen, ORMs, die auf N+1-Queries ausweichen, wenn sich eine Relation nicht per Eager Loading laden lässt, Cache-Treiber, die auf Array-Storage zurückfallen, wenn Redis nicht erreichbar ist, und das nur in einem Debug-Log erwähnen, das niemand mitliest. Jeder davon hält den Request am Leben und hinterlässt dir ein Performance-Rätsel, das du später lösen darfst, unter der Deadline von jemand anderem.
Die günstige Lösung steckt eher in unserem eigenen Code als in php-src. Mit den Platform-Requirements von Composer kannst du ext-mbstring oder ext-intl schon heute als Pflicht deklarieren, und viele Projekte tun das schlicht nicht. Ein Check beim Booten, der eine Exception wirft, wenn der angeforderte Treiber nicht derjenige ist, der tatsächlich den Traffic bedient, sind etwa zehn Zeilen. PyTorch hat einen Release-Zyklus damit verbracht, versteckte Verlangsamungen auf einem Backend rückgängig zu machen, und sich bei einem anderen entschieden, früh zu scheitern. Den Fehler jetzt zu schreiben ist billiger, als später nach der Verlangsamung zu suchen.
Wo ziehst du diese Grenze in deinem eigenen Stack? Gibt es in deiner App gerade einen Fallback, den du lieber in eine Exception verwandeln würdest, oder einen, über dessen Schweigen du froh bist, weil er dich in einem ungünstigen Moment gerettet hat? Ich würde gern von beiden hören, besonders von allen, die PHP auf Hosting betreiben, das sie nicht vollständig kontrollieren.




Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.