13 KiB
시나리오 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 -9exit 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. 실험에서 배운 교훈
- plan.md 가설을 실험으로 검증해야 함 — 예상과 다르게 driver는 잘 동작
- 진짜 문제는 application 코드에 있음 — driver는 죄가 없고, SIGTERM handler 부재가 문제
- NCCL 같은 분산 라이브러리는 별도 대응 필요 — driver 자동 회수로 안 되는 유일한 케이스
- 대안적 분석 도구 필요 — 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 가설 자체를 "현대 환경에서 미발생"으로 정정