Published on

TorchServe 503·OOM·워커 튜닝 실전 가이드

Authors

TorchServe는 “일단 띄우면 끝”이 아니라, 트래픽이 붙는 순간부터 503, OOM, 지연 폭증 같은 운영 이슈가 본격적으로 시작됩니다. 특히 TorchServe는 워커 프로세스(또는 스레드), 모델 로딩 방식, 배치 처리, JVM/파이썬 메모리, 컨테이너 리소스 제한이 서로 얽히면서 문제가 재현되기 쉬운 편입니다.

이 글에서는 다음 3가지를 중심으로 실전적인 진단 루틴과 튜닝 포인트를 정리합니다.

  • 503이 나는 진짜 원인(헬스 체크, 워커 준비, 큐 적체, 타임아웃)
  • OOM의 유형(컨테이너 OOMKilled, 파이썬 힙/네이티브, GPU VRAM)별 대응
  • 워커/배치/동시성 파라미터를 “안전하게” 올리는 방법

관련해서 OOM 진단 자체가 급하면 먼저 이 글의 체크리스트도 같이 보면 좋습니다: EKS Pod CrashLoopBackOff? OOMKilled 5분 진단

TorchServe에서 503이 발생하는 대표 시나리오

503은 보통 “서버가 요청을 처리할 준비가 안 됨”을 의미하지만, TorchServe에서는 아래 케이스로 자주 수렴합니다.

1) 모델 로딩 중인데 트래픽이 먼저 들어옴

  • 모델이 LOADING 상태인데 로드밸런서/Ingress가 트래픽을 전달
  • readiness probe가 너무 느슨하거나, 반대로 너무 엄격해서 계속 재시작

확인 방법

  • management API에서 모델 상태 확인
  • 로그에서 Model ... loaded 이전에 요청이 들어오는지 확인

2) 워커가 죽었거나(크래시) 응답 불가 상태

  • 핸들러 예외로 워커가 죽고 재시작을 반복
  • GPU OOM으로 워커가 종료되고, 재로딩하며 503이 간헐적으로 발생

3) 큐 적체로 인한 타임아웃(사실상 과부하)

  • 워커 수가 부족하거나, 배치 설정이 부적절해 처리량이 낮음
  • 상위 프록시(Nginx/ALB/Ingress) 타임아웃이 TorchServe 처리 시간보다 짧음

4) 토치서브 자체 타임아웃 설정

  • 모델 추론이 길어졌는데 기본 타임아웃이 낮아 503/504로 떨어짐

먼저 “지표/로그”로 원인을 분리하기

문제 해결 속도를 올리려면 원인 분리를 먼저 해야 합니다. 최소한 아래 3종을 확보하세요.

  1. TorchServe 로그
  • ts_log.log (서버/관리)
  • model_log.log (모델/워커)
  1. 컨테이너/노드 이벤트
  • kubectl describe pod에서 OOMKilled 여부
  1. TorchServe 메트릭(가능하면 Prometheus)
  • 큐 길이, 요청 수, 지연, 워커 상태

관리 API로 상태 확인(기본 포트 예시)

아래에서 8081은 management 포트 예시입니다.

curl -s http://localhost:8081/models | jq
curl -s http://localhost:8081/models/my_model | jq

모델이 LOADING에 오래 머물면, “모델 로딩이 느리다” 또는 “워커가 뜨다 죽는다”로 좁혀집니다.

OOM 유형 3가지: 어디가 터졌는지부터 확정

TorchServe의 OOM은 크게 3종입니다.

1) 컨테이너 메모리 제한으로 OOMKilled

  • K8s에서 가장 흔한 형태
  • 이벤트에 OOMKilled가 찍히고 프로세스가 강제 종료
kubectl describe pod my-torchserve-pod
kubectl get pod my-torchserve-pod -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'

대응 방향

  • resources.limits.memory 상향 또는
  • 워커 수/배치/모델 로딩 방식으로 상주 메모리 감소

2) 파이썬 프로세스 RSS 증가(네이티브/텐서 캐시 포함)

  • torch 텐서 캐시, 토크나이저/전처리 객체, numpy 버퍼 등으로 RSS가 서서히 증가
  • 컨테이너 제한에 닿기 전까지는 티가 덜 나다가 특정 시점에 OOMKilled

대응 방향

  • 워커당 상주 메모리 측정 후 워커 수 재산정
  • 불필요한 캐시 제거, 입력 크기 제한, 배치 크기 제한

3) GPU VRAM OOM

  • 워커가 뜨자마자 죽거나, 특정 입력에서만 죽음
  • 로그에 CUDA out of memory가 찍힘

대응 방향

  • 배치/입력 길이 제한
  • mixed precision, quantization, attention 최적화 등

VRAM OOM은 모델 종류에 따라 해결책 폭이 넓어서, 아래 글의 패턴이 그대로 적용되는 경우가 많습니다.

TorchServe 워커 튜닝의 핵심: “워커당 메모리”부터 계산

많은 팀이 minWorkersmaxWorkers를 감으로 올리다가 OOM 또는 503을 동시에 키웁니다. 올바른 순서는 다음입니다.

  1. 워커 1개로 띄운 뒤, steady 상태 RSS(또는 GPU VRAM) 측정
  2. 입력을 대표 케이스로 10~30분 태워서 peak 메모리 확인
  3. 워커당 상주 메모리 + 피크 여유분으로 워커 상한 계산

예시(개념)

  • 컨테이너 메모리 limit이 16Gi
  • TorchServe/JVM/OS 오버헤드로 2Gi 예약
  • 워커 1개 RSS가 평균 3Gi, 피크 4Gi

그러면 안전한 워커 상한은 대략 floor((16-2)/4)=3 수준부터 시작하는 게 맞습니다.

config.properties로 보는 실전 파라미터 세트

TorchServe는 배포 형태에 따라 설정이 갈리지만, 운영에서 자주 만지는 축은 아래입니다.

  • 워커 개수: minWorkers, maxWorkers
  • 배치: batchSize, maxBatchDelay
  • 타임아웃: default_response_timeout
  • 큐/스레드: 프론트엔드 스레드, 네트워크 스레드 등(버전에 따라 키가 다름)

예시: config.properties

아래 예시는 “503을 줄이기 위해 readiness를 보수적으로, 워커는 메모리 기반으로 제한, 배치는 지연을 과도하게 늘리지 않게” 잡는 패턴입니다.

inference_address=http://0.0.0.0:8080
management_address=http://0.0.0.0:8081
metrics_address=http://0.0.0.0:8082

# 모델 응답이 길어지는 경우를 대비(초)
default_response_timeout=300

# (환경에 따라) 로그 레벨/경로
# log_level=INFO

모델별 워커/배치는 보통 model-config.yaml 또는 등록 시 파라미터로 제어합니다.

모델 등록 시 워커/배치 파라미터 예시

8081 management API로 모델을 로드하면서 워커/배치를 지정하는 방식입니다.

curl -X POST "http://localhost:8081/models?url=my_model.mar&model_name=my_model&initial_workers=2&synchronous=true"

그리고 스케일 조정은 다음처럼 합니다.

curl -X PUT "http://localhost:8081/models/my_model?min_worker=2&max_worker=3"

포인트

  • synchronous=true로 로딩 완료까지 대기시키면, 배포 직후 503을 줄이는 데 도움이 됩니다.
  • 다만 배포 파이프라인 시간이 늘 수 있으니 롤링 전략과 함께 봐야 합니다.

503을 줄이는 운영 패턴: “준비 완료 후 트래픽” 강제

K8s에서 TorchServe는 모델 로딩이 끝나기 전까지 readiness를 false로 유지하는 것이 안전합니다.

readiness probe를 management API 기반으로 구성

아래는 개념 예시입니다. 중요한 건 probe가 “프로세스가 살아있다”가 아니라 “모델이 실제로 준비됐다”를 보게 만드는 것입니다.

readinessProbe:
  httpGet:
    path: /models
    port: 8081
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 12

더 엄격하게 하려면 특정 모델 엔드포인트를 체크하는 스크립트 방식도 고려합니다. 단, 이때는 curl 기반 exec probe가 컨테이너에 포함돼야 합니다.

배치 튜닝: 처리량과 지연의 균형점 찾기

배치는 503과 OOM에 동시에 영향을 줍니다.

  • batchSize를 올리면 처리량이 늘 수 있지만, VRAM/RAM 피크가 커져 OOM 위험 증가
  • maxBatchDelay를 올리면 배치가 잘 묶이지만, p95 지연이 커져 상위 타임아웃에 걸릴 수 있음

실전 팁

  • 온라인 서빙(사용자 요청)이라면 maxBatchDelay는 보통 수 ms~수십 ms에서 시작
  • OOM이 한 번이라도 났다면, 먼저 batchSize부터 낮추고 워커를 조정

모델 아카이브에 배치 설정 포함 예시(개념)

mar 내부에 포함되는 model-config.yaml 또는 핸들러 설정으로 배치를 다루는 경우가 많습니다.

minWorkers: 2
maxWorkers: 3
batchSize: 4
maxBatchDelay: 50
responseTimeout: 300

키 이름은 TorchServe 버전/배포 방식에 따라 달라질 수 있으니, 현재 사용하는 배포 템플릿 기준으로 확인하세요.

워커가 늘수록 느려지는 경우: CPU 경합과 GIL, 토크나이저 병목

워커를 늘리면 처리량이 선형으로 늘 것 같지만, 실제로는 다음 병목이 자주 나타납니다.

  • CPU 코어 수 대비 워커 과다로 컨텍스트 스위칭 증가
  • 전처리(이미지 디코딩, 토크나이즈)가 CPU 바운드인데 워커가 과도해져 경합
  • 파이썬 GIL 영향(특히 전처리가 파이썬 레벨 루프일 때)

대응

  • 전처리를 벡터화하거나, 가능하면 네이티브 라이브러리로 이동
  • 워커 수를 올리기 전에 CPU 사용률과 run queue를 확인
  • “워커 수 증가”가 아니라 “배치 + 적정 워커 + 입력 제한” 조합으로 해결

OOM을 줄이는 입력 방어선: 제한이 곧 안정성

TorchServe 장애의 상당수는 “예상보다 큰 입력”에서 시작됩니다.

  • 텍스트: 토큰 길이 제한 미설정
  • 이미지: 초고해상도 입력 그대로 통과
  • 멀티파트/바이너리: 압축 폭탄성 입력

핸들러에서 입력 제한을 명시적으로 두면 OOM과 지연 폭증을 동시에 줄일 수 있습니다.

핸들러에서 입력 크기 제한 예시

# handler.py
import json

MAX_TEXT_CHARS = 8000

class MyHandler:
    def initialize(self, ctx):
        self.manifest = ctx.manifest
        self.device = "cuda" if ctx.system_properties.get("gpu_id") is not None else "cpu"

    def preprocess(self, data):
        raw = data[0].get("body")
        if raw is None:
            raise ValueError("empty body")

        if isinstance(raw, (bytes, bytearray)):
            raw = raw.decode("utf-8")

        if len(raw) > MAX_TEXT_CHARS:
            raise ValueError("input too large")

        return raw

    def inference(self, text):
        # 실제 모델 추론
        return {"ok": True, "len": len(text)}

    def postprocess(self, out):
        return [json.dumps(out)]

이런 방어는 “정상 사용자 경험”에도 도움이 됩니다. 제한을 넘는 입력은 빠르게 4xx로 돌려서 시스템을 보호해야 합니다.

상위 프록시/로드밸런서 타임아웃도 같이 맞추기

TorchServe가 300초까지 기다리게 설정되어 있어도, 앞단이 60초 타임아웃이면 사용자는 503/504를 봅니다.

체크 포인트

  • Ingress 컨트롤러의 proxy_read_timeout, proxy_send_timeout
  • ALB idle timeout
  • 서비스 메시/프록시의 기본 타임아웃

권장 순서

  1. 목표 p95 지연을 정한다
  2. TorchServe 타임아웃은 p99보다 약간 크게
  3. 프록시 타임아웃은 TorchServe보다 약간 크게

배포 직후 OOM/503이 터질 때: 모델 로딩 전략 점검

배포 직후에만 장애가 나면 다음을 의심합니다.

  • 모델 로딩 시점에 워커가 동시에 가중치를 메모리에 올려 피크가 튐
  • initial_workers를 크게 줘서 동시에 로딩

해결 패턴

  • initial_workers=1로 시작하고, 준비 완료 후 점진적으로 스케일
  • 롤링 업데이트에서 surge를 줄여 “동시 로딩”을 피함

(체크리스트) 현장에서 바로 쓰는 진단 순서

  1. kubectl describe pod에서 OOMKilled 여부 확인
  2. model_log.log에서 CUDA out of memory 또는 핸들러 예외 확인
  3. management API로 모델 상태가 LOADING에 갇혔는지 확인
  4. 503이 특정 시간대에 몰리면 큐 적체와 워커 수/배치 설정 재검토
  5. 상위 프록시 타임아웃이 TorchServe 처리 시간보다 짧지 않은지 확인

OOM 대응 아이디어가 더 필요하면, 이미지/생성 모델 계열이지만 메모리 절감 접근 자체는 유사합니다: SDXL+ControlNet 메모리폭주 OOM 해결 8가지

결론: “워커를 올리기 전에” 안정성부터 만든다

TorchServe 운영에서 가장 흔한 실패는 503이 보이자마자 워커를 늘리고, 그 결과 OOM과 재시작 루프가 생기면서 더 큰 503으로 돌아오는 패턴입니다.

안정적인 순서는 다음입니다.

  • 입력 제한과 타임아웃 정렬로 폭주를 막고
  • 워커 1개 기준 메모리/지연을 측정한 뒤
  • 워커 상한을 계산하고, 배치는 작은 값부터 실험적으로 올리며
  • readiness를 “모델 준비 완료” 기준으로 구성해 배포 직후 503을 없앤다

이 루틴만 지켜도 TorchServe의 503·OOM·워커 튜닝 이슈는 대부분 재현 가능하고, 재현 가능해지면 해결도 빨라집니다.