# 시나리오 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 변형으로 검증 필요) ```python 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 가설 자체를 "현대 환경에서 미발생"으로 정정