본문으로 건너뛰기

© 2026 Molayo

GH Trending릴리즈2026. 05. 29. 04:20

turbovec: 4GB 메모리에서 10백만 문서 코퍼스 검색 (FAISS보다 빠름)

요약

turbovec는 Google Research의 TurboQuant 알고리즘을 기반으로 한 Rust 기반 벡터 인덱스로, 메모리 사용량을 획기적으로 줄이면서 FAISS보다 빠른 검색 성능을 제공합니다. 별도의 훈련 없이 온라인 수집이 가능하며, SIMD 커널을 통한 효율적인 필터링 기능을 지원합니다.

핵심 포인트

  • 1천만 문서 코퍼스를 31GB에서 4GB로 메모리 최적화
  • FAISS 대비 ARM 및 x86 환경에서 우수한 검색 속도
  • 훈련 단계가 필요 없는 데이터 비식별화 양자화 방식 적용
  • SIMD 커널을 활용한 검색 시점의 효율적인 필터링 지원
  • 로컬 환경 구축이 가능하여 보안 및 RAG 스택에 적합

float32로 1천만 문서 코퍼스가 31 GB의 RAM을 차지하는 반면, turbovec는 이를 4 GB에 담아내며 FAISS보다 빠르게 검색합니다.

turbovec는 Google Research의 TurboQuant 알고리즘을 기반으로 구축된 Rust 벡터 인덱스이며 Python 바인딩을 제공합니다. TurboQuant는 코드북 학습이나 별도의 훈련 단계 없이 왜곡(distortion)에 대한 Shannon 하한(lower bound)과 일치하는 데이터 비식별화 양자화기(data-oblivious quantizer)입니다.

온라인 수집 (Online ingest). 벡터를 추가하면 인덱싱됩니다. 코퍼스가 성장해도 훈련 단계, 매개변수 조정, 재구축이 필요 없습니다.FAISS보다 빠름. 직접 작성한 NEON (ARM) 및 AVX-512BW (x86) 커널은 ARM에서 FAISS IndexPQFastScan을 12–20% 능가하며 x86에서는 일치하거나 능가합니다.검색 시 필터링 (Filter at search time). search()에 ID 허용 목록(id allowlist) 또는 슬롯 비트마스크를 전달하면 커널이 이를 직접 처리합니다. 사용자가 지정한 허용 집합에서 항상 최대 k개의 결과를 얻을 수 있으며, 과도한 검색(over-fetching)이나 선택적 필터링 시 재현율 저하(recall hit)가 없습니다.순수 로컬 (Pure local). 관리형 서비스가 아니며 데이터가 사용자의 머신이나 VPC를 벗어나지 않습니다. 어떤 오픈 소스 임베딩 모델과도 결합하여 완전히 에어 갭된 RAG 스택을 구축할 수 있습니다.

개인 정보 보호, 메모리 또는 지연 시간이 중요한 곳에 RAG를 구축하고 있습니까? 여기가 바로 적절한 장소입니다.

pip install turbovec

from turbovec import TurboQuantIndex
index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)
...

삭제되어도 유지되는 안정적인 ID가 필요합니까? IdMapIndex를 사용하세요.

:

import numpy as np
from turbovec import IdMapIndex
index = IdMapIndex(dim=1536, bit_width=4)
...

다른 시스템(SQL, BM25, ACL, 시간 범위 등)이 생성한 후보 집합으로 결과를 제한하려면:

import numpy as np
from turbovec import IdMapIndex
dx = IdMapIndex(dim=1536, bit_width=4)
...

필터링은 32-벡터 블록(32-vector block) 단위로 SIMD 커널 내부에서 수행됩니다. 허용된 슬롯(allowed slots)이 없는 블록은 LUT(Look-Up Table) 조회나 스코어링(scoring) 작업을 수행하기 전에 단락 회로(short-circuited) 처리되며, 스코어링된 블록 내부의 허용되지 않은 개별 슬롯은 힙 삽입(heap-insert) 단계에서 제외됩니다. 따라서 선택적 허용 목록(allowlists, 인덱스의 아주 작은 부분만 허용)을 사용하면, SIMD 비용을 지불한 뒤 결과를 버리는 방식이 아니라 대부분의 SIMD 비용 자체를 회피할 수 있습니다.

출력 길이는 min(k, len(allowed))입니다.

— 허용 목록이 k보다 작은 경우

패딩된 폴백(padded fallbacks) 대신 정확히 len(allowed)개의 결과를 얻게 됩니다.

전체 레퍼런스는 docs/api.md를 참조하세요.

각 프레임워크의 인트리(in-tree) 레퍼런스 벡터/문서 저장소를 즉시 대체할 수 있습니다. 동일한 공개 인터페이스(public surface), 동일한 지속성 의미론(persistence semantics), 동일한 리트리버(retriever) 및 파이프라인 배선(pipeline wiring)을 제공하므로, 임포트(import) 문만 교체하면 기존 파이프라인을 그대로 유지할 수 있습니다.

  • LangChain —
    pip install turbovec[langchain]

· langchain_core.vectorstores.InMemoryVectorStore를 대체합니다.

  • LlamaIndex —
    pip install turbovec[llama-index]

· llama_index.core.vector_stores.SimpleVectorStore를 대체합니다.

  • Haystack —
    pip install turbovec[haystack]

· haystack.document_stores.in_memory.InMemoryDocumentStore를 대체합니다.

  • Agno —
    pip install turbovec[agno]

· agno.vectordb.lancedb.LanceDb를 대체합니다.

cargo add turbovec

use turbovec::TurboQuantIndex;
let mut index = TurboQuantIndex::new(1536, 4);
index.add(&vectors);
...

삭제 후에도 유지되는 안정적인 외부 ID를 사용하는 경우:

use turbovec::IdMapIndex;
let mut index = IdMapIndex::new(1536, 4);
index.add_with_ids(&vectors, &[1001, 1002, 1003]);
...

TurboQuant vs FAISS IndexPQ

(LUT256, nbits=8) — 논문의 Section 4.4 베이스라인. 100K 벡터, k=64. FAISS PQ 서브 양자화기(sub-quantizer)는 TurboQuant의 비트 레이트(bit rate)에 맞춰 크기를 조정했습니다 (2-bit일 때 m=d/4, 4-bit일 때 m=d/2).

OpenAI $d=1536$ 및 $d=3072$ 전반에 걸쳐, TurboQuant는 2-bit 및 4-bit 환경의 R@1에서 FAISS보다 0.4~3.4포인트 앞서며, 두 방식 모두 $k=4$까지 1.0으로 수렴합니다. GloVe $d=200$은 더 어려운 환경입니다. 저차원에서는 점근적 Beta 가정(asymptotic Beta assumption)이 더 느슨해지기 때문입니다. TurboQuant는 R@1 기준 4-bit에서 FAISS보다 0.3포인트 앞서지만, 2-bit에서는 1.2포인트 뒤처지며, 두 경우 모두 $k\approx16$ 부근에서 FAISS와의 격차를 좁힙니다.

베이스라인에 관한 참고 사항. 우리는 FAISS IndexPQ (LUT256, nbits=8, float32 LUT)와 비교했습니다. 이는 대부분의 사용자가 선택하게 될 기본 프로덕션급 PQ(Product Quantization)이기 때문입니다. 이는 TurboQuant 논문에 사용된 커스텀 u8-LUT PQ보다 더 강력한 베이스라인입니다. FAISS는 스코어링(scoring) 시점에 더 높은 정밀도의 LUT를 사용하며, 코드북(codebook) 학습을 위해 k-means++를 사용합니다. 우리는 OpenAI $d=1536$ / $d=3072$에서 논문의 TurboQuant 수치를 재현했으며, 저차원 임베딩(embeddings)에서 다른 커뮤니티 참조 구현체들과 유사한 수치를 기록했습니다 (d=384에서의 turboquant-py 참조).

Glove에서 나타나는 가시적인 격차는 FAISS가 강력한 베이스라인임을 반영하는 것이지, TurboQuant 구현의 문제는 아닙니다.

전체 결과: d=1536 2-bit, d=1536 4-bit, d=3072 2-bit, d=3072 4-bit, GloVe 2-bit, GloVe 4-bit.

모든 벤치마크: 100K 벡터, 1K 쿼리, k=64, 5회 실행의 중앙값.

ARM 환경에서 TurboQuant는 모든 설정에서 FAISS FastScan을 12~20% 앞섭니다.

x86 환경에서 TurboQuant는 모든 4-bit 설정에서 16% 승리하며, 2-bit ST(Single Thread)에서는 FAISS의 약 1% 이내 성능으로 실행됩니다. 2-bit MT(Multi-Thread) 행(d=1536 및 d=3072)은 FAISS보다 약간 뒤처지는(24%) 유일한 설정들인데, 이는 내부 누적 루프(inner accumulate loop)가 너무 짧아 언롤링 분할 amortized(unrolling amortization)가 FAISS의 AVX-512 VBMI 경로를 따라잡지 못하기 때문입니다.

각 벡터는 고차원 초구(hypersphere) 상의 방향입니다. TurboQuant는 다음과 같은 간단한 통찰을 사용하여 이러한 방향을 압축합니다: 무작위 회전(random rotation)을 적용한 후에는 입력 데이터와 관계없이 모든 좌표가 알려진 분포를 따른다는 것입니다.

1. 정규화(Normalize). 각 벡터에서 길이(norm)를 제거하고 이를 단일 부동 소수점(float)으로 저장합니다. 이제 모든 벡터는 초구 상의 단위 방향(unit direction)이 됩니다.

2. 무작위 회전 (Random rotation). 모든 벡터에 동일한 무작위 직교 행렬 (orthogonal matrix)을 곱합니다. 회전 후, 각 좌표는 고차원에서 가우시안 분포 $N(0, 1/d)$로 수렴하는 베타 분포 (Beta distribution)를 독립적으로 따르게 됩니다. 이는 어떤 입력 데이터에 대해서도 성립하며, 회전을 통해 좌표 분포를 예측 가능하게 만듭니다.

3. 좌표별 보정 (Per-coordinate calibration, TQ+). 2단계의 베타 분포는 점근적 (asymptotic)입니다. 즉, 유한한 차원에서는 개별 좌표가 표준 형태에서 벗어나는 경향이 있습니다 (특히 저비트 및 단어 벡터 스타일의 임베딩에서 두드러짐). TQ+는 첫 번째 추가 (add) 과정 중에 좌표당 두 개의 스칼라(shift 및 scale)를 맞추어, 각 좌표의 경험적 5/95% 분위수 (quantiles)를 표준 베타 주변 분포 (canonical Beta marginal)에 매핑합니다. 그 후 Lloyd-Max 코드북은 설계된 대상 분포에 맞춰 양자화 (quantize)를 수행합니다. 이 보정 값은 첫 번째 추가 이후 고정되어 이후의 추가 작업에서 재사용됩니다. 즉, 재학습(retraining), 재구축(rebuilds), 별도의 학습 단계가 필요 없습니다. 재현율(Recall) 이득: 가장 많이 벗어나는 셀(예: 2-bit에서의 GloVe)에 대해 @1 기준 최대 +1.4pp 향상.

4. Lloyd-Max 스칼라 양자화 (Lloyd-Max scalar quantization). 분포를 알고 있기 때문에, 각 좌표를 버킷(bucket)으로 나누는 최적의 방법을 미리 계산할 수 있습니다. 2-bit의 경우 4개의 버킷, 4-bit의 경우 16개의 버킷이 생성됩니다. Lloyd-Max 알고리즘은 평균 제곱 오차 (mean squared error)를 최소화하는 버킷 경계와 중심점 (centroids)을 찾습니다. 이는 데이터로부터가 아니라 수학적 계산을 통해 단 한 번만 계산됩니다.

5. 비트 패킹 (Bit-pack). 이제 각 좌표는 작은 정수(2-bit의 경우 0-3, 4-bit의 경우 0-15)가 됩니다. 이를 바이트 단위로 조밀하게 패킹합니다. 1536차원 벡터의 경우 6,144 바이트(FP32)에서 384 바이트(2-bit)로 줄어듭니다. 이는 16배의 압축률입니다.

6. 길이 재정규화 점수 산출 (Length-renormalized scoring). 스칼라 양자화는 내적 (inner product) 값을 체계적으로 과소평가합니다. 재구성된 단위 방향 (unit direction)이 원래보다 약간 짧기 때문입니다. 인코딩 시점에 벡터당 하나의 스칼라를 계산합니다. 이는 회전된 단위 벡터와 그 자체의 중심점 재구성 값 사이의 내적입니다. 그리고 ||v|| / ⟨u, x̂⟩를 저장합니다.

각 압축된 벡터와 함께 저장됩니다. 검색 커널(search kernel)은 힙 삽입(heap insertion) 전에 각 후보 점수에 이 스칼라 값을 곱함으로써, 내적 추정치(inner-product estimator)를 검색 시간 비용과 추가 저장 공간 없이 편향된(downward-biased) 상태에서 편향되지 않은(unbiased) 상태로 전환합니다. 재현율(recall) 이득은 양자화 수축(quantization shrinkage)이 가장 큰 낮은 비트 너비(bit width)에서 가장 두드러지게 나타납니다.

인코딩 비용: 벡터당 하나의 추가적인 d 차원 내적을 통해 ⟨u, x̂⟩를 계산합니다. d=1536인 100만 개의 벡터에 대해 이는 1초 미만의 추가 인코딩 시간이 소요되며, 이는 쿼리(query) 시점이 아닌 데이터 수집(ingest) 시점에 한 번만 지불하면 되는 비용입니다.

검색 (Search). 데이터베이스의 모든 벡터를 압축 해제하는 대신, 쿼리를 동일한 도메인으로 한 번 회전시킨 후 코드북(codebook) 값과 직접 점수를 비교합니다. 스코어링 커널(scoring kernel)은 최대 처리량(throughput)을 위해 nibble-split 룩업 테이블(lookup tables)과 함께 SIMD 인트린직(SIMD intrinsics) (ARM의 NEON, 현대적 x86의 AVX-512BW 및 AVX2 폴백(fallback))을 사용합니다.

Lloyd-Max 코드북은 정보 이론적 하한선(Shannon의 왜곡률 한계, distortion-rate limit)의 2.7배 이내의 왜곡(distortion)을 달성합니다. 길이 재정규화(length-renormalization) 단계는 Lloyd-Max 코드북이 내적 추정치 자체에 도입하는 잔여 편향(residual bias)을 제거합니다.

pip install maturin
cd turbovec-python
maturin build --release
...

cargo build --release

모든 x86_64 빌드는 .cargo/config.toml을 통해 x86-64-v3 (AVX2 베이스라인, Haswell 2013+)를 타겟팅합니다. AVX2 폴백 커널을 실행할 수 있는 모든 CPU는 전체 크레이트(crate)를 실행할 수 있습니다. AVX-512 커널은 is_x86_feature_detected!를 통해 런타임에 제어되며, 이를 지원하는 하드웨어에서만 작동합니다.

데이터셋 다운로드:

python3 benchmarks/download_data.py all # 모든 데이터셋
python3 benchmarks/download_data.py glove # GloVe d=200
python3 benchmarks/download_data.py openai-1536 # OpenAI DBpedia d=1536
...

각 벤치마크는 benchmarks/suite/에 있는 독립적인 스크립트입니다. 다음 중 하나를 개별적으로 실행할 수 있습니다:

python3 benchmarks/suite/speed_d1536_2bit_arm_mt.py
python3 benchmarks/suite/recall_d1536_2bit.py
python3 benchmarks/suite/compression.py

특정 카테고리의 모든 벤치마크 (benchmarks) 실행:

for f in benchmarks/suite/speed_*arm*.py; do python3 "$f"; done # 모든 ARM 속도
for f in benchmarks/suite/speed_*x86*.py; do python3 "$f"; done # 모든 x86 속도
for f in benchmarks/suite/recall_*.py; do python3 "$f"; done # 모든 재현율 (recall)
...

결과는 benchmarks/results/에 JSON 형식으로 저장됩니다.

차트 재생성:

python3 benchmarks/create_diagrams.py

  • TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate (ICLR 2026) -- 이 코드가 구현하고 있는 논문
  • RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search (SIGMOD 2024) -- 5단계에서 적용된 벡터별 길이 재정규화 (length-renormalization) 보정의 출처
  • FAISS Fast accumulation of PQ and AQ codes -- turbovec의 x86 SIMD 커널은 FastScan의 pack 레이아웃, nibble-LUT 스코어링, 그리고 u16 누산기 (accumulator) 전략을 채택함

AI 자동 생성 콘텐츠

본 콘텐츠는 GitHub Trending Python (weekly)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0