- lm.rs ist ein Projekt, das lokale Sprachmodell-Inferenz auf der CPU in Rust ausführt und eine minimale Code-Implementierung anstrebt, die die gesamte Inferenz ohne ML-Bibliotheken erledigt
- Inspiriert von Karpathys llama2.c und llm.c; anfangs wurde nur Google Gemma 2 unterstützt, inzwischen wurde die Unterstützung auf Llama 3.2 und Bildeingaben mit PHI-3.5 erweitert
- Mit der neuesten Änderung wurde Batch-Verarbeitung implementiert, wodurch die Bildkodierung bis zu etwa 3-mal schneller wurde; Llama 3.2 1B läuft auf der 16-Core-Maschine des Autors mit 50 tok/s
- Vorbereitete Modelle sind auf Hugging Face verfügbar; das README empfiehlt Q8_0 und weist darauf hin, dass die Q4_0-Quantisierung noch verbessert wird
- Nutzer können Modelle und Tokenizer im LMRS-Format herunterladen und direkt bauen oder die Originalmodelldateien von Hugging Face mit export.py und tokenizer.py konvertieren und ausführen
Was lm.rs erreichen will
- lm.rs ist eine in Rust geschriebene Implementierung für lokale, CPU-basierte Sprachmodell-Inferenz
- Ziel ist eine minimale Code-Implementierung, die die vollständige Inferenz eines Sprachmodells auf der CPU ohne ML-Bibliotheken ausführt
- Inspiriert von Karpathys llama2.c und llm.c
- Das README sagt, dass der aktuelle Code „nicht ganz so minimal“ sei und dass es bei Teilen des Codes noch Raum für Optimierung und Verbesserung gebe
- Das Projekt war für den Autor auch ein Anlass, Rust erstmals auszuprobieren
Unterstützte Modelle und multimodale Erweiterung
- Anfangs wurde nur das Google-Modell Gemma 2 unterstützt, später kam Unterstützung für Llama 3.2 hinzu
- Kürzlich wurde über PHI-3.5 die Option ergänzt, Bilder zu verwenden
- Aktuell hervorgehobene Unterstützungen
- Multimodale Unterstützung über das Modell PHI-3.5-vision
- Unterstützung für das reine Textmodell PHI-3.5-mini
- Zugehörige Ressourcen
Performance und vorbereitete Modelle
- Als neueste Meldung wurde Batch-Verarbeitung implementiert, wodurch die Bildkodierung bis zu etwa 3-mal schneller wurde
- Llama 3.2 1B läuft auf der 16-Core-Maschine des Autors mit 50 tok/s
- Vorbereitete Modelle und Tokenizer sind auf Hugging Face verfügbar
- Die Geschwindigkeitsmessungen wurden auf einem 16-Core AMD Epyc durchgeführt
- Das README empfiehlt Q8_0 und weist darauf hin, dass die Q4_0-Quantisierung noch verbessert wird
-
Tabelle vorbereiteter Modelle
- Gemma 2 2B IT Q4_0: 1.39G, 20 tok/s
- Gemma 2 2B IT Q8_0: 2.66GB, 24 tok/s
- Gemma 2 9B IT Q4_0: 4.91GB, 7 tok/s
- Gemma 2 9B IT Q8_0: 9.53GB, 8 tok/s
- Llama 3.2 1B IT: 4.94GB, 21 tok/s
- Llama 3.2 1B IT Q8_0: 1.27GB, 50 tok/s
- Llama 3.2 3B IT Q4_0: 1.71GB, 17 tok/s
- Llama 3.2 3B IT Q8_0: 3.31GB, 19 tok/s
- PHI 3.5 IT Vision Q8_0: 4.28GB, 17 tok/s
- PHI 3.5 IT Mini Q8_0: 3.94GB, 18 tok/s
Ablauf der Modellkonvertierung
- Wenn man vorbereitete quantisierte Modelle und Tokenizer von Hugging Face bezieht, kann der Konvertierungsschritt übersprungen werden
- Wer die von Google oder Meta auf Hugging Face veröffentlichten Modelle selbst konvertieren will, muss zusätzliche Python-Abhängigkeiten installieren
pip install -r requirements.txt
- Verwendet werden die von der Originalmodellseite heruntergeladenen Dateien .safetensors und config.json
- Für multimodale Modelle wie PHI3.5 Vision wird außerdem die CLIP-config-Datei benötigt
export.py konvertiert bfloat16-Gewichte in das LMRS-Format
python export.py --files [ordered .safetensor files] --config [model config.json] --save-path [name and path to save] --type [model type (GEMMA/LLAMA/PHI)]
- Um eine quantisierte Version zu exportieren, nutzt man die Flags --quantize und --quantize-type
- Die Größe eines int8-quantisierten Modells kann je nach Gruppengröße von etwa 9,8G auf etwa 2,5G sinken
- Bei multimodalen Modellen muss das Argument --vision-config enthalten sein
tokenizer.py konvertiert ein Tokenizer-Modell in das LMRS-Tokenizer-Format
python tokenizer.py --model-id [huggingface model_id] --tokenizer-type [type of the tokenizer (GEMMA/LLAMA/PHI)]
Build und Ausführung
- Der Rust-Code wird mit
cargo kompiliert; das README nennt ausdrücklich die Übergabe des target-cpu-Flags
RUSTFLAGS="-C target-cpu=native" cargo build --release --bin chat
- Um multimodale Funktionen zu aktivieren, fügt man das Argument --features multimodal hinzu
- Die Standardausführung erfolgt durch Angabe der Modellgewichtungsdatei
./target/release/chat --model [model weights file]
- Als zusätzliche Argumente können tokenizer, temperature, top-p, show-metrics und weitere verwendet werden
- Verfügbare Argumente lassen sich mit --help prüfen
- Bei multimodalen Modellen wird der Bildpfad mit dem Argument --image angegeben
- Bei Verwendung von PHI3.5-vision empfiehlt das README temperature 0
WebUI-Backend ausführen
- Um das Backend für die WebUI auszuführen, kompiliert man mit der backend-Funktion
RUSTFLAGS="-C target-cpu=native" cargo build --release --features backend --bin backend
- Für das multimodale Backend wird die Funktion backend-multimodal aktiviert
- Das Backend wird durch Angabe der Modellgewichtungsdatei ausgeführt
./target/release/backend --model [model weights file]
- IP und Port können mit --ip und --port geändert werden
- Zusätzliche Flags wie temperature können ebenfalls verwendet werden
- Für multimodale Kompatibilität nutzt man das Flag --multimodal
- Nach dem Start kann man sich mit dem Webinterface verbinden
TODO-Status und Lizenz
- Abgeschlossene Punkte
- Weitere Sampling-Methoden hinzugefügt
- Von den Testpunkten für 9B- und 27B-Modelle wurde der 9B-Test abgeschlossen; 27B ist als vermutlich zu langsam markiert
- Parallelisierung der Multi-Head-Attention-Schleife
- Performance-Metriken hinzugefügt
- Unterstützung für int8- und int4-Quantisierung
- Verbleibender Punkt
- Funktion zur Bereitstellung eines System-Prompts
- Die Lizenz ist MIT
1 Kommentare
Hacker-News-Kommentare
Auf einem M2 64GB MacBook lief die 1,2-GB-Datei
llama3.2-1b-it-q80.lmrsgefühlt ziemlich schnell; laut Activity Monitor wurden dabei 13 Threads und 1000 % CPU genutztlm.rswurde nach/tmpgeklont und mitRUSTFLAGS="-C target-cpu=native" cargo build --release --bin chatgebaut; danach wurdentokenizer.binundllama3.2-1b-it-q80.lmrsvon Hugging Face geladen und mit./target/release/chat --model llama3.2-1b-it-q80.lmrsausgeführt./target/release/chat --model llama3.2-1b-it-q80.lmrs --show-metricsprüfen könne, wie viele Tokens pro Sekunde herauskommenAus Formatgründen blieb nur ein Teil erhalten, aber es war im Wesentlichen eine lange Folge zufälliger Wörter
Der Artikel ist sehr gut geschrieben, und Teile des Source Codes könnten sich im Unterricht verwenden lassen, um zu erklären, wie Transformer tatsächlich funktionieren
Im Vergleich zu Diagrammen von Attention Heads ist der Code konkreter und detaillierter. Allerdings könnte eine Library, die direkt nach
stdoutschreibt, die Ausgabe von Anwendungen stören, etwa wenn ein Texteditor Stilprüfungen anzeigt; besser wäre es wohl, in den String-Buffer einer Logging-Instanz zu schreiben, die mit demlm.rs-Objekt verbunden istAußerdem scheint der Model Reader an einer Stelle
unsafezu verwenden, um Daten-Alignment zu erzwingen; es würde interessieren, ob das auch ohneunsafevernünftig machbar istDann könnte man so etwas wie Log-Ausgaben auch in einer GUI anzeigen
Es wurden bereits einige Werkzeuge in Rust für Model Loading und verschiedene LLM-Aufgaben gebaut
Dazu gehören das automatische Auswählen des größten quantisierten Modells abhängig vom verfügbaren Speicher, das Extrahieren des Tokenizers aus
ggufund das Einfügen von Prompts. Damit ließen sich wohl einige Python-Abhängigkeiten loswerdenDerzeit unterstützt es
llama.cpp, aber auch das ist ziemlich interessant. Es würde interessieren, ob eine Unterstützung für Grammar Constraints geplant isthttps://github.com/ShelbyJenkins/llm_client
Die Formulierung no dependency im Titel ist nicht besonders klar
Auf den ersten Blick könnte man an
no_stddenken, aber tatsächlich ist es nichtno_std, und es scheint auch einige Abhängigkeiten zu geben. Gemeint sein könnte höchstens, dass es ausschließlich Rust-Abhängigkeiten sindGanz transparent gesagt gibt es 5 grundlegende Rust-Abhängigkeiten; davon sollten
chronoundclapper Feature-Flag nur für die Chat-Funktion aktiviert werden. Die übrigen 3 sind Utility-Crates, um etwas mehr Hardware-Leistung herauszuholen:rayonfür einfachere Parallelisierung,widezur Unterstützung von SIMD undmemmap2für Memory-Mapping der Model-Dateienrequirements.txtPyTorch und mehrere Python-Abhängigkeiten benötigt, und das ist auf der Seite auch die einzige Stelle, an der überhaupt das Wort „dependency“ auftaucht; deshalb ist die Titelwahl ziemlich verwirrendDas Projekt selbst scheint einfach den Untertitel „Minimal LLM inference in Rust“ zu verwenden. Im Git-Verlauf sieht es so aus, als sei der Account, der den Beitrag gepostet hat, ein Contributor, aber nicht der Hauptautor; daher wäre eine Erklärung hilfreich, was zero dependencies hier genau bedeutet
Leider löscht HN in Titeln oft Wörter ohne besonderen Grund oder erkennbare Transparenz
cargoinzwischen fast wienpmgeworden istBei 16 Abhängigkeiten ist schwer nachzuvollziehen, wie man von keinen Abhängigkeiten sprechen kann
Jemand hatte früher etwas Ähnliches gebaut, war aber von der Performance im Vergleich zu C/C++-Code auf der CPU enttäuscht
Das heißt auch, dass man wohl nicht wirklich wusste, wie man Rust schnell macht. Benchmarks verschiedener Rust-Implementierungen wären interessant
Es sieht so aus, als könnte die Implementierung von LLM-Inferenz für ernsthafte Programmierer das neue „Hello, world!“ werden
https://github.com/gip/yllama.rs
https://github.com/crabml/crabml
Einige SIMD-Instruktionen wurden direkt verwendet, und leistungsmäßig schien es mit
llama.cppmithalten zu können. Entscheidend sind wohl der Einsatz von SIMD bei quantisierter Matrixmultiplikation und die Verwendung einer Busy-Wait-Schleife statt einer Condition Variable bei der Aufteilung der Arbeit zwischen ThreadsAllerdings fehlte die Zeit, weiter an Inferenz für quantisierte Modelle mit Vulkan auf der GPU zu arbeiten, daher gab es seit einiger Zeit keine Updates mehr
Interessant ist, dass bereits Dioxus verwendet wird, und ich frage mich, ob WASM ebenfalls in die Roadmap aufgenommen werden könnte.
Wenn sich ein leichtgewichtiges LLM wie RWKV im Browser ausführen ließe, könnte der Browser ganz neue Funktionskategorien eröffnen, ohne eine SaaS-API aufzurufen.
https://github.com/maedoc/rwkv.js
Ich habe mit Emscripten kompiliertes
Rwkv.cppverwendet, aber den Tokenizer-Teil noch nicht richtig gelöst. Trotzdem scheint 1.6B RWKV6 für einen rein offline genutzten Browser durchaus brauchbar zu sein.Für normales Chatten reicht die Leistung nicht aus, aber für Anwendungen wie RAG könnte es durchaus genügen.
Die notwendigen Abhängigkeiten
rayonundwideunterstützen WASM direkt, und wenn man den TypMmapintransformer.rsdurch&[u8]ersetzt, könnte auchmemmap2entfernt werden.RWKV hat allerdings eine völlig andere Architektur, daher müsste alles neu implementiert werden, und es wirkt sehr unwahrscheinlich, dass das in die Roadmap aufgenommen wird.
Ich frage mich, ob diese Implementierungen alle auf CPU beschränkt sind.
Es wird gefragt, ob man sich besser nach einer anderen Alternative umsehen sollte, wenn man eine gute GPU hat.
Wenn du ein Rust-Framework mit GPU-Unterstützung ausprobieren möchtest, lohnt sich ein Blick auf Candle https://github.com/huggingface/candle/tree/main.
Wenn das Ziel der tatsächliche Einsatz ist, wäre selbst bei ausschließlicher CPU-Nutzung eher eine Alternative wie
llama.cppsinnvoll. Dieses Projekt ist eher Lehrmaterial, das zeigt, wie es intern funktioniert, wenn man die komplexen Schichten des Ökosystems entfernt.LLMs wirken in ihrer Wirkung fast magisch, sind aus Codesicht aber ziemlich einfach.
Auf Rust-Seite gibt es
llama.cpp-Wrapper wie meinllm_client, außerdem Candle-basierte Projekte wiemistral.rsund Kalosm.Mein Projekt soll ebenfalls eine
mistral.rs-Implementierung anbieten, aber der vollständige Umstieg vonllama.cppist noch nicht geschafft. Eine vollständige Rust-Implementierung hat große Vorteile, etwa schnellere Installationszeiten. Mein aktuelles Crate muss allerdings geklont und gebaut werden; zwar ist das für macOS, Windows und Linux automatisiert, aber die Build-Zeit verlängert sich um etwa eine Minute.Eine RTX 3090 hat zum Beispiel fast 1 TB/s Speicherbandbreite. Um das zu erreichen, bräuchte man mindestens 12 Kanäle DDR5, selbst mit der schnellsten Proof-of-Concept-Konfiguration der Welt.
Wenn man eine dedizierte GPU hat, ist es eine völlig andere Welt, eine Implementierung zu verwenden, die diese nutzt. Dass die LLM-Inferenzwerte von Apple Silicon so beeindruckend sind, liegt auch an der integrierten CPU-GPU-Architektur mit hoher Speicherbandbreite; soweit ich mich erinnere, waren es rund 400 GB/s.
Selbst eine 4090 hat aus LLM-Sicht nicht besonders viel Speicher. Eine GPU wird zwar schneller sein, aber bei großen Modellen reicht der Speicher womöglich nicht aus.
Ich frage mich, welchen Wert das im Vergleich zu
llama.cpphat.Stark, und Glückwunsch zur ersten Rust-Bibliothek, aber für ernsthafte lokale Nutzung sind Metal/CUDA-Unterstützung unverzichtbar.
Ich bin zwar nicht der Hauptautor, experimentiere aber als Mitwirkender mit
wgpu, um ein gewisses Maß an GPU-Beschleunigung zu erreichen. Der Hauptautor möchte die Komplexität begrenzen, daher weiß ich nicht, wie weit das am Ende tatsächlich gehen wird.Die Leidenschaft der Rust-Community, fast alles neu zu schreiben, ist interessant und wirkt auf mich positiv