PEFT documentation
DeepSpeed
DeepSpeed
DeepSpeed est une bibliothèque conçue pour la vitesse et l’échelle, destinée à l’entraînement distribué de grands modèles comptant des milliards de paramètres. Son cœur est le Zero Redundancy Optimizer (ZeRO), qui découpe les états de l’optimiseur (ZeRO-1), les gradients (ZeRO-2) et les paramètres (ZeRO-3) entre les processus de parallélisme de données. Cela réduit considérablement l’utilisation de la mémoire, vous permettant d’étendre votre entraînement à des modèles de plusieurs milliards de paramètres. Pour débloquer encore plus d’efficacité mémoire, ZeRO-Offload réduit le calcul et la mémoire GPU en exploitant les ressources CPU pendant l’optimisation.
Ces deux fonctionnalités sont prises en charge dans 🤗 Accelerate, et vous pouvez les utiliser avec 🤗 PEFT.
Compatibilité avec la quantification bitsandbytes + LoRA
Le tableau ci-dessous résume la compatibilité entre LoRA de PEFT, la bibliothèque bitsandbytes et les stades Zero de DeepSpeed en ce qui concerne le fine-tuning. DeepSpeed Zero-1 et 2 n’auront aucun effet à l’inférence, car le stade 1 sharde les états de l’optimiseur et le stade 2 sharde les états de l’optimiseur et les gradients :
| Étape DeepSpeed | Est-il compatible ? |
|---|---|
| Zero-1 | 🟢 |
| Zero-2 | 🟢 |
| Zero-3 | 🟢 |
Pour DeepSpeed Stage 3 + QLoRA, veuillez consulter la section Utiliser PEFT QLoRA et DeepSpeed avec ZeRO3 pour l’ajustement fin de grands modèles sur plusieurs GPU ci-dessous.
Pour confirmer ces observations, nous avons exécuté les scripts d'exemple officiels de SFT (Supervised Fine-tuning) de la bibliothèque Transformers Reinforcement Learning (TRL) en utilisant QLoRA + PEFT et les configurations accelerate disponibles ici. Nous avons mené ces expériences sur un GPU 2x NVIDIA T4.
Utiliser PEFT et DeepSpeed avec ZeRO3 pour l’ajustement fin de grands modèles sur plusieurs appareils et plusieurs nœuds
Cette section du guide vous aide à apprendre à utiliser notre script d’entraînement DeepSpeed pour effectuer du SFT. Vous configurerez le script pour réaliser le SFT (supervised fine-tuning) du modèle Llama-70B avec LoRA et ZeRO-3 sur 8 GPU H100 80 Go sur une seule machine. Vous pouvez le configurer pour passer à l’échelle sur plusieurs machines en modifiant la configuration accelerate.
Configuration
Commencez par exécuter la commande suivante pour créer un fichier de configuration DeepSpeed avec 🤗 Accelerate. L'indicateur --config_file vous permet d'enregistrer le fichier de configuration à un emplacement spécifique, sinon il est enregistré sous la forme d'un fichier default_config.yaml dans le cache de 🤗 Accelerate.
Le fichier de configuration est utilisé pour définir les options par défaut lors du lancement du script d'entraînement.
accelerate config --config_file deepspeed_config.yaml
On vous posera quelques questions sur votre configuration, et vous devrez configurer les arguments suivants. Dans cet exemple, vous utiliserez ZeRO-3, assurez-vous donc de sélectionner ces options.
`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`.Une fois cela fait, la configuration correspondante doit ressembler à celle ci-dessous et vous pouvez la trouver dans le dossier config à deepspeed_config.yaml :
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: falseCommande de lancement
La commande de lancement est disponible dans run_peft_deepspeed.sh et est également affichée ci-dessous :
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 FalseNotez que nous utilisons LoRA avec rank=8, alpha=16 et que nous visons toutes les couches linéaires. Nous passons le fichier de configuration deepspeed et nous affinons le modèle Llama 70B sur un sous-ensemble du jeu de données ultrachat.
Les parties importantes
Plongeons un peu plus en profondeur dans le script pour voir ce qui se passe et comprendre son fonctionnement.
La première chose à savoir est que le script utilise DeepSpeed pour l'entraînement distribué, car la configuration DeepSpeed a été transmise. La classe SFTTrainer gère toute la charge de travail liée à la création du modèle PEFT à l'aide de la configuration peft transmise. Ensuite, lorsque vous appelez trainer.train(), SFTTrainer utilise en interne 🤗 Accelerate pour préparer le modèle, l'optimiseur et l'entraîneur en utilisant la configuration DeepSpeed afin de créer un moteur DeepSpeed, qui est ensuite entraîné. Le principal extrait de code est ci-dessous :
# 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()Utilisation de la mémoire
Dans l’exemple ci-dessus, la mémoire consommée par GPU est de 64 Go (80 %), comme le montre la capture d’écran ci-dessous :

Plus de ressources
Vous pouvez également consulter ce billet de blog Falcon 180B Finetuning using 🤗 PEFT and DeepSpeed sur la façon d’affiner le modèle Falcon 180B sur 16 GPU A100 répartis sur 2 machines.
Utiliser PEFT QLoRA et DeepSpeed avec ZeRO3 pour l’ajustement fin de grands modèles sur plusieurs GPU
Dans cette section, nous allons voir comment utiliser QLoRA et DeepSpeed Stage-3 pour l'ajustement fin du modèle llama 70B sur 2X40GB de GPU.
Pour cela, nous avons d'abord besoin de bitsandbytes>=0.43.3, accelerate>=1.0.1, transformers>4.44.2, trl>0.11.4 et peft>0.13.0. Nous devons définir zero3_init_flag sur true lors de l'utilisation de la configuration Accelerate. La configuration suivante peut être trouvée dans deepspeed_config_z3_qlora.yaml :
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: falseLa commande de lancement est donnée ci-dessous et est disponible dans 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"Notez le nouvel argument transmis, bnb_4bit_quant_storage_dtype, qui désigne le type de données utilisé pour l’empaquetage des paramètres 4 bits. Par exemple, lorsqu’il est défini sur bfloat16, 32/4 = 8 paramètres 4 bits sont regroupés ensemble après quantification.
En ce qui concerne le code d'entraînement, les modifications de code importantes sont les suivantes :
...
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,
)Remarquez que le dtype de AutoModelForCausalLM est identique au type de données bnb_4bit_quant_storage. C’est tout. Le reste est géré par Trainer et TRL.
Utilisation de la mémoire
Dans l’exemple ci-dessus, la mémoire consommée par GPU est de 36,6 Go. Ainsi, ce qui nécessitait 8 GPU de 80 Go avec DeepSpeed Stage 3+LoRA et quelques GPU de 80 Go avec DDP+QLoRA n’exige désormais que 2 GPU de 40 Go. Cela rend le fine-tuning de grands modèles plus accessible.
Utiliser PEFT et DeepSpeed avec ZeRO3 et CPU Offloading pour l’ajustement fin de grands modèles sur un seul GPU
Cette section du guide vous aidera à apprendre à utiliser notre script d’entraînement DeepSpeed. Vous configurerez le script pour entraîner un grand modèle de génération conditionnelle avec ZeRO-3 et CPU Offload.
💡 Pour vous aider à démarrer, consultez nos scripts d'entraînement d'exemple pour le modélisation de langage causale et la génération conditionnelle. Vous pouvez adapter ces scripts à vos propres applications ou même les utiliser tels quels si votre tâche est similaire à celle des scripts.
Configuration
Commencez par exécuter la commande suivante pour créer un fichier de configuration DeepSpeed avec 🤗 Accelerate. L'indicateur --config_file vous permet d'enregistrer le fichier de configuration à un emplacement spécifique, sinon il est enregistré sous la forme d'un fichier default_config.yaml dans le cache de 🤗 Accelerate.
Le fichier de configuration est utilisé pour définir les options par défaut lors du lancement du script d'entraînement.
accelerate config --config_file ds_zero3_cpu.yaml
On vous posera quelques questions sur votre configuration, et vous devrez configurer les arguments suivants. Dans cet exemple, vous utiliserez ZeRO-3 en combinaison avec CPU-Offload, veillez donc à sélectionner ces options.
`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. Un exemple de fichier de configuration pourrait ressembler à ce qui suit. Le point le plus important à noter est que zero_stage est défini sur 3, et que offload_optimizer_device et offload_param_device sont définis sur 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: falseLes parties importantes
Plongeons un peu plus en profondeur dans le script pour voir ce qui se passe et comprendre son fonctionnement.
Dans la fonction main, le script crée une classe Accelerator pour initialiser toutes les exigences nécessaires à l'entraînement distribué.
💡 N'hésitez pas à modifier le modèle et le jeu de données dans la fonction
main. Si le format de votre jeu de données diffère de celui du script, vous devrez peut-être écrire votre propre fonction de prétraitement.
Le script crée également une configuration pour la méthode 🤗 PEFT que vous utilisez, qui dans ce cas est LoRA. Le LoraConfig spécifie le type de tâche et des paramètres importants tels que la dimension des matrices de faible rang, le facteur d’échelle des matrices et la probabilité de dropout des couches LoRA. Si vous souhaitez utiliser une autre méthode 🤗 PEFT, assurez-vous de remplacer LoraConfig par la classe appropriée.
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
)Tout au long du script, vous verrez les fonctions main_process_first et wait_for_everyone, qui aident à contrôler et à synchroniser l’exécution des processus.
La fonction get_peft_model() prend un modèle de base et le peft_config que vous avez préparé précédemment pour créer un PeftModel :
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
+ model = get_peft_model(model, peft_config)Passez tous les objets d’entraînement pertinents à la fonction prepare de 🤗 Accelerate, qui s’assure que tout est prêt pour l’entraînement :
model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler = accelerator.prepare(
model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler
)Le morceau de code suivant vérifie si le plugin DeepSpeed est utilisé dans Accelerator, et si le plugin existe, nous vérifions si nous utilisons ZeRO-3. Ce drapeau conditionnel est utilisé lors de l'appel de la fonction generate pendant l'inférence pour synchroniser les GPU lorsque les paramètres du modèle sont partagés :
is_ds_zero_3 = False
if getattr(accelerator.state, "deepspeed_plugin", None):
is_ds_zero_3 = accelerator.state.deepspeed_plugin.zero_stage == 3À l’intérieur de la boucle d’entraînement, l’appel habituel loss.backward() est remplacé par backward de 🤗 Accelerate, qui utilise la méthode backward() appropriée en fonction de votre configuration :
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()C’est tout ! Le reste du script gère la boucle d’entraînement, l’évaluation et pousse même le modèle sur le Hub pour vous.
Entraînement
Exécutez la commande suivante pour lancer le script d’entraînement. Vous avez précédemment enregistré le fichier de configuration dans ds_zero3_cpu.yaml, vous devez donc transmettre le chemin au lanceur avec l’argument --config_file comme suit :
accelerate launch --config_file ds_zero3_cpu.yaml examples/conditional_generation/peft_lora_seq2seq_accelerate_ds_zero3_offload.py
Vous verrez des journaux de sortie qui suivent l’utilisation de la mémoire pendant l’entraînement, et une fois celui-ci terminé, le script renvoie la précision et compare les prédictions aux étiquettes :
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']Limites
- La fusion lors de l'utilisation de PEFT et de DeepSpeed n'est actuellement pas prise en charge et déclenchera une erreur.
- Lors de l’utilisation du déchargement sur CPU, les gains majeurs apportés par PEFT pour réduire les états de l’optimiseur et les gradients à ceux des poids de l’adaptateur seraient réalisés sur la RAM du CPU, et il n’y aurait pas d’économies en mémoire GPU.
- L'utilisation de DeepSpeed Stage 3 et de qlora avec le déchargement sur CPU entraîne une consommation de mémoire GPU plus élevée par rapport à la désactivation du déchargement sur CPU.
Update on GitHub💡 Lorsque votre code nécessite la fusion (et la défusion) de poids, essayez de collecter manuellement les paramètres avec DeepSpeed Zero-3 au préalable :
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()