<ML>

Kubernetes + PyTorch DDP에서 NCCL ALLREDUCE 타임아웃 해결기

9566 2025. 11. 13. 19:38
728x90

(NCCL_P2P_DISABLE / NCCL_IB_DISABLE 2줄로 해결)

 

최근 Kubernetes GPU 노드에서 PyTorch DDP(DistributedDataParallel) 학습을 돌리던 중,

GPU 2장이 모두 정상적으로 할당되고 nvidia-smi도 잘 찍히는데,

항상 일정 시간이 지나면 NCCL ALLREDUCE 타임아웃이 발생하면서 학습이 죽어버리는 문제가 있었다.

 

로그는 아래처럼 폭발적으로 길어지며 결국 DistBackendError로 종료된다.

Watchdog caught collective operation timeout:
WorkNCCL(SeqNum=1, OpType=ALLREDUCE)

c10::DistBackendError: watchdog thread terminated with exception
process 1 terminated with signal SIGABRT

처음엔 코드 문제(샘플러, 에폭 루프, barrier, DataLoader 워커 수 등)를 다 확인했지만

결정적인 원인은 NCCL 통신 경로 자체가 환경과 충돌하는 문제였다.

 

🔍 문제 원인 요약

우리가 사용한 환경은:

  • 단일 노드
  • GPU 2장 (RTX PRO 6000 Black)
  • Kubernetes Pod 1개에 nvidia.com/gpu: 2 할당
  • 외부 네트워크 통신 없이 로컬 IPC로만 통신해도 충분한 구조

 

즉, InfiniBand도 없고, GPU 간 P2P(Not NVLink)도 사실 필요 없는 환경이었다.

그런데 NCCL은 기본적으로 다음을 시도한다:

  1. GPU 간 P2P(NVLink) 통신
  2. InfiniBand / RDMA
  3. 소켓 기반 통신
  4. SHM 기반 IPC

 

여기서 **걸리는 경로(=지원되지 않거나, 지연이 발생하거나, 인터페이스가 꼬이는 경우)**가 발생하면

collective op(ALLREDUCE)의 타이밍이 뭉개지고, 결국 watchdog이 타임아웃을 때린다.

 


 

✨ 해결: 환경 변수 2줄 추가

결론적으로 문제는 아래 두 줄로 완전히 해결되었다.

    os.environ["NCCL_P2P_DISABLE"] = "1"  # GPU 간 P2P 통신 비활성화
    os.environ["NCCL_IB_DISABLE"] = "1"   # InfiniBand 비활성화

즉,

  • 단일 노드지만 NVLink가 없는 GPU들끼리의 P2P를 NCCL이 억지로 시도하다가 끊김 → 타임아웃
  • InfiniBand가 없는 환경에서 IB path 스캔 중 지연 발생 → 타임아웃

을 방지했다는 뜻이다.

 

이 두 옵션을 비활성화하자마자:

  • NCCL이 SHM/Socket 기반으로 깨끗하게 고정
  • 통신이 안정화
  • ALLREDUCE 타임아웃 완전 소멸
  • 2GPU DDP 학습이 정상적으로 끝까지 진행

정말 “두 줄의 기적”이었다.

 

 

🧪 수정 코드 (초기 DDP setup 직전)

 

전체 코드 구조는 유지하고, main_worker() 또는 setup_distributed() 가장 앞쪽에 아래처럼 넣으면 된다.

# --- 핵심 해결 코드 ---
os.environ["NCCL_P2P_DISABLE"] = "1"  # GPU 간 P2P 통신 비활성화
os.environ["NCCL_IB_DISABLE"] = "1"   # InfiniBand 비활성화
# ------------------------

dist.init_process_group(backend="nccl")
torch.cuda.set_device(local_rank)

Kubernetes 환경에서는 Container spec 안에 다음처럼 추가해도 동일하게 동작한다.

env:
- name: NCCL_P2P_DISABLE
  value: "1"
- name: NCCL_IB_DISABLE
  value: "1"

 

🧠 왜 이 두 줄이 효과가 있었을까?

 

정리하자면:

NCCL 기능우리 환경결과

P2P (NVLink) RTX PRO 6000 Black — NVLink 없음 NCCL이 P2P 경로를 시도 → 실패/지연
InfiniBand 없음 NCCL이 IB 스캔하다가 초기화 지연
Socket/SHM 있음 (정상) 우리가 원하는 통신 방식

따라서 “쓰지 않는 기능을 강제로 끄는 것”이 오히려 더 안전했다.

 

 

🚀 최종 결과

 

  • NCCL ALLREDUCE 타임아웃 100% 해결
  • rank0/rank1 모두 안정적으로 에폭 종료
  • GPU 2장 DDP 학습 정상 진행
  • 학습 속도와 GPU 활용률이 일정하게 유지됨
  • 종료 시 DistBackendError / SIGABRT도 더 이상 발생 안 함

 

결론은 아주 명확하다.

단일 노드에서 P2P(NVLink 없음) + InfiniBand 없는 환경이라면
NCCL_P2P_DISABLE=1 / NCCL_IB_DISABLE=1 을 기본값으로 깔고 가는 게 더 좋다.

 

 

📌 비슷한 문제를 겪는 분들에게

 

만약 다음과 같은 증상을 겪고 있다면:

 

  • DDP에서 ALLREDUCE 타임아웃 발생
  • rank 간 collective 불일치 에러
  • watchdog이 collective를 죽였다는 메시지 출력
  • DistBackendError
  • SIGABRT, SIGKILL로 종료
  • GPU는 잘 보이는데 통신이 계속 불안정함
  • NCCL이 “work sequence id 1”에서 멈춰 있음

 

가장 먼저 아래 두 줄을 시도해보길 추천한다.

os.environ["NCCL_P2P_DISABLE"] = "1"
os.environ["NCCL_IB_DISABLE"] = "1"

10분짜리 에러 로그를 한 번에 종결할 수 있는 해결책이었다.

 

728x90