TT Lab
블로그 시작하기
배우기 러닝패스 코스

쿠버네티스 GPU 스케줄링과 AI 배치

LeaderWorkerSet 으로 멀티 노드 추론 그룹을 다룬다

TT Lab 에서 이어서 보기

이 실습은 진짜 쿠버네티스에서 돕니다

VM 안에 k3s 한 대가 떠 있고, 가짜 GPU 4장과 LeaderWorkerSet v1.0.0 이 올라 있습니다. GPU 4장씩을 광고하는 KWOK 노드 gpu-a, gpu-b 도 미리 만들어 두었습니다(파드는 Running 으로 보이지만 컨테이너는 돌지 않습니다). 처음 뜨는 데 3~4분 걸립니다. 세션이 끝나면 VM 과 파일은 사라집니다.

목표

리더와 워커를 그룹으로 묶어 복제하고, 그룹 단위의 재시작·롤링 업데이트·독점 배치를 직접 일으킨 뒤, 갱 스케줄링이 없을 때 생기는 반쪽 그룹을 확인합니다.

왜 중요한가

여러 노드에 걸친 추론 엔진 하나는 파드 여러 개가 한 몸입니다. 하나가 죽으면 그룹 전체가 쓸모없고, 판을 올릴 때 옛 리더와 새 워커가 섞이면 엔진이 뜨지 않습니다. LWS 는 이 두 사고를 그룹이라는 단위로 막습니다. 그리고 LWS 가 막지 못하는 것(그룹을 함께 배치하기)을 보아 두면 다음 모듈의 갱 스케줄링이 왜 필요한지 분명해집니다.

단계

  1. LeaderWorkerSet llm(replicas 2, size 2, 파드마다 GPU 1장)을 /root/gsb-lws/lws.yaml 로 만들어 파드 넷을 Running 으로 둡니다.
  2. 파드 넷의 그룹·워커 번호를 /root/gsb-lws/names.txt 에 pod= group= worker= 꼴로 적습니다.
  3. llm-1-1 의 LWS_ 환경 변수를 /root/gsb-lws/env.txt 에 담습니다.
  4. llm-1-1 안에서 리더 주소를 조회해 /root/gsb-lws/dns.txt 에 담습니다.
  5. llm-0·llm-1 의 UID 를 /root/gsb-lws/restart.txt 에 적고, llm-0-1 을 실패시켜 그룹 0 만 다시 만들어지는 것을 봅니다.
  6. 템플릿에 VERSION=v2 를 더해 그룹 단위 롤링 업데이트를 끝까지 봅니다.
  7. KWOK 노드 둘에 그룹마다 노드 하나를 차지하는 LWS wide 를 /root/gsb-lws/wide.yaml 로 만듭니다.
  8. 리더만 뜨고 워커는 Pending 인 LWS orphan 을 만들어, 워커의 사유를 /root/gsb-lws/partial.txt 에 담습니다.

참고

그룹 둘, 그룹마다 파드 둘

infer 네임스페이스에 LeaderWorkerSet llm 을 「/root/gsb-lws/lws.yaml」 로 만들어 적용하세요. replicas: 2, size: 2, restartPolicy: RecreateGroupOnPodRestart 이고 리더·워커 모두 GPU 1장을 limits 로 요청합니다. 컨테이너는 /tmp/fail 이 생기면 종료 코드 1로 끝나게 하세요. 파드 넷이 Running 이어야 합니다.

leaderWorkerTemplate 아래에 size, restartPolicy, leaderTemplate, workerTemplate 이 들어갑니다. KWOK 노드에는 테인트가 있어 톨러레이션이 없는 파드는 실제 노드로만 갑니다.

이름이 곧 그룹 번호다

llm 의 파드 넷을 「/root/gsb-lws/names.txt」 에 한 줄에 하나씩 pod=<이름> group=<그룹 번호> worker=<워커 번호> 꼴로 적으세요. 번호는 파드 라벨에서 읽습니다.

라벨 leaderworkerset.sigs.k8s.io/group-index 와 leaderworkerset.sigs.k8s.io/worker-index 입니다. 리더의 워커 번호는 0 입니다.

워커는 리더를 환경 변수로 안다

그룹 1 의 워커 llm-1-1 안의 LWS_ 로 시작하는 환경 변수를 모두 「/root/gsb-lws/env.txt」 에 담으세요.

kubectl exec 로 env 를 보고 LWS_ 로 거릅니다. LWS_LEADER_ADDRESS 가 리더의 이름입니다.

리더의 이름을 실제로 풀어 본다

llm-1-1 안에서 리더의 주소(LWS_LEADER_ADDRESS 뒤에 .svc.cluster.local 을 붙인 이름)를 조회해 결과를 「/root/gsb-lws/dns.txt」 에 담으세요. 리더 파드 llm-1 의 IP 가 나와야 합니다.

busybox 의 nslookup 은 검색 도메인을 붙이지 않아 짧은 이름은 NXDOMAIN 이 납니다. 끝까지 다 적은 이름으로 물으세요.

워커 하나가 죽으면 그 그룹만 다시 만든다

실패시키기 전에 llm-0 과 llm-1 의 UID 를 「/root/gsb-lws/restart.txt」 에 group0_before=, group1_before= 두 줄로 적으세요. 그다음 llm-0-1 안에 /tmp/fail 을 만들어 실패시키고, 그룹 0 만 새로 만들어질 때까지 기다리세요.

컨테이너가 다시 시작되면 RecreateGroupOnPodRestart 정책이 그룹 0 의 리더와 워커를 함께 지우고 새로 만듭니다. 그룹 1 은 그대로입니다. 새 리더의 UID 는 이전과 다릅니다.

그룹 단위로 굴려 바꾼다

llm 의 리더·워커 템플릿에 환경 변수 VERSION=v2 를 더해 다시 적용하세요(파일 /root/gsb-lws/lws.yaml 을 고쳐도 됩니다). 그룹이 하나씩 새로 만들어져 두 그룹 모두 새 템플릿이 되고 Available 이어야 합니다.

LWS 는 그룹을 단위로 굴립니다. 옛 판 리더와 새 판 워커가 한 그룹에 섞이지 않습니다. status.updatedReplicas 가 replicas 와 같아질 때까지 기다리세요.

그룹마다 노드 하나를 독차지한다

KWOK 노드 gpu-a, gpu-b 위에 LeaderWorkerSet wide(replicas 2, size 2, 파드마다 GPU 1장)를 「/root/gsb-lws/wide.yaml」 로 만드세요. 주석 leaderworkerset.sigs.k8s.io/exclusive-topology: kubernetes.io/hostname 으로 그룹 하나가 노드 하나를 차지하게 하고, KWOK 테인트 톨러레이션과 type: kwok 노드 선택을 줍니다.

주석은 LeaderWorkerSet 의 metadata 에 답니다. 그러면 같은 그룹은 한 노드에 모이고 다른 그룹은 다른 노드로 갑니다. KWOK 위의 파드는 Running 으로 보이지만 컨테이너는 돌지 않습니다.

반쪽 그룹을 일부러 만든다

LeaderWorkerSet orphan(replicas 1, size 2, 리더는 GPU 없음, 워커는 GPU 1장, KWOK 톨러레이션 없음)을 만드세요. 실제 노드의 GPU 4장은 llm 이 다 쓰고 있어 리더만 Running 이고 워커는 Pending 입니다. 워커 파드에 스케줄러가 남긴 사유를 「/root/gsb-lws/partial.txt」 에 담으세요.

리더가 GPU 를 요청하지 않으니 리더는 자리를 찾습니다. 그룹을 함께 배치하는 장치(갱 스케줄링)가 없으면 이런 반쪽 그룹이 생깁니다.