kubectl을 이용한 모델 학습

Prev Next

VPC 환경에서 이용 가능합니다.

ML expert Platform은 모델 학습을 위한 여러 프레임워크의 Job을 제공합니다.
kubectl을 이용한 모델 학습에서는 가장 많이 사용하는 형태인 단일 노드 학습(Job), 분산 노드 학습(PytorchJob) 형태로 설명합니다.

Job vs PytorchJob

단일 노드 학습의 경우 Job 사용을 권장합니다.
PytorchJob을 사용할 경우, 분산 노드 학습을 위한 기능으로 인해 Master와 Worker 형태로 배포 관리 되기 때문에 리소스를 불필요하게 낭비할 수 있습니다.

단일 노드 학습 (Job) 실행하기

학습을 위해 Kubernetes Job 명세는 다음 예제와 같이 작성할 수 있습니다.

# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: mnist
  namespace: p-{projectName}
spec:
  backoffLimit: 1
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
    spec:
      restartPolicy: Never
      containers:
        - name: main
          image: { 학습 이미지 (e.g. example.com/mnist:latest ) } # NVIDIA Base 이미지 기준으로 작성된 학습 코드
          imagePullPolicy: Always
          resources:
            limits:
              memory: "8Gi"
              cpu: "4"
              nvidia.com/gpu: "1"
          command: ["python"]
          args:
            - /opt/mnist/src/mnist.py
            - --checkpoint_path
            - /opt/mnist/checkpoints/mnist.pt
            - --log_path
            - /opt/mnist/log
            - --data_path
            - /opt/mnist/data
            - --download_data
kubectl apply -f job.yaml
batch/mnist created

외부 컨테이너 레지스트리 사용

컨테이너 레지스트리에 Secret 정보가 필요할 경우에는 Container Secret 생성하기를 참고하여 생성할 수 있습니다.
생성된 Secret은 다음과 같이 사용 가능합니다.

...
spec:
    imagePullSecrets:
    - name: my-harbor-secret  # 미리 만들어둔 Docker Credential Secret 이름
...

기존 Volume 사용

Volumes를 통해 생성된 Volume을 사용하기 위해서는 다음과 같이 사용할 수 있습니다.

...
spec:
    containers:
    - name: main
      ...
      volumeMounts:
      - mountPath: /data
        name: mnist-data # 하단 spec.volumes에 기재한 이름
    volumes:
    - name: mnist-data
      persistentVolumeClaim:
        claimName: mnist-data # Volumes 를 통해 생성된 PVC 이름
...

Job 라이프 사이클

Job이 종료될 경우, 일정 시간 동안 컨테이너 로그 및 상태를 남기기 위해 삭제되지 않고 목록에 남아 있게 됩니다.
목록에 남아있을 수 있는 Job의 최대 개수 제한이 있기 때문에 TTL을 통해 라이프사이클을 관리해야합니다. 자세한 정보는 Kubernetes Job API 문서를 참조바랍니다.

Job을 생성하면서 TTL(Time To Live)을 임의로 설정할 수 있습니다. TTL은 Job이 완료(성공/실패)된 후에 활성화되고, TTL만큼 지나면 Job과 Job에 속하는 Pod이 모두 자동으로 삭제됩니다.

아래는 3주간의 TTL을 적용한 예시입니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: mnist
  namespace: p-{projectName}
spec:
  ttlSecondsAfterFinished: 1814400 # TTL을 직접 설정하는 필드 (단위: sec)

분산 노드 학습 (PytorchJob) 실행하기

PyTorchJob 이용 시 아래와 같은 장점이 있습니다.

  • 작성된 컨테이너 spec 을 기반하여 Master, Worker Pod을 적절히 생성해줍니다.
  • PyTorch 분산 학습에 일반적으로 필요한 환경변수 (i.e., WORLD_SIZE, RANK, MASTER_ADDR 등)를 자동으로 설정합니다.
  • 학습에 사용되는 Pod들이 서로 통신할 수 있도록 K8s Service를 자동으로 생성해줍니다. Master는 <pytorch-job-name>-master-0, Worker는 <pytorch-job-name>-worker-<idx> 와 같은 이름으로 접근할 수 있습니다.
  • 필요한 경우, Elastic Policy 사용를 통해 torchrun에서 사용할 인자들을 환경 변수들로 생성해줍니다(--nnodes, --nproc-per-node, --rdzv-endpoint 등).

PytorchJob 작성하기

Pytorch로 분산 학습 시에는 torchrun(Elastic Launch)을 사용하는 것이 권장됩니다. 또한, torchrun 사용 시 마스터 Pod을 따로 명시하지 않습니다. Torch Elastic 에서는 마스터 노드 역할을 하는 RANK=0 Pod이 실행 도중 변경될 수 있습니다.

  • spec.elasticPolicy - torchrun 관련 설정입니다. 여기에 명시한 설정들이 환경 변수로 주입됩니다. 자세한 것은 Elastic Policy 사용를 참고해주세요.
  • spec.runPolicy - PyTorchJob 실행 및 종료 후처리 관련된 파라미터들을 명시할 수 있습니다. 자세한 것은 Run Policy 사용를 참고해주세요.
  • spec.pytorchReplicaSpecs.Worker - 분산 학습을 수행하는 Worker Pod에 대한 설정입니다.
주의
  • 학습을 진행하는 컨테이너의 이름 (e.g. spec.pytorchReplicaSpecs.Worker.template.spec.containers[*].name)은 반드시 pytorch 이어야 합니다.
  • 원활한 분산학습을 위하여 Istio Sidecar가 Inject가 되지 않도록 spec.pytorchReplicaSpecs.Worker.template.metadata.annotationssidecar.istio.io/inject: "false"를 반드시 명기해야합니다. 해당 annotation이 설정되지 않는 경우, RuntimeError: Connection reset by peer 등 노드간 통신에 관련된 에러를 볼 수 있습니다.

학습을 위해 PytorchJob 명세는 다음 예제와 같이 작성할 수 있습니다.

# pytorchjob.yaml
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
    name: pytorch-mnist-dist-nccl
    namespace: p-{ projectName } # 프로젝트에 해당되는 Kubernetes Namespace 명
spec:
    pytorchReplicaSpecs:
        Worker:
            replicas: 2
            restartPolicy: OnFailure
            template:
                metadata:
                    annotations:
                        sidecar.istio.io/inject: "false"  # Istio sidecar injection 비활성화는 필수
                spec:
                    nodeSelector:
                        mlx.navercorp.com/zone: { 제공된 GPU Zone 이름 } # GPU Resources 에서 확인 가능한 Zone 명
                    containers:
                    - name: pytorch  # PyTorchJob의 container 이름은 반드시 pytorch로 설정
                      image: examples.com/pytorch-mnist-dist:23.03-py3
                      imagePullPolicy: Always
                      command: ["bash", "-c"]
                      args:
                      - >
                        torchrun --nnodes ${PET_NNODES} --nproc_per_node ${PET_NPROC_PER_NODE} --rdzv_id ${PET_RDZV_ID} --rdzv_backend ${PET_RDZV_BACKEND} --rdzv_endpoint ${PET_RDZV_ENDPOINT}
                        /opt/mnist/src/mnist.py --checkpoint_path /data/checkpoints/mnist.pt --log_path /data/logs --data_path /data/dataset
                      env:
                      - name: NCCL_DEBUG
                        value: INFO
kubectl apply -f pytorchjob.yaml
pytorchjob.kubeflow.org/pytorch-elastic-mnist-nccl created

Elastic Policy 사용

torchrun 을 사용하기 위해 elasticPolicy 를 작성합니다.

...
spec:
    ...
    elasticPolicy:
        rdzvId: mnist
        rdzvBackend: c10d
        minReplicas: 2
        maxReplicas: 2
        nProcPerNode: 8
    ...

elasticPolicy 각 필드값을 기반으로 PyTorchJob 에 사용될 환경변수가 설정됩니다. 환경변수들은 torchrun 인자를 설정하는데 쓰일 수 있습니다. torchrun 에서 사용하는 인자 등 대한 자세한 설명은 공식 문서를 참고하기 바랍니다.

elasticPolicy 필드 대응 환경변수 연관 torchrun 인자 설명
rdzvId PET_RDZV_ID --rdzv-id 랑데부용 Job ID
rdzvBackend PET_RDZV_BACKEND --rdzv-backend 랑데부 백엔드 (i.e., c10d)
minReplicas, maxReplicas PET_NNODES --nnodes 노드 개수
nProcPerNode PET_NPROC_PER_NODE --nproc-per-node 노드당 GPU 개수
maxRestarts PET_MAX_RESTARTS --max-restarts 최대 재시작 횟수

Run Policy 사용

torchrun을 사용하기 위해 runPolicy를 작성합니다.

...
spec:
  runPolicy:
    cleanPodPolicy: None
    ttlSecondsAfterFinished: 1814400 # TTL을 직접 설정하는 필드 (단위: sec)
...

spec.runPolicy에는 PyTorchJob 의 실행 및 정리에 관련된 파라미터들을 명시할 수 있습니다. 명시하지 않을 시 기본값을 사용하게 됩니다. spec.runPolicy 하위에 명시할 수 있는 파라미터들은 다음과 같습니다. (참고 자료: Kubeflow Trainer API Reference v1.9)

  • cleanPodPolicy - PytorchJob 이 완료된 이후 Pod들을 어떻게 정리할지 결정합니다.
    • 기본값: None
    • None: Job이 완료된 후 Pod들을 삭제하지 않기 때문에 추후 로그를 확인하는데 도움이됩니다.
    • All: Job이 완료된 후 모든 Pod들을 삭제합니다.
    • Running 은 Job이 완료된 후 실행 중인 Pod을 제거합니다. 특수한 경우가 아니면 사용할 일이 없습니다.
  • ttlSecondsAfterFinished - Job이 완료된 이후 몇 초 후에 Job을 삭제할지 정합니다.
  • activeDeadlineSeconds - Job의 최대 실행 시간입니다. 명시된 시간이 지난 후에는 실패 처리가 됩니다. 설정되지 않은 경우 job의 실행 시간에 제한이 없습니다.
  • backoffLimit - Job이 실패했을 때 최대 재시도 횟수입니다.

Infiniband 사용

참고

InfiniBand 네트워크로 연결된 노드들로 분산 학습을 실행하면 노드간 통신을 가속할 수 있습니다.
이전 섹션에서 보여주었던 예제를 InfiniBand 환경에 맞추기 위해서는 추가해야하는 명세가 몇가지 있는데, 다음과 같이 요약이 가능합니다.

  • InfiniBand 네트워크가 구성된 구역 이름을 Annotation으로 명시합니다 (i.e., mlx.navercorp.com/zone=ai-infra)
  • InfiniBand 사용을 위해 IPC_LOCK capability를 securityContext 에 추가합니다.
  • 분산학습을 돕기 위한 Shared Memory 를 volumes로 설정합니다.

Infiniband 사용을 위해서 다음과 같이 사용할 수 있습니다.

...
metadata:
  annotations:
    mlx.navercorp.com/zone="ai-infra"
...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                - name: pytorch
                   securityContext:  # Infiniband 를 사용하기 위한 securityContext가 필요합니다.
                        capabilities:
                            add: ["IPC_LOCK"]
                    # shared memory
                    volumeMounts:
                    - mountPath: /dev/shm
                       name: shared-memory
                volumes:
                - emptyDir:
                    medium: Memory
                  name: shared-memory
...

PytorchJob 디버깅

PytorchJob 사용 시, 문제가 발생하여 디버깅이 필요할 때 다음과 같이 환경변수 값을 설정하는 것으로 필요한 정보를 로깅하도록 할 수 있습니다.

  • NCCL_DEBUG: NCCL과 관련 디버깅
  • TORCH_DISTRIBUTED_DEBUG, TORCH_CPP_LOG_LEVEL: 분산 학습에 대한 디버깅; 자세한 것은 PyTorch 공식 문서를 참조해주세요.

디버깅을위하여 다음과 같이 사용할 수 있습니다.

...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                - name: pytorch
                  ...
                  env:
                  - name: NCCL_DEBUG
                    value: "INFO"
                  - name: TORCH_DISTRIBUTED_DEBUG
                    value: "DETAIL"
                  - name: TORCH_CPP_LOG_LEVEL
                    value: "INFO"
                  ...

외부 컨테이너 레지스트리 사용

컨테이너 레지스트리에 Secret 정보가 필요할 경우에는 Container Secret 생성하기를 참고하여 생성할 수 있습니다.
생성된 Secret은 다음과 같이 사용이 가능합니다.

...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                - name: pytorch
                  imagePullSecrets:
                  - name: my-harbor-secret  # 미리 만들어둔 Docker Credential Secret 이름
...

기존 Volume 사용

Volumes를 통해 생성된 Volume을 사용하기 위해서는 다음과 같이 사용할 수 있습니다.

...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                - name: pytorch
                    volumeMounts:
                    - mountPath: /data
                       name: mnist-data # 하단 spec.volumes에 기재한 이름
                volumes:
                - name: mnist-data
                  persistentVolumeClaim:
                      claimName: mnist-data # Volumes 를 통해 생성된 PVC 이름
...

PytorchJob 상태 확인

kubectl get, kubectl describe 를 이용하여 PyTorchJob 의 상태를 확인할 수 있습니다.

kubectl get pytorchjob pytorch-elastic-mnist-nccl
NAME                         STATE     AGE
pytorch-elastic-mnist-nccl   Running   12s
kubectl describe pytorchjob pytorch-elastic-mnist-nccl

Status:
  Completion Time:  2024-11-22T09:16:58Z
  Conditions:
    Last Transition Time:  2024-11-22T09:15:43Z
    Last Update Time:      2024-11-22T09:15:43Z
    Message:               PyTorchJob pytorch-elastic-mnist-nccl is created.
    Reason:                PyTorchJobCreated
    Status:                True
    Type:                  Created
    Last Transition Time:  2024-11-22T09:15:48Z
    Last Update Time:      2024-11-22T09:15:48Z
    Message:               PyTorchJob nb12706/pytorch-elastic-mnist-nccl is running.
    Reason:                PyTorchJobRunning
    Status:                False
    Type:                  Running
    Last Transition Time:  2024-11-22T09:16:58Z
    Last Update Time:      2024-11-22T09:16:58Z
    Message:               PyTorchJob nb12706/pytorch-elastic-mnist-nccl successfully completed.
    Reason:                PyTorchJobSucceeded
    Status:                True
    Type:                  Succeeded
  Last Reconcile Time:     2024-11-22T09:15:43Z
  Replica Statuses:
    Worker:
      Selector:   training.kubeflow.org/job-name=pytorch-elastic-mnist-nccl,training.kubeflow.org/operator-name=pytorchjob-controller,training.kubeflow.org/replica-type=worker
      Succeeded:  2
  Start Time:     2024-11-22T09:15:44Z
Events:
  Type    Reason                    Age                  From                   Message
  ----    ------                    ----                 ----                   -------
  Normal  SuccessfulCreatePod       4m21s                pytorchjob-controller  Created pod: pytorch-elastic-mnist-nccl-worker-0
  Normal  SuccessfulCreatePod       4m21s                pytorchjob-controller  Created pod: pytorch-elastic-mnist-nccl-worker-1
  Normal  SuccessfulCreateService   4m21s                pytorchjob-controller  Created service: pytorch-elastic-mnist-nccl-worker-0
  Normal  SuccessfulCreateService   4m21s                pytorchjob-controller  Created service: pytorch-elastic-mnist-nccl-worker-1
  Normal  ExitedWithCode            3m7s (x3 over 3m8s)  pytorchjob-controller  Pod: nb12706.pytorch-elastic-mnist-nccl-worker-1 exited with code 0
  Normal  ExitedWithCode            3m7s (x2 over 3m8s)  pytorchjob-controller  Pod: nb12706.pytorch-elastic-mnist-nccl-worker-0 exited with code 0
  Normal  PyTorchJobSucceeded       3m7s                 pytorchjob-controller  PyTorchJob nb12706/pytorch-elastic-mnist-nccl successfully completed.
  Normal  JobTerminated             3m6s (x4 over 3m7s)  pytorchjob-controller  Job has been terminated. Deleting PodGroup
  Normal  SuccessfulDeletePodGroup  3m6s (x4 over 3m7s)  pytorchjob-controller  Deleted PodGroup: pytorch-elastic-mnist-nccl

학습 Pod이 제대로 생성되지 않는 경우 아래처럼 Events 를 통해 원인을 파악해볼 수 있습니다.

kubectl describe pytorchjob pytorch-elastic-mnist-nccl

...
Events:
  Type     Reason           Age                 From                   Message
  ----     ------           ----                ----                   -------
  Warning  FailedCreatePod  47m (x3 over 103m)  pytorchjob-controller  Error creating: Pods "job-worker-1" is forbidden: exceeded quota: normal-quota, requested: requests.nvidia.com/gpu=1, used: requests.nvidia.com/gpu=2, limited: requests.nvidia.com/gpu=2

분산 학습 시 주의 사항

MLXP에서 분산 학습 시 주의해야 할 사항을 안내합니다.

NVIDIA Driver와 호환되는 CUDA 버전 사용

CUDA Runtime 버전은 노드에 설치된 NVIDIA Driver와 호환되어야 합니다. CUDA Runtime이 요구하는 최소 Driver 버전을 충족하지 못할 경우 CUDA 초기화 오류 또는 런타임 오류가 발생하며 학습 작업이 정상적으로 실행되지 않을 수 있습니다.

IPv6 사용 불가로 인한 GLOO_SOCKET_FAMILY=INET 설정

GLOO는 노드 간 상태 확인 및 Rank 관리를 위해 빈번한 TCP 연결을 생성합니다. IPv6를 사용할 수 없는 환경에서 대규모 학습을 수행할 경우, 각 연결 시도마다 IPv6 연결이 먼저 시도된 후 실패하고 IPv4로 재시도됩니다. 이러한 이중 시도 과정이 반복되면 파일 디스크립터 고갈 등의 자원 문제가 발생할 수 있으며, 결과적으로 학습이 중단될 수 있습니다.

이 문제를 방지하기 위해 GLOO_SOCKET_FAMILY=AF_INET 환경 변수를 설정하여 IPv4만 사용하도록 구성하는 것을 권장합니다.

GLOO에서 IPv4를 강제하려면 다음과 같이 설정할 수 있습니다.

...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                 - name: pytorch
                   ... 
                   env:
                   - name: GLOO_SOCKET_FAMILY
                     value: AF_INET 
...

PyTorchJob 이름 길이 제한 (권장)

PyTorchJob 이름은 Kubernetes 리소스(Pod, Service 등) 생성 시 접두어로 사용되므로 50자 이내로 설정하는 것을 권장합니다. 50자를 초과하면 파생 리소스 이름이 길이 제한을 초과하여 생성이 실패하거나, worker index 파싱에 문제가 발생할 수 있습니다.

Shared Memory 설정 시 memory limit 설정 제거

Shared Memory(/dev/shm)는 용량 제한을 두지 않거나 충분히 크게 설정하는 것을 권장합니다. /dev/shm이 부족하면 DataLoader의 멀티프로세싱 worker가 비정상 종료될 수 있으며, NCCL의 통신 성능 저하로 이어질 수 있습니다.

Memory limit을 제거한 Shared Memory 설정 예시는 다음과 같습니다.

...
spec:
    ...
    pytorchReplicaSpecs:
        Worker:
            template:
                containers:
                - name: pytorch
                    # shared memory
                    volumeMounts:
                    - mountPath: /dev/shm
                      name: shared-memory
                volumes:
                - emptyDir:
                    medium: Memory
                  name: shared-memory