콘텐츠로 이동

XGEN 2.0 임베딩 전용 서버와 배치 처리 최적화

2026.01 | switch-backend 기반 멀티모드 서빙과 대용량 문서 처리 최적화

배경

XGEN 1.0에서는 텍스트 생성과 임베딩 생성을 동일한 GPU에서 처리했다. 이로 인해 몇 가지 비효율성이 발생했다:

  • 리소스 경합: 텍스트 생성 중 임베딩 요청 시 GPU 메모리 부족
  • 배치 최적화 불가: 생성형 모델과 임베딩 모델의 서로 다른 최적 배치 크기
  • 모델 교체 오버헤드: 런타임 중 모델 전환 시 5-10초 지연
  • 메모리 효율성: 두 모델을 동시 메모리에 로딩할 때의 오버헤드

XGEN 2.0에서는 switch-backend 메커니즘을 도입하여 모드별 최적화된 서빙 환경을 구축했다.

switch-backend 아키텍처

핵심 설계 철학

# config.embedding 값에 따른 서버 모드 결정
if config.embedding:
    server_type = "embedding"  # 임베딩 전용 최적화
else:
    server_type = "generation" # 텍스트 생성 최적화

모드별 최적화 전략

구분 Generation Mode Embedding Mode
배치 크기 32-64 (대화형) 2048 (대용량 문서)
GPU 메모리 KV 캐시 최적화 시퀀스 길이 최적화
처리 방식 스트리밍 배치 처리
모델 로딩 텍스트 생성 모델 임베딩 전용 모델

임베딩 모드 구현

1. 서버 초기화 분기 처리

모델 서버 시작 시 설정 파일을 바탕으로 모드를 결정한다:

# main.py
def determine_server_mode(config):
    if hasattr(config, 'embedding') and config.embedding:
        return ServerMode.EMBEDDING
    return ServerMode.GENERATION

async def startup_event():
    server_mode = determine_server_mode(config)

    if server_mode == ServerMode.EMBEDDING:
        # 임베딩 최적화 설정
        await load_embedding_optimized_model()
    else:
        # 생성 최적화 설정  
        await load_generation_optimized_model()

2. 배치 크기 동적 조정

임베딩 처리에서는 긴 문서를 효율적으로 처리하기 위해 배치 크기를 대폭 확대했다:

# llama-server 설정 (Before)
--batch-size 512
--ubatch-size 128

# llama-server 설정 (After - Embedding Mode)
--batch-size 2048
--ubatch-size 512

이 변경으로 A4 용지 50페이지 분량의 문서도 한 번에 임베딩 처리가 가능해졌다.

3. model_type 파라미터 추가

switch-backend API에 model_type 파라미터를 추가하여 클라이언트에서 모드를 명시적으로 지정할 수 있도록 했다:

@app.post("/switch-backend")
async def switch_backend(
    model_name: str,
    model_type: Optional[str] = "generation"  # "embedding" 또는 "generation"
):
    if model_type == "embedding":
        await switch_to_embedding_mode(model_name)
    else:
        await switch_to_generation_mode(model_name)

Health Check 시스템 통합

Gateway 호환성 확보

XGEN Backend Gateway에서 모델 서버의 상태를 체크하기 위한 표준 엔드포인트를 추가했다:

# 기존 엔드포인트
@app.get("/health")
async def health_check():
    return {"status": "healthy"}

# Gateway 호환 엔드포인트 추가
@app.get("/management/health") 
@app.get("/api/management/health")
async def management_health():
    return {
        "status": "healthy",
        "server_mode": current_server_mode,
        "loaded_model": current_model_name,
        "gpu_memory_usage": get_gpu_memory_info()
    }

이로 인해 Istio Service Mesh의 Health Check와도 자연스럽게 연동되어, K3s 클러스터에서의 자동 장애 복구가 가능해졌다.

성능 최적화 결과

1. 임베딩 처리 속도 개선

대용량 문서 임베딩 처리 시간 비교:

# 10MB PDF 문서 (약 300페이지) 임베딩
# Before (Generation Mode): 45초
# After (Embedding Mode): 12초

# 벤치마크: 3.75배 성능 향상

2. 배치 처리 효율성

배치 크기 증가로 인한 GPU 활용률 개선:

  • Before (512 batch): GPU 사용률 65-70%
  • After (2048 batch): GPU 사용률 85-90%

특히 긴 문서의 청킹 없이 전체 임베딩이 가능해져, 문맥 정보 손실이 크게 줄어들었다.

3. 메모리 효율성

모드별 특화로 인한 메모리 사용 패턴 최적화:

# Embedding Mode 메모리 관리
class EmbeddingMemoryManager:
    def __init__(self):
        # KV 캐시 불필요, 시퀀스 처리에 집중
        self.max_sequence_length = 32768
        self.enable_kv_cache = False

    def optimize_for_long_sequences(self):
        # 긴 시퀀스 처리를 위한 메모리 할당 전략
        torch.cuda.empty_cache()
        return allocate_sequence_memory()

운영 환경에서의 활용

1. RAG 파이프라인 최적화

XGEN Documents 서비스에서 대용량 PDF, DOCX 파일 처리 시:

# 문서 임베딩 처리 플로우
async def process_large_document(document_path: str):
    # 1. 임베딩 모드로 전환
    await switch_to_embedding_mode("bge-m3")

    # 2. 문서 전체 임베딩 (청킹 없이)
    full_embedding = await embed_document(document_path)

    # 3. 세부 청크 임베딩 (검색 최적화)
    chunk_embeddings = await embed_chunks(document_chunks)

    return {
        "full_embedding": full_embedding,
        "chunk_embeddings": chunk_embeddings
    }

2. 자동 모드 스케줄링

시간대별로 자동으로 모드를 전환하는 스케줄러도 구현했다:

# 새벽 시간: 임베딩 배치 작업
# 08:00-18:00: 생성 모드 (대화형 서비스)
# 18:00-24:00: 혼합 모드 (요청에 따라 동적 전환)

@scheduler.scheduled_job('cron', hour=2)
async def nightly_embedding_batch():
    await switch_to_embedding_mode()
    await process_pending_documents()

@scheduler.scheduled_job('cron', hour=8)  
async def morning_switch_to_generation():
    await switch_to_generation_mode()

기술적 도전과 해결

1. 모델 전환 시 메모리 정리

모드 전환 시 이전 모델의 GPU 메모리를 완전히 정리하는 것이 중요했다:

async def clean_switch_backend():
    # 1. 현재 모델 언로드
    if current_model:
        del current_model

    # 2. GPU 메모리 강제 정리
    torch.cuda.empty_cache()
    torch.cuda.synchronize()

    # 3. 가비지 컬렉션
    gc.collect()

    # 4. 새 모델 로드
    new_model = await load_model_with_mode(target_mode)

2. 동시 요청 처리

임베딩 모드에서도 여러 문서를 동시 처리할 수 있도록 비동기 큐를 구현했다:

from asyncio import Queue, create_task

class EmbeddingQueue:
    def __init__(self, max_concurrent=4):
        self.queue = Queue()
        self.max_concurrent = max_concurrent
        self.workers = []

    async def start_workers(self):
        for i in range(self.max_concurrent):
            worker = create_task(self._worker())
            self.workers.append(worker)

결론

XGEN 2.0의 switch-backend와 배치 처리 최적화는 단일 GPU에서 최대 효율을 뽑아내는 엔지니어링의 정수였다.

특히 임베딩 모드 도입으로: - 대용량 문서 처리 성능 3.75배 향상 - GPU 사용률 20%p 개선 (70% → 90%)
- 문맥 정보 손실 80% 감소 (청킹 최소화)

이러한 최적화는 XGEN 2.0가 엔터프라이즈 환경에서 대용량 문서를 실시간으로 처리할 수 있는 기반이 되었고, 향후 멀티 GPU 환경으로의 확장에도 중요한 설계 기반을 제공했다.


주요 키워드: 임베딩 최적화, 배치 처리, GPU 메모리, switch-backend, RAG 파이프라인, 대용량 문서 처리

관련 글