Dokumentation

Laya MLX Performance-Forschung

Forschungsdatum: 2026-09-19. Ziel: Apple M3 Max, 40 GPU-Kerne, 128 GiB Unified Memory, MLX/MLX Metal 0.32.2. Dies ist eine statische Prüfung der nativen Runtime, der installierten MLX-Implementierung, der offiziellen Dokumentation und des bestehenden Benchmark-JSON. Für diese Forschung wurde kein GPU-Benchmark und keine Modellinferenz ausgeführt. Keine der unten vorgeschlagenen Optimierungen hat in diesem Bericht eine gemessene Beschleunigung.

Die ersten Experimente sollten die Whole-Model-Kompilierung und repräsentatives Batch-Scheduling sein, gefolgt von selektiver quantisierter Matrixmultiplikation. Diese adressieren die dominante wiederholte Arbeit. Ein spezialisierter Local-Attention-Kernel ist ein glaubwürdiges längerfristiges Projekt für lange Eingaben. Exaktes Pruning der letzten Decision-Head-Schicht ist machbar, aber seine Whole-Model-Arithmetikeinsparung beträgt nur wenige Prozent. Große Verbesserungen ohne Änderung des Checkpoints erfordern die Verbesserung des dichten Backbones, die Eliminierung wirklich redundanter Anfragen oder das Finden eines gemessenen Implementierungsengpasses; eine Aktivierung zu ersetzen oder ein weiteres Attention-Flag zu aktivieren wird wahrscheinlich nicht ausreichen.

Was die bestehenden Messungen belegen

Das Folgende sind bestehende Ende-zu-Ende-Medianlatenzen, einschließlich Prompt-Vorbereitung und Ergebnisformatierung, mit synchronisiertem GPU-Abschluss, fünf Warmups und 50 gemessenen Iterationen. Modellladen und Downloads sind ausgeschlossen. Der Benchmark erlaubt einen Batch von 64 Fragen; die Standardeinstellung der öffentlichen Runtime ist 16, sodass das 50-Fragen-Ergebnis nicht der Standard-API-Konfiguration entspricht.

Checkpoint / Präzision Kurz 1 Frage Kurz 10 Fragen Kurz 50 Fragen Lang 1 Frage Lang 10 Fragen
Laya MLX FP16 13.421 ms 71.068 ms 336.030 ms 44.927 ms 420.987 ms
Laya MLX FP32 15.954 ms 98.820 ms 450.712 ms 61.331 ms 534.242 ms
Laya Stock-Torch MPS FP32 24.918 ms 95.265 ms 497.856 ms 65.581 ms 586.594 ms
Mehrsprachig MLX FP16 7.390 ms 27.386 ms 127.565 ms 37.635 ms 389.487 ms
Mehrsprachig MLX FP32 7.988 ms 32.337 ms 151.387 ms 47.208 ms 451.331 ms
Mehrsprachig Stock-Torch MPS FP32 19.349 ms 43.158 ms 194.171 ms 52.939 ms 534.492 ms

Quellen: Laya FP16, Laya FP32, Laya MPS, Mehrsprachig FP16, Mehrsprachig FP32 und Mehrsprachig MPS. Lange Eingaben enthalten 512 Tokens für Laya und 1024 für Mehrsprachig; der Vergleich ihrer Lang-Eingabe-Latenzen ist daher kein Vergleich bei gleicher Sequenzlänge. Kurze gepaddete Längen sind 93 bzw. 91. Torch-Vergleiche müssen das FP32-Label behalten: Sie kombinieren eine Backend-Änderung mit einer Präzisionsänderung, wenn sie gegen MLX FP16 verglichen werden.

Es gibt erhebliche Laufvariabilität. Zum Beispiel hat der mehrsprachige FP16-Lang-10-Fragen-Lauf p50 389.487 ms, p95 462.319 ms und Maximum 619.663 ms. Sein kurzer Ein-Frage-Forward-Median ist 8.023 ms, während der unabhängig gemessene Ende-zu-Ende-Median 7.390 ms beträgt. Diese Medianwerte zu subtrahieren würde eine unsinnige negative Vorbereitungszeit ergeben. Die aktuellen Dateien isolieren nicht Tokenizer, Python-Dispatch, einzelne GPU-Kernels oder Synchronisierungskosten. Sie etablieren nützliche Baselines, keine Kernel-Level-Engpassdiagnose.

Die Validierungsberichte verzeichnen 63/63 Argmax-Übereinstimmung für jeden von drei Checkpoints in FP32 und FP16 sowie 100 endliche, deterministische Wiederholungsaufrufe pro Variante: 378/378 Antwortübereinstimmungen und insgesamt 600 Wiederholungsaufrufe. Dies sind Regressionstests über einen kleinen Fixture-Korpus, einschließlich wiederholter Fragen. Sie sind kein Beleg dafür, dass zukünftige Quantisierungs- oder Architekturänderungen die allgemeine Task-Genauigkeit bewahren.

Warum dichte Matrixmultiplikation Priorität verdient

Die Modellimplementierung wendet QKV- und Ausgabeprojektionen, eine gated Encoder-MLP und zwei konventionelle Transformer-Decision-Head-Schichten auf jedes gepaddete Token an. Sei D die Hidden-Größe, I die Encoder-Zwischengröße, N die Anzahl der Encoder-Schichten und H die Anzahl der Decision-Head-Schichten. Die Anzahl der Matrixgewichte, die pro Token in diesen Blöcken verwendet werden, ist:

A = N * (4 * D^2 + 3 * D * I) + H * 12 * D^2
dense FLOPs per batch ~= 2 * B * L * A
dense attention FLOPs ~= 4 * B * (N + H) * L^2 * D

Diese Schätzungen zählen Multiplikation und Addition getrennt und schließen Normalisierung, Aktivierungen, Embeddings, Scoring, Maskierung, Speicherbewegung und Kernel-Overhead aus. Sie sind ein arithmetisches Modell, kein Runtime-Profil.

Checkpoint-Familie D / I / Encoder-Schichten Token-Embedding-Gewichte Haupt-Token-Matrixgewichte A Globale / lokale Encoder-Schichten
Laya / typisierte Entscheidungen 1024 / 2624 / 28 51,576,832 368,312,320 10 / 18
Mehrsprachig 768 / 1152 / 22 196,608,000 124,452,864 8 / 14

Die ungefähr 322 Millionen Gesamtparameter des mehrsprachigen Checkpoints umfassen 196.6 Millionen Embedding-Parameter. Für die Inferenz werden nur ausgewählte Embedding-Zeilen gesammelt; dies ist keine Projektion über das gesamte Vokabular. Seine Haupt-Token-Matrixarbeit beträgt etwa ein Drittel der des englischen Modells, obwohl ihre Gesamtparameterzahlen viel näher erscheinen. Dies ist konsistent mit der gemessenen Kurz-Batch-Latenzlücke, beweist aber keinen bestimmten Hardware-Engpass. Eine Quantisierung nur des Embeddings würde vor allem die residente Gewichtsgröße reduzieren, besonders bei Mehrsprachig; sie muss die Inferenzlatenz nicht verbessern.

Mit dem aktuellen dichten Attention-Pfad machen Attention-Produkte etwa 1.5% der modellierten FLOPs bei L=93 für Laya, 7.9% bei L=512 für Laya und 23.3% bei L=1024 für Mehrsprachig aus. Ihr Anteil an der Wanduhrzeit kann erheblich abweichen. Ein Profiler sollte Matrix-Kernels, Attention-Kernels, elementweise Kernels, CPU-Graph-Konstruktion und Leerlauf-Lücken unterscheiden, bevor man sich auf eigene Metal-Arbeit festlegt.

Priorisierte Experimente

Priorität Experiment Bestes Ziel Hauptkompromiss / Abnahmebedingung
P0 Eine kurze und eine lange Shape profilen; das Modell oder Encoder-Blöcke kompilieren Latenz einer einzelnen Anfrage und Python-Dispatch Identische Ausgaben innerhalb der bestehenden Präzisionstoleranz beibehalten; die Erstnutzungskompilierung separat messen
P0 Batching nach tatsächlichem Token-Budget und Längenverteilung abstimmen Viele unterschiedliche Fragen und Verkehr mit gemischten Längen Durchsatz unter einem p95-Latenz- und Speicherbudget optimieren; Queueing berücksichtigen
P1 Ausgewählte lineare Backbone-Schichten quantisieren, beginnend mit 8-Bit, dann 4-Bit Gewichtsverkehr und potenziell dichte Inferenz Qualitäts- und Kalibrierungs-Gates; die tatsächliche M3-Max-Geschwindigkeit kann sich verschlechtern
P1 Tokenisierung geteilter Zustände und stabile Fragevorlagen cachen Viele Fragen, die sich einen Zustand oder wiederholte Rubriken teilen Exakte Token-Identität; begrenzter Cache; gecachte und ungecachte Ergebnisse trennen
P1 Nur die erforderlichen Ausgaben der letzten Head-Schicht berechnen Jeder Workload, besonders längere Sequenzen Exaktes Dependency-Pruning; bescheidene Whole-Model-Arithmetikeinsparung
P2 Echte Local-Window-Attention mit Kachelgrenzen 512/1024-Token-Workloads Neue Kernel-Komplexität; bidirektionales Fenster und Padding-Semantik bewahren
P2 Residual/Norm oder GELU/Gate nur fusionieren, wo das Profil es rechtfertigt Small-Kernel-Overhead oder Aktivierungsverkehr Bestehende schnelle Kernels decken viel davon bereits ab; exakte GELU-Semantik bewahren
Separate Produktfunktion Identische Forward-Eingaben deduplizieren Workloads, die Fragen tatsächlich wiederholen Anzahl eindeutiger Inferenzen und Cache-Treffer berichten; nicht als allgemeine Kernel-Beschleunigung darstellen

Den vollständigen ausgewerteten Inferenzpfad kompilieren

Agent.forward() konstruiert derzeit Arrays, ruft self.model auf und wertet das Ergebnis aus. Es gibt kein umschließendes mx.compile auf Modell- oder Blockebene. Kompilierung mit fester Shape kann die Python-Graph-Konstruktion reduzieren und unterstützte Operationen fusionieren. MLX dokumentiert Shape-Spezialisierung und explizites State Capture; es warnt außerdem, dass shapeless Kompilierung beliebige shape-abhängige Python-Operationen nicht sicher bewahren kann. Siehe den offiziellen Compilation-Leitfaden.

Beginne mit einem kompilierten Callable, das nach dem Laden, Casten und Auswerten der Gewichte erstellt wird, unter Verwendung normaler Shape-Spezialisierung. Behalte das bestehende unkompilierte Callable für Paritätsvergleich und CPU-Kompatibilität. Für ein eingefrorenes Inferenzmodell können Gewichte für diese Modellinstanz gecaptured bleiben; wenn sich Gewichte oder Modulstruktur ändern, baue das Callable neu auf oder capture den relevanten Zustand explizit. Verwende ein kompiliertes Closure nicht über Checkpoint-Ersetzungen hinweg wieder.

Teste einen Whole-Model-Wrapper und, wenn Tracing-Einschränkungen oder Kompilierungskosten das unattraktiv machen, kompiliere Encoder-Blöcke und den Head separat. Der aktuelle Code liest x.shape in Python-Integer, formt mit expliziten Batch-/Längenwerten um, erstellt arange(length)-Masken und indiziert Marker über einen aus der Shape abgeleiteten Zeilenbereich. shapeless=True ohne Umgestaltung auf diesen vollständigen Graphen anzuwenden ist unsicher. Das Verschieben der dynamischen Maskenkonstruktion aus einem kompilierten Block heraus und die Verwendung shape-unabhängiger Flatten-/Unflatten-Operationen können eine spätere shapeless Variante ermöglichen; verifiziere sie über geänderte B, L und Marker-Anzahlen.

Shape-Buckets können Retracing begrenzen, aber Padding hat Rechenkosten. L=93 auf 96 zu padden fügt etwa 3.2% Token-Arbeit hinzu; es auf 128 zu padden fügt etwa 37.6% hinzu. Vergleiche exakte Shape-Kompilierung mit kleinen Längenvielfachen und einer begrenzten Menge workload-informierter Buckets. Berücksichtige (batch size, padded length, marker slots, dtype, device/model instance) in Cache-Policy-Entscheidungen und messe Cold-Kompilierungslatenz und beibehaltenen Speicher bei Shape-Wechsel.

Der eigenständige forward-Benchmark in worker.py ruft agent.model direkt auf. Wenn Kompilierung nur in Agent.forward hinzugefügt wird, würde der aktuelle Forward-Benchmark sie umgehen, während der Ende-zu-Ende-Benchmark sie verwenden würde. Beide Pfade müssen explizit dieselbe Kandidatenimplementierung wählen, um einen aussagekräftigen Vergleich zu ermöglichen. Behalte mx.eval und GPU-Synchronisierung im Timing-Verfahren: Nur die Graph-Konstruktion zu messen würde nicht die Inferenz messen.

Nach nützlichen Tokens batchen, dann Matrix-Scheduling prüfen

collate_items padded jeden Chunk rechts auf seine längste Sequenz auf; die Runtime gruppiert Fragen in Einfügereihenfolge. Für heterogenen Verkehr sortiere oder bucket nach vorbereiteter Länge, verwende ein Token-Budget zusätzlich zu einer Obergrenze der Fragenanzahl und stelle ursprüngliche Fragen-IDs und Ausgabereihenfolge wieder her. Vergleiche Batches von 1, 2, 4, 8, 16, 32 und 64 nur dort, wo sie für den Service repräsentativ sind. Berücksichtige bei Online-Anfragen die Wartezeit auf einen Batch; Offline-Fragen/Sekunde allein können inakzeptable Latenz verbergen.

Der aktuelle kurze 50-Fragen-Workload verschwendet etwa 8.9% der gepaddeten Tokens für Laya und 5.8% für Mehrsprachig. Lange Benchmark-Zeilen haben keine Padding-Verschwendung. Folglich hat Unpadding oder Sortieren allein auf diesen Fixtures begrenzten arithmetischen Nutzen. Eine Verteilung mit gemischten Längen, einschließlich einer langen Frage unter vielen kurzen, ist nötig, um den Produktionsnutzen zu offenbaren. Das Entfernen von Padding muss die RoPE-Positionen pro Beispiel, Marker-Positionen und Attention-Grenzen bewahren; Beispiele ohne Isolationsmaske zu einer Sequenz zu verketten verändert das Modell.

Prüfe bei dichten Kernels die tatsächlichen Shapes und Strides. QKV ist bereits eine einzelne Projektion, und die beiden Eingabezweige der Encoder-MLP teilen sich bereits eine Projektion. Diese wahllos aufzuteilen würde Launches hinzufügen. Vergleiche explizites Flatten zusammenhängender [B,L,D]-Eingaben in [B*L,D] nur dann, wenn der Profiler oder MLX-Dispatch-Trace unerwünschte gebatchte GEMMs zeigt; das Framework flacht möglicherweise bereits effizient ab. Die alleinige Quellprüfung rechtfertigt es nicht, eine verpasste GEMM-Optimierung zu behaupten.

Füge in der Produktionsimplementierung keine Synchronisierung nach jeder Schicht ein. Die aktuelle Runtime wertet einmal pro Chunk aus. Zusätzliche Wartezeiten könnten die CPU/GPU-Überlappung entfernen und eine Scheduling-Verbesserung verschleiern; Layer-Level-Profiling sollte ein separater Diagnoselauf sein.

Quantisierung: das Backbone anvisieren und seinen Speichervertrag implementieren

Die installierte quantisierte Schichtimplementierung von MLX 0.32.2 bietet nn.quantize(..., class_predicate=...) und QuantizedLinear, mit rein gewichtsbezogener Matrixmultiplikation über mx.quantized_matmul. Gruppierte affine Quantisierung unterstützt 8-Bit- und 4-Bit-Experimente. Beginne mit linearen Encoder-Schichten bei Gruppengröße 64 und behalte Aktivierungen, Normen, Type-Embeddings, Scorer und Action-Head in FP16. Füge dann unabhängig lineare Decision-Head-Schichten und optional Embedding-Quantisierung hinzu. Messe jede Variante; rein gewichtsbezogene Kernels können bei hohen Token-Anzahlen gegen FP16-GEMMs verlieren.

Es gibt konkrete Integrationsgefahren im aktuellen Loader und Modell:

  1. Agent.__init__ castet jedes gespeicherte Gewicht in einen Fließkomma-dtype und instanziiert vor dem strikten Laden nur dichte Module. Ein quantisierter Checkpoint erfordert Metadaten, die die ausgewählten Module, Gruppengröße, Bitbreite und den Modus beschreiben; instanziiere passende quantisierte Module vor dem Laden und bewahre gepackte Integer-Gewichte. Ein Fließkomma-Cast gepackter Gewichte ist kein gültiges Laden.
  2. Die erste lineare Action-Head-Schicht hat Eingabebreite D+4, nämlich 1028 oder 772, die nicht durch eine affine Gruppengröße von 32, 64 oder 128 teilbar ist. Ein pauschaler Quantisierungsaufruf ist daher ungeeignet. Prüfe die Eingabebreite jeder ausgewählten Schicht vor der Konvertierung.
  3. DecisionModel.__call__ wählt den Eingabe-dtype des Action-Heads aus self.act_head.layers[0].weight.dtype. Bei einer quantisierten Schicht wäre dieses Gewicht gepackter Integer-Speicher, nicht der gewünschte Aktivierungs-dtype. Den Action-Head auszuschließen vermeidet diesen Pfad zunächst; ihn später zu unterstützen erfordert einen expliziten Aktivierungs-dtype-Vertrag.
  4. Quantisierung verändert Logits und kalibrierte Wahrscheinlichkeiten. Die bestehende kleine FP16-Fixture-Übereinstimmung ist kein ausreichender Beleg für 4-Bit-Qualität. Verwende zurückgehaltene gelabelte choice-, score- und noul-Aufgaben, mehrsprachige Eingaben, knappe Entscheidungen, unterschiedliche Optionsanzahlen und Eskalationsbeispiele. Verfolge Argmax-Übereinstimmung, Task-Genauigkeit, Score-Fehler, Wahrscheinlichkeitsdrift, Kalibrierung und Action-Wahrscheinlichkeiten. Gesättigte Action-Ausgaben können große Action-Logit-Änderungen verbergen.

Für FP16-affine Skalen und -Offsets mit Gruppengröße 64 beträgt der ungefähre Matrixspeicher bits/8 + 4/64 Bytes pro Parameter: 1.0625 Bytes bei 8-Bit und 0.5625 Bytes bei 4-Bit, verglichen mit 2 Bytes bei FP16. Dies sind Speicherschätzungen für quantisierte Matrizen, unter Ausschluss anderer Tensoren und Packaging-Overhead; sie sind keine Beschleunigungsschätzungen. Die offizielle Quantize-API beschreibt Gruppenteilbarkeit und Formate.

Neuere Low-Bit-Formate sollten gegen das tatsächliche M3-Max-Backend bewertet werden, nicht unter der Annahme, Hardware späterer Apple-Chips zu verwenden. Die NAX-Verfügbarkeitsprüfung von MLX erfordert eine neuere Architekturgeneration als das aufgezeichnete Gerät applegpu_g15s. Siehe die MLX 0.32.2-Geräteprüfung.

CPU-Vorbereitung wiederverwenden, wo die Eingaben tatsächlich identisch sind

build_sequence serialisiert, bereinigt und tokenisiert denselben Zustand für jede Frage separat. Es tokenisiert auch jede Instruktion und Option separat. Der Rust-Tokenizer wird bereits direkt verwendet; das Ersetzen der Transformers-Tokenisierung ist keine verbleibende Optimierung.

Serialisiere und bereinige den Zustand einmal pro prepare-Aufruf, kodiere ihn einmal und schneide seine Token-IDs auf den verfügbaren Platz jeder Frage zu. Cache unveränderliche vorbereitete Frage-Prefixe, wenn dieselbe Rubrik über Zustände hinweg verwendet wird, mit Schlüsseln, die Tokenizer-Identität/-Revision, Fragetyp, geordnete Kriterien, Instruktionsserialisierung, Special-Token-Bereinigung und Token-Budgets umfassen. Begrenzte Caches dürfen Ergebnisse nach Tokenizer- oder Konfigurationsänderungen nicht wiederverwenden. Gebatchte Tokenizer-Kodierung ist ein weiteres Experiment, sofern ihre Ausgabe exakt mit der aktuellen Sequenz unabhängiger Kodierungen übereinstimmt.

Tokenisiere keinen neu verketteten Prompt als Ersatz für das Verketten unabhängig kodierter Teile: Subwort-Grenzen können sich ändern. Verifiziere Byte-für-Byte Input-IDs, Attention-Masken, Marker-Positionen, qtypes, Trunkierungsverhalten, strukturierte Kriterien, Masken-Literale, leere Eingaben und Ausgabe-Zuordnung.

Für den langen Benchmark wiederholt der Zustand vor der Trunkierung einen Satz 200 Mal. N wiederholte Zustandskodierungen zu vermeiden könnte der CPU-Vorbereitung helfen, aber der bestehende Zeitunterschied zwischen Ende-zu-Ende und Forward misst diese Einsparung nicht. Benchmark prepare, Collate-/Array-Konstruktion, Forward und Postprocessing unabhängig und bestätige dann das Ende-zu-Ende-Ergebnis mit einem nicht wiederholenden zurückgehaltenen Korpus.

Ein Decoder-KV-Cache gilt nicht für diesen Encoder. Seine erste Schicht ist globale bidirektionale Attention; Zustands-Token-Repräsentationen hängen von der Frage, den Optionen und ihren Positionen ab. Zustands-Hidden-States oder K/V über verschiedene Fragen hinweg wiederzuverwenden verändert Ergebnisse. Tokenisierung und identische Whole-Input-Ergebnisse können gecacht werden; beliebiger kontextueller Encoder-Zustand nicht.

Die Ausgaben des finalen Decision-Heads exakt prunen

Nach der letzten HeadLayer werden nur das [CLS]-Token und Options-Marker-Tokens konsumiert. Frühere Head-Schichten müssen weiterhin alle Tokens erzeugen, weil die letzte Schicht ihre K/V liest. In der letzten Schicht allein:

  1. Alle Eingabe-Tokens normalisieren und alle K/V berechnen.
  2. Q an [CLS] und gültigen Options-Positionen sammeln und diese Queries gegen die vollständige maskierte K/V-Sequenz ausführen.
  3. Ausgabeprojektion, Residual, zweite Norm und Feed-Forward-Netzwerk nur auf diese ausgewählten Positionen anwenden.
  4. Die ausgewählte [CLS]-Ausgabe für den Action-Head und ausgewählte Marker-Ausgaben für das Scoring verwenden; Marker-Padding und ursprüngliche Reihenfolge bewahren.

Eine erste Implementierung kann die fusionierte vollständige QKV-Projektion behalten und Q danach sammeln. Eine aggressivere Variante teilt ihre Gewichte in eine vollständige KV-Projektion und eine Q-Projektion für ausgewählte Tokens. Das spart mehr Arithmetik, kann aber das GEMM-Scheduling weniger effizient machen. Doppelte gepaddete Marker-Indizes sind nur dann harmlos, wenn maskierte Ergebnisse unbeobachtbar bleiben. Die Fälle mit 1 Option, vielen Optionen und variablen Markern brauchen explizite Paritätsprüfungen. Da weniger Queries einen anderen SDPA-Kernel auswählen können, impliziert mathematische Äquivalenz keine bitweise identischen Fließkommaergebnisse.

Mit R = 1 + number of option slots entfernt das Beibehalten der vollen QKV ungefähr 18 * B * (L-R) * D^2 dichte FLOPs und 4 * B * L * (L-R) * D Attention-FLOPs aus der letzten Head-Schicht. Das Aufteilen von Q/KV ändert den dichten Koeffizienten von 18 auf 20. Relativ zur obigen Whole-Model-Arithmetikschätzung ergibt R=5:

Checkpoint / Länge Fusionierte volle QKV beibehalten Zusätzlich nur ausgewähltes Q berechnen
Laya, L=93 2.44% 2.70%
Laya, L=512 2.60% 2.86%
Typisierte Entscheidungen, L=1024 2.66% 2.90%
Mehrsprachig, L=93 4.03% 4.47%
Mehrsprachig, L=1024 4.22% 4.58%

Dies sind statische FLOP-Reduktionen, keine vorhergesagten Latenzreduktionen. Die Technik entfernt den größten Teil der Arbeit aus einer Head-Schicht, nicht den größten Teil der Arbeit aus dem Modell. Sie ist nützlich, weil sie Abhängigkeiten bewahrt und ohne Neuschulung implementierbar ist, nicht weil sie ein Mehrfaches an Whole-Model-Geschwindigkeit verspricht.

Echte lokale Attention erst nach Messung ihres Beitrags bauen

Alle Encoder-Attention-Aufrufe verwenden bereits mx.fast.scaled_dot_product_attention; RoPE ist bereits mx.fast.rope; nn.LayerNorm ruft das schnelle Normalisierungsprimitive auf. Die Attention-API von MLX akzeptiert Boolean-Masken und führt Softmax in FP32 aus. Die aktuelle Head-Dimension ist 64. Der MLX 0.32.2 Metal-Dispatch unterstützt diese Shape mit Array-Masken und wählt während der Inferenz nicht den unfusionierten Fallback dafür. Es gibt keinen Beleg dafür, dass Layas Boolean-Maske fusionierte Attention deaktiviert. force_fused=True, in der installierten Version verfügbar, ist als diagnostische Assertion nützlich, sollte hier aber nicht als neuer Fast Path beworben werden.

Die verbleibende Einschränkung ist strukturierte Sparsity. Die Implementierung baut eine dichte lokale Boolean-Maske der Shape [B,1,L,L]. Im konventionellen Metal-Attention-Kernel durchläuft die noncausal-Schleife den vollständigen KV-Kachelbereich; die Array-Maske wird nach der QK-Multiplikation auf die Scores angewendet. Sie bewahrt die Local-Attention-Semantik, ohne einen lokalen Kachelbereich auszunutzen.

Ein exakter spezialisierter Kernel kann jede Query-Kachel auf das überlappende K/V-Fenster begrenzen, die FP32-Softmax-Akkumulation beibehalten und eine dichte L-mal-L-Maske vermeiden. Das korrekte Fenster ist bidirektional und inklusiv: abs(query_position - key_position) <= 64. Innere Queries können 129 Positionen sehen, trotz des Konfigurationsnamens local_attention=128. Full-Attention-Schichten und beide Decision-Head-Schichten müssen global bleiben. Gepaddete Keys müssen ausgeschlossen bleiben, und ungenutzte gepaddete Queries brauchen definiertes endliches Verhalten.

Ein Prototyp mit geringerem Aufwand kann Query-Blöcke mit überlappenden K/V-Slices gruppieren und die bestehende SDPA mit einer kleineren exakten Maske aufrufen. Verwende bereits positionierte RoPE Q/K oder behalte absolute Offsets explizit bei. Bevorzuge gebatchte Blöcke gegenüber vielen Python-Aufrufen und berücksichtige duplizierte K/V-Materialisierung. Dieser Prototyp kann bei kurzen Längen gegen den aktuellen Kernel verlieren; er ist ein Korrektheits- und Break-even-Experiment, bevor man eigenes Metal pflegt.

Die maximale Reduktion der modellierten Whole-Model-FLOPs durch das Entfernen aller verbotenen lokalen Attention-Paare ist für kurze Eingaben klein und für lange vielversprechender:

Checkpoint / Länge Ideale Gesamt-FLOP-Reduktion durch exakte lokale Sparsity
Laya, L=93 0.086%
Laya, L=512 3.61%
Typisierte Entscheidungen, L=1024 7.69%
Mehrsprachig, L=93 0.147%
Mehrsprachig, L=1024 11.92%

Die Schätzungen verwenden local_pairs = L*(2*r+1) - r*(r+1) für L>r, mit r=64. Sie umfassen alle dichten Projektionen und beide Full-Attention-Decision-Head-Schichten. Sie schließen Maskengenerierung und Speicherverkehr aus. Der Runtime-Nutzen kann den FLOP-Anteil übersteigen oder unterschreiten, weil Attention und GEMMs unterschiedliche Effizienz haben; nur Profiling kann ihn feststellen. Bei 8192 Tokens wäre der Kompromiss anders, aber die ausgelieferten Agents begrenzen Eingaben auf 512 oder 1024, sodass eine 8192-Token-Aussage einen separat unterstützten Workload erfordern würde.

Fusion jenseits der Kompilierung

Das installierte exakte nn.gelu ist bereits mit shapeless Kompilierung dekoriert, und nn.Linear verwendet bereits ein bias-bewusstes addmm, wenn angemessen. Whole-Block-Kompilierung kann die GELU dennoch mit ihrer Gate-Multiplikation, Residual-Additionen, Casts, Masken und kleinen Score-Feature-Operationen fusionieren. Prüfe den kompilierten Kernel-Graphen, bevor du einen äquivalenten eigenen Kernel implementierst.

Wenn Aktivierungsverkehr weiterhin signifikant bleibt, prototypisiere exakte GELU-und-Gate- oder Residual-und-LayerNorm-Fusion. Bewahre die aktuelle exakte erf-basierte GELU; eine tanh- oder Sigmoid-Approximation verändert das Modell und braucht separate Qualitätsmessungen. Prüfe die tatsächlichen Q/K/V-Strides und -Kopien, bevor du Layout-Konvertierungen hinzufügst: Die Full-Attention-Implementierung von MLX akzeptiert eine zusammenhängende Head-Dimension mit anderem Striding und schreibt ein Ausgabe-Layout, das zum Mergen von Heads günstig ist. Eine bedingungslose zusammenhängende Kopie kann Arbeit hinzufügen.

Die Marker-Softmax, Top-Zwei-Sortierung, Entropie, der kleine Action-Head und die NumPy-Ergebnisformatierung sind legitime spätere Ziele, aber nur wenn gemessen. Es gibt wenige Options-Positionen im Vergleich zu Hunderten vollbreiter Transformer-Operationen, sodass ihre vorrangige Optimierung wahrscheinlich nicht den dominanten Pfad adressiert.

Die Benchmark-Bedeutung schützen, während aggressive Gewinne angestrebt werden

Der aktuelle Workload-Generator durchläuft drei Fragedefinitionen, um 5, 10 oder 50 Fragen zu konstruieren. Es gibt höchstens drei eindeutige Modelleingaben in diesen Batches. Exakte Deduplizierung pro Aufruf kann redundante Inferenz in einer echten Anwendung vermeiden, würde aber diese Fixtures überproportional verbessern. Halte diese Funktion getrennt von der Kernel-Optimierung und berichte questions, unique_forward_inputs, Cache-Treffer und tatsächlich ausgewertete Tokens. Rekonstruiere jede ursprüngliche Antwort mit ihren eigenen Labels, geordneten Kriterien und Kalibrierungsmetadaten. Behalte eine Suite mit 50 wirklich unterschiedlichen Fragen und eine Suite mit absichtlich duplizierten Eingaben.

Verwende aufrufübergreifendes Ergebnis-Caching nicht für den primären Inferenz-Benchmark: Er ruft wiederholt exakt dieselbe Anfrage auf. Berichte jedes Cache-Experiment als solches. Tokenizer-Cache-Benchmarks sollten sowohl ein Szenario mit wiederholter Rubrik als auch ein Szenario mit frischer Eingabe umfassen.

Verwende für jeden Kandidaten dieses Versuchsdesign:

  1. Halte das Ziel konstant. Zeichne Quell-Revision/Hash, Modell-Revision, dtype, Kompilierungsflags, ausgewählte quantisierte Module, Token-IDs oder deren Hash, Shape, Marker-Anzahl, Batching-Policy, Warmup, Synchronisierung und Gerät auf. Das bestehende input_sha256 hashed Zustand/Fragen statt tatsächlicher Token-Tensoren; es etabliert keine tokenizer-übergreifende Tensor-Identität. Führe die Baseline von derselben endgültigen Quell-Revision erneut aus, weil historische JSON-Quell-Hashes abweichen.
  2. Trenne Workload-Klassen. Verwende feste Shapes für kontrollierte Kernel-Vergleiche; echte variable Längen für Scheduling und Kompilierung; unterschiedliche Fragen für den Durchsatz; wiederholte Rubriken für legitimes CPU-Caching; und absichtlich duplizierte Fragen für die Deduplizierung. Berücksichtige lange Enden und unterschiedliche Optionsanzahlen. Halte Quelltext und Trunkierung innerhalb jedes Backend-Vergleichs identisch.
  3. Messe Kalt- und Warmverhalten. Zeichne Modellladen und erste Kompilierung separat auf. Messe Vorbereitung, Graph-Konstruktion, synchronisierten ausgewerteten Forward, Ausgabekonvertierung und Ende-zu-Ende-Latenz, ohne unabhängige Medianwerte als additiv zu behandeln. Profiliere Kernels in einem separaten Lauf, weil Tracing die Latenz stören kann.
  4. Ändere jeweils nur eine Optimierung. Screene mit der bestehenden Iterationszahl, wiederhole dann die Finalisten in abwechselnden Baseline-/Kandidatenblöcken und sammle genug Stichproben für ein glaubwürdiges p95, zum Beispiel mindestens 200 getimte Anfragen pro Workload. Verwende einen einzigen aktiven GPU-Benchmark, konsistente Strom-/Temperaturbedingungen und bewahre Rohproben. Verlange eine Verbesserung, die größer ist als die gemessene Laufvariabilität.
  5. Prüfe Korrektheit und Stabilität. Vergleiche gegen MLX beim selben dtype und die bestehende FP32-Referenz; erzwinge Token-Identität bei Scheduling- und CPU-Vorbereitungsänderungen. Teste Längen um 64, 128 und Bucket-Grenzen; Batches um Chunk-Limits; eine und viele Optionen; mehrsprachige Eingaben; Padding; und Shape-Änderungen nach der Kompilierung. Wiederhole Anfragen mit wechselnder Shape, beobachte sowohl aktiven als auch Cache-Speicher nach dem Warmup und verifiziere endliche deterministische Ergebnisse innerhalb jeder Konfiguration.
  6. Wende strengere Gates auf approximative Änderungen an. Quantisierung, Aktivierungsapproximation, Token-Pruning, Early Exits und Destillation erfordern zurückgehaltene Task- und Kalibrierungsergebnisse jenseits des kleinen Regression-Fixtures. Bewahre eine eigene Modellidentität und ein Benchmark-Label, wenn du trainiertes Verhalten änderst. Ein schnellerer mehrsprachiger Checkpoint oder ein destilliertes Modell ist ein anderes Modell, keine Beschleunigung des identischen Laya-Checkpoints.

Verwende für jeden gemessenen Hotspot, der den Anteil f der Ende-zu-Ende-Zeit einnimmt und um den Faktor s beschleunigt wird, Amdahls Schranke 1 / (1 - f + f/s), um die erwartete Gesamtwirkung einzuschätzen. Verwende für f einen gemessenen Zeitanteil; die obigen Arithmetikanteile sind kein Ersatz. Die nächste konkrete Engineering-Entscheidung sollte der Compile-/Batching-Ablation und einem Kernel-Profil für kurze vs. lange Eingaben folgen, nicht einem unverifizierten Multiplikator.

Dokumentations- und Reproduzierbarkeitshinweise

Die Dokumentationsabfrage verwendete den erforderlichen Context7-Workflow: eine library MLX-Auflösung, dann separate Abfragen der offiziellen Dokumentation für Kompilierung und schnelle Attention, unter Verwendung von /websites/ml-explore_github_io_mlx_build_html (insgesamt drei Befehle). Installierte .pyi- und Python-Quellen wurden geprüft, um das Verhalten von MLX 0.32.2 zu verifizieren, einschließlich force_fused, Quantisierungs-APIs, kompilierter GELU und schnellem LayerNorm. Versionsfixierte Upstream-C++-/Metal-Quellen wurden für Dispatch- und Tile-Loop-Details gelesen. Keine Bibliothek wurde für diese Forschung aktualisiert.

Die beiden FP16-Baseline-Dateien, die für die detaillierten Beobachtungen verwendet wurden, hatten zum Prüfzeitpunkt diese SHA-256-Digests:

laya-mlx-float16.json
63146dd664d039dde1a728b17aad896e491bd01ea36ea0786953691180e55b09

laya-multilingual-mlx-float16.json
9af74bd5a11e4edc15e6a8c9dc929a7b9fd2d19cb06f348cd0e04a076f912473

Der Haupt-Benchmark-Prozess erzeugte während dieser Forschung noch zusätzliche Artefakte. Die Tabelle zitiert bewusst vollständige Baseline-Dateien, die bei der Prüfung bereits verfügbar waren, und erhebt keine Ansprüche über nicht gemessene Kandidatenimplementierungen.