# Paralelismo de Dados Totalmente Fragmentado

[Fully sharded data parallel](https://pytorch.org/docs/stable/fsdp.html) (FSDP) foi desenvolvido para o treinamento distribuído de grandes modelos pré-treinados com até 1T de parâmetros. O FSDP atinge isso ao dividir (sharding) os parâmetros do modelo, os gradientes e os estados do otimizador entre processos de paralelismo de dados, e também pode descarregar (offload) os parâmetros do modelo divididos para a CPU. A eficiência de memória proporcionada pelo FSDP permite escalar o treinamento para tamanhos maiores de lote ou de modelo.

Ambas essas funcionalidades são suportadas no 🤗 Accelerate e você pode usá-las com o 🤗 PEFT.

# Use PEFT e FSDP
Esta seção do guia ajudará você a aprender como usar nosso [script de treinamento](https://github.com/huggingface/peft/blob/main/examples/sft/train.py) do DeepSpeed para realizar SFT. Você configurará o script para executar SFT (ajuste fino supervisionado) do modelo Llama-70B com LoRA e FSDP em 8 GPUs H100 de 80GB em uma única máquina. Você pode configurá-lo para escalar para múltiplas máquinas alterando a configuração do accelerate.

## Configuração

Comece executando o seguinte comando para [criar um arquivo de configuração do FSDP](https://huggingface.co/docs/accelerate/quicktour#launching-your-distributed-script) com o 🤗 Accelerate. A flag `--config_file` permite salvar o arquivo de configuração em um local específico; caso contrário, ele é salvo como um arquivo `default_config.yaml` no cache do 🤗 Accelerate.

O arquivo de configuração é usado para definir as opções padrão ao iniciar o script de treinamento.

```bash
accelerate config --config_file fsdp_config.yaml
```

Você será solicitado a responder algumas perguntas sobre sua configuração e configurar os seguintes argumentos. Neste exemplo, você responderá ao questionário conforme mostrado na imagem abaixo.

    

Criando a configuração do Accelerate para usar FSDP

Uma vez concluído, a configuração correspondente deve ter o formato abaixo e pode ser encontrada na pasta de configuração em [fsdp_config.yaml](https://github.com/huggingface/peft/blob/main/examples/sft/configs/fsdp_config.yaml):

```yml
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
  fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
  fsdp_backward_prefetch: BACKWARD_PRE
  fsdp_cpu_ram_efficient_loading: true
  fsdp_forward_prefetch: false
  fsdp_offload_params: false
  fsdp_sharding_strategy: FULL_SHARD
  fsdp_state_dict_type: SHARDED_STATE_DICT
  fsdp_sync_module_states: true
  fsdp_use_orig_params: false
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
```

## Comando de inicialização

O comando de inicialização está disponível em [run_peft_fsdp.sh](https://github.com/huggingface/peft/blob/main/examples/sft/run_peft_fsdp.sh) e também é mostrado abaixo:
```bash
accelerate launch --config_file "configs/fsdp_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-fsdp" \
--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 False
```

Observe que estamos usando LoRA com rank=8, alpha=16 e direcionando para todas as camadas lineares. Estamos passando o arquivo de configuração do FSDP e fazendo o ajuste fino do modelo Llama de 70B em um subconjunto do [conjunto de dados ultrachat](https://huggingface.co/datasets/HuggingFaceH4/ultrachat_200k).

## As partes importantes

Vamos mergulhar um pouco mais no script para que você possa ver o que está acontecendo e entender como ele funciona.

A primeira coisa a saber é que o script usa FSDP para treinamento distribuído, pois a configuração do FSDP foi passada. A classe [SFTTrainer](https://huggingface.co/docs/trl/main/en/sft_trainer#trl.SFTTrainer) cuida de todo o trabalho pesado de criar o modelo PEFT usando a configuração peft que é passada. Depois disso, quando você chama `trainer.train()`, o Trainer usa internamente o 🤗 Accelerate para preparar o modelo, o otimizador e o trainer usando a configuração do FSDP para criar um modelo envolto em FSDP, que então é treinado. O trecho principal de código está abaixo:

```python
# 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}")
if model_args.use_peft_lora:
    # handle PEFT+FSDP case
    trainer.model.print_healthcheck()
    if getattr(trainer.accelerator.state, "fsdp_plugin", None):
        from peft.utils.other import fsdp_auto_wrap_policy

        fsdp_plugin = trainer.accelerator.state.fsdp_plugin
        fsdp_plugin.auto_wrap_policy = fsdp_auto_wrap_policy(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
if trainer.is_fsdp_enabled:
    trainer.accelerator.state.fsdp_plugin.set_state_dict_type("FULL_STATE_DICT")
trainer.save_model()
```

Aqui, um ponto principal a observar atualmente ao usar FSDP com PEFT é que `use_orig_params` precisa ser `False` para realizar economias de memória de GPU. Devido a `use_orig_params=False`, a política de auto wrap para FSDP precisa mudar para que os parâmetros treináveis e não treináveis sejam embrulhados separadamente. Isso é feito pelo trecho de código abaixo, que usa a função utilitária `fsdp_auto_wrap_policy` do PEFT:

```
if getattr(trainer.accelerator.state, "fsdp_plugin", None):
    from peft.utils.other import fsdp_auto_wrap_policy

    fsdp_plugin = trainer.accelerator.state.fsdp_plugin
    fsdp_plugin.auto_wrap_policy = fsdp_auto_wrap_policy(trainer.model)
```

## Uso de memória

No exemplo acima, a memória consumida por GPU é de 72-80 GB (90-98%), conforme visto no screenshot abaixo. O leve aumento na memória da GPU no final ocorre ao salvar o modelo usando o tipo de state dict `FULL_STATE_DICT` em vez do `SHARDED_STATE_DICT`, para que o modelo tenha pesos de adaptador que podem ser carregados normalmente com o método `from_pretrained` durante a inferência:

    

Uso de memória de GPU para a execução de treinamento

# Use PEFT QLoRA e FSDP para ajuste fino de modelos grandes em várias GPUs

Nesta seção, veremos como usar QLoRA e FSDP para fazer o ajuste fino do modelo llama de 70B em GPUs 2X24GB. O [Answer.AI](https://www.answer.ai/), em colaboração com bitsandbytes e Hugging Face 🤗, disponibilizou em código aberto o código que permite o uso de FSDP+QLoRA e explicou todo o processo em seu blogpost informativo [You can now train a 70b language model at home](https://www.answer.ai/posts/2024-03-06-fsdp-qlora.html). Isso agora está integrado no ecossistema do Hugging Face.

Para isso, primeiro precisamos de `bitsandbytes>=0.43.3`, `accelerate>=1.0.1`, `transformers>4.44.2`, `trl>0.11.4` e `peft>0.13.0`. Precisamos definir `fsdp_cpu_ram_efficient_loading=true`, `fsdp_use_orig_params=false` e `fsdp_offload_params=true` (offloading para CPU) ao usar a configuração do Accelerate. Quando não estiver usando o launcher do accelerate, você pode alternativamente definir a variável de ambiente `export FSDP_CPU_RAM_EFFICIENT_LOADING=true`. Aqui, estaremos usando a configuração do accelerate e a configuração abaixo pode ser encontrada em [fsdp_config_qlora.yaml](https://github.com/huggingface/peft/blob/main/examples/sft/configs/fsdp_config_qlora.yaml):

```yml
compute_environment: LOCAL_MACHINE                                                                                                                                           
debug: false                                                                                                                                                                 
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
  fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
  fsdp_backward_prefetch: BACKWARD_PRE
  fsdp_cpu_ram_efficient_loading: true
  fsdp_forward_prefetch: false
  fsdp_offload_params: true
  fsdp_sharding_strategy: FULL_SHARD
  fsdp_state_dict_type: SHARDED_STATE_DICT
  fsdp_sync_module_states: true
  fsdp_use_orig_params: false
machine_rank: 0
main_training_function: main
mixed_precision: 'no'
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
```

O comando de lançamento é apresentado abaixo e está disponível em [run_peft_qlora_fsdp.sh](https://github.com/huggingface/peft/blob/main/examples/sft/run_peft_qlora_fsdp.sh):
```
accelerate launch --config_file "configs/fsdp_config_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-fsdp" \
--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"
```

Observe o novo argumento sendo passado, `bnb_4bit_quant_storage_dtype`, que denota o tipo de dados para empacotar os parâmetros de 4 bits. Por exemplo, quando definido como `bfloat16`, **16/4 = 4** parâmetros de 4 bits são empacotados juntos após a quantização. Ao usar treinamento com precisão mista com `bfloat16`, `bnb_4bit_quant_storage_dtype` pode ser `bfloat16` para finetuning puro em `bfloat16`, ou `float32` para precisão mista automática (isso consome mais memória de GPU). Ao usar treinamento com precisão mista com `float16`, `bnb_4bit_quant_storage_dtype` deve ser definido como `float32` para um treinamento estável com precisão mista automática.

Em termos de código de treinamento, as alterações importantes no código são:

```diff
...

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,
)
```

Observe que o `dtype` de `AutoModelForCausalLM` é o mesmo que o tipo de dados `bnb_4bit_quant_storage`. É isso. Tudo o mais é tratado pelo Trainer e pelo TRL.

## Uso de memória

No exemplo acima, a memória consumida por GPU é de **19,6 GB**, enquanto o uso de RAM da CPU fica em torno de **107 GB**. Ao desativar o offloading para a CPU, o uso de memória de GPU é de **35,6 GB/GPU**. Portanto, o que exigia 16 GPUs de 80 GB para finetuning completo, 8 GPUs de 80 GB com FSDP+LoRA e algumas GPUs de 80 GB com DDP+QLoRA, agora requer apenas 2 GPUs de 24 GB. Isso torna o finetuning de modelos grandes mais acessível.

## Mais recursos
Você também pode consultar o repositório [llama-recipes](https://github.com/facebookresearch/llama-recipes/?tab=readme-ov-file#fine-tuning) e o guia [Getting started with Llama](https://llama.meta.com/get-started/#fine-tuning) sobre como fazer fine-tuning usando FSDP e PEFT.

## Observações
1. A fusão ao usar PEFT e FSDP não é suportada no momento e resultará em erro.
2. Passar o parâmetro de configuração `modules_to_save` não é testado no momento.
3. A economia de memória de GPU ao usar CPU Offloading ainda não foi testada no momento.
4. Ao usar FSDP+QLoRA, `paged_adamw_8bit` atualmente resulta em um erro ao salvar um checkpoint.
5. O treinamento com DoRA usando FSDP deve funcionar (embora com velocidade menor do que a do LoRA). Se combinado com bitsandbytes (QDoRA), a quantização de 4 bits também deve funcionar, mas a quantização de 8 bits tem problemas conhecidos e não é recomendada.
6. Com o FSDP2, `set_adapter()` e `set_requires_grad()` re-shardam os módulos FSDP2 dentro do [PeftModel](/docs/peft/main/pt/package_reference/peft_model#peft.PeftModel), incluindo o próprio `PeftModel`, para que as alterações em `requires_grad` alcancem os parâmetros particionados. A saída de `disable_adapter()` só faz isso se o FSDP2 substituiu os objetos de parâmetro registrados dentro dele, como um forward faz com parâmetros particionados, ou se a reativação altera `requires_grad` em relação ao valor que ele tinha na entrada. Módulos FSDP2 que particionam parâmetros de uma camada de adaptador mesclado não são re-shardados, portanto, desmescle antes de chamar esses métodos. Com `reshard_after_forward=False`, ou para o módulo raiz com o valor padrão `None`, o próximo forward faz o all-gather novamente. Módulos FSDP2 que envolvem o `PeftModel` não são re-shardados; chame `reshard()` neles antes desses métodos. Como essas chamadas podem deixar os pesos base particionados, mesclar imediatamente depois delas não funciona corretamente (veja 1). Métodos de aprendizado de prompt e adaption prompt ainda não são cobertos.

