Unter der Haube

Von der Zeichenfläche zu WebGPU: die IR-Leiter

Im Studio zeichnest du einen Workflow — Knoten, Verbindungen, Parameter. Was läuft, ist aber nicht die Zeichnung: Es ist GPU-Code, erzeugt für deine Maschine und ausgeführt direkt im Browser-Tab. Dazwischen liegt eine Leiter von Zwischenrepräsentationen, und beim Abstieg über sie entstehen die Garantien — Machbarkeit geklärt, bevor irgendetwas läuft, Determinismus deklariert statt angenommen, jeder Kernel verdient sich Vertrauen gegen eine Referenz. Die Engine, der diese Leiter gehört, ist der Microkernel aus dem vorigen Artikel; hier sehen wir ihn arbeiten.

Die Leiter hinab, Sprosse für Sprosse#

Die Absenkung verläuft in benannten, inspizierbaren Stufen. Das Workflow-Dokument — der Graph, den du gezeichnet hast — kompiliert zu einem typisierten Programm, dessen Einheit der Block ist (BlockIR): Die Struktur wird explizit — welche Blöcke, in welcher Reihenfolge, unter welchem Kontrollfluss. Numerische Arbeit senkt weiter ab in einen Tensor-Operationsgraphen, die Sprosse der automatischen Differenziation (WebIR), dann in backend-spezifische Kernel — WGSL für WebGPU, daneben die CPU-Referenz (KernelIR) — und zuletzt in einen Ausführungsplan aus konkreten Dispatches, Puffern und einem Speicherplan (ExecPlan). Verteilt sich ein Lauf über Geräte, partitioniert eine weitere Sprosse — der WorkPlan — ihn in verifizierbare Shards.

Jede Sprosse ist ein vollwertiges Artefakt, das du öffnen und lesen kannst. Zwischen Bild und Ergebnis gibt es keine Blackbox; willst du wissen, was die Maschine gleich tut, inspizierst du die Sprosse, die deine Frage beantwortet.

text
graph (mid-ladder, simplified)
  %x    = input "dataset"             shape [N, D]  dtype f32
  %w    = param "weights"             shape [D, K]  dtype f32
  %mean = reduce_mean %x axis=0       shape [D]
  %xc   = sub %x, broadcast %mean     shape [N, D]
  %y    = matmul %xc, %w              shape [N, K]

plan (computed before any dispatch)
  determinism_tier : prod             declared, never defaulted
  tolerance_class  : default          resolves to explicit thresholds per tier
  peak_memory      : parameters + activations + gradients
                     + optimizer_state + working
  verdict          : green            fits the declared budgets
Workflowwas du zeichnestBlockIRtypisiertes ProgrammWebIRTensoren + GradientenKernelIRGPU- und CPU-CodeExecPlanDispatches + Speicherdeine GPUhier läuft esWorkPlanverteilt, verifiziertein Abzweig, keine Stufe — nur wenn die Arbeit auf Geräte verteilt wird
Abb. 1 — Der ganze Abstieg, einmal. Deine Zeichnung wird ein typisiertes Programm, ein Tensor-Graph mit Gradienten, GPU-Code und zuletzt ein Plan, der auf der GPU vor dir läuft. Die Arbeit auf Geräte zu verteilen ist ein Abzweig von diesem Plan, nie eine Stufe auf dem Weg zu deiner eigenen GPU — und jede Sprosse ist ein Dokument, das du öffnen kannst.

Speicher steht vor dem Lauf fest#

Den Plan gibt es, damit Machbarkeit vor dem ersten Dispatch entschieden ist. Speicher wird als explizite Aufschlüsselung nach Allokationsklassen geplant — Parameter, für den Backward-Pass behaltene Aktivierungen, Gradienten, Optimizer-Zustand und Arbeitspuffer für Kernel-Temporäre — „mysteriöser GPU-Speicher“ hat also keinen Ort zum Verstecken. Mixed Precision fügt ihre f32-Master-Gewichte hinzu. Große Eingaben werden als deklarierte Streaming-Klassen geplant, Chunk-Größen gegen das Budget berechnet statt durch Abstürzen entdeckt, und Tiling behandelt das 128-MB-Binding-Limit von WebGPU automatisch. Diese fünf Klassen — und was zu ändern ist, wenn sie nicht passen — führt Training im Browser im Detail vor.

Die Planung endet in einem von drei Urteilen. Grün läuft im Budget. Gelb läuft mit Warnungen — etwa Streaming, das langsamer ist als ein Lauf im Speicher. Rot wird abgelehnt, und die Ablehnung ist umsetzbar: was scheiterte, warum, und was zu ändern ist — ein kleinerer Batch, Streaming aktivieren, eine andere Präzision. Ein Out-of-Memory mitten im Lauf gilt als Architekturdefekt, nicht als Pech.

eine Operation, drei SprossenWebIRcenter_columnsx − mean(x)shape [N, D] · f32die RechnungKernelIR@computeworkgroup_size 64out[i] = x[i] − mean[col]der GPU-CodeExecPlandispatch ×3buffers: 4peak: 5.9 GBwas wirklich läuftjede davon kannst du zu jedem Lauf öffnen
Abb. 2 — Dieselbe Operation, dreimal. Die Spalten einer Matrix zu zentrieren ist Rechnung im Tensor-Graphen, ein Kernel in GPU-Code und ein konkreter Plan aus Dispatches und Speicher. Zu jedem Lauf kannst du alle drei öffnen — genau das heißt hier „keine Blackbox“.

Tipp

Öffne den Plan vor einem langen Lauf. Dieselbe Aufschlüsselung, die die Machbarkeit prüft, zeigt, welche Klasse deinen Bedarf dominiert — oft bewegt ihn ein einziger Regler.

Drei Determinismus-Stufen — nie still voreingestellt#

Jeder Lauf deklariert, wie deterministisch er sein muss. Es gibt genau drei Stufen:

  • debug — bitweise identische Ergebnisse über Läufe hinweg, mit demselben Seed auf demselben Gerät. Auf einer GPU erzwingt das deterministische Kernel: feste Reduktionsreihenfolge, keine atomare Float-Akkumulation — kostet Tempo, kauft Gewissheit.
  • prod — deterministisch innerhalb einer deklarierten numerischen Toleranzklasse; die Alltagshaltung für echte Arbeit.
  • fast — Durchsatz zuerst; Nichtdeterminismus nur erlaubt, wo er als sicher deklariert ist.

Die Stufe wird mit der Arbeit mitgeführt: in Spezifikationen deklariert, zur Ausführung gewählt, mit dem Lauf aufgezeichnet. Sie wird nie still voreingestellt — ein Workflow ohne effektive Stufe scheitert beim Kompilieren mit einer benannten Diagnose, statt stillschweigend eine anzunehmen. Deklarieren zusammengesetzte Teile verschiedene Stufen, gewinnt die strengste — außer du übersteuerst explizit; die Übersteuerung meldet sich mit einer Diagnose, wann immer sie tatsächlich eine strengere Stufe fallen lässt.

Was „dasselbe Ergebnis“ über GPUs hinweg bedeutet#

Klar gesagt: Ergebnisse auf verschiedenen GPUs sind nicht bit-identisch. Gleitkomma-Addition ist nicht assoziativ, und verschiedene Geräte ordnen Arbeit anders. Was über Geräte hinweg gilt, ist Übereinstimmung innerhalb der deklarierten Toleranzklasse — eine von exact, tight, default, loose, jede aufgelöst in explizite Schwellen pro Stufe. Reproduzierbarkeit ist also ehrlich zweistufig: replayable auf dem Orakelpfad (bit-exakt) und convergent auf dem GPU-Pfad (toleranzbegrenzt). Bit-exakte Wiederholung existiert an genau zwei Orten — die debug-Stufe auf demselben Gerät und der CPU-Referenzpfad, der überall deterministisch ist. Ein Plan, der nicht einhalten kann, was du verlangt hast — geräteübergreifende Bit-Exaktheit auf GPUs etwa — wird mit einer Diagnose abgelehnt, nie auf eine schwächere Garantie heruntergestuft, die auf dem Bildschirm gleich aussieht.

Hinweis

Vergleiche Zahlen von zwei Maschinen so, wie es die Plattform tut: innerhalb der deklarierten Toleranz. Zwei GPU-Läufe, die in der Klasse übereinstimmen und in den letzten Dezimalstellen abweichen, sind das erwartete, ehrliche Ergebnis — das ganze Bild steht auf der Honest-Limits-Seite.

Vertrauen wird gegen eine Referenz verdient#

Jede eingebaute GPU-Operation wird gegen eine CPU-Referenzimplementierung validiert — das Orakel — innerhalb ihrer deklarierten Toleranzklasse. Das Orakel ist der Korrektheitsanker, kein Laufzeit-Fallback: Für eine eingebaute Op ist ein fehlender GPU-Kernel ein Bug, nie ein stiller Umweg über langsameren Code. Am Fuß der Leiter ist das geprüfte Artefakt gewöhnliches WGSL — lesbar und prüfbar:

wgsl
@group(0) @binding(0) var<storage, read>       x    : array<f32>;
@group(0) @binding(1) var<storage, read>       mean : array<f32>;
@group(0) @binding(2) var<storage, read_write> out  : array<f32>;
@group(0) @binding(3) var<uniform>             dims : vec2<u32>;  // rows, cols

@compute @workgroup_size(64)
fn center_columns(@builtin(global_invocation_id) gid: vec3<u32>) {
  let i = gid.x;
  if (i >= dims.x * dims.y) { return; }
  let col = i % dims.y;
  // one element per invocation; conformance compares this kernel
  // to its CPU reference within the declared tolerance class
  out[i] = x[i] - mean[col];
}

Nutzer-Compute verdient es genauso#

Nutzergeschriebene Berechnung unterliegt derselben Disziplin, denn sie ist nie selbst-attestiert. Schreibst du einen Kernel, lieferst du auch eine Referenz; die Konformanz erzeugt Fälle aus deklarierter Signatur und Toleranz und beweist die Übereinstimmung. Ein eigener Gradient wird gegen finite Differenzen deines eigenen Forward gradgecheckt. Bestehen prägt ein ConformanceReceipt — profilgebunden, sodass ein Gerät mit anderer GPU-Umgebung die Prüfung lokal erneut ausführt, statt eine Behauptung zu erben, die keine GPU einlösen kann. Das Receipt begrenzt Vertrauen, nicht Ausführung: Auf deiner eigenen Maschine kannst du einen unkonformierten Kernel immer weiterentwickeln. Das ist das ganze Korrektheits-Regime, und beim Dispatch unterscheidet die Engine einen Community-Kernel nicht von einem eingebauten.

Keine GPU? Ein lauter CPU-Pfad, nie eine leere Zeichenfläche#

Nicht jedes Gerät hat WebGPU — WebGPU-Rendering ist zudem ein Capability-Flag, keine garantierte Grundlage, mit Canvas2D und WebGL2 als garantierten Kontexten. Scellis prüft die Fähigkeiten beim Start und gibt jedem Ergebnis einen definierten, sichtbaren Zustand. Keine GPU, aber eine funktionierende CPU-Engine: Ganze Workflows laufen auf dem CPU-Referenz-Backend, hinter einem lauten, dauerhaften Banner — läuft auf der CPU, langsamer, schweres Training eingeschränkt. Keins von beidem: Das Studio öffnet trotzdem zum Ansehen und Erstellen — komponieren, inspizieren, teilen — und sagt genau, warum nichts ausgeführt wird. Was nie passiert, ist eine leere Zeichenfläche oder eine stille Nicht-Antwort.

Das ist der ganze Weg eines Laufs: Eine Zeichnung senkt Sprosse für Sprosse zu WGSL ab; Speicher und Machbarkeit stehen vor dem ersten Dispatch fest; Determinismus ist ein deklarierter Vertrag mit ehrlicher geräteübergreifender Semantik; und jeder Kernel verdient sich sichtbares Vertrauen gegen eine Referenz, die du inspizieren kannst. Dieselben inhaltsadressierten Artefakte, die einen Lauf vertrauenswürdig machen, machen ihn auch per URL reproduzierbar.