turbovec: TurboQuant 알고리즘을 Rust로 구현한 학습이 필요 없는 벡터 인덱스

turbovec 소개

turbovec는 Google Research가 ICLR 2026에서 공개한 TurboQuant 알고리즘을 Rust로 구현하고 Python 바인딩으로 노출한 벡터 인덱스(vector index) 라이브러리 입니다. 핵심 주장은 단순합니다. 1,000만 건 규모의 임베딩(embedding)을 float32로 저장하면 31GB가 필요하지만, turbovec은 같은 데이터를 4GB에 담으면서 FAISS보다 빠르게 검색한다. 사용자는 별도의 코드북(codebook) 학습이나 캘리브레이션 패스 없이 벡터를 그대로 인덱스에 추가하기만 하면 됩니다.

기존 FAISS의 PQ(Product Quantization) 계열 양자화는 데이터 의존적(data-dependent) k-means 코드북 학습 이 필요합니다. 데이터가 늘어나거나 도메인이 바뀌면 코드북을 다시 학습하고 인덱스를 재구축해야 했고, 멀티 테넌트 RAG처럼 임베딩 분포가 자주 변하는 환경에서는 이 과정이 운영의 큰 부담이었습니다. TurboQuant는 이 문제를 데이터에 무관한(data-oblivious) 양자화기 로 풀어냅니다. 입력 벡터에 무작위 직교 회전(random orthogonal rotation)을 한 번 적용하면, 회전된 좌표의 분포가 입력과 상관없이 알려진 Beta 분포 가 되어 고차원에서 Gaussian으로 수렴한다는 정리에 기반합니다. 이 분포는 사전에 알고 있으므로, Lloyd-Max 알고리즘으로 각 좌표를 양자화하는 최적 버킷(bucket) 도 데이터를 보지 않고 미리 계산해 둘 수 있습니다.

라이선스는 MIT License 로 공개되어 있으며, PyPI(turbovec)와 crates.io(turbovec) 양쪽으로 배포됩니다. Python 측에서는 LangChain, LlamaIndex, Haystack, Agno 네 가지 RAG 프레임워크용 드롭인 어댑터(drop-in adapter) 까지 함께 제공되어, 기존 파이프라인에서 import 한 줄만 바꿔도 인메모리 벡터 스토어를 그대로 대체할 수 있도록 설계되어 있습니다. turbovec은 관리형 서비스나 외부 API 호출 없이 순수 로컬(pure local) 로 동작하도록 만들어졌으며, 오픈소스 임베딩 모델과 짝지으면 데이터가 사용자의 머신이나 VPC 밖으로 나가지 않는 완전 에어갭(air-gapped) RAG 스택 을 구성할 수 있습니다.

turbovec 압축률과 메모리 효과

turbovec은 OpenAI 임베딩 차원(d=1536) 기준 다음 절감 효과를 보입니다.

  • 2비트 양자화: 1,536차원 float32 벡터 한 개의 원본 크기 6,144바이트가 384바이트 로 줄어 약 16배 압축 입니다.
  • 4비트 양자화: 동일 벡터가 768바이트가 되어 약 8배 압축이며, 회복률은 2비트 대비 훨씬 안정적입니다.
  • 10M 코퍼스 메모리: float32 그대로 보관하면 약 31GB가 필요한데, 2비트 양자화로는 4GB 수준에 들어옵니다.

이 압축은 단순히 비트수만 줄이는 것이 아닙니다. 양자화는 일반적으로 내적(inner product) 을 체계적으로 과소 추정하는 경향이 있는데, turbovec은 인코딩 시점에 벡터당 길이 재정규화 스칼라(length-renormalization scalar) 를 함께 계산해 저장합니다. 검색 커널은 후보별 점수에 이 스칼라를 곱하므로 내적 추정기(inner-product estimator) 가 검색 시점 비용 0과 추가 저장 0으로 편향이 사라진(unbiased) 형태로 동작합니다. 인코딩 비용은 벡터당 한 번의 d차원 내적이 추가되는 정도이며, 1M × d=1536 기준 1초 미만의 원샷(one-shot) 비용 입니다.

turbovec 검색 속도와 회복률

벤치마크는 100K 벡터, 1K 쿼리, k=64, 다섯 번 실행의 중앙값을 기준으로 측정되었습니다. 비교 대상은 프로덕션 수준 PQ의 표준 선택지 로 자주 쓰이는 FAISS IndexPQ (LUT256, nbits=8, float32 LUT)입니다.

회복률 측면에서는 다음 패턴이 확인됩니다.

  • OpenAI d=1536 / d=3072: TurboQuant가 R@1 기준 FAISS 대비 0.4~3.4점 우위이며, 2비트·4비트 모두 k=4 시점에는 양쪽이 1.0으로 수렴합니다.
  • GloVe d=200: 저차원에서는 Beta 분포 가정의 점근적 근사가 다소 약해져, 4비트에서는 turbovec이 0.3점 앞서고 2비트에서는 1.2점 뒤집니다. 다만 두 곡선 모두 k≈16에서 FAISS와 만납니다.

속도 측면에서 turbovec의 SIMD 커널은 다음과 같이 동작합니다.

  • ARM (Apple M3 Max, NEON 커널): 모든 설정에서 FAISS FastScan 대비 12~20% 빠릅니다.
  • x86 (Intel Xeon Platinum 8481C, AVX-512BW 커널): 4비트 설정에서는 1~6% 우위, 2비트 단일 스레드에서는 FAISS와 ~1% 이내, 2비트 멀티스레드 d=1536/3072 두 케이스에서만 FAISS의 AVX-512 VBMI 경로에 2~4% 뒤집니다.

x86 빌드는 x86-64-v3 (AVX2 기반, Haswell 2013+) 을 기본 타깃으로 하고, AVX-512 경로는 런타임 is_x86_feature_detected! 매크로로 감지해 자동 전환됩니다. 즉, AVX-512가 없는 머신에서도 AVX2 폴백 커널로 동일한 코드를 그대로 돌릴 수 있습니다.

turbovec의 핵심 알고리즘 흐름

turbovec이 적용하는 다섯 단계는 README의 How it works 섹션에 단순한 형태로 정리되어 있습니다.

  1. 정규화(normalize): 각 벡터의 길이(norm)를 떼어 단일 float으로 저장하고, 본체는 단위 방향(unit direction)으로 처리합니다.
  2. 무작위 직교 회전(random rotation): 모든 벡터에 동일한 무작위 직교 행렬을 곱합니다. 회전 후 각 좌표는 입력과 상관없이 알려진 Beta 분포를 따르며, 고차원에서 N(0, 1/d) Gaussian에 수렴합니다.
  3. Lloyd-Max 스칼라 양자화: 분포가 알려져 있으므로 Lloyd-Max 알고리즘 으로 평균 제곱 오차(MSE)를 최소화하는 버킷 경계와 중심값을 데이터를 보지 않고 한 번에 계산합니다. 2비트는 4버킷, 4비트는 16버킷입니다.
  4. 비트 패킹(bit-packing): 각 좌표가 0~3 또는 0~15의 작은 정수가 되었으므로 비트 단위로 패킹합니다. d=1536 기준 6,144바이트 → 384바이트(2비트)로 떨어집니다.
  5. 길이 재정규화 점수(length-renormalized scoring): 인코딩 시점에 한 번 계산해 저장한 ||v|| / ⟨u, x̂⟩ 스칼라를 검색 커널이 후보별 점수에 곱해, 내적 추정기를 검색 시점 비용 0에 편향이 없는 형태로 만듭니다.

Lloyd-Max 코드북은 섀넌(Shannon) 정보 이론 의 왜곡-비트율 하한 대비 2.7배 이내의 왜곡(distortion)에 도달하고, 길이 재정규화 단계가 코드북 자체가 만들어내는 잔존 편향을 제거하는 구조입니다.

검색 시점에는 데이터베이스 벡터를 복원하지 않습니다. 쿼리를 동일한 무작위 직교 행렬로 한 번 회전한 뒤, 회전된 쿼리와 코드북 중심값을 직접 점수화합니다. 스코어링 커널은 ARM에서는 NEON, 최신 x86에서는 AVX-512BW, 그 외 x86에서는 AVX2 폴백 SIMD intrinsic을 사용하며, 처리량을 최대화하기 위해 nibble-split lookup table 을 채택합니다. x86 커널의 패킹 레이아웃, 니블 LUT 스코어링, u16 누산 전략은 FAISS의 FastScan 구현에서 차용한 부분이며, README의 References 섹션에 출처가 명시되어 있습니다.

turbovec Python API와 RAG 통합

Python 사용 측면에서는 두 가지 인덱스가 제공됩니다. 기본 TurboQuantIndex 는 추가 순서대로 슬롯 인덱스를 부여하고, IdMapIndex 는 사용자 지정 uint64 외부 ID를 유지해 삭제(remove) 가 O(1)에 가능합니다.

import numpy as np
from turbovec import TurboQuantIndex, IdMapIndex

# 1) 기본 인덱스 - 슬롯 인덱스 기반
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.tq")

# 2) 외부 uint64 ID를 보존하는 인덱스
idmap = IdMapIndex(dim=1536, bit_width=4)
idmap.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64))
idmap.remove(1002)                                   # O(1) 삭제
scores, ids = idmap.search(query, k=10)               # ids 는 사용자 ID

# 3) 외부 시스템이 좁힌 후보 집합으로 필터링하는 하이브리드 검색
allowed = np.array(
    db.execute("SELECT id FROM docs WHERE tenant=?", (tenant_id,)).fetchall(),
    dtype=np.uint64,
)
scores, ids = idmap.search(query, k=10, allowlist=allowed)

세 번째 예시인 allowlist 필터링 이 turbovec의 차별점 중 하나입니다. 필터링은 32-벡터 블록 단위로 SIMD 커널 내부에서 수행되며, 허용된 슬롯이 하나도 없는 블록은 LUT 조회·점수 계산을 단락(short-circuit) 합니다. 선택적 필터(인덱스 대비 작은 비율만 허용되는 경우)는 SIMD 비용 대부분을 회피하므로, 후처리 필터링 대비 비용 절감 효과가 큽니다. 출력 길이는 min(k, len(allowed)) 로, 허용 집합이 k 보다 작으면 정확히 그 크기만큼만 결과가 반환됩니다.

프레임워크 통합은 모두 pip install turbovec[<integration>] 형태로 설치합니다.

  • 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 대체

turbovec Rust 사용 예시

turbovec은 Rust 크레이트로도 동일한 API 표면을 제공합니다. Python 바인딩이 PyO3를 통해 같은 코어를 호출하므로, 임베딩 파이프라인이 Rust 서비스 안에 있다면 별도 변환 없이 그대로 사용할 수 있습니다.

use turbovec::{TurboQuantIndex, IdMapIndex};

let mut index = TurboQuantIndex::new(1536, 4);
index.add(&vectors);
let results = index.search(&queries, 10);
index.write("index.tv").unwrap();
let loaded = TurboQuantIndex::load("index.tv").unwrap();

// 외부 ID 보존 및 O(1) 삭제가 필요한 경우
let mut id_index = IdMapIndex::new(1536, 4);
id_index.add_with_ids(&vectors, &[1001, 1002, 1003]);
let (scores, ids) = id_index.search(&queries, 10);
id_index.remove(1002);
id_index.write("index.tvim").unwrap();

크레이트는 cargo add turbovec 으로 설치하며, x86_64 빌드는 .cargo/config.toml 을 통해 x86-64-v3 (AVX2 기반, Haswell 2013+) 을 기본 타깃으로 합니다. AVX-512 커널은 런타임 is_x86_feature_detected! 매크로로 감지해 자동 전환되므로, AVX-512 미지원 CPU에서도 AVX2 폴백 커널로 동일한 코드를 그대로 실행할 수 있습니다.

라이선스

turbovec은 MIT License 로 공개되어 있습니다. 상업적 활용·수정·재배포가 자유로우며, 저장소에 동봉된 라이선스 파일과 저작권 고지를 그대로 유지하면 됩니다. 또한 turbovec은 TurboQuant 논문(arXiv:2504.19874) 의 알고리즘, RaBitQ 논문(SIGMOD 2024) 의 길이 재정규화 보정, 그리고 FAISS FastScan 의 패킹 레이아웃·니블 LUT·u16 누산 전략을 결합한 구현체임을 README의 References 섹션이 명시하고 있어, 학술적·공학적 출처와의 관계가 명확합니다.

:scroll: TurboQuant 논문 (ICLR 2026, arXiv:2504.19874)

:scroll: RaBitQ 논문 (SIGMOD 2024, arXiv:2405.12497)

:github: turbovec 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 글을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 덧글로 알려주시기를 부탁드립니다. :hugs:

:pytorch:파이토치 한국 사용자 모임:south_korea:이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일:love_letter:로 보내드립니다! (기본은 Weekly지만 Daily로 변경도 가능합니다.)

:wrapped_gift: 아래:down_right_arrow:쪽에 좋아요:+1:를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ :star_struck: