23 điểm bởi GN⁺ 6 ngày trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Thực hiện text embedding và tìm kiếm độ tương đồng ngay trong trình duyệt mà không cần gọi máy chủ, giúp xây dựng tìm kiếm ngữ nghĩa (semantic) nhanh chóng
  • Chỉ chạy bằng CPU không cần GPU; gói mặc định gồm engine và trọng số chỉ 7MB, bản mini là 5MB
  • Tích hợp engine, model và BERT tokenizer vào một tệp .wasm duy nhất nên không cần postinstall hay fetch lúc runtime
  • Chưng cất từ mô hình all-MiniLM-L6 với quantization-aware training (QAT) theo kiểu BitNet b1.58
  • Sau khi cài @ternlight/base, chỉ cần import embed, similar là có thể tạo luồng tìm kiếm ngữ nghĩa với 3 dòng code
  • Đạt mức nén 30× trong khi độ chính xác chỉ giảm nhẹ, cho phép tìm kiếm riêng tư, ngoại tuyến và tại edge mà không cần khứ hồi mạng

ternlight - embedding hoàn tất ngay trong trình duyệt

  • Công cụ semantic embedding được phân phối dưới dạng bundle WebAssembly 5–7MB, chứa engine + model + tokenizer trong một tệp duy nhất để thực hiện tìm kiếm embedding trên CPU
  • Embedding văn bản ở mức mili giây và không gọi máy chủ
  • Cung cấp hai tier với cùng một API, có thể chọn theo đánh đổi giữa kích thước/chất lượng
    • @ternlight/base — tier chất lượng (7MB wire, khoảng 5ms cho mỗi embedding)
    • @ternlight/mini — tier nhỏ gọn/tốc độ cao (5MB wire, khoảng 2.5ms cho mỗi embedding)

API cốt lõi

  • Một primitive duy nhất: chuỗi → trả về Float32Array 384 chiều đã chuẩn hóa L2
  • Cung cấp ba hàm embed, cosineSim, similar
    • cosineSim(embed('reset my password'), embed('I forgot my password')) → 0.88
    • similar hỗ trợ tìm kiếm láng giềng gần nhất trên corpus, có thể chỉ định topK
      • Ví dụ: khi tìm 'I want my money back' sẽ trả về 'Refunds: how to get your money back' (sim 0.70), 'Update your billing address' (sim 0.24)
  • Chạy trên Node ≥ 18, trình duyệt (qua bundler), Cloudflare Workers, Vercel Edge, Deno, Bun và tự động định tuyến tới loader phù hợp với từng môi trường

Nguyên lý thiết kế

  • Chưng cất từ all-MiniLM-L6 và áp dụng quantization-aware training kiểu BitNet b1.58, đạt kích thước chỉ vài MB nhờ ba lựa chọn thiết kế
  • Trọng số tam phân (Ternary weights)

    • Mọi trọng số chỉ thuộc một trong ba giá trị -1, 0, +1, nên suy luận được xử lý bằng phép cộng và trừ
    • Mô hình được huấn luyện ngay từ đầu dưới dạng tam phân để giữ chất lượng
  • Một bundle duy nhất (One bundle)

    • Tích hợp model + BERT tokenizer + engine vào một tệp .wasm
    • Không cần bước postinstall hay fetch lúc runtime
  • Engine suy luận SIMD

    • Rust viết thủ công được biên dịch sang WASM SIMD
    • Các phép cộng/trừ tận dụng lệnh vector của CPU

Chỉ số hiệu năng

  • Tất cả số liệu được đo trên bản int4 đã phát hành (Mac dòng M, Node/V8)
  • @ternlight/mini

    • Wire size (gzip wasm): 5.0MB, Latency (p50): 2.5ms
    • Throughput (đơn luồng): khoảng 400 emb/s
    • Spearman (so với teacher): 0.820, Retrieval (SciFact NDCG@10): 0.439
    • Kiến trúc: 2-layer · d_model=256 · 4 heads, khoảng 9.5M tham số
  • @ternlight/base

    • Wire size (gzip wasm): 7.2MB, Latency (p50): 5.1ms
    • Throughput (đơn luồng): khoảng 195 emb/s
    • Spearman (so với teacher): 0.844, Retrieval (SciFact NDCG@10): 0.465
    • Kiến trúc: 2-layer · d_model=384 · 6 heads, khoảng 15.4M tham số
  • Thông số chung

    • Đầu ra: 384 chiều đã chuẩn hóa L2
    • Đầu vào tối đa: 128 token (khoảng 95 từ)
    • Lượng tử hóa: trọng số tam phân · embedding int4

Ứng dụng của embedding on-device

  • Search-as-you-type

    • Hiển thị kết quả trước khi người dùng nhập xong, nhanh hơn mọi vòng khứ hồi mạng
  • Ứng dụng nhạy cảm về quyền riêng tư

    • Query và tài liệu không rời khỏi thiết bị, không có rủi ro rò rỉ hay yêu cầu hợp đồng xử lý dữ liệu
  • Ứng dụng ưu tiên ngoại tuyến

    • Tiện ích mở rộng trình duyệt, plugin Obsidian, ứng dụng desktop
  • Ứng dụng runtime ở edge

    • Trên Cloudflare Workers, Deno Deploy, Vercel Edge, embedding được đặt cùng vị trí với request handler nên không cần gọi dịch vụ suy luận riêng
  • Thiết bị edge và phần cứng IoT

    • Raspberry Pi, máy tính bo mạch đơn, gateway công nghiệp, kiosk
    • Các phép cộng/trừ chạy hiệu quả trên lõi ARM nên không cần GPU hay NPU
  • Trang web tĩnh

    • Trên Jekyll, Hugo, Astro có thể phát hành model cùng bundle để chạy tìm kiếm ngữ nghĩa mà không cần backend

Ví dụ cài đặt và sử dụng

  • Được cung cấp dưới dạng một gói npm duy nhất, có thể dùng mà không cần bước tải model riêng hay máy chủ
  • Lệnh cài đặt như sau
    npm install @ternlight/base  
    
  • Import embed, similar từ @ternlight/base để chạy tìm kiếm theo ngữ nghĩa
    import { embed, similar } from '@ternlight/base';  
    
    similar('easy weeknight dinner ideas', recipes, { topK: 3 });  
    // → ranked matches · ~5 ms · zero network  
    

Mã nguồn mở nền tảng và giấy phép

  • BitNet b1.58 (Ma et al., Microsoft Research, 2024) — nghiên cứu kiến trúc cho huấn luyện trọng số tam phân
  • bitlinear — triển khai tham chiếu PyTorch của BitLinear, được dùng trực tiếp khi huấn luyện (bitlinear==2.4.6), còn engine suy luận Rust phản ánh nguyên vẹn phép toán lan truyền xuôi
  • sentence-transformers/all-MiniLM-L6-v2 — mô hình teacher mà student model được chưng cất từ đó
  • Giấy phép: MIT

1 bình luận

 
Ý kiến trên Hacker News
  • Tôi làm một dự án sở thích để thử chạy một mô hình hữu ích ngay trong trình duyệt, nên đã chưng cất một bộ mã hóa câu nhỏ từ MiniLM và áp dụng huấn luyện nhận biết lượng tử hóa tam phân
    Tôi cũng tự viết engine suy luận rồi phát hành bằng Rust → WASM SIMD
    Vì đây là mô hình embedding chứ không phải LLM, khi đưa văn bản vào sẽ nhận được vector 384 chiều, và dùng độ tương đồng cosine giữa hai vector để đánh giá mức liên quan của văn bản. Ví dụ, "reset my password" và "I forgot my password" cho ra khoảng 0.88
    Có thể dùng cho tìm kiếm ngữ nghĩa, khớp FAQ/ý định, phân cụm, và vì chạy trên thiết bị nên có thể tìm kiếm ngữ nghĩa rất nhanh ngay khi nhập mà không phụ thuộc API
    Demo tìm kiếm 2.000 tài liệu React hoàn toàn trên thiết bị: https://ternlight-demo.vercel.app
    Trên npm có hai mức: @ternlight/base (7MB, khoảng 5ms mỗi embedding, embedding chất lượng tốt hơn) và @ternlight/mini (truyền tải 5MB, khoảng 2.5ms mỗi embedding), được đóng gói cho Node và trình duyệt
    Kho lưu trữ có chi tiết kỹ thuật, giấy phép MIT và pipeline huấn luyện: https://github.com/soycaporal/ternlight
    Tôi muốn biết liệu embedding trên thiết bị có thực sự hữu ích không, và có những trường hợp sử dụng nào

    • Tôi có một từ điển ánh xạ từ sang thẻ OpenStreetMap, dạng như https://codeberg.org/cartes/web/src/branch/master/components...
      Tôi muốn biết liệu nó có thể giúp tìm ra crêpe khi người dùng nhập "pancake" mà không cần một mục từ điển tường minh kiểu "pancake = crêpe" hay không
      Nếu tôi hiểu đúng thì thư viện sẽ tải một lần 5MB ban đầu, rồi sau đó dùng theo kiểu giống như hiện tôi đang dùng Fuse.js, đúng không
      Tôi cũng muốn biết nó xử lý các ngôn ngữ ngoài tiếng Anh tốt đến đâu, và liệu có thể huấn luyện bằng wiki thẻ OpenStreetMap hay không
    • Tôi thực sự rất quan tâm đến việc đưa tìm kiếm ngữ nghĩa đơn giản vào một ứng dụng desktop native
      Tôi muốn biết có so sánh nào với các mô hình embedding siêu nhỏ khác không. Thật khó đánh giá liệu lý do bắt đầu từ MiniLM-L6 có phải vì đó là mô hình đặc biệt tốt trong cùng phân khúc hay không, vì chỉ số được cung cấp chỉ có "Retrieval (SciFact NDCG@10)"
      Tuy vậy, hiệu năng thực tế chênh khá nhiều so với tuyên bố: trên Firefox chạy i5-4570, tôi chỉ đạt 35 embedding mỗi giây chứ không phải 400. Tôi nghi có vấn đề rơi xuống đường chạy không SIMD, và cũng định thử binary Rust native
    • Tôi vừa embedding toàn bộ tài liệu django và knowledge base nội bộ của công ty, giờ có thể tìm kiếm tức thì trên cả hai nguồn
  • Rất hay, nhưng sẽ tốt hơn nếu trang đích có nút bắt đầu demo. Ngay lúc mở trang web thì quạt quay như điên làm tôi khá giật mình

    • Đồng ý. Cũng thấy vui vì nó gợi nhớ thời mà chỉ cần nghe tiếng máy tính là đã thân thuộc với việc biết chuyện gì đang xảy ra
    • Tôi cũng giật mình khi quạt bắt đầu quay. Dù vậy, máy nướng bánh mì cũng hay làm tôi giật mình
    • Nói đến vắt kiệt chu kỳ CPU, ai bảo chỉ GPU mới đặc biệt chứ
  • Sẽ hay nếu làm thành plugin cho Astro hay một meta framework phổ thông, để tự động phân tích tất cả các file HTML được tạo ra và xây dựng một cơ sở dữ liệu embedding nhỏ
    Ở frontend có thể lazy-load nó, và có lẽ cả HNSW cũng có thể được lưu theo từng chunk để chỉ tải phần cần cho truy vấn tìm kiếm
    Ví dụ như https://pagefind.app/ nhưng cung cấp tìm kiếm vector hoàn toàn tĩnh

    • Tôi từng muốn dùng sqlite-vec cho static site generator của mình, nhưng lần cuối kiểm tra thì HNSW chưa được triển khai hoặc hỗ trợ tìm kiếm vector trong trình duyệt chưa tốt. Hình như nó vẫn đang quét toàn bộ bảng
      Nếu sau nhiều tháng, nhiều năm mà vẫn vậy thì khá thất vọng, vì trông như dấu hiệu dự án không có đủ năng lực để hoàn thành cho ra hồn. Tôi thậm chí còn đề xuất dự án đó như một ứng viên ổn trong khoản tài trợ mà tôi nộp, và bên họ được chọn còn tôi thì trượt
      Nếu ai biết giải pháp tốt trong mảng này, hoặc nếu tôi đã hiểu sai về SQLite-vec, thì rất mong được chỉ ra. Với SSG của chúng tôi, hiện gần như đã chốt là sẽ làm hạ tầng khác vài tháng rồi nếu khi đó vẫn chưa xong thì tự xây
  • Đây có thể là một phần bổ sung rất hay cho dự án tìm kiếm DuckDB HNSW mà tôi từng thấy ở đây: https://github.com/jasonjmcghee/portable-hnsw
    Điểm rất thú vị là nó tìm kiếm trên các file Parquet được host tĩnh bằng HTTP range requests
    Tôi nghĩ những thứ như vậy có thể phát triển thành một hệ sinh thái tìm kiếm tương đối mở và phân tán, không bị các tập đoàn lớn kiểm soát

    • Với ý tưởng tương tự, cách dùng SQLite DB trên host tĩnh qua HTTP range requests và WASM cũng có thể rất thú vị
      https://news.ycombinator.com/item?id=27016630
    • Ý tưởng hay đấy. Tôi thực sự thích range requests và các định dạng mà client có thể tự điều hướng trên hosting tĩnh
  • Cái này thực sự rất hay, và có thể là mảnh ghép còn thiếu cho thứ tôi từng muốn làm
    Với https://github.com/npiesco/absurder-sql, bạn có thể lưu bền toàn bộ corpus gốc trong IndexedDB/SQLite ngay trong trình duyệt
    Sau đó, thay vì lập chỉ mục trước mọi thứ như https://weaviate.io/blog/chunking-strategies-for-rag, bạn có thể dùng Ternlight để tạo embedding và cache khi cần
    Khi đó cũng có thể làm tìm kiếm lai bằng Reciprocal Rank Fusion, tức là kết hợp FTS5/BM25 của SQLite native với tìm kiếm ngữ nghĩa của Ternlight

  • Làm tốt lắm
    Dù được quảng bá là 7MB, nhưng cũng có bản mini 5MB
    Có vẻ mini nội bộ dùng vector 256 phần tử thay vì 384 để tiết kiệm không gian, rồi cuối cùng chiếu lên 384 để tương thích
    Kích thước giảm còn một phần ba nhưng tổn thất không tuyến tính, nên ngay cả khi dùng đường dữ liệu nhỏ hơn thì lượng mất mát thông tin có vẻ vẫn ít hơn một phần ba

  • Dự án hay đấy
    Trước đây tôi từng thử thứ tương tự: http://sol.quipu-strands.com/
    Tôi muốn tải một mô hình embedding trong trình duyệt để sắp xếp văn bản theo ngữ nghĩa
    Tôi lấy trọng số ONNX (MPNet, MiniLM) từ HuggingFace, tạo embedding bằng Transformers.js, rồi dùng bộ phân cụm của scikit-learn chạy bằng pyodide ngay trong trang. Tất cả đều chạy phía client, và tôi đã ngạc nhiên vì nó hoạt động hoàn hảo

  • Demo hoạt động khá kỳ lạ. Ví dụ, khi tìm "how to use typescript with createContext" thì các kết quả đầu toàn là mục typescript, trông như tìm kiếm tương đồng đã thất bại

  • Cảm ơn. Mô hình cục bộ rồi sẽ mang lại quyền riêng tư, và tôi đã biết một trường hợp sử dụng rất phù hợp cho các mô hình embedding nhỏ như thế này: tìm kiếm rẻ và nhanh trên cơ sở dữ liệu sản phẩm
    Trong trường hợp của tôi, việc nó phụ thuộc vào CPU cũng là một lợi thế

    • Hay đấy. Nếu có cách nào để hỗ trợ hoặc có trường hợp sử dụng cụ thể nên đưa vào roadmap thì mong bạn chia sẻ
  • Có thể tạo trước embedding mất 30 giây rồi gửi sang trình duyệt không?
    Sau đó suy luận sẽ nhanh và ổn

    • Được. Bạn chỉ cần chạy lập chỉ mục một lần ở phía server, rồi chỉ gửi embedding sang frontend