Published on

Triton 모델 핫스왑 실패 원인과 해결 체크리스트

Authors

서빙 중인 모델을 끊김 없이 교체하려고 Triton Inference Server의 모델 리로드(핫스왑)를 쓰다 보면, 기대와 달리 모델이 UNAVAILABLE로 떨어지거나 리로드가 무시되는 일이 흔합니다. 문제는 대개 “Triton이 모델 변경을 감지하지 못했거나”, “감지는 했지만 새 버전을 로드하지 못했거나”, “로드는 되었는데 기존 인스턴스와 충돌한” 케이스로 나뉩니다.

이 글에서는 핫스왑 실패를 로그와 상태 API로 빠르게 분류하고, 가장 자주 터지는 원인(레포지토리 구조, config.pbtxt, 버전 디렉터리, 파일 교체 방식, GPU 메모리/OOM, 동시성 설정)을 체크리스트 형태로 해결합니다.

또한 운영 환경이 Kubernetes라면 리로드 실패가 결국 CrashLoopBackOff나 OOM으로 이어질 수 있는데, 그때의 진단 흐름은 아래 글도 같이 참고하면 좋습니다.

Triton의 “핫스왑”이 실제로 의미하는 것

Triton에서 핫스왑은 보통 아래 두 가지 중 하나입니다.

  1. 모델 레포지토리 폴링 기반 자동 로드/언로드

    • --model-control-mode=poll + --repository-poll-secs=N
    • Triton이 주기적으로 모델 레포지토리 변경을 감지해 로드/언로드
  2. 명시적 API 호출로 로드/언로드

    • --model-control-mode=explicit
    • HTTP/gRPC로 load/unload 호출

여기서 “교체”는 보통 같은 모델 이름 아래 새 버전 디렉터리(숫자)를 추가하거나, 기존 버전의 파일을 바꾸고 리로드하는 방식입니다. 운영에서 안전한 방식은 대체로 전자(새 버전 추가)입니다. 후자(파일 덮어쓰기)는 변경 감지/원자성/캐시 문제로 실패 확률이 올라갑니다.

실패 유형을 3분 안에 분류하는 방법

핫스왑이 안 될 때는 감으로 고치지 말고, 아래 3가지를 먼저 확인합니다.

1) 서버 로그에서 “감지했는지” 확인

Triton 로그에서 모델 로드 관련 키워드를 찾습니다.

  • polling model repository
  • loading: <model> 같은 라인
  • failed to load / UNAVAILABLE / INVALID_ARG

Kubernetes라면:

kubectl logs -n <namespace> <triton-pod> | grep -E "poll|load|unload|UNAVAILABLE|failed" -n

부등호가 포함된 부분(예: <namespace>)은 MDX에서 태그로 오인될 수 있으니 위처럼 반드시 백틱 코드로 감쌉니다.

2) 모델 상태 API로 “현재 어떤 버전이 떠 있는지” 확인

HTTP 엔드포인트가 열려 있다면:

curl -s http://127.0.0.1:8000/v2/models | jq
curl -s http://127.0.0.1:8000/v2/models/my_model | jq

버전별 상태까지 보려면(환경에 따라 응답이 다를 수 있음):

curl -s http://127.0.0.1:8000/v2/models/my_model/versions/1 | jq

핵심은 “새 버전을 추가했는데도 목록에 안 보인다”면 감지 실패, “보이는데 UNAVAILABLE”면 로드 실패, “READY인데 트래픽이 구버전으로 간다”면 버전 정책/클라이언트 라우팅 문제일 가능성이 큽니다.

3) 메트릭에서 리로드 직후 리소스 급증 여부 확인

Triton은 Prometheus 메트릭을 노출할 수 있습니다(기본 포트 8002). 리로드 시점에 GPU 메모리나 CPU 메모리 급증이 있으면 OOM/할당 실패로 로드가 깨질 수 있습니다.

curl -s http://127.0.0.1:8002/metrics | grep -E "nv_inference|triton" | head

가장 흔한 원인 1: 모델 레포지토리 구조/버전 규칙 위반

Triton의 기본 규칙은 다음 형태입니다.

  • model_repository/my_model/config.pbtxt
  • model_repository/my_model/1/model.plan 또는 model.onnx

버전 폴더는 반드시 숫자 디렉터리여야 합니다. v2, latest 같은 이름은 버전으로 인식되지 않습니다.

예시(정상):

model_repository/
  my_model/
    config.pbtxt
    1/
      model.onnx
    2/
      model.onnx

예시(문제):

model_repository/
  my_model/
    config.pbtxt
    latest/
      model.onnx

해결 체크:

  • 새 모델을 올릴 때는 2/처럼 새 숫자 버전 디렉터리 추가
  • 기존 버전 폴더 안 파일을 덮어쓰는 방식은 가급적 피하기

가장 흔한 원인 2: 파일 덮어쓰기(비원자적 교체)로 인한 부분 로드

핫스왑을 급하게 하다 보면 model.onnx를 그대로 덮어씁니다. 그런데 Triton이 폴링 중인 타이밍에 파일이 “반쯤 써진 상태”를 읽으면, 파서가 깨지거나 체크섬/크기 불일치로 로드가 실패할 수 있습니다.

권장 배포 패턴: 스테이징 후 원자적 rename

같은 파일시스템에서 mv는 보통 원자적입니다. 따라서 다음처럼 배포합니다.

# 1) 임시 경로에 완성본 업로드
cp model.onnx /repo/my_model/2/model.onnx.tmp

# 2) 완성 후 원자적으로 교체
mv /repo/my_model/2/model.onnx.tmp /repo/my_model/2/model.onnx

더 안전한 방식은 “파일 교체” 자체를 하지 않고 새 버전 디렉터리 전체를 준비한 뒤 디렉터리를 rename하는 것입니다.

mkdir -p /repo/my_model/3
cp config.pbtxt /repo/my_model/config.pbtxt
cp model.onnx /repo/my_model/3/model.onnx
# 준비 완료 후 트래픽 전환은 version policy로

NFS/오브젝트 스토리지 마운트에서는 원자성이 깨질 수 있으니(특히 일부 CSI/네트워크 FS), 가능하면 로컬 디스크에 동기화한 뒤 교체하거나 explicit load 방식을 고려합니다.

가장 흔한 원인 3: config.pbtxt 불일치로 로드 실패

핫스왑 시 가장 많이 놓치는 것이 config.pbtxt와 실제 모델의 입출력 스펙 불일치입니다.

  • 입력 이름/shape/type 변경
  • 출력 텐서 이름 변경
  • dynamic shape 처리 방식 변경
  • max_batch_size 변경

예시로 ONNX 모델을 바꿨는데 입력 이름이 input_ids에서 input으로 바뀌면 로드가 실패하거나, 로드는 되더라도 요청이 전부 INVALID_ARG가 됩니다.

config.pbtxt 예시:

name: "my_model"
platform: "onnxruntime_onnx"
max_batch_size: 8

input [
  {
    name: "input"
    data_type: TYPE_INT64
    dims: [ -1 ]
  }
]
output [
  {
    name: "logits"
    data_type: TYPE_FP32
    dims: [ -1, 10 ]
  }
]

해결 체크:

  • 새 모델을 export할 때 입출력 이름을 고정
  • config.pbtxt를 모델과 함께 버전 관리하고, 변경 시 반드시 함께 배포
  • 리로드 실패 시 로그에 나오는 “expected input … got …” 류 메시지를 최우선으로 확인

가장 흔한 원인 4: 버전 정책(version policy) 때문에 “새 버전이 떠도 안 바뀜”

모델을 2/로 올리고 상태가 READY인데도 호출이 계속 1로 가는 경우가 있습니다. 이때는 대개 버전 정책이 고정되어 있거나, 클라이언트가 특정 버전을 지정해 호출하고 있습니다.

config.pbtxt에서 버전 정책 예시:

version_policy: { latest { num_versions: 1 } }

위 설정이면 가장 최신 버전 1개만 활성화합니다. 반대로 아래처럼 특정 버전만 고정해두면 핫스왑이 안 된 것처럼 보입니다.

version_policy: { specific { versions: [ 1 ] } }

클라이언트가 버전을 명시 호출하는지도 확인하세요.

  • 기본(버전 미지정): /v2/models/my_model/infer
  • 버전 지정: /v2/models/my_model/versions/1/infer

운영에서는 “새 버전 올림 → readiness 확인 → 트래픽 전환”을 명확히 하려면, 아예 클라이언트가 버전을 명시하고 점진적으로 올리는 방식도 많이 씁니다.

가장 흔한 원인 5: GPU/CPU 메모리 부족으로 새 인스턴스 로드 실패

새 버전을 로드할 때 Triton은 일시적으로 구버전과 신버전이 동시에 메모리에 존재할 수 있습니다(정책/타이밍에 따라 다르지만, 무중단을 기대하면 이 상황을 염두에 둬야 합니다). 그 결과 GPU 메모리가 빡빡하면 핫스왑 순간에만 로드가 실패합니다.

증상:

  • 로그에 CUDA out of memory 또는 백엔드 초기화 실패
  • 컨테이너가 OOMKilled 또는 재시작

해결 체크:

  • instance_group 수를 줄여 피크 메모리 완화
  • max_batch_size/동시성 튜닝
  • 필요 시 롤링 방식으로 unloadload (순수 무중단은 포기하고 짧은 드레인으로 타협)

Kubernetes에서 OOM 진단은 아래 글의 흐름이 그대로 적용됩니다.

가장 흔한 원인 6: --model-control-mode 설정과 운영 방식 불일치

의외로 “폴링 모드인 줄 알았는데 explicit였다” 또는 그 반대가 많습니다.

서버 실행 옵션을 확인하세요.

tritonserver \
  --model-repository=/repo \
  --model-control-mode=poll \
  --repository-poll-secs=5

explicit 모드라면 파일을 바꿔도 자동 반영되지 않습니다. 이 경우 API로 로드/언로드를 호출해야 합니다.

# unload
curl -X POST http://127.0.0.1:8000/v2/repository/models/my_model/unload

# load
curl -X POST http://127.0.0.1:8000/v2/repository/models/my_model/load

운영 팁:

  • 폴링은 단순하지만 “언제 반영됐는지”가 모호합니다.
  • explicit은 배포 파이프라인에서 “로드 성공 확인”까지 자동화하기 좋습니다.

실전 디버깅 루틴(재현 가능한 순서)

아래 순서대로 하면 대부분의 핫스왑 실패를 빠르게 좁힐 수 있습니다.

1) 새 버전을 “추가” 방식으로 배포

  • my_model/2/ 생성
  • 완성된 파일을 복사
  • 덮어쓰기 대신 rename 사용

2) Triton이 새 버전을 감지했는지 확인

curl -s http://127.0.0.1:8000/v2/models/my_model | jq

3) 상태가 UNAVAILABLE면 즉시 로그에서 백엔드 에러 확인

kubectl logs -n <namespace> <triton-pod> --tail=300

주요 포인트:

  • config.pbtxt 파싱 오류
  • 엔진/모델 파일 로드 실패(경로, 권한)
  • CUDA/OOM

4) READY인데 트래픽이 안 바뀌면 version policy/클라이언트 호출 확인

  • version_policyspecific인지
  • 클라이언트가 /versions/1/로 고정 호출하는지

5) 리소스 문제면 “동시 로드” 피크를 줄이는 방향으로 조정

  • 인스턴스 수 감소
  • 배치 감소
  • 필요 시 unloadload로 절충

운영에서 추천하는 “안전한 핫스왑” 설계

마지막으로, 실패를 줄이는 구조적 방법입니다.

  1. 새 버전 디렉터리 추가 + latest 정책
    • 가장 단순하고 안전
  2. explicit load + 배포 스크립트에서 검증
    • load 호출 후 /v2/models/... 상태가 READY인지 확인
  3. 헬스체크/카나리
    • 새 버전 READY 확인 후 일부 트래픽만 전환
  4. 리소스 헤드룸 확보
    • 핫스왑 순간에는 구/신 모델이 공존할 수 있음을 전제로 GPU 메모리 예산을 잡기

Kubernetes에서 배포/재시작이 섞이며 증상이 복잡해질 때는 CrashLoopBackOff 진단 글도 함께 보면 원인 분리가 빨라집니다.

마무리

Triton 모델 핫스왑 실패는 “모델 파일만 바꾸면 되겠지”라는 기대와 달리, 변경 감지 방식, 원자적 배포, config.pbtxt 정합성, 버전 정책, 리로드 순간 리소스 피크가 동시에 맞물리며 발생합니다.

가장 재현성 높은 해결책은 다음 한 줄로 정리됩니다.

  • 덮어쓰지 말고 새 숫자 버전을 추가하고, 가능하면 explicit load로 성공을 확인하라.

원하시면 사용 중인 백엔드(ONNX Runtime, TensorRT, Python backend), 현재 config.pbtxt, 그리고 리로드 시점의 로그 일부(민감정보 제거)를 기준으로 “어느 유형의 실패인지”를 더 구체적으로 짚어서 수정안을 제안해드릴 수 있습니다.