Slurm + Pyxis + Enroot¶
이 페이지는 GPU 학습 클러스터의 스케줄러·컨테이너 런타임 레이어 를 구성하는 방법을 설명합니다. 호스트(드라이버·EFA·컨테이너 툴킷 등)가 이미 검증된 상태를 전제로, 그 위에 다음을 올립니다.
- Slurm — 노드를 묶어 잡을 스케줄링하는 워크로드 매니저
- munge — 노드 간 인증(공유 키 기반)
- Enroot — 루트 권한 없이 컨테이너 이미지를 실행하는 비특권 런타임
- Pyxis — Slurm의 SPANK 플러그인으로,
srun한 줄에서 Enroot 컨테이너를 띄우게 해 주는 접착제
목표는 노드 간 SSH 없이 srun --container-image=... 한 줄로 컨테이너 학습 잡을 전 노드에 띄우는 것입니다. 이 레이어에는 분산 학습 성능을 조용히 깎아먹거나 잡을 즉시 실패시키는 함정이 여럿 있어, 아래에서 "왜 이렇게 하는가"를 함께 설명합니다.
검증 환경 (2026-10 기준)
아래 버전·값은 특정 시점에 검증한 레퍼런스 스냅샷입니다. 실제 구성에 맞게 갱신해 사용하십시오. 원칙(본문)은 버전과 무관하게 유효합니다.
- 호스트 OS: Ubuntu 24.04 (Deep Learning Base OSS Nvidia Driver GPU AMI)
- Slurm: 23.11.4 (Ubuntu apt 패키지)
- Pyxis: 0.24.0
- Enroot: 4.2.1
- 인스턴스: GPU 노드(노드당 GPU 8개, EFA 8개)
- 스케줄러 토폴로지: GPU 노드 1대가 컨트롤러(
slurmctld)를 겸임 - 노드 접속: SSM Session Manager (노드 간 SSH 없음)
아키텍처: 컨트롤러 겸임 구성¶
소규모~중규모 클러스터에서는 GPU 노드 1대가 Slurm 컨트롤러(slurmctld)를 겸임 하는 구성이 간단하고 실용적입니다. 별도 관리 노드를 두지 않아도 되고, 모든 노드가 동일한 AMI·동일한 데몬 집합을 쓰므로 운영이 단순합니다.
[ GPU 노드 × N (한 블록 권장) ]
노드 1 : slurmctld(컨트롤러 겸임) + slurmd + munge + Enroot + Pyxis
노드 2 : slurmd + munge + Enroot + Pyxis
...
노드 N : slurmd + munge + Enroot + Pyxis
네트워크 : 노드 안 NVLink / 노드 간 EFA
공유 스토리지 : /fsx (컨테이너 이미지 sqsh, 데이터, 체크포인트)
- 모든 노드에
slurmd가 돌고, 그중 1대가 추가로slurmctld를 실행합니다. - 노드 간 인증은 munge 공유 키로 처리하므로 노드 간 SSH가 필요 없습니다. 분산 실행은 전부
srun을 통해 이뤄집니다. - Slurm·Pyxis·Enroot를 설치해 둔 상태를 하나의 운영용 AMI 로 구워 두면, 장애 노드 교체 시 같은 템플릿으로 다시 띄우기만 하면 됩니다.
검증 환경 (2026-10 기준)
검증 구성은 Ubuntu apt의 Slurm 23.11.4를 사용했습니다. 실제 환경에서 Slurm을 직접 빌드하거나 다른 패키지로 설치하는 경우에도, 아래 각 항목(노드 이름 해석, munge, Enroot /run 디렉터리, Pyxis 플러그인 등록)은 동일하게 적용됩니다.
1단계. 노드 이름 해석 (가장 흔한 함정)¶
Slurm은 컨트롤러와 각 slurmd가 서로를 IPv4로 찾아 통신합니다. 그런데 최신 Ubuntu(24.04)에서는 노드의 자기 호스트 이름이 IPv6 링크 로컬 주소(fe80::...)로 해석될 수 있습니다. 이 상태에서는 Slurm 노드가 down으로 빠지거나 통신 오류가 납니다.
먼저 현재 해석 결과를 확인합니다.
fe80::...가 보이면 다음 중 하나 를 적용해 호스트 이름이 반드시 IPv4로 풀리도록 고정합니다.
slurm.conf의 노드 정의에NodeAddr=<사설 IPv4>를 명시한다. (각 노드의 통신 주소를 Slurm에 직접 알려 주는 방법)- 모든 노드의
/etc/hosts에<사설 IPv4> <호스트 이름>항목을 추가한다.
두 방법 모두 "Slurm이 노드를 IPv4로 찾게 한다"는 같은 목적을 달성합니다. 운영 AMI를 구울 때 이 설정을 포함시켜 두면 노드 교체 때마다 다시 손댈 필요가 없습니다.
2단계. munge (노드 간 인증)¶
munge는 Slurm 데몬 간 요청을 인증하는 공유 키 메커니즘입니다. 모든 노드가 동일한 /etc/munge/munge.key를 가져야 하며, 키 파일은 소유자 munge, 권한 400이어야 합니다.
# (컨트롤러에서 한 번) 키 생성
sudo /usr/sbin/mungekey --verbose
sudo chown munge:munge /etc/munge/munge.key
sudo chmod 400 /etc/munge/munge.key
노드 간 SSH가 없으므로, 생성한 키는 공유 스토리지(/fsx)를 경유해 모든 노드에 배포한 뒤 각 노드의 /etc/munge/로 복사합니다. 배포가 끝나면 공유 위치에 둔 사본은 반드시 삭제하십시오(키 노출 방지).
# 각 노드에서
sudo install -o munge -g munge -m 400 /fsx/<path>/munge.key /etc/munge/munge.key
sudo systemctl enable --now munge
munge -n | unmunge | grep STATUS # STATUS: Success (0) 확인
3단계. slurm.conf 와 gres.conf¶
Slurm의 노드·리소스 정의입니다. 핵심은 (1) GPU를 GRES(Generic RESource)로 등록하고, (2) 노드의 하드웨어 토폴로지를 정확히 기술하는 것입니다. 노드 값은 각 노드에서 slurmd -C로 출력되는 값을 그대로 써야 합니다(틀리면 노드가 down됩니다).
SlurmctldHost=<controller>(<controller IPv4>)
AuthType=auth/munge
ProctrackType=proctrack/cgroup
TaskPlugin=task/cgroup,task/affinity
SelectType=select/cons_tres
SelectTypeParameters=CR_Core
GresTypes=gpu
# 노드 공통 기본값 — slurmd -C 출력에 맞춤
NodeName=DEFAULT CPUs=192 Boards=1 SocketsPerBoard=2 CoresPerSocket=48 \
ThreadsPerCore=2 RealMemory=<slurmd -C 값보다 약간 작게> Gres=gpu:8 State=UNKNOWN
# 노드별 통신 주소 명시 (1단계의 IPv6 함정 회피)
NodeName=<host-1> NodeAddr=<IPv4-1>
NodeName=<host-2> NodeAddr=<IPv4-2>
# ...
PartitionName=train Nodes=ALL Default=YES MaxTime=INFINITE State=UP
gres.conf에서 GPU 디바이스 파일을 등록합니다.
지킬 것:
RealMemory는slurmd -C가 보고하는 값보다 약간 작게 잡습니다. 실제 가용 메모리가 보고치보다 조금이라도 작으면 노드가down으로 빠지기 때문입니다.cgroup.conf의ConstrainDevices를yes로 운영하면 잡에 할당된 GPU만 보이도록 격리됩니다. 이 경우 잡 안에서/dev/infiniband(EFA)와/dev/gdrdrv(GDRCopy)가 여전히 보이는지 반드시 확인하십시오. 이들이 가려지면 EFA 통신·GPUDirect가 동작하지 않습니다.
검증 환경 (2026-10 기준)
- 노드 토폴로지:
CPUs=192,SocketsPerBoard=2,CoresPerSocket=48,ThreadsPerCore=2,Gres=gpu:8(slurmd -C출력 기준) - 검증 구성은
cgroup.conf에ConstrainDevices=no로 두었습니다.yes로 운영하려면 위 디바이스 가시성 확인이 필요합니다.
4단계. PMIx (MPI 런처)¶
노드 간 SSH 없이 MPI 기반 프로그램(예: nccl-tests의 all_reduce_perf)을 띄우려면 Slurm의 --mpi=pmix를 사용합니다. srun이 PMIx로 각 랭크를 조율하므로 호스트 간 SSH가 필요 없습니다.
--mpi=pmix는 Slurm이 PMIx 지원으로 빌드된 경우에만 사용할 수 있습니다. Slurm을 직접 빌드한다면--with-pmix로 빌드하십시오.
검증 환경 (2026-10 기준)
Ubuntu apt의 Slurm 23.11.4는 PMIx(v5) 지원으로 빌드되어 있어 srun --mpi=list에 pmix가 나옵니다.
5단계. Enroot (비특권 컨테이너 런타임)¶
Enroot는 루트 권한 없이 컨테이너를 실행합니다. 설정의 핵심은 (1) 런타임 디렉터리 /run/enroot를 매 부팅마다 보장 하고, (2) 캐시·데이터처럼 용량이 큰 경로를 노드 로컬 NVMe 에 두는 것입니다.
sudo tee /etc/enroot/enroot.conf > /dev/null <<'CONF'
ENROOT_RUNTIME_PATH /run/enroot/user-$(id -u)
ENROOT_CACHE_PATH /opt/dlami/nvme/enroot/cache/user-$(id -u)
ENROOT_DATA_PATH /opt/dlami/nvme/enroot/data/user-$(id -u)
ENROOT_TEMP_PATH /opt/dlami/nvme/enroot/tmp
ENROOT_SQUASH_OPTIONS -noI -noD -noF -noX -no-duplicates
ENROOT_MOUNT_HOME y
ENROOT_RESTRICT_DEV n
CONF
sudo mkdir -p /opt/dlami/nvme/enroot/tmp
sudo chmod 1777 /opt/dlami/nvme/enroot /opt/dlami/nvme/enroot/tmp
/run/enroot 를 tmpfiles로 보장하기¶
/run은 tmpfs이므로 재부팅 시 비워집니다. 이 디렉터리가 없으면 Pyxis 잡이 다음과 같이 실패합니다.
따라서 systemd-tmpfiles로 등록해 부팅 때마다 자동 생성 되게 합니다. (잊고 수동으로 한 번만 만들면, 노드 재부팅 후 다시 깨집니다.)
echo "d /run/enroot 1777 root root -" | sudo tee /etc/tmpfiles.d/enroot.conf
sudo systemd-tmpfiles --create /etc/tmpfiles.d/enroot.conf
지킬 것:
- 캐시·데이터는 노드 로컬 NVMe(
/opt/dlami/nvme)에 둡니다. 컨테이너 이미지는 수십 GB에 달하므로 루트 EBS에 두면 금방 가득 찹니다. ENROOT_RESTRICT_DEV n으로 두면 컨테이너 안에서/dev/infiniband(EFA)가 별도 마운트 지정 없이 그대로 보입니다. EFA 통신에 필요합니다.
6단계. Pyxis (Slurm ↔ Enroot 접착제)¶
Pyxis는 Slurm의 SPANK 플러그인으로 설치됩니다. 등록되면 srun에 --container-image 같은 옵션이 생기고, 잡이 뜰 때 Pyxis가 자동으로 Enroot 컨테이너를 띄웁니다.
# 빌드에는 Slurm 헤더(libslurm-dev)가 필요합니다.
git clone https://github.com/NVIDIA/pyxis.git && cd pyxis && sudo make install
# SPANK 플러그인 등록
echo "required /usr/local/lib/slurm/spank_pyxis.so" | sudo tee /etc/slurm/plugstack.conf.d/pyxis.conf
echo "include /etc/slurm/plugstack.conf.d/*" | sudo tee /etc/slurm/plugstack.conf
sudo systemctl restart slurmd
등록 확인:
7단계. 기본 동작 확인¶
여기까지 왔으면 스케줄러·컨테이너 레이어가 완성됩니다. 아래 순서로 점검합니다.
sinfo # 노드가 idle 상태로 보이는지
srun -N2 --ntasks-per-node=1 hostname # 노드 간 실행 (munge·PMIx 경로)
# Pyxis + Enroot 컨테이너 실행 (NGC 이미지)
srun -N2 --ntasks-per-node=1 --gres=gpu:8 \
--container-image=nvcr.io#nvidia/pytorch:<tag> \
bash -c 'hostname; nvidia-smi -L | wc -l; ls /dev/infiniband | head -3'
nvidia-smi -L이 노드당 8줄,/dev/infiniband에 EFA 장치가 보이면 컨테이너가 GPU·EFA를 정상적으로 집어낸 것입니다.--container-image의 레지스트리 표기는nvcr.io#nvidia/pytorch:<tag>형식(Pyxis 규약)입니다. 공유 스토리지의 sqsh 파일을 쓸 때는--container-image=/fsx/<path>/<image>.sqsh로 지정합니다.
검증 환경 (2026-10 기준)
컨테이너 이미지는 nvcr.io/nvidia/pytorch:26.08-py3(및 여기에 EFA installer 1.50.0을 추가한 이미지)로 검증했습니다. NGC 이미지에는 AWS EFA 스택이 이미 포함되어 있어, 2노드 컨테이너 통신에서 호스트와 동일한 성능이 나왔습니다(단, --cpus-per-task 지정 필수 — 아래 참고).
잡 제출¶
스케줄러·컨테이너 레이어가 서면, 실제 학습 잡은 sbatch/salloc + srun으로 제출합니다. 아래는 컨테이너 학습 잡의 기본형입니다.
#!/bin/bash
#SBATCH -N <노드 수>
#SBATCH --ntasks-per-node=8
#SBATCH --cpus-per-task=24
#SBATCH --gres=gpu:8
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -1)
export MASTER_PORT=29500
srun --container-image=/fsx/<path>/<image>.sqsh \
--container-mounts=/fsx:/fsx \
<학습 실행 스크립트>
구조:
- 노드당 GPU 8개에 8 rank(
--ntasks-per-node=8)를 띄우고, 각 rank가 GPU 1개를 씁니다. --gres=gpu:8로 노드당 GPU 8장을 요청합니다.- 마스터 노드 주소는 연속 번호를 가정하지 말고
scontrol show hostnames "$SLURM_JOB_NODELIST"로 받습니다.
반드시 지킬 두 가지 옵션¶
이 두 옵션은 빠뜨려도 잡이 "뜨긴" 하기 때문에 특히 놓치기 쉽습니다. 하나는 성능을 조용히 깎고, 하나는 잡을 즉시 죽입니다.
1. --cpus-per-task 를 반드시 지정
rank당 할당할 CPU 코어 수입니다. 지정하지 않으면 Slurm 설정에 따라 rank당 CPU 1코어만 배정될 수 있고, 이 경우 통신 성능이 기준값의 ¼~1/10 수준으로 떨어집니다. EFA는 멀쩡히 "타는데"(Using network Libfabric) 대역폭만 낮게 나오는, 원인을 찾기 어려운 증상입니다. 네트워크 스택이 패킷을 처리할 CPU가 부족하기 때문입니다.
2. srun --export 를 쓸 때는 ALL 을 함께
srun --export로 환경변수를 전달할 때는 반드시 ALL을 포함하십시오(--export=ALL,VAR=value,...). ALL 없이 변수만 나열하면 PATH 같은 기본 환경이 전달되지 않아, 잡이 다음과 같이 즉시 실패 합니다.
보조 규칙:
- 잡 스크립트 안에서
export로 설정하고--export를 생략해도 됩니다(기본값이ALL). --export값 안에는 쉼표를 쓸 수 없습니다(Slurm이 변수 구분자로 읽음). 예를 들어NCCL_DEBUG_SUBSYS=INIT,NET처럼 쉼표가 든 값은--export에 넣지 말고 잡 스크립트에서export로 설정하십시오.- 한 할당(
salloc/sbatch) 안에서 여러srun을 실행할 때는--container-name=<이름>으로 컨테이너를 재사용하면, 두 번째부터는 sqsh를 다시 풀지 않고 바로 시작합니다.
환경변수 (EFA + NCCL)¶
NGC 계열 이미지에는 AWS EFA 스택과 올바른 기본 환경변수가 이미 들어 있습니다. 대부분의 변수는 지정하지 않는 것이 정답 이며, 잘못 지정하면 TCP로 fallback하거나 초기화가 실패합니다.
| 변수 | 필요 여부 | 설명 |
|---|---|---|
NCCL_NET |
지정 불필요 | 이미지 기본값(NCCL_NET_PLUGIN=ofi)이 EFA 플러그인을 고릅니다. 굳이 지정하면 Libfabric(대소문자 무관)이어야 하고, IB를 지정하면 초기화가 실패, 이전 이름(AWS Libfabric)은 통신 결과가 나오지 않습니다. 두 값 모두 쓰지 마십시오. |
NCCL_NET_PLUGIN |
바꾸지 말 것 | 이미지 기본값 ofi 유지. 다른 값으로 바꾸면 IB용 플러그인을 가리켜 EFA 경로를 벗어납니다. |
FI_PROVIDER, FI_EFA_USE_DEVICE_RDMA |
지정 불필요 | 지정하지 않아도 EFA·GPUDirect RDMA가 정상 사용됩니다. 지정해도 무방. |
NCCL_DEBUG=INFO |
점검 시 | 잡 시작 로그에서 Using network Libfabric, found 8 nics를 확인할 때 켭니다. |
IB 환경의 NCCL_IB_*, NCCL_NET=IB 등 |
제거 | EFA 환경에서는 쓰지 않습니다. IB용 설정이 남아 있으면 EFA 경로를 벗어나 TCP로 떨어지거나 초기화가 실패합니다. |
- 핵심 원칙: EFA 환경에서는 NCCL 네트워크 관련 변수를 "건드리지 않는 것"이 기본 입니다. 다른 환경(IB)에서 가져온
NCCL_*설정을 그대로 쓰면 성능이 떨어지거나 잡이 실패합니다. - 점검 시
NCCL_DEBUG=INFO로그에서Using network Libfabric과provider is efa (... found 8 nics)가 보이면 EFA 경로입니다.Using network Socket이 보이면 TCP로 fallback한 것이므로(성능 급락)NCCL_NET_PLUGIN=ofi여부와 잔존 IB 설정을 점검하십시오.
검증 환경 (2026-10 기준)
- aws-ofi-nccl 1.19.0(NGC 26.08 내장)과 1.21.1(EFA installer 1.50.0)에서 동작을 확인했으며 1.21.1을 권장 합니다.
--cpus-per-task미지정 시 성능 하락 실측(8 GiB): all-reduce algbw 92 → 368 GB/s, all-to-all 8.7 → 95 GB/s (--cpus-per-task=24적용 시 복원).- 위 두 버전 모두
NCCL_NET을 지정하지 않아도 EFA 플러그인이 선택됩니다.
트러블슈팅 요약¶
이 레이어에서 실제로 자주 마주치는 증상과 조치입니다.
| 증상 | 원인 | 조치 |
|---|---|---|
Slurm 노드가 down, 통신 오류, getent hosts $(hostname)이 fe80::... |
자기 호스트 이름이 IPv6 링크 로컬로 풀림 | slurm.conf에 NodeAddr(IPv4) 명시 또는 /etc/hosts 등록 (1단계) |
노드가 down, RealMemory 관련 |
실제 가용 메모리 < 설정값 | RealMemory를 slurmd -C 값보다 약간 작게 (3단계) |
Pyxis 잡이 mkdir: cannot create directory '/run/enroot': Permission denied |
재부팅으로 /run/enroot 사라짐 |
tmpfiles로 생성 보장 (5단계) |
slurmstepd: error: execve(): bash: No such file or directory |
--export에 ALL 없음 |
--export=ALL,... (잡 제출) |
EFA는 타는데(Using network Libfabric) 통신이 기준값의 ¼~1/10 |
rank당 CPU 1코어 | --cpus-per-task=24 (잡 제출) |
NCCL 로그에 Using network Socket |
EFA 플러그인 미로드 / TCP fallback | NCCL_NET_PLUGIN이 ofi인지, IB용 NCCL_* 잔존 여부 확인 |
NCCL_NET=IB 등으로 통신 초기화 실패 |
EFA 환경에 IB 경로 강제 | IB 설정 제거 (잡 제출) |
컨테이너 안에서 /dev/infiniband가 안 보임 |
cgroup.conf 디바이스 격리 또는 ENROOT_RESTRICT_DEV 설정 |
ConstrainDevices 시 디바이스 가시성 확인(3단계), ENROOT_RESTRICT_DEV n(5단계) |
분산 학습 검증 (nccl-tests)¶
클러스터의 노드·네트워크·컨테이너 구성을 모두 마쳤다면, 실제 학습을 시작하기 전에 노드 간 집합 통신(collective communication) 성능이 기대 수준으로 나오는지 반드시 확인해야 합니다. 아래는 그 최종 검증 게이트 입니다. 여기서 통과하지 못하면(no-go) 학습을 시작해도 네트워크 병목으로 성능이 나오지 않거나 학습이 비정상적으로 느려집니다.
이 단계에서 수행하는 것:
- 2노드 all-reduce / all-to-all 대역폭 측정 (호스트 nccl-tests)
- 노드 간 지연 측정 (
fi_pingpong) - 컨테이너(학습 이미지) 안에서의 통신 측정 (PyTorch 기반, MPI 충돌 회피)
- nccl-tests 로그를 읽고 EFA 경로 여부·성능을 판정하는 방법
왜 이 게이트가 필요한가¶
대규모 GPU 학습에서 각 스텝은 GPU 연산 + 노드 간 통신 으로 이루어집니다. 노드 안(GPU 8개)은 NVLink로, 노드 사이는 EFA로 연결됩니다. 노드 간 EFA 대역폭은 인스턴스당 약 3.2 Tbps(약 400 GB/s), GPU 8개로 나누면 GPU당 약 50 GB/s 로, NVLink(GPU당 한 방향 수백 GB/s)와 큰 차이가 납니다. 즉 노드 수가 늘수록 노드 간 통신이 전체 처리량을 좌우하므로, 그 경로가 설계 대역폭에 가깝게 동작하는지 를 학습 전에 확인해야 합니다.
이 검증이 중요한 또 다른 이유는, 통신 성능 저하의 원인 대부분이 학습을 돌려 보기 전에는 드러나지 않는 설정 문제 이기 때문입니다. 대표적으로:
- NCCL이 EFA 플러그인을 못 잡아 TCP(Socket)로 fallback → 성능이 수분의 1로 떨어짐
- rank당 CPU가 1코어만 배정되어 통신 성능이 ¼~1/10로 저하
- 보안그룹 self-reference 규칙 누락으로 EFA 통신 자체가 실패
- IB용 환경변수가 남아 있어 EFA가 선택되지 않음
이 게이트에서 기준선(baseline) 수치를 확보해 두면, 이후 운영 중 성능이 떨어졌을 때 "원래 이 환경에서 얼마가 나왔는지"와 비교할 수 있습니다. 그래서 측정값은 반드시 기록해 두어야 합니다.
검증 순서 (권장)¶
통신 경로를 아래 → 위 순서로 하나씩 좁혀 가며 검증합니다. 아래 단계에서 문제가 있으면 위 단계는 의미가 없으므로, 반드시 순서대로 진행합니다.
- 호스트 EFA·NCCL 기준선 — DLAMI 내장 nccl-tests로 2노드 이상 all-reduce·all-to-all 측정 (Slurm
srun --mpi=pmix) - 컨테이너 NCCL 경로 — 학습 이미지로 PyTorch 측정 스크립트를 실행해 호스트와 비슷한지 확인 (
--cpus-per-task필수) - 노드 간 지연 —
fi_pingpong으로 소형 메시지 왕복 지연 측정 - (선택) EP 디스패처 확인 — MoE를 쓰는 경우 Expert Parallelism 백엔드 단독 테스트
- 대상 모델·병렬화 구성으로 짧은 학습 — 몇십 iteration만 돌려 iteration 시간을 기준선으로 기록
- (용량 블록이 여러 개로 나뉘었다면) 토폴로지를 확인한 뒤 블록 안·블록 사이를 각각 측정해 비교
아래는 1~4단계(순수 통신 검증)에 집중합니다.
1. 호스트 2노드 nccl-tests (EFA 기준선)¶
가장 먼저 컨테이너 없이 호스트에서 nccl-tests를 돌려 EFA·NCCL 경로가 정상인지 확인합니다. 호스트에는 드라이버·EFA 스택·nccl-tests가 사전 설치되어 있어, 컨테이너 변수를 배제한 "순수한" 기준선을 얻을 수 있습니다.
- nccl-tests는 CUDA 버전별로 설치되어 있습니다. 노드당 8 rank, rank당 GPU 1개(
-g 1)로 실행합니다. - 노드 간 SSH 없이
srun --mpi=pmix로 실행합니다. 이는 Slurm이 PMIx 지원으로 빌드된 경우에만 가능합니다 —srun --mpi=list에pmix가 보이는지 먼저 확인하십시오. --cpus-per-task를 반드시 지정하십시오. 지정하지 않으면 rank당 CPU 1코어만 배정될 수 있고, 이때 통신 성능이 크게 떨어집니다.
검증 환경 (2026-10 기준)
아래 경로·버전·수치는 특정 시점에 P6-B200 2노드 환경에서 측정한 참고 레퍼런스 입니다. 버전이 바뀌면 이 박스만 갱신하면 됩니다.
- DLAMI: Deep Learning Base OSS Nvidia Driver GPU AMI (Ubuntu 24.04)
- nccl-tests 경로:
/usr/local/cuda-XX/efa/test-cuda-XX/(CUDA 12.8·12.9·13.0·13.2 각각 제공) - 드라이버 기준 CUDA 13.2, EFA installer 1.50.0 (libfabric 2.6.0, aws-ofi-nccl 1.21.1, PMIx 4.2.8)
- Slurm 23.11.4 (PMIx v5 지원), Pyxis 0.24.0, Enroot 4.2.1
- 아래 예시 명령의
cuda-13.2는 실제 설치된 CUDA 버전에 맞춰 바꾸십시오.
TESTDIR=/usr/local/cuda-13.2/efa/test-cuda-13.2
LIBS=/usr/local/cuda-13.2/lib:/usr/local/cuda-13.2/lib64:/opt/amazon/efa/lib:/opt/amazon/ofi-nccl/lib:/opt/amazon/openmpi/lib
# all-reduce (데이터 병렬 통신 패턴)
srun -N2 --ntasks-per-node=8 --cpus-per-task=24 --gres=gpu:8 --mpi=pmix \
--export=ALL,LD_LIBRARY_PATH=$LIBS,NCCL_DEBUG=INFO \
$TESTDIR/all_reduce_perf -b 8 -e 16G -f 2 -g 1 -c 1 -n 50
# all-to-all (MoE expert 병렬 통신 패턴)
srun -N2 --ntasks-per-node=8 --cpus-per-task=24 --gres=gpu:8 --mpi=pmix \
--export=ALL,LD_LIBRARY_PATH=$LIBS,NCCL_DEBUG=INFO \
$TESTDIR/alltoall_perf -b 8 -e 16G -f 2 -g 1 -c 1 -n 50
플래그 의미:
-b 8 -e 16G: 메시지 크기를 8바이트부터 16 GiB까지 스윕-f 2: 크기를 2배씩 증가-g 1: rank당 GPU 1개 (rank는--ntasks-per-node=8로 이미 노드당 8개)-c 1: 정확성 검사 수행-n 50: 각 크기당 50회 반복 측정
--export에는 반드시 ALL을 함께 쓰십시오
srun --export에 변수만 나열하면 PATH 등이 전달되지 않아 잡이 바로 실패할 수 있습니다. 반드시 --export=ALL,VAR=value,... 형태로 쓰거나, 잡 스크립트에서 export로 설정하고 --export를 생략하십시오(기본값이 ALL). 또한 --export 값 안에는 쉼표를 쓸 수 없습니다(Slurm이 변수 구분자로 읽음).
1노드 사전 확인 (NVLink만)¶
2노드로 넘어가기 전에, 1노드 all-reduce로 GPU·드라이버·NCCL 자체가 정상인지 먼저 확인하면 문제 범위를 좁히는 데 도움이 됩니다. 1노드는 노드 안 NVLink만 사용하므로 EFA와 무관하게 GPU 스택의 건전성을 봅니다.
TESTDIR=/usr/local/cuda-13.2/efa/test-cuda-13.2
/opt/amazon/openmpi/bin/mpirun -n 8 --bind-to none \
-x LD_LIBRARY_PATH=/usr/local/cuda-13.2/lib:/usr/local/cuda-13.2/lib64:/opt/amazon/efa/lib:/opt/amazon/ofi-nccl/lib:$LD_LIBRARY_PATH \
-x NCCL_DEBUG=INFO \
$TESTDIR/all_reduce_perf -b 8 -e 1G -f 2 -g 1 -c 1 -n 50
2. 컨테이너 2노드 통신 (PyTorch 측정)¶
호스트 기준선이 확인되면, 실제 학습에 쓸 컨테이너 이미지 안 에서도 같은 성능이 나오는지 검증합니다. 이미지 안의 EFA 스택·NCCL·플러그인 설정이 호스트와 다를 수 있기 때문입니다.
컨테이너 안에서는 nccl-tests를 MPI로 돌리지 마십시오
학습용 NGC 계열 이미지에는 자체 Open MPI가 들어 있습니다. EFA installer의 Open MPI로 빌드한 프로그램(nccl-tests 포함)을 컨테이너 안에서 MPI로 실행하면 두 Open MPI가 섞여 opal_shmem_base_select failed → MPI_INIT failed로 실패합니다. 따라서 컨테이너 안 통신 확인은 MPI가 필요 없는 PyTorch 기반 측정 으로 수행합니다. 이 스크립트는 Slurm 환경변수(SLURM_PROCID 등)로 rank를 잡아 MPI 없이 분산 그룹을 초기화합니다.
측정 스크립트를 공유 스토리지에 두고, 학습 이미지로 실행합니다.
salloc -N2 --ntasks-per-node=8 --cpus-per-task=24 --gres=gpu:8
srun --container-image=/<shared-path>/<image>.sqsh --container-name=train --container-mounts=/<shared-path>:/<shared-path> \
--export=ALL,MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -1),MASTER_PORT=29500,NCCL_DEBUG=INFO \
python /<shared-path>/torch_comm_bench.py
참고 측정 스크립트 — torch_comm_bench.py
MPI 없이 Slurm 환경변수로 rank를 잡는 PyTorch 측정 스크립트입니다. busbw 계산식은 nccl-tests와 동일합니다. 특정 환경을 위한 참고 예시이므로, 라이브러리 버전·측정 범위는 실제 환경에 맞춰 조정하십시오.
import os, time, torch, torch.distributed as dist
rank = int(os.environ["SLURM_PROCID"])
world = int(os.environ["SLURM_NTASKS"])
local = int(os.environ["SLURM_LOCALID"])
torch.cuda.set_device(local)
dist.init_process_group("nccl", rank=rank, world_size=world,
device_id=torch.device("cuda", local))
sizes = [2**k for k in range(20, 34)]
for op in ["all_reduce", "all_to_all"]:
for nbytes in sizes:
x = torch.ones(nbytes // 4, dtype=torch.float32, device="cuda")
y = torch.empty_like(x)
def run():
if op == "all_reduce":
dist.all_reduce(x)
else:
dist.all_to_all_single(y, x)
for _ in range(5):
run()
torch.cuda.synchronize()
dist.barrier()
iters = 20
t0 = time.time()
for _ in range(iters):
run()
torch.cuda.synchronize()
dt = (time.time() - t0) / iters
algbw = nbytes / dt / 1e9
busbw = algbw * 2 * (world - 1) / world if op == "all_reduce" else algbw * (world - 1) / world
if rank == 0:
print(f"{op:10s} {nbytes:>12d} B time {dt*1e3:9.2f} ms "
f"algbw {algbw:7.1f} GB/s busbw {busbw:7.1f} GB/s", flush=True)
del x, y
dist.destroy_process_group()
rank당 CPU를 충분히 배정하십시오
--cpus-per-task를 지정하지 않으면 rank당 CPU 1코어만 배정될 수 있고, 이때 EFA가 정상 동작해도 통신 성능이 ¼~1/10로 떨어집니다. 한 측정에서 8 GiB all-reduce가 algbw 92 → 368 GB/s, all-to-all이 8.7 → 95 GB/s로 바뀐 사례가 있습니다. 인스턴스의 vCPU를 GPU 수로 나눈 값(예: 192 vCPU ÷ 8 GPU = 24)을 지정하십시오.
3. 노드 간 지연 (fi_pingpong)¶
대역폭과 별개로, 소형 메시지의 왕복 지연(latency) 도 측정해 둡니다. all-reduce는 대역폭에 민감하지만, 작은 메시지가 많은 통신 패턴은 지연에 민감합니다.
salloc -N2 --ntasks-per-node=1
srun --overlap -N1 -n1 -w <node-A> /opt/amazon/efa/bin/fi_pingpong -p efa -e rdm -I 10000 -S 64 &
sleep 5
srun --overlap -N1 -n1 -w <node-B> /opt/amazon/efa/bin/fi_pingpong -p efa -e rdm -I 10000 -S 64 <node-A IP>
wait
fi_pingpong의 기본 endpoint는 dgram이므로-e rdm을 지정합니다.-I는 반복 횟수,-S는 메시지 크기(바이트)입니다.- 서버 측을 먼저 띄우고(
&), 잠깐 기다린 뒤 클라이언트 측에서 서버 IP를 지정해 접속합니다.
4. 기준값과 판정¶
측정이 끝나면 아래 기준값과 비교하고, 실 환경 측정값을 기준선으로 기록 해 둡니다.
검증 환경 (2026-10 기준) — 참고 기준값
아래 수치는 특정 시점에 P6-B200 2노드 환경에서 측정한 참고값이며, 성능을 보장하는 값이 아닙니다. 노드 수, 모델, 병렬화 구성, 소프트웨어 버전에 따라 달라지므로 실 환경 측정값을 기준선으로 삼으십시오.
| 측정 | 참고 기준값 |
|---|---|
| 호스트 all-reduce (16 GiB) | algbw 374 GB/s, busbw 702 GB/s |
| 호스트 all-to-all (16 GiB) | algbw 95 GB/s, busbw 89 GB/s |
| 컨테이너 all-reduce / all-to-all (8 GiB, PyTorch) | algbw 367~368 / 95 GB/s |
fi_pingpong 64 B |
10.85 µs/xfer |
- 참고로 노드당 EFA 대역폭은 약 400 GB/s이며, 위 all-reduce algbw 374 GB/s는 그 약 94% 수준입니다.
- all-to-all algbw 95 GB/s는 GPU당 노드 간 트래픽으로 환산하면 약 48 GB/s로, GPU당 EFA 몫(약 50 GB/s)의 약 95% 수준입니다.
판정 지침:
- 노드를 늘려 측정할 때 값이 크게 떨어지면, 노드를 절반씩 나눠 다시 측정해 문제 노드를 좁힙니다. 문제 노드는 종료 후 같은 런치 템플릿으로 다시 기동해 교체합니다.
- 용량 블록이 여러 개로 나뉘어 있으면 블록 안 노드 쌍과 블록 사이 노드 쌍을 각각 측정해 비교하십시오. 블록 사이는 네트워크 구간(spine)이 달라 성능이 다를 수 있습니다.
5. nccl-tests 결과 읽는 법¶
NCCL_DEBUG=INFO를 켜고 실행한 뒤, 아래 순서로 확인합니다.
① 계산이 맞았는가 — 오류 수¶
0이어야 합니다. 결과 표의 #wrong 열도 모두 0이어야 합니다. 0이 아니면 성능과 관계없이 실패 입니다(no-go).
② 어떤 네트워크를 탔는가 — 로그 앞부분¶
NET/OFI Initializing aws-ofi-nccl <버전>
NET/OFI Selected provider is efa, fabric is efa-direct (found 8 nics)
Using network Libfabric
Using network Libfabric와provider is efa가 보여야 EFA 경로입니다.found 8 nics(EFA 8개)가 보여야 모든 네트워크 카드를 쓰는 것입니다.Using network Socket이 보이면 TCP로 fallback한 것으로, 성능이 크게 떨어집니다. 이 경우 아래 트러블슈팅을 참고하십시오.- GPUDirect RDMA 사용은 로그의
via NET/Libfabric/.../GDRDMA로 확인합니다.
③ 성능 — 결과 표의 algbw·busbw¶
| 열 | 의미 |
|---|---|
| size | 한 번에 보내는 데이터 크기(바이트) |
| time | 걸린 시간(마이크로초) |
| algbw | 데이터 크기 ÷ 시간 (알고리즘 대역폭) |
| busbw | algbw를 통신 패턴에 맞게 보정한 값. 하드웨어 대역폭과 비교할 수 있는 지표 |
| 왼쪽 / 오른쪽 | out-of-place / in-place 결과 (보통 비슷함) |
- 큰 메시지(수백 MiB~GiB) 줄 을 봅니다. 작은 메시지는 지연 시간 때문에 대역폭이 낮게 나오는 것이 정상입니다.
- 마지막의
Avg bus bandwidth는 모든 크기의 평균이라 참고용입니다.
④ 무엇과 비교하는가¶
- 1노드: 노드 안 NVLink만 사용합니다. GPU·드라이버·NCCL이 정상인지 확인하는 용도입니다.
- 2노드 이상: EFA를 거칩니다. ②에서 EFA 경로인지 먼저 확인한 뒤 성능을 봅니다.
- all-reduce는 NCCL이 NVLS tree(노드 안 합산은 NVLink Switch, 노드 간은 EFA)를 쓰면 busbw가 1노드와 비슷하게 나올 수 있습니다. 이때는 algbw가 노드당 EFA 대역폭에 얼마나 가까운지 로 판단합니다.
- all-to-all은 노드 간 트래픽 비중이 커서 크게 낮게 나오는 것이 정상입니다. GPU당 노드 간 트래픽(2노드면 algbw의 절반)을 GPU당 EFA 몫(약 50 GB/s)과 비교합니다.
- 기준값(4절)과 비교하고, 실 환경 측정값을 기준선으로 기록해 두십시오.
트러블슈팅¶
| 증상 | 원인 | 조치 |
|---|---|---|
NCCL 로그에 Using network Socket |
EFA 플러그인 미로드(TCP fallback) | 이미지의 NCCL 네트워크 플러그인이 EFA(ofi)용인지, IB용 NCCL_* 설정이 남아 있지 않은지 확인 |
NCCL_NET=IB 등으로 통신 초기화 실패 |
IB 경로 강제 | IB용 환경변수(NCCL_NET=IB, NCCL_IB_*) 제거. EFA 환경에서는 NCCL_NET을 지정하지 않는 것이 안전 |
EFA는 타는데(Libfabric) 성능이 기준값의 ¼~1/10 |
rank당 CPU 1코어 | --cpus-per-task를 vCPU ÷ GPU 수로 지정 |
slurmstepd: error: execve(): bash: No such file or directory |
--export에 ALL 없음 |
--export=ALL,...로 수정 |
컨테이너 안 MPI 프로그램이 opal_shmem_base_select failed |
이미지의 Open MPI와 다른 Open MPI가 섞임 | 컨테이너 안 통신 확인은 MPI 없는 PyTorch 측정으로 수행 |
--mpi=pmix가 동작하지 않음 |
Slurm이 PMIx 지원으로 빌드되지 않음 | srun --mpi=list로 pmix 지원 확인. Slurm을 직접 빌드하면 --with-pmix 사용 |
/sys/class/infiniband/나 컨테이너 /dev/infiniband에 EFA 외 다른 장치가 보임 |
일부 인스턴스에는 링크 DOWN 상태의 추가 RDMA 장치가 보임(EFA와 별개) | 조치 불필요. NCCL이 EFA를 선택하면 됨(Using network Libfabric, found 8 nics). IB 경로를 강제하지 말 것 |
#wrong이 0이 아님 / Out of bounds values가 0이 아님 |
데이터 정확성 실패 | 성능과 무관하게 no-go. 하드웨어·드라이버·메모리 점검 |
체크리스트¶
-
srun --mpi=list에pmix지원 확인 - 1노드 all-reduce 정상 (GPU·드라이버·NCCL 건전성)
- 호스트 2노드 all-reduce·all-to-all 측정 (
--cpus-per-task지정) - NCCL 로그에
Using network Libfabric,found 8 nics확인 (EFA 경로) -
#wrong/Out of bounds values모두 0 - 컨테이너(학습 이미지) 2노드 측정이 호스트와 비슷함 (PyTorch 측정)
-
fi_pingpong노드 간 지연 측정 - 실 환경 측정값을 기준선으로 기록
📎 참고 공식 문서: Get started with EFA and NCCL · nccl-tests ·
fi_pingpong(1)· NCCL 환경변수
체크리스트¶
- 노드 호스트 이름이 IPv4로 해석됨 —
NodeAddr//etc/hosts(1단계) - 모든 노드가 동일한
/etc/munge/munge.key(소유자 munge, 권한 400), 공유 사본 삭제 (2단계) -
slurm.conf노드 값이slurmd -C와 일치,gres.confGPU 등록 (3단계) -
srun --mpi=list에pmix(4단계) -
/etc/enroot/enroot.conf설정, 캐시·데이터는 로컬 NVMe,/run/enroottmpfiles 등록 (5단계) - Pyxis SPANK 플러그인 등록,
srun --help에--container-image(6단계) - 2노드 컨테이너 실행에서 GPU 8개·
/dev/infiniband확인 (7단계) - 잡 제출:
--cpus-per-task지정,--export=ALL,...,--gres=gpu:8(잡 제출) -
NCCL_DEBUG=INFO로그에Using network Libfabric·found 8 nics(잡 제출)