Stable Diffusion XL 1.0 läuft mit 298 MB RAM
(github.com/vitoplantamura)- OnnxStream ist eine ultrakleine Inferenzbibliothek, die darauf abzielt, Stable Diffusion 1.5 und SDXL 1.0 Base auf einem Raspberry Pi Zero 2 mit 512 MB RAM ohne zusätzlichen Swap oder Auslagerung von Zwischenergebnissen auf die Festplatte auszuführen
- Die Inferenz-Engine ist von
WeightsProvidergetrennt und unterstütztDiskNoCache,DiskPrefetchsowie benutzerdefiniertes Laden, Caching und Prefetching; Gewichte können auch direkt per HTTP empfangen und ohne Schreibzugriffe auf die Festplatte verarbeitet werden - SDXL 1.0 Base verwendet ONNX-Dateien, die aus der Implementierung in Hugging Face Diffusers 0.19.3 exportiert wurden; auf dem Raspberry Pi Zero 2 dauert die Bilderzeugung mit 10 Schritten etwa 11 Stunden
- Der VAE-Decoder von SDXL benötigt in FP32 4,4 GB RAM und lässt sich nicht einfach nur mit FP16 oder UINT8 beherrschen; durch überlappendes Tile-Decoding und Blending sinkt der Speicherbedarf auf 298 MB
- Im Vergleich zu OnnxRuntime auf der CPU benötigt es bis zu 55-mal weniger Speicher, kann dafür aber 0,5- bis 2-mal langsamer sein und ist auf Batch-Größe 1 sowie bestimmte ONNX-Operatoren beschränkt
Ziel und Design von OnnxStream
- OnnxStream wurde mit dem Ziel begonnen, Stable Diffusion 1.5 auf dem Raspberry Pi Zero 2 auszuführen
- Der Raspberry Pi Zero 2 ist ein Mikrocomputer mit 512 MB RAM
- Der allgemein empfohlene minimale RAM-/VRAM-Bedarf für Stable Diffusion liegt bei 8 GB
- Die Zielbedingungen sind: kein zusätzlicher Swap-Speicher, keine Auslagerung von Zwischenergebnissen auf die Festplatte
- Allgemeine Machine-Learning-Frameworks priorisieren meist Inferenzlatenz oder Durchsatz, wodurch der RAM-Verbrauch leicht groß wird
- OnnxStream ist eine kleine, leicht modifizierbare Inferenzbibliothek mit Fokus auf minimalen Speicherverbrauch
- Die Kernstruktur ist die Trennung von Inferenz-Engine und der Komponente zur Bereitstellung der Modellgewichte,
WeightsProvider- Abgeleitete Klassen können das Laden, Caching und Prefetching von Modellparametern implementieren
- Ein benutzerdefinierter
WeightsProviderkann Daten direkt von einem HTTP-Server herunterladen und ohne Lese- oder Schreibzugriffe auf die Festplatte verarbeiten - Die Standardimplementierungen sind
DiskNoCacheundDiskPrefetch
- Auf der CPU benötigt es im Vergleich zu OnnxRuntime bis zu 55-mal weniger Speicher, ist dabei aber etwa 0,5- bis 2-mal langsamer
Ausführung von Stable Diffusion 1.5
- Das Beispiel für Stable Diffusion 1.5 erzeugt mit OnnxStream Bilder mit unterschiedlichen VAE-Decoder-Präzisionen
- Die einzige Komponente, die nicht in den Arbeitsspeicher des Raspberry Pi Zero 2 passte, war der VAE-Decoder
- Gründe dafür waren residual connections im Modell, große Tensoren und große Convolutions
- Weder Single Precision noch Half Precision passten in den RAM des Raspberry Pi Zero 2
- Die Lösung war eine statische 8-Bit-Quantisierung
- Ein Bild mit einem VAE-Decoder in W8A8-Präzision wurde auf dem Raspberry Pi Zero 2 erzeugt; mit der Option
MAX_SPEEDdauert dies etwa 1,5 Stunden - Das Vergleichsbild in W16A16 wurde mit demselben Latent auf einem PC erzeugt
Unterstützung für Stable Diffusion XL 1.0 Base
- Das Stable-Diffusion-Beispiel von OnnxStream unterstützt SDXL 1.0 Base, aber nicht den Refiner
- Die ONNX-Dateien wurden aus der SDXL-1.0-Implementierung der Hugging Face-Diffusers-Bibliothek exportiert; verwendet wurde Version 0.19.3
- SDXL 1.0 verursacht deutlich höhere Rechenkosten als SD 1.5
- Der größte Unterschied ist, dass statt 512x512 Bilder mit 1024x1024 erzeugt werden können
- Auf einem 12-Core-PC mit 32 GB RAM dauert die Erzeugung eines Bilds mit 10 Schritten in Hugging Face Diffusers 26 Minuten
- Der allgemein empfohlene minimale VRAM-Bedarf für SDXL liegt bei 12 GB
- OnnxStream kann SDXL 1.0 mit weniger als 300 MB RAM ausführen und läuft damit auch auf dem Raspberry Pi Zero 2
- kein zusätzlicher Swap
- keine Schreibzugriffe auf die Festplatte während der Inferenz
- die Erzeugung eines Bilds mit 10 Schritten dauert auf dem Raspberry Pi Zero 2 etwa 11 Stunden
Speicherspezifische Optimierungen für SDXL
- Auf SDXL 1.0 wird derselbe Satz an Optimierungen wie bei SD 1.5 angewendet, mit einigen Unterschieden
- Das UNET-Modell verwendet auf dem Raspberry Pi Zero 2 eine dynamische UINT8-Quantisierung, um mit weniger als 300 MB RAM ausgeführt zu werden
- Die Quantisierung ist auf bestimmte Teilmengen großer Zwischentensoren beschränkt
- Der VAE-Decoder von SDXL 1.0 ist schwieriger zu handhaben als der von SD 1.5
- Der VAE-Decoder von SDXL 1.0 ist viermal größer als der VAE-Decoder von SD 1.5
- In OnnxStream benötigt er bei FP32-Präzision 4,4 GB RAM
- Bei SD 1.5 konnte der VAE-Decoder durch statische UINT8-Quantisierung auf 260 MB RAM reduziert werden
- Beim VAE-Decoder von SDXL 1.0 kommt es bei FP16-Arithmetik zu Überläufen, und wegen des großen Aktivierungswertebereichs ist es schwierig, mit UINT8-Quantisierung Bilder in guter Qualität zu erhalten
- Es gibt FP16-Lösungen wie sdxl-vae-fp16-fix, aber selbst halbierter Speicherbedarf bedeutet immer noch 2,2 GB, was für den Raspberry Pi Zero 2 zu viel ist
- Die endgültige Lösung war Tile-Decoding, inspiriert von der VAE-Decoder-Implementierung in Hugging Face Diffusers
- Der resultierende Diffusion-Tensor hat die Shape
(1,4,128,128) - Dieser wird in 5x5, insgesamt 25 überlappende Tensoren mit der Shape
(1,4,32,32)aufgeteilt - Jedes Tile überlappt das linke und obere Tile um 25 %
- Das Decoding-Ergebnis jedes Tiles ist ein Tensor der Shape
(1,3,256,256)und wird in das finale Bild eingeblendet - Ohne Blending sind Tile-Grenzen sichtbar, mit Blending sind sie im Endergebnis nicht sichtbar
- Der resultierende Diffusion-Tensor hat die Shape
- Mit diesem Verfahren sinkt der RAM-Verbrauch des SDXL-VAE-Decoders von 4,4 GB auf 298 MB
Unterstützte Funktionen und Abhängigkeiten
- OnnxStream bündelt die für speichersparende Inferenz nötigen Funktionen in kleinem Umfang
- Trennung von Inferenz-Engine und
WeightsProvider DiskNoCache,DiskPrefetch, benutzerdefinierterWeightsProvider- attention slicing
- dynamische 8-Bit-Unsigned-Asymmetric-Percentile-Quantisierung
- statische W8A8-Unsigned-Asymmetric-Percentile-Quantisierung
- Kalibrierung quantisierter Modelle
- FP16-Unterstützung
- Implementierung von 25 häufig genutzten ONNX-Operatoren
- Trennung von Inferenz-Engine und
- Operationen werden sequenziell ausgeführt, aber jeder Operator arbeitet multithreaded
- Die Implementierung besteht aus einer einzelnen Implementierungsdatei und einer Header-Datei; die Klasse
XnnPackkapselt die Aufrufe an XNNPACK - Einige beschleunigte Primitive hängen von XNNPACK ab
- MatMul
- Convolution
- element-wise Add/Sub/Mul/Div
- Sigmoid
- Softmax
Leistungsvergleich und Einschränkungen
- Stable Diffusion besteht aus drei Modellen
- text encoder: 672 Operationen, 123 Millionen Parameter
- UNET: 2050 Operationen, 854 Millionen Parameter
- VAE decoder: 276 Operationen, 49 Millionen Parameter
- Für die Erzeugung eines Bilds mit 10 Schritten bei Batch-Größe 1 sind folgende Ausführungen nötig
- text encoder 2-mal
- UNET 20-mal
- VAE decoder 1-mal
- Auf Basis eines FP16-UNET gibt es große Unterschiede bei Speicher und Zeit zwischen OnnxStream und OnnxRuntime
- OnnxStream: etwa 0,133 GB, 18,2 bis 19,8 Sekunden
- OnnxRuntime: 5,085 bis 7,353 GB, 7,28 bis 12,8 Sekunden
- OnnxStream benötigt bis zu 55-mal weniger Speicher, ist aber 0,5- bis 2-mal langsamer
- Beim FP32-text encoder nutzt OnnxStream 0,147 GB, OnnxRuntime 0,641 GB
- Beim FP32-VAE decoder nutzt OnnxStream 1,004 GB, OnnxRuntime 1,330 bis 2,026 GB
- Für die Vergleichsergebnisse gelten einige Bedingungen
- Der erste Lauf von OnnxRuntime ist eine Warm-up-Inferenz vor der Wiederverwendung von
InferenceSession - OnnxStream hat wegen seines eager Designs kein Warm-up-Konzept, kann aber bei späteren Läufen vom OS-Dateicache für Gewichte profitieren
- OnnxStream unterstützt derzeit keine Eingaben mit anderer Batch-Größe als 1
- OnnxRuntime kann beim UNET-Lauf Batch-Größe 2 verwenden und dadurch die gesamte Diffusion deutlich beschleunigen
- Änderungen an
SessionOptionsvon OnnxRuntime wieEnableCpuMemArenaundExecutionModeführten im Test zu keinem signifikanten Unterschied - NCNN war bei Speicherverbrauch und Inferenzzeit OnnxRuntime sehr ähnlich
- Die Testumgebung war Windows Server 2019, 16 GB RAM, 8750H-CPU mit AVX2, 970 EVO Plus SSD, VMWare mit 8 virtuellen Kernen
- Der erste Lauf von OnnxRuntime ist eine Warm-up-Inferenz vor der Wiederverwendung von
Attention Slicing und Quantisierung
- Beim UNET-Lauf waren attention slicing und die W8A8-Quantisierung des VAE-Decoders entscheidend, um den Speicherbedarf auf ein auf dem Raspberry Pi Zero 2 lauffähiges Niveau zu senken
- Attention slicing sorgt bei der Berechnung von scaled dot-product attention in Multi-Head-Attention dafür, dass nicht die gesamte
Q @ K^T-Matrix materialisiert wird - Bei einer Attention-Head-Anzahl von 8 im UNET-Modell haben die Tensoren folgende Shapes
Q:(8,4096,40)K^T:(8,40,4096)- Ergebnis der ersten MatMul:
(8,4096,4096) - In FP32-Präzision ergibt das einen 512-MB-Tensor
- Die Lösung besteht darin,
Qvertikal aufzuteilen und die Attention-Berechnung für jeden Chunk separat auszuführenQ_slicedhat die Shape(1,x,40)xist 4096 geteilt durchonnxstream::Model::m_attention_fused_ops_parts- Der Standardwert ist 2 und kann angepasst werden
- Dadurch sinkt der gesamte Speicherverbrauch des FP32-UNET-Modells von 1,1 GB auf 300 MB
- FlashAttention könnte eine effizientere Alternative sein, würde aber benutzerdefinierte Kernel für unterstützte Architekturen wie AVX oder NEON erfordern und damit XNNPACK umgehen
Modellkonvertierung und Ausführungsweise
- OnnxStream führt das in
path_to_model_folder/model.txtdefinierte Modell aus- Alle Modelloperationen stehen im ASCII-Format in
model.txt - Die Gewichtsdateien müssen als Serie von
.bin-Dateien im selben Ordner vorhanden sein
- Alle Modelloperationen stehen im ASCII-Format in
- Im Objekt
Modellassen sich mehrere optionale Parameter setzen- anderen Weights Provider angeben
- Datei für Activation-Clipping-Bereiche quantisierter Modelle lesen oder schreiben
- Modellkalibrierungsmodus
- FP16-Arithmetik verwenden
- UINT8-Arithmetik verwenden
- dynamische UINT8-Quantisierung verwenden
- attention slicing aktivieren
- Anzahl der Attention-Teile festlegen
- Um ONNX-Dateien in OnnxStream zu verwenden, werden mit dem Notebook
onnx2txt.ipynbmodel.txtund.bin-Gewichtsdateien exportiert - Beim Export eines PyTorch-
nn.Modulenach ONNX gibt es einige Bedingungen- Beim Aufruf von
torch.onnx.exportmussdynamic_axesleer bleiben - OnnxStream unterstützt keine Eingaben mit dynamischen Shapes
- Vor der Konvertierung wird die Ausführung von ONNX Simplifier dringend empfohlen
- Beim Aufruf von
Build und Vorbereitung der Ausführung
- Das Stable-Diffusion-Beispiel kann unter Linux, Mac, Windows und Termux gebaut werden
- Unter Windows wird die
x64 Native Tools Command Promptaus denVisual Studio Toolsverwendet - Unter Mac ist die Installation von cmake per
brew install cmakenötig
- Unter Windows wird die
- Zuerst muss XNNPACK gebaut werden
- Da sich Funktionsprototypen von XNNPACK jederzeit ändern können, gehört auch das Auschecken eines Commits aus einem passenden Zeitpunkt zum Verfahren
- Referenz ist ein Master-Commit vor
2023-06-27 00:00
- Anschließend wird das OnnxStream-Repository geklont und in
src/buildmit cmake gebautMAX_SPEED=ON- den Pfad des XNNPACK-Klons mit
XNNPACK_DIRangeben
- Die Option
MAX_SPEEDkann die Leistung erhöhen, benötigt beim Build aber mehr Speicher- unter Windows etwa 10 % Leistungsgewinn
- auf dem Raspberry Pi mehr als 50 % Leistungsgewinn
- die erzeugte ausführbare Datei funktioniert möglicherweise nicht; bei Tests unter Termux traten Probleme auf
- bei Problemen wird empfohlen, zuerst
MAX_SPEED=OFFzu setzen
- Die Gewichte für das Stable-Diffusion-1.5-Beispiel können aus den Releases des Repositories heruntergeladen werden und sind etwa 2 GB groß
- Die Gewichte für Stable Diffusion XL 1.0 Base können von Hugging Face heruntergeladen werden und sind etwa 8 GB groß
Optionen des Stable-Diffusion-Beispiels
- Die Beispiel-Binärdatei steuert Modellauswahl, Ein-/Ausgabe, Prompt und Decoding-Verfahren über Kommandozeilenoptionen
--xl: führt Stable Diffusion XL 1.0 statt Stable Diffusion 1.5 aus--models-path: gibt den Ordner des Stable-Diffusion-Modells an--ops-printf: gibt während der Inferenz die aktuelle Operation auf stdout aus--output: gibt die Ausgabe-PNG-Datei an--decode-latents: überspringt die Diffusion und decodiert die angegebene Latents-Datei--prompt: gibt den positiven Prompt an--neg-prompt: gibt den negativen Prompt an--steps: gibt die Anzahl der Diffusionsschritte an--save-latents: speichert die Latents nach der Diffusion in der angegebenen Datei
- Es gibt außerdem separate Optionen für Raspberry Pi und Decoder
--decoder-calibrate: kalibriert den quantisierten VAE-Decoder nur für SD 1.5--decoder-fp16: verwendet den FP16-VAE-Decoder nur für SD 1.5--not-tiled: verwendet den tiled VAE-Decoder nur für SDXL 1.0 nicht--rpi: konfiguriert das Modell für die Ausführung auf dem Raspberry Pi--rpi-lowmem: wendet die Low-Memory-Konfiguration für den Raspberry Pi Zero 2 nur bei SDXL 1.0 an
1 Kommentare
Meinungen auf Hacker News
Interessant. Der Kernsatz ist dieser: „OnnxStream kann bis zu 55-mal weniger Speicher verbrauchen als OnnxRuntime und dabei nur 0,5- bis 2-mal langsamer sein“
Der Kompromiss zwischen Videospeicher-/Speichernutzung und Inferenzzeit scheint nicht nur in speicherbeschränkten Fällen wie beim Raspberry Pi vorteilhaft zu sein, sondern auch in anderen Situationen.
Wenn sich mit diesem Weight-Unloading-Verfahren tatsächlich größere Batch-Größen im selben Speicher verarbeiten lassen, frage ich mich, ob man trotz höherer Latenz den Durchsatz deutlich steigern könnte.
Vermutlich war „1,5- bis 2-mal langsamer“ gemeint.
Inferenz ist normalerweise speicherbandbreitengebunden, sobald man über die Frage „passt dieses Modell in dieses System?“ hinaus ist. Daher glaube ich nicht, dass diese Technik viel dabei hilft, den Durchsatz durch größere Batch-Größen zu erhöhen. Schon eine einzelne Instanz sättigt vermutlich bereits den Speichercontroller.
Beim Training könnte es allerdings hilfreich sein.
11 Stunden – das erinnert mich an die Zeiten, als man auf dem Amiga 500 Raytracing gemacht hat. Der „finale“ Render war definitiv etwas, das über Nacht lief.
Aber auch wenn Caustics hell wirken, sind sie statistisch ziemlich selten; wenn man sie wirklich sauber bekommen will, muss man die Beschränkungen der Render-Engine aufheben und sie einfach über Nacht laufen lassen.
Das Ergebnis ist ein einzelnes Bild einer mittelmäßigen Szene eines untalentierten Künstlers, aber mit schönen Caustics. Ich bleibe wohl bei meinem Hauptberuf.
Ungefähr da dachte ich, dass ich ein anderes Hobby brauche. Kurz zuvor hatte irgendein brillanter Mensch ein Tool veröffentlicht, mit dem man die Szene zuerst per OpenGL ansehen konnte. Auf dem Amiga wäre das wohl nicht gegangen, aber auf meiner Maschine lief es gerade so.
Ich nutze Stable Diffusion auf einem MBP mit invoke.ai. Gibt es Empfehlungen, wie man SD-Parameter besser einstellt? Selbst mit demselben Prompt und scheinbar denselben Einstellungen, etwa demselben Modell wie Euler A, komme ich bei weitem nicht an die Bildqualität heran, die man im Internet sieht.
Für normales SD 1.5 nutze ich Volta, weil es schnell ist: https://github.com/VoltaML/voltaML-fast-stable-diffusion/com...
Wirklich gute SD-1.5-Bildqualität bekommt man erst, wenn man feinabgestimmte Modelle, LoRA, ControlNet und andere Erweiterungen großzügig einsetzt. Zum Beispiel, indem man ein Ausgangsbild als Strukturvorgabe nachzeichnen lässt oder Prompts für bestimmte Bereiche eines Bildes festlegt. InvokeAI hat tatsächlich auch viele Funktionen, und viele dieser Erweiterungen sind in der Node-UI versteckt, aber andere UIs wie Volta legen sie direkter offen.
Im Allgemeinen sind beeindruckende feinabgestimmte Modelle deutlich weniger universell einsetzbar als die Basisgewichte, aber in der Praxis ist das oft kein großes Problem, und die Ergebnisse können erheblich besser werden.
Es braucht mehr Zeit und Speicher als Invoke oder eine Nvidia-Grafikkarte, ist aber nicht allzu schlecht. Ein Standard-Qualitätsbild mit 512x768 px liegt bei 1–2 s/it, ein hochwertiges 1024x1536-px-Bild mit Hires Fix bei etwa 14–20 s/it.
Es wäre wirklich cool, das in einen digitalen Bilderrahmen oder ein Wandbild einzubauen und dort laufen zu lassen.
https://github.com/rvdveen/epaper-slow-generative-art/
Eine erstaunliche Leistung, aber natürlich dauert es extrem lange, ein Bild zu erzeugen. Im README steht 11 Stunden
Zum Beispiel: 100 Pi Zero 2 – es müssen nicht einmal W-Modelle sein – erzeugen 100 Bilder, verglichen nach Zeit, Kosten, der gesamten benötigten Hardware und Strom mit so etwas wie einem durchschnittlichen Mittelklasse-PC
Vermutlich würde der PC immer noch gewinnen
Der Zero 2 wirkt eher wie eine Challenge als wie etwas Praktisches; ein Pi 4 oder 5 wäre vielleicht ein besserer Bezugspunkt
Beeindruckend
Wahrlich, die Zeit ist nahe, in der selbst Lampen und Toaster über herausragende Intelligenz verfügen werden
Ich verfolge dieses Feld seit vielen Jahren, aber die letzten 10 Jahre waren erstaunlich
Ich sage „waren“, weil die Beschleunigung der letzten 6 bis 18 Monate noch einmal auf einem völlig anderen Niveau liegt
Ich mache mir nicht Sorgen darüber, was in zwei Jahren möglich sein wird, sondern darüber, wie viel schneller die Entwicklung noch werden wird. Und dann wieder schneller. Und wieder
Lass uns ein Startup gründen
Ist an diesem Punkt nicht der Versuch, Stable-Diffusion-artige Technologien zu regulieren, erledigt? Wenn man Modell und Inferenz-Infrastruktur so weit schrumpfen kann, dass es auf einer PS2 laufen könnte, scheint es unmöglich, diese Technologie ohne einen totalitären Überwachungsstaat aufzuhalten – und selbst in einem solchen Staat wäre es wohl nur gerade so möglich
Dasselbe gilt für Medien-„Piraterie“ oder Ransomware
Staaten regulieren schon lange Dinge, die sich rein technisch nicht durchsetzen lassen
Ich weiß, dass das als witzige Übertreibung gemeint ist, aber für eine PS2 müsste man es noch viel weiter schrumpfen
Ich kann den Tag kaum erwarten, an dem Stable Diffusion für Windows 3.1 erscheint
Wahnsinn. Ob es nun 11 Stunden dauert oder nicht – ich hätte nie erwartet, dass SD auf Hardware wie einem Pi Zero laufen kann