Skip to content

Split View: 멀티 GPU·멀티노드 학습 플랫폼 총정리 — 프레임워크 지도부터 Slurm·Kubeflow 실전 가이드까지

|

멀티 GPU·멀티노드 학습 플랫폼 총정리 — 프레임워크 지도부터 Slurm·Kubeflow 실전 가이드까지

들어가며 — GPU가 늘어나는 순간 문제는 소프트웨어가 된다

GPU 한 장의 학습은 프레임워크가 알아서 해 줍니다. GPU 여덟 장, 노드 네 대가 되는 순간부터는 프로세스를 어떻게 띄우고, 서로를 어떻게 찾게 하고, 실패하면 어떻게 다시 시작하는가라는 인프라 문제가 시작됩니다. 이 글은 그 지형 전체를 다룹니다 — 어떤 라이브러리들이 있는지(생태계 지도), 모델을 어떻게 쪼개는지(병렬화 전략), 그리고 잡을 어디에 던지는지(Slurm vs Kubeflow vs Ray). AI 모델 개발 생애주기의 "분산 학습 스택" 절을 실전 수준으로 확장한 글입니다.

1부 — AI 라이브러리·프레임워크 생태계 지도

먼저 등장인물부터. 층위로 나누면 지형이 명확해집니다.

층위               대표 주자                      한 줄 요약
────────────────  ────────────────────────────  ─────────────────────────────
기반 프레임워크     PyTorch 2.x                   사실상 표준. torch.compile로 컴파일 가속
                   JAX                           함수형+XLA. TPU·구글 계열 연구의 중심
모델·데이터 허브    HuggingFace Transformers      모델 정의의 공용어
                   Datasets / Tokenizers         데이터 로딩·토크나이즈 표준
파인튜닝 계층       PEFT (LoRA), TRL (SFT/DPO)    적은 자원으로 튜닝·정렬
                   Accelerate                    단일↔분산 코드를 같게 만드는 추상화
분산 학습 엔진      PyTorch DDP/FSDP              프레임워크 내장 병렬화
                   DeepSpeed (ZeRO)              메모리 최적화의 대명사
                   Megatron-LM                   TP/PP 포함 초대형 사전학습의 표준
오케스트레이션      Slurm                         HPC 클러스터의 왕
                   Kubeflow / Ray                쿠버네티스·파이썬 네이티브 진영
서빙(참고)         vLLM / SGLang / TensorRT-LLM  학습이 끝나면 이쪽 — 서빙 편 참조

선택 감각: 연구·파인튜닝은 PyTorch + HF 스택이 기본값, TPU나 대규모 병렬 연구는 JAX, 초대형 사전학습은 Megatron 계열, 그리고 그 사이의 모든 곳에 DeepSpeed와 FSDP가 있습니다.

2부 — 병렬화 전략: 무엇이 안 들어가는가부터 묻기

병렬화 선택은 "무엇이 GPU에 안 들어가는가"로 결정됩니다.

  • DDP(Data Parallel) — 모델은 들어가는데 더 빨리 학습하고 싶다. 모델을 GPU마다 복제하고 배치를 나눠, 역전파 후 그래디언트를 all-reduce로 동기화합니다. 가장 단순하고 확장 효율도 좋아, 가능하면 항상 DDP가 1순위입니다.
  • ZeRO / FSDP(Fully Sharded Data Parallel) — 모델(정확히는 파라미터+그래디언트+옵티마이저 상태)이 안 들어간다. 그 세 가지를 GPU들에 샤딩해 두고, 필요한 순간에만 모아 씁니다. DeepSpeed ZeRO의 단계(1: 옵티마이저, 2: +그래디언트, 3: +파라미터)와 PyTorch FSDP가 같은 아이디어의 두 구현입니다. 7B~70B 파인튜닝의 주력입니다.
  • TP(Tensor Parallel)한 층조차 안 들어간다, 또는 지연이 중요하다. 행렬 곱 자체를 GPU들에 쪼갭니다. 통신이 잦아 노드 내(NVLink) 사용이 원칙입니다.
  • PP(Pipeline Parallel) — 층들을 GPU 그룹에 나눠 조립 라인처럼 흘립니다. 마이크로배치로 파이프라인 거품을 줄입니다. 노드 경계를 넘는 분할에 적합합니다.
  • 실전 초대형 학습은 이들을 3D 병렬(DP × TP × PP)로 조합하고, MoE 모델이면 EP(Expert Parallel)가 추가됩니다.

암기 공식: 들어가면 DDP → 안 들어가면 FSDP/ZeRO → 그래도 안 되면 TP·PP 추가. 그리고 어떤 조합이든 혼합 정밀도(bf16)와 그래디언트 체크포인팅은 기본 장착입니다.

3부 — torchrun: 모든 것의 공통 분모

Slurm이든 Kubeflow든, 바닥에서는 대부분 torchrun(PyTorch의 분산 런처)이 돕니다. 개념은 세 가지뿐입니다 — 전체 프로세스 수(world size), 내 순번(rank), 만남의 장소(rendezvous).

# 단일 노드 8 GPU
torchrun --nproc_per_node=8 train.py

# 멀티 노드 (예: 2노드 × 8GPU = world size 16)
# 모든 노드에서 같은 명령 실행, node_rank만 다르게
torchrun \
  --nnodes=2 --nproc_per_node=8 --node_rank=0 \
  --rdzv_backend=c10d --rdzv_endpoint=10.0.0.1:29500 \
  train.py

학습 코드는 torch.distributed.init_process_group()LOCAL_RANK 환경변수로 자기 GPU를 잡으면 됩니다. HF Accelerate나 Lightning을 쓰면 이 배선을 라이브러리가 대신합니다. 멀티노드의 성능 전제 조건 하나 — 노드 간은 이더넷이 아니라 RDMA(InfiniBand/RoCE) 여야 all-reduce가 병목이 되지 않고, NCCL이 그 위에서 통신을 담당합니다.

4부 — Slurm 사용 가이드: HPC의 왕

Slurm은 수십 년 HPC 역사에서 다듬어진 배치 스케줄러로, GPU 클러스터 학습의 사실상 표준입니다. 버전은 연.월 체계(24.11, 25.05 …)로 SchedMD가 관리합니다. 개념은 네 개면 충분합니다: 노드(기계) — 파티션(노드 묶음, 큐) — (자원 요청+스크립트) — GRES(GPU 같은 일반 자원).

# 클러스터 상태 훑기
sinfo                      # 파티션·노드 상태
squeue --me                # 내 잡 큐
scontrol show node node01  # 노드 상세

# 잡 던지기 3형제
sbatch job.sh              # 배치 제출 (표준)
srun --pty bash            # 인터랙티브 셸 (디버깅용)
salloc --gres=gpu:2        # 자원 선점 후 수동 실행

멀티노드 학습의 핵심은 sbatch 스크립트입니다. 2노드 × 8GPU torchrun 예시:

#!/bin/bash
#SBATCH --job-name=llm-train
#SBATCH --partition=gpu
#SBATCH --nodes=2                  # 노드 2대
#SBATCH --ntasks-per-node=1        # 노드당 torchrun 1개 (프로세스는 torchrun이 8개 생성)
#SBATCH --gres=gpu:8               # 노드당 GPU 8장
#SBATCH --cpus-per-task=64
#SBATCH --time=48:00:00
#SBATCH --output=logs/%x-%j.out    # %x=잡이름 %j=잡ID

# 랑데부 주소 = 첫 번째 노드
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
export MASTER_PORT=29500

# 각 노드에서 torchrun 실행 — SLURM 변수로 배선 자동화
srun torchrun \
  --nnodes=$SLURM_JOB_NUM_NODES \
  --nproc_per_node=8 \
  --node_rank=$SLURM_NODEID \
  --rdzv_backend=c10d \
  --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \
  train.py --config config.yaml

읽는 법: srun이 잡에 배정된 각 노드에서 한 번씩 torchrun을 실행하고, SLURM_NODEID가 노드 순번을, scontrol show hostnames가 마스터 주소를 자동으로 채웁니다. 이 패턴 하나가 Slurm 멀티노드 학습의 80%입니다. 나머지 20%는 운영 팁입니다 — 체크포인트는 --time 제한보다 자주 쓰고(선점·타임아웃 대비), --signal=SIGUSR1@120으로 종료 2분 전 신호를 받아 마지막 체크포인트를 남기고, 어레이 잡(--array)으로 하이퍼파라미터 스윕을 돌립니다.

5부 — Kubeflow 사용 가이드: 쿠버네티스 진영의 답

GPU 클러스터가 이미 쿠버네티스라면(GPU Operator로 세팅된 그 클러스터), 학습 잡도 쿠버네티스 리소스로 던지는 것이 자연스럽습니다. Kubeflow는 그 진영의 종합 플랫폼입니다.

구성요소 지도 — 전부 쓸 필요는 없습니다:

컴포넌트             역할                          꼭 필요한가
──────────────────  ───────────────────────────  ─────────────────
Training Operator    분산 학습 잡 CRD (핵심!)       ★ 이것만으로 시작 가능
Pipelines            ML 워크플로 DAG 오케스트레이션   파이프라인화 단계에서
Katib                하이퍼파라미터 튜닝·AutoML      필요할 때
Notebooks            클러스터 내 주피터 환경          편의 기능
KServe               모델 서빙 (별도 프로젝트화)      서빙 단계에서

핵심은 Training Operator입니다. PyTorchJob CRD 하나로 멀티노드 학습이 선언됩니다:

apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: llm-train
spec:
  nprocPerNode: "8"                # 노드(파드)당 프로세스 수 = GPU 수
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      template:
        spec:
          containers:
            - name: pytorch
              image: my-registry/train:latest
              command: ["torchrun", "train.py"]
              resources:
                limits:
                  nvidia.com/gpu: 8
    Worker:
      replicas: 3                  # 마스터 1 + 워커 3 = 4노드
      template:
        spec:
          containers:
            - name: pytorch
              image: my-registry/train:latest
              command: ["torchrun", "train.py"]
              resources:
                limits:
                  nvidia.com/gpu: 8

Training Operator가 파드들을 만들고 MASTER_ADDR·WORLD_SIZE·RANK 환경변수를 자동 주입하므로, Slurm에서 손으로 하던 배선이 사라집니다. 잡 상태는 kubectl get pytorchjobs로 봅니다. 참고로 차세대 Kubeflow Trainer(v2) 는 프레임워크별 CRD들을 TrainJob 하나로 통합하는 방향으로 진화 중입니다 — 신규 도입이라면 문서에서 v2 지원 상태를 확인하고 시작하세요. 갱 스케줄링(전 노드가 동시에 확보되어야 시작)은 Volcano/Kueue 같은 스케줄러를 붙여 해결합니다.

6부 — 선택 기준: Slurm vs Kubeflow vs Ray

상황                                        추천
─────────────────────────────────────────  ─────────────────────────
HPC 전통이 있는 조직, 베어메탈 GPU 팜         Slurm
이미 쿠버네티스로 모든 걸 운영               Kubeflow (Training Operator)
파이썬 코드 안에서 유연한 분산·서빙 통합       Ray (Train/Serve)
연구실 소규모, 노드 2~4대                    그냥 torchrun + pdsh/tmux도 충분
클라우드 매니지드 선호                        SageMaker/Vertex 등 (개념은 동일)

본질을 하나만 남기면 — 어느 플랫폼이든 바닥은 같습니다: torchrun(또는 그 등가물)이 프로세스를 띄우고, NCCL이 GPU 간 통신을 하고, 체크포인트가 장애를 견딥니다. 플랫폼은 "누가 노드를 빌려주고 환경변수를 채워주는가"의 차이일 뿐입니다. 그래서 한 플랫폼에서 멀티노드를 제대로 이해하면 나머지는 번역 문제가 됩니다.

마치며

GPU가 한 장에서 여러 장, 여러 노드로 늘 때 마주치는 결정을 순서대로 정리하면: 병렬화 전략(들어가면 DDP, 안 들어가면 FSDP/ZeRO, 그래도 안 되면 TP·PP) → 런처(torchrun) → 오케스트레이터(Slurm 또는 Kubeflow) → 운영(체크포인트·RDMA·갱 스케줄링). 이 스택 위에서 학습이 돌기 시작하면, 다음 병목은 데이터입니다 — 그 이야기는 LLM 학습 데이터 전처리 편에서 이어집니다.

참고 자료

Multi-GPU, Multi-Node Training Platforms: The Complete Map — from the Framework Ecosystem to Hands-On Slurm and Kubeflow Guides

Introduction — The Moment GPUs Multiply, the Problem Becomes Software

Training on a single GPU is something the framework handles for you. The moment it becomes eight GPUs across four nodes, an infrastructure problem begins: how do you launch the processes, how do they find each other, and how do you restart when something fails? This post covers that entire terrain — which libraries exist (the ecosystem map), how to split the model (parallelization strategies), and where to submit the job (Slurm vs Kubeflow vs Ray). It expands the "distributed training stack" section of the AI model development lifecycle post to a hands-on level.

Part 1 — A Map of the AI Library and Framework Ecosystem

First, the cast of characters. Split them into layers and the terrain becomes clear.

Layer                Key players                   One-line summary
───────────────────  ────────────────────────────  ─────────────────────────────
Base frameworks      PyTorch 2.x                   The de facto standard; compiled speedups via torch.compile
                     JAX                           Functional + XLA; the center of TPU and Google-sphere research
Model & data hub     HuggingFace Transformers      The lingua franca of model definitions
                     Datasets / Tokenizers         The standard for data loading and tokenization
Fine-tuning layer    PEFT (LoRA), TRL (SFT/DPO)    Tuning and alignment on modest resources
                     Accelerate                    One abstraction for single-GPU and distributed code
Distributed engines  PyTorch DDP/FSDP              Framework-native parallelism
                     DeepSpeed (ZeRO)              Synonymous with memory optimization
                     Megatron-LM                   The standard for giant pretraining, TP/PP included
Orchestration        Slurm                         The king of HPC clusters
                     Kubeflow / Ray                The Kubernetes and Python-native camps
Serving (reference)  vLLM / SGLang / TensorRT-LLM  Where you go after training — see the serving post

A sense for choosing: research and fine-tuning default to the PyTorch + HF stack, TPU and large-scale parallelism research point to JAX, giant pretraining belongs to the Megatron family, and everywhere in between you will find DeepSpeed and FSDP.

Part 2 — Parallelization Strategies: Start by Asking What Does Not Fit

The choice of parallelization is decided by "what does not fit on the GPU."

  • DDP (Data Parallel) — the model fits, but you want to train faster. Replicate the model on every GPU, split the batch, and synchronize gradients with all-reduce after the backward pass. It is the simplest and scales well, so whenever possible DDP is the first choice.
  • ZeRO / FSDP (Fully Sharded Data Parallel) — the model (more precisely, parameters + gradients + optimizer state) does not fit. Shard those three across the GPUs and gather them only at the moment they are needed. The stages of DeepSpeed ZeRO (1: optimizer, 2: +gradients, 3: +parameters) and PyTorch FSDP are two implementations of the same idea. This is the workhorse of 7B–70B fine-tuning.
  • TP (Tensor Parallel)not even a single layer fits, or latency matters. Split the matrix multiplications themselves across GPUs. Communication is frequent, so the rule is to stay within a node (NVLink).
  • PP (Pipeline Parallel) — distribute the layers across GPU groups and stream data through like an assembly line. Micro-batches shrink the pipeline bubbles. A good fit for splits that cross node boundaries.
  • Real-world giant training combines these into 3D parallelism (DP × TP × PP), and MoE models add EP (Expert Parallel) on top.

The formula to memorize: fits → DDP; does not fit → FSDP/ZeRO; still stuck → add TP and PP. And whatever the combination, mixed precision (bf16) and gradient checkpointing come as standard equipment.

Part 3 — torchrun: The Common Denominator of Everything

Whether it is Slurm or Kubeflow, at the bottom it is usually torchrun (PyTorch's distributed launcher) doing the work. There are only three concepts — the total number of processes (world size), your position in line (rank), and the meeting point (rendezvous).

# Single node, 8 GPUs
torchrun --nproc_per_node=8 train.py

# Multi-node (e.g., 2 nodes × 8 GPUs = world size 16)
# Run the same command on every node, changing only node_rank
torchrun \
  --nnodes=2 --nproc_per_node=8 --node_rank=0 \
  --rdzv_backend=c10d --rdzv_endpoint=10.0.0.1:29500 \
  train.py

The training code just calls torch.distributed.init_process_group() and then grabs its GPU from the LOCAL_RANK environment variable. With HF Accelerate or Lightning, the library does this wiring for you. One performance precondition for multi-node: the fabric between nodes must be RDMA (InfiniBand/RoCE), not plain Ethernet, or all-reduce becomes the bottleneck — NCCL handles the communication on top of it.

Part 4 — A Slurm Guide: The King of HPC

Slurm is a batch scheduler polished over decades of HPC history, and the de facto standard for GPU cluster training. Versions follow a year.month scheme (24.11, 25.05, ...) maintained by SchedMD. Four concepts are enough: node (a machine) — partition (a group of nodes, i.e., a queue) — job (a resource request plus a script) — GRES (generic resources such as GPUs).

# Survey the cluster
sinfo                      # Partition and node status
squeue --me                # My job queue
scontrol show node node01  # Node details

# The three ways to submit work
sbatch job.sh              # Batch submission (the standard)
srun --pty bash            # Interactive shell (for debugging)
salloc --gres=gpu:2        # Reserve resources, then run by hand

The heart of multi-node training is the sbatch script. A 2-node × 8-GPU torchrun example:

#!/bin/bash
#SBATCH --job-name=llm-train
#SBATCH --partition=gpu
#SBATCH --nodes=2                  # 2 nodes
#SBATCH --ntasks-per-node=1        # 1 torchrun per node (torchrun spawns the 8 processes)
#SBATCH --gres=gpu:8               # 8 GPUs per node
#SBATCH --cpus-per-task=64
#SBATCH --time=48:00:00
#SBATCH --output=logs/%x-%j.out    # %x=job name %j=job ID

# Rendezvous address = the first node
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
export MASTER_PORT=29500

# Run torchrun on each node — SLURM variables automate the wiring
srun torchrun \
  --nnodes=$SLURM_JOB_NUM_NODES \
  --nproc_per_node=8 \
  --node_rank=$SLURM_NODEID \
  --rdzv_backend=c10d \
  --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \
  train.py --config config.yaml

How to read it: srun runs torchrun once on each node assigned to the job, SLURM_NODEID supplies the node index, and scontrol show hostnames fills in the master address automatically. This one pattern is 80% of Slurm multi-node training. The remaining 20% is operational craft — write checkpoints more often than the --time limit forces you to (to survive preemption and timeouts), catch a signal two minutes before termination with --signal=SIGUSR1@120 to save one last checkpoint, and run hyperparameter sweeps as array jobs (--array).

Part 5 — A Kubeflow Guide: The Kubernetes Camp's Answer

If your GPU cluster is already Kubernetes (the very cluster set up with the GPU Operator), it is natural to submit training jobs as Kubernetes resources too. Kubeflow is that camp's all-in-one platform.

A map of the components — you do not need all of them:

Component          Role                                        Do you need it?
─────────────────  ─────────────────────────────────────────   ────────────────────────────
Training Operator  Distributed training job CRDs (the core!)   ★ Enough on its own to start
Pipelines          ML workflow DAG orchestration                When you pipeline things
Katib              Hyperparameter tuning / AutoML               When you need it
Notebooks          Jupyter environments inside the cluster      A convenience
KServe             Model serving (now its own project)          At the serving stage

The core is the Training Operator. A single PyTorchJob CRD declares multi-node training:

apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: llm-train
spec:
  nprocPerNode: "8"                # Processes per node (pod) = number of GPUs
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      template:
        spec:
          containers:
            - name: pytorch
              image: my-registry/train:latest
              command: ["torchrun", "train.py"]
              resources:
                limits:
                  nvidia.com/gpu: 8
    Worker:
      replicas: 3                  # 1 master + 3 workers = 4 nodes
      template:
        spec:
          containers:
            - name: pytorch
              image: my-registry/train:latest
              command: ["torchrun", "train.py"]
              resources:
                limits:
                  nvidia.com/gpu: 8

The Training Operator creates the pods and automatically injects the MASTER_ADDR, WORLD_SIZE, and RANK environment variables, so the wiring you did by hand on Slurm disappears. Check job status with kubectl get pytorchjobs. Note that the next-generation Kubeflow Trainer (v2) is evolving toward unifying the per-framework CRDs into a single TrainJob — if you are adopting fresh, check the v2 support status in the docs before you start. Gang scheduling (the job starts only when all nodes are secured at once) is solved by attaching a scheduler such as Volcano or Kueue.

Part 6 — Selection Criteria: Slurm vs Kubeflow vs Ray

Situation                                                Recommendation
───────────────────────────────────────────────────────  ─────────────────────────
Organization with an HPC tradition, bare-metal GPU farm  Slurm
Everything already runs on Kubernetes                    Kubeflow (Training Operator)
Flexible distributed training + serving inside Python    Ray (Train/Serve)
Small research lab, 2~4 nodes                            Plain torchrun + pdsh/tmux is enough
Prefer cloud-managed services                            SageMaker, Vertex, etc. (same concepts)

To keep only one essence — the floor is the same on every platform: torchrun (or its equivalent) launches the processes, NCCL carries the GPU-to-GPU communication, and checkpoints survive the failures. Platforms differ only in "who lends you the nodes and who fills in the environment variables." That is why, once you properly understand multi-node on one platform, the rest becomes a translation problem.

Closing

Lining up the decisions you face as GPUs grow from one card to many cards and many nodes: parallelization strategy (fits → DDP; does not fit → FSDP/ZeRO; still stuck → TP and PP) → launcher (torchrun) → orchestrator (Slurm or Kubeflow) → operations (checkpoints, RDMA, gang scheduling). Once training runs on top of this stack, the next bottleneck is data — that story continues in the LLM training data preprocessing post.

References