LabHub
배우기 러닝패스 코스

GPU Operator and Time-Slicing

Sharing Configuration — Splitting It Up with Slices, MIG and Quotas

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

한 장을 여럿이 쓰게 만드는 세 가지 방식을 설정과 오브젝트로 다룹니다. 슬라이스 수만큼 자리가 늘어나는 것을 노드 status 로 확인하고, MIG 노드는 자원 이름 자체가 다르다는 것을 스케줄링으로 확인하고, 팀에게 나눠 준 몫을 쿼터로 못 박는 데까지 갑니다.

왜 중요한가

GPU 는 사고 나면 사용률이 하루 평균 한 자리인데 대기열은 늘 차 있습니다. 파드 하나가 카드 한 장을 통째로 잡기 때문입니다. 여기서 "한 장을 여럿이 쓰게 해 달라" 는 요구가 나오고, 답이 셋인데 이름이 비슷해 자주 섞입니다.

가장 중요한 구분은 무엇을 나누는가 입니다. 타임슬라이싱의 replicas 는 GPU 를 조각내라는 뜻이 아니라 같은 장치를 여러 번 등록하라는 뜻입니다. 자리는 늘어나지만 VRAM 은 나뉘지 않은 한 덩어리라, 한 파드가 많이 잡으면 나머지가 메모리 부족을 맞습니다. MPS 는 커널을 동시에 실행시켜 처리량을 올리지만 격리는 여전히 약합니다. MIG 만이 하드웨어에서 나누므로 메모리도 오류도 격리됩니다. 그리고 MIG 노드는 자원 이름 자체가 프로파일 이름으로 바뀌어서, 옛 매니페스트가 그 노드를 영영 못 쓰는 일이 생깁니다.

환경

이 파드에는 GPU 도 장치 플러그인도 없습니다. 대신 kwok 이 띄운 진짜 컨트롤 플레인이 있어서, 노드 status 에 숫자를 적으면 진짜 스케줄러가 그것을 보고 판정합니다. 쿼터도 진짜 apiserver 가 검사합니다. 작업 디렉터리는 /root/gpushare 이고 그 아래 k8s/·bin/·out/ 을 씁니다.

단계

  1. gpu-share·team-a 네임스페이스와 설정 두 벌을 담은 ConfigMap 을 만듭니다.
  2. 노드 라벨로 설정을 고르고 광고량을 각각 적습니다.
  3. lab-node-2 를 MIG mixed 노드로 만듭니다.
  4. 자원 이름만 다른 파드 둘을 띄워 갈리는 것을 봅니다.
  5. team-a 에 쿼터를 걸고 넘치는 요청이 거절되는 것을 out/quota-denied.txt 에 담습니다.
  6. bin/check-request.sh 로 슬라이스 여러 개 요청을 미리 거릅니다.
  7. ConfigMap 에 mps 를 더하고 out/isolation.txt 에 격리 표를 적습니다.
  8. 세 워크로드를 각자 맞는 노드에 놓고 out/placement.txt 에 적습니다.

참고

장치 플러그인 설정을 이름 붙여 두 벌 담는다

gpu-shareteam-a 네임스페이스를 만들고, /root/gpushare/k8s/device-plugin-configs.yamlgpu-share 에 ConfigMap device-plugin-configs 를 만드세요. 키는 defaultshared 두 개이고, shared 에는 sharing.timeSlicing 아래 failRequestsGreaterThanOne: trueresources[0]nvidia.com/gpu · replicas: 4 로 들어갑니다. default 에는 sharing 을 두지 마세요.

장치 플러그인은 설정을 ConfigMap 하나에 여러 벌 담아 두고 노드마다 골라 쓰게 할 수 있습니다. 그래야 같은 클러스터 안에서 어떤 노드는 통째로, 어떤 노드는 나눠 쓸 수 있습니다. failRequestsGreaterThanOne 은 슬라이스를 두 개 이상 달라는 요청을 거부하는 스위치입니다. 슬라이스 두 개를 받아도 같은 장치를 두 번 예약하는 것일 뿐이라 성능도 메모리도 두 배가 되지 않기 때문입니다. ConfigMap 의 값은 여러 줄 문자열이므로 |- 를 씁니다.

노드 라벨로 어느 설정을 쓸지 고른다

lab-node-0nvidia.com/device-plugin.config=default 를, lab-node-1=shared 를 붙이세요. 그다음 두 노드가 광고하는 nvidia.com/gpu 를 status 에 적습니다. 두 노드 모두 물리 GPU 는 2장이라고 보고, lab-node-0 은 2, lab-node-1 은 2에 슬라이스 수를 곱한 값입니다.

장치 플러그인은 노드의 nvidia.com/device-plugin.config 라벨을 보고 ConfigMap 의 어느 키를 쓸지 고릅니다. 그래서 같은 클러스터 안에서 노드마다 다른 나눠 쓰기 정책을 둘 수 있습니다. 광고량은 짐작하지 말고 1단계의 replicas 를 그대로 곱해 넣으세요. capacity 와 allocatable 양쪽에 적어야 합니다. 여기서 늘어나는 것은 자리 수뿐이고 VRAM 은 여전히 한 덩어리입니다.

MIG mixed 노드는 자원 이름 자체가 다르다

lab-node-2nvidia.com/mig.strategy=mixednvidia.com/gpu.product 라벨을 붙이고, status 에 nvidia.com/mig-1g.10gb7 로 광고하세요. 이 노드에는 nvidia.com/gpu 를 적지 마세요.

MIG 는 하드웨어에서 카드를 나눕니다. mixed 전략에서는 장치 플러그인이 프로파일마다 다른 이름으로 자원을 광고하므로, 파드가 요구하는 자원 이름도 nvidia.com/gpu 가 아니라 nvidia.com/mig-1g.10gb 처럼 프로파일 이름이 됩니다. 그래서 이 노드는 nvidia.com/gpu 를 아예 광고하지 않습니다. A100 40GB 를 1g.10gb 로 자르면 조각이 일곱 개 나옵니다.

자원 이름이 다르면 서로의 자리에 못 간다

/root/gpushare/k8s/mig-job.yaml 에 파드 두 개를 적어 gpu-share 에 적용하세요. mig-jobnvidia.com/mig-1g.10gb 를 1개, plain-gpunvidia.com/gpu 를 1개 요구합니다. 둘 다 노드 조건은 주지 마세요.

조건을 주지 않아도 자원 이름만으로 갈립니다. mig-job 은 그 이름을 광고하는 노드로만 갈 수 있고, plain-gpu 는 MIG 노드에는 갈 수 없습니다. 같은 물리 카드를 쓰더라도 스케줄러에게는 전혀 다른 자원입니다. 그래서 MIG 노드를 도입할 때 옛 매니페스트를 그대로 두면 파드가 그 노드를 영영 못 쓰는 일이 생깁니다.

팀에게 나눠 준 몫을 쿼터로 못 박는다

/root/gpushare/k8s/gpu-quota.yamlteam-a 에 ResourceQuota gpu-quota 를 만드세요. requests.nvidia.com/gpu2 로 제한합니다. 그다음 GPU 를 한 장씩 쓰는 파드 두 개를 team-a 에 띄워 한도를 채우고, 한 장을 더 요구하는 /root/gpushare/k8s/team-over.yaml 을 적용해 거절 메시지를 /root/gpushare/out/quota-denied.txt 에 저장하세요.

확장 자원도 네임스페이스 쿼터로 제한할 수 있습니다. 키 이름이 requests.<자원이름> 형태라는 점만 다릅니다. 파드 두 개는 nvidia.com/device-plugin.config: shared 노드셀렉터로 슬라이스 노드에 놓으면 자리가 넉넉합니다. 한도를 채운 뒤의 요청은 스케줄러가 아니라 어드미션 단계에서 막히므로 파드가 아예 만들어지지 않습니다. 메시지는 표준 오류로 나가므로 2>&1 로 받으세요.

슬라이스를 여러 개 달라는 요청을 배포 전에 거른다

/root/gpushare/bin/check-request.sh <파드매니페스트> 를 만드세요. 컨테이너가 nvidia.com/gpu 를 1보다 크게 요구하면 OVER=<컨테이너이름>=<개수> 를 한 줄씩 찍고 종료 코드 1로 끝냅니다. 그렇지 않으면 OK 를 찍고 0으로 끝냅니다. GPU 를 아예 요구하지 않는 파드도 통과여야 합니다.

failRequestsGreaterThanOne 은 장치 플러그인이 런타임에 하는 판정입니다. 같은 판정을 배포 전에 해 두면 사용자가 자원을 오해한 채 계획을 세우는 것을 막을 수 있습니다. 여러 문서가 든 파일도 있으니 yaml.safe_load_all 로 읽고, limitsrequests 양쪽을 보세요. 채점기는 이 스크립트를 세 가지 매니페스트로 실제로 돌려 봅니다.

세 번째 방식을 더하고 격리 표를 만든다

ConfigMap device-plugin-configsmps 키를 더하세요. sharing.mps.resources[0]nvidia.com/gpu · replicas: 4 이고 timeSlicing 은 함께 두지 않습니다. 그리고 /root/gpushare/out/isolation.txt 에 다섯 줄로 격리 표를 적으세요. TIMESLICING_MEMORY_ISOLATION, MPS_MEMORY_ISOLATION, MIG_MEMORY_ISOLATION, TIMESLICING_FAULT_ISOLATION, MIG_FAULT_ISOLATION 입니다.

MPS 는 여러 프로세스의 커널을 하나의 컨텍스트로 모아 진짜로 동시에 실행시킵니다. 프로세스별 메모리 상한을 걸 수도 있지만 그것은 협조적인 제한에 가깝고, 한 프로세스가 죽을 때 다른 프로세스까지 영향을 받을 수 있습니다. 다섯 줄의 값은 yes·no·partial 중 하나입니다. 값이 완전한 격리면 yes, 없으면 no, 상한은 걸 수 있지만 강제 격리가 아니면 partial 입니다.

성격이 다른 세 워크로드를 각자 맞는 노드에 놓는다

/root/gpushare/k8s/placement.yamlgpu-share 에 파드 세 개를 만드세요. prod-inference 는 MIG 노드에서 프로파일 자원을, notebook 은 슬라이스 노드에서 nvidia.com/gpu 를, batch-train 은 나누지 않는 노드에서 nvidia.com/gpu 를 한 개씩 씁니다. 노드는 라벨로 고르세요. 그다음 실제로 어디에 갔는지 /root/gpushare/out/placement.txt<파드이름>=<노드이름> 세 줄로 적습니다.

기준선은 이렇습니다. 격리가 필요하면 MIG, 활용률이 목적이면 타임슬라이싱입니다. 프로덕션 추론은 옆 파드의 메모리 사고에 휘말리면 안 되므로 하드웨어 파티션 쪽으로 보내고, 실험용 노트북은 사용률이 낮으니 슬라이스 노드로 보냅니다. 배치 학습은 카드를 통째로 쓰는 편이 빠르니 나누지 않는 노드로 보냅니다. 노드 조건은 2단계와 3단계에서 붙인 라벨을 그대로 쓰면 됩니다. 파일의 노드 이름은 짐작하지 말고 kubectl get pod -o wide 로 확인해 적으세요.