이 글은 누구를 위한 것인가
- 자체 훈련 ML 모델을 프로덕션에 서빙해야 하는 팀
- Triton Inference Server 설정이 처음인 ML 엔지니어
- Kubernetes에서 GPU 워크로드를 운영하는 DevOps
들어가며
FastAPI로 ML 모델을 서빙하면 편하지만, GPU 활용률이 낮고 배칭이 없어 비효율적이다. Triton Inference Server는 GPU 최대 활용, 동적 배칭, 앙상블 파이프라인을 지원한다. 배포·롤아웃 단위는 Kubernetes Deployment 문서를 기준으로 잡는다.
이 글은 bluefoxdev.kr의 ML 서빙 인프라 를 참고하여 작성했습니다.
1. Triton 서빙 아키텍처
[Triton Inference Server 구성]
모델 저장소 구조:
model_repository/
├── text_classifier/
│ ├── config.pbtxt # 모델 설정
│ └── 1/ # 버전 1
│ └── model.onnx
├── embedding_model/
│ ├── config.pbtxt
│ └── 1/
│ └── model.pt
└── ensemble_pipeline/
├── config.pbtxt # 앙상블 정의
└── 1/ # 빈 디렉토리
[동적 배칭 (Dynamic Batching)]
개별 요청 → 큐에 적재 → 배치 구성 → GPU 실행
최대 대기 시간: 5-20ms (레이턴시 허용 범위 내)
배치 크기: 8-64 (GPU 메모리에 따라)
효과: GPU 활용률 20% → 80%+
[Kubernetes GPU 리소스]
resources:
limits:
nvidia.com/gpu: 1
tolerations:
- key: nvidia.com/gpu
operator: Exists
[모델 A/B 배포]
트래픽 스플릿: canary 10% → 50% → 100%
메트릭 모니터링: 오류율, 레이턴시, 정확도
자동 롤백: 이상 감지 시
2. Triton 배포 설정
# triton-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: triton-inference-server
spec:
replicas: 2
selector:
matchLabels:
app: triton
template:
metadata:
labels:
app: triton
spec:
containers:
- name: triton
image: nvcr.io/nvidia/tritonserver:24.01-py3
args:
- tritonserver
- --model-repository=s3://my-model-bucket/models
- --http-port=8000
- --grpc-port=8001
- --metrics-port=8002
- --log-verbose=0
ports:
- containerPort: 8000
- containerPort: 8001
- containerPort: 8002
resources:
limits:
nvidia.com/gpu: "1"
memory: "16Gi"
requests:
memory: "8Gi"
readinessProbe:
httpGet:
path: /v2/health/ready
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
# config.pbtxt 생성 유틸리티
def generate_triton_config(
model_name: str,
backend: str, # "onnxruntime", "pytorch", "tensorrt"
input_shapes: list[dict],
output_shapes: list[dict],
max_batch_size: int = 32,
dynamic_batching: bool = True,
) -> str:
inputs = "\n".join(f""" {{
name: "{inp['name']}"
data_type: {inp['dtype']}
dims: {inp['dims']}
}}""" for inp in input_shapes)
outputs = "\n".join(f""" {{
name: "{out['name']}"
data_type: {out['dtype']}
dims: {out['dims']}
}}""" for out in output_shapes)
dynamic_batch_config = """
dynamic_batching {
preferred_batch_size: [ 4, 8, 16 ]
max_queue_delay_microseconds: 10000
}""" if dynamic_batching else ""
return f"""name: "{model_name}"
backend: "{backend}"
max_batch_size: {max_batch_size}
input [{inputs}]
output [{outputs}]
{dynamic_batch_config}
instance_group [
{{
kind: KIND_GPU
count: 1
}}
]"""
import tritonclient.grpc as grpcclient
import numpy as np
def triton_inference(
server_url: str,
model_name: str,
inputs: dict[str, np.ndarray],
output_names: list[str],
) -> dict[str, np.ndarray]:
"""Triton gRPC 추론 클라이언트"""
client = grpcclient.InferenceServerClient(url=server_url)
triton_inputs = []
for name, array in inputs.items():
inp = grpcclient.InferInput(name, array.shape, "FP32")
inp.set_data_from_numpy(array)
triton_inputs.append(inp)
triton_outputs = [grpcclient.InferRequestedOutput(name) for name in output_names]
response = client.infer(
model_name=model_name,
inputs=triton_inputs,
outputs=triton_outputs,
)
return {name: response.as_numpy(name) for name in output_names}
def canary_deploy_model(
old_model: str,
new_model: str,
traffic_pct: int = 10,
) -> callable:
"""카나리 배포: 트래픽 분할"""
import random
def route_request(request_data):
if random.randint(1, 100) <= traffic_pct:
return triton_inference("triton:8001", new_model, request_data, ["output"])
else:
return triton_inference("triton:8001", old_model, request_data, ["output"])
return route_request
마무리
Triton은 단순 FastAPI 서빙 대비 GPU 활용률을 4배 이상 높인다. 동적 배칭을 켜면 레이턴시를 5-20ms 희생하고 처리량을 3-5배 늘릴 수 있다. Kubernetes HPA(Horizontal Pod Autoscaler)와 결합하면 트래픽에 따라 자동 스케일링이 가능하다.