PEFT documentation
DeepSpeed
DeepSpeed
DeepSpeed는 수십억 개의 파라미터를 가진 대규모 모델의 분산 학습을 위해 속도와 규모에 초점을 맞춰 설계된 라이브러리입니다. 그 핵심은 옵티마이저 상태(ZeRO-1), 그라디언트(ZeRO-2), 파라미터(ZeRO-3)를 데이터 병렬 프로세스에 걸쳐 샤딩하는 Zero Redundancy Optimizer(ZeRO)입니다. 이를 통해 메모리 사용량이 크게 줄어 수십억 파라미터 모델까지 학습을 확장할 수 있습니다. 더 높은 메모리 효율성을 위해 ZeRO-Offload는 최적화 단계에서 CPU 리소스를 활용하여 GPU의 연산과 메모리 부담을 줄입니다.
이 두 가지 기능은 모두 🤗 Accelerate에서 지원되며, 🤗 PEFT와 함께 사용할 수 있습니다.
bitsandbytes 양자화 + LoRA와의 호환성
아래 표는 PEFT의 LoRA, bitsandbytes 라이브러리 및 DeepSpeed Zero 단계 간의 미세 조정 관련 호환성을 요약합니다. DeepSpeed Zero-1과 2는 추론 시 영향을 미치지 않으며, 1단계는 옵티마이저 상태를, 2단계는 옵티마이저 상태와 그래디언트를 샤딩하기 때문입니다:
| DeepSpeed 단계 | 호환 가능한가요? |
|---|---|
| Zero-1 | 🟢 |
| Zero-2 | 🟢 |
| Zero-3 | 🟢 |
DeepSpeed Stage 3 + QLoRA의 경우, 아래 여러 GPU에서 PEFT QLoRA와 ZeRO3를 사용하여 DeepSpeed로 대형 모델 파인튜닝하기 섹션을 참조하세요.
이러한 관찰을 확인하기 위해, Transformers Reinforcement Learning (TRL) library의 SFT(Supervised Fine-tuning) 공식 예제 스크립트를 QLoRA + PEFT와 함께 여기에서 제공되는 accelerate 설정을 사용하여 실행했습니다. 이 실험은 2x NVIDIA T4 GPU에서 수행되었습니다.
PEFT와 DeepSpeed의 ZeRO3를 사용하여 여러 디바이스와 여러 노드에서 대규모 모델을 파인튜닝합니다
이 가이드 섹션에서는 DeepSpeed 학습 스크립트를 사용하여 SFT를 수행하는 방법을 배울 수 있습니다. 스크립트를 구성하여 단일 머신에서 8xH100 80GB GPU로 Llama-70B 모델에 LoRA와 ZeRO-3를 적용한 SFT(지도 기반 미세 조정)를 수행하도록 설정합니다. accelerate config를 변경하여 여러 머신으로 확장하도록 구성할 수 있습니다.
구성
🤗 Accelerate를 사용하여 DeepSpeed 구성 파일을 생성하는 다음 명령을 실행하여 시작합니다. --config_file 플래그를 사용하면 구성 파일을 특정 위치에 저장할 수 있으며, 그렇지 않으면 🤗 Accelerate 캐시에 default_config.yaml 파일로 저장됩니다.
설정 파일은 학습 스크립트를 실행할 때 기본 옵션을 설정하는 데 사용됩니다.
accelerate config --config_file deepspeed_config.yaml
설정에 대한 몇 가지 질문이 표시됩니다. 다음 인수를 구성하세요. 이 예제에서는 ZeRO-3를 사용하므로 해당 옵션을 선택하도록 하세요.
`zero_stage`: [0] Disabled, [1] optimizer state partitioning, [2] optimizer+gradient state partitioning and [3] optimizer+gradient+parameter partitioning
`gradient_accumulation_steps`: Number of training steps to accumulate gradients before averaging and applying them. Pass the same value as you would pass via cmd argument else you will encounter mismatch error.
`gradient_clipping`: Enable gradient clipping with value. Don't set this as you will be passing it via cmd arguments.
`offload_optimizer_device`: [none] Disable optimizer offloading, [cpu] offload optimizer to CPU, [nvme] offload optimizer to NVMe SSD. Only applicable with ZeRO >= Stage-2. Set this as `none` as don't want to enable offloading.
`offload_param_device`: [none] Disable parameter offloading, [cpu] offload parameters to CPU, [nvme] offload parameters to NVMe SSD. Only applicable with ZeRO Stage-3. Set this as `none` as don't want to enable offloading.
`zero3_init_flag`: Decides whether to enable `deepspeed.zero.Init` for constructing massive models. Only applicable with ZeRO Stage-3. Set this to `True`.
`zero3_save_16bit_model`: Decides whether to save 16-bit model weights when using ZeRO Stage-3. Set this to `True`.
`mixed_precision`: `no` for FP32 training, `fp16` for FP16 mixed-precision training and `bf16` for BF16 mixed-precision training. Set this to `True`.이 작업이 완료되면 해당 config는 다음과 같이 보이며 deepspeed_config.yaml 파일은 config 폴더에서 찾을 수 있습니다:
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_multinode_launcher: standard
gradient_accumulation_steps: 4
offload_optimizer_device: none
offload_param_device: none
zero3_init_flag: true
zero3_save_16bit_model: true
zero_stage: 3
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false시작 명령
실행 명령은 run_peft_deepspeed.sh에서 사용할 수 있으며, 아래에도 표시됩니다:
accelerate launch --config_file "configs/deepspeed_config.yaml" train.py \
--seed 100 \
--model_name_or_path "meta-llama/Llama-2-70b-hf" \
--dataset_name "smangrul/ultrachat-10k-chatml" \
--chat_template_format "chatml" \
--add_special_tokens False \
--append_concat_token False \
--splits "train,test" \
--max_seq_len 2048 \
--num_train_epochs 1 \
--logging_steps 5 \
--log_level "info" \
--logging_strategy "steps" \
--eval_strategy "epoch" \
--save_strategy "epoch" \
--push_to_hub \
--hub_private_repo True \
--hub_strategy "every_save" \
--bf16 True \
--packing True \
--learning_rate 1e-4 \
--lr_scheduler_type "cosine" \
--weight_decay 1e-4 \
--warmup_steps 0 \
--max_grad_norm 1.0 \
--output_dir "llama-sft-lora-deepspeed" \
--per_device_train_batch_size 8 \
--per_device_eval_batch_size 8 \
--gradient_accumulation_steps 4 \
--gradient_checkpointing True \
--use_reentrant False \
--dataset_text_field "content" \
--use_flash_attn True \
--use_peft_lora True \
--lora_r 8 \
--lora_alpha 16 \
--lora_dropout 0.1 \
--lora_target_modules "all-linear" \
--use_4bit_quantization Falserank=8, alpha=16을 사용하고 모든 linear 레이어를 대상으로 하는 LoRA를 사용하고 있음을 주목하세요. deepspeed 설정 파일을 전달하고 ultrachat 데이터셋의 하위 집합에서 70B Llama 모델을 fine-tuning하고 있습니다.
중요한 부분
스크립트를 조금 더 자세히 살펴보겠습니다. 어떻게 동작하는지 이해할 수 있도록 내부 구조를 확인해 보겠습니다.
가장 먼저 알아둘 점은, DeepSpeed 설정이 전달되었으므로 스크립트가 분산 학습에 DeepSpeed를 사용한다는 것입니다. SFTTrainer 클래스는 전달된 peft 설정을 사용하여 PEFT 모델을 생성하는 모든 핵심 작업을 처리합니다. 이후 trainer.train()을 호출하면, SFTTrainer는 내부적으로 🤗 Accelerate를 사용하여 DeepSpeed 설정에 따라 모델을 준비하고, DeepSpeed 엔진을 생성한 후 이를 학습합니다. 주요 코드 스니펫은 다음과 같습니다:
# trainer
trainer = SFTTrainer(
model=model,
processing_class=tokenizer,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
peft_config=peft_config,
)
trainer.accelerator.print(f"{trainer.model}")
# train
checkpoint = None
if training_args.resume_from_checkpoint is not None:
checkpoint = training_args.resume_from_checkpoint
trainer.train(resume_from_checkpoint=checkpoint)
# saving final model
trainer.save_model()메모리 사용량
위의 예시에서 아래 스크린샷과 같이 GPU당 소비되는 메모리는 64 GB(80%)입니다:

추가 리소스
You can also refer this blog post Falcon 180B Finetuning using 🤗 PEFT and DeepSpeed on how to finetune 180B Falcon model on 16 A100 GPUs on 2 machines.
PEFT QLoRA와 DeepSpeed ZeRO3를 사용하여 여러 GPU에서 대규모 모델을 파인튜닝합니다
이 섹션에서는 2X40GB GPU에서 70B Llama 모델을 fine-tuning하기 위해 QLoRA와 DeepSpeed Stage-3를 사용하는 방법을 살펴봅니다. 이를 위해 먼저 bitsandbytes>=0.43.3, accelerate>=1.0.1, transformers>4.44.2, trl>0.11.4 및 peft>0.13.0가 필요합니다. Accelerate config를 사용할 때 zero3_init_flag를 true로 설정해야 합니다. 아래는 deepspeed_config_z3_qlora.yaml에서 찾을 수 있는 config입니다:
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_multinode_launcher: standard
offload_optimizer_device: none
offload_param_device: none
zero3_init_flag: true
zero3_save_16bit_model: true
zero_stage: 3
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 2
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false아래는 run_peft_qlora_deepspeed_stage3.sh에서 사용할 수 있는 실행 명령입니다:
accelerate launch --config_file "configs/deepspeed_config_z3_qlora.yaml" train.py \
--seed 100 \
--model_name_or_path "meta-llama/Llama-2-70b-hf" \
--dataset_name "smangrul/ultrachat-10k-chatml" \
--chat_template_format "chatml" \
--add_special_tokens False \
--append_concat_token False \
--splits "train,test" \
--max_seq_len 2048 \
--num_train_epochs 1 \
--logging_steps 5 \
--log_level "info" \
--logging_strategy "steps" \
--eval_strategy "epoch" \
--save_strategy "epoch" \
--push_to_hub \
--hub_private_repo True \
--hub_strategy "every_save" \
--bf16 True \
--packing True \
--learning_rate 1e-4 \
--lr_scheduler_type "cosine" \
--weight_decay 1e-4 \
--warmup_steps 0 \
--max_grad_norm 1.0 \
--output_dir "llama-sft-qlora-dsz3" \
--per_device_train_batch_size 2 \
--per_device_eval_batch_size 2 \
--gradient_accumulation_steps 2 \
--gradient_checkpointing True \
--use_reentrant True \
--dataset_text_field "content" \
--use_flash_attn True \
--use_peft_lora True \
--lora_r 8 \
--lora_alpha 16 \
--lora_dropout 0.1 \
--lora_target_modules "all-linear" \
--use_4bit_quantization True \
--use_nested_quant True \
--bnb_4bit_compute_dtype "bfloat16" \
--bnb_4bit_quant_storage_dtype "bfloat16"4-bit 파라미터를 패킹할 때 사용할 데이터 타입을 나타내는 새로운 인자 bnb_4bit_quant_storage_dtype가 전달되는 것을 확인하세요. 예를 들어, 이 값이 bfloat16로 설정되면 양자화 후 32/4 = 8개의 4-bit 파라미터가 함께 패킹됩니다.
학습 코드 측면에서 중요한 코드 변경 사항은 다음과 같습니다:
...
bnb_config = BitsAndBytesConfig(
load_in_4bit=args.use_4bit_quantization,
bnb_4bit_quant_type=args.bnb_4bit_quant_type,
bnb_4bit_compute_dtype=compute_dtype,
bnb_4bit_use_double_quant=args.use_nested_quant,
+ bnb_4bit_quant_storage=quant_storage_dtype,
)
...
model = AutoModelForCausalLM.from_pretrained(
args.model_name_or_path,
quantization_config=bnb_config,
trust_remote_code=True,
attn_implementation="flash_attention_2" if args.use_flash_attn else "eager",
+ dtype=quant_storage_dtype or torch.float32,
)AutoModelForCausalLM의 dtype이 bnb_4bit_quant_storage 데이터 타입과 동일하다는 점에 유의하세요. 이 정도면 충분합니다. 나머지는 Trainer와 TRL이 처리합니다.
메모리 사용량
위 예시에서 GPU당 소비되는 메모리는 36.6 GB입니다. 따라서 DeepSpeed Stage 3+LoRA를 사용할 때 8X80GB GPU가 필요했던 작업과 DDP+QLoRA를 사용할 때 80GB GPU 몇 개가 필요했던 작업이 이제 2X40GB GPU로 가능합니다. 이를 통해 대규모 모델의 파인튜닝이 더 쉽게 접근할 수 있게 되었습니다.
PEFT와 DeepSpeed의 ZeRO3 및 CPU Offloading를 사용하여 단일 GPU에서 대규모 모델을 파인튜닝합니다
이 가이드 섹션에서는 DeepSpeed 학습 스크립트를 사용하는 방법을 배울 수 있습니다. ZeRO-3와 CPU Offload를 사용하여 조건 생성을 위한 대규모 모델을 학습하도록 스크립트를 구성합니다.
💡 시작을 돕기 위해 causal language modeling과 conditional generation의 예제 학습 스크립트를 확인해 보세요. 이러한 스크립트는 자체 애플리케이션에 맞게 수정하거나, 작업이 스크립트의 작업과 유사하다면 그대로 사용할 수 있습니다.
구성
🤗 Accelerate를 사용하여 DeepSpeed 구성 파일을 생성하는 다음 명령을 실행하여 시작합니다. --config_file 플래그를 사용하면 구성 파일을 특정 위치에 저장할 수 있으며, 그렇지 않으면 🤗 Accelerate 캐시에 default_config.yaml 파일로 저장됩니다.
설정 파일은 학습 스크립트를 실행할 때 기본 옵션을 설정하는 데 사용됩니다.
accelerate config --config_file ds_zero3_cpu.yaml
설정에 대한 몇 가지 질문이 표시됩니다. 다음 인수를 구성하세요. 이 예제에서는 ZeRO-3와 CPU-Offload를 함께 사용하므로 해당 옵션을 선택하도록 하세요.
`zero_stage`: [0] Disabled, [1] optimizer state partitioning, [2] optimizer+gradient state partitioning and [3] optimizer+gradient+parameter partitioning
`gradient_accumulation_steps`: Number of training steps to accumulate gradients before averaging and applying them.
`gradient_clipping`: Enable gradient clipping with value.
`offload_optimizer_device`: [none] Disable optimizer offloading, [cpu] offload optimizer to CPU, [nvme] offload optimizer to NVMe SSD. Only applicable with ZeRO >= Stage-2.
`offload_param_device`: [none] Disable parameter offloading, [cpu] offload parameters to CPU, [nvme] offload parameters to NVMe SSD. Only applicable with ZeRO Stage-3.
`zero3_init_flag`: Decides whether to enable `deepspeed.zero.Init` for constructing massive models. Only applicable with ZeRO Stage-3.
`zero3_save_16bit_model`: Decides whether to save 16-bit model weights when using ZeRO Stage-3.
`mixed_precision`: `no` for FP32 training, `fp16` for FP16 mixed-precision training and `bf16` for BF16 mixed-precision training. 예를 들어 설정 파일은 다음과 같은 형태일 수 있습니다. 가장 중요한 점은 zero_stage이 3으로 설정되어 있고, offload_optimizer_device와 offload_param_device가 cpu로 설정되어 있다는 것입니다.
compute_environment: LOCAL_MACHINE
deepspeed_config:
gradient_accumulation_steps: 1
gradient_clipping: 1.0
offload_optimizer_device: cpu
offload_param_device: cpu
zero3_init_flag: true
zero3_save_16bit_model: true
zero_stage: 3
distributed_type: DEEPSPEED
downcast_bf16: 'no'
dynamo_backend: 'NO'
fsdp_config: {}
machine_rank: 0
main_training_function: main
megatron_lm_config: {}
mixed_precision: 'no'
num_machines: 1
num_processes: 1
rdzv_backend: static
same_network: true
use_cpu: false중요한 부분
스크립트를 조금 더 자세히 살펴보겠습니다. 어떻게 동작하는지 이해할 수 있도록 내부 구조를 확인해 보겠습니다.
main 함수 내에서 스크립트는 분산 학습에 필요한 모든 요구 사항을 초기화하기 위해 Accelerator 클래스를 생성합니다.
💡
main함수 내에서 모델과 데이터셋을 자유롭게 변경할 수 있습니다. 데이터셋 형식이 스크립트의 형식과 다르면 자체 전처리 함수를 작성해야 할 수도 있습니다.
스크립트는 사용하는 🤗 PEFT 방법, 이 경우 LoRA에 대한 구성도 생성합니다. LoraConfig는 작업 유형과 저순위 행렬의 차원, 행렬 스케일링 계수, LoRA 레이어의 드롭아웃 확률과 같은 중요한 매개변수를 지정합니다. 다른 🤗 PEFT 방법을 사용하려면 LoraConfig를 적절한 class로 교체해야 합니다.
def main():
+ accelerator = Accelerator()
model_name_or_path = "facebook/bart-large"
dataset_name = "twitter_complaints"
+ peft_config = LoraConfig(
task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1
)Throughout the script, you’ll see the main_process_first and wait_for_everyone functions which help control and synchronize when processes are executed.
get_peft_model() 함수는 기본 모델과 이전에 준비한 peft_config를 받아 PeftModel을 생성합니다:
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
+ model = get_peft_model(model, peft_config)🤗 Accelerate의 prepare에 모든 관련 학습 객체를 전달하여 학습 준비가 완료되었는지 확인합니다:
model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler = accelerator.prepare(
model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler
)다음 코드 조각은 Accelerator에서 DeepSpeed 플러그인이 사용되는지 확인합니다. 플러그인이 존재하는 경우 ZeRO-3을 사용하는지 확인합니다. 이 조건 플래그는 모델 파라미터가 샤딩된 상태에서 추론 시 generate 함수를 호출하여 GPU를 동기화할 때 사용됩니다:
is_ds_zero_3 = False
if getattr(accelerator.state, "deepspeed_plugin", None):
is_ds_zero_3 = accelerator.state.deepspeed_plugin.zero_stage == 3학습 루프 내부에서 일반적인 loss.backward()는 설정에 따라 올바른 backward() 메서드를 사용하는 🤗 Accelerate의 backward로 대체됩니다:
for epoch in range(num_epochs):
with TorchTracemalloc() as tracemalloc:
model.train()
total_loss = 0
for step, batch in enumerate(tqdm(train_dataloader)):
outputs = model(**batch)
loss = outputs.loss
total_loss += loss.detach().float()
+ accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()이것으로 끝입니다! 나머지 스크립트는 학습 루프, 평가, 그리고 Hub에 푸시하는 작업까지 자동으로 처리합니다.
학습
학습 스크립트를 실행하려면 다음 명령을 실행하세요. 앞서 구성 파일을 ds_zero3_cpu.yaml로 저장했으므로, --config_file 인수를 사용하여 런처에 경로를 전달해야 합니다.
accelerate launch --config_file ds_zero3_cpu.yaml examples/conditional_generation/peft_lora_seq2seq_accelerate_ds_zero3_offload.py
학습 중 메모리 사용량을 추적하는 출력 로그를 확인하게 되며, 완료되면 스크립트가 정확도를 반환하고 예측값을 라벨과 비교합니다:
GPU Memory before entering the train : 1916
GPU Memory consumed at the end of the train (end-begin): 66
GPU Peak Memory consumed during the train (max-begin): 7488
GPU Total Peak Memory consumed during the train (max): 9404
CPU Memory before entering the train : 19411
CPU Memory consumed at the end of the train (end-begin): 0
CPU Peak Memory consumed during the train (max-begin): 0
CPU Total Peak Memory consumed during the train (max): 19411
epoch=4: train_ppl=tensor(1.0705, device='cuda:0') train_epoch_loss=tensor(0.0681, device='cuda:0')
100%|████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:27<00:00, 3.92s/it]
GPU Memory before entering the eval : 1982
GPU Memory consumed at the end of the eval (end-begin): -66
GPU Peak Memory consumed during the eval (max-begin): 672
GPU Total Peak Memory consumed during the eval (max): 2654
CPU Memory before entering the eval : 19411
CPU Memory consumed at the end of the eval (end-begin): 0
CPU Peak Memory consumed during the eval (max-begin): 0
CPU Total Peak Memory consumed during the eval (max): 19411
accuracy=100.0
eval_preds[:10]=['no complaint', 'no complaint', 'complaint', 'complaint', 'no complaint', 'no complaint', 'no complaint', 'complaint', 'complaint', 'no complaint']
dataset['train'][label_column][:10]=['no complaint', 'no complaint', 'complaint', 'complaint', 'no complaint', 'no complaint', 'no complaint', 'complaint', 'complaint', 'no complaint']주의 사항
- PEFT와 DeepSpeed를 함께 사용할 때의 병합은 현재 지원되지 않으며 오류가 발생합니다.
- CPU 오프로딩을 사용할 경우, PEFT를 사용해 옵티마이저 상태와 그래디언트를 어댑터 가중치 수준으로 줄이는 것에서 얻는 주요 이점은 CPU RAM에서 실현되며 GPU 메모리 측면에서는 절감 효과가 없습니다.
- CPU 오프로딩을 사용하면서 DeepSpeed Stage 3과 qlora를 함께 사용하면, CPU 오프로딩을 비활성화한 경우보다 GPU 메모리 사용량이 더 증가합니다.
Update on GitHub💡 가중치 병합(및 병합 해제)이 필요한 코드가 있는 경우, 사전에 DeepSpeed Zero-3를 사용하여 매개변수를 수동으로 수집해 보세요:
import deepspeed is_ds_zero_3 = ... # check if Zero-3 with deepspeed.zero.GatheredParameters(list(model.parameters()), enabled= is_ds_zero_3): model.merge_adapter() # do whatever is needed, then unmerge in the same context if unmerging is required ... model.unmerge_adapter()