A vector index built on TurboQuant, written in Rust with Python bindings
이 번역은 AI가 원문 README를 옮긴 것입니다. 원문이 항상 우선합니다.
turbovec는 Google Research의 TurboQuant 알고리즘을 기반으로 구축된 Rust 벡터 인덱스로, Python 바인딩을 제공합니다. TurboQuant는 데이터에 무관한(data-oblivious) 양자화기로, 별도의 학습 단계 없이 최적에 가까운 왜곡을 달성합니다.
search()에 ID 허용 목록(allowlist) 또는 슬롯 비트마스크를 전달하면 커널이 이를 직접 처리합니다. 항상 허용된 집합에서 최대 k개의 결과를 반환하며, 오버페칭이나 선택적 필터에 따른 재현율 저하가 없습니다.pip install turbovec
from turbovec import TurboQuantIndex
index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)
index.add(more_vectors)
scores, indices = index.search(query, k=10)
index.write("my_index.tv")
loaded = TurboQuantIndex.load("my_index.tv")
삭제 후에도 유지되는 안정적인 ID가 필요하면 IdMapIndex를 사용하세요:
import numpy as np
from turbovec import IdMapIndex
index = IdMapIndex(dim=1536, bit_width=4)
index.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64))
scores, ids = index.search(query, k=10) # ids는 사용자의 uint64 외부 ID
index.remove(1002) # O(1) ID 기반 삭제
index.write("my_index.tvim")
loaded = IdMapIndex.load("my_index.tvim")
다른 시스템(SQL, BM25, ACL, 시간 윈도우 등)에서 생성된 후보 집합으로 결과를 제한합니다:
import numpy as np
from turbovec import IdMapIndex
idx = IdMapIndex(dim=1536, bit_width=4)
idx.add_with_ids(vectors, ids)
# 1단계: 외부 시스템이 후보 ID로 좁힘.
allowed = np.array(db.execute("SELECT id FROM docs WHERE tenant=?", (t,)).fetchall(),
dtype=np.uint64)
# 2단계: 후보 집합 내에서 밀집 재순위화.
scores, ids = idx.search(query, k=10, allowlist=allowed)
필터링은 32-벡터 블록 단위로 SIMD 커널 내부에서 발생합니다. 허용된 슬롯이 없는 블록은 LUT 조회나 점수 계산 전에 조기 종료되고, 점수가 계산된 블록 내에서 허용되지 않은 개별 슬롯은 힙 삽입 시 제거됩니다. 선택적 허용 목록(인덱스의 작은 부분만 허용)은 SIMD 비용을 지불한 후 결과를 폐기하는 대신 대부분의 SIMD 비용을 회피합니다.
출력 길이는 min(k, len(allowed))입니다. 허용 목록이 k보다 작으면 패딩된 폴백 없이 정확히 len(allowed)개의 결과를 반환합니다.
전체 API 참조는 docs/api.md를 참조하세요.
각 프레임워크의 기본 벡터/문서 저장소를 대체할 수 있는 드롭인(drop-in) 대체품입니다. 동일한 공개 인터페이스, 동일한 지속성 의미, 동일한 검색기 및 파이프라인 연결을 유지합니다. 임포트만 변경하면 파이프라인을 그대로 사용할 수 있습니다.
pip install turbovec[langchain] · langchain_core.vectorstores.InMemoryVectorStore 대체pip install turbovec[llama-index] · llamaindex.core.vector_stores.SimpleVectorStore 대체pip install turbovec[haystack] · haystack.documentstores.inmemory.InMemoryDocumentStore 대체pip install turbovec[agno] · agno.vectordb.lancedb.LanceDb 대체cargo add turbovec
use turbovec::TurboQuantIndex;
let mut index = TurboQuantIndex::new(1536, 4).unwrap();
index.add(&vectors);
let results = index.search(&queries, 10);
index.write("index.tv").unwrap();
let loaded = TurboQuantIndex::load("index.tv").unwrap();
삭제 후에도 유지되는 안정적인 외부 ID가 필요하면:
use turbovec::IdMapIndex;
let mut index = IdMapIndex::new(1536, 4).unwrap();
index.add_with_ids(&vectors, &[1001, 1002, 1003]).unwrap();
let (scores, ids) = index.search(&queries, 10);
index.remove(1002);
index.write("index.tvim").unwrap();
let loaded = IdMapIndex::load("index.tvim").unwrap();
TurboQuant 대 FAISS IndexPQ (LUT256, nbits=8) — 논문의 섹션 4.4 기준. 100K 벡터, k=64. FAISS PQ 하위 양자화기 개수는 TurboQuant의 비트 전송률과 일치하도록 설정 (2비트에서 m=d/4, 4비트에서 m=d/2).
!Recall GloVe d=200
!Recall d=1536
!Recall d=3072
OpenAI d=1536 및 d=3072에서 TurboQuant는 2비트와 4비트 모두에서 R@1 기준 FAISS보다 0.2–1.9 포인트 높으며, 둘 다 k=8에서 1.0에 도달합니다 (k=4에서 이미 ≥0.997). GloVe d=200은 더 어려운 영역입니다. 저차원에서는 점근적 베타 가정이 덜 정확합니다. TurboQuant는 4비트에서 FAISS보다 0.9 포인트 높고, 2비트에서는 R@1에서 사실상 동등하며(0.1 포인트 이내), 둘 다 k≈16에서 FAISS를 밀접하게 추적합니다.
기준에 대한 참고. FAISS IndexPQ (LUT256, nbits=8, float32 LUT)와 비교합니다. 이는 대부분의 사용자가 사용할 기본 프로덕션 등급 PQ이기 때문입니다. 이는 TurboQuant 논문의 사용자 정의 u8-LUT PQ보다 더 강력한 기준입니다. FAISS는 점수 계산 시 더 높은 정밀도의 LUT를 사용하고 코드북 학습에 k-means++를 사용합니다. OpenAI d=1536 / d=3072에서 논문의 TurboQuant 수치를 재현했으며, 저차원 임베딩(d=384에서 turboquant-py 참조)에서 다른 커뮤니티 참조 구현과 유사한 수치를 얻었습니다. GloVe (d=200) — 점근적 베타 가정이 가장 느슨한 저차원 영역 — 에서 TurboQuant는 2비트에서 FAISS와 동등하고 4비트에서 앞섭니다. TQ+ 보정은 기본 알고리즘이 남긴 저차원 격차를 줄입니다.
전체 결과: d=1536 2-bit, d=1536 4-bit, d=3072 2-bit, d=3072 4-bit, GloVe 2-bit, GloVe 4-bit.
!Compression
모든 벤치마크: 100K 벡터, 1K 쿼리, k=64, 5회 실행 중앙값.
!ARM Speed — Single-threaded
!ARM Speed — Multi-threaded
ARM에서 TurboQuant는 모든 설정에서 FAISS FastScan보다 10–19% 빠릅니다.
!x86 Speed — Single-threaded
!x86 Speed — Multi-threaded
x86에서 TurboQuant는 4비트 설정에서 최대 ~5% 우세하며(d=3072 멀티스레드는 동률), 2비트에서는 FAISS에 약간 뒤집니다. 가장 눈에 띄는 것은 d=1536 단일 스레드(~8%)이며, 나머지는 몇 퍼센트 이내입니다. FAISS의 AVX-512 VBMI 경로가 짧은 2비트 누적 루프에서 우위를 점합니다.
각 벡터는 고차원 초구 위의 방향입니다. TurboQuant는 간단한 통찰을 사용하여 이러한 방향을 압축합니다. 무작위 회전을 적용한 후, 모든 좌표는 입력 데이터와 관계없이 알려진 분포를 따릅니다.
1. 정규화. 각 벡터에서 길이(노름)를 제거하고 단일 float로 저장합니다. 이제 모든 벡터는 초구 위의 단위 방향입니다.
2. 무작위 회전. 모든 벡터에 동일한 무작위 직교 행렬을 곱합니다. 회전 후, 각 좌표는 독립적으로 고차원에서 가우시안 N(0, 1/d)으로 수렴하는 베타 분포를 따릅니다. 이는 모든 입력 데이터에 대해 성립합니다. 회전은 좌표 분포를 예측 가능하게 만듭니다.
3. 좌표별 보정 (TQ+). 2단계의 베타 분포는 점근적입니다. 유한 차원에서는 개별 좌표가 표준 형태에서 벗어납니다(특히 저비트 및 단어 벡터 스타일 임베딩). TQ+는 첫 번째 추가 중에 각 좌표에 대해 두 개의 스칼라(이동 및 스케일)를 피팅하여 각 좌표의 경험적 5/95% 분위수를 표준 베타 주변 분포에 매핑합니다. 그런 다음 Lloyd-Max 코드북은 설계된 대상 분포에 대해 양자화합니다. 보정은 첫 번째 추가 후 고정되고 이후 추가에서 재사용됩니다. 재학습, 재구축, 별도의 학습 단계가 없습니다. 재현율 향상: 가장 많이 벗어나는 셀(예: 2비트 GloVe)에서 @1 기준 최대 +1.4pp.
4. Lloyd-Max 스칼라 양자화. 분포가 알려져 있으므로 각 좌표를 버킷팅하는 최적의 방법을 미리 계산할 수 있습니다. 2비트의 경우 4개 버킷, 4비트의 경우 16개 버킷입니다. Lloyd-Max 알고리즘은 평균 제곱 오차를 최소화하는 버킷 경계와 중심을 찾습니다. 이는 데이터가 아닌 수학에서 한 번 계산됩니다.
5. 비트 패킹. 각 좌표는 이제 작은 정수(2비트의 경우 0-3, 4비트의 경우 0-15)입니다. 이를 바이트에 촘촘히 패킹합니다. 1536차원 벡터는 6,144바이트(FP32)에서 384바이트(2비트)로 줄어듭니다. 이는 16배 압축입니다.
6. 길이 재정규화 점수 계산. 스칼라 양자화는 내적을 체계적으로 과소평가합니다. 재구성된 단위 방향은 원래보다 약간 짧습니다. 인코딩 시 각 벡터에 대해 하나의 스칼라(회전된 단위 벡터와 자체 중심 재구성의 내적)를 계산하고 ||v|| / ⟨u, x̂⟩를 각 압축된 벡터와 함께 저장합니다. 검색 커널은 힙 삽입 전에 후보별 점수에 이 스칼라를 곱하여 내적 추정기를 하향 편향에서 무편향으로 전환합니다. 검색 시간 비용이나 추가 저장 공간이 없습니다. 재현율 향상은 양자화 수축이 가장 큰 저비트 폭에서 가장 두드러집니다.
인코딩 비용: ⟨u, x̂⟩를 계산하기 위한 벡터당 하나의 추가 d차원 내적. d=1536에서 100만 벡터의 경우 인코딩 시간이 1초 미만으로 추가됩니다. 이는 쿼리 시가 아닌 수집 시 지불하는 일회성 비용입니다.
검색. 모든 데이터베이스 벡터를 압축 해제하는 대신, 쿼리를 동일한 도메인으로 회전시키고 코드북 값에 대해 직접 점수를 계산합니다. 점수 계산 커널은 니블 분할 룩업 테이블을 사용하여 SIMD 내장 함수(NEON on ARM, 최신 x86에서 AVX-512BW, AVX2 폴백)를 사용하여 최대 처리량을 달성합니다.
Lloyd-Max 코드북은 정보 이론적 하한(Shannon의 왜곡-율 한계)의 2.7배 이내의 왜곡을 달성합니다. 길이 재정규화 단계는 Lloyd-Max 코드북이 내적 추정기 자체에 도입하는 잔여 편향을 제거합니다.
pip install maturin
cd turbovec-python
maturin build --release
pip install target/wheels/*.whl
cargo build --release
모든 x8664 빌드는 .cargo/config.toml을 통해 x86-64-v3 (AVX2 기준, Haswell 2013+)을 대상으로 합니다. AVX2 폴백 커널을 실행할 수 있는 모든 CPU는 전체 크레이트를 실행할 수 있습니다. AVX-512 커널은 런타임에 isx86featuredetected!를 통해 게이트되며, 이를 지원하는 하드웨어에서만 활성화됩니다.
데이터셋 다운로드:
python3 benchmarks/d