Ternlight – 7-MB-Embedding-Modell, das im Browser (WASM) läuft
(ternlight-demo.vercel.app)- Führt Text-Embeddings und Ähnlichkeitssuche direkt im Browser ohne Serveraufruf aus und ermöglicht so den schnellen Aufbau semantischer Suche
- Läuft nur auf der CPU ohne GPU; das Basispaket ist inklusive Engine und Gewichten 7 MB groß, mini 5 MB
- Integriert Engine, Modell und BERT-Tokenizer in einer einzigen
.wasm-Datei; kein postinstall und kein Runtime-Fetch nötig - Wendet beim Modell
all-MiniLM-L6quantization-aware training (QAT) im BitNet-b1.58-Stil an und distilliert es - Nach der Installation von
@ternlight/baselassen sichembedundsimilarimportieren, um mit 3 Zeilen Code einen Semantic-Search-Flow aufzubauen - Erreicht 30× Kompression bei nur geringem Genauigkeitsverlust und ermöglicht Privacy-, Offline- und Edge-Suche ohne Netzwerk-Roundtrip
ternlight – Embeddings komplett im Browser
- Ein Semantic-Embedding-Tool als 5–7-MB-WebAssembly-Bundle, das Engine + Modell + Tokenizer in einer Datei bündelt und Embedding-Suche auf der CPU ausführt
- Embeddet Text in Millisekunden und ruft keinen Server auf
- Bietet zwei Tiers mit derselben API; die Auswahl erfolgt je nach Trade-off zwischen Größe und Qualität
@ternlight/base— Qualitäts-Tier (7 MB wire, ca. 5 ms pro Embed)@ternlight/mini— kleines, schnelles Tier (5 MB wire, ca. 2,5 ms pro Embed)
Kern-API
- Ein Primitive: String → gibt ein 384-dimensionales L2-normalisiertes Float32Array zurück
- Bietet die drei Funktionen
embed,cosineSim,similarcosineSim(embed('reset my password'), embed('I forgot my password'))→ 0.88similarunterstützt Nearest-Neighbor-Suche über einen Korpus;topKkann angegeben werden- Beispiel: Bei der Suche nach „I want my money back“ werden „Refunds: how to get your money back“ (sim 0.70) und „Update your billing address“ (sim 0.24) zurückgegeben
- Läuft unter Node ≥ 18, im Browser (über Bundler), Cloudflare Workers, Vercel Edge, Deno und Bun und routet automatisch auf den passenden Loader für die jeweilige Umgebung
Designprinzipien
- Aus
all-MiniLM-L6distilliert und mit quantization-aware training im Stil von BitNet b1.58 versehen; erreicht durch drei Designentscheidungen eine Größe von wenigen MB -
Ternäre Gewichte (Ternary weights)
- Alle Gewichte sind entweder
-1,0oder+1, sodass Inferenz über Addition und Subtraktion abgewickelt wird - Das Modell wurde von Anfang an als ternäres Modell trainiert, um die Qualität zu erhalten
- Alle Gewichte sind entweder
-
Ein einzelnes Bundle (One bundle)
- Integriert Modell + BERT-Tokenizer + Engine in einer einzigen
.wasm-Datei - Kein postinstall-Schritt und kein Runtime-Fetch
- Integriert Modell + BERT-Tokenizer + Engine in einer einzigen
-
SIMD-Inferenz-Engine
- Handgeschriebenes Rust, kompiliert zu WASM SIMD
- Additions-/Subtraktionsoperationen nutzen CPU-Vektorbefehle
Leistungskennzahlen
- Alle Werte wurden auf Basis des veröffentlichten int4-Builds gemessen (Mac der M-Serie, Node/V8)
-
@ternlight/mini
- Wire size (gzip wasm): 5,0 MB, Latency (p50): 2,5 ms
- Throughput (Single-Thread): ca. 400 emb/s
- Spearman (gegenüber Teacher): 0.820, Retrieval (SciFact NDCG@10): 0.439
- Architektur: 2-layer · d_model=256 · 4 heads, ca. 9,5 Mio. Parameter
-
@ternlight/base
- Wire size (gzip wasm): 7,2 MB, Latency (p50): 5,1 ms
- Throughput (Single-Thread): ca. 195 emb/s
- Spearman (gegenüber Teacher): 0.844, Retrieval (SciFact NDCG@10): 0.465
- Architektur: 2-layer · d_model=384 · 6 heads, ca. 15,4 Mio. Parameter
-
Gemeinsame Spezifikationen
- Ausgabe: 384-dimensional, L2-normalisiert
- Maximale Eingabe: 128 Tokens (ca. 95 Wörter)
- Quantisierung: ternäre Gewichte · int4-Embeddings
Einsatzmöglichkeiten von On-Device-Embeddings
-
Search-as-you-type
- Zeigt Ergebnisse an, bevor Nutzer die Eingabe beendet haben; schneller als jeder Netzwerk-Roundtrip
-
Privacy-sensitive Apps
- Queries und Dokumente verlassen das Gerät nicht; keine Datenverarbeitungsverträge und kein Leckrisiko
-
Offline-first-Apps
- Browser-Erweiterungen, Obsidian-Plugins, Desktop-Apps
-
Apps für Edge-Runtimes
- In Cloudflare Workers, Deno Deploy und Vercel Edge werden Embeddings am selben Ort wie der Request-Handler platziert; ein separater Aufruf eines Inferenzdienstes ist nicht nötig
-
Edge-Geräte und IoT-Hardware
- Raspberry Pi, Single-Board-Computer, industrielle Gateways, Kioske
- Additions-/Subtraktionsoperationen laufen effizient auf ARM-Cores, sodass keine GPU oder NPU nötig ist
-
Statische Websites
- In Jekyll, Hugo und Astro wird das Modell zusammen mit dem Bundle ausgeliefert; semantische Suche funktioniert ohne Backend
Installation und Nutzungsbeispiel
- Wird als einzelnes npm-Paket bereitgestellt und kann ohne separaten Modell-Download-Schritt oder Server genutzt werden
- Der Installationsbefehl lautet:
npm install @ternlight/base embedundsimilarwerden aus@ternlight/baseimportiert, um bedeutungsbasierte Suche auszuführenimport { embed, similar } from '@ternlight/base'; similar('easy weeknight dinner ideas', recipes, { topK: 3 }); // → ranked matches · ~5 ms · zero network
Zugrunde liegende Open-Source-Projekte und Lizenz
- BitNet b1.58 (Ma et al., Microsoft Research, 2024) — Architekturforschung zu ternärem Gewichtetraining
bitlinear— PyTorch-Referenzimplementierung von BitLinear; wird beim Training direkt verwendet (bitlinear==2.4.6), und die Rust-Inferenz-Engine bildet die Forward-Propagation-Operationen unverändert absentence-transformers/all-MiniLM-L6-v2— Teacher-Modell, aus dem das Student-Modell distilliert wurde- Lizenz: MIT
1 Kommentare
Hacker-News-Kommentare
Ich wollte als Hobbyprojekt ein nützliches Modell im Browser laufen lassen, habe daher aus MiniLM einen kleinen Satz-Encoder destilliert und ternary quantization-aware training angewendet.
Die Inferenz-Engine habe ich ebenfalls selbst geschrieben und als Rust → WASM SIMD ausgeliefert.
Es ist kein LLM, sondern ein Embedding-Modell: Gibt man Text hinein, erhält man einen 384-dimensionalen Vektor, und die Textrelevanz wird über die Kosinus-Ähnlichkeit zweier Vektoren bestimmt. Zum Beispiel kommen „reset my password“ und „I forgot my password“ auf etwa 0,88.
Es lässt sich für semantische Suche, FAQ-/Intent-Matching und Clustering nutzen; weil es on-device läuft, ist eine schnelle semantische Suche direkt bei der Eingabe möglich, ohne Abhängigkeit von APIs.
Die Demo durchsucht 2.000 React-Dokumente vollständig on-device: https://ternlight-demo.vercel.app
Auf npm gibt es zwei Stufen: @ternlight/base (7 MB, etwa 5 ms pro Embedding, bessere Embeddings) und @ternlight/mini (5 MB Transfergröße, etwa 2,5 ms pro Embedding), jeweils für Node und Browser gebündelt.
Das Repository enthält technische Details, MIT-Lizenz und die Trainings-Pipeline: https://github.com/soycaporal/ternlight
Mich interessiert, ob On-Device-Embeddings tatsächlich nützlich sind und welche Anwendungsfälle es dafür gibt.
Ich frage mich, ob es helfen könnte, dass ein Nutzer, der „pancake“ eingibt, crêpe findet, ohne dass ich explizit einen Wörterbucheintrag „pancake = crêpe“ anlegen muss.
Wenn ich es richtig verstanden habe, lädt die Library am Anfang einmal 5 MB herunter und lässt sich danach ähnlich nutzen wie derzeit Fuse.js?
Außerdem würde ich gern wissen, wie gut sie andere Sprachen als Englisch verarbeitet und ob man sie mit dem OpenStreetMap-Tag-Wiki trainieren könnte.
Mich würde interessieren, ob es Vergleiche mit anderen winzigen Embedding-Modellen gibt. Es ist schwer einzuschätzen, ob der Startpunkt MiniLM-L6 gewählt wurde, weil es in dieser Größenklasse besonders gut ist; als Metrik wird nur „Retrieval (SciFact NDCG@10)“ angegeben.
Allerdings sehe ich eine deutliche Abweichung von der behaupteten Performance: Auf einem i5-4570 in Firefox komme ich nicht auf 400, sondern nur auf 35 Embeddings pro Sekunde. Ich vermute, dass es auf einen Nicht-SIMD-Pfad zurückfällt, und werde auch das native Rust-Binary ausprobieren.
Cool, aber es wäre gut, auf der Landingpage einen Button zum Starten der Demo zu haben. Ich war ziemlich überrascht, als ich die Webseite geöffnet habe und sofort hörte, wie der Lüfter wie verrückt hochdrehte.
Es wäre schön, das als Astro- oder allgemeines Meta-Framework-Plugin zu bauen, das alle generierten HTML-Dateien automatisch parst und eine kleine Embedding-Datenbank erstellt.
Im Frontend könnte man diese lazy laden, und vielleicht ließe sich auch HNSW in Chunks speichern, sodass nur die für eine Suchanfrage benötigten Teile geladen werden müssen.
Also etwa wie https://pagefind.app/, aber mit vollständig statischer Vektorsuche.
Dass das nach Monaten oder Jahren immer noch so war, hat mich ziemlich enttäuscht, weil es wie ein Hinweis darauf wirkte, dass dem Projekt die Fähigkeit fehlt, es wirklich fertigzustellen. Zumal ich dieses Projekt bei einem Förderprogramm, auf das ich mich beworben hatte, als guten Kandidaten empfohlen hatte; sie wurden ausgewählt, ich nicht.
Falls jemand in diesem Bereich eine gute Lösung kennt oder ich bei SQLite-vec falschliege, würde ich mich über Hinweise freuen. Für unseren SSG haben wir uns im Grunde darauf festgelegt, erst noch ein paar Monate an anderer Infrastruktur zu arbeiten und es dann selbst zu bauen, falls es bis dahin nicht fertig ist.
Das könnte eine ziemlich coole Ergänzung zu einem DuckDB-HNSW-Suchprojekt sein, das ich früher hier gesehen habe: https://github.com/jasonjmcghee/portable-hnsw
Besonders spannend ist, dass die Suche über HTTP Range Requests auf statisch gehosteten Parquet-Dateien läuft.
Ich kann mir vorstellen, dass solche Dinge zu einem relativ offenen, verteilten Such-Ökosystem heranwachsen, das nicht von großen Konzernen kontrolliert wird.
https://news.ycombinator.com/item?id=27016630
Das ist wirklich cool und könnte das fehlende Puzzlestück für etwas sein, das ich früher einmal bauen wollte.
Mit https://github.com/npiesco/absurder-sql könnte man den gesamten Originalkorpus im Browser dauerhaft in IndexedDB/SQLite speichern.
Anschließend könnte man, statt alles vorab zu indexieren wie in https://weaviate.io/blog/chunking-strategies-for-rag, mit Ternlight bei Bedarf Embeddings erzeugen und cachen.
Damit würde auch Reciprocal Rank Fusion möglich, also hybride Suche, die natives SQLite-FTS5/BM25 mit der semantischen Suche von Ternlight kombiniert.
Gut gemacht.
Es wird mit 7 MB beworben, aber es gibt auch eine 5-MB-Mini-Version.
Mini nutzt intern offenbar 256-Element-Vektoren statt 384, um Platz zu sparen, und projiziert sie am Ende aus Kompatibilitätsgründen wieder auf 384.
Die Größe sinkt um ein Drittel, aber der Verlust ist nicht linear; selbst mit dem kleineren Datenpfad scheint der Informationsverlust geringer als ein Drittel zu sein.
Cooles Projekt.
Ich habe früher etwas Ähnliches versucht: http://sol.quipu-strands.com/
Ich wollte im Browser ein Embedding-Modell laden und Texte semantisch anordnen.
Ich habe ONNX-Gewichte (MPNet, MiniLM) von HuggingFace geholt, mit Transformers.js Embeddings erzeugt und dann einen Clusterer aus scikit-learn verwendet, der innerhalb der Seite über pyodide lief. Alles lief clientseitig, und ich war überrascht, dass es perfekt funktionierte.
Die Demo verhält sich ziemlich seltsam. Wenn man zum Beispiel nach „how to use typescript with createContext“ sucht, sind die Top-Ergebnisse nur Typescript-Einträge, sodass es wirkt, als würde die Ähnlichkeitssuche versagen.
Danke. Lokale Modelle werden eines Tages Privatsphäre bringen, und ich kenne bereits einen hervorragenden Anwendungsfall, der genau zu solchen kleinen Embedding-Modellen passt: günstige und schnelle Suche in Produktdatenbanken.
In meinem Fall ist auch die Abhängigkeit von der CPU ein Vorteil.
Kann man die Embedding-Erzeugung, die 30 Sekunden dauert, vorab durchführen und an den Browser schicken?
Die anschließende Inferenz ist dann schnell und gut.