늦은 상호작용 검색을 위한 밀집 토큰 임베딩 활용

늦은 상호작용 검색을 위한 밀집 토큰 임베딩 활용 (Late Interaction Retrieval with Dense Token Embeddings)

* 적어도 모든 오픈소스 모델 — 내부 동작에 접근할 수 있어야 하거든요.

출처: 공식문서

밀집 임베딩 모델을 늦은 상호작용에 적용할 수 있어요

Qdrant 1.10은 멀티벡터 표현에 대한 지원을 도입했는데, 늦은 상호작용(late interaction)이 이 방식의 대표적인 예예요. 본질적으로 문서와 쿼리 모두 여러 벡터로 표현되고, 가장 관련성 높은 문서를 찾는 것은 대응하는 쿼리·문서 임베딩 사이의 유사도를 기반으로 점수를 계산하는 것을 포함해요. 이 패러다임이 낯설다면, 업데이트된 Hybrid Search 아티클에서 멀티벡터 표현이 검색 품질을 어떻게 높이는지 설명하니 참고해 보세요.

그림 1: 대응하는 문서-쿼리 임베딩 쌍 사이의 늦은 상호작용을 시각화할 수 있어요.

Late interaction model

ColBERT 같은 특화된 늦은 상호작용 모델이 많이 있지만, 일반적인 밀집 임베딩 모델도 이런 방식으로 효과적으로 활용될 수 있는 것처럼 보여요.

이 연구에서 우리는 전통적으로 단일 벡터 표현에 사용되던 표준 밀집 임베딩 모델이, 출력 토큰 임베딩을 멀티벡터 표현으로 사용하면 늦은 상호작용 시나리오에 효과적으로 적용될 수 있음을 보여 드릴게요.

Qdrant의 멀티벡터 기능으로 검색을 테스트하면서, 이 모델들이 검색 성능에서 특화된 늦은 상호작용 모델에 필적하거나 능가하면서도 더 낮은 복잡도와 더 높은 효율을 제공한다는 걸 보여 줄 거예요. 이 작업은 고급 검색 파이프라인에서 밀집 모델의 잠재력을 재정의하고, 검색 시스템을 최적화하는 새로운 방법을 제시합니다.

임베딩 모델 이해하기

임베딩 모델의 내부 동작은 어떤 이들에게는 놀라울 수 있어요. 모델은 입력 텍스트에 직접 동작하지 않아요. 대신 텍스트를 토큰 식별자 시퀀스로 변환하는 토크나이제이션 단계가 필요하죠. 각 토큰 식별자는 임베딩 레이어를 통과해 밀집 벡터로 변환돼요. 본질적으로 임베딩 레이어는 토큰 식별자를 밀집 벡터에 매핑하는 룩업 테이블처럼 동작해요. 이 벡터들은 그다음 트랜스포머 모델의 입력으로 들어갑니다.

그림 2: 벡터가 트랜스포머 모델에 추가되기 전에 일어나는 토크나이제이션 단계.

Input token embeddings

입력 토큰 임베딩은 컨텍스트가 없고 학습 과정에서 얻어져요. 즉 각 토큰은 텍스트에서의 위치와 무관하게 항상 같은 임베딩을 받아요. 이 단계에서 토큰 임베딩은 자신이 나타나는 맥락을 알지 못해요. 이 임베딩들을 맥락화하는 것이 바로 트랜스포머 모델의 역할이죠.

트랜스포머 모델에서 attention의 역할에 대해 많은 얘기가 오가지만, 본질적으로 이 메커니즘은 토큰 간 관계를 포착하는 역할을 해요. 각 트랜스포머 모듈은 토큰 임베딩 시퀀스를 입력으로 받아 토큰 임베딩 시퀀스를 출력으로 만들어요. 두 시퀀스는 같은 길이이고, 각 토큰 임베딩은 현재 단계에서 다른 토큰 임베딩들의 정보로 풍부해집니다.

그림 3: 출력 토큰 임베딩 시퀀스를 만드는 메커니즘.

Output token embeddings

그림 4: 임베딩 모델이 수행하는 마지막 단계는 출력 토큰 임베딩을 풀링해 입력 텍스트의 단일 벡터 표현을 만드는 것입니다.

Pooling

여러 풀링 전략이 있지만, 모델이 어떤 것을 쓰든 출력은 항상 단일 벡터 표현이며, 이는 필연적으로 입력에 대한 일부 정보를 잃어요. 어떤 사람에게 가장 가까운 식료품점까지 가는 자세하고 단계별 지시사항을 주는 것과 대략적인 방향만 가리키는 것을 비교해 보면 비슷해요. 모호한 방향이 어떤 경우에는 충분할 수 있지만, 자세한 지시사항이 더 원하는 결과로 이끌 가능성이 높죠.

출력 토큰 임베딩을 멀티벡터 표현으로 사용하기

우리는 출력 토큰 임베딩을 자주 간과하지만, 사실은 이것들도 입력 텍스트의 멀티벡터 표현으로 기능해요. 그렇다면 늦은 상호작용 모델처럼 멀티벡터 검색 모델에서 그 사용을 탐구해 보는 건 어떨까요?

실험 결과

출력 토큰 임베딩이 전통적인 늦은 상호작용 모델을 대신해 효과적으로 쓰일 수 있는지 알아보기 위해 몇 가지 실험을 진행했어요. 결과는 꽤 유망해요.

Dataset Model Experiment NDCG@10
SciFact prithivida/Splade_PP_en_v1 sparse vectors 0.70928
colbert-ir/colbertv2.0 late interaction model 0.69579
all-MiniLM-L6-v2 single dense vector representation 0.64508
output token embeddings 0.70724
BAAI/bge-small-en single dense vector representation 0.68213
output token embeddings 0.73696
NFCorpus prithivida/Splade_PP_en_v1 sparse vectors 0.34166
colbert-ir/colbertv2.0 late interaction model 0.35036
all-MiniLM-L6-v2 single dense vector representation 0.31594
output token embeddings 0.35779
BAAI/bge-small-en single dense vector representation 0.29696
output token embeddings 0.37502
ArguAna prithivida/Splade_PP_en_v1 sparse vectors 0.47271
colbert-ir/colbertv2.0 late interaction model 0.44534
all-MiniLM-L6-v2 single dense vector representation 0.50167
output token embeddings 0.45997
BAAI/bge-small-en single dense vector representation 0.58857
output token embeddings 0.57648

이 실험들의 소스 코드는 오픈소스이고, Qdrant와 BeIR 라이브러리의 통합인 beir-qdrant를 활용해요. 이 패키지는 Qdrant 팀이 공식적으로 유지·관리하는 것은 아니지만, 다양한 Qdrant 설정이 검색 품질에 어떤 영향을 주는지 실험해 보고 싶은 분들에게 유용할 수 있어요. 모든 실험은 Qdrant의 정확 검색(exact search) 모드로 진행되어, 근사 검색의 영향이 결과에 반영되지 않게 했어요.

단순한 all-MiniLM-L6-v2 모델조차 늦은 상호작용 모델 방식으로 적용되어 검색 품질에 긍정적인 영향을 줬어요. 하지만 최상의 결과는 BAAI/bge-small-en 모델로 얻었는데, 이 모델은 희소 모델과 늦은 상호작용 모델을 모두 능가했죠.

한 가지 유의할 점은 ColBERT가 BeIR 데이터셋으로 훈련되지 않아 완전히 도메인 밖(out of domain) 성능이라는 거예요. 그럼에도 all-MiniLM-L6-v2훈련 데이터셋도 BeIR 데이터를 전혀 포함하지 않는데, 여전히 놀랄 만큼 잘 동작해요.

밀집 대 늦은 상호작용 모델 비교 분석

검색 품질이 스스로 말해 주지만, 고려해야 할 다른 중요한 요소도 있어요.

우리가 테스트한 전통적인 밀집 임베딩 모델은 늦은 상호작용이나 희소 모델보다 덜 복잡해요. 파라미터가 적어서 이 모델들은 추론 시 더 빠르고 유지·관리도 더 비용 효율적일 것으로 기대돼요. 아래는 실험에 사용된 모델들의 비교예요.

Model Number of parameters
prithivida/Splade_PP_en_v1 109,514,298
colbert-ir/colbertv2.0 109,580,544
BAAI/bge-small-en 33,360,000
all-MiniLM-L6-v2 22,713,216

출력 토큰 임베딩 사용에 반대하는 논거 하나는 ColBERT류 모델에 비해 저장 요구량이 늘어난다는 점이에요. 예를 들어 all-MiniLM-L6-v2 모델은 384차원의 출력 토큰 임베딩을 만들어내는데, 이는 ColBERT류 모델이 만드는 128차원 임베딩보다 세 배 많아요. 이 증가는 메모리 사용을 높일 뿐 아니라, 거리 계산에 더 많은 시간이 걸려 검색의 연산 비용에도 영향을 줘요. 벡터 압축으로 이 문제를 완화하면 훨씬 합리적일 거예요.

멀티벡터 표현의 양자화 탐구

바이너리 양자화는 일반적으로 고차원 벡터에 더 효과적이라, 비교적 저차원 출력을 가진 all-MiniLM-L6-v2 모델은 이 접근에 덜 적합해요. 하지만 스칼라 양자화는 실행 가능한 대안으로 보였어요. 아래 표는 양자화가 검색 품질에 미치는 영향을 요약해요.

Dataset Model Experiment NDCG@10
SciFact all-MiniLM-L6-v2 output token embeddings 0.70724
output token embeddings (uint8) 0.70297
NFCorpus all-MiniLM-L6-v2 output token embeddings 0.35779
output token embeddings (uint8) 0.35572

양자화가 항상 검색 품질을 같은 수준으로 보존하는 것은 아니지만, 이 경우 스칼라 양자화가 검색 성능에 미치는 영향은 아주 적어 보여요. 효과는 무시할 만한 반면 메모리 절약은 상당하죠.

원래 품질을 유지하면서도 메모리를 4배 덜 쓰게 됐어요. 게다가 양자화된 벡터는 384바이트를 필요로 하는데, ColBERT의 512바이트에 비해요. 이는 25%의 메모리 사용 감소를 만들고 검색 품질은 거의 그대로 유지돼요.

실제 적용: 밀집 모델로 검색 향상하기

sentence transformer 모델 중 하나를 쓰고 있다면 출력 토큰 임베딩이 기본적으로 계산돼요. 단일 벡터 표현이 저장·연산 측면에서 더 효율적이지만, 출력 토큰 임베딩을 버릴 필요는 없어요. 우리 실험에 따르면 이 임베딩들이 검색 품질을 상당히 향상시킬 수 있어요. Qdrant에 단일 벡터와 출력 토큰 임베딩을 모두 저장하고, 단일 벡터로 초기 검색을 한 뒤 출력 토큰 임베딩으로 결과를 리랭킹할 수 있어요.

그림 5: 리랭킹에 출력 토큰 임베딩만을 사용하는 단일 모델 파이프라인.

Single model reranking

이 개념을 보여 주기 위해 Qdrant에서 간단한 리랭킹 파이프라인을 구현했어요. 이 파이프라인은 초기 과표집(oversampled) 검색에 밀집 임베딩 모델을 사용하고, 리랭킹 단계에서는 출력 토큰 임베딩에만 의존해요.

단일 모델 검색·리랭킹 벤치마크

테스트는 검색과 리랭킹에 같은 모델을 사용하는 데 초점을 맞췄어요. 보고된 메트릭은 NDCG@10이에요. 모든 테스트에서 5배 과표집 계수를 적용했어요. 즉 검색 단계가 50개의 결과를 돌려주고, 리랭킹 단계에서 10개로 좁혀졌죠. 아래는 몇몇 BeIR 데이터셋에 대한 결과예요.

Dataset all-miniLM-L6-v2 dense embeddings only all-miniLM-L6-v2 dense + reranking BAAI/bge-small-en dense embeddings only BAAI/bge-small-en dense + reranking
SciFact 0.64508 0.70293 0.68213 0.73053
NFCorpus 0.31594 0.34297 0.29696 0.35996
ArguAna 0.50167 0.45378 0.58857 0.57302
Touche-2020 0.16904 0.19693 0.13055 0.19821
TREC-COVID 0.47246 0.6379 0.45788 0.53539
FiQA-2018 0.36867 0.41587 0.31091 0.39067

벤치마크의 소스 코드는 공개되어 있고, beir-qdrant 패키지 저장소에서 찾을 수 있어요.

전반적으로 같은 모델로 리랭킹 단계를 추가하면 보통 검색 품질이 개선돼요. 하지만 다양한 늦은 상호작용 모델의 품질은 초기 검색에 BM25를 사용했을 때의 리랭킹 성능을 기준으로 보고되는 경우가 많아요. 이 실험은 단일 모델이 검색과 리랭킹 모두에 효과적으로 쓰일 수 있음을 보여 주는 것을 목표로 했고, 결과는 꽤 유망합니다.

이제 Qdrant 1.10에 도입된 새 Query API로 어떻게 구현하는지 살펴볼게요.

늦은 상호작용을 위한 Qdrant 설정

Qdrant 1.10의 새 Query API는 훨씬 더 복잡한 검색 파이프라인을 만들 수 있게 해 줘요. 초기 검색 단계에는 풀링 후 만들어진 단일 벡터를, 리랭킹 단계에는 출력 토큰 임베딩을 사용할 수 있어요.

컬렉션이 my-collection이고 dense-vectoroutput-token-embeddings라는 두 개의 네임드 벡터를 저장하도록 설정돼 있다고 가정할게요. Qdrant에서 그런 컬렉션은 이렇게 만들 수 있어요.

from qdrant_client import QdrantClient, models

client = QdrantClient("http://localhost:6333")

client.create_collection(
    collection_name="my-collection",
    vectors_config={
        "dense-vector": models.VectorParams(
            size=384,
            distance=models.Distance.COSINE,
        ),
        "output-token-embeddings": models.VectorParams(
            size=384,
            distance=models.Distance.COSINE,
            multivector_config=models.MultiVectorConfig(
                comparator=models.MultiVectorComparator.MAX_SIM
            ),
        ),
    }
)

두 벡터는 같은 all-MiniLM-L6-v2 모델이 만들기 때문에 크기가 같아요.

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

이제 단일 밀집 벡터만 쓰는 search API 대신, 리랭킹 파이프라인을 만들 수 있어요. 먼저 밀집 벡터로 50개의 결과를 검색하고, 출력 토큰 임베딩으로 리랭킹해 상위 10개 결과를 얻어요.

query = "What else can be done with just all-MiniLM-L6-v2 model?"

client.query_points(
    collection_name="my-collection",
    prefetch=[
        # Prefetch the dense embeddings of the top-50 documents
        models.Prefetch(
            query=model.encode(query).tolist(),
            using="dense-vector",
            limit=50,
        )
    ],
    # Rerank the top-50 documents retrieved by the dense embedding model
    # and return just the top-10. Please note we call the same model, but
    # we ask for the token embeddings by setting the output_value parameter.
    query=model.encode(query, output_value="token_embeddings").tolist(),
    using="output-token-embeddings",
    limit=10,
)

직접 실험해 보기

실제 시나리오에서는 한 걸음 더 나아가 먼저 토큰 임베딩을 계산한 뒤 풀링으로 단일 벡터 표현을 얻을 수도 있어요. 이 접근을 쓰면 모든 것을 단일 패스로 완료할 수 있어요.

Qdrant에서 복잡한 리랭킹 파이프라인을 구축하는 실험을 시작하는 가장 간단한 방법은 Qdrant Cloud의 영구 무료 클러스터를 사용하고 Qdrant 문서를 읽는 거예요.

이 실험들의 소스 코드는 오픈소스이고, Qdrant와 BeIR 라이브러리의 통합인 beir-qdrant를 사용해요.

향후 방향과 연구 기회

검색 과정에 출력 토큰 임베딩을 사용한 초기 실험은 유망한 결과를 얻었어요. 하지만 이러한 발견을 검증하고 초기 검색에 희소 방법을 통합하는 가능성을 탐구하기 위해 추가 벤치마크를 진행할 계획이에요. 또한 멀티벡터 표현에 대한 양자화의 영향과 검색 품질에 미치는 효과를 조사할 거예요. 마지막으로, 많은 애플리케이션에서 중요한 요소인 검색 속도를 평가할 계획입니다.

더 알아보기 (Learn more)