Schnelle Backends
TileLang-Portabilität
Alle fünf Kernel in laya/tl_kernels.py lassen sich über compile_cpu auf TileLangs CPU-Ziel (c) ausführen. Das ist eine explizite, skalare fp32-Spezialisierung, kein CPU-Beschleunigungs-Backend für Agent.accelerate. Sie benötigt TileLang und einen lokalen C++-Compiler. Sie führt keine Abhängigkeit von einem Laufzeitdienst ein.
import torch
from laya import tl_kernels as K
A = torch.randn(17, 67)
W = torch.randn(70, 67)
b = torch.randn(70)
C = torch.empty(17, 70)
kernel = K.compile_cpu(K.gemm_kernel, 70, 67, bias=True, act="gelu")
kernel(A, W, b, C)
compile_cpu(factory, *args, **kwargs) nimmt dieselben Form- und Operationsoptionen wie die fünf GPU-Factories. Es wählt cpu=True, dtype="float32", das Ziel c und deaktiviert die Vektorisierung. Alle Gleitkomma-Eingaben und -Ausgaben müssen zusammenhängende CPU-float32-Tensoren sein; Attention-Längen bleiben int32. Konvertiere 16-Bit-Aktivierungen mit .cpu().float().contiguous(), bevor du es aufrufst. LayerNorm aktualisiert seinen Residual-Tensor weiterhin in-place, und RoPE aktualisiert weiterhin die Q/K-Spalten in-place. Behalte den kompilierten Kernel, um seine dynamischen Dimensionen wiederzuverwenden.
Die CPU-Spezialisierung ersetzt Fragment- und Shared-Allokationen durch lokale Puffer, verwendet serielle T.grid-Schleifen und lässt die GPU-Swizzle-Annotation der Attention weg. GEMMs nutzen TileLangs skalare CPU-Implementierung und Reduktionen nutzen lokale Puffer. Die ursprünglichen gekachelten Algorithmen, Padding-Prädikate, Attention-Masken und der Online-Softmax bleiben mit der GPU-Implementierung geteilt. CPU-Zwischenergebnisse bleiben fp32, einschließlich der Attention-Wahrscheinlichkeiten; GPU-Zwischenergebnisse behalten ihren ursprünglichen dtype. Es wird weder eine CPU-Beschleunigung noch ein CPU-Backend für das ganze Modell behauptet.
Erneute Prüfung auf TileLang 0.1.14
Beobachtet auf Linux, Python 3.12.13, torch 2.11.0+cu130, TileLang 0.1.14 und einer RTX 4070 Ti SUPER. Die frühere Sorge um die Portabilität betrifft das Kompilieren der unveränderten GPU-Spezialisierung, nicht die Möglichkeit eines CPU-Lowerings. Beim Basis-Commit fa9a2a7 fehlte diese Dokumentationsseite im Checkout.
Der Test kompiliert factory.get_tir(...) mit target="c" und der FAST-Pass-Konfiguration der GPU. Dies sind die exakten Diagnosetexte (Quellorte und Stack-Traces ausgelassen). Für jeden Kernel wurden sowohl bf16 als auch fp16 geprüft.
| Kernel und Prüf-Dimensionen | Erster bf16-Fehler | Erster fp16-Fehler |
|---|---|---|
gemm_kernel(128, 64) |
Check failed: layout_map.count(buffer) != 0 (0 vs. 0) : The layout for fragment C_l can not be inferred correctly. |
Gleich |
gemm_geglu_kernel(64, 64) |
CPU fill only supports local and global buffers, but got dst scope `local.fragment`. |
Gleich |
add_ln_kernel(128) |
CPU reduce only supports local src and local/local.var dst buffers, got src scope `local.fragment` and dst scope `local.fragment`. |
Gleich |
rope_kernel(2, 64) |
Cannot convert type bfloat16 to C type |
C++-Compiler: error: no matching function for call to ‘vec_type<float, 4>::vec_type(half4&)’ |
attn_kernel(1, 64, 2, 64) |
Check failed: layout_map.count(buffer) != 0 (0 vs. 0) : The layout for fragment s_c can not be inferred correctly. |
Gleich |
Die ersten vier Fragment-/Reduktionsfehler sind tvm.error.InternalError. Der bf16-Codegen-Fehler ist ebenfalls ein InternalError. FP16-RoPE wirft RuntimeError: Compilation Failed!, gefolgt vom Compiler-Aufruf und Quelltext; die obige Diagnose wird auf stderr ausgegeben. Seine generierten Vektor-Casts scheitern ebenfalls daran, float-Vektoren zurück in half-Vektoren zu konvertieren.
Um die dtype-Unterstützung von der Fragment-Unterstützung zu trennen, kompilieren die Tests jeden Kernel erneut mit cpu=True (lokale Puffer und serielle Schleifen), behalten bf16 bei und deaktivieren die Vektorisierung. Alle fünf scheitern dann exakt mit:
Cannot convert type bfloat16 to C type
Damit sind Fragmente der erste Blocker für GEMM, GEGLU und Attention, Fragment-Reduktionen für LayerNorm, und bf16 ist unabhängig ein Blocker für alle fünf. RoPE hat keine Fragment-Allokation oder -Reduktion; GEMM und GEGLU haben keine explizite T.reduce_*-Operation. Die Reduktionen der Attention werden anfangs von ihrem Layout-Fehler maskiert. Getrennte reine fp32-Fragment-Prüfungen reproduzieren den Füll-Fehler und den Reduktionsfehler sowohl für reduce_sum als auch für reduce_max, ohne jegliches GEMM oder bf16. Das alleinige Ersetzen von Scopes erzeugte außerdem diesen Semantikfehler für den GEMM-Puffer (die anderen betroffenen Puffer waren Ci, x und s):
[Tilelang Semantic Check] Local buffer `C_l` is indexed by T.Parallel loop variable `i`. Local buffers are thread-private and do not participate in parallel layout inference. Use T.serial/T.vectorized/T.unroll for per-thread local indexing, or T.alloc_fragment when the indexed dimension should be distributed across threads.
Die serielle fp32-Spezialisierung beseitigt diese Blocker. Diagnose-Assertions sind auf 0.1.14 festgelegt und werden bei einer anderen Version übersprungen, wo die Fehler erneut geprüft werden sollten. Numerische CPU-Tests laufen auf anderen Versionen weiter.
Numerische Prüfungen
Führe python -m pytest tests/test_fast_cpu.py -q -s aus. In der obigen Umgebung: 58 bestanden. Reine CPU-Prüfungen benötigen kein CUDA; nur die GPU-Vergleiche werden ohne CUDA übersprungen. Die Abdeckung umfasst ungleichmäßige GEMM-M/N/K-Kacheln, alle GEMM-Epiloge, GEGLU, alle Residual-/Bias-Kombinationen, große Residualwerte, RoPE-Positions-Wraparound und unberührte V-Spalten, statische/dynamische Attention-Formen, Sliding Windows, Teilkacheln, ungleiche Längen, leere Sequenzen und endliche Padding-Ausgaben.
Der Seed ist 1234. GPU-Vergleiche verwenden identische Eingabewerte, auf bf16 oder fp16 gerundet und dann für die CPU-Ausführung auf fp32 hochgestuft. Dies sind absolute Toleranzen für die begrenzten Fixtures, keine Garantie für beliebige Größenordnungen oder Modelltiefe. Attention-Vergleiche verwenden gültige Query-Zeilen, wie in tests/test_fast.py.
| Kernel | Max. CPU vs. fp32-Referenz | Max. CPU vs. GPU (beide dtypes) | CPU/GPU-Toleranz |
|---|---|---|---|
| GEMM | 2.38419e-7 | 0.00770831 | 0.05 |
| GEGLU | 2.98023e-8 | 0.000208303 | 0.05 |
| LayerNorm | 1.07288e-6 | 0.0156183 | 0.05 |
| RoPE | 0 | 0.0130053 | 0.05 |
| Attention | 5.96046e-7 | 0.00377572 | 0.02 |
Die CPU/Referenz-Toleranz ist 2e-5 (2e-6 bei RoPE). Aktualisierungen des Residual-Streams sind exakt gleich, einschließlich des Falls ohne Residual.
Nachweis der GPU-Erhaltung
Das Schlüsselwort cpu hat den Standardwert false. Bestehende bf16/fp16-Standardwerte, GPU-Allokations-Scopes, parallele Schleifen, Swizzles und FAST-Optionen bleiben unverändert. Standardmäßige fp32-GPU-Aufrufe werfen weiterhin ValueError.
Die Vorher-/Nachher-Läufe verwendeten das ursprüngliche, mit git show fa9a2a7:laya/tl_kernels.py extrahierte Modul bzw. das geänderte Modul. Das Original wurde für die Baseline-Läufe über importlib als laya.tl_kernels geladen; der übrige Laya-Code und die Python-Umgebung blieben gleich.
python -m pytest tests/test_fast.py -q, wobei die Full-Forward-Tests so konfiguriert sind, dass sie den unten genannten gecachten englischen Checkpoint laden: 13 vorher bestanden; 13 nachher bestanden. Anfangs, ohne Checkpoint, waren es 11 bestanden / 2 übersprungen. Beide vollständigen Läufe gaben die bestehende Temperatur-Clamping-Warnung des Checkpoints aus (choice:11+=0.10058280825614929 -> 0.5).- Geseedete Erfassung der bestehenden Kernel-Tests: 24 Ausgabe-Tensoren bit-identisch, einschließlich Residual-Updates; maximale Vorher-/Nachher-Differenz 0.
- Generierter CUDA-Quelltext für alle fünf obigen Prüf-Formen, in beiden dtypes: 10/10 byte-identisch. Das deckt auch RoPE ab, das in der ursprünglichen Fast-Suite fehlt.
python benchmarks/parity_fast.py --model "$MODEL" --dtype bf16 --json ...und der äquivalente fp16-Befehl: 288 Fragen über 60 Zustände pro dtype. Alle Vorher-/Nachher-JSON-Datensätze (fp32-, Stock- und Fast-Wahrscheinlichkeiten) stimmen exakt überein; maximale Vorher-/Nachher-Wahrscheinlichkeitsdifferenz 0.
MODEL war der gecachte englische Snapshot convaiinnovations/laya 55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851; die Läufe verwendeten HF_HUB_OFFLINE=1. Es wird hier kein Vergleich mit einem mehrsprachigen oder typisierten Checkpoint behauptet.
| dtype | type | n | Max. Fast-Stock, vorher = nachher | Fast/Stock-Argmax-Übereinstimmung, vorher = nachher |
|---|---|---|---|---|
| bf16 | choice | 48 | 0.0310 | 47/48 |
| bf16 | noul | 180 | 0.0756 | 180/180 |
| bf16 | score | 60 | 0.0152 | 60/60 |
| fp16 | choice | 48 | 0.0069 | 48/48 |
| fp16 | noul | 180 | 0.0092 | 180/180 |
| fp16 | score | 60 | 0.0040 | 60/60 |
Repository-Prüfungen
Alle Befehle verwendeten /home/ckl/projects/S/laya/.venv/bin/python; ruff und zensical kamen aus dem bin-Verzeichnis dieser virtuellen Umgebung.
| Befehl | Ergebnis |
|---|---|
ruff check laya/ --select=E9,F63,F7,F82,F401,F811 --line-length=120 |
All checks passed! |
python -m compileall -q laya/ tests/ |
Exit-Code 0, keine Ausgabe |
python tests/test_router.py |
703 bestanden, 0 fehlgeschlagen |
python tests/test_criteria.py |
198 bestanden, 0 fehlgeschlagen |
python tests/test_hooks.py |
240 bestanden, 0 fehlgeschlagen |
python tests/test_hooks_api.py |
415 bestanden, 0 fehlgeschlagen |
python tests/test_packaging.py |
131 bestanden, 0 fehlgeschlagen |
uv pip install --python /home/ckl/projects/S/laya/.venv/bin/python -r requirements-docs.txt |
3 Pakete geprüft (bereits installiert); die virtuelle Umgebung hat kein pip |
zensical build --strict --clean |
No issues found; keine griffe:-Zeilen |
Die neue Suite ist in den bestehenden Ausnahmen des Packaging-Tests als benötigend das optionale TileLang-Extra und einen C++-Compiler registriert. Der API-Contract-Test fixiert den additiven, nur-Keyword-CPU-Selektor, den unveränderten GPU-dtype-Standard und die compile_cpu-Signatur, ohne TileLang in die Basis-CI-Umgebung zu importieren.
AOTInductor
DecisionModel kann exportiert, in ein .pt2-Paket kompiliert und mit torch._inductor.aoti_load_package geladen werden. Das Paket gibt sowohl Decision-Logits als auch Action-Logits zurück. Tokenisierung, Padding, Temperaturkalibrierung und Antwortformatierung bleiben Aufgabe des Aufrufers; dies fügt kein Agent-Backend hinzu und ändert seinen standardmäßigen Ausführungspfad nicht.
Der geschlossene PR #472 dokumentierte den früheren dtype-Blocker. Der gepoolte Zustand und die Konfidenz-Merkmale des Action-Head werden in fp32 berechnet, selbst wenn die Gewichte eines Modells explizit bf16 sind. Ohne Autocast erhielt der erste Action-Linear daher eine fp32-Eingabe und bf16-Gewichte. Der Forward castet die konkatenierte Eingabe jetzt nur außerhalb von Autocast auf den Gewichts-dtype des Heads. Softmax, Entropie und die zurückgegebenen Decision-Logits behalten ihre fp32-Berechnungen. Die bestehende Eager-Inferenz mit fp32-Parametern, einschließlich fp16/bf16-AMP, behält ihre Numerik; gemischte Gewichts-/Autocast-dtypes behalten ebenfalls die ursprüngliche Konvertierung von Autocast.
Exportiere eine separate, nach bf16 konvertierte Evaluierungs-Kopie, außerhalb von Autocast. Deklariere die Token-Achse als 16 * Dim("tokens16", ...), um die Attention-Ausrichtungs-Guards zu erfüllen. Zeilen und Marker-Anzahlen können unabhängig dynamisch sein. Nimm nicht an, dass ein unter Autocast erfasster Export außerhalb dieses Kontexts paketiert werden kann: Verwende für dieses Rezept explizite Gewichts-dtypes.
Offline reproduzieren
Die assert-basierte Prüfung verwendet standardmäßig ein winziges, zufällig initialisiertes ModernBERT, ohne Netzwerkzugriff. Übergib ein lokales Checkpoint-Verzeichnis für eine Messung mit einem echten Modell. Fehlendes CUDA, fehlende AOTInductor-APIs oder ein fehlender C++-Compiler erzeugen ein explizites SKIP; Kompilierungs- und Paritätsfehler auf einer unterstützten Installation lassen die Prüfung fehlschlagen.
python scripts/check_aoti.py --output-dir /tmp/laya-aoti-smoke
HF_HUB_OFFLINE=1 TORCHINDUCTOR_CACHE_DIR=/tmp/laya-aoti/cache \
python scripts/check_aoti.py --model /path/to/local/multilingual \
--output-dir /tmp/laya-aoti
Das Ausgabeverzeichnis enthält decision.pt2, results.json, inputs.pt und eager.pt. Die JSON-Datei enthält vollständige nvidia-smi-Snapshots, Export-/Packaging-/Ladezeiten, Paket-Bytes, Latenz und maximale absolute Logit-/Wahrscheinlichkeits-Deltas für beide Heads. Binäre Artefakte und Compiler-Caches werden bewusst aus dem Repository herausgehalten.
Gemessen auf einer RTX 4070 Ti SUPER
2026-10-03, Linux x86-64, Python 3.12.13, torch 2.11.0+cu130, transformers 5.17.0, NVIDIA-Treiber 615.71.09, 16.376 MiB VRAM. Checkpoint: convaiinnovations/laya, Unterverzeichnis multilingual bei Revision 1c5edc17a7acd8701df6fc341c0d179f1c62c982. Die Baseline ist main fa9a2a7. Vollständige Maschinen-Snapshots und ungerundete Messungen stehen in aoti_multilingual_rtx4070.json.
| Messung | Vorher | Nachher |
|---|---|---|
| Export | 5.00 s | 3.89 s |
| Packaging | fehlgeschlagen nach 16.59 s | 60.02 s |
| Paketgröße | kein Artefakt | 645,729,621 Bytes (615.82 MiB) |
| Laden im bereits initialisierten Prozess | nicht verfügbar | 0.447 s |
| Laden in einem frischen Prozess mit leerem Cache | nicht verfügbar | 4.867 s |
Der reproduzierte Fehler tritt beim Packaging auf, nach erfolgreichem Export: mat1 and mat2 must have the same dtype, but got Float and BFloat16. Jeder Lauf verwendete einen separaten, anfangs leeren Inductor-Cache; die AMP-Kompilierung lief vor dem Packaging, sodass die Paket-Zeit keine Kaltstart-Messung eines frischen Interpreters ist. Die Prüfung im frischen Prozess lud nur das Paket und gespeicherte Eingaben (keinen Checkpoint), wobei torch.compile und Packaging-Einstiegspunkte blockiert waren. Sie reproduzierte die Antworten beider Heads. PyTorch kompilierte dennoch eine kleine CPU-AVX-Fähigkeitsprüfung in den anfangs leeren Cache; es wurde kein Modellgraph und kein CUDA-Kernel kompiliert. Eine pauschale Aussage „keine Compiler-Aktivität beim Laden“ wäre auf dieser Laufzeit daher ungenau.
Dieselben gespeicherten Eingaben enthalten sieben Decision-Zeilen über drei Batches, mit echtem tokenisiertem Abrechnungs-/Rückerstattungstext, choice/score/noul-Fragen, Padding und ungleichen gültigen Marker-Anzahlen. Jede Latenz ist der Median aus fünf Gruppen von 30 Forwards nach zehn Warmups, mit synchronisierter Wall-Clock-Zeitmessung. torch.compile(dynamic=True) verwendet den Standardmodus. Es sind keine expliziten CUDA-Graphen, Tokenisierung, Export oder Kompilierung in diesen Latenzzahlen enthalten.
| Zeilen × Tokens × Marker-Slots | AMP eager vorher → nachher (ms) | AMP compiled vorher → nachher (ms) | Explizit bf16 eager (ms) | Explizit bf16 compiled (ms) | AOTI bf16 (ms) |
|---|---|---|---|---|---|
| 2 × 128 × 3 | 15.1173 → 14.1200 | 6.7117 → 6.9076 | 13.9635 | 4.8835 | 2.2674 |
| 1 × 256 × 3 | 15.4687 → 14.4156 | 6.7274 → 5.4772 | 14.7994 | 4.7250 | 2.4154 |
| 4 × 160 × 5 | 14.8452 → 14.5384 | 7.3872 → 5.5934 | 14.7115 | 5.1393 | 3.6018 |
AMP bedeutet hier fp32-Parameter mit bf16-Autocast. Explizit bf16 bedeutet, dass Gewichte und der Residual-Stream bf16 sind, ohne Autocast; verwende diese Spalten für den engsten Ausführungsvergleich mit dem Paket. Explizit bf16 eager/compiled und AOTI waren vor diesem Fix nicht verfügbar.
| Vergleich, Maximum über alle sieben Zeilen | Decision-Logits | Decision-Wahrscheinlichkeiten | Action-Logits | Action-Wahrscheinlichkeiten |
|---|---|---|---|---|
| Bestehendes Eager vorher vs. nachher, fp32 und fp16/bf16-AMP | 0 | 0 | 0 | 0 |
| AOTI vs. explizit bf16 eager | 0.5625 | 0.00659859 | 0 | 0 |
| AOTI vs. bestehendes bf16-AMP-eager | 0.4375 | 0.00769910 | 8.0 | 0 |
Sowohl der Decision- als auch der Action-Argmax stimmen bei beiden AOTI-Vergleichen über 7/7 Zeilen überein. Wahrscheinlichkeiten sind rohe Softmax-Ausgaben, ohne Kalibrierung. Die Action-Verteilung ist bei diesen Eingaben gesättigt, sodass ihr Wahrscheinlichkeits-Delta von null nicht identische zugrunde liegende Logits gegenüber AMP impliziert. Der explizit bf16 kompilierte Forward unterscheidet sich auch vom explizit bf16 Eager (maximales Decision-Logit-Delta 0.5, Wahrscheinlichkeits-Delta 0.00659859). Kompilierung mit reduzierter Präzision ist nicht bit-exakt.
Die GPU wurde mit Desktop-Anwendungen und anderen Python-Jobs geteilt; die CPU-Kompilierung wurde ebenfalls geteilt, und die Takte waren nicht festgeschrieben. Dies sind beobachtete Zeiten, keine isolierte Speedup-Behauptung. nvidia-smi-Snapshots, die die Läufe einrahmen:
| Lauf / Snapshot | GPU-Auslastung | Belegter VRAM | Leistung | Temperatur / Status |
|---|---|---|---|---|
| Vorher / Start | 28% | 2,817 MiB | 12 W | 33°C / P8 |
| Vorher / Ende | 10% | 8,560 MiB | 23 W | 36°C / P3 |
| Nachher / Start | 0% | 2,634 MiB | 12 W | 33°C / P8 |
| Nachher / Ende | 73% | 6,476 MiB | 160 W | 42°C / P2 |
Verbleibende Grenzen
- Das Artefakt ist spezifisch für diesen Checkpoint, diese Präzision, diesen PyTorch-/Laufzeit-Stack und dieses GPU-Ziel; die Portabilität auf andere Hardware oder PyTorch-Versionen wurde nicht getestet.
- Diese Prüfung exportiert Zeilen 1–8, Tokens 32–512 in Vielfachen von 16 und Marker-Slots 2–8. Sie übt drei Formen aus, darunter Formen, die vom Export-Beispiel abweichen, statt jeden Punkt in diesen Bereichen. Eine gültige Option kann gepaddete Marker-Slots verwenden; ein echtes Ein-Slot-Tensor nimmt den separaten Einzeloption-Zweig und benötigt einen separaten Export. Beliebige Token-Längen und eine Produktions-Bucketing-/Dispatch-Schicht liegen außerhalb dieser Änderung.
- Sieben Zeilen verifizieren die Packaging-Regression, nicht die breite Checkpoint-Genauigkeit oder kalibrierte Konfidenzparität. Das Casten einer Export-Kopie nach bf16 unterscheidet sich vom Beibehalten der fp32-Gewichte unter Autocast; der bestehende Eager-Pfad selbst bleibt unverändert.
- Das Skript benötigt einen CUDA-fähigen PyTorch-Build und eine lokale Compiler-Toolchain, um das Artefakt zu erzeugen. Die
.pt2bettet das Modell und die CUDA-Kernel ein; es ist kein gehosteter Dienst beteiligt.