GPU 자원 선점 사용
Kueue의 WorkloadPriorityClass를 사용하여 GPU 워크로드의 우선순위를 지정하고, 우선순위가 높은 워크로드가 낮은 워크로드의 자원을 선점하도록 설정할 수 있습니다.
선점 개요
GPU 자원의 경합과 선점은 Kueue에서 관리합니다. 하나의 테넌트 네임스페이스는 하나의 ClusterQueue에 대응하며, 선점은 같은 ClusterQueue 안에서만 발생합니다. 다른 테넌트의 워크로드를 선점하거나 다른 테넌트의 워크로드에 의해 선점되지 않습니다.
| 항목 | 설명 |
|---|---|
| 선점 범위 | 같은 테넌트의 ClusterQueue |
| 선점 대상 | 새 워크로드보다 WorkloadPriorityClass 값이 낮은 실행 중 워크로드 |
| 테넌트 간 선점 | 지원하지 않음 |
| LocalQueue 간 입장 순서 | GPU 사용 이력 기반 Fair Sharing 및 LocalQueue weight에 따라 결정 |
테넌트의 ClusterQueue에는 할당된 자원에 맞는 quota와 다음 정책이 적용됩니다.
| 설정 | 값 | 설명 |
|---|---|---|
preemption.withinClusterQueue | LowerPriority | 같은 ClusterQueue 안에서 우선순위가 낮은 워크로드만 선점 |
preemption.reclaimWithinCohort | Never | 다른 테넌트의 워크로드를 선점하지 않음 |
admissionScope.admissionMode | UsageBasedAdmissionFairSharing | LocalQueue 간 GPU 사용 이력이 적은 Queue를 우선 입장 |
queueingStrategy | BestEffortFIFO | 앞선 워크로드가 자원 부족으로 대기 중이어도 실행 가능한 후속 워크로드는 입장 가능 |
선점 조건
다음 조건을 모두 충족하면 Kueue가 우선순위가 낮은 실행 중 워크로드를 내보내고 새 워크로드를 입장시킵니다.
- 새 워크로드가 테넌트 ClusterQueue의 quota 안에 들어가지 못하는 경우
- 같은 ClusterQueue 안에 새 워크로드보다 WorkloadPriorityClass 값이 낮은 실행 중 워크로드가 있는 경우
- 우선순위가 낮은 워크로드를 내보내면 새 워크로드가 quota 안에 들어가는 경우
우선순위가 같거나 높은 워크로드는 선점되지 않습니다. 선점할 수 있는 워크로드가 없으면 새 워크로드는 자원을 사용할 수 있을 때까지 Queue에서 대기합니다.
우선순위 클래스
다음 WorkloadPriorityClass를 사용할 수 있습니다.
| 이름 | 값 | 용도 |
|---|---|---|
low | 100 | 선점되어도 되는 낮은 우선순위의 작업 |
normal | 500 | 일반 작업에 사용하는 기본 우선순위 |
high | 1000 | 높은 우선순위가 필요한 작업 |
critical | 1500 | 즉시 실행해야 하는 긴급 작업 |
high와 critical은 운영상 필요한 경우에만 사용하세요. 우선순위를 높여도 테넌트에 할당된 quota를 초과하여 사용할 수 없으며, 같은 테넌트에서 실행 중인 낮은 우선순위의 워크로드가 선점될 수 있습니다.
워크로드에 우선순위 지정
워크로드의 kueue.x-k8s.io/priority-class 라벨에 사용할 우선순위 클래스 이름을 입력합니다.
TrainJob은 metadata.labels에 라벨을 추가합니다.
apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
metadata:
name: finetune-01
namespace: <사용자 namespace>
labels:
kueue.x-k8s.io/queue-name: training
kueue.x-k8s.io/priority-class: high
spec:
runtimeRef:
apiGroup: trainer.kubeflow.org
kind: ClusterTrainingRuntime
name: torch-distributed
trainer:
numNodes: 2
numProcPerNode: 8
resourcesPerNode:
requests:
nvidia.com/gpu: "8"
limits:
nvidia.com/gpu: "8"
Deployment, LeaderWorkerSet, DynamoGraphDeployment와 같이 Pod 단위로 Kueue가 관리하는 워크로드는 Pod 템플릿의 metadata.labels에 라벨을 추가합니다.
kueue.x-k8s.io/queue-name에는 사용자 네임스페이스에 생성한 LocalQueue 이름을 입력합니다. 워크로드를 생성할 때 Queue와 우선순위 라벨을 함께 지정하는 것을 권장합니다.
테넌트 전용 우선순위 클래스 생성
기본 우선순위 사이에 별도의 등급이 필요하면 사용자 네임스페이스에 CustomWorkloadPriorityClass를 생성합니다. 플랫폼에서 같은 이름의 WorkloadPriorityClass를 생성합니다.
이름은 <사용자 namespace>-<우선순위 이름> 형식으로 입력합니다. 생성한 이름은 워크로드의 kueue.x-k8s.io/priority-class 라벨에 동일하게 입력합니다.
apiVersion: kakaocloud.io/v1alpha1
kind: CustomWorkloadPriorityClass
metadata:
name: <사용자 namespace>-urgent
namespace: <사용자 namespace>
spec:
value: 1200
description: 분기 마감 전 재학습
다음 명령어를 실행하여 생성 상태를 확인합니다.
kubectl get cwpc -n <사용자 namespace>
READY가 True이면 사용할 수 있습니다. 같은 이름의 WorkloadPriorityClass가 이미 있으면 READY가 False, reason이 NameConflict로 표시됩니다. 이 경우 다른 이름으로 다시 생성합니다.
metadata:
labels:
kueue.x-k8s.io/queue-name: training
kueue.x-k8s.io/priority-class: <사용자 namespace>-urgent
CustomWorkloadPriorityClass를 삭제하면 연결된 WorkloadPriorityClass도 함께 삭제됩니다. 삭제된 클래스를 참조하는 새 워크로드는 WorkloadPriorityClassNotFound 경고와 함께 입장하지 못하므로, 삭제하기 전에 해당 클래스를 참조하는 워크로드가 없는지 확인하세요.
LocalQueue 간 입장 순서 조정
같은 테넌트 안에서 팀별로 LocalQueue를 생성하여 워크로드의 입장 순서를 조정할 수 있습니다. 모든 LocalQueue는 테넌트의 ClusterQueue를 사용해야 하므로 spec.clusterQueue에는 사용자 네임스페이스와 같은 값을 입력합니다.
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: training
namespace: <사용자 namespace>
spec:
clusterQueue: <사용자 namespace>
fairSharing:
weight: "2"
fairSharing.weight 값이 클수록 GPU 사용 이력이 같은 조건에서 먼저 입장합니다. 이 값은 LocalQueue의 입장 순서에만 영향을 주며, 워크로드의 선점 여부는 WorkloadPriorityClass 값으로 결정됩니다.
LocalQueue를 생성하고 워크로드에 적용하는 방법은 Queue와 Scheduler 사용을 참고하세요.
선점된 워크로드 확인
다음 명령어를 실행하여 Kueue Workload의 상태를 확인합니다.
kubectl get workloads -n <사용자 namespace>
kubectl describe workload -n <사용자 namespace> <Workload 이름>
선점된 Workload에는 다음 조건이 표시됩니다.
Evicted가True,reason이PreemptedPreempted조건의reason이InClusterQueuemessage에 선점한 워크로드의 UID와 JobUID 표시
message에 표시된 JobUID를 사용하여 선점한 워크로드를 확인할 수 있습니다.
kubectl get workloads -n <사용자 namespace> -l kueue.x-k8s.io/job-uid=<JobUID>
선점된 TrainJob은 Suspend 상태로 변경되며, Queue에서 다시 자원을 기다립니다.
주의 사항
- TrainJob에
scheduling.volcano.sh/queue-nameannotation 또는kai.scheduler/queue라벨을 추가하지 마세요. TrainJob의 자원 통제와 선점은 Kueue가 담당하며, 해당 값을 추가하면 생성이 거부됩니다. - WorkloadPriorityClass는 Pod의 PriorityClass와 별개입니다. 워크로드의 선점 순서는 WorkloadPriorityClass 값으로 결정됩니다.
- 우선순위를 높여도 테넌트에 할당된 quota를 초과하여 사용할 수 없습니다. 선점은 같은 테넌트의 quota 안에서 워크로드의 실행 순서를 조정합니다.
Gang Scheduling
Gang Scheduling 개요
분산 학습이나 멀티 노드 서빙은 Pod 여러 개가 동시에 떠야 동작합니다. 일부만 먼저 뜨면 나머지를 기다리며 GPU를 점유하므로, 필요한 Pod 전부를 한 번에 배치할 수 있을 때만 시작하는 것이 Gang Scheduling 입니다.
이 클러스터에서 역할은 다음과 같이 나뉩니다.
| 단계 | 담당 | 하는 일 |
|---|---|---|
| admission | Kueue | 테넌트 quota 안에 들어가는지 판단. Job, TrainJob은 입장 전까지 suspend 되어 Pod가 생성되지 않고, Deployment, LeaderWorkerSet, DynamoGraphDeployment 는 Pod가 생성되지만 scheduling gate에 묶여 배치되지 않음 |
| placement | Volcano 또는 KAI Scheduelr | Kueue 를 통과한 워크로드s의 Pod 전부를 한 번에 노드에 배치. 자리가 모자라면 전부 대기. |
TrainJob, LLMInferenceService, DynamoGraphDeployment 를 쓰면 gang 여부와 스케줄러는 플랫폼이 정해 둔 설정을 따르므로 고객이 PodGroup 을 만들 필요가 없습니다.
Kubernetes Job, JobSet, Deployment, Pod 를 직접 쓸 때는 플랫폼이 gang 을 만들어 주지 않으므로 고객이 gang 그룹을 직접 선언해야 합니다.
방법은 아래 "직접 Gang 설정 (Job, JobSet, Deployment, Pod)" 절에 있습니다.
| 워크로드 | 스케줄러 | Gang 단위 | 고객이 지정하는 것 |
|---|---|---|---|
| Trainjob | Runtime 에 따라 KAI 또는 Volcano | TrainJob의 노드 Pod 전부 | runtimeRef 로 Runtime 선택 |
| LLMInferenceService (worker 있음) | Volcano | LWS 그룹(leader + worker) 하나 prefill Deployment 는 Gang 밖 | schedulerName: volcano-scheduler |
| DynamoGraphDeployment | KAI (Grove PodGang) | DGD 전체. 각 컴포넌트의 minAvailable 만큼이 한 Gang 이며, 멀티노드 worker 는 leader 1 + worker(nodeCount - 1)개 Pod가 포함 | schedulerName: kai-scheduler, kai.scheduler/enabled 라벨 |
| Job, JobSet, Deployment, Pod(직접 설정) | Volcano 또는 KAI 중 선택 | 직접 만든 PodGroup 또는 Job/JobSet의 batch-min-member | PodGroup 또는 gang 어노테이션을 직접 작성 아래 "직접 Gang 설정" 절 참고 |
TrainJob
TrainJob 은 spec.runtimeRef 로 고르는 ClusterTrainingRuntime 이 스케줄러와 gang 정책을 결정합니다.
플랫폼이 제공하는 Runtime 은 다음과 같습니다.
| Runtime 이름 | 스케줄러 | 배치 정책 |
|---|---|---|
| torch-distributed-kai-scheduler | KAI | 같은 스위치에 모아 배치, 자리가 없으면 스위치에 걸쳐서라도 배치 |
| torch-distributed-volcano | Volcano | 네트워크 토폴로지 soft 모드, 같은 스위치에 모으려 하되 안되면 분산 배치 |
| torch-distributed-volcano-hard | Volcano | 네트워크 토폴로지 hard 모드, highestTierAllowed: 1. 이 클러스터의 HyperNode 는 tier 1 하나이고, 같은 스위치 안에 전부 들어갈 때만 배치하고 아니면 계속 대기 |
세 Runtime 모두 numNodes 개 Pod 를 하나의 PodGroup 으로 묶어 전부 배치 가능할 때만 시작합니다. 일부만 먼저 실행되는 일은 없습니다.
apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
metadata:
name: pretrain-01
namespace: kbm-g-np-example
labels:
kueue.x-k8s.io/queue-name: training
kueue.x-k8s.io/priority-class: normal
spec:
runtimeRef:
apiGroup: trainer.kubeflow.org
kind: ClusterTrainingRuntime
name: torch-distributed-kai-scheduler
trainer:
numNodes: 4
numProcPerNode: 8
resourcesPerNode:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
LLMInferenceService (멀티노드 서빙)
spec.worker 가 있는 LLMInferenceService 는 LeaderWorkerSet 으로 배포되고, leader 와 worker 가 하나의 gang 으로 묶입니다. LWS 컨트롤러가 그룹마다 Volcano PodGroup 을 만들며, 그룹 크기(Pod 수)는 parallelism.data / parallelism.dataLocal 로 결정됩니다. parallelism.data 는 데이터 병렬(DP) rank 수, dataLocal 은 Pod 하나가 맡는 DP rank 수이고, parallelism.tensor 는 DP rank 하나 안에서 쓰는 GPU 수(TP)입니다. TP 는 Pod 안에서만 이루어지고 Pod 사이를 넘지 않습니다.
prefill 도 같은 규칙입니다. spec.prefill.worker 를 주면 prefill 용 LeaderWorkerSet 이 따로 만들어져 자기 그룹 안에서 gang 이 되고, spec.prefill.worker 가 없으면 Deployment 로 만들어져 gang 대상이 아닙니다. 어느 경우든 prefill 과 decode 는 서로 다른 LWS 또는 Deployment 라서 둘을 묶는 gang 은 없습니다.
Pod 가 Volcano 로 가도록 spec.template, spec.worker, spec.prefill.template(멀티노드면 spec.prefill.worker 도) 각각에 schedulerName: volcano-scheduler 를 적습니다.
apiVersion: serving.kserve.io/v1alpha2
kind: LLMInferenceService
metadata:
name: glm-5-3
namespace: kbm-g-np-example
labels:
kueue.x-k8s.io/queue-name: serving
kueue.x-k8s.io/priority-class: high
spec:
replicas: 1
parallelism:
tensor: 8
data: 2
dataLocal: 1
template:
schedulerName: volcano-scheduler
worker:
schedulerName: volcano-scheduler
prefill:
replicas: 1
parallelism:
tensor: 8
template:
schedulerName: volcano-scheduler
위 예제에서 decode 는 DP rank 2개(data: 2)이고 Pod 하나가 DP rank 1개(dataLocal: 1)를 맡으므로 LWS 그룹은 Pod 2개(leader 1 + worker 1)입니다. 각 Pod 는 TP 8 로 GPU 8장을 쓰므로 decode 전체 GPU 는 16장입니다. 이 Pod 2개가 Volcano PodGroup 하나(minMember 2)로 묶여 함께 스케줄됩니다. 이 예제의 prefill 은 spec.prefill.worker 가 없어 Deployment Pod 1개(TP 8, GPU 8장)로 만들어지고 gang 대상이 아닙니다. 따라서 이 예제에서 gang 은 decode 그룹 안에서만 성립하고, prefill 과 decode 가 같은 시점에 자리 잡는 것은 보장되지 않습니다.
spec.worker 는 KServe 의 decode worker 템플릿 위에 strategic merge 로 겹쳐집니다. 템플릿의 worker 컨테이너에는 probe 가 없으므로, 예를 들어 startupProbe.failureThreshold 만 적으면 handler(httpGet 등) 없는 probe 가 만들어져 API 서버가 worker Pod 생성을 거부합니다. worker 가 없으면 LWS 그룹(minMember 2)이 채워지지 않아 leader 도 Pending 에 남습니다. 템플릿에 없는 객체를 덮어쓸 때는 필드 하나가 아니라 객체 전체를 적어야 합니다.
DynamoGraphDeployment (멀티노드 서빙)
Grove 가 DGD 의 모든 컴포넌트(Frontend 포함)를 PodGang 하나로 묶고 KAI 가 배치합니다. 각 컴포넌트의 minAvailable 만큼이 하나의 gang 으로 묶여 Frontend, prefill, decode 가 같이 자리를 잡아야 스케줄되며, multinode.nodeCount 가 없는 단일 노드 컴포넌트도 예외가 아닙니다. worker 에 multinode.nodeCount 를 주면 그 worker 는 leader 1 + worker(nodeCount - 1) 개 Pod 로 전개되어 같은 gang 에 들어갑니다. Pod 템플릿에 kai.scheduler/enabled: "true" 라벨과 schedulerName: kai-scheduler 를 적습니다.
apiVersion: nvidia.com/v1beta1
kind: DynamoGraphDeployment
metadata:
name: glm-5-3-nodecount4
namespace: kbm-g-np-example
spec:
backendFramework: vllm
components:
- name: VllmWorker
type: worker
replicas: 1
multinode:
nodeCount: 4
podTemplate:
metadata:
labels:
kai.scheduler/enabled: "true"
kueue.x-k8s.io/queue-name: serving
kueue.x-k8s.io/priority-class: high
spec:
schedulerName: kai-scheduler
nodeCount 는 모델 한 벌이 노드 한 대에 들어가지 않을 때만 씁니다. 한 노드에 들어가는 모델은 replicas 로 복제하는 것이 단순하고 장애에 강합니다. Pod 가 서로 다른 노드에 놓이는 것은 Pod 당 GPU 요청량(8장)에 의해 결정되며, nodeCount 자체가 노드 분산을 강제하지는 않습니다.
멀티노드 worker 의 resources, image, args 를 바꾸면 Dynamo operator 는 롤링 업데이트를 직접 관리하지 않습니다. RollingUpdateNotSupported 경고를 내고 PodCliqueSet 템플릿만 갱신하며, 실제 교체는 Grove 의 기본 전략 RollingRecreate 가 PCSG replica 단위로 삭제 후 재생성합니다. replica 가 1개인 멀티노드 worker 는 이 과정에서 전체가 내려가므로 무중단이 아니고, 교체가 지연되는 동안 gang 이 채워지지 않습니다. 운영에서는 DGD 를 삭제한 뒤 다시 만드는 쪽이 결과를 예측하기 쉽습니다.
직접 Gang 설정 (Job, JobSet, Deployment, Pod)
TrainJob, LLMInferenceService, DynamoGraphDeployment 를 쓰지 않고 Kubernetes Job, JobSet, Deployment, 단일 Pod 로 직접 워크로드를 만들 때도 gang scheduling 을 쓸 수 있습니다. 이때는 gang 그룹을 고객이 직접 선언합니다. Volcano 와 KAI 중 하나를 고르고, 한 워크로드 안에서 섞지 않습니다.
어느 쪽이든 Kueue 입장은 그대로 적용됩니다. Job, JobSet, Deployment, Pod 모두 kueue.x-k8s.io/queue-name 라벨이 있어야 하고, 라벨이 없으면 Pod 가 생성되지 않거나 스케줄링 게이트에 걸려 시작하지 않습니다.
Volcano 로 직접 설정
PodGroup 을 먼저 만들고, 같은 gang 에 속할 Pod 템플릿에 scheduling.k8s.io/group-name 어노테이션으로 PodGroup 이름을 적습니다. schedulerName 은 volcano-scheduler 입니다. spec.queue 는 적지 않습니다. 이 클러스터는 Volcano 큐를 쓰지 않습니다.
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: allreduce-test
namespace: kbm-g-np-example
spec:
minMember: 4
---
apiVersion: batch/v1
kind: Job
metadata:
name: allreduce-test
namespace: kbm-g-np-example
labels:
kueue.x-k8s.io/queue-name: training
kueue.x-k8s.io/priority-class: normal
spec:
parallelism: 4
completions: 4
completionMode: Indexed
template:
metadata:
annotations:
scheduling.k8s.io/group-name: allreduce-test
spec:
schedulerName: volcano-scheduler
restartPolicy: Never
containers:
- name: main
image: pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime
command: ["sleep", "600"]
resources:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
minMember 는 동시에 배치되어야 하는 Pod 수이며 보통 parallelism 과 같게 둡니다. Deployment 나 단일 Pod 도 같은 방식으로 Pod 템플릿에 어노테이션과 schedulerName 을 적으면 됩니다.
어노테이션 없이 schedulerName: volcano-scheduler 만 적으면 Volcano 가 Pod 마다 minMember: 1 인 PodGroup 을 자동으로 만들어 gang 이 되지 않습니다. 어노테이션을 반드시 적습니다.
KAI 로 직접 설정
Job 이나 JobSet 은 PodGroup 을 직접 만들지 않습니다. 리소스에 kai.scheduler/batch-min-member 어노테이션으로 gang 최소 크기를 적으면 KAI 가 PodGroup 을 만듭니다. Pod 템플릿에는 kai.scheduler/enabled: "true", kai.scheduler/queue: default-queue 라벨과 schedulerName: kai-scheduler 를 적습니다.
apiVersion: batch/v1
kind: Job
metadata:
name: allreduce-test
namespace: kbm-g-np-example
labels:
kueue.x-k8s.io/queue-name: training
kueue.x-k8s.io/priority-class: normal
annotations:
kai.scheduler/batch-min-member: "4"
spec:
parallelism: 4
completions: 4
completionMode: Indexed
template:
metadata:
labels:
kai.scheduler/enabled: "true"
kai.scheduler/queue: default-queue
spec:
schedulerName: kai-scheduler
restartPolicy: Never
containers:
- name: main
image: pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime
command: ["sleep", "600"]
resources:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
Job 은 kai.scheduler/batch-min-member 가 없으면 KAI 가 minMember 1 로 PodGroup 을 만들어 gang 이 되지 않습니다. 어노테이션을 반드시 적고, 값은 보통 parallelism 과 같게 둡니다.
JobSet 에서는 어노테이션 위치에 따라 의미가 다릅니다. JobSet metadata.annotations 는 replicatedJob 몇 개가 동시에 시작해야 하는지, replicatedJobs[].template.metadata.annotations 는 그 replicatedJob 안에서 Pod 몇 개가 동시에 배치되어야 하는지를 정합니다. JobSet 은 어노테이션이 없을 때 기본값이 Job 의 parallelism 입니다.
Deployment 나 단일 Pod 처럼 Job 이 아닌 리소스를 gang 으로 묶으려면 PodGroup 을 직접 만들고 Pod 를 붙입니다. Deployment 의 Pod 마다 PodGroup 을 따로 만들므로, 리소스에 kai.scheduler/skip-podgrouper: "true" 어노테이션을 주어 KAI 가 PodGroup 을 만들지 않게 하고, Pod 템플릿 어노테이션 pod-group-name 에 PodGroup 이름을 적습니다.
apiVersion: scheduling.run.ai/v2alpha2
kind: PodGroup
metadata:
name: serving-group
namespace: kbm-g-np-example
spec:
minMember: 2
queue: default-queue
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: serving-group
namespace: kbm-g-np-example
annotations:
kai.scheduler/skip-podgrouper: "true"
spec:
replicas: 2
selector:
matchLabels:
app: serving-group
template:
metadata:
labels:
app: serving-group
kai.scheduler/enabled: "true"
kueue.x-k8s.io/queue-name: serving
kueue.x-k8s.io/priority-class: normal
annotations:
pod-group-name: serving-group
spec:
schedulerName: kai-scheduler
containers:
- name: main
image: docker.io/vllm/vllm-openai:v0.28.0
resources:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "8"
상태 확인
Pending 이 길어지면 먼저 Kueue 입장 단계인지, 스케줄러 배치 단계인지 구분합니다.
kubectl get workloads -n kbm-g-np-example
kubectl describe workload -n kbm-g-np-example <workload-name>
Workload 의 Admitted 가 False 이면 테넌트 quota 부족으로 Kueue 에서 대기 중입니다. Job, TrainJob 은 Pod 가 아직 생성되지 않고, Deployment, LeaderWorkerSet, DynamoGraphDeployment 는 Pod 가 SchedulingGated 상태로 보입니다.
Admitted 가 True 인데 Pod 가 Pending 이면 gang 배치 단계입니다. PodGroup 을 확인합니다.
kubectl get podgroups.scheduling.run.ai -n kbm-g-np-example
kubectl describe podgroups.scheduling.run.ai -n kbm-g-np-example <podgroup-name>
kubectl get podgroups.scheduling.volcano.sh -n kbm-g-np-example
kubectl describe podgroups.scheduling.volcano.sh -n kbm-g-np-example <podgroup-name>
KAI 를 쓰는 워크로드(TrainJob kai Runtime, Dynamo, KAI 직접 설정)는 scheduling.run.ai, Volcano 를 쓰는 워크로드(TrainJob volcano Runtime, LLMInferenceService, Volcano 직접 설정)는 scheduling.volcano.sh 를 봅니다. minMember 가 필요한 Pod 수이고, describe 의 이벤트에 자리가 부족한 이유가 적힙니다.
kubectl get events -n kbm-g-np-example --sort-by=.lastTimestamp
kubectl describe pod -n kbm-g-np-example <pod-name>
gang 이 채워지지 않는 대표 원인:
minMember만큼의 GPU 노드가 동시에 비어 있지 않음- hard 토폴로지 Runtime 에서 같은 HyperNode(IB spine) 안에 전부 들어갈 자리가 없음
- Pod 생성 자체가 실패해 gang 멤버 수가 모자람(worker 템플릿 부분 덮어쓰기, 이미지 없음, admission webhook 거부 등)
- Pod 가 requests 에
nvidia.com/mlnxnics를 함께 요청하지 않아 IB 네트워크가 붙지 않고 ContainerCreating 에 머묾
주의 사항
- gang 은 Kueue 입장 뒤의 배치 단계에서만 동작합니다. 우선순위와 선점은 Kueue 가 WorkloadPriorityClass 로 결정하고, 스케줄러는 선점을 하지 않습니다.
- TrainJob 에는
scheduling.volcano.sh/queue-name어노테이션과kai.scheduler/queue라벨을 넣지 않습니다. 넣으면 생성이 거부됩니다. 직접 설정하는 Job, JobSet, Deployment, Pod 의 KAI 큐 라벨은kai.scheduler/queue: default-queue하나만 씁니다. Volcano 큐 어노테이션은 어디에도 쓰지 않습니다. - 학습 코드의 world size 는
numNodes와 일치해야 합니다. gang 은 Pod 수를 보장할 뿐 학습 프레임워크의 rendezvous 를 대신하지 않습니다.