Skip to content
SON BLOG
Go back

임베딩 전용 서버 분리와 대용량 배치 처리 최적화

Edit page
시리즈 · XGEN 개발기 (5 / 6편)
  1. Code Assistant 개발기 - AI 기반 개발 도우미 시스템 구축
  2. vLLM + llama.cpp GPU 모델 서빙 최적화 실전기
  3. FastAPI 워크플로우 엔진에 Qdrant 하이브리드 검색 붙이기
  4. vLLM에서 llama.cpp로: LLM 서빙 아키텍처 통합 마이그레이션
  5. 임베딩 전용 서버 분리와 대용량 배치 처리 최적화 — 현재 글
  6. FastAPI 워크플로우 엔진: 접근 제어와 감사 로깅 구현

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

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

배경

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

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

switch-backend 아키텍처

핵심 설계 철학

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

모드별 최적화 전략

구분Generation ModeEmbedding 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 활용률 개선:

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

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에서 최대 효율을 뽑아내는 엔지니어링의 정수였다.

특히 임베딩 모드 도입으로:

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


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


Edit page
Share this post:

Previous Post
Embedding 모델 서빙: batch size 최적화로 긴 문서 처리
Next Post
Redis 기반 SSE 세션 상태 공유: 멀티 POD 환경