- Published on
TorchServe 503·OOM·워커 튜닝 실전 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
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가 너무 느슨하거나, 반대로 너무 엄격해서 계속 재시작
확인 방법
managementAPI에서 모델 상태 확인- 로그에서
Model ... loaded이전에 요청이 들어오는지 확인
2) 워커가 죽었거나(크래시) 응답 불가 상태
- 핸들러 예외로 워커가 죽고 재시작을 반복
- GPU OOM으로 워커가 종료되고, 재로딩하며 503이 간헐적으로 발생
3) 큐 적체로 인한 타임아웃(사실상 과부하)
- 워커 수가 부족하거나, 배치 설정이 부적절해 처리량이 낮음
- 상위 프록시(Nginx/ALB/Ingress) 타임아웃이 TorchServe 처리 시간보다 짧음
4) 토치서브 자체 타임아웃 설정
- 모델 추론이 길어졌는데 기본 타임아웃이 낮아 503/504로 떨어짐
먼저 “지표/로그”로 원인을 분리하기
문제 해결 속도를 올리려면 원인 분리를 먼저 해야 합니다. 최소한 아래 3종을 확보하세요.
- TorchServe 로그
ts_log.log(서버/관리)model_log.log(모델/워커)
- 컨테이너/노드 이벤트
kubectl describe pod에서OOMKilled여부
- 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 워커 튜닝의 핵심: “워커당 메모리”부터 계산
많은 팀이 minWorkers와 maxWorkers를 감으로 올리다가 OOM 또는 503을 동시에 키웁니다. 올바른 순서는 다음입니다.
- 워커 1개로 띄운 뒤, steady 상태 RSS(또는 GPU VRAM) 측정
- 입력을 대표 케이스로 10~30분 태워서 peak 메모리 확인
워커당 상주 메모리 + 피크 여유분으로 워커 상한 계산
예시(개념)
- 컨테이너 메모리 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
- 서비스 메시/프록시의 기본 타임아웃
권장 순서
- 목표 p95 지연을 정한다
- TorchServe 타임아웃은 p99보다 약간 크게
- 프록시 타임아웃은 TorchServe보다 약간 크게
배포 직후 OOM/503이 터질 때: 모델 로딩 전략 점검
배포 직후에만 장애가 나면 다음을 의심합니다.
- 모델 로딩 시점에 워커가 동시에 가중치를 메모리에 올려 피크가 튐
initial_workers를 크게 줘서 동시에 로딩
해결 패턴
initial_workers=1로 시작하고, 준비 완료 후 점진적으로 스케일- 롤링 업데이트에서 surge를 줄여 “동시 로딩”을 피함
(체크리스트) 현장에서 바로 쓰는 진단 순서
kubectl describe pod에서OOMKilled여부 확인model_log.log에서CUDA out of memory또는 핸들러 예외 확인- management API로 모델 상태가
LOADING에 갇혔는지 확인 - 503이 특정 시간대에 몰리면 큐 적체와 워커 수/배치 설정 재검토
- 상위 프록시 타임아웃이 TorchServe 처리 시간보다 짧지 않은지 확인
OOM 대응 아이디어가 더 필요하면, 이미지/생성 모델 계열이지만 메모리 절감 접근 자체는 유사합니다: SDXL+ControlNet 메모리폭주 OOM 해결 8가지
결론: “워커를 올리기 전에” 안정성부터 만든다
TorchServe 운영에서 가장 흔한 실패는 503이 보이자마자 워커를 늘리고, 그 결과 OOM과 재시작 루프가 생기면서 더 큰 503으로 돌아오는 패턴입니다.
안정적인 순서는 다음입니다.
- 입력 제한과 타임아웃 정렬로 폭주를 막고
- 워커 1개 기준 메모리/지연을 측정한 뒤
- 워커 상한을 계산하고, 배치는 작은 값부터 실험적으로 올리며
- readiness를 “모델 준비 완료” 기준으로 구성해 배포 직후 503을 없앤다
이 루틴만 지켜도 TorchServe의 503·OOM·워커 튜닝 이슈는 대부분 재현 가능하고, 재현 가능해지면 해결도 빨라집니다.