본문 바로가기
AI

쿠버네티스에서 GPU 한 장을 여러 파드가 나눠 쓰기: Time-Slicing vs MIG

냉장고를사다줘·2026년 8월 17일·조회 1

GPU 노드를 처음 붙이고 나면 곧 이런 상황을 만난다. A100 한 장이 노드에 꽂혀 있는데, 추론 파드 하나가 그걸 통째로 물고 있고 나머지 파드는 Pending으로 줄을 선다. GPU 메모리 사용률을 보면 3GB밖에 안 쓰는데도 그렇다. 쿠버네티스 기본 스케줄러에게 GPU는 정수 단위 자원이라 그렇다. 한 장은 한 파드에게 통째로 나간다.

결론부터 말하면, GPU 한 장을 여러 파드가 나눠 쓰는 방법은 크게 두 가지다. Time-Slicing은 GPU를 시간으로 쪼개 여러 파드가 번갈아 쓰게 하고, MIG(Multi-Instance GPU)는 하드웨어 수준에서 GPU를 물리적으로 분할한다. Time-Slicing은 격리가 없어 개발/테스트나 부하가 낮은 추론에 맞고, MIG는 메모리와 연산을 실제로 나눠 격리가 필요한 멀티테넌트 운영에 맞는다. 아래에서 각각을 NVIDIA GPU Operator로 설정하는 방법과 격리 차이, 선택 기준을 차례로 살펴본다.

1. 두 방식이 무엇인지부터

Time-Slicing은 GPU 하나를 여러 개의 가상 복제본(replica)으로 광고하는 방식이다. 물리 GPU는 그대로 하나지만, 디바이스 플러그인이 nvidia.com/gpu: 4처럼 여러 개인 척 노출한다. 실제 실행은 GPU가 컨텍스트를 시분할로 번갈아 처리한다. 즉 파드들이 GPU 시간을 나눠 쓰는 것이지, 메모리나 연산 코어를 나눠 갖는 게 아니다.

MIG는 Ampere 세대 이상(A100, A30, H100 등)에서 지원하는 하드웨어 파티셔닝이다. GPU를 GPU Instance(GI)라는 격리된 조각으로 쪼갠다. 각 조각은 전용 SM(연산 유닛), 전용 L2 캐시 슬라이스, 전용 메모리를 갖는다. 한 조각에서 무슨 일이 나든 다른 조각은 영향받지 않는다.

핵심 차이는 격리다. NVIDIA 문서도 Time-Slicing에 대해 "복제본 사이에 메모리나 결함 격리가 없다(no memory or fault-isolation between replicas)"고 못 박는다. 파드 하나가 GPU 메모리를 다 먹으면 같은 GPU를 쓰는 다른 파드가 OOM으로 죽는다. MIG는 그런 일이 없다.

2. 사전 준비: GPU Operator

두 방식 모두 NVIDIA GPU Operator를 깐 상태를 전제로 한다. Operator가 드라이버, 컨테이너 툴킷, 디바이스 플러그인, MIG Manager를 한 번에 관리해준다. Helm으로 설치한다.

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm install --wait --generate-name \
    -n gpu-operator --create-namespace \
    nvidia/gpu-operator

설치 후 노드 라벨을 확인한다. GPU가 잡히면 nvidia.com/gpu.count, nvidia.com/gpu.product 같은 라벨이 붙는다.

kubectl get nodes -o json | jq '.items[].metadata.labels | with_entries(select(.key|startswith("nvidia.com")))'

여기서 한 번 걸리기 쉽다. GPU Operator 버전과 노드 커널 헤더가 안 맞으면 드라이버 데몬셋이 CrashLoopBackOff로 돈다. kubectl logs -n gpu-operator -l app=nvidia-driver-daemonset로 로그부터 확인한다. 클라우드 관리형 노드(EKS GPU AMI 등)를 쓰면 드라이버가 이미 깔려 있으니 --set driver.enabled=false로 Operator의 드라이버 설치를 끄는 편이 깔끔하다.

3. Time-Slicing 설정

Time-Slicing은 ConfigMap 하나와 ClusterPolicy 패치로 끝난다. 먼저 GPU 하나를 4개로 쪼개는 설정을 만든다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: false
        failRequestsGreaterThanOne: false
        resources:
          - name: nvidia.com/gpu
            replicas: 4

필드 하나씩 보면 이렇다.

  • replicas: 4: 물리 GPU 하나를 4개의 가상 GPU로 광고한다. 이 숫자는 메모리를 4등분한다는 뜻이 아니라, 스케줄링 슬롯을 4개로 늘린다는 뜻이다.
  • renameByDefault: true면 자원 이름을 nvidia.com/gpu 대신 nvidia.com/gpu.shared로 광고한다. 공유 GPU와 전용 GPU를 자원 이름으로 구분하고 싶을 때 켠다.
  • failRequestsGreaterThanOne: true면 파드가 공유 GPU를 2개 이상 요청하는 걸 막는다. 시분할 자원을 여러 개 요청하는 건 의미가 없으니 켜두는 걸 권한다.

적용하고 ClusterPolicy에 기본 설정으로 물린다.

kubectl create -n gpu-operator -f time-slicing-config.yaml

kubectl patch clusterpolicies.nvidia.com/cluster-policy \
    -n gpu-operator --type merge \
    -p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config", "default": "any"}}}}'

여기서 두 번째 함정. Operator는 이 ConfigMap 변경을 자동 감지하지 않는다. 값을 바꿔도 반영이 안 된다. 디바이스 플러그인 데몬셋을 직접 재시작해야 한다.

kubectl rollout restart -n gpu-operator daemonset/nvidia-device-plugin-daemonset

잠시 뒤 노드의 GPU 개수를 다시 보면 4배로 늘어 있다.

kubectl get node <node-name> -o jsonpath='{.status.capacity.nvidia\.com/gpu}'
4

이제 파드 4개가 각각 nvidia.com/gpu: 1을 요청해도 GPU 한 장에 다 올라간다. 다만 이들은 GPU 메모리를 공유하므로, 큰 모델 하나를 로드하면 나머지 파드가 메모리를 못 잡을 수 있다는 걸 잊지 마라. 시분할은 시간만 나눌 뿐 메모리를 지켜주지 않는다.

4. MIG 설정

MIG는 전략(strategy) 개념부터 잡아야 한다. GPU Operator는 두 가지를 지원한다.

  • single: 노드의 모든 GPU가 같은 MIG 프로필로 동일하게 쪼개진다. 자원 이름은 nvidia.com/gpu 그대로 노출된다. 노드 전체를 한 종류 조각으로 쓸 때 단순하다.
  • mixed: 한 노드 안에서 GPU마다 다른 프로필을 섞거나, 일부만 MIG를 켤 수 있다. 자원 이름이 nvidia.com/mig-1g.5gb처럼 프로필별로 갈라져 노출된다.

설치 시점에 전략을 정한다. 이미 Operator가 깔려 있으면 재설치하거나 값을 패치한다.

helm install --wait --generate-name \
    -n gpu-operator --create-namespace \
    nvidia/gpu-operator \
    --set mig.strategy=mixed

실제 파티셔닝은 노드 라벨로 지시한다. MIG Manager가 이 라벨을 보고 GPU를 리셋한 뒤 지정한 프로필로 재구성한다.

kubectl label nodes <node-name> nvidia.com/mig.config=all-1g.5gb --overwrite

프로필 이름의 규칙은 {GI개수}g.{메모리}gb다. A100 40GB를 예로 들면 대표 프로필은 이렇다. 사용 가능한 프로필과 최대 개수는 GPU 모델(A100 40GB/80GB, H100 등)에 따라 다르다.

  • 1g.5gb: 조각 하나가 SM 1/7, 메모리 5GB. 최대 7개까지.
  • 2g.10gb: 메모리 10GB. 최대 3개.
  • 3g.20gb: 메모리 20GB. 최대 2개.
  • 7g.40gb: GPU 전체. 1개(사실상 MIG 비분할과 동일).

라벨을 걸면 MIG Manager 파드가 작업을 시작한다. 진행 상태는 노드 라벨로 추적한다.

kubectl get node <node-name> -o jsonpath='{.metadata.labels.nvidia\.com/mig\.config\.state}'
success

pending이나 rebooting에서 오래 멈춰 있으면 MIG Manager 로그를 본다. 흔한 원인은 GPU에 아직 프로세스가 붙어 있어 MIG 모드 전환이 막히는 경우다. MIG 모드를 켜려면 GPU를 완전히 비워야 한다. 실행 중인 GPU 파드가 있으면 먼저 내려야 한다.

kubectl logs -n gpu-operator -l app=nvidia-mig-manager -f

구성이 끝나면 노드 안에서 직접 확인해볼 수 있다. GPU Operator 없이 수동으로 MIG를 다룰 때 쓰는 명령이기도 하다.

# MIG 모드 켜기 (수동)
nvidia-smi -i 0 -mig 1

# 사용 가능한 GPU Instance 프로필 목록
nvidia-smi mig -lgip

# 현재 만들어진 인스턴스 확인
nvidia-smi -L
GPU 0: NVIDIA A100-SXM4-40GB (UUID: GPU-...)
  MIG 1g.5gb  Device  0: (UUID: MIG-...)
  MIG 1g.5gb  Device  1: (UUID: MIG-...)

mixed 전략에서는 파드가 프로필 이름으로 자원을 요청한다.

apiVersion: v1
kind: Pod
metadata:
  name: mig-inference
spec:
  containers:
    - name: app
      image: nvcr.io/nvidia/pytorch:24.05-py3
      command: ["sleep", "infinity"]
      resources:
        limits:
          nvidia.com/mig-1g.5gb: 1

5. 격리 차이를 표로 정리

같은 "GPU 공유"라도 격리 수준이 근본적으로 다르다.

  • 메모리 격리: Time-Slicing은 없음(전 파드가 GPU 메모리 공유). MIG는 있음(조각마다 전용 메모리).
  • 연산 격리: Time-Slicing은 없음(시간으로 번갈아 씀, 성능 간섭 발생). MIG는 있음(조각마다 전용 SM).
  • 결함 격리: Time-Slicing은 없음(한 파드가 GPU를 죽이면 같이 죽음). MIG는 있음(조각 단위 격리).
  • 지원 GPU: Time-Slicing은 사실상 모든 NVIDIA GPU. MIG는 Ampere 이상(A100, A30, H100 등).
  • 세분화 단위: Time-Slicing은 replicas 숫자만큼 임의로. MIG는 하드웨어가 정한 프로필 범위 내.
  • 동적 변경: Time-Slicing은 ConfigMap 수정 후 플러그인 재시작. MIG는 GPU 리셋이 필요해 더 무겁다.

6. 워크로드별 선택 기준

고르는 기준은 단순하다. 격리가 필요하냐가 첫 갈림길이다.

Time-Slicing을 쓸 상황: 개발/테스트 환경에서 여러 사람이 GPU 노드를 나눠 쓸 때, 부하가 낮고 간헐적인 추론 파드를 여러 개 올릴 때, MIG를 지원하지 않는 구형 GPU(T4, V100 등)일 때. 신뢰하는 내부 팀이 같은 GPU를 쓰는 상황이면 격리 없이도 무방하다. 설정이 가볍고 GPU 종류를 안 가린다는 게 장점이다.

MIG를 쓸 상황: 여러 테넌트가 같은 GPU를 나눠 쓰는데 서로 간섭하면 안 될 때, SLA가 걸린 추론 서비스라 성능이 예측 가능해야 할 때, 파드마다 메모리 상한을 하드웨어로 보장해야 할 때. A100/H100처럼 크고 비싼 GPU를 여러 작은 워크로드로 잘게 쓸 때 특히 유리하다. "한 파드의 폭주가 옆 파드를 죽이면 안 된다"가 요구사항이면 답은 MIG다.

둘을 겹쳐 쓸 수도 있다. MIG로 GPU를 조각낸 다음 각 조각에 다시 Time-Slicing을 걸어 격리된 조각 안에서 시분할 오버서브스크립션을 하는 구성이다. GPU Operator의 migStrategytimeSlicing을 함께 설정하면 된다. 다만 운영 복잡도가 확 올라가니, 먼저 한 방식으로 충분한지부터 따져보는 걸 권한다.

정리하면, 격리가 필요 없고 GPU를 최대한 우겨넣는 게 목적이면 Time-Slicing, 격리와 성능 예측이 목적이면 MIG다. 그리고 어느 쪽이든 GPU Operator를 통하면 노드에 SSH로 들어가 nvidia-smi를 두들기는 수작업 없이 선언적으로 관리된다는 점이 크다.

자주 묻는 질문

Time-Slicing의 replicas 숫자를 늘리면 GPU 메모리도 그만큼 나눠지나?

아니다. replicas는 스케줄링 슬롯 수만 늘린다. GPU 메모리는 여전히 전 파드가 공유하며, 하드웨어 상한이 파드별로 걸리지 않는다. 큰 모델을 로드하는 파드가 메모리를 다 쓰면 같은 GPU의 다른 파드가 OOM으로 죽을 수 있다. 파드별 메모리 격리가 필요하면 MIG를 써야 한다.

MIG는 아무 NVIDIA GPU에서나 되나?

아니다. MIG는 Ampere 세대 이상에서만 지원한다. A100, A30, H100 등이 대상이다. T4나 V100 같은 이전 세대는 MIG를 지원하지 않으므로, 이 GPU에서 공유가 필요하면 Time-Slicing이 유일한 선택이다.

Time-Slicing ConfigMap을 바꿨는데 반영이 안 된다.

GPU Operator는 time-slicing ConfigMap의 변경을 자동 감지하지 않는다. 값을 수정한 뒤 nvidia-device-plugin-daemonset을 직접 재시작해야 한다. kubectl rollout restart -n gpu-operator daemonset/nvidia-device-plugin-daemonset 로 다시 굴리고 노드 capacity를 확인하면 된다.

mig.strategy의 single과 mixed는 어떻게 고르나?

노드의 모든 GPU를 같은 프로필로 동일하게 쪼갤 거면 single이 단순하다. 자원 이름이 nvidia.com/gpu 그대로 유지된다. 노드 안에서 GPU마다 다른 프로필을 섞거나 일부만 MIG를 켜려면 mixed를 쓴다. mixed에서는 자원 이름이 nvidia.com/mig-1g.5gb처럼 프로필별로 갈라진다.

MIG와 Time-Slicing을 같이 쓸 수 있나?

가능하다. MIG로 GPU를 격리된 조각으로 나눈 뒤, 각 조각에 다시 Time-Slicing을 걸어 조각 안에서 오버서브스크립션할 수 있다. GPU Operator에서 migStrategy와 timeSlicing을 함께 설정하면 된다. 다만 운영 복잡도가 올라가므로 한 방식으로 충분한지 먼저 검토하는 것이 좋다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.