Field Log · Entry
ColBERT Late Interaction은 일반 dense retrieval과 뭐가 다를까?
ColBERT Late Interaction은 문서를 토큰별 embedding으로 저장하고 MaxSim으로 질문 단서를 찾는 검색 방식입니다. 일반 dense retrieval처럼 문서를 벡터 하나로 압축하지 않기 때문에, 장비명·알람명·조치 키워드처럼 작은 단서가 중요한 RAG 검색에서 차이가 납니다.
이 글은 “ColBERT도 사전 embedding을 하는데 왜 더 느린가?”라는 질문에 답합니다. 결론부터 말하면 병목은 문서를 매번 encoding하는 데 있지 않습니다. 병목은 미리 저장해 둔 많은 토큰 벡터 중에서 query token별 best match를 찾고, 그 MaxSim 점수를 합산하는 과정에 있습니다.
이 글이 답하는 질문
- ColBERT는 일반 dense embedding 검색과 무엇이 다른가?
- Late Interaction과 MaxSim은 정확히 어떤 계산인가?
- 문서 embedding을 미리 해두는데도 왜 계산과 저장 공간이 무거워지는가?
- 반도체 SOP/TSG/챗로그 같은 도메인 RAG에서는 어디에 배치하면 좋은가?
1. 결론부터: “사전 embedding”은 맞지만, 저장 단위가 다르다
일반적인 RAG 검색에서는 문서를 embedding해서 vector DB에 넣습니다. 이때 대부분의 dense retriever는 문서 하나를 벡터 하나로 만듭니다.
문서 A 전체 → 벡터 1개
문서 B 전체 → 벡터 1개
문서 C 전체 → 벡터 1개
질문 → 벡터 1개
검색할 때는 질문 벡터와 문서 벡터의 cosine similarity 또는 dot product를 계산합니다.
Score(q, d) = q · d
문서 하나당 비교가 거의 한 번입니다. 그래서 빠릅니다. 대신 문서 전체 의미를 벡터 하나에 압축하기 때문에, 장비명, 알람명, 부품명, 조치 키워드 같은 세부 단서가 흐려질 수 있습니다.
핵심 차이
일반 dense retrieval은 문서 1개 = 벡터 1개입니다. ColBERT는 문서 1개 = 토큰별 벡터 여러 개입니다.

그림 1. Dense retrieval은 벡터 하나끼리 비교하고, ColBERT는 토큰별 벡터를 마지막 단계에서 비교한다.
2. 일반 dense embedding 검색은 왜 빠른가?
일반 dense retrieval에서는 질문과 문서가 각각 하나의 벡터로 요약됩니다. 예를 들어 embedding 차원이 768이라면, 문서가 짧든 길든 최종적으로는 768차원 벡터 하나로 검색됩니다.
질문 벡터 1개 · 문서 벡터 1개 = 점수 1개
그래서 vector DB가 잘 처리할 수 있습니다. FAISS, Milvus, Qdrant, Weaviate 같은 시스템은 이런 단일 벡터 nearest neighbor search에 최적화되어 있습니다.
문제는 압축 손실입니다. 예를 들어 질문이 아래와 같다고 해보겠습니다.
INTEGER plus 설비에서 Recipe Body Unmatch alarm 원인과 조치 방법
이 질문에는 여러 단서가 있습니다.
INTEGER plus
Recipe Body Unmatch
alarm
원인
조치 방법
일반 dense embedding은 이 전체를 하나의 벡터에 섞습니다. 문서도 하나의 벡터에 섞습니다. 그래서 전체 의미가 비슷한 문서는 잘 찾지만, 특정 알람명이나 장비명이 실제로 문서 안에 있는지를 세밀하게 보기는 어렵습니다.
3. ColBERT는 문서를 토큰별로 저장한다
ColBERT는 문서를 이렇게 저장합니다.
문서 D
→ 토큰1 벡터
→ 토큰2 벡터
→ 토큰3 벡터
→ ...
→ 토큰N 벡터
질문도 비슷하게 표현합니다.
질문 Q
→ 질문토큰1 벡터
→ 질문토큰2 벡터
→ 질문토큰3 벡터
→ ...
즉 ColBERT의 검색은 “문서 전체가 질문 전체와 비슷한가?”만 보지 않습니다. 대신 다음 질문을 던집니다.
질문의 각 토큰이 문서 안에서 자기와 가장 잘 맞는 증거를 찾을 수 있는가?
이 구조가 바로 Late Interaction입니다. 질문과 문서를 처음부터 같이 넣지는 않습니다. 질문은 질문대로, 문서는 문서대로 먼저 인코딩합니다. 그리고 마지막 단계에서 토큰별로 상호작용합니다.
4. 그러면 왜 계산이 느려지는가?
병목은 “문서를 매번 embedding해야 해서”가 아닙니다. 문서 embedding은 이미 되어 있습니다. 느려지는 원인은 그 다음입니다.
ColBERT의 병목 미리 저장해둔 수많은 토큰 벡터들 중에서, query token별로 가장 잘 맞는 document token을 찾아야 합니다.
ColBERT의 핵심 scoring은 아래처럼 표현됩니다.
S(q, d) = Σᵢ maxⱼ qᵢ · dⱼ
말로 풀면 이렇습니다.
질문의 각 토큰 qᵢ마다
문서의 모든 토큰 dⱼ와 비교한다.
그중 가장 잘 맞는 점수 하나를 고른다.
이 점수들을 질문 토큰 전체에 대해 더한다.
이 연산을 보통 MaxSim이라고 부릅니다.

그림 2. 질문 토큰마다 문서 토큰 전체와 비교한 뒤, 가장 높은 점수만 선택하고 합산한다.
5. 숫자로 보면 차이가 확실해진다
문서 평균 길이가 200 토큰, 질문 길이가 16 토큰, embedding 차원이 128이라고 해보겠습니다.
일반 dense retrieval
문서 하나당 128차원 내적 1번이면 됩니다.
질문 벡터 1개 × 문서 벡터 1개
= 128차원 내적 1번
ColBERT
질문 토큰과 문서 토큰 전체를 비교해야 합니다.
질문 토큰 16개
× 문서 토큰 200개
× 128차원 내적
16 × 200 × 128 = 409,600
문서 하나를 비교하는 비용만 보면 ColBERT가 훨씬 무겁습니다. 실제 시스템은 PLAID, pruning, compression 같은 ColBERT 계열 최적화와 FAISS 같은 벡터 검색 백엔드를 조합해 이 계산을 줄입니다. 그래도 기본 구조상 일반 dense retrieval보다 계산과 메모리 접근이 무거운 것은 맞습니다.
6. 느려지는 진짜 이유 3가지
첫째, 저장해야 할 벡터 수가 많다
Dense retrieval에서는 문서 100만 개면 벡터도 100만 개입니다.
문서 100만 개 → 벡터 100만 개
ColBERT에서는 문서 100만 개, 문서당 평균 200토큰이면 다음과 같습니다.
문서 100만 개 × 200토큰 → 벡터 2억 개
벡터 수가 늘어나면 저장 공간도 커지고, 인덱스도 커지고, 메모리에서 읽어야 하는 양도 많아집니다. 실제 검색 속도에서는 연산량뿐 아니라 메모리에서 얼마나 많은 벡터를 읽어야 하는지가 매우 중요합니다.
둘째, 검색 시 MaxSim 계산이 필요하다
일반 dense retrieval은 cosine similarity 하나면 됩니다.
score = query_vector · doc_vector
ColBERT는 query token마다 document token 전체를 훑어야 합니다.
Recipe → 문서 안 모든 토큰과 비교 → 최고 점수 선택
Body → 문서 안 모든 토큰과 비교 → 최고 점수 선택
Unmatch → 문서 안 모든 토큰과 비교 → 최고 점수 선택
Alarm → 문서 안 모든 토큰과 비교 → 최고 점수 선택
셋째, 전체 문서에 바로 MaxSim을 하기는 어렵다
문서가 100만 개라면 이론적으로는 아래 연산이 필요합니다.
질문 토큰들 × 모든 문서의 모든 토큰
그래서 ColBERT 계열 시스템은 보통 다음과 같은 최적화를 사용합니다.
1. 문서 토큰 벡터들을 인덱싱한다.
2. query token별로 가까운 token 후보를 찾는다.
3. 그 token들이 속한 문서 후보를 모은다.
4. 후보 문서에 대해서만 MaxSim 점수를 계산한다.
5. 최종 top-k를 반환한다.
즉 ColBERT가 빠르게 동작하려면 단순 vector DB보다 더 복잡한 검색 엔진 구조가 필요합니다.

그림 3. 문서 토큰 벡터는 미리 인덱싱하고, 질문이 들어오면 후보 문서를 모은 뒤 MaxSim으로 점수를 계산한다.
7. 그러면 ColBERT는 느린 모델인가?
비교 대상에 따라 다릅니다.
| 방식 | 정확도 | 속도 | 저장 공간 | 특징 |
|---|---|---|---|---|
| Dense embedding | 보통 | 빠름 | 작음 | 문서 1개를 벡터 1개로 압축 |
| ColBERT | 높은 편 | 중간 | 큼 | 토큰별 matching 유지 |
| Cross-Encoder | 매우 높음 | 느림 | 작음 | query-document 쌍마다 모델 실행 |
Cross-Encoder와 비교하면 ColBERT는 빠릅니다. 문서 embedding을 미리 계산해둘 수 있기 때문입니다. 하지만 일반 dense retriever와 비교하면 ColBERT는 느리고 무겁습니다. 문서당 토큰 수만큼 벡터를 저장하고, query token과 document token 사이의 MaxSim을 계산해야 하기 때문입니다.
8. 그래도 ColBERT를 쓰는 이유
이유는 검색 품질입니다. 특히 아래처럼 정확한 용어가 중요한 질문에서 ColBERT가 의미를 갖습니다.
Flow Switch alarm 원인
Recipe Body Unmatch 조치
Baratron Gauge 이상
INTEGER plus interlock 발생
이런 질문은 전체 의미도 중요하지만, 장비명, 알람명, 부품명, 조치 키워드가 정확히 맞아야 합니다. ColBERT는 query의 각 토큰이 문서 안에서 자기 근거를 찾습니다.
Query:
Recipe Body Unmatch alarm 조치 방법
Document:
... recipe body mismatch alarm occurred ...
... check recipe body configuration ...
... re-download recipe and clear alarm ...
ColBERT는 대략 이런 식으로 봅니다.
Recipe ↔ recipe
Body ↔ body
Unmatch ↔ mismatch / unmatch
alarm ↔ alarm
조치 ↔ check / re-download / clear
이렇게 토큰별 증거를 잡기 때문에, 정확한 용어와 희귀 토큰이 중요한 도메인 검색에서는 single-vector dense embedding보다 유리할 수 있습니다.
9. 실무적으로는 어디에 놓으면 좋을까?
반도체 SOP/TSG/챗로그 검색 기준으로 보면 각 방식의 역할은 다음처럼 나눌 수 있습니다.
BM25 / SPLADE
정확한 장비명, 알람명, 부품명처럼 lexical signal이 강한 검색에 좋다.
Dense embedding
표현이 달라도 의미가 비슷한 문서를 찾는 데 좋다.
ColBERT
dense처럼 의미를 보면서도, 토큰별 근거 매칭을 유지한다.
현실적인 구조는 아래와 같습니다.
1차 후보 검색: BM25 / SPLADE / Dense
2차 정밀 검색 또는 rerank: ColBERT
최종 재정렬: Cross-Encoder 또는 LLM rerank
제가 반도체 SOP/TSG/챗로그 검색 구조를 설계할 때도 ColBERT는 특히 의미 있는 후보였습니다. 이런 도메인에서는 “전체적으로 비슷한 문서”보다 특정 장비명, 알람명, 부품명, 조치 표현이 문서 안에 실제로 있는지가 중요하기 때문입니다.
10. 한 문장 비유
일반 dense embedding은 문서 한 권을 읽고 요약문 한 줄만 저장하는 방식입니다.
문서 → 요약 벡터 1개
ColBERT는 문서 한 권을 읽고 단어별 증거 카드를 잔뜩 만들어두는 방식입니다.
문서 → 단어별 증거 카드 여러 장
검색할 때는 질문의 각 단어가 이 카드들 중에서 가장 잘 맞는 카드를 찾습니다. 그래서 ColBERT는 더 정확할 수 있지만, 카드가 너무 많아서 찾고 비교하는 비용이 커집니다.
마무리
ColBERT도 사전에 embedding을 해놓습니다. 하지만 일반 embedding 검색과 다른 점은 문서 전체를 하나의 벡터로 압축하지 않고, 토큰별 embedding을 유지한다는 것입니다.
따라서 ColBERT의 병목은 문서를 매번 encoding하는 데 있지 않습니다. 병목은 수많은 토큰 벡터 중에서 query token별 best match를 찾고, MaxSim 점수를 계산하는 과정에 있습니다.
ColBERT는 “빠른 요약 벡터 검색”이 아니라, “토큰별 증거를 살린 정밀 검색”에 가깝다.
관련해서 긴 문맥 인덱싱은 RAPTOR 정리에서, 후보 검색 병목은 SPLADE 전 단계 추적 글에서 이어서 볼 수 있습니다.
자주 묻는 질문
Q1. ColBERT는 문서를 검색할 때마다 다시 embedding하나요?
아닙니다. ColBERT도 문서 embedding은 미리 계산해둘 수 있습니다. 차이는 문서 하나를 벡터 하나로 압축하지 않고, 문서 토큰별 벡터를 저장한다는 점입니다.
Q2. ColBERT는 Cross-Encoder보다 항상 빠른가요?
대체로 Cross-Encoder보다 운영하기 쉽고 빠른 쪽에 가깝습니다. Cross-Encoder는 query-document 쌍마다 모델을 다시 실행해야 하지만, ColBERT는 문서 표현을 미리 저장하고 마지막 상호작용만 늦게 계산합니다.
Q3. ColBERT를 1차 검색기로 바로 쓰면 되나요?
작은 컬렉션에서는 가능하지만, 큰 컬렉션에서는 보통 BM25, SPLADE, dense retrieval로 후보를 줄인 뒤 ColBERT를 정밀 검색 또는 rerank 단계에 놓는 편이 안전합니다.
Q4. 정확한 키워드 검색이면 BM25만으로 충분하지 않나요?
정확한 장비명이나 알람명만 보면 BM25가 강합니다. 하지만 표현이 조금 달라지거나 문맥 단서가 함께 필요하면 BM25만으로는 부족할 수 있습니다. ColBERT는 dense 의미 표현을 쓰면서도 토큰별 증거 매칭을 유지한다는 점에서 중간 지점을 만듭니다.
참고자료
- ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT
- ColBERT 공식 GitHub 저장소
- ColBERTv2: Efficient and Effective Retrieval via Lightweight Late Interaction
- PLAID: An Efficient Engine for Late Interaction Retrieval
- Jina AI: What is ColBERT and late interaction and why they matter in search