gpu-zombie-test/results/comparison.md

13 KiB
Raw Permalink Blame History

시나리오 1 — 5가지 변형 비교 분석 보고서

목적: GPU 좀비/고아/orphaned CUDA context 재현 시도 5가지 변형의 결과 비교 결론 미리보기: orphan CUDA context는 모든 변형에서 미발생 (driver/cgroup이 자동 회수). 진짜 문제는 ① 30초 grace period 낭비 ② NCCL hang으로 인한 GPU 점유.


1. 5개 변형 한눈에 보기

# 디렉토리 노드 GPU Driver 유저 Worker 특이사항
1 scenario-1/ gpu-4 A100 80GB 590.48.01 root 1개 (torch) 기본 시나리오
2 scenario-1-driver-580.126.09/ v1003 V100 32GB 580.126.09 root 1개 (raw ctypes) 구버전 driver 비교
3 scenario-1-user-soo/ gpu-4 A100 80GB 590.48.01 UID 1000 1개 (torch) non-root 권한
4 scenario-1-user-soo-10workers/ gpu-4 A100 80GB 590.48.01 UID 1000 10개 (torch) 다중 worker
5 scenario-1-nccl-3gpu/ gpu-4 A100 80GB ×3 590.48.01 UID 1000 3 NCCL ranks 멀티 GPU 분산

2. 환경 비교

항목 #1 root #2 driver 580 #3 soo 1w #4 soo 10w #5 NCCL 3GPU
노드 gpu-4 v1003 gpu-4 gpu-4 gpu-4
GPU 모델 A100 SXM4 80GB V100 SXM2 32GB A100 80GB A100 80GB A100 80GB
GPU 수 (요청) 1 1 1 1 3
Driver 590.48.01 580.126.09 590.48.01 590.48.01 590.48.01
CUDA 13.1 12.4 13.1 13.1 13.1
컨테이너 유저 root (UID 0) root UID 1000 UID 1000 UID 1000
GPU 할당 방식 torch.randn raw ctypes torch.randn torch.randn torch.randn + NCCL
Worker 개수 1 1 1 10 3 (각 다른 GPU)
terminationGracePeriodSeconds 30 30 30 30 30

#2의 raw ctypes 사용 이유: V100(CC 7.0)이 현재 이미지의 torch(CC 7.5+ 빌드)와 비호환. CUDA driver API 직접 호출로 우회.


3. 기본 동작 비교 — Step 2 (worker spawn 후 상태)

항목 #1 #2 #3 #4 #5
PID 1 sleep sleep sleep sleep sleep
좀비 발생 (kill 전)
고아 발생 (PPID=1) 1개 1개 1개 10개 3개
GPU 메모리 점유 2466 MiB 2356 MiB 2466 MiB 24702 MiB 5691 MiB (1897×3)
nvidia-smi PID 노드 PID 컨테이너 PID 노드 PID 노드 PID 노드 PID
observe.sh PID 일치 오탐지 일치 오탐지 오탐지 오탐지
/dev/nvidia fd worker만 worker만 worker만 10개 모두 3개 × 모든 GPU

흥미로운 발견 ①: nvidia-smi PID namespace 차이

  • torch 사용 시: nvidia-smi가 노드 PID(예: 537847)를 반환 → 컨테이너 안의 /proc/537847 못 찾음 → observe.sh가 ORPHANED 오탐지
  • raw ctypes 사용 시: nvidia-smi가 컨테이너 PID(예: 50)를 반환 → 일치 → 정확한 판정
  • 원인 추정: torch가 내부적으로 다른 namespace에서 CUDA context를 초기화하는 것으로 보임

흥미로운 발견 ②: NCCL의 P2P context

  • 3 GPU NCCL 환경에서 각 rank는 자기 GPU + 다른 모든 GPU의 fd를 보유
  • /dev/nvidia0 → 95 96 97 식으로 모든 rank가 모든 device에 fd 등록
  • 한 rank 죽어도 다른 rank들이 P2P context를 들고 있어서 그 GPU에 약간의 메모리 잔존

4. kill -9 후 동작 비교 (Step 3)

항목 #1 #2 #3 #4 #5
kill 권한 (sudo 필요?) n/a (root) n/a (root) 불필요 불필요 불필요
kill 후 좀비 1개 2개 (이전 잔존 포함) 1개 10개 1개 (rank 0만)
GPU 메모리 해제 즉시 0 즉시 0 즉시 0 즉시 0 ⚠️ 부분 해제
GPU 메모리 (kill 후) 0 MiB 0 MiB 0 MiB 0 MiB 148 MiB 잔존 (peer ctx)
GPU 해제 시간 <1초 <1초 <1초 <1초 (24GB 한꺼번에) <1초
orphan CUDA context 미발생 미발생 미발생 미발생 ⚠️ 부분 발생 (148 MiB)
다른 worker 영향 n/a n/a n/a n/a (독립) ⚠️ rank 1, 2 NCCL hang

핵심 발견: orphan CUDA context는 거의 발생 안 함

  • 단일 프로세스 kill: driver가 즉시 회수 → 0 MiB
  • 10 프로세스 동시 kill: driver가 일괄 회수 → 24GB → 0 MiB (1초 이내)
  • NCCL 멀티 GPU: 유일하게 부분 잔존 (148 MiB) — peer context가 살아있는 다른 rank에 의존

Non-root 권한 검증 (#3, #4, #5)

사용자 의문: "non-root는 sudo 없이 kill 안 되지 않나?"

답: 같은 UID끼리는 sudo 없이 kill 가능 (Linux 표준)

  • exec UID 1000 → worker UID 1000 → kill -9 exit 0
  • sudo 필요한 경우: 다른 UID 프로세스를 kill 시도할 때

5. Pod 삭제 동작 비교 (Step 3-B) — 가장 중요한 비교

worker가 살아있는 상태에서 kubectl delete pod을 실행한 결과.

항목 #1 #2 #3 #4 #5
Pod 삭제 명령 → 완료 31초 32초 31초 32초 32초
GPU 점유 지속 시간 ~30초 ~30초 ~30초 ~30초 ~30초
점유된 GPU 메모리 2466 MiB 2356 MiB 2466 MiB 24702 MiB 5691 MiB
GPU·sec 낭비 73 GB·sec 71 GB·sec 73 GB·sec 740 GB·sec 170 GB·sec
SIGTERM 효과 무시 무시 무시 무시 무시
SIGKILL 후 GPU 해제 <1초 <1초 <1초 <1초 (24GB) <1초 (5.7GB)
최종 GPU 잔존 0 MiB 0 MiB 0 MiB 0 MiB 0 MiB
최종 좀비/고아 0 0 0 0 0
노드 재부팅 필요

결정적 패턴 (모든 변형 공통)

kubectl delete pod
    ↓ kubelet SIGTERM → PID 1
        worker가 SIG_IGN → 무반응
    ↓ terminationGracePeriodSeconds = 30s 카운트다운
    ↓ 30초간 GPU 메모리 점유 지속 ⚠️
    ↓ SIGKILL → cgroup 전체 강제 종료
    ↓ <1초 이내 모든 GPU 해제 ✅

가장 큰 GPU 낭비 (#4)

  • 10 worker × 30초 동안 24.7 GB 점유 = 740 GB·초 낭비
  • 분산 학습 환경에서 매 배포/스케일링마다 발생 → 누적 영향 큼

6. NCCL 멀티 GPU의 특수 케이스 (#5만 발생)

다른 시나리오와 달리 두 가지 추가 문제 발생:

A. NCCL hang으로 인한 GPU 자원 누수

시점 rank 0 rank 1 rank 2 GPU 0 GPU 2 GPU 3
Step 2 alive alive alive 1897 1897 1897
rank 0 kill 직후 dead (좀비) alive (hang) alive (hang) 148 ⚠️ 1897 ⚠️ 1897 ⚠️
30초 후 좀비 여전히 hang 여전히 hang 148 1897 1897
  • rank 1, 2가 NCCL all_reduce에서 무한 hang
  • 살아있으니 GPU 메모리 못 풀음 → 5.7 GB 영구 점유 (Pod 삭제 전까지)
  • NCCL timeout 미설정 시 영원히 자원 낭비

B. Peer context 잔존 (148 MiB)

  • rank 0이 죽었지만 rank 1, 2가 GPU 0에 P2P 통신용 context 보유
  • driver가 GPU 0의 메모리 일부(148 MiB)를 풀지 못함
  • 살아있는 rank들이 죽어야 비로소 해제됨

NCCL 권장 설정 (시나리오 4 변형으로 검증 필요)

os.environ["NCCL_ASYNC_ERROR_HANDLING"] = "1"
dist.init_process_group(
    backend="nccl",
    timeout=timedelta(seconds=60)   # 60초 후 abort
)

def cleanup(signum, frame):
    dist.destroy_process_group()  # NCCL 정리
    torch.cuda.empty_cache()
    sys.exit(0)
signal.signal(signal.SIGTERM, cleanup)

7. Driver 버전 영향 비교 (#1 vs #2)

가설: 구버전 driver(580)에서는 orphan context가 발생할 수도 있음

항목 gpu-4 (590.48.01) v1003 (580.126.09) 결론
GPU 할당 동일
kill 후 GPU 해제 <1초 <1초 동일
Pod 삭제 시간 31초 32초 동일 (측정 오차)
SIGKILL 후 해제 <1초 <1초 동일
orphan context 미발생 미발생 동일
노드 재부팅 필요 동일

결론: Driver 580/590 모두 동일하게 안전. 과거 driver(390/450 시대)에서 보고되던 orphan context 문제는 현대 driver에서 사실상 해결됨.


8. Worker 개수 영향 (#3 vs #4)

가설: worker 많아지면 cleanup이 안 될 수도 있음

항목 1 worker 10 workers 비율
GPU 점유 2466 MiB 24702 MiB 10×
고아 발생 1 10 10×
kill 후 좀비 1 10 10×
GPU 해제 시간 <1초 <1초 동일
Pod 삭제 시간 31초 32초 동일
GPU·sec 낭비 73 740 10×

결론: 선형 스케일. 개수 증가는 메모리·낭비량을 비례로 증가시킬 뿐, 동작 패턴은 동일. SIGKILL의 cgroup 일괄 종료로 100개여도 1초 이내 해제 가능 (이론).


9. 권한(root vs non-root) 영향 (#1 vs #3)

사용자 의문: "non-root면 kill에 sudo 필요 아닌가?"

항목 root UID 1000 차이
GPU 할당 없음
같은 유저 kill (sudo 없이) 없음
좀비 발생 동일
고아 발생 동일
Pod 삭제 흐름 31초 31초 동일
GPU 해제 <1초 <1초 동일
observe.sh 동작 모든 fd 보임 자기 UID fd만 보임 권한 차이

결론: 보안 best practice인 non-root 적용이 실험 결과에 영향 없음. 단, fuser/proc 접근 시 다른 UID 정보를 못 볼 수 있는 부수 효과 존재.


10. 종합 결론

10-1. orphan CUDA context 재현 — 사실상 실패

여러 각도로 시도했으나, 현대 stack(Driver 580+, containerd, K8s, cgroup v1)에서는:

  • Linux kernel이 process death → fd close → driver release callback 견고
  • cgroup이 모든 컨테이너 프로세스를 일괄 종료 (cgroup 밖으로 안 새어나감)
  • CUDA Driver 580/590이 GPU context를 즉시 회수
  • "프로세스 죽었는데 GPU 메모리 잔존" 패턴 미발생

예외: NCCL multi-GPU의 peer context 부분 잔존 (148 MiB) — 진정한 orphan은 아니고 살아있는 rank들이 보유 중인 자원

10-2. 진짜 발견된 문제들

문제 발생 조건 영향 해결 방안
30초 grace 낭비 SIGTERM 무시 코드 GB·sec 낭비 시나리오 4 (SIGTERM handler)
좀비 누적 PID 1 = init 아님 프로세스 슬롯 점유 시나리오 2 (tini)
NCCL hang rank 비정상 종료 무한 GPU 점유 NCCL_ASYNC_ERROR_HANDLING + timeout
Peer context 잔존 NCCL 멀티 GPU 작은 누수 (148 MiB) hang 해결 시 자동 해소

10-3. 시나리오 2~4의 진짜 가치

plan.md에서 시나리오 2~4의 목적은 "orphan context 방지"였지만, 실제로는:

  • 시나리오 2 (tini): 좀비 reaping — 좀비 누적 방지 (init protection)
  • 시나리오 3 (preStop): GPU cleanup 명령을 Pod 삭제 시 먼저 실행 — grace period 활용
  • 시나리오 4 (SIGTERM handler): 30초 grace 낭비 제거 + NCCL graceful shutdown ← 가장 큰 가치

10-4. 실험에서 배운 교훈

  1. plan.md 가설을 실험으로 검증해야 함 — 예상과 다르게 driver는 잘 동작
  2. 진짜 문제는 application 코드에 있음 — driver는 죄가 없고, SIGTERM handler 부재가 문제
  3. NCCL 같은 분산 라이브러리는 별도 대응 필요 — driver 자동 회수로 안 되는 유일한 케이스
  4. 대안적 분석 도구 필요 — observe.sh의 PID namespace 오탐지 → 노드 레벨 도구(gpu-pod-trace.sh)가 정확

11. 시나리오별 결과 디렉토리

디렉토리 notes.md 내용 핵심 발견
scenario-1/ 기본 시나리오 (root, 1 worker) orphan ctx 미발생, 30초 grace 낭비
scenario-1-driver-580.126.09/ V100 + 구버전 driver driver 버전 무관 동일 결과
scenario-1-user-soo/ UID 1000 비교 non-root에서도 kill에 sudo 불필요
scenario-1-user-soo-10workers/ 10 worker 동시 선형 스케일, 740 GB·sec 낭비
scenario-1-nccl-3gpu/ NCCL 멀티 GPU 유일한 부분 누수 (148 MiB) + NCCL hang

12. 다음 단계 제안

A. 시나리오 4 변형으로 NCCL hang 해결 검증

  • SIGTERM handler에서 dist.destroy_process_group() 호출
  • NCCL_ASYNC_ERROR_HANDLING=1 + timeout 설정
  • rank 죽었을 때 다른 rank가 timeout 후 abort + cleanup하는지 측정

B. 시나리오 4 단일 GPU 검증

  • 30초 grace 낭비 → 0초로 단축되는지 측정 (1 worker, 10 workers, 둘 다)
  • GPU 메모리 즉시 반환 타이밍

C. orphan context 재현이 정말 필요하면

  • 호스트에서 직접 띄운 GPU 프로세스 (K8s 우회)
  • nvidia-container-toolkit bug 재현 (특정 version)
  • 또는 plan.md 가설 자체를 "현대 환경에서 미발생"으로 정정