WebGPU - Mehr als nur Grafik
Diese neue API eröffnet Webentwicklern viele neue Möglichkeiten, die zuvor nur außerhalb des Browsers möglich waren.
Als jemand, der gerne mit Grafiken arbeitet, war ich überrascht von den neuen Möglichkeiten der WebGPU API.
Ohne Zweifel könnte diese API die Art und Weise verändern, wie wir GPU-Computing betreiben. Anstatt vortrainierte KI-Modelle herunterzuladen und auszuführen, könnte dies nun direkt im Browser geschehen – ohne jegliche Einrichtung.
In diesem Blogpost möchte ich dir einen Überblick über die API, ihre Herkunft und ihre Fähigkeiten geben.
Was ist WebGPU?
Im Wesentlichen handelt es sich um eine moderne Browser-API zur Interaktion mit der Grafikkarte (GPU) deines Computers. Damit ermöglicht sie leistungsstarkes Rendering und Berechnungen.
Aber verwechsle es nicht mit WebGL! WebGPU ist eine modernisierte, aktuelle und leistungsfähigere Version des Vorgängers WebGL.
WebGL – nur Grafik
WebGL basiert ursprünglich auf OpenGL, einem nativen API-Standard für GPUs. Doch seitdem haben sich GPUs weiterentwickelt – und so auch ihre APIs.
Heute gibt es moderne APIs wie Vulkan, DirectX und Metal. OpenGL hingegen wird nicht mehr aktiv weiterentwickelt.
Doch es gibt ein größeres Problem: WebGL basiert auf der Idee, Grafiken zu rendern. Heutzutage sind GPUs aber viel vielseitiger – sie werden für Machine Learning, Kryptographie, Simulationen oder Data Analysis genutzt.
Was wir also brauchen, ist eine Browser-API, die auf modernen GPU-APIs basiert und ebenso vielseitig einsetzbar ist. Hier kommt WebGPU ins Spiel.
WebGPU – mehr als nur Grafik
Natürlich bietet WebGPU weiterhin ein breites Spektrum an Rendering-Funktionen und viele Features, die zu umfangreich sind, um sie in einem einzigen Blogpost zu erklären.
Stattdessen möchte ich dir zeigen, wie du deine Grafikkarte direkt aus dem Browser nutzen kannst, um aufwendige Berechnungen durchzuführen.
Das bringt natürlich zusätzlichen Overhead mit sich. Man muss gut abwägen, ob sich dieser Ansatz lohnt. Als Ausgangspunkt ist es hilfreich zu verstehen, warum man überhaupt die GPU nutzen sollte.
Warum GPU?
Ein großes Problem von JavaScript ist, dass es nur auf einem einzelnen Thread läuft. Das bedeutet, dass rechenintensive Prozesse auf der Client-Seite deine UI blockieren und unresponsive machen können.
Die GPU sticht besonders dann heraus, wenn eine Aufgabe parallelisierbar ist. Zum Beispiel, wenn du große Datenmengen verarbeiten möchtest. Wenn die Datenzeilen unabhängig voneinander sind, können alle Berechnungen parallel durchgeführt werden.
Oder ein anderes Beispiel: Wenn Daten mehrfach transformiert werden müssen, bevor sie dem Nutzer angezeigt werden. Zum Beispiel bei einem force-directed Graphen, bei dem erst die Bewegung der Kanten simuliert oder ein Minimierungsalgorithmus durchgeführt werden muss, um einen ansprechenden Graphen zu erzeugen.
Aber Vorsicht! Für einfache Probleme könnte die Nutzung eines Web Workers die bessere Lösung sein.
Deep Dive in Compute Pipelines
Ich möchte anhand einem (sehr) groben Beispiel zeigen, wie eine Berechnung mit der WebGPU API aussehen könnte. Für eine allgemeine Einführung siehe die MDN-Dokumentation.
Wir gehen dabei in folgenden Schritten vor:
1. Definieren der zu verarbeitenden Daten
2. Schreiben des Codes, der mit den Daten arbeitet
3. Konfiguration, wie die GPU den Code ausführen soll
4. Übertragung der Ausgangsdaten auf die GPU
5. Ausführen der Berechnung
6. Auslesen der Ergebnisse
Zunächst muss ein GPU-Adapter und ein Gerät angefordert werden:
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
Um Berechnungen durchzuführen, muss man der GPU zunächst mitteilen, welche Daten sie speichern soll und wie sie genutzt werden.
const stagingBuffer = device.createBuffer({
size: BUFFER_SIZE, // Größe des Buffers in Bytes
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const outputBuffer = device.createBuffer({ ... });
Dann wird definiert, was mit den Daten geschehen soll – mit einer Rust-ähnlichen Sprache namens WebGPU Shading Language (WGSL). Diese wird zu nativen GPU-Anweisungen kompiliert.
const shaderModule = device.createShaderModule({ code: [`... WGSL-Code ...`] });
Letztendlich benötigt man eine Pipeline, um Daten und Code miteinander zu verbinden.
const pipeline = device.createComputePipeline({ ... });
Nachdem die Ausgangsdaten auf die GPU kopiert und die Pipeline ausgeführt wurde (was ich hier nicht im Detail zeigen möchte), können die Ergebnisse zurück in JavaScript gelesen werden:
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, BUFFER_SIZE);
device.queue.submit([commandEncoder.finish()])
await stagingBuffer.mapAsync(GPUMapMode.READ, 0, BUFFER_SIZE);
const copyArrayBuffer = stagingBuffer.getMappedRange(0, BUFFER_SIZE);
const data = copyArrayBuffer.slice();
stagingBuffer.unmap();
console.log(new Float32Array(data));
Warum das ein großer Schritt für das Web ist
Machine Learning ist derzeit eines der am schnellsten wachsenden Gebiete. Bisher arbeiten ML-Interfaces entweder mit einem zusätzlichen Backend oder erfordern den Download und die lokale Ausführung von Code. Mit WebGPU kann die Entwicklung und Ausführung vollständig in den Browser verlagert werden. Viele ML-Frameworks wie z.B. TensorFlow.js nutzen WebGPU bereits.
Zusätzlich können durch den Zugriff auf moderne GPU-APIs Spiele und Game Engines effizient im Browser laufen – ohne spürbare Performance-Unterschiede zu nativen Anwendungen. 3D-Rendering-Bibliotheken wie Three.js unterstützen WebGPU bereits als Backend.
Obwohl jede Entwicklungsumgebung ihre Vor- und Nachteile hat, bleibt Web-Development das Ökosystem mit der größten Reichweite. Es wächst jedes Jahr rasant und ist für jeden gut zugänglich. Da Browser immer mehr native Features hinzufügen, schließt sich die Lücke zwischen dem Web und nativen Anwendungen. Wenn du an weiteren nativen Features interessiert bist, sieh dir den Blogpost über PWA an.
Fazit
In den nächsten Jahren wird WebGPU für das Web-Ökosystem immer wichtiger werden.
Auch wenn ich in meinem Beispiel viel Code weggelassen habe, ist der Overhead immer noch groß. Es erfordert einiges an Low-Level-Know-how und hat anfangs eine steile Lernkurve. Doch es gibt viele Ressourcen und Bibliotheken, die dir den Einstieg erleichtern können.
Da es sich um eine relativ neue API handelt, ist die Browserunterstützung derzeit noch eingeschränkt.
Auch wenn du nicht mit ML arbeitest, könnte es sich lohnen, WebGPU im Hinterkopf zu behalten. JavaScript ist zwar extrem optimiert, aber es gibt Grenzen, an denen nur eine native Lösung weiterhilft. Neben WebAssembly ist WebGPU wahrscheinlich der beste Weg, um aufwendige Berechnungen performant auszuführen.
![Philipp Dehler](https://quarxi.mo.cloudinary.net/default/f/156829/800x800/1c2ba92120/1630094546364.jpeg?tx=f_auto,h_100,w_100)
![Johann Roth](https://quarxi.mo.cloudinary.net/default/f/156829/1280x1280/87eaf3c6cb/johann.jpg?tx=f_auto,h_100,w_100)
![Robert Kramer](https://quarxi.mo.cloudinary.net/default/f/156829/812x812/70728b51ac/robert.png?tx=f_auto,h_100,w_100)
![Team esveo](https://quarxi.mo.cloudinary.net/default/f/156829/240x240/10e1016578/esveo-avatar.png?tx=f_auto,h_100,w_100)
![Andreas Roth](https://quarxi.mo.cloudinary.net/default/f/156829/2000x2000/4ffa9c04d2/andreas.jpg?tx=f_auto,h_100,w_100)
![Andreas Roth](https://quarxi.mo.cloudinary.net/default/f/156829/2000x2000/4ffa9c04d2/andreas.jpg?tx=f_auto,h_100,w_100)
![Jonathan Frere](https://quarxi.mo.cloudinary.net/default/f/156829/942x942/7a985c806a/jonathan.jpeg?tx=f_auto,h_100,w_100)
![Jonathan Frere](https://quarxi.mo.cloudinary.net/default/f/156829/942x942/7a985c806a/jonathan.jpeg?tx=f_auto,h_100,w_100)
![Julien Seerig](https://quarxi.mo.cloudinary.net/default/f/156829/512x512/e55ae32038/julien.jpeg?tx=f_auto,h_100,w_100)
![Andreas Roth](https://quarxi.mo.cloudinary.net/default/f/156829/2000x2000/4ffa9c04d2/andreas.jpg?tx=f_auto,h_100,w_100)
![Paul Dittrich](https://quarxi.mo.cloudinary.net/default/f/156829/648x648/8f14c46daa/paul.jpeg?tx=f_auto,h_100,w_100)
![Andreas Roth](https://quarxi.mo.cloudinary.net/default/f/156829/2000x2000/4ffa9c04d2/andreas.jpg?tx=f_auto,h_100,w_100)