AI 모델을 개발하는 것도 중요하지만, 이를 실제 서비스 환경에 안정적으로 배포하고 운영하는 것 또한 매우 중요합니다.
NVIDIA에서 개발한 Triton Inference Server는 이러한 고민을 해결해 줄 강력한 도구입니다. Triton은 다양한 AI 프레임워크를 지원하며, 높은 성능과 처리량을 제공하여 모델 추론 과정을 효율적으로 만들어줍니다.
이 글에서는 Triton Inference Server의 주요 장점들을 살펴보고, 실제 모델을 배포하고 테스트하는 과정을 단계별로 안내합니다. 또한, curl을 이용한 요청 방법과 운영 시 고려 사항까지 다루어 Triton을 처음 접하는 분들도 쉽게 따라 할 수 있도록 구성했습니다.
Triton Inference Server 란?
먼저, Triton Inference Server에 대해 자세히 알아보고자 합니다.
NVIDIA Triton Inference Server는 다양한 딥러닝 및 머신러닝 모델을 쉽고 효율적으로 배포하고 실행할 수 있도록 설계된 오픈 소스 추론(인퍼런스) 서버입니다. 이 서버는 GPU, CPU 등 다양한 하드웨어에서 TensorFlow, PyTorch, ONNX, TensorRT 등 여러 프레임워크로 학습된 모델을 지원하며, AI 모델의 프로덕션 환경 배포를 표준화하고 확장성을 제공합니다
Triton Inference Server의 강력한 장점 알아보기
Triton Inference Server는 AI 모델을 프로덕션 환경에 배포하고 운영할 때 다음과 같은 강력한 장점을 제공합니다.
- 다양한 프레임워크 지원 (Framework Agility / Interoperability): TensorFlow, PyTorch, ONNX, TensorRT, OpenVINO 등 현재 널리 사용되는 대부분의 딥러닝 프레임워크를 지원합니다. 특정 프레임워크에 종속되지 않고 유연하게 모델을 관리하고 배포할 수 있다는 것은 큰 장점입니다.
- 높은 성능 및 처리량 (High Performance & Throughput):
- GPU 및 CPU 최적화: NVIDIA GPU의 성능을 최대한 활용하도록 최적화되어 있으며, CPU 환경에서도 효율적인 추론을 지원합니다.
- 동적 배치 (Dynamic Batching): 들어오는 요청들을 자동으로 배치 처리하여 추론 처리량을 극대화합니다. 개별 요청을 일일이 처리하는 것보다 훨씬 효율적입니다.
- 모델 분석 (Model Analyzer): 모델의 배치 크기 및 인스턴스 수에 따른 최적의 성능 구성을 자동으로 찾아주는 도구를 제공하여, 수동으로 최적화 설정을 찾는 수고를 덜어줍니다.
- 동시 모델 실행 (Concurrent Model Execution): 여러 모델 또는 동일 모델의 여러 인스턴스를 동시에 실행할 수 있습니다. 예를 들어, 단일 GPU에서 서로 다른 종류의 모델 여러 개를 동시에 로드하여 요청을 처리하거나, 하나의 인기 있는 모델에 대해 여러 인스턴스를 두어 병렬 처리 성능을 높일 수 있습니다. 이는 시스템 리소스 활용도를 극대화하는 데 기여합니다.
- 모델 앙상블: 성능을 높이기 위해, 여러 모델을 앙상블 형태로 지원합니다.
- 스트리밍 오디오/비디오 입력 지원: 모델의 Input 값으로 스트리밍 오디오/비디오를 지원합니다.
- 클라우드 네이티브 아키텍처 지원:
- Kubernetes, Docker Swarm과 같은 컨테이너 오케스트레이션 플랫폼과 잘 통합되어 확장 가능하고 관리하기 쉬운 서비스 배포가 가능합니다.
- Stateless 아키텍처: Triton 서버 자체는 상태를 가지지 않아 수평 확장이 용이하며, 장애 발생 시에도 새로운 인스턴스로 빠르게 대체하여 서비스 연속성을 확보할 수 있습니다.
- 다양한 프로토콜 및 추론 모드 지원:
- HTTP/REST, gRPC 등 표준 프로토콜을 지원하여 다양한 클라이언트 환경과의 연동이 쉽습니다.
- 실시간 스트리밍 추론과 배치 추론을 모두 지원하여 다양한 서비스 요구사항에 대응할 수 있습니다.
- 모니터링 및 관리 용이성:
- Prometheus에서 쉽게 모니터링할 수 있도록 GPU 활용률, 메모리 사용량, 추론 처리량, 지연 시간 등 다양한 메트릭을 표준화된 형태로 제공합니다. 이를 통해 운영 중인 모델의 상태를 실시간으로 파악하고 문제를 신속하게 진단할 수 있습니다.
Triton Inference Server 직접 사용해보기: 설치부터 테스트까지
이제 실제로 Triton Inference Server를 설치하고 모델을 배포하여 테스트하는 과정을 살펴보겠습니다.
관련 레퍼런스
더 자세한 정보는 아래 공식 문서 및 저장소를 참고하세요.
- Triton Server 컨테이너 (NVIDIA NGC): https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver
- Triton Server GitHub 레포지토리: https://github.com/triton-inference-server/server
- Triton 튜토리얼 GitHub 레포지토리: https://github.com/triton-inference-server/tutorials
- 위 튜토리얼 레포에서 PyTorch 등 다양한 프레임워크의 모델을 직접 다운로드하고 사용하는 예제를 참고할 수 있습니다.
사용한 코드를 참고하고 싶으면 아래 레포를 참고해주세요.
- https://github.com/ppippi-dev/triton_inference_server_test
1. Triton 서버 셋팅: 모델 저장소 구성
Triton은 특정 구조의 모델 저장소(Model Repository)를 사용합니다. 권장되는 기본 구조는 다음과 같습니다.
model_repository/
└── <model-name>/
├── config.pbtxt
├── <output-labels-file>
└── <version>/
└── <model-definition-file>
-
: 모델의 이름입니다. - config.pbtxt: 모델의 설정 파일입니다. 입력 및 출력 텐서 정보, 배치 크기, 플랫폼 등을 정의합니다.
-
: 분류 모델의 경우 클래스 레이블 정보를 담은 텍스트 파일 (선택 사항). -
: 모델의 버전 번호 (보통 정수 사용). 여러 버전을 두고 관리할 수 있습니다. -
: 실제 모델 파일 (예: model.onnx, model.plan, model.pt 등).
예제: DenseNet ONNX 모델 배포
ONNX 형식의 DenseNet 모델을 Triton에 배포해보겠습니다.
1.1. 모델 다운로드 및 저장소 생성
아래 쉘 명령어를 실행하여 모델 저장소 디렉토리를 만들고 ONNX DenseNet 모델 파일을 다운로드합니다.
# 모델 저장소 및 버전별 디렉토리 생성
mkdir -p model_repository/densenet_onnx/1
# ONNX DenseNet 모델 다운로드
wget -O model_repository/densenet_onnx/1/model.onnx \
https://github.com/onnx/models/raw/main/validated/vision/classification/densenet-121/model/densenet-7.onnx
1.2. config.pbtxt 설정 파일 작성
model_repository/densenet_onnx/config.pbtxt 파일을 생성하고 아래 내용을 기입합니다.
name: "densenet_onnx" # 모델의 이름 (디렉토리 이름과 일치 권장)
platform: "onnxruntime_onnx" # 모델 실행 플랫폼 (ONNX Runtime 사용)
max_batch_size : 0 # 0으로 설정 시 동적 배치 비활성화, 모델 자체에서 배치 처리 가정
# >0 값으로 설정하면 해당 크기까지 동적 배치 활성화
input [
{
name: "data_0" # 모델이 기대하는 입력 텐서의 이름
data_type: TYPE_FP32 # 입력 데이터 타입 (FP32)
format: FORMAT_NCHW # 입력 데이터 형식 (Batch, Channel, Height, Width)
dims: [ 3, 224, 224 ] # 단일 입력의 차원 (Channel, Height, Width)
reshape { shape: [ 1, 3, 224, 224 ] } # 서버 내부적으로 처리될 때의 shape (배치 차원 포함)
# max_batch_size > 0 이면 이 reshape는 무시될 수 있음
}
]
output [
{
name: "fc6_1" # 모델의 출력 텐서 이름
data_type: TYPE_FP32 # 출력 데이터 타입 (FP32)
dims: [ 1000 ] # 단일 출력의 차원 (클래스 수)
reshape { shape: [ 1, 1000, 1, 1 ] } # 서버 내부적으로 처리될 때의 shape
label_filename: "densenet_labels.txt" # 클래스 레이블 파일 이름
}
]
- max_batch_size: 0으로 설정하면 Triton의 동적 배치 기능을 사용하지 않고, 모델이 입력으로 받는 배치 크기를 그대로 사용합니다. 만약 >0 (예: 8, 16)으로 설정하면, Triton이 해당 배치 크기까지 요청을 모아 한 번에 처리합니다. 이 예제에서는 0으로 설정되어 모델 파일(model.onnx)이 이미 배치 차원을 처리할 수 있거나, 단일 요청만 처리하는 시나리오를 가정합니다. 실제 운영 시에는 모델의 특성과 예상 트래픽에 맞게 max_batch_size를 조절하는 것이 중요합니다.
- input.dims: 모델이 실제로 기대하는 단일 샘플의 차원입니다. [3, 224, 224]는 채널(RGB), 높이, 너비를 의미합니다.
- input.reshape: max_batch_size: 0이고 모델이 [-1, 3, 224, 224]와 같이 동적인 배치 크기를 받을 수 있다면, 클라이언트가 단일 이미지를 보낼 때 이 reshape 설정에 따라 서버 내부에서 [1, 3, 224, 224]로 변환되어 모델에 전달됩니다. 만약 max_batch_size가 0보다 크다면, 이 reshape 설정은 일반적으로 무시되고 동적 배치 로직에 따라 입력 shape이 결정됩니다.
1.3. densenet_labels.txt 라벨 파일 준비
ImageNet 1000개 클래스에 대한 레이블 파일이 필요합니다. model_repository/densenet_onnx/densenet_labels.txt 파일을 생성하고, 각 줄에 클래스 이름을 기입합니다. (내용이 길어 여기서는 생략합니다. 일반적으로 모델과 함께 제공되거나, 직접 생성해야 합니다. 예시: synset.txt, imagenet_classes.txt 등)
2. Triton 서버 실행
이제 준비된 모델 저장소를 사용하여 Triton 서버를 Docker 컨테이너로 실행합니다.
docker run --rm --net=host -v ${PWD}/model_repository:/models nvcr.io/nvidia/tritonserver:25.02-py3 tritonserver --model-repository=/models --model-control-mode explicit --load-model densenet_onnx
- -v ${PWD}/model_repository:/models: 호스트의 현재 디렉토리 아래 model_repository를 컨테이너 내부의 /models 디렉토리로 마운트합니다. Triton 서버는 이 경로에서 모델을 찾습니다.
- nvcr.io/nvidia/tritonserver:25.02-py3: 사용할 Triton 서버 Docker 이미지입니다. 25.02-py3는 2025년 2월 릴리즈의 Python3 버전 이미지를 의미합니다. (버전은 필요에 따라 최신으로 변경 가능)
- tritonserver: 컨테이너 내에서 실행될 명령어입니다.
- --model-repository=/models: 컨테이너 내의 모델 저장소 경로를 지정합니다.
- --model-control-mode explicit: 서버가 시작될 때 모든 모델을 자동으로 로드하는 대신, –load-model 옵션으로 명시된 모델만 로드하도록 합니다.
- --load-model densenet_onnx: densenet_onnx 모델을 로드하도록 지정합니다.
서버가 성공적으로 실행되면 모델이 로드되었다는 로그와 함께 HTTP/gRPC 서비스가 준비되었다는 메시지가 출력됩니다.
I0516 13:09:23.557992 1 grpc_server.cc:2558] "Started GRPCInferenceService at 0.0.0.0:8001"
I0516 13:09:23.558151 1 http_server.cc:4725] "Started HTTPService at 0.0.0.0:8000"
I0516 13:09:23.607014 1 http_server.cc:358] "Started Metrics Service at 0.0.0.0:8002"
주의: triton inference server의 이미지 크기가 매우 큽니다. 참고하세요!
3. Triton 클라이언트 실행 (이미지 분류 테스트)
다른 터미널을 열고, Triton SDK Docker 이미지를 사용하여 이미지 분류 클라이언트를 실행합니다. 이 클라이언트는 지정된 이미지를 서버로 보내고 추론 결과를 받아옵니다.
# /workspace/images/mug.jpg 이미지를 사용하여 densenet_onnx 모델에 추론 요청
docker run -it --rm --net=host nvcr.io/nvidia/tritonserver:25.02-py3-sdk /workspace/install/bin/image_client -m densenet_onnx -c 3 -s INCEPTION /workspace/images/mug.jpg
- nvcr.io/nvidia/tritonserver:25.02-py3-sdk: Triton 클라이언트 라이브러리 및 예제 코드가 포함된 SDK 이미지입니다.
- /workspace/install/bin/image_client: 실행할 클라이언트 프로그램입니다.
- -m densenet_onnx: 추론을 요청할 모델의 이름입니다.
- -c 3: 상위 3개의 예측 결과를 출력합니다.
- -s INCEPTION: 이미지 전처리 방식을 지정합니다 (Inception 스타일: 픽셀 값을 [-1, 1] 범위로 스케일링).
- /workspace/images/mug.jpg: 테스트에 사용할 이미지 경로입니다. SDK 이미지 내에 예제 이미지가 포함되어 있습니다.
3.1. 직접 다운로드한 이미지로 테스트해보기
로컬에 있는 다른 이미지나 웹에서 다운로드한 새로운 이미지로도 테스트해볼 수 있습니다.
먼저, SDK 컨테이너의 쉘로 들어갑니다.
docker run -it --rm --net=host nvcr.io/nvidia/tritonserver:25.02-py3-sdk /bin/sh
컨테이너 내부 쉘에서 다음 명령어를 실행하여 이미지를 다운로드하고 image_client로 테스트합니다.
# 테스트할 이미지 다운로드 (예: 새 이미지)
wget -O img1.jpg "https://www.hakaimagazine.com/wp-content/uploads/header-gulf-birds.jpg"
# 다운로드한 이미지로 추론 실행
/workspace/install/bin/image_client -m densenet_onnx -c 3 -s INCEPTION ./img1.jpg
예상 출력 (위 새 이미지 예시에 대한)
image_client는 추론 결과를 클래스 이름, 점수(확률 또는 로짓 값), 클래스 인덱스 형태로 출력합니다. 예를 들어, 위 새 이미지에 대한 예상 출력은 다음과 같을 수 있습니다 (모델과 레이블에 따라 다름):
Image './img1.jpg':
10.838984 (92) BEE EATER
10.836040 (14) INDIGO FINCH
8.926279 (88) MACAW
curl을 이용한 HTTP/REST 요청 보내기
Triton은 gRPC 외에도 HTTP/REST 인터페이스를 제공합니다. curl과 같은 도구를 사용하여 직접 추론 요청을 보낼 수 있습니다. 이를 위해서는 모델이 기대하는 형식에 맞춰 JSON 페이로드를 생성해야 합니다.
다음은 이미지 파일을 전처리하고 Triton의 KServe V2 API 표준에 맞는 JSON 페이로드를 생성하는 Python 스크립트 예제입니다.
preprocess_and_create_payload.py
import json
from PIL import Image
import numpy as np
import argparse
# --- 모델 특정 파라미터 (config.pbtxt 및 모델 특성에 따라 수정) ---
MODEL_INPUT_NAME = "data_0" # config.pbtxt의 input name과 일치
MODEL_OUTPUT_NAME = "fc6_1" # config.pbtxt의 output name과 일치
MODEL_EXPECTED_H = 224
MODEL_EXPECTED_W = 224
MODEL_EXPECTED_CHANNELS = 3
MODEL_DATATYPE = "FP32" # config.pbtxt의 input data_type과 일치 (TYPE_FP32 -> FP32)
# 일반적인 ImageNet 정규화 값 (모델 학습 시 사용된 값에 맞춰야 함)
MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
# --- 모델 특정 파라미터 끝 ---
def preprocess_image(image_path):
"""
이미지를 로드하고, 모델 입력에 맞게 전처리합니다.
config.pbtxt의 input format: FORMAT_NCHW, dims: [3, 224, 224] 에 맞춥니다.
"""
try:
img = Image.open(image_path).convert("RGB")
except FileNotFoundError:
print(f"Error: Image file not found at {image_path}")
return None, None
# 모델 입력 크기로 리사이즈
img_resized = img.resize((MODEL_EXPECTED_W, MODEL_EXPECTED_H))
# Numpy 배열로 변환 및 0-1 스케일링
img_np = np.array(img_resized, dtype=np.float32) / 255.0
# 정규화
img_np = (img_np - MEAN) / STD
# NCHW 형식으로 변경 (Channel, Height, Width)
# 현재 img_np shape: (MODEL_EXPECTED_H, MODEL_EXPECTED_W, MODEL_EXPECTED_CHANNELS)
# 변경 후 img_np_chw shape: (MODEL_EXPECTED_CHANNELS, MODEL_EXPECTED_H, MODEL_EXPECTED_W)
img_np_chw = img_np.transpose((2, 0, 1))
# config.pbtxt의 input dims는 [3, 224, 224]로, 배치 차원이 없습니다.
# 따라서 페이로드의 shape도 [3, 224, 224]로 맞춰줍니다.
# flatten()을 통해 1차원 리스트로 만듭니다.
return img_np_chw.flatten().tolist(), list(img_np_chw.shape)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("image_file_path", help="Path to the image file for preprocessing.")
parser.add_argument(
"--payload_file",
type=str,
default="payload.json",
help="Filename for the output JSON payload.",
)
args = parser.parse_args()
preprocessed_data_list, actual_shape_for_payload = preprocess_image(args.image_file_path)
if preprocessed_data_list is None:
exit(1)
# KServe V2 API 표준에 따른 요청 페이로드 구성
triton_request_payload = {
"inputs": [
{
"name": MODEL_INPUT_NAME,
"shape": actual_shape_for_payload, # 예: [3, 224, 224]
"datatype": MODEL_DATATYPE,
"data": preprocessed_data_list, # 1차원으로 펼쳐진 데이터
}
],
"outputs": [{"name": MODEL_OUTPUT_NAME}], # 요청할 출력 텐서 지정
}
with open(args.payload_file, "w") as f:
json.dump(triton_request_payload, f)
print(f"Preprocessed data and created JSON payload: {args.payload_file}")
print(f"Input tensor name for the request: {MODEL_INPUT_NAME}")
print(f"Input tensor shape for the request (in {args.payload_file}): {actual_shape_for_payload}")
print(f"Output tensor name requested: {MODEL_OUTPUT_NAME}")
스크립트 실행 및 curl 요청
- 위 Python 스크립트를 preprocess_and_create_payload.py로 저장합니다.
- 테스트할 이미지를 준비합니다 (예: img1.jpg) - 이전 3.1 단계에서 다운로드한 이미지 사용
-
Python 스크립트를 실행하여 payload.json 파일을 생성합니다.실행 결과 payload.json 파일이 생성되고, 입력 텐서 이름과 shape 정보가 출력됩니다.
# (필요시 Pillow, numpy 설치): pip install Pillow numpy python preprocess_and_create_payload.py img1.jpg
- 생성된 payload.json 파일을 사용하여 curl로 Triton 서버에 추론 요청을 보냅니다. (Triton 서버가 localhost:8000에서 실행 중이라고 가정)
- http://localhost:8000/v2/models/densenet_onnx/infer: Triton의 KServe V2 추론 API 엔드포인트입니다. densenet_onnx는 모델 이름입니다.
-
-d @payload.json: 요청 본문으로 payload.json 파일의 내용을 사용합니다.
curl -X POST \ -H "Content-Type: application/json" \ http://localhost:8000/v2/models/densenet_onnx/infer \ -d @payload.json
Triton 서버는 이 요청을 받아 추론을 수행하고, 결과를 JSON 형식으로 응답합니다.
고려 사항 및 추가 정보
Triton Inference Server를 운영하면서 고려할 만한 몇 가지 사항들입니다.
- Triton Docker 이미지 크기: Triton Docker 이미지는 다양한 프레임워크와 기능을 지원하기 때문에 크기가 다소 클 수 있습니다. (예: nvcr.io/nvidia/tritonserver:25.02-py3 이미지는 수 GB에 달할 수 있습니다). 이는 다양한 모델을 별도의 환경 구성 없이 하나의 서버에서 서비스할 수 있는 유연성을 제공하는 대신, 초기 다운로드 및 배포 시 디스크 공간 및 네트워크 대역폭을 고려해야 함을 의미합니다. 필요한 백엔드(프레임워크)만 포함된 커스텀 이미지를 빌드하여 크기를 줄이는 방법도 있습니다.
- 리소스 관리 및 최적화: 단일 Triton 인스턴스에서 다수의 모델을 호스팅하거나, 트래픽이 많은 단일 모델을 서비스할 경우 GPU/CPU 리소스, 메모리 사용량 등을 세심하게 모니터링하고 최적화해야 합니다.
- config.pbtxt의 instance_group 설정을 통해 모델별로 GPU 또는 CPU 인스턴스 수를 조절할 수 있습니다.
- dynamic_batching 설정을 통해 지연 시간과 처리량 사이의 균형을 맞출 수 있습니다.
- NVIDIA에서 제공하는 Model Analyzer를 활용하면 각 모델에 대한 최적의 배치 크기 및 인스턴스 구성을 찾는 데 도움을 받을 수 있습니다.
- Kubernetes 환경에서의 멀티 노드 운영: 대규모 서비스 환경에서는 단일 노드를 넘어 여러 노드에 Triton 서버를 분산 배포하여 확장성과 가용성을 높일 수 있습니다. Kubernetes와 같은 오케스트레이션 도구를 사용하면 이러한 멀티 노드 배포, 로드 밸런싱, 오토 스케일링 등을 보다 쉽게 관리할 수 있습니다.
- NVIDIA는 EKS (Amazon Elastic Kubernetes Service)와 같은 환경에서 여러 노드에 걸쳐 Triton 및 TensorRT-LLM과 같은 대규모 언어 모델을 배포하는 튜토리얼을 제공하고 있습니다. 다음 링크에서 자세한 내용을 확인할 수 있습니다.
- 이러한 방식은 각 Triton Pod이 독립적으로 모델을 서빙하면서 Kubernetes의 서비스 디스커버리 및 로드 밸런싱 기능을 통해 전체적인 처리 용량을 확장하는 구조입니다.
결론
NVIDIA Triton Inference Server는 다양한 AI 모델을 프로덕션 환경에 효율적으로 배포하고 관리하기 위한 강력하고 유연한 솔루션입니다. 광범위한 프레임워크 지원, 높은 성능, 동시 모델 실행, 클라우드 네이티브 아키텍처 지원 등 다양한 장점을 통해 개발자와 운영자 모두에게 편리함을 제공합니다.
이 글에서 소개된 내용을 바탕으로 Triton을 시작해보시고, 공식 문서와 튜토리얼을 통해 더 많은 기능과 고급 활용법을 익혀보시길 바랍니다. 성공적인 AI 모델 배포 여정에 Triton이 든든한 동반자가 되어줄 것입니다.