Published on

Seldon Core v2로 LLM 카나리 배포와 롤백

Authors

서빙 중인 LLM을 바꾸는 순간이 가장 위험합니다. 새 모델이 더 똑똑해 보이더라도 실제 트래픽에서는 지연이 튀거나, 토큰 비용이 급증하거나, 특정 프롬프트에서만 품질이 무너지는 일이 흔합니다. 그래서 운영 환경에서는 “한 번에 전부 교체”가 아니라 카나리로 조금씩 노출하고, 지표로 승격 여부를 결정하며, 이상 신호가 보이면 즉시 롤백할 수 있어야 합니다.

Seldon Core v2는 쿠버네티스 네이티브 방식으로 모델(또는 파이프라인)을 배포하고, 라우팅/관측을 엮어 **점진적 릴리스(카나리, 블루/그린)**를 구성하기 좋습니다. 이 글에서는 LLM 배포라는 현실적인 제약(긴 워밍업, GPU 리소스, 스트리밍, 토큰 단위 지표)을 전제로, Seldon Core v2에서 카나리와 롤백을 운영 관점에서 어떻게 설계하는지 다룹니다.

관련해서 지연이 갑자기 늘어나는 문제는 모델 자체보다 서빙 계층에서 터지는 경우가 많습니다. 트러블슈팅 체크리스트는 Triton 배포 후 지연 폭증 8가지 원인도 함께 참고하면 좋습니다.

Seldon Core v2에서의 “배포 단위” 이해하기

Seldon Core v2를 LLM 서빙에 쓸 때, 보통 배포 단위는 다음 중 하나로 정리됩니다.

  • 단일 모델 서버: 예) vLLM, TGI, Triton, llama.cpp 서버 등을 그대로 띄우고 Seldon이 라우팅과 관측을 담당
  • 파이프라인: 전처리(프롬프트 템플릿) → LLM → 후처리(필터링, JSON 스키마 검증) 같은 체인
  • 멀티 모델/멀티 라우트: 같은 엔드포인트에서 버전별로 트래픽을 나누는 구조

LLM에서 중요한 포인트는 “버전 차이”가 단순히 가중치 파일만 바뀌는 게 아니라는 점입니다.

  • 토크나이저가 바뀌면 입력 길이/토큰화 결과가 달라져 지연과 비용이 바뀜
  • 시스템 프롬프트가 바뀌면 품질 지표가 바뀜
  • 서빙 엔진(vLLM, TGI) 설정이 바뀌면 배치/캐시/스트리밍 동작이 바뀜

따라서 카나리/롤백의 대상은 “모델 파일”이 아니라 **서빙 스펙 전체(이미지, 설정, 프롬프트 템플릿, 리소스, 오토스케일링)**라고 보는 게 안전합니다.

카나리 전략의 핵심: 무엇을 기준으로 승격할 것인가

일반 웹 서비스는 5xx 비율이나 p95 latency만 봐도 충분한 경우가 많지만, LLM은 품질이 숫자로 잘 안 떨어집니다. 그래서 카나리 승격 기준을 다음처럼 운영 지표 + 품질 프록시 지표로 나누면 실무에서 굴리기 쉽습니다.

운영 지표(필수)

  • p50/p95/p99 latency
  • 오류율: 타임아웃, 429, 5xx
  • GPU 메모리/사용률, KV 캐시 압박
  • 토큰 처리량: tokens/sec 또는 time-to-first-token
  • 스로틀링/큐 길이(동시성 포화)

로컬/자체 호스팅 LLM에서 OOM이 잦다면 KV 캐시와 배치 전략이 원인일 때가 많습니다. 이 부분은 Transformers 로컬 LLM OOM과 KV 캐시 최적화도 함께 보면 카나리 구간에서 “왜 특정 트래픽에서만 죽는지”를 빨리 좁힐 수 있습니다.

품질 프록시 지표(권장)

  • 구조화 출력 성공률: JSON 스키마 검증 통과율
  • 금칙어/정책 위반률(간단한 룰 기반이라도)
  • 재시도율(클라이언트가 파싱 실패로 재요청하는 비율)
  • “정답”이 있는 소규모 온라인 평가(가능하면)

특히 구조화 출력 성공률은 카나리 승격 판단에 매우 유용합니다. 모델이 좋아졌는데도 프롬프트가 바뀌며 출력 형식이 흔들리면, 실제 제품은 더 나빠집니다. 구조화 프롬프트 패턴은 Chain-of-Thought 없이 성능↑ 구조화 프롬프트에서 정리한 방식이 카나리 품질 지표를 안정화하는 데 도움이 됩니다.

배포 전 준비: “롤백 가능”하게 만드는 체크리스트

카나리와 롤백은 YAML만으로 되는 게 아니라, 실패했을 때 안전하게 돌아갈 수 있는 상태를 미리 만들어야 합니다.

1) 이미지와 설정의 불변성

  • 모델/서빙 이미지 태그는 latest 금지
  • 모델 아티팩트는 해시 기반 경로 또는 버전 디렉터리로 고정
  • 환경변수/서빙 플래그도 GitOps로 추적

2) 워밍업과 준비 상태(Ready) 정의

LLM은 “Pod가 떴다”와 “서빙 가능”이 다릅니다.

  • 모델 로딩 완료
  • GPU 메모리 할당 완료
  • 토크나이저 로딩 완료
  • 첫 토큰까지 스트리밍 가능한 상태

따라서 readinessProbe를 단순 HTTP 200으로 두지 말고, 최소한 “간단한 프롬프트로 1토큰 생성까지 성공” 같은 엔드포인트를 두는 게 좋습니다.

3) 관측 지표의 라벨링

카나리/롤백을 자동화하려면, 최소한 다음 라벨이 메트릭/로그에 들어가야 합니다.

  • 모델 버전(예: model_version=v1, model_version=v2)
  • 라우트/디플로이먼트 이름
  • 요청 유형(스트리밍/논스트리밍)

Seldon Core v2 카나리 배포 구성 패턴

Seldon Core v2에서의 카나리는 개념적으로 “같은 엔드포인트에 두 개의 백엔드(Stable/Canary)를 두고 가중치로 분배”입니다. 실제 리소스 이름은 조직/차트/설치 방식에 따라 다를 수 있으니, 아래 예시는 패턴 중심으로 보시면 됩니다.

예시 1: 두 버전의 모델 서버를 동시에 띄우고 가중치 라우팅

아래는 stablecanary를 동시에 배포하고, 초기에 카나리로 5%만 보내는 형태의 예시입니다. (실제 CRD 필드명은 설치한 Seldon Core v2 버전에 따라 다를 수 있으니, 적용 전 kubectl explain로 확인하세요.)

apiVersion: mlops.seldon.io/v1alpha1
kind: Model
metadata:
  name: llm-stable
spec:
  predictors:
    - name: default
      replicas: 2
      graph:
        name: llm
        implementation: CUSTOM
      componentSpecs:
        - spec:
            containers:
              - name: llm
                image: ghcr.io/acme/llm-server:1.3.7
                env:
                  - name: MODEL_ID
                    value: "acme-llm-7b-v1"
                resources:
                  limits:
                    nvidia.com/gpu: "1"
---
apiVersion: mlops.seldon.io/v1alpha1
kind: Model
metadata:
  name: llm-canary
spec:
  predictors:
    - name: default
      replicas: 1
      graph:
        name: llm
        implementation: CUSTOM
      componentSpecs:
        - spec:
            containers:
              - name: llm
                image: ghcr.io/acme/llm-server:1.4.0
                env:
                  - name: MODEL_ID
                    value: "acme-llm-7b-v2"
                resources:
                  limits:
                    nvidia.com/gpu: "1"
---
apiVersion: mlops.seldon.io/v1alpha1
kind: ModelGateway
metadata:
  name: llm-gateway
spec:
  routes:
    - name: llm
      trafficPolicy:
        weightedTargets:
          - name: llm-stable
            weight: 95
          - name: llm-canary
            weight: 5

운영 팁:

  • 카나리 Pod는 처음부터 replicas=1로 두되, 트래픽을 받기 전에 워밍업이 완료되도록 readinessProbe를 강하게 잡습니다.
  • 모델 로딩 시간이 길면, 카나리 비율을 올리기 전에 미리 replicas를 늘려 “용량을 먼저 확보”하고 “트래픽을 나중에 이동”하는 식으로 운영합니다.

예시 2: 헤더 기반 라우팅으로 “내부 사용자만” 카나리 체험

가중치 라우팅은 랜덤 샘플링이라 재현이 어렵습니다. 품질 검증 단계에서는 특정 사용자/테스터 트래픽만 카나리로 보내는 게 훨씬 유용합니다.

apiVersion: mlops.seldon.io/v1alpha1
kind: ModelGateway
metadata:
  name: llm-gateway
spec:
  routes:
    - name: llm
      match:
        headers:
          - name: "x-canary"
            exact: "true"
      forwardTo: llm-canary
    - name: llm
      forwardTo: llm-stable

이 방식의 장점:

  • QA 팀이 같은 프롬프트를 반복해도 항상 같은 버전으로 라우팅되어 비교가 쉬움
  • 장애가 나도 일반 사용자 영향이 거의 없음

단점:

  • 실제 트래픽 분포(길이, 주제, 동시성)를 충분히 반영하지 못할 수 있음

그래서 보통은 “헤더 기반으로 1차 검증” 후 “가중치 카나리로 2차 검증”을 권장합니다.

승격(프로모션)과 롤백 절차

카나리를 올렸다고 끝이 아니라, 승격과 롤백을 명확한 런북으로 만들어야 야간 장애에서 덜 흔들립니다.

승격 런북(예시)

  1. 카나리 1%로 30분
  2. 지표가 정상(오류율, p95, GPU OOM 0)이고 품질 프록시 지표가 기준 이상이면 5%
  3. 5%에서 2시간, 이상 없으면 25%
  4. 25%에서 피크 타임을 한 번 통과하면 50%
  5. 50%에서 하루 운영 후 100%
  6. stablecanary 이름을 뒤집거나, stable을 새 버전으로 재배포해 정리

롤백 런북(예시)

  • 즉시 조치: 가중치 canary=0, stable=100
  • 원인 분석: 로그에서 model_version 라벨로 실패 요청을 분리
  • 리소스 회수: 카나리 Pod replicas=0 또는 디플로이 삭제

가중치만 0으로 내려도 대부분의 장애는 멈추지만, GPU 메모리 누수나 노드 압박이 있으면 카나리 Pod 자체를 내리는 게 더 안전합니다.

카나리에서 자주 터지는 LLM 특화 이슈 6가지

1) 첫 토큰 지연(time-to-first-token) 급증

스트리밍 LLM은 사용자 체감이 p95 latency보다 첫 토큰에 좌우됩니다. 카나리에서 TTFT가 튀면 아래를 의심합니다.

  • 프롬프트 길이 증가(시스템 프롬프트 변경)
  • 토크나이저 변경
  • 배치 정책 변경으로 큐 대기 증가

2) KV 캐시 압박으로 OOM

카나리 비율은 낮아도, 특정 요청(긴 컨텍스트)이 들어오면 바로 죽을 수 있습니다. 카나리 단계에서 긴 입력이 섞이는지 확인하고, 최대 컨텍스트/최대 생성 토큰을 정책으로 제한하세요.

3) 출력 포맷 붕괴

특히 JSON 출력은 모델 버전이 바뀌면 작은 변화로도 깨집니다. 이때는 “프롬프트를 더 엄격하게” 또는 “후처리에서 강제 보정”이 필요합니다.

4) 안전/정책 위반률 증가

모델이 바뀌면 거절 정책이 달라질 수 있습니다. 간단한 룰 기반 필터라도 카나리에서 먼저 켜고, 위반률을 수치로 봐야 합니다.

5) 동일 요청의 비결정성 증가

샘플링 파라미터(temperature, top_p)가 바뀌면 재현이 어려워집니다. 카나리 비교를 위해 최소한 내부 평가 트래픽은 temperature=0 같은 결정적 설정을 병행하는 게 좋습니다.

6) 노드/네트워크 레벨 이슈로 “모델이 느려 보이는” 상황

EKS에서 노드가 불안정하거나 CNI/보안그룹 이슈가 있으면, 서빙이 아니라 인프라가 병목이 됩니다. 노드가 NotReady를 반복하면 모델 지연이 튀는 것처럼 보일 수 있으니, Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검 같은 인프라 체크도 카나리 런북에 포함시키세요.

자동 롤백까지: 지표 기반 가드레일 설계

사람이 보는 것만으로는 늦습니다. 최소한 “자동 중단” 장치를 두면 피해를 줄일 수 있습니다.

권장 가드레일 예시

  • 5xx 비율이 1%를 5분 이상 초과하면 카나리 가중치 0으로
  • p95 latency가 stable 대비 1.5배 이상이면 승격 중단
  • OOM 이벤트가 1회라도 발생하면 카나리 중단(LLM은 한 번 터지면 연쇄적으로 영향)
  • 구조화 출력 성공률이 기준(예: 98%) 아래로 떨어지면 중단

이 가드레일을 구현하는 방법은 여러 가지가 있지만, 핵심은 “stable과 canary를 같은 대시보드에서 비교”하는 것입니다. 즉, 메트릭에 model_version 같은 라벨이 반드시 있어야 합니다.

실전 코드: 간단한 카나리 호출 스크립트

헤더 기반 카나리를 쓰면, 클라이언트에서 테스트를 쉽게 자동화할 수 있습니다.

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://llm.example.com/v1/chat/completions"

payload='{
  "model": "acme",
  "messages": [
    {"role": "system", "content": "You must output JSON only."},
    {"role": "user", "content": "Return {\\"ok\\": true}."}
  ],
  "temperature": 0
}'

echo "--- stable"
curl -sS \
  -H "content-type: application/json" \
  -d "$payload" \
  "$BASE_URL" | jq .

echo "--- canary"
curl -sS \
  -H "content-type: application/json" \
  -H "x-canary: true" \
  -d "$payload" \
  "$BASE_URL" | jq .

이 스크립트를 CI에서 주기적으로 돌리면, 카나리 배포 직후 “기본 동작이 깨졌는지”를 빠르게 감지할 수 있습니다.

운영 결론: LLM 카나리는 “트래픽”보다 “지표와 재현성”이 먼저

Seldon Core v2로 LLM을 카나리 배포할 때 성공 확률을 높이는 순서는 다음이었습니다.

  1. 라우팅(가중치/헤더)은 단순하게 시작
  2. readinessProbe로 워밍업을 강제해 “준비된 Pod만” 트래픽을 받게 함
  3. 관측 라벨(model_version)을 심고 stable vs canary 비교 대시보드를 먼저 만듦
  4. 품질 프록시 지표(구조화 출력 성공률 등)를 승격 조건에 포함
  5. 자동 롤백 가드레일로 피해 상한을 고정

이렇게 해두면, 새 모델을 더 자주 배포할 수 있고(속도), 장애가 나도 되돌릴 수 있으며(안전), 무엇이 나빠졌는지 빠르게 분리할 수 있습니다(진단 가능성). 결국 LLM 운영에서 중요한 건 “모델을 잘 고르는 것”만이 아니라, 바꿔도 안전한 배포 메커니즘입니다.