Update README with training results, TFJob comparison, and full troubleshooting
- Add successful training results (2 nodes × 8 GPU, 16 replicas, NCCL + IB + GPUDirect RDMA) - Add TFJob (v1) vs TrainJob (v2) chief/worker comparison - Add model.evaluate() collective operation pattern - Add Pod naming pattern mismatch troubleshooting - Add hostname regex parsing details - Consolidate all findings from real cluster testing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a80f3bd97e
commit
5633426a5e
199
README.md
199
README.md
|
|
@ -15,6 +15,35 @@ Kubeflow Trainer v2 (v1alpha1)에는 다음 런타임만 기본 제공된다:
|
||||||
|
|
||||||
**TensorFlow runtime은 없다.** 따라서 Custom ClusterTrainingRuntime을 직접 생성해야 한다.
|
**TensorFlow runtime은 없다.** 따라서 Custom ClusterTrainingRuntime을 직접 생성해야 한다.
|
||||||
|
|
||||||
|
## 실행 결과 요약
|
||||||
|
|
||||||
|
2노드 × 8 GPU (Tesla V100-SXM2-32GB) 분산학습 성공:
|
||||||
|
|
||||||
|
```
|
||||||
|
환경:
|
||||||
|
- 노드: 2개
|
||||||
|
- GPU: 노드당 8장 (총 16장)
|
||||||
|
- GPU 모델: Tesla V100-SXM2-32GB
|
||||||
|
- 통신: NCCL + InfiniBand + GPUDirect RDMA
|
||||||
|
- 이미지: nvcr.io/nvidia/tensorflow:24.03-tf2-py3
|
||||||
|
|
||||||
|
학습 결과:
|
||||||
|
- 데이터셋: CIFAR-10 (로컬 hostPath)
|
||||||
|
- 모델: CNN (Conv2D + BatchNorm + Dropout)
|
||||||
|
- Strategy: MultiWorkerMirroredStrategy (NCCL)
|
||||||
|
- num_replicas_in_sync: 16
|
||||||
|
- Global Batch Size: 64 × 16 = 1024
|
||||||
|
- Epochs: 10
|
||||||
|
- Test Loss: 1.6169
|
||||||
|
- Test Accuracy: 44.59%
|
||||||
|
|
||||||
|
NCCL 초기화 로그:
|
||||||
|
NCCL INFO Connected all trees
|
||||||
|
NCCL INFO 16 coll channels, 0 nvls channels, ...
|
||||||
|
NCCL INFO comm 0x... rank 0 nranks 16 ... busId ... COMPLETE
|
||||||
|
→ 16개 rank 전부 NCCL Init COMPLETE, GPUDirect RDMA 활성화 확인
|
||||||
|
```
|
||||||
|
|
||||||
## PyTorch vs TensorFlow Runtime 핵심 차이
|
## PyTorch vs TensorFlow Runtime 핵심 차이
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -78,6 +107,8 @@ mlPolicy에 framework(torch/mpi) 설정 여부에 따라 Pod 네이밍이 달라
|
||||||
└────────────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
이 차이를 모르면 DNS 프로빙 패턴이 잘못되어 워커를 1개만 발견하는 문제가 발생한다.
|
||||||
|
|
||||||
## TF_CONFIG 자동 구성 원리
|
## TF_CONFIG 자동 구성 원리
|
||||||
|
|
||||||
### 1. Pod hostname에서 정보 추출
|
### 1. Pod hostname에서 정보 추출
|
||||||
|
|
@ -89,6 +120,14 @@ hostname: tf-distributed-training-node-0-1
|
||||||
└─── replica_index (항상 0)
|
└─── replica_index (항상 0)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
hostname 파싱 정규식:
|
||||||
|
```python
|
||||||
|
match = re.match(r'^(.+)-node-(\d+)-(\d+)$', hostname)
|
||||||
|
job_name = match.group(1) # tf-distributed-training
|
||||||
|
replica_index = match.group(2) # 0 (항상)
|
||||||
|
worker_index = int(match.group(3)) # completion_index = worker_index
|
||||||
|
```
|
||||||
|
|
||||||
### 2. Headless Service DNS로 워커 주소 구성
|
### 2. Headless Service DNS로 워커 주소 구성
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -134,6 +173,108 @@ import tensorflow as tf # 2. 그 다음 tensorflow import
|
||||||
# "different incarnation" 에러가 발생한다.
|
# "different incarnation" 에러가 발생한다.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## TFJob (Trainer v1) vs TrainJob (Trainer v2) 비교
|
||||||
|
|
||||||
|
### chief/worker 패턴 비교
|
||||||
|
|
||||||
|
Trainer v1의 TFJob은 `chief`와 `worker` role을 명시적으로 분리했다:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# TFJob (Trainer v1) - chief/worker 명시적 분리
|
||||||
|
apiVersion: kubeflow.org/v1
|
||||||
|
kind: TFJob
|
||||||
|
spec:
|
||||||
|
tfReplicaSpecs:
|
||||||
|
Chief:
|
||||||
|
replicas: 1
|
||||||
|
template: ... # chief 전용 설정
|
||||||
|
Worker:
|
||||||
|
replicas: 3
|
||||||
|
template: ... # worker 설정
|
||||||
|
PS:
|
||||||
|
replicas: 2
|
||||||
|
template: ... # Parameter Server (선택)
|
||||||
|
```
|
||||||
|
|
||||||
|
TrainJob (Trainer v2)은 단일 role 타입(`node`) 기반이다:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# TrainJob (Trainer v2) - 단일 role
|
||||||
|
apiVersion: trainer.kubeflow.org/v1alpha1
|
||||||
|
kind: TrainJob
|
||||||
|
spec:
|
||||||
|
trainer:
|
||||||
|
numNodes: 4 # 모든 노드가 동일한 role
|
||||||
|
```
|
||||||
|
|
||||||
|
### 구현 가능성 분석
|
||||||
|
|
||||||
|
| 기능 | TFJob (v1) | TrainJob (v2) | 비고 |
|
||||||
|
|------|-----------|---------------|------|
|
||||||
|
| chief/worker 분리 | `tfReplicaSpecs.Chief/Worker` | 미지원 (단일 role) | TrainJob은 replicatedJobs로 부분 구현 가능 |
|
||||||
|
| Parameter Server | `tfReplicaSpecs.PS` | 미지원 | PS 전략 자체가 레거시 |
|
||||||
|
| Evaluator | `tfReplicaSpecs.Evaluator` | 미지원 | - |
|
||||||
|
| worker index 기반 분기 | role별로 자동 | `worker_index == 0`으로 직접 구현 | 실질적으로 동일한 효과 |
|
||||||
|
| 분산 전략 | PS Strategy / MirroredStrategy | MultiWorkerMirroredStrategy | v2는 AllReduce 기반 |
|
||||||
|
|
||||||
|
### worker_index == 0 패턴 (현재 구현)
|
||||||
|
|
||||||
|
TFJob의 chief가 하던 역할(체크포인트 저장, 로그 출력, 평가 결과 출력 등)을
|
||||||
|
`worker_index == 0`으로 분기하여 동일하게 구현:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# worker_index == 0 이 사실상 chief 역할
|
||||||
|
if worker_index == 0:
|
||||||
|
print(f"Test Loss: {loss:.4f}")
|
||||||
|
print(f"Test Accuracy: {accuracy:.4f}")
|
||||||
|
# 체크포인트 저장, TensorBoard 로그 등도 여기서
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의**: `model.evaluate()`는 모든 워커가 함께 호출해야 한다 (collective operation).
|
||||||
|
Worker 0만 호출하면 다른 워커가 heartbeat timeout으로 실패한다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 올바른 패턴: 모든 워커가 evaluate 참여, 출력만 worker 0
|
||||||
|
loss, accuracy = model.evaluate(test_dataset, verbose=eval_verbose) # 모든 워커
|
||||||
|
if worker_index == 0: # 출력만 worker 0
|
||||||
|
print(f"Test Accuracy: {accuracy:.4f}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### replicatedJobs를 이용한 multi-role 구현 (참고)
|
||||||
|
|
||||||
|
TrainJob의 Runtime에서 replicatedJobs를 여러 개 정의하면 role 분리가 이론적으로 가능하다:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 이론적 구현 (권장하지 않음)
|
||||||
|
replicatedJobs:
|
||||||
|
- name: chief # chief role
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: node
|
||||||
|
env:
|
||||||
|
- name: TF_ROLE
|
||||||
|
value: "chief"
|
||||||
|
- name: worker # worker role
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: node
|
||||||
|
env:
|
||||||
|
- name: TF_ROLE
|
||||||
|
value: "worker"
|
||||||
|
```
|
||||||
|
|
||||||
|
하지만 TrainJob API의 `trainer` 필드는 단일 role을 가정하므로, 여러 replicatedJobs에 대한
|
||||||
|
`numNodes`, `resourcesPerNode` 등의 매핑이 명확하지 않다.
|
||||||
|
|
||||||
|
**결론**: 현재 TrainJob에서는 `worker_index == 0` 패턴으로 chief 역할을 대체하는 것이 가장 실용적이다.
|
||||||
|
TFJob의 PS(Parameter Server) 전략 자체가 레거시이므로, MultiWorkerMirroredStrategy 기반 AllReduce가 현대적 표준이다.
|
||||||
|
|
||||||
## CIFAR-10 데이터 로딩
|
## CIFAR-10 데이터 로딩
|
||||||
|
|
||||||
`tf.keras.datasets.cifar10.load_data()`는 외부 URL에서 다운로드를 시도한다.
|
`tf.keras.datasets.cifar10.load_data()`는 외부 URL에서 다운로드를 시도한다.
|
||||||
|
|
@ -154,13 +295,14 @@ volumes:
|
||||||
## 통신 구조 (InfiniBand + NCCL)
|
## 통신 구조 (InfiniBand + NCCL)
|
||||||
|
|
||||||
TensorFlow도 PyTorch와 동일하게 NCCL + InfiniBand를 사용할 수 있다.
|
TensorFlow도 PyTorch와 동일하게 NCCL + InfiniBand를 사용할 수 있다.
|
||||||
|
NCCL은 NVIDIA에서 제공하는 프레임워크 독립적인 GPU 간 통신 라이브러리이므로, TF/PyTorch 구분 없이 동일하게 동작한다.
|
||||||
|
|
||||||
```
|
```
|
||||||
PyTorch: torchrun → torch.distributed → NCCL → InfiniBand
|
PyTorch: torchrun → torch.distributed → NCCL → InfiniBand/GPUDirect RDMA
|
||||||
TensorFlow: gRPC (조정) → MultiWorkerMirroredStrategy → NCCL → InfiniBand
|
TensorFlow: gRPC (조정) → MultiWorkerMirroredStrategy → NCCL → InfiniBand/GPUDirect RDMA
|
||||||
```
|
```
|
||||||
|
|
||||||
NCCL은 프레임워크 독립적인 라이브러리이므로, 동일한 환경변수가 양쪽 모두에 적용된다:
|
동일한 NCCL 환경변수가 양쪽 모두에 적용된다:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
env:
|
env:
|
||||||
|
|
@ -170,6 +312,16 @@ env:
|
||||||
value: "net" # OOB 통신용 인터페이스
|
value: "net" # OOB 통신용 인터페이스
|
||||||
- name: NCCL_DEBUG
|
- name: NCCL_DEBUG
|
||||||
value: "INFO" # NCCL 디버그 로그
|
value: "INFO" # NCCL 디버그 로그
|
||||||
|
- name: NCCL_DEBUG_SUBSYS
|
||||||
|
value: "INIT,NET,IB" # NCCL 서브시스템별 디버그
|
||||||
|
```
|
||||||
|
|
||||||
|
실제 학습 시 확인된 NCCL 초기화 로그:
|
||||||
|
```
|
||||||
|
NCCL INFO NET/IB : Using [0]mlx5_0:1/RoCE ... ; OOB net0:<ip>
|
||||||
|
NCCL INFO Connected all trees
|
||||||
|
NCCL INFO 16 coll channels, 0 nvls channels, ...
|
||||||
|
NCCL INFO comm 0x... rank 0 nranks 16 ... COMPLETE
|
||||||
```
|
```
|
||||||
|
|
||||||
## 파일 구조
|
## 파일 구조
|
||||||
|
|
@ -310,6 +462,23 @@ spec:
|
||||||
value: "WARN"
|
value: "WARN"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### model.evaluate()는 모든 워커가 참여해야 함
|
||||||
|
|
||||||
|
MultiWorkerMirroredStrategy는 collective operation 기반이다.
|
||||||
|
`model.evaluate()`를 worker 0만 호출하면 다른 워커가 heartbeat timeout으로 실패한다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 잘못된 패턴 (worker 0만 evaluate)
|
||||||
|
if worker_index == 0:
|
||||||
|
loss, accuracy = model.evaluate(test_dataset) # ← 다른 워커 timeout 발생
|
||||||
|
|
||||||
|
# 올바른 패턴 (모든 워커가 evaluate, 출력만 worker 0)
|
||||||
|
eval_verbose = 2 if worker_index == 0 else 0
|
||||||
|
loss, accuracy = model.evaluate(test_dataset, verbose=eval_verbose) # 모든 워커 참여
|
||||||
|
if worker_index == 0:
|
||||||
|
print(f"Test Accuracy: {accuracy:.4f}") # 출력만 worker 0
|
||||||
|
```
|
||||||
|
|
||||||
## 트러블슈팅
|
## 트러블슈팅
|
||||||
|
|
||||||
### "different incarnation" 에러
|
### "different incarnation" 에러
|
||||||
|
|
@ -322,15 +491,37 @@ unexpectedly tried to connect with a different incarnation. It has likely restar
|
||||||
**원인**: `TF_CONFIG`가 `import tensorflow` 이후에 설정됨
|
**원인**: `TF_CONFIG`가 `import tensorflow` 이후에 설정됨
|
||||||
**해결**: 학습 스크립트에서 `setup_tf_config()`를 `import tensorflow` 전에 호출
|
**해결**: 학습 스크립트에서 `setup_tf_config()`를 `import tensorflow` 전에 호출
|
||||||
|
|
||||||
### DNS 프로빙에서 워커를 1개만 발견
|
### Pod 네이밍 패턴 불일치로 워커 감지 실패
|
||||||
|
|
||||||
```
|
```
|
||||||
[Discovery] attempt 1/60: found 1 workers, retrying in 5s...
|
[Discovery] attempt 1/60: found 1 workers, retrying in 5s...
|
||||||
|
(영원히 1개만 발견)
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인**: DNS 프로빙 패턴이 `node-{i}-0` (PyTorch 패턴)으로 되어있으나, 실제는 `node-0-{i}` (completion 패턴)
|
||||||
|
**해결**: mlPolicy에 framework가 없으면 `numNodes`가 단일 Job의 `completions`로 매핑됨을 이해하고, DNS 패턴을 `{trainjob}-node-0-{i}`로 수정
|
||||||
|
|
||||||
|
### DNS 프로빙에서 워커를 1개만 발견 (정상 대기)
|
||||||
|
|
||||||
|
```
|
||||||
|
[Discovery] attempt 1/60: found 1 workers, retrying in 5s...
|
||||||
|
[Discovery] attempt 2/60: found 2 workers, retrying in 5s...
|
||||||
|
[Discovery] Discovered 2 workers via DNS
|
||||||
```
|
```
|
||||||
|
|
||||||
**원인**: 다른 워커 Pod이 아직 시작되지 않았거나 DNS 전파 지연
|
**원인**: 다른 워커 Pod이 아직 시작되지 않았거나 DNS 전파 지연
|
||||||
**해결**: Volcano gang scheduling 사용 시 모든 Pod이 동시에 스케줄링되지만, DNS 전파에 시간이 걸릴 수 있다. `discover_workers()`가 연속 2회 동일 결과를 확인한 후 진행하므로 정상적으로 기다린다.
|
**해결**: Volcano gang scheduling 사용 시 모든 Pod이 동시에 스케줄링되지만, DNS 전파에 시간이 걸릴 수 있다. `discover_workers()`가 연속 2회 동일 결과를 확인한 후 진행하므로 정상적으로 기다린다.
|
||||||
|
|
||||||
|
### model.evaluate() heartbeat timeout
|
||||||
|
|
||||||
|
```
|
||||||
|
W tensorflow/core/distributed_runtime/...coordination_service_agent.cc:
|
||||||
|
Heartbeat timeout from ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인**: Worker 0만 `model.evaluate()` 호출, 다른 워커는 이미 종료
|
||||||
|
**해결**: MultiWorkerMirroredStrategy는 collective operation이므로, 모든 워커가 `model.evaluate()`에 참여해야 함. 출력만 `worker_index == 0`에서 처리.
|
||||||
|
|
||||||
### CIFAR-10 데이터 다운로드 시도
|
### CIFAR-10 데이터 다운로드 시도
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue