Sentence Transformers v6.0 다중 벡터 인코더 릴리스
요약
Sentence Transformers 6.0은 ColBERT 스타일의 후기 상호작용(다중 벡터) 검색을 위한 네 번째 모델 유형인 MultiVectorEncoder를 도입하여, 텍스트 및 멀티모달 데이터에 대해 더 강력한 의미 검색과 시각 문서 검색을 가능하게 하며, 토큰 수준의 인덱스가 더 커지는 비용을 수반합니다.
다중 벡터 모델이란?
다중 벡터 모델은 전체 문장 하나를 단일 벡터로 압축하는 대신 각 토큰마다 하나의 임베딩을 유지합니다. 인코딩 후, 쿼리는 문서와 MaxSim 연산자를 사용하여 점수를 매깁니다. 이 연산자는 각 쿼리 토큰에 대해 문서 내 모든 토큰 중 가장 높은 코사인 유사도를 합산합니다. 이 방식은 정확한 토큰 매칭(예: 제품 코드)을 보존하고, 토큰 임베딩이 맥락화되어 의미적 동의어 표현도 포착할 수 있습니다.
"'Where do penguins live?'라는 쿼리를 'Penguins inhabit Antarctica.'라는 문서와 비교할 때, 쿼리 토큰 live는 inhabit와 0.94의 유사도로 최고 매칭을 찾습니다." – Hugging Face 블로그
MaxSim 연산자
쿼리 토큰 집합을 $Q$, 문서 토큰 집합을 $D$라 하면:
$$ \text{MaxSim}(Q, D) = \sum_{q_i \in Q} \max_{d_j \in D} ; q_i \cdot d_j $$
임베딩이 L2 정규화되어 있으므로 각 점곱은 $[-1, 1]$ 범위의 코사인 유사도이며, 전체 점수는 $[-|Q|, |Q|]$ 범위에 있습니다.
균형 조건
- 품질 향상 – 토큰 수준 매칭은 단일 정확한 텀이나 다수의 독립적인 제약 조건에 의존하는 쿼리의 검색을 향상시킵니다.
- 인덱스 비용 증가 – 토큰당 하나의 벡터로 인해 저장 공간이 증가합니다(예: 128차원 모델에서 4,874개 문장은 311MB, 384차원 밀집 모델은 7.5MB). PLAID 또는 계층적 토큰 풀링과 같은 압축 기술로 이 부담을 줄일 수 있습니다.
설치
pip install -U sentence-transformers
# 시각 문서 검색을 위해 이미지 확장 기능 추가
pip install -U "sentence-transformers[image]"
Sentence Transformers v6.0은
transformersv5.x,torch≥ 2.2,huggingface-hubv1.x를 필요로 합니다.
다중 벡터 모델 로딩
from sentence_transformers import MultiVectorEncoder
model = MultiVectorEncoder("lightonai/LateOn")
multi-vector과 sentence-transformers 태그가 붙은 체크포인트는 PyLate, Stanford-NLP ColBERT, 또는 colpali-engine에서 온 것이든 상관없이 직접 로드됩니다. ColPali 시각 모델의 경우, 작은 저장소 구성이 필요합니다(자세한 내용은 지원되는 모델 참조).
모델 구성 확인
model = MultiVectorEncoder("colbert-ir/colbertv2.0")
print(model)
print(model.prompts)
일반적인 출력은 Transformer, 토큰 수준 Dense 프로젝션(예: 128차원), 스킵리스트 토큰을 위한 MultiVectorMask, 그리고 Normalize 계층을 보여줍니다. 쿼리 및 문서 길이 제한(예: 32 및 180 토큰)도 표시됩니다.
쿼리 및 문서 인코딩
다중 벡터 모델은 비대칭적입니다. 전용 메서드를 사용해야 합니다:
queries = ["What is the capital of France?"]
documents = ["Paris is the capital of France.", "Berlin is the capital of Germany."]
q_emb = model.encode_query(queries) # shape (n_query_tokens, dim)
d_emb = model.encode_document(documents) # list of (n_doc_tokens, dim)
각 문서는 토큰 수와 같은 첫 번째 차원을 가진 행렬을 반환하므로, 패딩 없이 단일 직사각형 배치로 스택할 수 없습니다.
MaxSim으로 점수 매기기
scores = model.similarity(q_emb, d_emb) # 모든 쌍에 대한 MaxSim 행렬
print(scores)
결과는 코사인 유사도의 합산된 텐서입니다. 쿼리 길이에 따라 스케일이 달라지므로, 서로 다른 쿼리 제한을 가진 모델 간에 점수를 직접 비교할 수 없습니다. 범위가 제한된 지표를 얻으려면 MeanMaxSim으로 전환하세요:
model = MultiVectorEncoder("lightonai/LateOn", similarity_fn_name="meanmaxsim")
print(model.similarity(q_emb, d_emb)) # 값은 [0, 1] 범위
의미 검색(완전 탐색)
작은 코퍼스의 경우 전체 컬렉션을 한 번 인코딩하고, 쿼리 시에 완전한 MaxSim을 실행할 수 있습니다:
from datasets import load_dataset
from sentence_transformers import MultiVectorEncoder
corpus = list(dict.fromkeys(load_dataset("sentence-transformers/natural-questions", split="train[:5000]")["answer"]))
model = MultiVectorEncoder("lightonai/LateOn")
corpus_emb = model.encode_document(corpus, convert_to_tensor=True)
query = "when did richmond last play in a preliminary final"
q_emb = model.encode_query([query], convert_to_tensor=True)
score_vec = model.similarity(q_emb, corpus_emb)[0]
top_scores, top_idx = score_vec.topk(3)
for s, i in zip(top_scores.tolist(), top_idx.tolist()):
print(f"{s:.4f} {corpus[i][:100]}")
RTX 3090에서 4,874개 문장 코퍼스는 약 20초 내에 인코딩되며, 각 쿼리 점수는 약 120ms 소요됩니다.
검색-재순위 패턴
빠른 밀집 이중 인코더와 다중 벡터 재순위 모델을 결합하여 전체 후기 상호작용 인덱스를 만들지 않아도 됩니다:
from sentence_transformers import SentenceTransformer, MultiVectorEncoder, util
retriever = SentenceTransformer("jinaai/jina-embeddings-v5-text-nano-retrieval")
reranker = MultiVectorEncoder("perplexity-ai/pplx-embed-v1-late-0.6b", trust_remote_code=True)
# 1️⃣ 밀집 모델로 상위-k 검색
corpus_emb = retriever.encode_document(corpus, convert_to_tensor=True)
hits = util.semantic_search(retriever.encode_query([query], convert_to_tensor=True), corpus_emb, top_k=50)[0]
candidates = [corpus[h['corpus_id']] for h in hits]
# 2️⃣ MaxSim으로 후보 재순위
q_emb = reranker.encode_query([query])
d_emb = reranker.encode_document(candidates)
rerank_scores = reranker.similarity(q_emb, d_emb)[0]
print(rerank_scores.argsort(descending=True)[:3])
선택된 후보만 다중 벡터로 인코딩되므로 메모리 사용량이 크게 줄어들지만, 후기 상호작용 품질은 유지됩니다.
인덱싱 옵션
다음 벡터 데이터베이스는 MaxSim을 위한 네이티브 다중 벡터 필드를 지원합니다:
- Qdrant (v1.10+)
- Weaviate (v1.29+)
- Vespa (장문 ColBERT 지원)
- LanceDB (v0.15.0+)
- VectorChord (Postgres 확장)
- Milvus (v2.6.4, 배열-구조)
- fast-plaid (Rust 구현, 근사적이지만 빠름)
각 시스템은 encode_document가 반환하는 토큰 수준 텐서 목록을 수신합니다. fast-plaid 예시(정확한 인덱싱):
from fast_plaid import search
fast_plaid = search.FastPlaid(index="nlp-index", device="cuda")
fast_plaid.create(documents_embeddings=document_emb)
results = fast_plaid.search(queries_embeddings=q_emb.unsqueeze(0), top_k=3)
원시 608k 토큰 벡터는 float32 기준 311MB를 차지하지만, PLAID의 중심점-잔차 방식으로 압축하면 약 92MB로 줄어듭니다.
시각 문서 검색
ColPali 스타일 모델은 페이지 이미지를 이미지 패치의 토큰 시퀀스로 간주합니다. 동일한 API가 작동합니다:
model = MultiVectorEncoder("vidore/colqwen2.5-v0.2")
queries = ["What is the variable on the y-axis?", "Total outlay is maximum in which year?"]
images = [".../doc1.jpg", ".../doc2.jpg", ".../doc3.jpg", ".../doc4.jpg"]
q_emb = model.encode_query(queries)
d_emb = model.encode_document(images)
print(model.similarity(q_emb, d_emb))
한 페이지는 수백 개의 토큰 벡터를 생성할 수 있습니다(예: 755 × 128), 따라서 토큰 풀링이 유용해집니다(다음 섹션 참조).
오디오 및 비디오 검색
오미니 모달 모델 vidore/colqwen-omni-v0.1은 어떤 변환 단계 없이 텍스트, 이미지, 오디오, 비디오를 지원합니다.
model = MultiVectorEncoder("vidore/colqwen-omni-v0.1", model_kwargs={"dtype": torch.bfloat16})
# 오디오 예시
audio = [...] # 원시 웨이브폼 목록 (16kHz 모노)
q_emb = model.encode_query(["medicine for car nausea"])
d_emb = model.encode_document(audio, batch_size=2)
print(model.similarity(q_emb, d_emb)[0].topk(3))
모델이 이미지-텍스트 쌍으로 훈련되었기 때문에, 제로샷 오디오 검색이 가능합니다. 이는 교차 모달 토큰 정렬을 학습하기 때문입니다.
해석 가능성
MaxSim의 토큰 수준 분해는 정확한 기여도를 제공합니다:
- 이미지용 히트맵 – 페이지 패치 위에 토큰 수준 점수를 겹쳐 표시.
- 텍스트 유사도 맵 – 각 쿼리 토큰에 대해 최고 매칭 문서 토큰을 나열.
저장소에는 heatmap.py와 text_similarity_map.py 스크립트가 포함되어 있어 토큰별 기여도를 출력하고 소스 문서를 강조 표시합니다.
인덱스 크기 감소를 위한 토큰 풀링
HierarchicalTokenPooling은 문서의 토큰 벡터를 군집화(코사인 거리 기반 워드 연결)하고, 각 군집을 중심점으로 대체하여 대략 1 / pool_factor 수준의 크기 감소를 달성합니다.
from sentence_transformers.multi_vector_encoder.modules import HierarchicalTokenPooling
pool = HierarchicalTokenPooling(pool_factor=2)
pooled_emb = model.encode_document(docs, token_pooling=pool)
자연 질문 코퍼스에서 pool_factor=2는 인덱스를 반으로 줄이지만(311MB → 156MB), BEIR에서 약 0.4%의 NDCG 하락만 발생합니다. 더 높은 요인은 수익 감소 효과를 보이며, 결정하기 전에 자체 데이터에서 평가하는 것이 좋습니다.
추론 속도 향상
- GPU – fp16 + Flash Attention (
attn_implementation="flash_attention_2")는 fp32 대비 약 2.4배의 처리량을 제공합니다. - CPU – OpenVINO(지원 시) 및 int8 양자화는 <0.5% 정확도 손실로 적당한 속도 향상을 제공합니다.
attend=False를 사용하는 쿼리 확장 마스크를 사용하는 모델은 마스크된 토큰이 제거되므로 Flash Attention을 사용할 수 없습니다. 대신 기본sdpa구현을 사용하세요.
평가
MultiVectorNanoBEIREvaluator는 13개 하위 벤치마크를 즉시 실행할 수 있습니다:
from sentence_transformers import MultiVectorEncoder
from sentence_transformers.multi_vector_encoder.evaluation import MultiVectorNanoBEIREvaluator
model = MultiVectorEncoder("lightonai/LateOn")
eval = MultiVectorNanoBEIREvaluator()
print(eval(model))
결과는 LateOn(다중 벡터, 128차원)이 13개 데이터셋 중 9개에서 밀집 대응 모델인 lightonai/DenseOn(0.6764)보다 높은 평균 NDCG@10(0.6868)을 달성함을 보여줍니다.
PyLate / colpali-engine에서 마이그레이션
| PyLate | Sentence Transformers |
|---|---|
pylate.models.ColBERT(...) |
MultiVectorEncoder(...) |
model.encode(..., is_query=True) |
model.encode_query(...) |
model.encode(..., is_query=False) |
model.encode_document(...) |
pylate.scores.colbert_scores |
model.similarity |
pylate.indexes.PLAID |
PyLate를 계속 사용하거나 fast-plaid / Qdrant로 전환 |
| colpali-engine | Sentence Transformers |
|---|---|
ColQwen2.from_pretrained(...) |
MultiVectorEncoder(...) |
processor.process_queries(...) |
model.encode_query(...) |
processor.process_images(...) |
model.encode_document(...) |
processor.score_multi_vector(...) |
model.similarity(...) |
접두사, 쿼리 확장, 스킵리스트 처리에 대한 자세한 내용은 마이그레이션 가이드를 참조하세요.
지원되는 모델 (요약)
텍스트 검색 (선택)
| 모델 | 파라미터 수 | 차원 | NanoBEIR |
|---|---|---|---|
lightonai/LateOn-regularized |
149M | 128 | 0.6897 |
lightonai/LateOn |
149M | 128 | 0.6868 |
LiquidAI/LFM2.5-ColBERT-350M |
353M | 128 | 0.6864 |
mixedbread-ai/mxbai-edge-colbert-v0-32m |
32M | 64 | 0.6524 |
colbert-ir/colbertv2.0 |
110M | 128 | 0.6053 |
시각 문서 검색 (선택)
| 모델 | 파라미터 수 | 차원 | NanoViDoRe |
|---|---|---|---|
webAI-Official/webAI-ColVec1.1-8b |
8.4B | 640 | 0.6580 |
tencent/EVIE-Preview-4.5B |
4.54B | 128 | 0.6405 |
vidore/colqwen2.5-v0.2 |
3.8B | 128 | 0.5402 |
vidore/colpali-v1.3 |
2.9B | 128 | 0.4802 |
허브에 multi-vector 태그가 붙은 모든 모델은 호환되며, 시각 모델은 저장소 구성 PR이 병합될 때까지 revision이 필요할 수 있습니다.
감사의 말
이 구현은 ColBERT(Khattab & Zaharia, 2020), LightOn의 PyLate 및 fast-plaid, ColPali 연구 팀, 그리고 Clavié, Chaffin, Adams의 토큰 풀링 연구에 기반합니다. MTEB 벤치마크 기여자 및 모든 체크포인트 저작자에게 추가 감사를 표합니다.
추가 자료
- 문서 – 사용법, 사전 훈련된 모델, 사용자 정의 모델 생성, 효율성, API 참조(원본 게시물의 링크 포함).
- 예제 스크립트 – 의미 검색, 검색-재순위, 토큰 풀링, 히트맵, NanoBEIR 평가.
- 훈련 가이드 – 개요, 손실 함수, LateOn/mLateOn용 레시피 스크립트.
- 보조 블로그 포스트 – 밀집 훈련, 재순위 모델, 희소 인코더, 멀티모달 임베딩, 마트료시카 임베딩.
Sources
관련
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch