#
DeepSpeed

DeepSpeed는 분산 학습 메모리를 효율적이고 빠르게 만드는 PyTorch 최적화 라이브러리입니다. 그 핵심은 대규모 모델을 규모에 맞게 훈련할 수 있는 Zero Redundancy Optimizer(ZeRO)입니다. ZeRO는 여러 단계로 작동합니다:

- ZeRO-1, GPU 간 최적화 상태 분할
- ZeRO-2, GPU 간 그레이디언트 분할
- ZeRO-3, GPU 간 매개변수 분할

GPU가 제한된 환경에서 ZeRO는 최적화 메모리와 계산을 GPU에서 CPU로 오프로드하여 단일 GPU에 대규모 모델을 장착하고 훈련할 수 있습니다. DeepSpeed는 모든 ZeRO 단계 및 오프로딩을 위해 Transformers Trainer 클래스와 통합되어 있습니다. 구성 파일을 제공하거나 제공된 템플릿을 사용하기만 하면 됩니다. 추론의 경우, Transformers는 대용량 모델을 가져올 수 있으므로 ZeRO-3 및 오프로딩을 지원합니다.

이 가이드에서는 DeepSpeed 트레이닝을 배포하는 방법, 활성화할 수 있는 기능, 다양한 ZeRO 단계에 대한 구성 파일 설정 방법, 오프로딩, 추론 및 Trainer 없이 DeepSpeed를 사용하는 방법을 안내해 드립니다.

##
설치

DeepSpeed는 PyPI 또는 Transformers에서 설치할 수 있습니다(자세한 설치 옵션은 DeepSpeed 설치 상세사항 또는 GitHub README를 참조하세요).

DeepSpeed를 설치하는 데 문제가 있는 경우 DeepSpeed CUDA 설치 가이드를 확인하세요. DeepSpeed에는 pip 설치 가능한 PyPI 패키지로 설치할 수 있지만, 하드웨어에 가장 잘 맞고 PyPI 배포판에서는 제공되지 않는 1비트 Adam과 같은 특정 기능을 지원하려면 소스에서 설치하기를 적극 권장합니다.

PyPI
Transformers

    pip install deepspeed

##
메모리 요구량

시작하기 전에 모델에 맞는 충분한 GPU 및 CPU 메모리가 있는지 확인하는 것이 좋습니다. DeepSpeed는 필요한 CPU/GPU 메모리를 추정할 수 있는 도구를 제공합니다. 예를 들어, 단일 GPU에서 bigscience/T0_3B 모델의 메모리 요구 사항을 추정할 수 있습니다:

    $ python -c 'from transformers import AutoModel; \
    from deepspeed.runtime.zero.stage3 import estimate_zero3_model_states_mem_needs_all_live; \
    model = AutoModel.from_pretrained("bigscience/T0_3B"); \
    estimate_zero3_model_states_mem_needs_all_live(model, num_gpus_per_node=1, num_nodes=1)'
    [...]
    Estimated memory needed for params, optim states and gradients for a:
    HW: Setup with 1 node, 1 GPU per node.
    SW: Model with 2783M total params, 65M largest layer params.
      per CPU  |  per GPU |   Options
       70.00GB |   0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=1
       70.00GB |   0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=0
       62.23GB |   5.43GB | offload_param=none, offload_optimizer=cpu , zero_init=1
       62.23GB |   5.43GB | offload_param=none, offload_optimizer=cpu , zero_init=0
        0.37GB |  46.91GB | offload_param=none, offload_optimizer=none, zero_init=1
       15.56GB |  46.91GB | offload_param=none, offload_optimizer=none, zero_init=0

즉, CPU 오프로드가 없는 단일 80GB GPU 또는 오프로드 할 8GB GPU와 최대 60GB CPU가 필요합니다 (이는 매개변수, 최적화 상태 및 그레이디언트에 대한 메모리 요구 사항일 뿐이며 CUDA 커널 및 활성화에는 조금 더 필요합니다). 또한 더 작은 GPU를 대여하거나 구입하는 것이 더 저렴하지만 모델을 훈련하는 데 시간이 더 오래 걸리므로 비용과 속도 간의 균형을 고려해야 합니다.

GPU 메모리가 충분하다면 CPU/NVMe 오프로드를 비활성화하여 모든 작업을 더 빠르게 처리하세요.

##
ZeRO 단계 설정하기

DeepSpeed를 설치하고 메모리 요구 사항을 더 잘 파악했다면 다음 단계는 사용할 ZeRO 스테이지를 선택하는 것입니다. 가장 빠르고 메모리 효율이 높은 순서대로 정렬하면 다음과 같습니다:

속도 | 메모리 효율
--- | ---
ZeRO-1 | ZeRO-3 + offload
ZeRO-2 | ZeRO-3
ZeRO-2 + offload | ZeRO-2 + offload
ZeRO-3 | ZeRO-2
ZeRO-3 + offload | ZeRO-1

자신에게 가장 적합한 방법을 찾으려면 가장 빠른 방법부터 시작하고 메모리가 부족하면 더 느리지만 메모리 효율이 높은 다음 단계를 시도하세요. 속도와 메모리 사용량 사이의 적절한 균형을 찾기 위해 (가장 메모리 효율적이거나 가장 빠른 것부터 시작하여) 원하는 방향으로 자유롭게 작업하세요.

일반적으로 사용할 수 있는 프로세스는 다음과 같습니다(배치 크기 1로 시작):

1. 그레이디언트 체크포인팅 활성화
2. ZeRO-2 시도
3. ZeRO-2와 매개변수 오프로드 시도
4. ZeRO-3 시도
5. ZeRO-3과 매개변수 CPU 오프로드 시도
6. ZeRO-3, 매개변수와 옵티마이저 CPU 오프로드 시도
7. generate() 메소드를 사용하는 경우 더 좁은 빔 서치 검색 범위와 같은 다양한 기본값을 낮춰보기
8. 전체 정밀도 가중치보다 반정밀도(구형 GPU 구조의 경우 fp16, 암페어 이후 GPU의 경우 bf16)를 혼합해보기
9. 가능하면 하드웨어를 더 추가하거나 Infinity가 매개변수와 옵티마이저를 NVMe로 오프로드하도록 활성화
10. 메모리가 부족하지 않으면 유효 처리량을 측정한 다음 배치 크기를 최대한 크게 늘려 GPU 효율성을 극대화
11. 마지막으로 일부 오프로드 기능을 비활성화하거나 더 빠른 ZeRO 스테이지를 사용하고 배치 크기를 늘리거나 줄여 속도와 메모리 사용량 간의 최적의 균형을 찾아 트레이닝 설정을 최적화

##
DeepSpeed 구성 파일

DeepSpeed는 트레이닝 실행 방법을 구성하는 모든 매개변수가 포함된 구성 파일을 통해 Trainer 클래스와 함께 작동합니다. 트레이닝 스크립트를 실행하면 DeepSpeed는 Trainer로부터 받은 구성을 콘솔에 기록하므로 어떤 구성이 사용되었는지 정확히 확인할 수 있습니다.

DeepSpeed 구성 옵션의 전체 목록은 DeepSpeed Configuration JSON에서 확인할 수 있습니다. 또한 DeepSpeedExamples 리포지토리 또는 기본 DeepSpeed 리포지토리에서 다양한 DeepSpeed 구성 예제에 대한 보다 실용적인 예제를 찾을 수 있습니다. 구체적인 예제를 빠르게 찾으려면 다음과 같이 하세요:

    git clone https://github.com/microsoft/DeepSpeedExamples
    cd DeepSpeedExamples
    find . -name '*json'
    # Lamb 옵티마이저 샘플 찾기
    grep -i Lamb $(find . -name '*json')

명령줄 인터페이스에서 트레이닝하는 경우 DeepSpeed 구성 파일은 JSON 파일의 경로로 전달되거나 노트북 설정에서 Trainer를 사용하는 경우 중첩된 `dict` 객체로 전달됩니다.

path to file
nested dict

    TrainingArguments(..., deepspeed="path/to/deepspeed_config.json")

###
DeepSpeed와 Trainer 매개변수

구성 매개변수에는 세 가지 유형이 있습니다:

1. 일부 구성 매개변수는 Trainer와 DeepSpeed가 공유하며, 정의가 충돌하는 경우 오류를 식별하기 어려울 수 있습니다. 이러한 공유 구성 매개변수는 Trainer 명령줄 인수에서 쉽게 설정할 수 있습니다.

2. 모델 설정에서 자동으로 도출되는 일부 설정 매개변수는 수동으로 값을 조정할 필요가 없습니다. Trainer는 구성 값 `auto`를 사용하여 가장 정확하거나 효율적인 값을 설정합니다. 직접 구성 매개변수를 명시적으로 설정할 수도 있지만, Trainer 인수와 DeepSpeed 설정 매개변수가 일치하도록 주의해야 합니다. 일치하지 않으면 감지하기 매우 어려운 방식으로 훈련이 실패할 수 있습니다!

3. 교육 요구 사항에 따라 수동으로 설정해야 하는 일부 설정 매개변수는 DeepSpeed에만 해당됩니다.

DeepSpeed 구성을 수정하고 TrainingArguments를 편집할 수도 있습니다:

1. 기본 구성으로 사용할 DeepSpeed 구성 파일을 생성하거나 로드합니다.
2. 다음 DeepSpeed 구성을 기반으로 TrainingArguments 객체를 생성합니다.

`scheduler.params.total_num_steps`와 같은 일부 값은 트레이닝 중 Trainer에 의해 계산됩니다.

###
ZeRO 구성

세 가지 구성이 있으며, 각 구성은 서로 다른 ZeRO 단계에 해당합니다. 1단계는 확장성 측면에서 그다지 눈여겨볼만하지 않으므로 이 가이드에서는 2단계와 3단계에 중점을 둡니다. `zero_optimization` 구성에는 활성화할 항목과 구성 방법에 대한 모든 옵션이 포함되어 있습니다. 각 매개변수에 대한 자세한 설명은 DeepSpeed 구성 JSON 참조를 참조하세요.

DeepSpeed는 매개변수 이름의 유효성을 검사하지 않으며 오타가 있으면 매개변수의 기본 설정으로 대체합니다. DeepSpeed 엔진 시작 로그 메시지를 보고 어떤 값을 사용할지 확인할 수 있습니다.

Trainer는 동등한 명령줄 인수를 제공하지 않으므로 다음 구성은 DeepSpeed로 설정해야 합니다.

ZeRO-1
ZeRO-2
ZeRO-3

ZeRO-1은 옵티마이저 상태를 GPU에 분할하여 약간의 속도 향상을 기대할 수 있습니다. ZeRO-1 구성은 다음과 같이 설정할 수 있습니다:

    {
        "zero_optimization": {
            "stage": 1
        }
    }

###
NVMe 설정

ZeRO-Infinity를 사용하면 모델 상태를 CPU 및/또는 NVMe로 오프로드하여 더 많은 메모리를 절약할 수 있습니다. 스마트 파티셔닝 및 타일링 알고리즘을 통해 각 GPU는 오프로딩 중에 매우 적은 양의 데이터를 주고받을 수 있으므로 최신 NVMe는 훈련 프로세스에 사용할 수 있는 것보다 훨씬 더 큰 총 메모리 풀에 맞출 수 있습니다. ZeRO-Infinity에는 ZeRO-3가 필요합니다.

사용 가능한 CPU 및/또는 NVMe 메모리에 따라 옵티마이저와 매개변수 중 하나만 오프로드하거나 아무것도 오프로드하지 않을 수 있습니다. 또한 일반 하드 드라이브나 솔리드 스테이트 드라이브에서도 작동하지만 속도가 현저히 느려지므로 `nvme_path`가 NVMe 장치를 가리키고 있는지 확인해야 합니다. 최신 NVMe를 사용하면 읽기 작업의 경우 최대 3.5GB/s, 쓰기 작업의 경우 최대 3GB/s의 전송 속도를 기대할 수 있습니다. 마지막으로, 트레이닝 설정에서 벤치마크 실행하기을 통해 최적의 ‘aio’ 구성을 결정합니다.

아래 예제 ZeRO-3/Infinity 구성 파일은 대부분의 매개변수 값을 `auto`으로 설정하고 있지만, 수동으로 값을 추가할 수도 있습니다.

    {
        "fp16": {
            "enabled": "auto",
            "loss_scale": 0,
            "loss_scale_window": 1000,
            "initial_scale_power": 16,
            "hysteresis": 2,
            "min_loss_scale": 1
        },

        "optimizer": {
            "type": "AdamW",
            "params": {
                "lr": "auto",
                "betas": "auto",
                "eps": "auto",
                "weight_decay": "auto"
            }
        },

        "scheduler": {
            "type": "WarmupLR",
            "params": {
                "warmup_min_lr": "auto",
                "warmup_max_lr": "auto",
                "warmup_num_steps": "auto"
            }
        },

        "zero_optimization": {
            "stage": 3,
            "offload_optimizer": {
                "device": "nvme",
                "nvme_path": "/local_nvme",
                "pin_memory": true,
                "buffer_count": 4,
                "fast_init": false
            },
            "offload_param": {
                "device": "nvme",
                "nvme_path": "/local_nvme",
                "pin_memory": true,
                "buffer_count": 5,
                "buffer_size": 1e8,
                "max_in_cpu": 1e9
            },
            "aio": {
                "block_size": 262144,
                "queue_depth": 32,
                "thread_count": 1,
                "single_submit": false,
                "overlap_events": true
            },
            "overlap_comm": true,
            "contiguous_gradients": true,
            "sub_group_size": 1e9,
            "reduce_bucket_size": "auto",
            "stage3_prefetch_bucket_size": "auto",
            "stage3_param_persistence_threshold": "auto",
            "stage3_max_live_parameters": 1e9,
            "stage3_max_reuse_distance": 1e9,
            "stage3_gather_16bit_weights_on_model_save": true
        },

        "gradient_accumulation_steps": "auto",
        "gradient_clipping": "auto",
        "steps_per_print": 2000,
        "train_batch_size": "auto",
        "train_micro_batch_size_per_gpu": "auto",
        "wall_clock_breakdown": false
    }

##
DeepSpeed 구성

이 섹션에서 간략하게 설명하는 몇 가지 중요한 매개변수를 DeepSpeed 구성 파일에 지정할 수 있습니다.

###
활성화/그레이디언트 체크포인팅

활성화 및 그레이디언트 체크포인팅은 속도를 더 많은 GPU 메모리와 교환하여 GPU 메모리가 부족한 상황을 극복하거나 배치 크기를 늘려 성능을 향상시킬 수 있습니다. 이 기능을 활성화하려면 다음과 같이 하세요:

1. 허깅 페이스 모델의 경우, Trainer에서 `model.gradient_checkpointing_enable()` 또는 `--gradient_checkpointing`을 설정합니다.
2. 허깅 페이스가 아닌 모델의 경우, 딥스피드 Activation Checkpointing API를 사용합니다. 트랜스포머 모델링 코드를 대체하고 `torch.utils.checkpoint`를 DeepSpeed API로 대체할 수도 있습니다. 이 접근 방식은 순방향 활성화를 다시 계산하는 대신 CPU 메모리로 오프로드할 수 있으므로 더 유연합니다.

###
옵티마이저와 스케줄러

`offload_optimizer`를 활성화하지 않는 한 DeepSpeed와 트랜스포머 옵티마이저 및 스케줄러를 혼합하여 사용할 수 있습니다. `offload_optimizer`를 활성화하면 CPU와 GPU 구현이 모두 있는 경우 DeepSpeed가 아닌 최적화기(LAMB 제외)를 사용할 수 있습니다.

구성 파일의 최적화 프로그램 및 스케줄러 매개변수는 명령줄에서 설정할 수 있으므로 오류를 찾기 어렵지 않습니다. 예를 들어 학습 속도가 다른 곳에서 다른 값으로 설정된 경우 명령줄에서 이를 재정의할 수 있습니다. 최적화 프로그램 및 스케줄러 매개변수 외에도 Trainer 명령줄 인수가 DeepSpeed 구성과 일치하는지 확인해야 합니다.

optimizer
scheduler

DeepSpeed는 여러 옵티마이저를 제공하지만(Adam, AdamW, OneBitAdam 및 LAMB) PyTorch에서 다른 옵티마이저를 가져올 수도 있습니다. 설정에서 옵티마이저를 구성하지 않으면 Trainer가 자동으로 AdamW를 선택하고 명령줄에서 제공된 값 또는 기본값을 사용합니다: `lr`, `adam_beta1`, `adam_beta2`, `adam_epsilon`, `weight_decay`.

매개변수를 `"auto"`으로 설정하거나 원하는 값을 직접 수동으로 입력할 수 있습니다.

    {
       "optimizer": {
           "type": "AdamW",
           "params": {
             "lr": "auto",
             "betas": "auto",
             "eps": "auto",
             "weight_decay": "auto"
           }
       }
    }

최상위 구성에 다음을 추가하여 지원되지 않는 옵티마이저를 사용할 수도 있습니다.

    {
       "zero_allow_untested_optimizer": true
    }

DeepSpeed==0.8.3부터 오프로드를 사용하려면 오프로드가 DeepSpeed의 CPU Adam 옵티마이저에서 가장 잘 작동하므로 최상위 수준 구성에 다음 사항을 추가해야 합니다.

    {
       "zero_force_ds_cpu_optimizer": false
    }

###
정밀도

DeepSpeed는 fp32, fp16 및 bf16 혼합 정밀도를 지원합니다.

fp32
fp16
bf16

모델이 혼합 정밀도로 사전 학습되지 않은 경우와 같이 혼합 정밀도로 잘 작동하지 않는 경우 NaN 손실을 유발할 수 있는 오버플로 또는 언더플로 문제가 발생할 수 있습니다. 이러한 경우에는 기본 fp16 모드를 명시적으로 비활성화하여 전체 fp32 정밀도를 사용해야 합니다.

    {
        "fp16": {
            "enabled": false
        }
    }

Ampere GPU 및 PyTorch 1.7 이상의 경우 일부 연산에 대해 더 효율적인 tf32 형식으로 자동 전환되지만 결과는 여전히 fp32로 표시됩니다. Trainer에서 `--tf32`를 설정하여 활성화하고 `--tf32 0` 또는 `--no_tf32`를 비활성화하면 제어할 수 있습니다.

###
배치 크기

배치 크기는 자동으로 구성하거나 명시적으로 설정할 수 있습니다. `"auto"` 옵션을 사용하도록 선택하면 Trainer는 `train_micro_batch_size_per_gpu`를 args.`per_device_train_batch_size`의 값으로, `train_batch_size`를 `args.world_size * args.per_device_train_batch_size * args.gradient_accumulation_steps`로 설정합니다.

    {
        "train_micro_batch_size_per_gpu": "auto",
        "train_batch_size": "auto"
    }

###
그레이디언트 누적

그레이디언트 누적을 자동으로 구성하거나 명시적으로 설정할 수 있습니다. `"auto"` 옵션을 사용하도록 선택하면 Trainer가 `args.gradient_accumulation_steps`의 값으로 설정합니다.

    {
        "gradient_accumulation_steps": "auto"
    }

###
그레이디언트 클리핑

그레이디언트 클리핑은 자동으로 구성하거나 명시적으로 설정할 수 있습니다. `"auto"` 옵션을 사용하도록 선택하면 Trainer가 `args.max_grad_norm`의 값으로 설정합니다.

    {
        "gradient_clipping": "auto"
    }

###
통신 데이터 유형(Communication data type)

축소, 수집 및 분산 작업과 같은 통신 집합체의 경우 별도의 데이터 유형이 사용됩니다.

모든 수집 및 분산 작업은 데이터와 동일한 데이터 유형으로 수행됩니다. 예를 들어 bf16으로 훈련하는 경우, 수집은 비손실 연산이므로 데이터도 bf16으로 수집됩니다.

예를 들어 그레이디언트가 여러 GPU에 걸쳐 평균화되는 경우와 같이 감소 연산은 손실이 발생합니다. 통신이 fp16 또는 bf16으로 수행되는 경우, 낮은 정밀도로 여러 숫자를 더하면 정확하지 않기 때문에 손실이 발생할 가능성이 더 높습니다. 특히 fp16보다 정밀도가 낮은 bf16의 경우 더욱 그렇습니다. 이러한 이유로 기울기를 평균화할 때 손실이 최소화되므로 감소 연산에는 fp16이 기본값으로 사용됩니다.

통신 데이터 유형은 설정 파일에서 `communication_data_type` 매개변수를 설정하여 선택할 수 있습니다. 예를 들어, fp32를 선택하면 약간의 오버헤드가 추가되지만 감소 연산이 fp32에 누적되고 준비가 되면 훈련 중인 반정밀 dtype으로 다운캐스트됩니다.

    {
        "communication_data_type": "fp32"
    }

##
모델 배포

torchrun, `deepspeed` 런처 또는 Accelerate 등 다양한 런처를 통해 DeepSpeed를 배포할 수 있습니다. 배포하려면 Trainer 명령줄에 `--deepspeed ds_config.json`을 추가합니다. 필요한 명령줄 인수를 코드에 추가하려면 DeepSpeed의 `add_config_arguments` 유틸리티를 사용하는 것이 좋습니다.

이 가이드에서는 다양한 트레이닝 설정에 대해 `deepspeed` 런처로 DeepSpeed를 배포하는 방법을 보여드립니다. 보다 실용적인 사용 예제는 이 post에서 확인할 수 있습니다.

multi-GPU
single-GPU

여러 GPU에 DeepSpeed를 배포하려면 `--num_gpus` 매개변수를 추가하세요. 사용 가능한 모든 GPU를 사용하려는 경우 `--num_gpus`를 추가할 필요가 없습니다. 아래 예제에서는 2개의 GPU를 사용합니다.

    deepspeed --num_gpus=2 examples/pytorch/translation/run_translation.py \
    --deepspeed tests/deepspeed/ds_config_zero3.json \
    --model_name_or_path google-t5/t5-small --per_device_train_batch_size 1 \
    --output_dir output_dir --overwrite_output_dir --fp16 \
    --do_train --max_train_samples 500 --num_train_epochs 1 \
    --dataset_name wmt16 --dataset_config "ro-en" \
    --source_lang en --target_lang ro

###
다중 노드 환경에서의 모델 배포

노드는 워크로드를 실행하기 위한 하나 이상의 GPU입니다. 더 강력한 설정은 멀티 노드 설정으로, `deepspeed` 런처로 실행할 수 있습니다. 이 가이드에서는 각각 8개의 GPU가 있는 두 개의 노드가 있다고 가정해 보겠습니다. 첫 번째 노드는 `ssh hostname1`로, 두 번째 노드는 `ssh hostname2`로 접속할 수 있습니다. 두 노드 모두 비밀번호 없이 ssh를 통해 로컬로 서로 통신할 수 있어야 합니다.

기본적으로 DeepSpeed는 멀티노드 환경에서 공유 저장소를 사용할 것으로 예상합니다. 그렇지 않고 각 노드가 로컬 파일 시스템만 볼 수 있는 경우, 공유 파일 시스템에 대한 액세스 없이 로딩할 수 있도록 `checkpoint`를 포함하도록 구성 파일을 조정해야 합니다:

    {
      "checkpoint": {
        "use_node_local_storage": true
      }
    }

Trainer의 ``--save_on_each_node` 인수를 사용하여 위의 `checkpoint`를 구성에 자동으로 추가할 수도 있습니다.

torchrun
deepspeed

torchrun의 경우, 각 노드에 ssh로 접속한 후 두 노드 모두에서 다음 명령을 실행해야 합니다. 런처는 두 노드가 동기화될 때까지 기다렸다가 트레이닝을 시작합니다.

    torchrun --nproc_per_node=8 --nnode=2 --node_rank=0 --master_addr=hostname1 \
    --master_port=9901 your_program.py <normal cl args> --deepspeed ds_config.json

###
SLURM

SLURM 환경에서는 특정 SLURM 환경에 맞게 SLURM 스크립트를 조정해야 합니다.SLURM 스크립트 예시는 다음과 같습니다:

    #SBATCH --job-name=test-nodes        # 작업 이름
    #SBATCH --nodes=2                    # 노드 수
    #SBATCH --ntasks-per-node=1          # 중요 - 노드당 분산 작업 1개!
    #SBATCH --cpus-per-task=10           # 작업당 CPU 코어 수
    #SBATCH --gres=gpu:8                 # gpu 수
    #SBATCH --time 20:00:00              # 최대 실행 시간 (HH:MM:SS)
    #SBATCH --output=%x-%j.out           # 출력 파일 이름

    export GPUS_PER_NODE=8
    export MASTER_ADDR=$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1)
    export MASTER_PORT=9901

    srun --jobid $SLURM_JOBID bash -c 'python -m torch.distributed.run \
     --nproc_per_node $GPUS_PER_NODE --nnodes $SLURM_NNODES --node_rank $SLURM_PROCID \
     --master_addr $MASTER_ADDR --master_port $MASTER_PORT \
    your_program.py <normal cl args> --deepspeed ds_config.json'

그런 다음 모든 노드에서 동시에 학습을 시작하는 다음 명령을 사용하여 다중 노드 배포를 예약할 수 있습니다.

    sbatch launch.slurm

###
노트북

`deepspeed` 런처는 노트북에서의 배포를 지원하지 않으므로 분산 환경을 에뮬레이션해야 합니다. 하지만 이는 1개의 GPU에서만 작동합니다. 1개 이상의 GPU를 사용하려면 딥스피드가 작동할 수 있는 다중 프로세스 환경을 사용해야 합니다. 즉, 여기에 표시된 것처럼 에뮬레이션할 수 없는 `deepspeed` 런처를 사용해야 합니다.

    # DeepSpeed는 단일 프로세스만 사용하더라도 분산 환경을 필요로 합니다.
    # 이 코드로 분산 환경을 모방합니다.
    import os

    os.environ["MASTER_ADDR"] = "localhost"
    os.environ["MASTER_PORT"] = "9994"  # RuntimeError: Address already in use 오류 발생 시 수정
    os.environ["RANK"] = "0"
    os.environ["LOCAL_RANK"] = "0"
    os.environ["WORLD_SIZE"] = "1"

    # 이제 평소와 같이 진행하되, DeepSpeed 설정 파일을 전달합니다.
    training_args = TrainingArguments(..., deepspeed="ds_config_zero3.json")
    trainer = Trainer(...)
    trainer.train()

현재 디렉터리의 노트북에 구성 파일을 즉석에서 만들고 싶다면 전용 셀을 만들 수 있습니다.

    %%bash
    cat <<'EOT' > ds_config_zero3.json
    {
        "fp16": {
            "enabled": "auto",
            "loss_scale": 0,
            "loss_scale_window": 1000,
            "initial_scale_power": 16,
            "hysteresis": 2,
            "min_loss_scale": 1
        },

        "optimizer": {
            "type": "AdamW",
            "params": {
                "lr": "auto",
                "betas": "auto",
                "eps": "auto",
                "weight_decay": "auto"
            }
        },

        "scheduler": {
            "type": "WarmupLR",
            "params": {
                "warmup_min_lr": "auto",
                "warmup_max_lr": "auto",
                "warmup_num_steps": "auto"
            }
        },

        "zero_optimization": {
            "stage": 3,
            "offload_optimizer": {
                "device": "cpu",
                "pin_memory": true
            },
            "offload_param": {
                "device": "cpu",
                "pin_memory": true
            },
            "overlap_comm": true,
            "contiguous_gradients": true,
            "sub_group_size": 1e9,
            "reduce_bucket_size": "auto",
            "stage3_prefetch_bucket_size": "auto",
            "stage3_param_persistence_threshold": "auto",
            "stage3_max_live_parameters": 1e9,
            "stage3_max_reuse_distance": 1e9,
            "stage3_gather_16bit_weights_on_model_save": true
        },

        "gradient_accumulation_steps": "auto",
        "gradient_clipping": "auto",
        "steps_per_print": 2000,
        "train_batch_size": "auto",
        "train_micro_batch_size_per_gpu": "auto",
        "wall_clock_breakdown": false
    }
    EOT

트레이닝 스크립트가 노트북 셀이 아닌 파일에 있는 경우, 노트북 셀의 셸에서 `deepspeed`를 정상적으로 실행할 수 있습니다. 예를 들어 `run_translation.py`를 시작하려면 다음과 같이 하세요.:

    !git clone https://github.com/huggingface/transformers
    !cd transformers; deepspeed examples/pytorch/translation/run_translation.py ...

또한 `%%bash` 매직을 사용하여 여러 줄의 코드를 작성하여 셸 프로그램을 실행할 수도 있지만 교육이 완료될 때까지 로그를 볼 수 없습니다. `%%bash` 매직으로 분산 환경을 에뮬레이션할 필요는 없습니다.

    %%bash

    git clone https://github.com/huggingface/transformers
    cd transformers
    deepspeed examples/pytorch/translation/run_translation.py ...

##
모델 가중치 저장하기

딥스피드는 기본 고정밀 fp32 가중치를 사용자 지정 체크포인트 최적화 파일(glob 패턴은 `global_step*/*optim_states.pt`처럼 보입니다)에 저장하고 일반 체크포인트 아래에 저장합니다.

fp16
fp32

ZeRO-2로 훈련된 모델은 pytorch_model.bin 가중치를 fp16에 저장합니다. ZeRO-3으로 훈련된 모델의 모델 가중치를 fp16에 저장하려면 모델 가중치가 여러 GPU에 분할되어 있으므로 `“stage3_gather_16bit_weights_on_model_save”: true`를 설정해야 합니다. 그렇지 않으면 Trainer가 가중치를 fp16에 저장하지 않고 pytorch_model.bin 파일을 생성하지 않습니다. 이는 DeepSpeed의 state_dict에 실제 가중치 대신 플레이스홀더가 포함되어 있어 이를 로드할 수 없기 때문입니다.

    {
        "zero_optimization": {
            "stage3_gather_16bit_weights_on_model_save": true
        }
    }

##
ZeRO Inference

ZeRO Inference는 모델 가중치를 CPU 또는 NVMe 메모리에 배치하여 GPU에 부담을 주지 않으므로 GPU에서 대규모 모델을 사용하여 추론을 실행할 수 있습니다. 추론은 최적화 상태 및 그레이디언트에 많은 양의 메모리를 추가로 필요로 하지 않으므로 동일한 하드웨어에 훨씬 더 큰 배치 및/또는 시퀀스 길이를 맞출 수 있습니다.

ZeRO Inference는 ZeRO-3와 동일한 구성 파일을 공유하며, ZeRO-2 및 ZeRO-1 구성은 추론에 아무런 이점을 제공하지 않으므로 작동하지 않습니다.

ZeRO Inference를 실행하려면 일반적인 훈련 인수를 TrainingArguments 클래스에 전달하고 `--do_eval` 인수를 추가합니다.

    deepspeed --num_gpus=2 your_program.py <normal cl args> --do_eval --deepspeed ds_config.json

##
Trainer 없이 DeepSpeed 사용하기

DeepSpeed는 Trainer 클래스가 없는 트랜스포머에서도 작동합니다. 이는 from_pretrained()를 호출할 때 ZeRO-3 매개변수를 수집하고 모델을 여러 GPU에 분할하는 작업만 처리하는 `HfDeepSpeedConfig`가 처리합니다.

모든 것이 자동으로 처리되기를 원한다면, Trainer와 함께 DeepSpeed를 사용해 보세요! DeepSpeed 문서를 참조하여 설정 파일에서 매개변수 값을 수동으로 구성해야 합니다(`"auto"` 값은 사용할 수 없음).

ZeRO-3를 효율적으로 배포하려면 모델 앞에 `HfDeepSpeedConfig` 객체를 인스턴스화하고 해당 객체를 유지해야 합니다:

pretrained model
non-pretrained model

    from transformers.integrations import HfDeepSpeedConfig
    from transformers import AutoModel
    import deepspeed

    ds_config = {...}  # deepspeed 설정 객체 또는 파일 경로
    # Zero 3를 감지하기 위해 모델을 인스턴스화하기 전에 반드시 실행해야 합니다
    dschf = HfDeepSpeedConfig(ds_config)  # 이 객체를 유지하세요.
    model = AutoModel.from_pretrained("openai-community/gpt2")
    engine = deepspeed.initialize(model=model, config_params=ds_config, ...)

###
Trainer 없이 ZeRO Inference 사용하기

단일 GPU에 모델을 맞출 수 없는 경우 Trainer없이 ZeRO 추론을 실행하려면 추가 GPU를 사용하거나 CPU 메모리로 오프로드를 시도하세요. 여기서 이해해야 할 중요한 뉘앙스는 ZeRO가 설계된 방식에 따라 서로 다른 GPU에서 서로 다른 입력을 병렬로 처리할 수 있다는 것입니다.

반드시 확인하세요:

- GPU 메모리가 충분한 경우 CPU 오프로드를 비활성화합니다(속도가 느려지므로).
- Ampere 이상의 GPU를 사용하는 경우 bf16을 활성화하면 속도가 빨라집니다. 이러한 GPU가 없는 경우 오버플로 오류가 발생할 수 있으므로 bf16으로 사전 학습된 모델(T5 모델)을 사용하지 않는 한 fp16을 활성화할 수 있습니다.

단일 GPU에 맞지 않는 모델에서 Trainer 없이 ZeRO 추론을 실행하는 방법에 대한 더 나은 아이디어를 얻으려면 다음 스크립트를 살펴보시기 바랍니다.

    #!/usr/bin/env python

    # 이 스크립트는 단일 GPU에 모델을 맞출 수 없을 때 추론 모드에서 Deepspeed ZeRO를 사용하는 방법을 보여줍니다.
    #
    # 1. CPU 오프로드와 함께 1개의 GPU 사용
    # 2. 또는 여러 GPU 사용
    #
    # 먼저 deepspeed를 설치해야 합니다: pip install deepspeed
    #
    # 여기서는 약 15GB의 GPU RAM이 필요한 3B "bigscience/T0_3B" 모델을 사용합니다 - 따라서 1개의 큰 GPU나 2개의
    # 작은 GPU로 처리할 수 있습니다. 또는 1개의 작은 GPU와 많은 CPU 메모리로도 가능합니다.
    #
    # 약 50GB가 필요한 "bigscience/T0"와 같은 더 큰 모델을 사용하려면, 80GB GPU가 없는 한
    # 2-4개의 GPU가 필요할 것입니다. 그리고 여러 입력을 한 번에 처리하고 싶다면
    # 스크립트를 수정하여 더 많은 GPU를 처리할 수 있습니다.
    #
    # 제공된 deepspeed 설정은 CPU 메모리 오프로딩도 활성화하므로, 사용 가능한 CPU 메모리가 많고
    # 속도 저하를 감수할 수 있다면 일반적으로 단일 GPU에 맞지 않는 모델을 로드할 수 있을 것입니다.
    # GPU 메모리가 충분하다면 CPU로의 오프로드를 원하지 않을 때 프로그램이 더 빠르게 실행될 것입니다 - 그럴 때는 해당 섹션을 비활성화하세요.
    #
    # 1개의 GPU에 배포하려면:
    #
    # deepspeed --num_gpus 1 t0.py
    # 또는:
    # python -m torch.distributed.run --nproc_per_node=1 t0.py
    #
    # 2개의 GPU에 배포하려면:
    #
    # deepspeed --num_gpus 2 t0.py
    # 또는:
    # python -m torch.distributed.run --nproc_per_node=2 t0.py

    from transformers import AutoTokenizer, AutoConfig, AutoModelForSeq2SeqLM
    from transformers.integrations import HfDeepSpeedConfig
    import deepspeed
    import os
    import torch

    os.environ["TOKENIZERS_PARALLELISM"] = "false"  # 토크나이저의 병렬 처리에 관한 경고를 피하기 위함입니다.

    # 분산 환경 설정
    local_rank = int(os.getenv("LOCAL_RANK", "0"))
    world_size = int(os.getenv("WORLD_SIZE", "1"))
    torch.cuda.set_device(local_rank)
    deepspeed.init_distributed()

    model_name = "bigscience/T0_3B"

    config = AutoConfig.from_pretrained(model_name)
    model_hidden_size = config.d_model

    # 배치 크기는 world_size로 나누어 떨어져야 하지만, world_size보다 클 수 있습니다
    train_batch_size = 1 * world_size

    # ds_config 참고사항
    #
    # - Ampere 이상의 GPU를 사용하는 경우 bf16을 활성화하세요 - 이는 혼합 정밀도로 실행되어
    # 더 빠를 것입니다.
    #
    # - 오래된 GPU의 경우 fp16을 활성화할 수 있지만, bf16으로 사전 훈련되지 않은 모델에서만 작동합니다 - 예를 들어
    # 모든 공식 t5 모델은 bf16으로 사전 훈련되었습니다
    #
    # - CPU 오프로드를 원하지 않는다면 offload_param.device를 "none"으로 설정하거나 `offload_param` 섹션을
    # 완전히 제거하세요
    #
    # - `offload_param`을 사용하는 경우, stage3_param_persistence_threshold를 수동으로 미세 조정하여
    # 어떤 매개변수가 GPU에 남아있어야 하는지 제어할 수 있습니다 - 값이 클수록 오프로드 크기가 작아집니다
    #
    # Deepspeed 설정에 대한 자세한 정보는 다음을 참조하세요
    # https://huggingface.co/docs/transformers/main/main_classes/deepspeed

    # 일관성을 위해 json과 동일한 형식을 유지하되, true/false에는 소문자를 사용합니다
    # fmt: off
    ds_config = {
        "fp16": {
            "enabled": False
        },
        "bf16": {
            "enabled": False
        },
        "zero_optimization": {
            "stage": 3,
            "offload_param": {
                "device": "cpu",
                "pin_memory": True
            },
            "overlap_comm": True,
            "contiguous_gradients": True,
            "reduce_bucket_size": model_hidden_size * model_hidden_size,
            "stage3_prefetch_bucket_size": 0.9 * model_hidden_size * model_hidden_size,
            "stage3_param_persistence_threshold": 10 * model_hidden_size
        },
        "steps_per_print": 2000,
        "train_batch_size": train_batch_size,
        "train_micro_batch_size_per_gpu": 1,
        "wall_clock_breakdown": False
    }
    # fmt: on

    # 다음 줄은 모델의 `from_pretrained` 메소드가 호출될 때
    # deepspeed.zero.Init를 사용하여 모델을 여러 GPU에 직접 분할하도록 transformers에 지시합니다.
    #
    # **이는 AutoModelForSeq2SeqLM.from_pretrained(model_name)로 모델을 로드하기 전에 실행되어야 합니다**
    #
    # 그렇지 않으면 모델이 먼저 정상적으로 로드된 후 포워드 시에만 분할되는데, 이는
    # 덜 효율적이며 CPU RAM이 부족할 경우 실패할 수 있습니다
    dschf = HfDeepSpeedConfig(ds_config)  # 이 객체를 유지하세요

    # 이제 모델을 로드할 수 있습니다.
    model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

    # Deepspeed ZeRO를 초기화하고 엔진 객체만 저장
    ds_engine = deepspeed.initialize(model=model, config_params=ds_config)[0]
    ds_engine.module.eval()  # inference

    # Deepspeed ZeRO는 각 GPU에서 서로 관련 없는 입력을 처리할 수 있습니다. 따라서 2개의 GPU를 사용하면 한 번에 2개의 입력을 처리할 수 있습니다.
    # GPU를 더 많이 사용하는 경우 그에 맞게 조정하세요.

    # 물론 처리할 입력이 하나뿐이라면 두 GPU에 동일한 문자열을 전달해야 합니다.
    # GPU를 하나만 사용하는 경우에는 rank 0만 갖게 됩니다.
    rank = torch.distributed.get_rank()
    if rank == 0:
        text_in = "Is this review positive or negative? Review: this is the best cast iron skillet you will ever buy"
    elif rank == 1:
        text_in = "Is this review positive or negative? Review: this is the worst restaurant ever"

    tokenizer = AutoTokenizer.from_pretrained(model_name)
    inputs = tokenizer.encode(text_in, return_tensors="pt").to(device=local_rank)
    with torch.no_grad():
        outputs = ds_engine.module.generate(inputs, synced_gpus=True)
    text_out = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(f"rank{rank}:\n   in={text_in}\n  out={text_out}")

스크립트를 t0.py로 저장하고 실행합니다:

    $ deepspeed --num_gpus 2 t0.py
    rank0:
       in=Is this review positive or negative? Review: this is the best cast iron skillet you will ever buy
      out=Positive
    rank1:
       in=Is this review positive or negative? Review: this is the worst restaurant ever
      out=negative

이것은 매우 기본적인 예시이므로 사용 사례에 맞게 조정할 수 있습니다.

###
생성

생성에 ZeRO-3와 함께 여러 개의 GPU를 사용하려면 generate() 메서드에서 `synced_gpus=True`를 설정하여 GPU를 동기화해야 합니다. 그렇지 않으면 한 GPU가 다른 GPU보다 먼저 생성을 완료하면 나머지 GPU가 먼저 완료한 GPU로부터 가중치 샤드를 받지 못하여 전체 시스템이 중단됩니다.

트랜스포머>=4.28의 경우, 생성 중에 여러 개의 GPU가 감지되면 `synced_gpus`가 자동으로 `True`로 설정됩니다.

##
트러블슈팅

문제가 발생하면 DeepSpeed가 문제의 원인이 아닌 경우가 많으므로(아주 명백하고 예외적으로 DeepSpeed 모듈을 볼 수 있는 경우가 아니라면) DeepSpeed가 문제의 원인인지 고려해야 합니다! 첫 번째 단계는 DeepSpeed 없이 설정을 다시 시도하고 문제가 지속되면 문제를 신고하는 것입니다. 문제가 핵심적인 DeepSpeed 문제이고 transformers와 관련이 없는 경우, DeepSpeed 리포지토리에서 이슈를 개설하세요.

transformers와 관련된 이슈를 개설할 때에는 다음 정보를 제공해 주세요:

- 전체 DeepSpeed 구성 파일

*Trainer의 명령줄 인수, 또는Trainer 설정을 직접 작성하는 경우TrainingArguments 인수(관련 없는 항목이 수십 개 있는 TrainingArguments는 덤프하지 마세요).

- 다음 코드의 출력 결과:

    python -c 'import torch; print(f"torch: {torch.__version__}")'
    python -c 'import transformers; print(f"transformers: {transformers.__version__}")'
    python -c 'import deepspeed; print(f"deepspeed: {deepspeed.__version__}")'

- 문제를 재현할 수 있는 Google Colab 노트북 링크

- 불가능할 경우 기존 예제를 사용하여 문제를 재현할 수 있는 표준 및 사용자 지정이 아닌 데이터 집합을 사용할 수 있습니다.

다음 섹션에서는 가장 일반적인 두 가지 문제를 해결하기 위한 가이드를 제공합니다.

###
DeepSpeed 프로세스가 시작 단계에서 종료되었을 경우

실행 중에 트레이스백 없이 DeepSpeed 프로세스가 종료되면 일반적으로 프로그램이 시스템보다 많은 CPU 메모리를 할당하려고 시도했거나 프로세스가 허용된 것보다 많은 CPU 메모리를 할당하려고 시도하여 OS 커널이 프로세스를 종료했음을 의미합니다. 이 경우 구성 파일에 `offload_optimizer`, `offload_param` 또는 둘 다 CPU로 오프로드하도록 구성되어 있는지 확인하세요.

NVMe 및 ZeRO-3를 설정한 경우 NVMe로 오프로드를 실험해 보세요(모델의 메모리 요구 사항을 확인하세요).

###
NaN 손실

모델을 bf16으로 사전 훈련한 다음 fp16으로 사용하려고 할 때 NaN 손실이 발생하는 경우가 많습니다(특히 TPU 훈련 모델에 해당). 이 문제를 해결하려면 하드웨어가 이를 지원하는 경우(TPU, Ampere GPU 이상) fp32 또는 bf16을 사용하세요.

다른 문제는 fp16 사용과 관련이 있을 수 있습니다. 예를 들어 이것이 fp16 구성인 경우입니다:

    {
        "fp16": {
            "enabled": "auto",
            "loss_scale": 0,
            "loss_scale_window": 1000,
            "initial_scale_power": 16,
            "hysteresis": 2,
            "min_loss_scale": 1
        }
    }

로그에 다음과 같은 `OVERFLOW!` 메시지가 표시될 수 있습니다:

    0%|                                                                                                                             | 0/189 [00:00<?, ?it/s]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 262144
      1%|▌                                                                                                                    | 1/189 [00:00<01:26,  2.17it/s]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 131072.0
      1%|█▏
     [...]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1
     14%|████████████████▌                                                                                                   | 27/189 [00:14<01:13,  2.21it/s]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1
     15%|█████████████████▏                                                                                                  | 28/189 [00:14<01:13,  2.18it/s]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1
     15%|█████████████████▊                                                                                                  | 29/189 [00:15<01:13,  2.18it/s]
     [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1
    [...]

이는 DeepSpeed 손실 스케일러가 손실 오버플로를 극복할 수 있는 스케일링 계수를 찾을 수 없음을 의미합니다. 이 문제를 해결하려면 `initial_scale_power` 값을 더 높게 설정하세요(일반적으로 32가 적절합니다).

##
리소스

DeepSpeed ZeRO는 제한된 GPU 리소스로 추론을 위해 매우 큰 모델을 훈련하고 로드하는 강력한 기술로, 누구나 쉽게 사용할 수 있습니다. DeepSpeed에 대해 자세히 알아보려면 블로그 포스트, 공식 문서, 깃허브 리포지토리를 참조하세요.

다음 문서도 ZeRO에 대해 자세히 알아볼 수 있는 훌륭한 자료입니다:

- ZeRO: Memory Optimizations Toward Training Trillion Parameter Models
- ZeRO-Offload: Democratizing Billion-Scale Model Training
- ZeRO-Infinity: Breaking the GPU Memory Wall for Extreme Scale Deep Learning

< > Update on GitHub