285 lines
13 KiB
Markdown
285 lines
13 KiB
Markdown
# 시나리오 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 가설 자체를 "현대 환경에서 미발생"으로 정정
|