Ternlight - 브라우저(WASM)에서 실행되는 7MB 임베딩 모델
(ternlight-demo.vercel.app)- 서버 호출 없이 브라우저 안에서 텍스트 임베딩과 유사도 검색을 수행해, 의미(Semantic) 검색을 빠르게 구성
- GPU 없이 CPU만으로 동작하며, 기본 패키지는 엔진과 가중치를 합쳐 7MB, mini는 5MB
- 엔진·모델·BERT 토크나이저를 단일
.wasm파일에 통합해 postinstall이나 런타임 fetch 필요없음 all-MiniLM-L6모델에서 BitNet b1.58 방식의 양자화 인지 학습(QAT) 을 적용해 증류함@ternlight/base를 설치한 뒤embed,similar를 가져오면 코드 3줄로 시맨틱 검색 흐름을 만들 수 있음- 30× 압축을 달성하면서 정확도 손실은 소폭에 그쳐, 네트워크 왕복 없이 프라이버시·오프라인·엣지 검색을 가능케 함
ternlight - 브라우저 안에서 끝나는 임베딩
- 5–7MB WebAssembly 번들로 제공되는 시맨틱 임베딩 도구로, 엔진 + 모델 + 토크나이저를 하나의 파일에 담아 CPU에서 임베딩 검색 수행
- 텍스트를 밀리초 단위로 임베딩하고 서버를 호출하지 않음
- 두 개 티어를 동일 API로 제공, 크기/품질 트레이드오프에 따라 선택
@ternlight/base— 품질 티어 (7MB wire, embed당 약 5ms)@ternlight/mini— 소형·고속 티어 (5MB wire, embed당 약 2.5ms)
핵심 API
- 하나의 프리미티브: 문자열 → 384차원 L2 정규화 Float32Array 반환
embed,cosineSim,similar세 함수 제공cosineSim(embed('reset my password'), embed('I forgot my password'))→ 0.88similar는 코퍼스에 대한 최근접 이웃 검색 지원,topK지정 가능- 예: 'I want my money back' 검색 시 'Refunds: how to get your money back' (sim 0.70), 'Update your billing address' (sim 0.24) 반환
- Node ≥ 18, 브라우저(번들러 경유), Cloudflare Workers, Vercel Edge, Deno, Bun에서 동작하며 각 환경에 맞는 로더로 자동 라우팅
설계 원리
all-MiniLM-L6에서 증류하고 BitNet b1.58 스타일 양자화 인지 학습 적용, 세 가지 설계 선택으로 수 MB 크기 달성-
삼진 가중치 (Ternary weights)
- 모든 가중치가
-1,0,+1중 하나로, 추론이 덧셈과 뺄셈으로 처리됨 - 모델이 처음부터 삼진 모델로 학습되어 품질 유지
- 모든 가중치가
-
단일 번들 (One bundle)
- 모델 + BERT 토크나이저 + 엔진을 하나의
.wasm에 통합 - postinstall 단계나 런타임 fetch 없음
- 모델 + BERT 토크나이저 + 엔진을 하나의
-
SIMD 추론 엔진
- 손으로 작성한 Rust를 WASM SIMD로 컴파일
- 덧셈/뺄셈 연산이 CPU 벡터 명령어를 활용
성능 지표
- 모든 수치는 출시된 int4 빌드 기준 측정 (M 시리즈 Mac, Node/V8)
-
@ternlight/mini
- Wire size (gzip wasm): 5.0MB, Latency (p50): 2.5ms
- Throughput (단일 스레드): 약 400 emb/s
- Spearman(교사 대비): 0.820, Retrieval(SciFact NDCG@10): 0.439
- 아키텍처: 2-layer · d_model=256 · 4 heads, 파라미터 약 9.5M
-
@ternlight/base
- Wire size (gzip wasm): 7.2MB, Latency (p50): 5.1ms
- Throughput (단일 스레드): 약 195 emb/s
- Spearman(교사 대비): 0.844, Retrieval(SciFact NDCG@10): 0.465
- 아키텍처: 2-layer · d_model=384 · 6 heads, 파라미터 약 15.4M
-
공통 사양
- 출력: 384차원 L2 정규화
- 최대 입력: 128 토큰(약 95단어)
- 양자화: 삼진 가중치 · int4 임베딩
온디바이스 임베딩의 활용
-
Search-as-you-type
- 사용자가 입력을 끝내기 전에 결과 표시, 어떤 네트워크 왕복보다 빠름
-
프라이버시 민감 앱
- 쿼리와 문서가 기기를 벗어나지 않아 데이터 처리 계약이나 유출 위험 없음
-
오프라인 우선 앱
- 브라우저 확장, Obsidian 플러그인, 데스크톱 앱
-
엣지 런타임 앱
- Cloudflare Workers, Deno Deploy, Vercel Edge에서 임베딩이 요청 핸들러와 같은 위치에 배치되어 별도 추론 서비스 호출 불필요
-
엣지 디바이스 및 IoT 하드웨어
- Raspberry Pi, 단일 보드 컴퓨터, 산업용 게이트웨이, 키오스크
- 덧셈/뺄셈 연산이 ARM 코어에서 효율적으로 실행되어 GPU나 NPU 불필요
-
정적 사이트
- Jekyll, Hugo, Astro에서 모델을 번들과 함께 배포, 백엔드 없이 시맨틱 검색 동작
설치와 사용 예시
- npm 패키지 하나로 제공되며, 별도 모델 다운로드 단계나 서버 없이 사용할 수 있음
- 설치 명령은 다음과 같음
npm install @ternlight/base @ternlight/base에서embed,similar를 가져와 의미 기반 검색을 실행함import { embed, similar } from '@ternlight/base'; similar('easy weeknight dinner ideas', recipes, { topK: 3 }); // → ranked matches · ~5 ms · zero network
기반 오픈소스 및 라이선스
- BitNet b1.58 (Ma et al., Microsoft Research, 2024) — 삼진 가중치 학습의 아키텍처 연구
bitlinear— BitLinear의 PyTorch 참조 구현, 학습 시 직접 사용(bitlinear==2.4.6)하고 Rust 추론 엔진이 순전파 연산을 그대로 반영sentence-transformers/all-MiniLM-L6-v2— 학생 모델이 증류된 교사 모델- 라이선스: MIT
댓글과 토론
Hacker News 의견들
-
취미 프로젝트로 브라우저에서 유용한 모델을 돌려보고 싶어서 MiniLM에서 작은 문장 인코더를 증류하고, ternary 양자화 인식 학습을 적용함
추론 엔진도 직접 작성해서 Rust → WASM SIMD로 배포함
LLM이 아니라 임베딩 모델이라 텍스트를 넣으면 384차원 벡터가 나오고, 두 벡터의 코사인 유사도로 텍스트 관련성을 판단함. 예를 들어 "reset my password"와 "I forgot my password"가 0.88처럼 나옴
의미 기반 검색, FAQ/의도 매칭, 군집화에 쓸 수 있고, 기기 내 실행이라 API 의존 없이 입력 즉시 의미 검색이 빠르게 가능함
데모는 React 문서 2천 개를 완전히 기기 내에서 검색함: https://ternlight-demo.vercel.app
npm에는 @ternlight/base(7MB, 임베딩당 약 5ms, 더 성능 좋은 임베딩)와 @ternlight/mini(전송 5MB, 임베딩당 약 2.5ms) 두 단계가 있고, Node와 브라우저용으로 번들링됨
저장소에는 기술 세부사항과 MIT 라이선스, 학습 파이프라인이 포함됨: https://github.com/soycaporal/ternlight
기기 내 임베딩이 실제로 유용한지, 어떤 사용 사례가 있는지 궁금함- OpenStreetMap 태그로 단어를 매핑하는 사전이 있는데, https://codeberg.org/cartes/web/src/branch/master/components...처럼 되어 있음
사용자가 "pancake"를 입력했을 때 명시적으로 "pancake = crêpe" 사전 항목을 쓰지 않아도 crêpe를 찾게 만드는 데 도움이 될지 궁금함
이해한 게 맞다면 라이브러리가 처음에 5MB를 한 번 다운로드하고, 이후에는 지금 Fuse.js를 쓰는 것처럼 쓰는 구조인지도 궁금함
영어 외 언어를 얼마나 잘 처리하는지, OpenStreetMap 태그 위키로 학습시킬 수 있는지도 알고 싶음 - 단순 의미 기반 검색을 네이티브 데스크톱 앱에 넣는 데 정말 관심이 감
다른 초소형 임베딩 모델과 비교가 있는지 궁금함. MiniLM-L6에서 시작한 이유가 같은 급에서 특히 좋은 모델이라서인지 판단하기 어려운데, 제공된 지표가 "Retrieval (SciFact NDCG@10)"뿐임
다만 주장한 성능과는 꽤 차이가 나서, i5-4570의 Firefox에서는 초당 400개가 아니라 초당 35개 임베딩만 나옴. SIMD가 아닌 경로로 떨어지는 문제가 있는지 의심되고, 네이티브 Rust 바이너리도 시도해볼 예정임 - django 문서 전체와 사내 지식 베이스를 방금 임베딩해서 두 소스를 즉시 검색할 수 있게 됨
- OpenStreetMap 태그로 단어를 매핑하는 사전이 있는데, https://codeberg.org/cartes/web/src/branch/master/components...처럼 되어 있음
-
멋지지만, 랜딩 페이지에 데모를 시작하는 버튼을 두면 좋겠음. 웹페이지를 여는 순간 팬이 미친 듯이 도는 소리가 나서 꽤 놀람
- 동의함. 동시에 컴퓨터 소리만 들어도 지금 무슨 일이 벌어지는지 친밀하게 알 수 있던 시절이 떠올라 반갑기도 함
- 팬이 돌기 시작해서 놀랐음. 다만 토스터도 종종 나를 놀라게 하긴 함
- CPU 사이클 극한 활용이라니, GPU만 특별하다고 누가 그랬나 싶음
-
Astro나 범용 메타 프레임워크 플러그인으로 만들어서 생성된 모든 HTML 파일을 자동 파싱하고 작은 임베딩 데이터베이스를 만들면 좋겠음
프런트엔드에서는 이를 지연 로딩할 수 있고, HNSW도 청크 단위로 저장해서 검색 질의에 필요한 부분만 불러올 수 있을지도 모름
예를 들면 https://pagefind.app/와 비슷하지만, 완전 정적 벡터 검색을 제공하는 형태임- 우리 정적 사이트 생성기에는 sqlite-vec를 쓰고 싶었지만, 마지막으로 확인했을 때는 HNSW가 구현되어 있지 않았거나 브라우저 내 벡터 검색 지원이 좋지 않았음. 아마 여전히 전체 테이블 스캔을 하고 있었던 것 같음
몇 달, 몇 년이 지났는데도 그 상태라면 프로젝트를 제대로 완수할 역량이 부족한 신호처럼 보여 꽤 실망했음. 심지어 내가 지원한 보조금에서 그 프로젝트를 괜찮은 후보로 추천했는데, 그쪽은 선정되고 나는 떨어졌음
이 영역에서 좋은 해법을 아는 사람이 있거나, SQLite-vec에 대해 내가 틀렸다면 알려줬으면 함. 우리 SSG에서는 일단 다른 인프라를 몇 달 작업해보고, 그때도 완성되지 않았으면 직접 만들기로 거의 정했음
- 우리 정적 사이트 생성기에는 sqlite-vec를 쓰고 싶었지만, 마지막으로 확인했을 때는 HNSW가 구현되어 있지 않았거나 브라우저 내 벡터 검색 지원이 좋지 않았음. 아마 여전히 전체 테이블 스캔을 하고 있었던 것 같음
-
예전에 여기서 본 DuckDB HNSW 검색 프로젝트에 꽤 멋진 추가 기능이 될 수 있겠음: https://github.com/jasonjmcghee/portable-hnsw
정적으로 호스팅된 Parquet 파일을 대상으로 HTTP 범위 요청을 써서 검색이 일어나는 점이 정말 흥미로움
이런 것들이 대기업이 통제하지 않는 비교적 개방적이고 분산된 검색 생태계로 커질 수 있다고 봄- 비슷한 아이디어로, 정적 호스트의 SQLite DB를 HTTP 범위 요청과 WASM으로 사용하는 방식도 흥미로울 수 있음
https://news.ycombinator.com/item?id=27016630 - 멋진 아이디어임. 범위 요청과 정적 호스팅에서 클라이언트가 탐색할 수 있는 형식을 정말 좋아함
- 비슷한 아이디어로, 정적 호스트의 SQLite DB를 HTTP 범위 요청과 WASM으로 사용하는 방식도 흥미로울 수 있음
-
이건 정말 멋지고, 예전에 만들고 싶었던 것의 빠진 조각일 수 있겠음
https://github.com/npiesco/absurder-sql을 쓰면 원본 말뭉치 전체를 브라우저 안에 IndexedDB/SQLite로 영속 저장할 수 있음
그다음 https://weaviate.io/blog/chunking-strategies-for-rag처럼 모든 것을 미리 색인하지 않고, Ternlight로 필요할 때 임베딩 생성과 캐시를 할 수 있음
그러면 네이티브 SQLite의 FTS5/BM25와 Ternlight의 의미 검색을 합치는 Reciprocal Rank Fusion, 즉 하이브리드 검색도 가능해짐 -
잘 만들었음
7MB로 홍보되어 있지만 5MB mini 버전도 있음
mini는 내부적으로 384가 아니라 256요소 벡터를 써서 공간을 줄이고, 마지막에 호환성을 위해 384로 투영하는 것으로 보임
크기는 3분의 1 줄지만 손실은 선형적이지 않아서, 더 작은 데이터 경로를 쓰더라도 정보 손실은 3분의 1보다 적어 보임 -
멋진 프로젝트임
예전에 비슷한 것을 시도했음: http://sol.quipu-strands.com/
브라우저 안에서 임베딩 모델을 불러와 텍스트를 의미적으로 정렬하고 싶었음
HuggingFace에서 ONNX 가중치(MPNet, MiniLM)를 가져오고, Transformers.js로 임베딩을 만든 뒤, 페이지 안에서 pyodide로 실행되는 scikit-learn의 군집화기를 사용했음. 전부 클라이언트 측에서 돌아갔고, 이게 완벽히 동작해서 놀랐음 -
데모가 꽤 이상하게 동작함. 예를 들어 "how to use typescript with createContext"를 검색하면 상위 결과가 typescript 항목뿐이라 유사도 검색이 실패한 것처럼 보임
-
고마움. 로컬 모델은 언젠가 프라이버시를 가져올 것이고, 이런 작은 임베딩 모델에 딱 맞는 훌륭한 사용 사례도 이미 알고 있음. 제품 데이터베이스에서 저렴하고 빠른 검색을 하는 용도임
내 경우에는 CPU에 의존한다는 점도 장점임- 좋음. 지원할 방법이 있거나 로드맵에서 다뤄야 할 구체적인 사용 사례가 있으면 알려주면 좋겠음
-
30초 걸리는 임베딩 생성을 미리 해두고 브라우저로 보낼 수 있나?
그다음 추론은 빠르고 좋음- 가능함. 서버 쪽에서 한 번만 색인을 돌리고, 임베딩만 프런트엔드로 보내면 됨