[250908월2132H] 모델 서빙 프레임워크 (2) triton 심화

윤승호·2025년 9월 8일

드디어 마지막 이론 수업이 끝났다.

학습시간 09:00~03:00(당일18H/누적2132H)


◆ 학습내용

1. Triton Inference Server

(1) Triton이란?

  • NVIDIA에서 개발한 오픈소스 추론 서버
  • AI 모델을 실제 서비스(Production) 환경에 배포하기 위해 설계됨
  • 복잡한 추론 작업을 단순화하고 성능을 최적화하는 데 중점을 둠
  • 다재다능한 모델 서빙 솔루션(특정 머신러닝 프레임워크에 종속되지 않음)
  • CPU 및 GPU 환경 모두에서 높은 성능을 제공하도록 구축됨
  • HTTP/gRPC 요청에 빠르고 안정적인 추론 결과를 보임

(2) 목표

  • Triton을 사용하는 이유는 AI 모델 배포를 단순화하기 위함
  • 모델 종류와 상관없이 일관된 방식으로 배포 및 관리 가능
  • MLOps 파이프라인에 쉽게 통합할 수 있는 표준 인터페이스 제공
  • 추론 성능을 극대화할 수 있음
  • 하드웨어(특히 GPU)의 연산 능력을 최대한 활용
  • 높은 처리량(Throughput)과 낮은 지연 시간(Latency) 밸런스 맞추는 게 중요

2. 핵심 기능 및 장점

(1) 다양한 프레임워크 지원

  • 다양한 프레임워크를 '백엔드'라는 플러그인 형태로 지원
  • TensorFlow, PyTorch, ONNX, TensorRT 등 거의 모든 주요 프레임워크와 호환됨
  • 모델을 개발한 프레임워크에 구애받지 않고 서빙 환경을 표준화 가능
  • 예를 들어 PyTorch로 만든 모델과 TensorFlow로 만든 모델을 동일한 Triton 서버에서 동시에 실행

(2) 모델 동시 실행 가능

  • 하나의 GPU 메모리 안에 여러 AI 모델을 동시에 올려두고 서비스 가능
  • GPU 자원을 효율적으로 분배하여 유휴 시간을 최소화
  • 특정 모델에 대한 요청이 많을 경우, 해당 모델의 인스턴스를 여러 개 생성하여 병렬 처리
  • 이를 통해 특정 모델의 병목 현상을 해결하고 전체 처리량을 향상

(GPU 동시 실행 개념 예시)

GPU 0설명
Model A (Instance 1)A 모델의 첫 번째 인스턴스
Model A (Instance 2)A 모델의 두 번째 인스턴스 (병렬 처리용)
Model B (Instance 1)B 모델의 인스턴스

(3) 동적 배치 가능

  • 클라이언트로부터 들어오는 개별 요청들을 서버 측에서 실시간으로 수집
  • 정해진 시간 또는 요청 수에 도달하면 하나의 배치(Batch)로 묶어서 모델에 전달
  • GPU는 단일 데이터를 처리할 때보다 여러 데이터를 묶어서 병렬 처리할 때 훨씬 효율적
  • 동적 배치를 통해 GPU가 항상 최적의 배치 크기로 연산을 수행하도록 유도

(4) 모델 파이프라인 (Ensemble & BLS)

앙상블 모델 (Ensemble Model)

  • 여러 개의 모델을 순차적 또는 병렬적으로 연결하여 하나의 워크플로우로 구성
  • 전처리 모델 → 메인 모델 → 후처리 모델과 같은 파이프라인을 서버 설정만으로 구현 가능

비즈니스 로직 스크립팅 (BLS, Business Logic Scripting)

  • 단순 모델 호출을 넘어, Python 코드로 복잡한 로직을 파이프라인에 포함
  • 모델 입출력의 동적인 제어, 조건부 모델 호출 등 높은 자유도를 제공
단계모델명역할
1pre-processing_model입력 이미지 전처리
2main_classification_model핵심 분류 작업 수행
3post-processing_model분류 결과를 사람이 읽기 좋은 형태로 변환

3. Triton 모델 저장소

(1) 기본 구조

  • Triton 서버는 지정된 모델 저장소 경로에서 정해진 규칙에 따라 모델을 탐색
  • 모델 이름 / 버전 / 모델 파일 형태의 계층 구조를 가짐
  • 버전 번호(숫자)로 폴더를 만들어 모델의 여러 버전을 관리 가능
  • 별도 설정이 없으면 가장 높은 버전 번호의 모델을 자동으로 로드

(모델 저장소 구조 예시)

📦 model_repository/
 └── 📂 resnet_classifier/
     ├── 📜 config.pbtxt
     ├── 📂 1/
     │   └── 📜 model.pt
     └── 📂 2/
         └── 📜 model.pt

(2) 모델 설정 파일

  • config.pbtxt
  • 모델의 이름, 플랫폼, 입출력 텐서의 정보 등을 정의하는 텍스트 파일
  • Triton 서버는 이 파일을 읽어 모델을 어떻게 로드하고 실행할지 결정
  • name: 모델 이름, 반드시 폴더 이름과 일치해야 함
  • platform: 모델의 프레임워크 (예: pytorch_libtorch)
  • max_batch_size: 동적 배치를 사용할 경우 최대 배치 크기 지정
  • input, output: 입출력 텐서의 이름, 데이터 타입(data_type), 모양(dims) 정의

(config.pbtxt 예시)

name: "resnet_classifier"
platform: "pytorch_libtorch"
max_batch_size: 16
input [
  {
    name: "INPUT__0"
    data_type: TYPE_FP32
    dims: [ 3, 224, 224 ]
  }
]
output [
  {
    name: "OUTPUT__0"
    data_type: TYPE_FP32
    dims: [ 1000 ]
  }
]

2. Triton 실습 (CIFAR-10)

(1) 모델 준비

  • Triton이 모델을 이해할 수 있도록 표준 형식인 ONNX로 변환하는 과정
  • PyTorch, TensorFlow 등 다양한 프레임워크의 모델을 공통된 형태로 통일
  • CIFAR-10 데이터셋에 맞게 ResNet18 모델의 최종 출력층을 10개로 수정
  • torch.onnx.export 함수를 사용하여 변환하며,
  • 이때 입출력 텐서의 이름(input, output)을 명시적으로 지정

(ONNX 변환 코드)

import torch
import torchvision.models as models

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

num_ftrs = model.fc.in_features
model.fc = torch.nn.Linear(num_ftrs, 10)
model.eval()

dummy_input = torch.randn(1, 3, 32, 32)
onnx_model_path = "model.onnx"

torch.onnx.export(model,
                  dummy_input,
                  onnx_model_path,
                  input_names=['input'],
                  output_names=['output'],
                  opset_version=11)

(2) 모델 저장소 구성

  • Triton 서버가 모델을 인식하고 로드하기 위해 반드시 지켜야 하는 폴더 구조
  • 최상위 폴더 / 모델 이름 / 버전 번호 / 모델 파일 순서로 구성
  • 모델 설정 파일(config.pbtxt) 작성
  • 모델의 이름, 사용 플랫폼, 최대 배치 사이즈, 입출력 텐서의 상세 정보(이름, 타입, 차원)를 명시
  • 이 설정 파일을 통해 Triton은 모델을 어떻게 실행해야 할지 파악

(Triton 폴더 구조 예시)

📦 model_repository/
 └── 📂 cifar10_resnet/
     ├── 📜 config.pbtxt
     └── 📂 1/
         └── 📜 model.onnx

(config.pbtxt 파일 예시)

name: "cifar10_resnet"
platform: "onnxruntime_onnx"
max_batch_size: 8
input [
  {
    name: "input"
    data_type: TYPE_FP32
    dims: [ 3, 32, 32 ]
  }
]
output [
  {
    name: "output"
    data_type: TYPE_FP32
    dims: [ 10 ]
  }
]

(3) Triton 서버 실행

  • NVIDIA에서 제공하는 공식 Triton 서버 Docker 이미지를 사용하여 서버를 실행
  • 복잡한 라이브러리 의존성 문제없이 격리된 환경에서 간편하게 서버를 구축 가능
  • -v 옵션을 사용하여 로컬 컴퓨터에 구성한 모델 저장소 폴더를 Docker 컨테이너 내부의 /models 경로에 마운트
  • -p 옵션으로 서버와 통신하기 위한 HTTP(8000), gRPC(8001) 포트를 개방

(Docker 명령어)

docker run --gpus=all --rm -p 8000:8000 -p 8001:8001 -p 8002:8002 \
-v $(pwd)/model_repository:/models \
nvcr.io/nvidia/tritonserver:23.12-py3 \
tritonserver --model-repository=/models

(4) 클라이언트 테스트

  • tritonclient 라이브러리 사용
  • Triton 서버와 상호작용하기 위해 NVIDIA에서 제공하는 공식 파이썬 클라이언트 라이브러리
  • HTTP 또는 gRPC 프로토콜을 통해 서버에 추론 요청을 보내고 응답을 받는 기능을 제공

추론 요청 및 결과 처리 과정

  • 서버 주소로 클라이언트 객체를 생성하여 연결
  • InferInput 객체로 입력 데이터를 Triton 형식에 맞게 준비
  • infer 메소드를 호출하여 모델 이름과 입력 데이터를 서버에 전송
  • 서버로부터 받은 추론 결과를 as_numpy로 변환하여 후처리

(클라이언트 테스트 코드)

import numpy as np
import tritonclient.http as httpclient
import torchvision.transforms as transforms

triton_client = httpclient.InferenceServerClient(url="localhost:8000")

dummy_image = np.random.rand(32, 32, 3).astype(np.float32)
preprocess = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = preprocess(dummy_image).unsqueeze(0)
input_tensor_np = input_tensor.numpy()

inputs = [httpclient.InferInput('input', input_tensor_np.shape, "FP32")]
inputs[0].set_data_from_numpy(input_tensor_np, binary_data=True)

outputs = [httpclient.InferRequestedOutput('output', binary_data=True)]

results = triton_client.infer(model_name="cifar10_resnet", inputs=inputs, outputs=outputs)
output_data = results.as_numpy('output')
predicted_class = np.argmax(output_data)

print(f"모델 예측 결과: {predicted_class}")

3. Triton 서버 구축 실습

(1) 동적 배치 최적화

Dynamic Batching Optimization

  • 처리량(Throughput)과 지연시간(Latency)의 밸런스를 잘 맞추어야 함
  • config.pbtxt에서 동적 배치 설정을 통해 성능 특성을 제어 가능
  • 배치(batch)를 모으기 위해 대기하는 시간이 길어질수록 한 번에 처리하는 양(처리량)은 늘어나지만, 개별 요청의 응답 시간(지연시간)은 길어짐
  • 핵심 파라미터: max_queue_delay_microseconds
  • 서버가 배치를 만들기 위해 요청을 대기하는 최대 시간(마이크로초)을 설정
  • 이 값을 낮추면 실시간성이 중요한 서비스에 유리하고, 높이면 전체 처리 효율이 중요한 서비스에 유리

(2) 모델 인스턴스와 동시성

instance_group 설정

  • config.pbtxt에서 모델을 몇 개나, 그리고 어떤 장치(CPU/GPU)에 복제해서 띄울지 결정
  • 특정 모델에 대한 요청이 몰릴 때 여러 인스턴스가 병렬로 처리하여 병목 현상을 해소

GPU/CPU 자원 할당

  • kind: KIND_GPU 또는 kind: KIND_CPU로 실행 장치를 명시
  • count 값을 조절하여 하나의 장치에 몇 개의 모델 인스턴스를 실행할지 지정 가능
  • 예를 들어, GPU 하나에 count: 4로 설정하면 4개의 모델이 동시에 요청을 처리

(config.pbtxt 내 인스턴스 그룹 설정 예시)

# config.pbtxt 파일의 일부

# GPU 0번에 2개의 인스턴스, GPU 1번에 2개의 인스턴스를 생성
instance_group [
  {
    count: 2
    kind: KIND_GPU
    gpus: [ 0 ]
  },
  {
    count: 2
    kind: KIND_GPU
    gpus: [ 1 ]
  }
]

(3) TensorRT를 이용

  • NVIDIA GPU에 특화된 추론 최적화 라이브러리인 TensorRT로 모델을 변환하여 서빙
  • ONNX나 PyTorch 모델을 그대로 사용하는 것보다 훨씬 높은 성능을 기대할 수 있음 (일반적으로 3~5배 이상)
  • 기존 모델(PyTorch 등) → ONNX → TensorRT 엔진(.plan 파일) 순서로 변환 필요
  • 변환된 TensorRT 엔진을 Triton 모델 저장소에 배치하고 platformtensorrt_plan으로 설정

(TensorRT 모델용 config.pbtxt 예시)

name: "cifar10_resnet_trt"
platform: "tensorrt_plan"
max_batch_size: 128
input [
  {
    name: "input"
    data_type: TYPE_FP32
    dims: [ 3, 32, 32 ]
  }
]
output [
  {
    name: "output"
    data_type: TYPE_FP32
    dims: [ 10 ]
  }
]

(4) 앙상블 구성

  • 여러 모델을 묶는 앙상블 모델처럼 config.pbtxt를 구성할 수 있음
  • platformensemble로 지정하고, 내부에 데이터 흐름을 정의
  • 앙상블 파이프라인의 각 단계를 step으로 정의
  • model_name으로 해당 단계에서 호출할 모델을 지정
  • input_map, output_map을 통해 이전 모델의 출력을 다음 모델의 입력으로 어떻게 전달할지 명시

(앙상블 config.pbtxt 예시)

platform: "ensemble"
name: "cifar10_pipeline"
max_batch_size: 8
input [
  {
    name: "PIPELINE_INPUT"
    data_type: TYPE_UINT8
    dims: [ -1, -1, 3 ]
  }
]
output [
  {
    name: "PIPELINE_OUTPUT"
    data_type: TYPE_STRING
    dims: [ 1 ]
  }
]
step [
  {
    model_name: "preprocessor"
    model_version: -1
    input_map {
      key: "PREPROCESS_INPUT"
      value: "PIPELINE_INPUT"
    }
    output_map {
      key: "PREPROCESS_OUTPUT"
      value: "preprocessed_tensor"
    }
  },
  {
    model_name: "cifar10_resnet"
    model_version: -1
    input_map {
      key: "input"
      value: "preprocessed_tensor"
    }
    output_map {
      key: "output"
      value: "classifier_output"
    }
  }
]
profile
나는 AI 엔지니어가 된다.

0개의 댓글