[메모] DGX Spark에서 Docker로 LLM 서빙하기: 최적의 모델 조합 가이드

[메모] DGX Spark에서 Docker로 LLM 서빙하기: 최적의 모델 조합 가이드
📝 Memos에서 자동 발행됨
🕐 작성일: 2026-02-09
✨ AI가 보충 설명을 추가했습니다

DGX Spark에서 Docker로 LLM 서빙하기: 최적의 모델 조합 가이드

개요

이 포스트는 NVIDIA DGX Spark 환경에서 Docker를 활용하여 LLM(Large Language Model)을 효율적으로 서빙하는 방법을 다룹니다. 고성능 GPU 가속 환경의 이점을 최대한 활용하며, Docker를 통해 모델 배포의 복잡성을 줄이고 안정성을 확보하는 과정을 상세히 안내합니다. 특히, 다양한 성능 요구사항과 자원 제약을 고려하여 최적의 LLM 모델 및 서빙 프레임워크 조합을 선택하고, 이를 DGX Spark 클러스터에 배포하는 실질적인 방법을 제시합니다.

상세 가이드

1. DGX 환경 준비

DGX 시스템은 강력한 GPU 연산을 위한 인프라입니다. LLM 서빙을 시작하기 전에 기본 환경이 올바르게 설정되었는지 확인해야 합니다.

  • 원본 내용 (핵심 요약): DGX 시스템에 접속하고 Docker 및 NVIDIA 드라이버가 최신 버전인지 확인합니다.
  • 추가 설명: 최신 GPU 드라이버와 CUDA 툴킷이 설치되어 있어야 LLM 추론 시 GPU를 최대한 활용할 수 있습니다. Docker는 모델과 서빙 환경을 컨테이너화하여 일관성 있는 배포를 가능하게 합니다.
# NVIDIA 드라이버 및 CUDA 버전 확인
nvidia-smi

# Docker 설치 확인
docker --version

2. Spark 환경 구성 및 GPU 자원 활용

DGX 환경에서 Spark 클러스터를 구성하고, LLM 서빙에 필요한 GPU 자원을 Spark 작업이 활용할 수 있도록 설정합니다.

  • 원본 내용 (핵심 요약): DGX Spark 클러스터를 설정하고, Spark 작업이 GPU 자원을 사용할 수 있도록 구성합니다.
  • 추가 설명: Spark는 주로 대규모 데이터 처리에 사용되지만, Kubernetes와 같은 컨테이너 오케스트레이션 도구와 결합하여 DGX의 GPU 리소스를 효율적으로 관리하고 할당할 수 있습니다. 이는 LLM 모델 로딩 및 고속 추론에 필수적인 기반을 제공합니다.
# Spark on Kubernetes 환경에서 GPU 리소스 요청 예시
# spark-submit --master k8s://<kubernetes-api-server> --deploy-mode cluster \
#   --conf spark.kubernetes.container.request.gpu=1 \  # Executor 컨테이너 당 GPU 1개 요청
#   --conf spark.kubernetes.driver.request.gpu=1 \     # Driver 컨테이너 당 GPU 1개 요청
#   --conf spark.executor.instances=2 \                # Executor 인스턴스 2개 생성
#   --conf spark.kubernetes.authenticate.driver.serviceAccountName=<service-account> \
#   --conf spark.kubernetes.container.image=<your-spark-image> \
#   ...
# (실제 환경의 Spark 버전 및 Kubernetes 설정에 따라 상세 옵션은 달라질 수 있습니다.)

3. LLM 모델 선정 및 최적의 조합

"최적의 모델 조합"은 단순한 모델 선택을 넘어 성능, 자원 효율성, 서비스 요구사항을 모두 고려하는 복합적인 과정입니다.

  • 원본 내용 (핵심 요약): 서빙할 LLM 모델을 선택하고, 성능 및 자원 효율성을 고려한 최적의 조합을 구성합니다.
  • 추가 설명:
    • 성능 요구사항 분석: 서비스가 요구하는 응답 지연 시간(Latency)과 처리량(Throughput)을 명확히 정의합니다. 실시간 대화형 서비스라면 낮은 지연 시간이 중요하며, 배치 처리라면 높은 처리량이 우선될 수 있습니다.
    • 모델 종류 선택: Hugging Face Transformers 라이브러리에서 제공하는 다양한 파운데이션 모델(Foundation Model) 중에서 선택하거나, 특정 도메인에 파인튜닝(Fine-tuned)된 모델을 고려합니다.
    • 최적화 기법 적용:
      • 양자화(Quantization): 8-bit, 4-bit 양자화를 통해 모델 크기를 줄여 GPU 메모리 사용량을 최적화하고 추론 속도를 향상시킬 수 있습니다. AWQ, GPTQ 같은 기술이 대표적입니다.
      • 지식 증류(Knowledge Distillation): 크고 복잡한 모델의 지식을 작고 빠른 모델로 전달하여 성능은 유지하면서 효율성을 높이는 방법입니다.
    • 서빙 프레임워크 선택: vLLM, Text Generation Inference (TGI by Hugging Face), NVIDIA Triton Inference Server 등 GPU에 최적화된 서빙 프레임워크를 선택하여 높은 처리량과 낮은 지연 시간을 달성할 수 있습니다. 각 프레임워크는 배치 추론, 페이지드 어텐션 등 고급 기법을 통해 GPU 활용률을 극대화합니다.

4. Docker 이미지 생성

선택한 LLM과 서빙 프레임워크를 Docker 이미지로 패키징하여 배포의 일관성과 이식성을 확보합니다.

  • 원본 내용 (핵심 요약): 선택한 LLM과 서빙 프레임워크를 포함하는 Docker 이미지를 빌드합니다.
  • 추가 설명: Dockerfile을 작성하여 기본 이미지 선택, 모델 파일 추가, 필요한 라이브러리 설치, 그리고 서빙 스크립트 정의 단계를 거칩니다. NVIDIA가 제공하는 CUDA 지원 기본 이미지를 사용하는 것이 GPU 호환성을 위해 권장됩니다.
# Dockerfile 예시 (vLLM과 Llama 2 7B 모델을 가정)
# NVIDIA PyTorch 이미지를 베이스로 사용 (CUDA, cuDNN 등 GPU 관련 라이브러리 포함)
FROM nvcr.io/nvidia/pytorch:23.08-py3

WORKDIR /app

# 필요한 패키지 설치
RUN pip install --no-cache-dir huggingface-hub vllm fastapi uvicorn

# LLM 모델 다운로드 또는 복사
# 1. Hugging Face CLI를 사용하여 모델 다운로드 (온라인 환경)
# RUN python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='meta-llama/Llama-2-7b-hf', local_dir='/app/model', allow_patterns='*.safetensors')"
# 2. 미리 다운로드된 모델 파일을 컨테이너로 복사 (오프라인/보안 환경)
COPY ./models/Llama-2-7b-hf /app/model/

# 서빙 스크립트 추가
COPY ./serve.py /app/serve.py

# 서빙 포트 노출
EXPOSE 8000

# 컨테이너 실행 시 서빙 스크립트 실행
# 모델 경로를 인자로 전달
CMD ["python", "serve.py", "--model", "/app/model"]
# serve.py (vLLM FastAPI 서버 예시)
from vllm import LLM, SamplingParams
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
import argparse
import asyncio

app = FastAPI()
llm = None

class GenerationRequest(BaseModel):
    prompt: str
    max_tokens: int = 50
    temperature: float = 0.7
    top_p: float = 0.95
    do_sample: bool = True

@app.on_event("startup")
async def startup_event():
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", type=str, required=True, help="Path to the LLM model directory.")
    # GPU 메모리 활용률, 모델 로딩 방법 등 vLLM 특정 인자들을 추가할 수 있습니다.
    parser.add_argument("--gpu-memory-utilization", type=float, default=0.9,
                        help="Fraction of GPU memory to be used for model weights and KV cache.")
    args, _ = parser.parse_known_args() # _를 사용하여 FastAPI가 사용하는 인자를 무시

    global llm
    print(f"Loading model: {args.model} with GPU memory utilization: {args.gpu_memory_utilization}")
    llm = LLM(model=args.model,
              trust_remote_code=True,
              gpu_memory_utilization=args.gpu_memory_utilization)
    print("Model loaded successfully.")

@app.post("/generate")
async def generate_text(request: GenerationRequest):
    if llm is None:
        raise HTTPException(status_code=503, detail="LLM not initialized.")

    sampling_params = SamplingParams(
        max_new_tokens=request.max_tokens,
        temperature=request.temperature,
        top_p=request.top_p,
        do_sample=request.do_sample,
    )

    try:
        # vLLM의 generate 메서드는 비동기적으로 실행될 수 있습니다.
        outputs = await asyncio.to_thread(llm.generate, request.prompt, sampling_params)
        generated_text = outputs[0].outputs[0].text
        return {"generated_text": generated_text.strip()}
    except Exception as e:
        print(f"Error during generation: {e}")
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
# Docker 이미지 빌드
docker build -t llm-service:latest .

5. Docker 컨테이너 배포 및 Spark 연동

빌드된 Docker 이미지를 DGX 클러스터에 배포하고, Spark와 연동하여 LLM 서빙을 시작합니다.

  • 원본 내용 (핵심 요약): 빌드된 Docker 이미지를 DGX Spark 클러스터에 배포하여 LLM 서빙을 시작합니다.
  • 추가 설명: LLM 서빙 자체는 docker run 명령으로 직접 실행하거나 Kubernetes와 같은 컨테이너 오케스트레이션 시스템을 통해 관리됩니다. Spark는 LLM 서빙 인프라를 관리하거나, Spark 작업에서 LLM 추론 엔드포인트에 REST API 요청을 보내는 방식으로 연동될 수 있습니다.
# DGX 노드에서 Docker 컨테이너 실행 (직접 실행하는 경우)
# '--gpus all' 또는 '--gpus "device=0,1"' 등으로 GPU 할당
docker run --gpus all -p 8000:8000 --name llm-api llm-service:latest

# 컨테이너 로그 확인
docker logs -f llm-api

Spark 연동 시나리오 (예시):

  • 오케스트레이션: Spark on Kubernetes 환경에서는 Spark Pod와 LLM 서빙 Pod를 함께 배포하고 내부 네트워크를 통해 통신하도록 구성할 수 있습니다. Spark는 데이터 전처리를 수행하고, 처리된 데이터를 LLM 서빙 엔드포인트에 전달하여 추론 결과를 얻는 파이프라인을 구축할 수 있습니다.
  • API 호출: Spark Job 내에서 HTTP 클라이언트를 사용하여 배포된 LLM 서빙 엔드포인트(예: http://llm-service-ip:8000/generate)에 REST API 요청을 보내 LLM 추론을 수행할 수 있습니다.

6. 성능 모니터링 및 스케일링

배포된 LLM 서빙 서비스의 성능을 지속적으로 모니터링하고, 트래픽 변화에 따라 유연하게 스케일링 전략을 적용합니다.

  • 원본 내용 (핵심 요약): LLM 서빙 서비스의 성능을 모니터링하고 필요에 따라 스케일링합니다.
  • 추가 설명: GPU 사용량, 메모리 사용량(특히 KV 캐시), 요청 처리 시간, 에러율 등을 지속적으로 추적해야 합니다. NVIDIA DCGM(Data Center GPU Manager), Prometheus, Grafana 같은 도구들을 활용하여 모니터링 시스템을 구축할 수 있습니다. 트래픽 증가에 대비하여 Horizontal Pod Autoscaler(HPA) 등을 통해 컨테이너 수를 자동으로 늘리거나, 더 고성능의 GPU 자원을 할당하는 방식으로 스케일링 계획을 수립합니다.

💡 유용한 팁

  1. 모델 양자화(Quantization) 적극 활용: LLM은 대량의 GPU 메모리를 소모합니다. 8-bit, 4-bit 양자화를 통해 모델 크기를 줄여 DGX의 한정된 GPU 메모리를 효율적으로 사용하고, 더 많은 모델을 동시에 서빙하거나 더 큰 모델을 로드할 수 있습니다.
  2. GPU Direct Storage (GDS) 고려: 대용량 모델 파일을 로딩할 때 스토리지 I/O가 병목이 될 수 있습니다. GDS는 스토리지와 GPU 간 직접 데이터 경로를 제공하여 모델 로딩 시간을 획기적으로 단축시킬 수 있습니다.
  3. LLM 전용 서빙 프레임워크 사용: vLLM, Text Generation Inference (TGI), NVIDIA Triton Inference Server 등은 LLM 추론에 특화된 기능을 제공합니다. 배치 추론, 페이지드 어텐션, 연속 배치(Continuous Batching) 등을 통해 GPU 활용률과 처리량을 극대화하므로, 일반적인 FastAPI 서버 구성보다 훨씬 효율적입니다.
  4. 컨테이너 오케스트레이션 도구 활용: DGX 클러스터에서 여러 LLM 서빙 컨테이너를 효율적으로 배포하고 관리하려면 Kubernetes (K8s)와 같은 도구를 적극적으로 활용하세요. 이를 통해 자원 할당, 로드 밸런싱, 자동 복구 등을 손쉽게 구현할 수 있습니다.
  5. 캐싱 전략 도입: 동일하거나 유사한 프롬프트에 대한 응답이 자주 요청되는 경우, 캐싱 메커니즘을 도입하여 불필요한 추론 요청을 줄이고 응답 시간을 단축할 수 있습니다. Redis와 같은 인메모리 캐시를 활용해 보세요.

⚠️ 주의사항

  1. GPU 메모리 부족 (OOM): LLM은 GPU 메모리를 매우 많이 사용합니다. 모델 크기, 배치 사이즈, 서빙 프레임워크 설정에 따라 메모리 사용량이 급증하여 OOM(Out Of Memory) 에러가 발생할 수 있습니다. 항상 GPU 메모리 사용량을 모니터링하고, 양자화나 GPU 메모리 활용률 조정 등의 옵션을 통해 적절히 조절해야 합니다.
  2. 최적화되지 않은 Docker 이미지: 불필요한 라이브러리, 개발/디버깅 도구 등이 포함된 Docker 이미지는 크기가 커지고 보안 취약점을 높일 수 있습니다. slim 또는 distroless와 같은 경량화된 베이스 이미지를 사용하고, 프로덕션 환경에서는 필요한 의존성만 설치하여 이미지 크기를 최소화하세요.
  3. Spark와 LLM 서빙 역할 분리: Apache Spark는 대규모 데이터 처리 및 작업 오케스트레이션에 강력하지만, 실시간 LLM 추론 서빙 자체는 vLLM, TGI와 같은 전용 프레임워크가 더 효율적입니다. Spark는 LLM 서빙 인프라를 관리하거나, LLM 추론 결과를 활용하는 데이터 파이프라인의 일부로 사용하는 것이 일반적이며, Spark Executor 내에서 직접 LLM을 로드하고 추론하는 방식은 비효율적일 수 있습니다.

🔗 참고 자료

(원본 메모에 링크가 없어 현재는 비어 있습니다.)

개인정보보호링크