새소식

AI

LLM 스터디 2주차) LLM 모델 서빙 서비스 구현

CloudNet@ 팀의 가시다님께서 Leading 하시는 LLM Study 1주차 스터디 내용 정리

 

1.1. 단일 모델 Serving Service 구성

핵심 목표
  • LLM 서비스의 핵심 요소를 포함한 단순한 아키텍처
  • HTTP API 로 Prompt 입력
  • Backend Worker Process 에서 모델 로드 및 Inference 수행
  • 단일 요청, Batch 요청, Streaming 요청 처리
  • Sequence ID 관리를 통한 Request 와 Output 추적

 

1.1.1. 서비스 아키텍처

LLM 서비스 핵심 구성 요소

 

  • API Server
    - HTTP 요청/응답 처리

    Batch, Streaming Endpoint
  • LLM Engine
    LLM 오케스트레이터

    다른 컴포넌트를 초기화하고 조율
  • Workload Manager
    요청 큐잉, 배치 구성 관리

    배칭 전략 결정 지점
    스케쥴러
  • Model Executor
    모델 워커 프로세스 초기화 및 관리

    프로세스 간 통신으로 추론 트리거
  • Model Worker
    실제 모델 추론을 자신의 별도 프로세스에서 실행
  • Model Manager
    모델 로드 및 캐싱

 

요청 처리 흐름
Client → API server → LLM engine → Workload manager → Model executor → Model worker(별도 프로세스)
                                                                              ↓
Client ← API server ← LLM engine ← Workload manager ←──── 생성 결과 ────────────┘

 

왜 프로세스를 분리하는가?

 

  • 메인 프로세스
    스케쥴링 처리, 가벼운 작업, 실행 속도 빠름
  • 워커 프로세스
    LLM 추론, 무거운 작업, 실행 속도 느림

    메인 프로세스가 하는 작업과 워커 프로세스가 하는 작업의 시간차이 발생
    하나의 프로세스에서 동작할 경우 LLM 추론이 끝날 때까지 메인 프로세스의 스케쥴링이 동작하지 못함 → 시간 낭비 발생
    작업의 효율화를 위해 메인과 워커 프로세스 사이에 큐를 두어 비동기 처리

 

1.1.2. 단일 모델 서빙 시스템 아키텍처

하나의 서비스가 하나의 모델을 전용으로 실행

 

  • Single-Model Serving

 

  • 코드
 

GitHub - orca3/llm-model-inference: Source code repo for book: Hands-On LLM Serving and Optimization: Hosting LLMs at Scale

Source code repo for book: Hands-On LLM Serving and Optimization: Hosting LLMs at Scale - orca3/llm-model-inference

github.com

 

  • chapter 3 - Single model llm serving 코드
    - vLLM 이 Apple Silicon MacOS 를 지원하지 않으므로 Docker Container 로 실행 필요
    해당 코드 레포지토리에서 아래 Dockerfile 및 docker-compose 추가 후 컨테이너 실행
# Dockerfile
FROM vllm/vllm-openai-cpu:latest-arm64

WORKDIR /app

# 이미지 버전에 맞는 패키지 설치
RUN pip install --no-cache-dir pytest pytest-asyncio httpx debugpy

COPY . .

EXPOSE 8000

ENTRYPOINT []
CMD ["python3", "main.py"]
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.app
    platform: linux/arm64
    ports:
      - "8000:8000"
    volumes:
      - huggingface_cache:/root/.cache/huggingface
    environment:
      - VLLM_CPU_KVCACHE_SPACE=4
      - VLLM_CPU_OMP_THREADS_BIND=0-11
    security_opt:
      - seccomp=unconfined
    cap_add:
      - SYS_NICE
    shm_size: "4g"

volumes:
  huggingface_cache:

 

1.2. 단일 요청 처리 테스트

1.2.1. Workflow

한 번에 1개 요청만 처리

 

실행 시 확인 필요 포인트

- 응답이 한 번에 출력됨 → 모든 추론이 완료되기까지 기다렸다가 한 번에 결과값 출력

curl -s -X POST http://localhost:8000/basic_generate \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello, I am"}' | jq

 

1.2.2. 문제점

  • 낮은 처리량
    한 번에 하나의 프롬프트만 전송

    모델 워커는 추론 호출 1개당 하나의 프롬프트만 처리

 

1.3. 배치 처리 테스트

배치(Batch) : 여러 Prompt 를 묶어 한 번에 Model Worker 로 전송해 GPU 사용량을 높이는 방식

 

1.3.1. 왜 배치 처리가 필요할까?

- GPU 는 병렬 처리가 가능한데, Prompt 1개만 처리하면 GPU 리소스 낭비 발생

- 배치 처리를 통해 여러 Prompt 를 한 번에 수행

 

1.3.2. 배치 처리 아키텍처

  1. API 서버가 프롬프트 입력 받아 LLM 엔진으로 전달
  2. LLM 엔진이 ID 부여 및 시퀀스로 감쌈
  3. 감싸진 시퀀스를 워크로드 매니저로 전달
  4. 워크로드 매니저가 모든 활성 시퀀스를 메모리 내에서 추적
  5. LLM 엔진은 워크로드 매니저로부터 다음 프롬프트 배치를 가져와 모델 실행기와 워커에 전달하여 추론 실행
  6. 배치 처리 후 생성된 텍스트와 각 프롬프트 ID 를 반환
  7. API 서버가 원래 요청과 연결

 

1.3.3. 시퀀스(Sequence)

배치 처리되는 프롬프트를 구분하기 위한 프롬프트 생애주기 단위

 

  • 시퀀스 ID 를 통해 프롬프트 구분
  • 담고 있는 정보
    • 토큰
    • 진행 상태
    • 종료 여부
    • KV 캐시

 

  • 대기 큐(Incoming_queue)
    새로 들어온 시퀀스가 대기하는 곳
  • 스케쥴링(Scheduling)
    워크로드 매니저가 이번 배치에 넣을 시퀀스를 결정해서 활성 시퀀스로 승격
  • 활성 시퀀스(Active_sequence)
    실제로 배치에 포함되어 GPU 에서 처리 중인 시퀀스
  • 완료 시 기록
    특정 시퀀스가 EOS 에 도달하여 끝나면, 그 결과를 시퀀스 맵에 기록하고 활성 시퀀스에서 빠짐
  • 시퀀스 맵(Sequence_map)
    ID 별로 프롬프트 최종 생성 결과를 매핑해두는 곳

    원본 요청으로 재조립할 때 시퀀스 맵에서 ID 를 꺼내 사용

 

1.3.4. 배치 처리 테스트

curl -s -X POST http://localhost:8000/generate \
  -H "Content-Type: application/json" \
  -d '{
    "prompts": [
      "Hello, I am",
      "The weather is",
      "I want to",
      "The best way to",
      "The most efficient way to"
    ]
  }' | jq

 

로그 확인

- 배치 작업을 통해서 4개 - 1개씩 처리한 것을 알 수 있음

 

1.3.5. 문제점

  • 높은 지연시간
    모든 배치가 종료될 때까지 응답을 기다림

 

1.4. 스트리밍 배치 테스트

모든 처리 결과를 기다리지 않고, 출력 토큰 생성 즉시 사용자에게 전송

 

  • 스트리밍(Streaming)
    실시간 응답, 높은 사용자 경험 제공

    실제 GPT, Claude 에게 질문할 때 보여주는 응답 형태
curl -X POST http://localhost:8000/generate_stream \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello, I am"}' \
  --no-buffer

 

응답 결과

- 토큰 생성 즉시 출력

 

1.5. 멀티 모델 Serving Service 구성

여러 LLM 모델을 서비스할 수 있는 구조

 

- 단일 모델 서빙은 1개의 LLM 모델만 서비스할 수 있지만, 멀티 모델 서빙은 모델 크기, 버전, 분류 등에 따른 작업별 모델 분리가 가능

- 실제 운영 환경에선 멀티 모델 서빙은 선택이 아닌 필수

 

주요 기능

 

  • 크로스 프레임워크 지원 (Cross-framework Support)
    하나의 통합 시스템에서 다양한 모델 형식 지원
  • 통합 API 인터페이스(Unified API Interface)
    일관된 웹 인터페이스에서 다양한 유형의 모델 지원
  • 자원 관리 (Resource Management)
    제한된 컴퓨팅 자원 효율적 관리 및 할당

 

1.5.1. 서비스 아키텍처

  • API 서버
    HTTP 엔드포인트 노출, 요청/응답 처리
  • Model Manager
    모델 캐시 관리, 모델 워커의 라이프사이클 조정

    모델 캐시 없으면 동적 로드
    캐시가 가득 차면 LRU eviction 수행
  • Model Store
    모델 메타데이터 저장, 조회
  • Model Engine
    모델 스토어에서 제공된 메타데이터를 기반으로 모델 워커 인스턴스 생성
  • Model Worker
    모델을 메모리에 로드하고 추론 실행

 

  • 코드
 

GitHub - orca3/llm-model-inference: Source code repo for book: Hands-On LLM Serving and Optimization: Hosting LLMs at Scale

Source code repo for book: Hands-On LLM Serving and Optimization: Hosting LLMs at Scale - orca3/llm-model-inference

github.com

 

  • chapter 3 - Multi model llm serving 코드
    vLLM 이 Apple Silicon MacOS 를 지원하지 않으므로 Docker Container 로 실행 필요
    해당 코드 레포지토리에서 아래 Dockerfile 및 docker-compose 추가 후 컨테이너 실행
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8001

CMD ["python", "-m", "app.server"]
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.app
    platform: linux/arm64
    ports:
      - "8001:8001"
    environment:
      - PORT=8001
    volumes:
      - huggingface_cache:/root/.cache/huggingface

volumes:
  huggingface_cache:

 

1.5.2. workflow

  1. 웹 요청 - API 서버가 모델 ID 와 입력 데이터가 담긴 요청 수신
  2. 모델 매니저가 모델 캐시 조회
    - 캐시 히트 → 이미 로드된 모델 워커 실행 → 끝

    - 캐시 미스 → 아래 단계 수행
  1. 모델 저장소에게 메타 데이터 요청
  2. 모델 매니저가 모델 엔진에게 모델 워커 생성 지시
  3. 새로 만든 워커를 모델 캐시에 추가
  4. 모델 워커 실행

 

1.5.3. 멀티 모델 테스트

모델 캐시 미스 확인
curl -s http://localhost:8001/models | jq

 

캐시가 없는 spam detection 모델 호출
curl -X POST http://localhost:8001/predict \
  -H "Content-Type: application/json" \
  -d '{"model_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "input_data": "Win a free iPhone now!"}'

 

모델 캐시 확인

- 추가된 모델 캐시 확인 가능

curl -s http://localhost:8001/models | jq

 

1.5.4. 멀티 모델 서빙 한계

  • 콜드 스타트
    모델 캐시 미스가 될 때 모델 로드를 위해 모델의 첫 요청이 느린 이슈
  • 스케일링 복잡도
    어떤 모델을 확장할 지 복잡해짐

 

 

Contents

포스팅 주소를 복사했습니다