압축 멀티벡터 검색: turbo4

시간: 25분 난이도: 중급 출력: GitHub

멀티벡터(multivector) 를 사용하면 ColBERT 같은 모델이 문서당 벡터 하나가 아니라 토큰당 벡터 하나로 문서를 표현할 수 있어요. 이를 통해 late interaction으로 검색 품질이 좋아지지만, 포인트당 훨씬 많은 벡터를 저장해야 하는 비용이 들어요.

Qdrant 1.19부터 Qdrant는 turbo4 데이터 타입을 지원해요. 이 타입은 dense 벡터를 디스크에 차원당 4비트로 저장해요. float32가 차원당 32비트를 쓰는 것과 대비되죠. 이는 저장 공간의 8분의 1이라서 멀티벡터의 토큰당 비용을 감당할 만한 수준으로 유지해 줘요. 일반적인 벤치마크에서 재현율 비용은 낮은 한 자릿수 퍼센트에 그쳐서, 검색 전략 자체에 비해 거의 병목이 되지 않을 정도로 작아요.

turbo4는 Google의 TurboQuant 양자화 기법에서 영감을 받았어요. 각 벡터를 수학적으로 회전시켜 정보가 모든 차원에 고르게 퍼지게 하고, 그래야 압축 중 손실이 낮게 유지돼요. 그다음 회전된 각 값을 16개 레벨 중 하나로 저장하는데, 이는 4비트에 들어가고 벡터를 원래 크기의 8분의 1로 줄여요.

turbo4는 TurboQuant를 감싼 래퍼가 아니라 독립형 데이터 타입이라서 더 양자화할 수 있어요. 예를 들어 turbo4를 1비트 TurboQuant 양자화와 결합할 수 있어요.

이 튜토리얼은 데이터 타입 비교에서 보통 건너뛰는 경우인 멀티벡터 표현에서의 turbo4 에 초점을 맞춰요. ColBERT late interaction 벡터를 turbo4로 저장하고, 그 옆에 BM25 sparse 벡터를 두는 제품 검색 컬렉션을 만들 거예요.

BM25로 먼저 저비용 후보 집합을 prefetch한 다음, 그 후보들만 더 비싼 ColBERT 멀티벡터로 rescore할 거예요. 전체 컬렉션에 걸쳐 late interaction을 실행하는 것은 정확한 최종 순위를 얻는 데 필요한 것보다 훨씬 느리거든요.

이 튜토리얼은 named vector, 멀티벡터와 late interaction, Query API에 익숙하다고 가정해요.

출처: Qdrant 공식 문서 — turbo4-multivector-search

설정 (Setup)

임베딩을 서버 측에서 생성하는 Qdrant Cloud Inference를 사용할 거라, qdrant-client만 있으면 Qdrant 의존성은 충분해요. huggingface-hubpolars가 데이터셋을 다운로드하고 처리해요.

pip install qdrant-client huggingface-hub polars

이 튜토리얼은 Cloud 전용인 Qdrant Cloud Inference를 사용해요. 셀프호스팅하려면 FastEmbed 같은 라이브러리로 ColBERT와 BM25 벡터를 클라이언트에서 생성하고 models.Document 대신 원시 벡터로 전달하세요.

데이터셋 (Dataset)

McAuley-Lab/Amazon-Reviews-2023 데이터셋의 Pet_Supplies 카테고리로 작업하고, Polars로 로드해요:

import os

from huggingface_hub import snapshot_download
import polars as pl

path = snapshot_download(
    "McAuley-Lab/Amazon-Reviews-2023",
    repo_type="dataset",
    allow_patterns=["raw/meta_categories/meta_Pet_Supplies.jsonl"],
)

jsonl_path = os.path.join(path, "raw/meta_categories/meta_Pet_Supplies.jsonl")

각 제품 레코드에서:

  • title — 제품명, BM25로 sparse 벡터로 임베딩.
  • description — 제품 설명, ColBERT로 임베딩. late interaction 모델로 문서당 벡터 하나 대신 토큰당 벡터 하나를 만듦.
  • images, details, price — 페이로드 메타데이터로 유지.

컬렉션 만들기 (Create a Collection)

Qdrant 클러스터를 만들고 URL과 API 키를 저장한 뒤, cloud_inference=True로 클라이언트를 초기화해요:

from qdrant_client import QdrantClient, models

client = QdrantClient(
    url="https://xyz-example.qdrant.io:6333",
    api_key="<your-api-key>",
    cloud_inference=True,
)

이제 컬렉션을 만들어요. descriptionmultivector_config와 함께 datatype=models.Datatype.TURBO4로 설정하고, comparator로 MAX_SIM을 써요. 이렇게 하면 문서를 최적 매칭 토큰 쌍으로 점수화하게 되어, ColBERT의 late interaction 검색 방식과 일치해요.

client.create_collection(
    collection_name="pet_supplies",
    vectors_config={
        "description": models.VectorParams(
            size=384,
            distance=models.Distance.COSINE,
            datatype=models.Datatype.TURBO4,
            multivector_config=models.MultiVectorConfig(
                comparator=models.MultiVectorComparator.MAX_SIM,
            ),
        ),
        "title": models.SparseVectorParams(),
    },
)

쿼리 (Query)

BM25 title 벡터로 prefetch 후보를 가져온 다음, ColBERT description 벡터로 late interaction을 통해 rescore해요.

prefetch에 최종 limit보다 훨씬 큰 limit 을 줘서, rescore가 실제 후보 풀을 갖고 작업하게 해요. 결과 한두 개를 순서만 바꾸는 게 아니라요:

query = "Orijen dry cat food"
title_query = models.Document(text=query, model="qdrant/bm25")
colbert_query = models.Document(text=query, model="answerdotai/answerai-colbert-small-v1")

response = await client.query_points(
    collection_name="pet_supplies",
    prefetch=models.Prefetch(
        query=title_query,
        using="title",
        limit=50,
    ),
    query=colbert_query,
    limit=1,
    with_payload=True,
    using="description",
)

result = response.points[0]
print(result.payload["title"])
ORIJEN® Dry Adult Cat Food, Grain Free, Premium, High Protein, Fresh & Raw Animal Ingredients, Guardian 8, 10lb

제목 prefetch는 BM25 제목 점수가 쿼리와 매치되는 후보를 가져오고, ColBERT rescore는 그 후보들을 설명에 대한 토큰 수준 매치로 재정렬해요.

마무리 (Wrapping Up)

turbo4는 단일 dense 벡터를 위한 특수 케이스가 아니라 범용 데이터 타입이에요. 이 컬렉션은 토큰당 ColBERT 멀티벡터에 저장하면서 BM25 sparse 벡터와 나란히 두고, 둘 다 Query API 호출 하나로 쿼리했어요.

4비트 온디스크 공간(footprint)을 원하는 dense든 멀티벡터든 어떤 VectorParams에서든 datatype=models.Datatype.TURBO4를 설정하세요.

더 알아보기 (Learn more)