⏱️ 예상 읽기 시간: 12분

서론

시각적 문서 검색(Visual Document Retrieval) 분야에서 새로운 이정표가 세워졌습니다. NVIDIA에서 2025년 6월 27일에 공개한 llama-nemoretriever-colembed-3b-v1 모델이 ViDoRe V1, V2, 그리고 MTEB VisualDocumentRetrieval 벤치마크에서 모두 1위를 차지하며 업계의 주목을 받고 있습니다.

이 모델은 텍스트 쿼리와 이미지 문서를 동시에 처리할 수 있는 멀티모달 임베딩 모델로, 특히 RAG(Retrieval-Augmented Generation) 시스템에서 문서가 이미지 형태로 저장된 경우의 검색 성능을 획기적으로 개선했습니다.

본 포스트에서는 이 혁신적인 모델의 아키텍처부터 실제 사용법까지 상세히 분석해보겠습니다.

모델 개요 및 핵심 특징

기본 정보

  • 모델명: nvidia/llama-nemoretriever-colembed-3b-v1
  • 매개변수: 4.41B (44억 개)
  • 아키텍처: Transformer 기반 멀티모달 임베딩
  • 라이센스: NVIDIA Non-Commercial License (연구용 전용)
  • 공개일: 2025년 6월 27일

핵심 특징

1. 멀티모달 입력 지원

  • 쿼리: 텍스트 형태 (최대 8192 토큰)
  • 문서: 텍스트 또는 이미지 형태 (PIL 이미지, 512x512 타일로 자동 분할)

2. ColBERT 스타일 임베딩

  • Late interaction 방식의 멀티벡터 표현
  • 토큰 레벨에서의 세밀한 매칭 가능

3. 뛰어난 벤치마크 성능

  • ViDoRe V1: 0.9100 (nDCG@5)
  • ViDoRe V2: 0.6352 (nDCG@5)
  • MTEB Visual Document Retrieval: 0.8315 (Rank Borda)

모델 아키텍처 심화 분석

기반 모델 구성

모델은 두 개의 강력한 기반 모델을 결합하여 구성됩니다:

1. 비전 인코더: google/siglip2-giant-opt-patch16-384

  • 이미지 처리를 담당하는 비전 트랜스포머
  • 384x384 해상도의 패치 기반 처리
  • Giant 규모의 모델로 뛰어난 시각적 이해 능력

2. 언어 모델: meta-llama/Llama-3.2-3B

  • 텍스트 처리 및 멀티모달 융합 담당
  • 30억 매개변수의 효율적인 언어 모델
  • Qwen 모델을 활용한 추가 개선

입출력 사양

구분 쿼리 문서
입력 타입 텍스트 텍스트 또는 이미지
입력 형식 List[str] List[str] 또는 List[PIL.Image]
최대 길이 8192 토큰 8192 토큰
이미지 처리 - 512x512 타일로 자동 분할
출력 형식 [batch_size × seq_length × embedding_dim] [batch_size × seq_length × embedding_dim]

성능 벤치마크 상세 분석

ViDoRe (Visual Document Retrieval) 벤치마크

ViDoRe는 시각적 문서 검색을 위한 대표적인 벤치마크로, 다양한 도메인, 언어, 설정을 포괄합니다.

벤치마크 1B 모델 3B 모델 평가 지표
ViDoRe V1 (2025.06.27) 0.9050 0.9100 nDCG@5
ViDoRe V2 (2025.06.27) 0.6209 0.6352 nDCG@5
MTEB Visual Document Retrieval 0.8238 0.8315 Rank Borda

NVIDIA 검색 모델 라인업 비교

모델명 용도 특징
llama-NemoRetriever-ColEmbed-3B-v1 연구용 ViDoRe 1위, 최고 성능
llama-NemoRetriever-ColEmbed-1B-v1 연구용 경량화 버전
llama-3_2-nemoretriever-1b-vlm-embed-v1 상용 프로덕션 멀티모달 임베딩
NV-Embed-v2 연구용 MTEB 텍스트 임베딩 1위

설치 및 환경 설정

필수 라이브러리 설치

# 특정 버전의 transformers 설치 (중요!)
pip install transformers==4.49.0

# Flash Attention 설치 (성능 최적화)
pip install flash-attn==2.6.3 --no-build-isolation

# 이미지 처리용 라이브러리
pip install pillow requests torch

시스템 요구사항

권장 하드웨어:

  • NVIDIA A100 40GB/80GB 또는 H100 80GB
  • CUDA 지원 GPU (최소 8GB VRAM)
  • Linux 운영체제 (권장)

소프트웨어:

  • Python 3.8+
  • CUDA 11.8+
  • PyTorch 2.0+

기본 사용법 및 코드 예제

모델 로딩

import torch
from transformers import AutoModel
from PIL import Image
import requests
from io import BytesIO

# 모델 로딩 (GPU 사용)
model = AutoModel.from_pretrained(
    'nvidia/llama-nemoretriever-colembed-3b-v1',
    device_map='cuda',
    trust_remote_code=True,
    torch_dtype=torch.bfloat16,
    attn_implementation="flash_attention_2",
    revision='50c36f4d5271c6851aa08bd26d69f6e7ca8b870c'
).eval()

print("모델 로딩 완료!")

실제 검색 예제

# 검색 쿼리 정의
queries = [
    '2차 세계대전에서 독일 인구의 몇 퍼센트가 사망했나요?',
    '2018년 가스 처리로부터 포집된 CO2는 몇 백만 톤인가요?',
    '일본인의 평균 CO2 배출량은 얼마인가요?'
]

# 문서 이미지 URL
image_urls = [
    'https://upload.wikimedia.org/wikipedia/commons/3/35/Human_losses_of_world_war_two_by_country.png',
    'https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/20210413_Carbon_capture_and_storage_-_CCS_-_proposed_vs_implemented.svg/2560px-20210413_Carbon_capture_and_storage_-_CCS_-_proposed_vs_implemented.svg.png',
    'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/20210626_Variwide_chart_of_greenhouse_gas_emissions_per_capita_by_country.svg/2880px-20210626_Variwide_chart_of_greenhouse_gas_emissions_per_capita_by_country.svg.png'
]

# 이미지 다운로드 및 로딩
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
images = []

for url in image_urls:
    response = requests.get(url, headers=headers)
    image = Image.open(BytesIO(response.content))
    images.append(image)

print(f"이미지 {len(images)}개 로딩 완료!")

임베딩 생성 및 검색

# 쿼리 및 문서 임베딩 생성
print("임베딩 생성 중...")
query_embeddings = model.forward_queries(queries, batch_size=8)
passage_embeddings = model.forward_passages(images, batch_size=8)

# 유사도 점수 계산
scores = model.get_scores(query_embeddings, passage_embeddings)

print("검색 결과 점수:")
print(scores)
# 예상 출력:
# tensor([[13.9970, 11.4219, 12.1225],
#         [11.4157, 14.6388, 12.0341],
#         [ 9.9023,  9.8857, 11.3387]], device='cuda:0')

# 결과 해석
for i, query in enumerate(queries):
    best_match_idx = torch.argmax(scores[i]).item()
    best_score = scores[i][best_match_idx].item()
    print(f"쿼리 {i+1}: '{query}'")
    print(f"최적 매칭 문서: {best_match_idx+1}, 점수: {best_score:.4f}")
    print("-" * 50)

배치 처리 최적화

def batch_retrieval(queries, images, batch_size=4):
    """
    대량의 쿼리와 문서를 효율적으로 처리하는 함수
    """
    all_query_embeddings = []
    all_passage_embeddings = []
    
    # 쿼리 배치 처리
    for i in range(0, len(queries), batch_size):
        batch_queries = queries[i:i+batch_size]
        batch_embeddings = model.forward_queries(batch_queries, batch_size=batch_size)
        all_query_embeddings.append(batch_embeddings)
    
    # 문서 배치 처리
    for i in range(0, len(images), batch_size):
        batch_images = images[i:i+batch_size]
        batch_embeddings = model.forward_passages(batch_images, batch_size=batch_size)
        all_passage_embeddings.append(batch_embeddings)
    
    # 결합
    query_embeddings = torch.cat(all_query_embeddings, dim=0)
    passage_embeddings = torch.cat(all_passage_embeddings, dim=0)
    
    return query_embeddings, passage_embeddings

# 사용 예제
if len(queries) > 10 or len(images) > 10:
    query_emb, passage_emb = batch_retrieval(queries, images, batch_size=4)
    scores = model.get_scores(query_emb, passage_emb)

고급 활용법

1. 멀티모달 RAG 시스템 구축

class MultimodalRAGRetriever:
    def __init__(self, model):
        self.model = model
        self.document_embeddings = None
        self.documents = []
    
    def add_documents(self, documents):
        """텍스트와 이미지 문서를 임베딩 데이터베이스에 추가"""
        self.documents.extend(documents)
        embeddings = self.model.forward_passages(documents, batch_size=8)
        
        if self.document_embeddings is None:
            self.document_embeddings = embeddings
        else:
            self.document_embeddings = torch.cat([self.document_embeddings, embeddings], dim=0)
    
    def search(self, query, top_k=5):
        """쿼리에 대해 상위 k개 문서 검색"""
        query_embedding = self.model.forward_queries([query], batch_size=1)
        scores = self.model.get_scores(query_embedding, self.document_embeddings)
        
        top_indices = torch.topk(scores[0], k=top_k).indices
        return [(self.documents[idx], scores[0][idx].item()) for idx in top_indices]

# 사용 예제
retriever = MultimodalRAGRetriever(model)
retriever.add_documents(images)

results = retriever.search("CO2 배출량 차트를 찾아주세요", top_k=3)
for doc, score in results:
    print(f"점수: {score:.4f}")

2. 문서 클러스터링

from sklearn.cluster import KMeans
import numpy as np

def cluster_documents(images, n_clusters=3):
    """이미지 문서들을 의미적으로 클러스터링"""
    # 문서 임베딩 생성
    embeddings = model.forward_passages(images, batch_size=8)
    
    # 평균 풀링으로 문서별 단일 벡터 생성
    doc_vectors = embeddings.mean(dim=1).cpu().numpy()
    
    # K-means 클러스터링
    kmeans = KMeans(n_clusters=n_clusters, random_state=42)
    clusters = kmeans.fit_predict(doc_vectors)
    
    return clusters

# 사용 예제
if len(images) >= 3:
    clusters = cluster_documents(images, n_clusters=3)
    print("문서 클러스터링 결과:", clusters)

벤치마크 평가 실행

ViDoRe 벤치마크 평가

# ViDoRe 벤치마크 설치
pip install git+https://github.com/illuin-tech/vidore-benchmark@e0eb9032e7e00adc8aa6f9cb35d5a9371f67485a

# transformers 버전 다운그레이드 (필수)
pip install transformers==4.49.0

# ViDoRe V1, V2 평가 실행
CUDA_VISIBLE_DEVICES=0 python3 vidore_eval.py \
    --model_name_or_path nvidia/llama-nemoretriever-colembed-3b-v1 \
    --savedir_datasets ./results/ \
    --model_revision 50c36f4d5271c6851aa08bd26d69f6e7ca8b870c

MTEB 평가

# MTEB 설치
pip install git+https://github.com/embeddings-benchmark/mteb

# MTEB Visual Document Retrieval 평가
CUDA_VISIBLE_DEVICES=0 python3 mteb_eval.py \
    --model_name_or_path nvidia/llama-nemoretriever-colembed-3b-v1

성능 최적화 팁

1. 메모리 최적화

# 모델 로딩 시 메모리 효율성 개선
model = AutoModel.from_pretrained(
    'nvidia/llama-nemoretriever-colembed-3b-v1',
    device_map='auto',  # 자동 디바이스 매핑
    torch_dtype=torch.bfloat16,  # 메모리 절약
    low_cpu_mem_usage=True,  # CPU 메모리 절약
    trust_remote_code=True
).eval()

# 그래디언트 비활성화 (추론 시)
with torch.no_grad():
    embeddings = model.forward_queries(queries)

2. 배치 크기 조정

# GPU 메모리에 따른 최적 배치 크기 설정
def get_optimal_batch_size():
    gpu_memory = torch.cuda.get_device_properties(0).total_memory
    if gpu_memory > 40 * 1024**3:  # 40GB 이상
        return 16
    elif gpu_memory > 20 * 1024**3:  # 20GB 이상
        return 8
    else:  # 20GB 미만
        return 4

optimal_batch_size = get_optimal_batch_size()
embeddings = model.forward_queries(queries, batch_size=optimal_batch_size)

3. 이미지 전처리 최적화

def optimize_images(images, max_size=(1024, 1024)):
    """이미지 크기 최적화로 처리 속도 향상"""
    optimized_images = []
    for img in images:
        # 큰 이미지 리사이징
        if max(img.size) > max(max_size):
            img.thumbnail(max_size, Image.Resampling.LANCZOS)
        
        # RGB 모드로 변환
        if img.mode != 'RGB':
            img = img.convert('RGB')
        
        optimized_images.append(img)
    
    return optimized_images

# 사용 예제
optimized_images = optimize_images(images)
embeddings = model.forward_passages(optimized_images, batch_size=8)

실제 활용 사례

1. 기업 문서 검색 시스템

class EnterpriseDocumentSearch:
    """기업 내부 문서 검색을 위한 클래스"""
    
    def __init__(self, model):
        self.model = model
        self.indexed_documents = {}
        self.embeddings_db = None
    
    def index_pdf_pages(self, pdf_path):
        """PDF 문서의 각 페이지를 이미지로 변환하여 인덱싱"""
        import fitz  # PyMuPDF
        
        doc = fitz.open(pdf_path)
        pages = []
        
        for page_num in range(len(doc)):
            page = doc.load_page(page_num)
            pix = page.get_pixmap()
            img_data = pix.tobytes("png")
            image = Image.open(BytesIO(img_data))
            pages.append(image)
            
            self.indexed_documents[len(self.indexed_documents)] = {
                'source': pdf_path,
                'page': page_num,
                'image': image
            }
        
        # 임베딩 생성 및 저장
        embeddings = self.model.forward_passages(pages, batch_size=4)
        if self.embeddings_db is None:
            self.embeddings_db = embeddings
        else:
            self.embeddings_db = torch.cat([self.embeddings_db, embeddings], dim=0)
    
    def search_documents(self, query, top_k=10):
        """자연어 쿼리로 문서 검색"""
        query_embedding = self.model.forward_queries([query], batch_size=1)
        scores = self.model.get_scores(query_embedding, self.embeddings_db)
        
        top_indices = torch.topk(scores[0], k=min(top_k, len(scores[0]))).indices
        
        results = []
        for idx in top_indices:
            doc_info = self.indexed_documents[idx.item()]
            score = scores[0][idx].item()
            results.append({
                'source': doc_info['source'],
                'page': doc_info['page'],
                'score': score,
                'image': doc_info['image']
            })
        
        return results

2. 다국어 문서 검색

def multilingual_search_demo():
    """다국어 환경에서의 검색 성능 테스트"""
    
    # 다국어 쿼리
    multilingual_queries = [
        "What is the population growth rate?",  # 영어
        "인구 증가율은 얼마인가요?",  # 한국어
        "人口増加率はどのくらいですか?",  # 일본어
        "¿Cuál es la tasa de crecimiento poblacional?",  # 스페인어
    ]
    
    # 각 언어별 쿼리에 대한 검색 결과
    for query in multilingual_queries:
        query_embedding = model.forward_queries([query], batch_size=1)
        scores = model.get_scores(query_embedding, passage_embeddings)
        best_match = torch.argmax(scores[0]).item()
        
        print(f"쿼리: {query}")
        print(f"최적 매칭 문서: {best_match + 1}")
        print(f"점수: {scores[0][best_match].item():.4f}")
        print("-" * 60)

제한사항 및 주의사항

라이센스 제한

  • 연구용 전용: 상업적 사용 불가
  • NVIDIA Non-Commercial License 적용
  • 프로덕션 환경에서는 상용 버전 사용 필요

기술적 제한사항

1. 하드웨어 요구사항

  • 고성능 GPU 필요 (최소 8GB VRAM)
  • CPU 전용 환경에서는 매우 느린 성능

2. 메모리 사용량

  • 4.4B 매개변수로 인한 높은 메모리 사용량
  • 대량 문서 처리 시 OOM 위험

3. 처리 속도

  • 이미지 처리로 인한 상대적으로 느린 속도
  • 실시간 검색에는 최적화 필요

개선 방안

# 메모리 효율적인 처리를 위한 팁
def memory_efficient_processing(queries, images, chunk_size=100):
    """메모리 효율적인 대량 처리"""
    all_scores = []
    
    for i in range(0, len(images), chunk_size):
        chunk_images = images[i:i+chunk_size]
        chunk_embeddings = model.forward_passages(chunk_images, batch_size=4)
        
        for query in queries:
            query_embedding = model.forward_queries([query], batch_size=1)
            chunk_scores = model.get_scores(query_embedding, chunk_embeddings)
            all_scores.append(chunk_scores)
        
        # 메모리 정리
        del chunk_embeddings
        torch.cuda.empty_cache()
    
    return all_scores

향후 발전 방향

1. 성능 개선 예상

  • 더 큰 모델: 7B, 13B 버전 출시 예상
  • 추론 최적화: TensorRT, ONNX 지원 확대
  • 효율성 개선: 지식 증류를 통한 경량화

2. 기능 확장

  • 동영상 지원: 비디오 문서 검색 기능
  • 3D 문서: 3차원 문서 이해 능력
  • 실시간 처리: 스트리밍 검색 지원

3. 생태계 확장

  • 상용 버전: 프로덕션 환경용 라이센스
  • 클라우드 API: 쉬운 접근성 제공
  • 파인튜닝 지원: 도메인 특화 모델 개발

결론

NVIDIA의 llama-nemoretriever-colembed-3b-v1은 시각적 문서 검색 분야에서 새로운 표준을 제시한 혁신적인 모델입니다. ViDoRe와 MTEB 벤치마크에서의 압도적인 성능은 이 모델이 단순히 연구 단계를 넘어 실용적인 가치를 제공함을 보여줍니다.

주요 강점:

  • 멀티모달 검색에서의 뛰어난 성능
  • ColBERT 스타일의 정밀한 매칭
  • 실제 활용 가능한 코드 예제와 도구

활용 권장 분야:

  • 기업 문서 관리 시스템
  • 멀티모달 RAG 애플리케이션
  • 시각적 지식 베이스 구축
  • 연구 및 학술 문서 검색

다만 연구용 라이센스와 높은 하드웨어 요구사항은 상용화에 있어 고려해야 할 요소입니다. 향후 상용 버전의 출시와 최적화된 추론 엔진의 개발이 기대되는 상황입니다.

시각적 문서 검색 기술의 발전은 우리가 정보를 찾고 활용하는 방식을 근본적으로 변화시킬 것입니다. 이 모델은 그러한 미래로 가는 중요한 이정표가 될 것으로 확신합니다.


참고 링크: