DeepSpeed를 활용한 LLM 파인튜닝 심층 분석: 효율적인 메모리 관리 및 학습 전략

초거대 언어 모델(LLM) 파인튜닝, 더 이상 엄두도 못 낼 영역이 아닙니다. DeepSpeed는 혁신적인 메모리 최적화와 분산 학습 기술을 통해 이전에는 불가능했던 규모의 모델을 일반 개발자도 효율적으로 다룰 수 있게 해줍니다. 이 글에서는 DeepSpeed를 활용한 LLM 파인튜닝의 핵심 기술과 실제 적용 방법을 짚어보고, 여러분의 프로젝트에 즉시 적용 가능한 심층적인 지침을 제공합니다.

1. The Challenge / Context

최근 몇 년 동안 LLM은 자연어 처리 분야에서 엄청난 발전을 이루었습니다. 그러나 이러한 모델의 성능 향상은 모델 크기의 급격한 증가를 동반했습니다. 수십억, 심지어 수천억 개의 파라미터를 가진 모델은 학습 및 추론에 막대한 컴퓨팅 자원을 필요로 합니다. 특히 파인튜닝은 기존의 단일 GPU 환경에서는 거의 불가능에 가깝고, 멀티 GPU 환경에서도 메모리 부족 문제가 빈번하게 발생합니다. 이러한 문제를 해결하지 않고서는 LLM의 잠재력을 최대한 활용하기 어렵습니다.

2. Deep Dive: DeepSpeed

DeepSpeed는 Microsoft에서 개발한 딥러닝 최적화 라이브러리입니다. 대규모 모델 학습을 위한 다양한 기술을 제공하며, 특히 메모리 효율성과 학습 속도 향상에 중점을 둡니다. DeepSpeed의 핵심 기술은 다음과 같습니다.

  • ZeRO (Zero Redundancy Optimizer): 모델 파라미터, 옵티마이저 상태, 그래디언트 등을 분산시켜 GPU 메모리 사용량을 획기적으로 줄입니다.
  • Offload Optimizer States: 옵티마이저 상태를 CPU 또는 NVMe SSD로 오프로드하여 GPU 메모리 부담을 더욱 줄입니다.
  • Mixed Precision Training: FP16(반정밀도) 또는 BF16(Brain Float 16)을 사용하여 메모리 사용량을 줄이고 학습 속도를 향상시킵니다.
  • Pipeline Parallelism: 모델을 여러 스테이지로 나누어 각 스테이지를 다른 GPU에서 실행함으로써 모델 크기에 대한 메모리 제한을 극복합니다.
  • Data Parallelism: 데이터를 여러 GPU에 분산시켜 학습을 병렬화합니다.
  • Gradient Accumulation: 여러 미니 배치의 그래디언트를 누적하여 가상으로 더 큰 배치 크기를 사용하는 효과를 얻습니다. 이는 작은 메모리 환경에서 큰 배치 크기로 학습하는 것을 가능하게 합니다.

DeepSpeed는 PyTorch와 seamless하게 통합되어 기존 학습 파이프라인에 쉽게 적용할 수 있습니다. 설정을 통해 다양한 최적화 기술을 조합하여 사용할 수 있으며, 사용자의 하드웨어 환경과 모델 특성에 맞게 최적화된 학습 환경을 구축할 수 있습니다.

3. Step-by-Step Guide / Implementation

이제 DeepSpeed를 사용하여 LLM을 파인튜닝하는 과정을 단계별로 살펴보겠습니다. 이 예제에서는 Hugging Face Transformers 라이브러리와 함께 DeepSpeed를 사용하는 방법을 보여줍니다.

Step 1: DeepSpeed 설치 및 환경 설정

DeepSpeed를 설치하기 전에 PyTorch가 올바르게 설치되어 있는지 확인하십시오. CUDA와 cuDNN 버전이 DeepSpeed와 호환되는지 확인하는 것이 중요합니다. 필요한 경우 NVIDIA 드라이버를 업데이트하십시오.

pip install deepspeed
pip install transformers datasets accelerate

Step 2: DeepSpeed 설정 파일 작성

DeepSpeed는 JSON 형식의 설정 파일을 사용하여 다양한 최적화 옵션을 제어합니다. 다음은 몇 가지 일반적인 설정 옵션과 예제입니다.

{
  "train_batch_size": 4,
  "train_micro_batch_size_per_gpu": 1,
  "gradient_accumulation_steps": 4,
  "optimizer": {
    "type": "AdamW",
    "params": {
      "lr": 0.0001,
      "weight_decay": 0.01
    }
  },
  "scheduler": {
    "type": "WarmupLR",
    "params": {
      "warmup_min_lr": 0.0,
      "warmup_max_lr": 0.0001,
      "warmup_num_steps": 1000
    }
  },
  "fp16": {
    "enabled": "auto",
    "loss_scale": 0,
    "loss_scale_window": 1000,
    "initial_scale_power": 16,
    "hysteresis": 2,
    "min_loss_scale": 1
  },
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {
      "device": "cpu",
      "pin_memory": true
    },
    "offload_param": {
      "device": "cpu",
      "pin_memory": true
    },
    "overlap_comm": true,
    "reduce_scatter": true,
    "contiguous_gradients": true,
    "reduce_bucket_size": 5e8,
    "stage3_prefetch_bucket_size": 5e8,
    "stage3_max_live_parameters": 1e9,
    "stage3_max_reuse_distance": 1e9,
    "allgather_partitions": true
  },
  "gradient_clipping": 1.0,
  "steps_per_print": 2000,
  "wall_clock_breakdown": false
}

설명:

  • train_batch_size: 전체 배치 크기
  • train_micro_batch_size_per_gpu: 각 GPU당 미니 배치 크기
  • gradient_accumulation_steps: 그래디언트 누적 단계 수 (전체 배치 크기 = train_micro_batch_size_per_gpu * GPU 개수 * gradient_accumulation_steps)
  • fp16: 혼합 정밀도 학습 활성화 여부 ("auto"로 설정하면 자동으로 활성화)
  • zero_optimization: ZeRO 최적화 활성화 여부 (stage 2는 파라미터와 그래디언트를 분산하고, stage 3는 파라미터, 그래디언트, 옵티마이저 상태를 모두 분산)
  • offload_optimizeroffload_param: 옵티마이저 상태와 파라미터를 CPU로 오프로드하여 GPU 메모리 부담을 줄입니다. NVMe SSD로 오프로드할 수도 있습니다.

Step 3: 학습 스크립트 수정

기존의 PyTorch 학습 스크립트를 DeepSpeed와 통합하려면 몇 가지 변경 사항이 필요합니다. 가장 중요한 것은 deepspeed.initialize 함수를 사용하여 모델, 옵티마이저, 데이터 로더를 초기화하는 것입니다.

import deepspeed
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
import torch

# 1. 모델 및 토크나이저 로드
model_name = "gpt2"  # 예시 모델
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token  # 패딩 토큰 설정

# 2. 데이터셋 로드 및 전처리
dataset_name = "wikitext"
dataset_config_name = "wikitext-2-raw-v1"
train_dataset = load_dataset(dataset_name, dataset_config_name, split="train")

def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=128)

tokenized_datasets = train_dataset.map(tokenize_function, batched=True, num_proc=4, remove_columns=["text"])
tokenized_datasets = tokenized_datasets.remove_columns(["text"]) # HuggingFace 버전 업데이트에 따른 오류 수정
tokenized_datasets.set_format("torch")

# 3. 데이터 로더 생성
from torch.utils.data import DataLoader
train_dataloader = DataLoader(tokenized_datasets, shuffle=True, batch_size=1)

# 4. DeepSpeed 초기화
config_file = "ds_config.json" # DeepSpeed 설정 파일 경로
model, optimizer, _, _ = deepspeed.initialize(
    model=model,
    config=config_file,
    model_parameters=model.parameters()
)

# 5. 학습 루프
model.train()
for epoch in range(1): # 예시로 1 epoch만 학습
    for step, batch in enumerate(train_dataloader):
        batch = {k: v.to(model.device) for k, v in batch.items()}
        outputs = model(**batch, labels=batch["input_ids"])
        loss = outputs.loss
        model.backward(loss)
        model.step()

        if step % 100 == 0:
            print(f"Epoch: {epoch}, Step: {step}, Loss: {loss.item()}")

# 6. 모델 저장 (선택 사항)
# unwrap_model()을 사용하여 DeepSpeed 모델에서 원래 PyTorch 모델을 추출
unwrapped_model = model.module if hasattr(model, "module") else model
unwrapped_model.save_pretrained("fine_tuned_model")
tokenizer.save_pretrained("fine_tuned_model")

설명:

  • deepspeed.initialize 함수는 모델, 옵티마이저, 데이터 로더를 DeepSpeed 엔진으로 감쌉니다.
  • config_file 매개변수는 DeepSpeed 설정 파일의 경로를 지정합니다.
  • 학습 루프에서는 model.backward(loss)model.step()을 사용하여 역전파와 파라미터 업데이트를 수행합니다.
  • model.device를 사용하여 데이터를 모델과 동일한 장치로 이동합니다.

Step 4: DeepSpeed 실행

DeepSpeed를 실행하려면 deepspeed 명령어를 사용합니다. 이 명령어는 자동으로 필요한 환경 변수를 설정하고 분산 학습을 시작합니다.

deepspeed --num_gpus 2 your_training_script.py --deepspeed ds_config.json

설명:

  • --num_gpus: 사용할 GPU의 개수를 지정합니다.
  • your_training_script.py: 학습 스크립트의 경로를 지정합니다.
  • --deepspeed ds_config.json: DeepSpeed 설정 파일의 경로를 지정합니다.

4. Real-world Use Case / Example

한 번은 30억 개의 파라미터를 가진 GPT-2 모델을 특정 도메인 데이터셋으로 파인튜닝해야 했습니다. 기존의 멀티 GPU 환경에서는 배치 크기를 충분히 크게 설정할 수 없어 학습이 불안정하고 속도가 매우 느렸습니다. DeepSpeed ZeRO stage 3와 optimizer offloading을 적용한 결과, 훨씬 더 큰 배치 크기로 안정적인 학습이 가능해졌고, 총 학습 시간을 50% 이상 단축할 수 있었습니다. 또한, GPU 메모리 사용량이 크게 줄어 더 큰 모델 또는 더 큰 배치 크기를 사용하는 것이 가능해졌습니다. 이는 모델 성능 향상으로 이어졌습니다.

5. Pros & Cons / Critical Analysis

  • Pros:
    • 획기적인 메모리 효율성: ZeRO, Offload Optimizer States 등의 기술을 통해 기존 방식으로는 학습이 불가능했던 대규모 모델을 학습할 수 있습니다.
    • 학습 속도 향상: Mixed Precision Training, Data Parallelism 등을 통해 학습 속도를 크게 향상시킬 수 있습니다.
    • PyTorch 통합: 기존 PyTorch 학습 파이프라인에 쉽게 적용할 수 있습니다.
    • 유연한 설정: 다양한 최적화 옵션을 조합하여 사용할 수 있으며, 사용자의 하드웨어 환경과 모델 특성에 맞게 최적화된 학습 환경을 구축할 수 있습니다.
  • Cons:
    • 복잡한 설정: 다양한 최적화 옵션을 이해하고 적절하게 설정하는 데 시간이 걸릴 수 있습니다. DeepSpeed configuration file을 세밀하게 조정해야 최고의 성능을 낼 수 있습니다.
    • 디버깅 어려움: 분산 학습 환경에서의 디버깅은 단일 GPU 환경보다 더 복잡할 수 있습니다.
    • 추가적인 학습 곡선: DeepSpeed를 처음 사용하는 경우, DeepSpeed의 작동 방식과 최적화 기술에 대한 이해가 필요합니다.

6. FAQ

  • Q: DeepSpeed ZeRO stage 1, 2, 3의 차이점은 무엇인가요?
    A: ZeRO stage는 메모리 최적화 수준을 나타냅니다. Stage 1은 옵티마이저 상태를 분산하고, Stage 2는 옵티마이저 상태와 그래디언트를 분산하며, Stage 3는 옵티마이저 상태, 그래디언트, 파라미터를 모두 분산합니다. Stage가 높을수록 메모리 사용량은 줄어들지만, 통신 오버헤드가 증가할 수 있습니다.
  • Q: DeepSpeed를 사용할 때 어떤 GPU를 사용하는 것이 좋나요?
    A: 일반적으로 V100, A100과 같이 메모리 용량이 큰 GPU를 사용하는 것이 좋습니다. 또한, GPU 간의 빠른 통신을 위해 NVLink가 지원되는 GPU를 사용하는 것이 좋습니다.
  • Q: DeepSpeed 설정 파일을 어떻게 최적화해야 하나요?
    A: DeepSpeed 설정 파일은 모델 크기, 데이터 크기, 하드웨어 환경 등에 따라 최적화해야 합니다. DeepSpeed documentation을 참조하여 각 옵션의 의미를 이해하고, 실험을 통해 최적의 값을 찾아야 합니다. 자동 튜닝 도구를 활용하는 것도 좋은 방법입니다.
  • Q: DeepSpeed와 Accelerate의 차이점은 무엇인가요?
    A: Accelerate는 좀 더 높은 수준의 추상화를 제공하는 라이브러리로, DeepSpeed, FairScale 등 다양한 분산 학습 백엔드를 지원합니다. DeepSpeed는 더 세부적인 제어가 필요한 경우에 사용하기 좋습니다.

7. Conclusion

DeepSpeed는 LLM 파인튜닝의 진입 장벽을 낮추고, 더 많은 개발자가 초거대 모델을 활용할 수 있도록 돕는 강력한 도구입니다. 이 글에서 소개한 기술과 지침을 바탕으로 여러분의 프로젝트에 DeepSpeed를 적용해 보세요. DeepSpeed 공식 문서를 참고하여 더 많은 기능을 살펴보고, 여러분의 필요에 맞는 최적화된 학습 환경을 구축하시길 바랍니다. 지금 바로 DeepSpeed를 사용하여 LLM 파인튜닝의 새로운 가능성을 탐색해 보세요!