인공지능 개발자 수다(유튜브 바로가기) 자세히보기

자연어처리/LLM을 위한 코딩

[LLM] 모델 학습 with HuggingFace (DeepSpeed) (7)

Suda_777 2025. 2. 20. 04:10

목차

    반응형

    1. DeepSpeed 란?

    DeepSpeed는 분산 학습 메모리를 효율적이고 빠르게 만드는 PyTorch 최적화 라이브러리

     

    내용 출처: DeepSpeed

     

    DeepSpeed

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

    huggingface.co

     

    설치

    pip install deepspeed

     


    2. 개념

    2.1. ZeRO(Zero Redundancy Optimizer)

    ZeRO (Zero Redundancy Optimizer) 방법을 사용함

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

    2.2. CPU 오프로드

    CPU 오프로드: GPU 메모리가 부족할 때, 전체 모델 파라미터와 연산에 필요한 모든 데이터를 GPU에만 적재하기 어려울 수 있다. 이러한 상황에서 일부 데이터를 CPU 메모리에 보관하여 GPU 메모리 사용량을 줄이고, 필요한 순간에만 GPU로 다시 가져와서 계산을 진행하는 방식

    • GPU 메모리가 충분하다면 CPU/NVMe 오프로드를 비활성화 하는 것을 추천

    결론

    • 속도와 메모리 사용량 사이의 적절한 균형을 찾아 적절히 사용하자
    효율 속도 메모리 효율
    좋음 ZeRO-1 ZeRO-3 + offload
      ZeRO-2 ZeRO-3
      ZeRO-2 + offload ZeRO-2 + offload
      ZeRO-3 ZeRO-2
    나쁨 ZeRO-3 + offload ZeRO-1

     


    3. 파이썬에 적용

    DeepSpeed는 주로 json 파일을 이용해

    파라미터를 정의하고

    그것을 학습(Trainer)에 적용해 준다.

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

     

     

    딕셔너리 형식으로도 가능

    ds_config_dict = {...} # 설정 내용
    
    ds_config_dict = dict(scheduler=scheduler_params, optimizer=optimizer_params)
    args = TrainingArguments(..., deepspeed=ds_config_dict)
    trainer = Trainer(model, args, ...)

     


    3. DeepSpeed Json 파일 만드는 법

    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": "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
    }

     

    3.1. 정밀도

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

     

    fp32 사용하기

    • 일반적인 단정밀도(float32) 부동소수점 형식
    • 연산의 정확도가 높다
    • 연산량이 많아 속도가 느리고 메모리 사용량이 많다
    {
        "fp16": {
            "enabled": false
        }
    }

     

    fp16 활성화

    • 반정밀도(float16) 부동소수점 형식

     

    파라미터

    • loss_scale : 손실을 스케일링함
    • loss_scale_window: 손실 스케일 값 업데이트 주기 스텝
    • initial_scale_power: 초기 손실 스케일 값
    • hysteresis: 손실 스케일을 낮춘 후, 일정 스텝(여기서는 2 스텝) 동안 overflow 없이 안정적으로 연산이 이루어지면 손실 스케일을 다시 높이는 방식
    • min_loss_scale : 손실 스케일의 최솟값
    {
        "fp16": {
            "enabled": "auto",
            "loss_scale": 0,
            "loss_scale_window": 1000,
            "initial_scale_power": 16,
            "hysteresis": 2,
            "min_loss_scale": 1
        }
    }

     

    bf16

    • Google이 TPU에서 성능 최적화를 위해 개발한 부동소수점 형식
    • fp32의 수치 표현 범위를 유지하면서 속도를 높이고 메모리를 절약할 때 (ex: TPU, NVIDIA A100 이상의 GPU)
    {
        "bf16": {
            "enabled": "auto"
        }
    }

     


    3.2. 옵티마이저

    • params : AdamW 에 대한 파라미터를 정의함
    • lr : 학습률 (Learning Rate)
    • betas : 아래 예시에서는 AdamW 옵티마이저에서 모멘텀과 RMSProp의 역할을 하는 beta 계수

    옵티마이저마다 파라미터는 다르니까 그때그때 가져와서 쓰자

    type 옵션에 넣어준다.

    • Adam : 일반적인 Adam 옵티마이저.
    • AdamW : Adam 옵티마이저에 weight decay를 효과적으로 적용할 수 있도록 개선된 버전.
    • SGD : 확률적 경사 하강법(Stochastic Gradient Descent).
    • Lamb : 대규모 배치 학습에 최적화된 옵티마이저.
    • FusedAdam : GPU 가속을 위해 최적화된 Adam 버전 (예: NVIDIA Apex와 같이 사용).
    • OneBitAdam : 통신 비용 절감을 위해 분산 학습 환경에서 사용되는 옵티마이저
    {
       "optimizer": {
           "type": "AdamW",
           "params": {
             "lr": "auto",
             "betas": "auto",
             "eps": "auto",
             "weight_decay": "auto"
           }
       }
    }

     


    3.3. 스케줄러

     

    스케줄러도 마찬가지로 다음과 같은 것들을 쓸 수 있으니

    그때그때 찾아서 쓰자

    • WarmupLR : 초기 학습률을 천천히 증가시키는 warmup 단계만 적용하는 스케줄러입니다.
    • WarmupDecayLR : 일정 단계 동안 warmup을 진행한 후, 이후에 학습률을 점진적으로 감소시키는 방식
    • ConstantLR : 학습률을 일정하게 유지
    • CosineAnnealingLR : 코사인 함수 형태로 학습률을 감소시키는 스케줄러

     

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

     


    3.4. ZeRO

    zero 1

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

     

    zero 2

    • offload_optimizer
      • 옵티마이저의 상태(예: momentum, variance 등)를 CPU 메모리로 오프로딩
      • CPU 메모리에서 pinned(고정) 메모리를 사용

     

    • allgather_partitions : 모든 파티션을 모으기 위해 allgather 통신을 사용
    • allgather_bucket_size : allgather 연산에 사용할 버킷(bucket)의 크기
    • overlap_comm : 통신(comm)과 계산(computation)을 동시에 수행하여 오버랩(overlap)하도록 설정
    • reduce_scatter : 각 GPU가 자신에게 필요한 파티션만 모아서 계산할 수 있도록 하는 효율적인 통신 방법으로, gradient의 집계 및 분산에 효과적
    • reduce_bucket_size : reduce scatter 연산에 사용할 버킷 크기
    • contiguous_gradients : 모든 gradient를 연속적인 메모리 블록에 저장
    • round_robin_gradients : 이 방법은 각 GPU에 균등하게 gradient를 할당하여 로드 밸런싱을 돕고, 특정 GPU에 과도한 부담이 가지 않도록 합니다.
    {
        "zero_optimization": {
            "stage": 2,
            "offload_optimizer": {
                "device": "cpu",
                "pin_memory": true
            },
            "allgather_partitions": true,
            "allgather_bucket_size": 5e8,
            "overlap_comm": true,
            "reduce_scatter": true,
            "reduce_bucket_size": 5e8,
            "contiguous_gradients": true
            "round_robin_gradients": true
        }
    }

     

    zero3

    • offload_param : 모델 파라미터(가중치 등)를 CPU 메모리로 오프로딩
    • sub_group_size : ZeRO Stage 3에서 파라미터를 그룹으로 나눌 때 사용할 최대 서브 그룹 크기
    • stage3_prefetch_bucket_size : Stage 3에서 파라미터 prefetch(미리 로드) 시 사용할 버킷 크기
    • stage3_param_persistence_threshold : Stage 3에서 파라미터를 GPU에 계속 상주시킬지(offload 하지 않을지) 결정하는 임계값 설정
    • stage3_max_live_parameters : 한 번에 GPU에 유지(live)될 수 있는 최대 파라미터 수
    • stage3_max_reuse_distance : 파라미터 재사용 간 최대 거리(steps)를 지정
    • stage3_gather_16bit_weights_on_model_save : 모델 저장 시 분산되어 있는 16-bit(반정밀도) 가중치를 하나로 모아서 저장
    {
        "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
        }
    }

     


    3.5. 배치 크기

    • train_micro_batch_size_per_gpu : 각 GPU가 한 번의 순전파/역전파 단계에서 처리할 데이터 샘플의 수를 자동으로 설정
    • train_batch_size : 전체 학습 배치 사이즈(즉, 모든 GPU에서 처리되는 데이터 샘플의 총합)를 자동으로 결정
    {
        "train_micro_batch_size_per_gpu": "auto",
        "train_batch_size": "auto"
    }

     


    3.6. 그레이디언트

    그레디언트 누적

    {
        "gradient_accumulation_steps": "auto"
    }

     

    그레디언트 클리핑

    • 학습 중에 gradient의 크기가 지나치게 커지는(gradient explosion) 문제를 방지하기 위해 gradient를 자동으로 클리핑하는 기능을 활성화
    • 미리 정해진 임계값 이상인 gradient를 일정 비율로 줄여주는 방법
    {
        "gradient_clipping": "auto"
    }

     


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

    {
        "communication_data_type": "fp32"
    }

     


    4. 모델 가중치 저장

    4.1. fp16

    ZeRO-2로 훈련된 모델은 pytorch_model.bin 가중치를 fp16에 저장합니다. ZeRO-3으로 훈련된 모델의 모델 가중치를 fp16에 저장하려면 모델 가중치가 여러 GPU에 분할되어 있으므로 “stage3_gather_16bit_weights_on_model_save”: true를 설정

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

     


    4.2. fp32 

    4.2.1.  학습 중 혹은 학습 직후

    • 이미 저장된 최신 체크포인트를 바로 불러와서 fp32 가중치를 복원
    • 주의: 이 방법으로 로드하면 DeepSpeed와 관련된 최적화(마법)가 제거되므로, 학습이 완전히 끝난 후에만 사용하는 것이 좋다
    from transformers.trainer_utils import get_last_checkpoint
    from deepspeed.utils.zero_to_fp32 import load_state_dict_from_zero_checkpoint
    
    # 마지막 체크포인트 디렉토리 가져오기
    checkpoint_dir = get_last_checkpoint(trainer.args.output_dir)
    # 분산 저장된 체크포인트에서 fp32 가중치 로드
    fp32_model = load_state_dict_from_zero_checkpoint(trainer.model, checkpoint_dir)

     

    4.2.2.  학습 후

    • DeepSpeed는 zero_to_fp32.py라는 독립 실행 스크립트를 제공합니다.
    • 이 스크립트는 분산된 체크포인트(여러 파일에 나누어 저장된 가중치)를 하나의 단일 파일(예: pytorch_model.bin)로 병합해줌

    예를 들어 체크포인트 폴더가 다음과 같은 경우

    $ ls -l output_dir/checkpoint-1/
    -rw-rw-r-- 1 stas stas 1.4K Mar 27 20:42 config.json
    drwxrwxr-x 2 stas stas 4.0K Mar 25 19:52 global_step1/
    -rw-rw-r-- 1 stas stas   12 Mar 27 13:16 latest
    -rw-rw-r-- 1 stas stas 827K Mar 27 20:42 optimizer.pt
    -rw-rw-r-- 1 stas stas 231M Mar 27 20:42 pytorch_model.bin
    -rw-rw-r-- 1 stas stas  623 Mar 27 20:42 scheduler.pt
    -rw-rw-r-- 1 stas stas 1.8K Mar 27 20:42 special_tokens_map.json
    -rw-rw-r-- 1 stas stas 774K Mar 27 20:42 spiece.model
    -rw-rw-r-- 1 stas stas 1.9K Mar 27 20:42 tokenizer_config.json
    -rw-rw-r-- 1 stas stas  339 Mar 27 20:42 trainer_state.json
    -rw-rw-r-- 1 stas stas 2.3K Mar 27 20:42 training_args.bin
    -rwxrw-r-- 1 stas stas 5.5K Mar 27 13:16 zero_to_fp32.py*

     

    다음 명령어 사용

    python zero_to_fp32.py . pytorch_model.bin

     

     


    5. 모델 배포 with DeepSpeed

    DeepSpeed로 학습한 모델은 저장될 때 DeepSpeed의 최적화 방식(예: ZeRO Stage 3 사용 시 분산된 체크포인트, fp16 저장 등)에 따라 일반적인 체크포인트와 다르게 저장될 수 있다

     

    5.1. 일반적인 경우

    • 많은 경우 학습된 모델은 Hugging Face의 from_pretrained 메서드를 통해 불러올 수 있으며, 별도의 복잡한 배포 절차 없이 모델을 실행할 수 있다.

    5.2. ZeRO Stage 3 등 특수 설정 사용 시

    • 모델을 배포(또는 추론) 전에 DeepSpeed가 제공하는 체크포인트 병합(conversion) 스크립트를 통해 하나의 통합된 체크포인트로 변환해야 할 수 있다
    deepspeed --num_gpus=1 merge_checkpoint \
        --input_dir /path/to/zero_stage3_checkpoint/ \
        --output_dir /path/to/merged_checkpoint/ \
        --zero-stage 3
    • --num_gpus=1: 단일 GPU 환경에서 병합 작업을 수행
    • --input_dir: ZeRO Stage 3로 저장된 분산 체크포인트가 있는 디렉토리
    • --output_dir: 병합된 체크포인트를 저장할 경로
    • --zero-stage 3: ZeRO Stage 3 체크포인트임을 지정

     


    5.3. DeepSpeed Inference 활용

    • DeepSpeed Inference API를 사용하면 모델 추론 시 GPU 메모리 사용을 최적화하고 속도를 향상시킬 수 있습니다. 아래는 Python 코드 예시입니다.
    • ZeRO Inference는 ZeRO-3와 동일한 구성 파일을 공유하며, ZeRO-2 및 ZeRO-1 구성은 추론에 아무런 이점을 제공하지 않음
    • deepspeed.init_inference() 메서드 사용
    import torch
    from transformers import AutoModelForSequenceClassification, AutoTokenizer
    import deepspeed
    
    # 병합된 체크포인트에서 모델 로드 (fp16 사용)
    model = AutoModelForSequenceClassification.from_pretrained(
        "/path/to/merged_checkpoint",
        torch_dtype=torch.float16
    )
    
    # DeepSpeed Inference 초기화: 내부적으로 최적화된 커널로 대체됨
    model = deepspeed.init_inference(
        model,
        mp_size=1,  # 단일 GPU 사용, 멀티 프로세스 크기를 지정할 수 있음
        dtype=torch.half,  # fp16 연산 사용
        replace_method='auto',  # 최적화 커널 자동 대체
        replace_with_kernel_inject=True  # 커널 인젝션 최적화 활성화
    )
    
    tokenizer = AutoTokenizer.from_pretrained("/path/to/merged_checkpoint")
    
    # 추론 실행 예시
    inputs = tokenizer("DeepSpeed Inference 예시", return_tensors="pt")
    inputs = {k: v.to("cuda") for k, v in inputs.items()}  # GPU로 데이터 이동
    outputs = model(**inputs)
    print(outputs)
    • deepspeed.init_inference(): 기존 모델을 DeepSpeed의 추론 최적화 버전으로 변환합니다.
    • mp_size: 멀티프로세스(MP) 설정입니다. 단일 GPU 환경이면 1로 지정합니다.
    • replace_methodreplace_with_kernel_inject: DeepSpeed가 제공하는 고속 커널로 모델의 특정 연산을 대체하여 추론 성능을 극대화

     

     

    반응형