Skip to content
SON BLOG
Go back

Qdrant로 대규모 PDF 검색 확장하기 — ColPali 멀티벡터 최적화

Edit page

대규모 PDF 검색은 RAG·에이전트형 워크플로우에서 핵심 요소이다.

ColPali·ColQwen2와 같은 비전 LLM(VLLM)은 OCR·룰 기반 파싱 없이 PDF 페이지 이미지를 직접 임베딩하여 최신 벤치마크(예: ViDoRe)에서 뛰어난 성능을 보인다.

그러나 VLLM은 한 페이지당 수백 ~ 천여 개의 멀티벡터를 생성하므로, 그대로 HNSW 인덱싱을 수행하면 RAM 사용량 폭증·인덱스 구축 지연·검색 지연 문제가 발생한다.

본 튜토리얼은 다음 전략으로 이러한 병목을 해소하는 방법을 설명한다.

  1. 1단계 검색 ― 행·열 기준 Mean Pooling 으로 벡터 수를 수십 개로 축소하여 HNSW 인덱스 구축

  2. 2단계 재랭킹 ― 원본 멀티벡터를 사용해 상위 후보(예: 100개)를 ColPali·ColQwen2의 Late Interaction(MAX SIM)으로 정밀 재정렬

1. 사전 준비

pip install colpali_engine>=0.3.1         # ColPali / ColQwen2
pip install qdrant-client[fastembed]>=1.12 # Qdrant + 임베딩
from colpali_engine.models import ColPali, ColPaliProcessor
from qdrant_client import QdrantClient, models

client = QdrantClient(url="<Qdrant_Cluster_URL>", api_key="<API_KEY>")
model      = ColPali.from_pretrained("vidore/colpali-v1.3",
                                     torch_dtype="bfloat16",
                                     device_map="cuda:0").eval()
processor  = ColPaliProcessor.from_pretrained("vidore/colpali-v1.3")

_ColQwen2_도 동일한 방법으로 로드할 수 있다.

2. 컬렉션 설계

벡터 이름용도크기HNSWmultivector
original원본 멀티벡터 → 재랭킹128비활성(m=0)MAX_SIM
mean_pooling_rows행별 평균128활성MAX_SIM
mean_pooling_columns열별 평균128활성MAX_SIM
client.create_collection(
    "pdf-pages",
    vectors_config = {
        "original": models.VectorParams(
            size=128, distance=models.Distance.COSINE,
            multivector_config=models.MultiVectorConfig(
                comparator=models.MultiVectorComparator.MAX_SIM),
            hnsw_config=models.HnswConfigDiff(m=0)            # 인덱싱 OFF
        ),
        "mean_pooling_rows": models.VectorParams(
            size=128, distance=models.Distance.COSINE,
            multivector_config=models.MultiVectorConfig(
                comparator=models.MultiVectorComparator.MAX_SIM)
        ),
        "mean_pooling_columns": models.VectorParams(
            size=128, distance=models.Distance.COSINE,
            multivector_config=models.MultiVectorConfig(
                comparator=models.MultiVectorComparator.MAX_SIM)
        )
    }
)

3. 데이터셋 준비

from datasets import load_dataset
dataset = load_dataset("davanstrien/ufo-ColPali", split="train")

4. 임베딩 및 Mean Pooling

import torch, uuid, tqdm

def embed_page(image):
    proc = processor.process_images([image])
    vecs = model(**proc)[0]               # (1030, 128) ColPali 예시
    mask = proc.input_ids[0] == processor.image_token_id
    img_tokens = vecs[mask]
    x, y = processor.get_n_patches(image.size, model.patch_size)  # (32, 32)
    img_tokens = img_tokens.view(x, y, -1)

    # 행·열 평균
    rows    = img_tokens.mean(dim=1)                # (x, 128)
    columns = img_tokens.mean(dim=0)                # (y, 128)

    # 특수 토큰 6개 유지
    specials = vecs[~mask]
    rows    = torch.cat([rows, specials])           # (x+6, 128)
    columns = torch.cat([columns, specials])        # (y+6, 128)

    return vecs, rows, columns

5. 업로드

points=[]
for i, row in enumerate(tqdm.tqdm(dataset[:1000])):  # 예시: 1,000페이지
    original, rows, cols = embed_page(row["image"])

    points.append(
        models.PointStruct(
            id=uuid.uuid4().hex,
            vector={
                "original": original.cpu().numpy(),
                "mean_pooling_rows": rows.cpu().numpy(),
                "mean_pooling_columns": cols.cpu().numpy()
            },
            payload={"index": i}
        )
    )

client.upload_points("pdf-pages", points=points, batch_size=8)

6. 검색 파이프라인

query = "Lee Harvey Oswald's involvement in the JFK assassination"
q_vec  = model(**processor.process_queries([query]).to(model.device))[0]

prefetched = client.query_points(
    "pdf-pages",
    query=q_vec,
    prefetch=[
        models.Prefetch(query=q_vec, using="mean_pooling_rows",    limit=100),
        models.Prefetch(query=q_vec, using="mean_pooling_columns", limit=100)
    ],
    using="original",            # 재랭킹 단계
    limit=10,
    with_payload=True
)

7. 성능 효과

8. 결론 및 권장 사항


Edit page
Share this post:

Previous Post
Qdrant GPU 인덱싱 가속 — Docker 이미지와 설정 가이드
Next Post
Qdrant 시맨틱 검색 튜토리얼 — 5분 만에 검색 엔진 만들기