PEFT documentation

Solução de problemas

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Solução de problemas

Se você encontrar algum problema ao usar o PEFT, verifique a lista a seguir com os problemas comuns e suas soluções.

Os exemplos não funcionam

Os exemplos geralmente dependem das versões mais recentes dos pacotes, portanto, verifique se eles estão atualizados. Em particular, confira as versões dos seguintes pacotes:

  • peft
  • transformers
  • accelerate
  • torch

Em geral, você pode atualizar a versão do pacote executando este comando dentro do seu ambiente Python:

python -m pip install -U <package_name>

Instalar o PEFT a partir do código-fonte é útil para acompanhar os últimos desenvolvimentos:

python -m pip install git+https://github.com/huggingface/peft

Problemas relacionados a Dtype

ValueError: Tentando desescalar gradientes FP16

Esse erro provavelmente ocorreu porque o modelo foi carregado com dtype=torch.float16 e, em seguida, usado em um contexto de precisão mista automática (AMP), por exemplo, ao definir fp16=True na classe Trainer do 🤗 Transformers. A razão é que, ao usar AMP, os pesos treináveis nunca devem usar fp16. Para fazer isso funcionar sem carregar o modelo inteiro em fp32, adicione o seguinte ao seu código:

peft_model = get_peft_model(...)

# add this:
for param in model.parameters():
    if param.requires_grad:
        param.data = param.data.float()

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

Alternativamente, você pode usar a função cast_mixed_precision_params() para converter os pesos corretamente:

from peft import cast_mixed_precision_params

peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

A partir da versão v0.12.0 do PEFT, o PEFT promove automaticamente o dtype dos pesos do adaptador de torch.float16 e torch.bfloat16 para torch.float32 quando apropriado. Para impedir esse comportamento, você pode passar autocast_adapter_dtype=False para ~get_peft_model(), para from_pretrained() e para load_adapter().

Selecionando o dtype do adaptador

A maioria dos métodos de PEFT, como LoRA, funciona adicionando pesos de adaptador treináveis. Por padrão, esses pesos são armazenados em dtype float32 (fp32), ou seja, em uma precisão relativamente alta. Portanto, mesmo que o modelo base seja carregado em float16 (fp16) ou bfloat16 (bf16), os pesos do adaptador serão float32. Quando os resultados do adaptador são calculados durante a passagem para frente, a entrada geralmente estará no dtype do modelo base, portanto, será convertida para float32 se necessário e, em seguida, convertida de volta para o dtype original.

Se você preferir que os pesos do adaptador estejam na menor precisão do modelo base, ou seja, em float16 ou bfloat16, você pode passar autocast_adapter_dtype=False ao criar o modelo (~get_peft_model()) ou ao carregar o modelo (from_pretrained()). Existem algumas vantagens e desvantagens para isso:

Vantagens do adaptador de meia precisão:

  • cálculo ligeiramente mais rápido
  • menos memória
  • menor tamanho de arquivo do checkpoint (metade do tamanho)

Desvantagens do adaptador de meia precisão:

  • perda ligeiramente pior
  • maior risco de estouro ou subestouro

Observe que, para a maioria dos casos de uso, o tempo de execução total e o custo de memória serão determinados pelo tamanho do modelo base e pelo conjunto de dados, enquanto o dtype do adaptador PEFT terá apenas um pequeno impacto.

O treinamento é executado, mas o modelo não melhora

Se o treinamento parece saudável (a perda diminui, os checkpoints são salvos sem erros), mas o modelo resultante não apresenta desempenho melhor que o modelo base, verifique se os parâmetros treináveis realmente mudaram durante o treinamento:

# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()

# ... train for at least three optimizer steps ...

after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))

Hooks de forward ou backward ausentes

Se os pesos não se moveram, uma causa provável é que as referências dos parâmetros foram capturadas antes que os parâmetros fossem materializados em seu dispositivo de destino. Isso pode ocorrer quando o modelo base é envolvido com get_peft_model() enquanto seus parâmetros ainda não foram movidos para o dispositivo de destino, e uma biblioteca de terceiros registra em seguida hooks de forward/backward (por exemplo, hooks de gradiente por amostra de um framework de privacidade diferencial) ou um otimizador é criado antes da primeira chamada de forward. Quando os parâmetros são materializados posteriormente, esses hooks e as referências do otimizador apontam para tensores obsoletos e cada atualização se torna um no-op silencioso.

Para evitar isso, use a seguinte ordem:

  1. Carregue o modelo base e mova-o para o dispositivo de destino (por exemplo, model.to(device)).
  2. Envolva-o com get_peft_model().
  3. Somente então registre os hooks ou crie o otimizador.

Os módulos de destino não correspondem ao modelo

Se o treinamento é executado sem erros, mas a perda quase não se move — ou o modelo não apresenta melhora após o ajuste fino —, os target_modules configurados (ou o padrão target_modules, caso o usuário não especifique o contrário) podem não corresponder aos nomes reais dos módulos no modelo. Isso é comum em arquiteturas híbridas, como Mamba, Jamba ou NemotronH, que usam nomes de camadas diferentes dos modelos Transformer padrão.

O PEFT gera um erro apenas quando nenhum dos target_modules configurados corresponde a algum módulo do modelo. Se alguns corresponderem e alguns não corresponderem, as entradas não correspondentes são ignoradas silenciosamente — não há nenhum aviso. Uma grande fração do modelo pode permanecer congelada sem qualquer indicação.

O primeiro diagnóstico é verificar a contagem de parâmetros treináveis:

from peft import LoraConfig, get_peft_model

config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()

Se o número reportado for muito menor do que o esperado, inspecione as camadas do adaptador no modelo:

print(model.get_layer_status())

Isso retorna uma lista de entradas TunerLayerStatus mostrando o nome de cada camada de adaptador, o tipo de módulo, o estado de habilitação e os adaptadores ativos. Alternativamente, imprimir o repr do modelo (print(model)) fornece uma visão geral da hierarquia completa de módulos, o que é útil para identificar os nomes corretos das camadas em arquiteturas não familiares.

Qualquer entrada de target_modules que não corresponda a um módulo no modelo é ignorada silenciosamente. Atualize target_modules para corresponder aos nomes reais dos módulos.

Esse problema afeta todos os métodos PEFT que usam target_modules (LoRA, LoHa, IA³, etc.), não apenas o LoRA. As etapas de diagnóstico são as mesmas, independentemente do método. Se você não tiver certeza de quais nomes de módulos uma determinada arquitetura usa, consulte a documentação do modelo ou inspecione model.named_modules() antes de configurar o adaptador.

Resultados ruins de um modelo PEFT carregado

Existem vários motivos para obter um resultado insatisfatório de um modelo PEFT carregado, listados abaixo. Se você ainda não conseguir resolver o problema, verifique se alguém teve um problema semelhante no GitHub e, se não encontrar nenhum, abra uma nova issue.

Ao abrir uma issue, ajuda muito se você fornecer um exemplo de código mínimo que reproduza o problema. Além disso, por favor, informe se o modelo carregado apresenta o mesmo desempenho que o modelo tinha antes do fine-tuning, se o desempenho é aleatório ou se é apenas ligeiramente inferior ao esperado. Essas informações nos ajudam a identificar o problema mais rapidamente.

Desvios aleatórios

Se as saídas do seu modelo não forem exatamente as mesmas das execuções anteriores, pode haver um problema com elementos aleatórios. Por exemplo:

  1. certifique-se de que ele esteja em modo .eval(), o que é importante, por exemplo, se o modelo usar dropout
  2. se você usa generate em um modelo de linguagem, pode haver amostragem aleatória, portanto, obter o mesmo resultado exige definir uma semente aleatória
  3. se você usou quantização e mesclou os pesos, pequenas variações são esperadas devido a erros de arredondamento

Modelo carregado incorretamente

Certifique-se de que você está carregando o modelo corretamente. Um erro comum é tentar carregar um modelo treinado com get_peft_model(), o que está incorreto. Em vez disso, o código de carregamento deve ser assim:

from peft import PeftModel, PeftConfig

base_model = ...  # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)

Caminhos de execução otimizados que contornam os adaptadores

Algumas rotas de execução otimizadas leem os pesos dos módulos diretamente, em vez de chamar seus métodos forward. Isso pode ignorar alterações feitas por adaptadores não mesclados sem aviso, mesmo que o adaptador esteja ativo. O problema pode afetar diferentes módulos e métodos PEFT.

Por exemplo, o torch.nn.TransformerEncoder nativo do PyTorch pode usar um caminho fundido durante a avaliação com torch.no_grad() ou torch.inference_mode(). Esse caminho pode ignorar adaptadores em submódulos de TransformerEncoderLayer, como adaptadores LoRA em linear1 e linear2. O treinamento pode funcionar corretamente, enquanto a avaliação ignora as alterações do adaptador. Veja issue #3976 para uma reprodução.

Se você suspeitar desse problema, primeiro verifique se o adaptador está habilitado e ativo com get_layer_status(). Compare as saídas para as mesmas entradas no modo de avaliação, com e sem o adaptador, e depois com a otimização desabilitada. Use um adaptador treinado que altere as saídas, pois adaptadores LoRA recém-inicializados geralmente não têm efeito. Pequenas diferenças numéricas são normais.

Para um adaptador não mesclado em um codificador nativo em modo eager, compare as saídas para a mesma entrada de tensor x:

import torch

model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
    with torch.inference_mode():
        torch.backends.mha.set_fastpath_enabled(True)
        fast_output = model(x)

        torch.backends.mha.set_fastpath_enabled(False)
        reference_output = model(x)
finally:
    torch.backends.mha.set_fastpath_enabled(previous)

print((fast_output - reference_output).abs().max().item())

Um flag de backend habilitado por si só não significa que o caminho fundido será executado; seu uso também depende do modelo, das entradas e das condições de tempo de execução. Inspecione a implementação de forward do módulo envolvente em busca de acesso direto a pesos ou chamadas de operadores fundidos. Tenha cuidado ao depurar com hooks de forward: registrar um hook pode, por si só, desabilitar uma otimização e alterar o comportamento que você está investigando.

Para codificadores nativos em modo eager, torch.backends.mha.set_fastpath_enabled(False) é uma solução alternativa que mantém os adaptadores sem mesclagem. Essa configuração é de escopo de processo, pode afetar outros modelos e o desempenho, e não deve ser alternada independentemente por solicitações concorrentes. Para outros caminhos otimizados, desative a otimização afetada ou use uma implementação que chame os módulos adaptados. Se o método PEFT e a configuração de quantização permitirem, você também pode mesclar os adaptadores com merge_and_unload() e usar o modelo retornado para inferência. As alterações do adaptador são então incluídas nos pesos base. A mesclagem remove os wrappers do adaptador, portanto, mantenha seus checkpoints originais se precisar alternar adaptadores ou retomar o treinamento.

Camadas inicializadas aleatoriamente

Para algumas tarefas, é importante configurar corretamente modules_to_save na configuração para levar em consideração as camadas inicializadas aleatoriamente.

Como exemplo, isso é necessário se você usar LoRA para fazer fine-tuning de um modelo de linguagem para classificação de sequência, pois o 🤗 Transformers adiciona uma cabeça de classificação inicializada aleatoriamente no topo do modelo. Se você não adicionar essa camada a modules_to_save, a cabeça de classificação não será salva. Na próxima vez que você carregar o modelo, obterá uma cabeça de classificação diferente inicializada aleatoriamente, resultando em resultados completamente diferentes.

O PEFT tenta adivinhar corretamente o modules_to_save se você fornecer o argumento task_type na configuração. Isso deve funcionar para modelos do transformers que seguem o esquema de nomenclatura padrão. No entanto, é sempre uma boa ideia verificar duas vezes, pois não podemos garantir que todos os modelos sigam o esquema de nomenclatura.

Ao carregar um modelo do Transformers que possui camadas inicializadas aleatoriamente, você deve ver um aviso semelhante a:

Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

As camadas mencionadas devem ser adicionadas a modules_to_save na configuração para evitar o problema descrito.

healthcheck() relata um aviso TASK_HEAD_NOT_SAVED para a forma mais comum desse erro: um cabeçalho de classificação de sequência, classificação de tokens ou resposta a perguntas que foi inicializado ao carregar o modelo base, mas não está em modules_to_save, por exemplo, porque task_type não foi definido. A verificação é ignorada se o modelo base não listar suas arquiteturas na configuração.

Como exemplo, ao carregar um modelo que utiliza a arquitetura DeBERTa para classificação de sequências, você verá um aviso de que os seguintes pesos foram inicializados: ['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']. A partir disso, conclui-se que as camadas classifier e pooler devem ser adicionadas a: modules_to_save=["classifier", "pooler"].

Estendendo o vocabulário

Para muitas tarefas de ajuste fino de linguagem, é necessário estender o vocabulário do modelo, pois novos tokens são introduzidos. Isso exige estender a camada de incorporação para levar em conta os novos tokens e, dependendo do método de ajuste fino, também armazenar a camada de incorporação, além dos pesos do adaptador, ao salvar o adaptador. Existem algumas maneiras de alcançar isso, ordenadas por eficácia de parâmetros:

  • tokens treináveis, treina apenas os tokens especificados, armazena opcionalmente apenas os valores atualizados
  • treinar um adaptador na matriz de incorporação, armazenando opcionalmente apenas os valores atualizados
  • full-finetuning da camada de embedding

Usando tokens treináveis

Vamos começar com os tokens treináveis, neste caso a integração do LoRA. Se você estiver interessado apenas em treinar os novos embeddings e nada mais, consulte a documentação independente.

Para habilitar o treinamento seletivo de tokens da camada de incorporação, você precisará fornecer os ids dos tokens dos tokens recém-adicionados por meio do parâmetro trainable_token_indices. Opcionalmente, você pode especificar qual camada deseja direcionar, caso haja mais de uma camada de incorporação. Para um modelo Mistral, isso poderia ser assim:

new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))

lora_config = LoraConfig(
    ...,
    trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)

Se o seu modelo usa pesos vinculados (como o lm_head), os tokens treináveis tentarão resolver esses vínculos e mantê-los atualizados também, de modo que, nesse caso, não deve ser necessário adicionar modules_to_save=["lm_head"]. Isso só funciona se o modelo usar a convenção do Transformers para vincular pesos.

Salvar o modelo com model.save_pretrained pode salvar a matriz de incorporação completa em vez de apenas a diferença, como medida de precaução, porque a matriz de incorporação foi redimensionada. Para economizar espaço, você pode desabilitar esse comportamento definindo save_embedding_layers=False ao chamar save_pretrained. Isso é seguro desde que você não modifique a matriz de incorporação por outros meios, pois tais alterações não serão rastreadas pelos tokens treináveis.

Usando um adaptador, por exemplo, LoRA

Prepare a camada de incorporação adicionando-a ao target_modules da configuração do seu adaptador. Por exemplo, a configuração do Mistral pode ser assim:

config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])

Uma vez adicionado a target_modules, o PEFT armazena automaticamente a camada de incorporação ao salvar o adaptador, caso o modelo possua get_input_embeddings e get_output_embeddings. Isso geralmente é o caso para modelos do Transformers.

Se a camada de incorporação do modelo não seguir o esquema de nomenclatura do Transformer, mas ainda assim implementar get_input_embeddings, você ainda pode salvá-la passando manualmente save_embedding_layers=True ao salvar o adaptador:

model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)

Para inferência, carregue primeiro o modelo base e redimensione-o da mesma forma que você fez antes de treinar o modelo. Depois de redimensionar o modelo base, você pode carregar o checkpoint do PEFT.

Para um exemplo completo, consulte este notebook.

Ajuste fino completo

O fine-tuning completo é mais custoso em termos de VRAM ou espaço de armazenamento, mas, se tudo mais falhar, você pode recorrer a ele e verificar se funciona para você. Para isso, adicione o nome da camada de embedding a modules_to_save. Observe que você também precisa adicionar as camadas vinculadas, por exemplo, lm_head. Exemplo para um modelo Mistral com LoRA:

config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])

Receber um aviso sobre “pesos não inicializados a partir do checkpoint do modelo”

Ao carregar seu modelo PEFT que foi treinado em uma tarefa (por exemplo, classificação), você pode receber um aviso como:

Alguns pesos de LlamaForSequenceClassification não foram inicializados a partir do checkpoint do modelo em meta-llama/Llama-3.2-1B e foram inicializados do zero: [‘score.weight’]. Você provavelmente deve TREINAR este modelo em uma tarefa de nível inferior para poder usá-lo para previsões e inferência.

Embora isso pareça assustador, é muito provável que não haja motivo para preocupação. Este aviso vem do Transformers e não é um aviso específico do PEFT. Ele informa que uma cabeça de classificação inicializada aleatoriamente (score) está acoplada ao modelo base, e que a cabeça precisa ser treinada para produzir previsões coerentes.

Quando você recebe esse aviso antes de treinar o modelo, o PEFT cuida automaticamente de tornar a cabeça de classificação treinável, desde que você tenha passado corretamente o argumento task_type para a configuração do PEFT.

from peft import LoraConfig, TaskType

lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)

Se a sua camada de classificação não seguir as convenções de nomenclatura usuais do Transformers (o que é raro), você precisa informar explicitamente ao PEFT o nome da camada em modules_to_save.

lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])

Para verificar o nome da cabeça de classificação, imprima o modelo e ele deve ser o último módulo.

Se você receber esse aviso do seu código de inferência, ou seja, após o treinamento do modelo, ao carregar o modelo PEFT, você sempre deve carregar o modelo Transformers primeiro. Como o Transformers não sabe que você carregará os pesos do PEFT em seguida, ele ainda exibe o aviso.

Como sempre, é uma boa prática garantir que o modelo funcione corretamente para inferência executando algumas validações nele.

Executar uma verificação de saúde do treinamento

Antes do treinamento, use print_healthcheck() para obter um resumo compacto e legível pelo estado do modelo PEFT. Ele é derivado das APIs de status do modelo e das camadas (veja a próxima seção) e sinaliza estados suspeitos, como adaptadores inconsistentes, adaptadores mesclados ou um número suspeito de parâmetros treináveis.

peft_model.print_healthcheck()

Preste especial atenção à presença de “Findings” no final do relatório, que é incluído se irregularidades forem encontradas.

Se você preferir uma saída em formato de dicionário, que é serializável em JSON, use healthcheck() em vez disso:

healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")

Esta verificação inspeciona apenas o modelo e seus adaptadores. Ela não valida seu loop de treinamento, otimizador ou conjunto de dados.

Verifique o status das camadas e do modelo

Às vezes, um modelo PEFT pode acabar em um estado indesejado, especialmente ao lidar com múltiplos adaptadores. Pode haver alguma confusão sobre quais adaptadores existem, qual está ativo, qual foi mesclado, etc. Para ajudar a investigar esse problema, chame os métodos get_layer_status() e get_model_status().

O método get_layer_status() fornece uma visão detalhada dos adaptadores ativos, mesclados e disponíveis de cada camada alvo.

>>> from transformers import AutoModel
>>> from peft import get_peft_model, LoraConfig

>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())

>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
 TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
...]

>>> model.get_model_status()
TunerModelStatus(
    base_model_type='T5Model',
    adapter_model_type='LoraModel',
    peft_types={'default': 'LORA'},
    trainable_params=344064,
    total_params=60855680,
    num_adapter_layers=48,
    enabled=True,
    active_adapters=['default'],
    merged_adapters=[],
    requires_grad={'default': True},
    available_adapters=['default'],
)

Na saída do estado do modelo, você deve procurar por entradas que indiquem "irregular". Isso significa que o PEFT detectou um estado inconsistente no modelo. Por exemplo, se merged_adapters="irregular", significa que, para pelo menos um adapter, ele foi mesclado em alguns módulos de destino, mas não em outros. Como resultado, os resultados de inferência provavelmente estarão incorretos.

A melhor maneira de resolver esse problema é recarregar o checkpoint do modelo completo e do(s) adaptador(es). Certifique-se de não realizar nenhuma operação incorreta no modelo, por exemplo, mesclar manualmente adaptadores em alguns módulos, mas não em outros.

Converta o status das camadas em um DataFrame do pandas para facilitar a inspeção visual.

from dataclasses import asdict
import pandas as pd

df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())

É possível obter essas informações para modelos não-PEFT, caso eles estejam usando camadas PEFT por baixo, mas algumas informações, como base_model_type ou peft_types, não podem ser determinadas nesse caso. Como exemplo, você pode chamar isso em um modelo diffusers da seguinte forma:

>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status

>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  available_adapters=['adapter-1', 'adapter-2'],
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
 TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]

>>> get_model_status(pipe.unet)
TunerModelStatus(
    base_model_type='other',
    adapter_model_type='None',
    peft_types={},
    trainable_params=797184,
    total_params=861115332,
    num_adapter_layers=128,
    enabled=True,
    active_adapters=['adapter-2'],
    merged_adapters=[],
    requires_grad={'adapter-1': False, 'adapter-2': True},
    available_adapters=['adapter-1', 'adapter-2'],
    devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)

Velocidade

Carregar os pesos do adaptador é lento

Carregar adaptadores, como pesos LoRA, geralmente deve ser rápido em comparação com o carregamento do modelo base. No entanto, podem existir casos de uso em que os pesos do adaptador sejam bastante grandes ou em que os usuários precisem carregar um grande número de adaptadores — nesse caso, o tempo de carregamento pode se acumular. A razão para isso é que os pesos do adaptador são primeiro inicializados e, em seguida, substituídos pelos pesos carregados, o que é desperdício. Para acelerar o tempo de carregamento, você pode passar o argumento low_cpu_mem_usage=True para from_pretrained() e load_adapter().

Se essa opção funcionar bem em diferentes casos de uso, ela pode se tornar o padrão para o carregamento de adaptadores no futuro.

Reprodutibilidade

Modelos que usam batch norm

Ao carregar um modelo PEFT treinado em que o modelo base usa batch norm (por exemplo, torch.nn.BatchNorm1d ou torch.nn.BatchNorm2d), você pode perceber que não consegue reproduzir exatamente os mesmos resultados. Isso ocorre porque as camadas de batch norm mantêm um registro das estatísticas de execução durante o treinamento, mas essas estatísticas não fazem parte do checkpoint do PEFT. Portanto, ao carregar o modelo PEFT, as estatísticas de execução do modelo base serão usadas (ou seja, as de antes do treinamento com PEFT).

Dependendo do seu caso de uso, isso pode não ser um grande problema. No entanto, se você precisar que suas saídas sejam 100% reproduzíveis, você pode alcançar isso adicionando as camadas de batch norm a modules_to_save. Abaixo está um exemplo disso usando resnet e LoRA. Observe que definimos modules_to_save=["classifier", "normalization"]. Precisamos do argumento "classifier" porque nossa tarefa é classificação de imagem, e adicionamos o argumento "normalization" para garantir que as camadas de batch norm sejam salvas no checkpoint do PEFT.

from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model

model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
    target_modules=["convolution"],
    modules_to_save=["classifier", "normalization"],
),

Dependendo do tipo de modelo que você usa, as camadas de batch norm podem ter nomes diferentes de "normalization", portanto, verifique se o nome corresponde à arquitetura do seu modelo.

Divergência de versão

Erro ao carregar a configuração devido a um argumento de palavra-chave inesperado

Quando você encontrar um erro como o mostrado abaixo, isso significa que o adaptador que você está tentando carregar foi treinado com uma versão mais recente do PEFT do que a versão instalada no seu sistema.

TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>

A melhor maneira de resolver esse problema é instalar a versão mais recente do PEFT:

python -m pip install -U PEFT

Se o adaptador foi treinado a partir de uma instalação de PEFT do código-fonte (uma versão não lançada do PEFT), você também precisa instalar o PEFT a partir do código-fonte.

python -m pip install -U git+https://github.com/huggingface/peft.git

Se não for possível para você atualizar o PEFT, há uma solução alternativa que você pode tentar.

Suponha que a mensagem de erro indique que o argumento de palavra-chave desconhecido é chamado de foobar. Pesquise dentro do adapter_config.json deste adaptador PEFT pela entrada foobar e exclua-a do arquivo. Em seguida, salve o arquivo e tente carregar o modelo novamente.

Esta solução funciona na maioria das vezes. Desde que seja o valor padrão para foobar, pode ser ignorado. No entanto, quando definido para algum outro valor, você obterá resultados incorretos. A atualização do PEFT é a solução recomendada.

Tratamento de adaptadores

Usando vários adaptadores ao mesmo tempo

PEFT permite que você crie mais de um adaptador no mesmo modelo. Isso pode ser útil em muitas situações. Por exemplo, para inferência, você pode querer servir dois modelos ajustados a partir do mesmo modelo base, em vez de carregar o modelo base uma vez para cada modelo ajustado, o que custaria mais memória. No entanto, vários adaptadores podem ser ativados ao mesmo tempo. Dessa forma, o modelo pode aproveitar os aprendizados de todos esses adaptadores simultaneamente. Como exemplo, se você tiver um modelo de difusão, pode querer usar um adaptador LoRA para alterar o estilo e outro diferente para alterar o assunto.

Ativar vários adaptadores ao mesmo tempo é geralmente possível em todos os métodos PEFT (LoRA, LoHa, IA³, etc.), exceto nos métodos de prompt learning (p-tuning, prefix tuning, etc.). O exemplo a seguir ilustra como fazer isso:

from transformers import AutoModelForCausalLM
from peft import PeftModel

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0)  # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])

No exemplo acima, você pode ver que precisamos chamar model.base_model.set_adapter(["default", "other"]). Por que não podemos chamar model.set_adapter(["default", "other"])? Infelizmente, isso não é possível porque, como explicado anteriormente, alguns métodos PEFT não suportam a ativação de mais de um adaptador por vez.

Também é possível treinar dois adaptadores ao mesmo tempo, mas você deve ter cuidado para garantir que os pesos de ambos os adaptadores sejam conhecidos pelo otimizador. Caso contrário, apenas um adaptador receberá atualizações.

from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)

Se agora chamarmos:

from transformers import Trainer

trainer = Trainer(model=model,  ...)
trainer.train()

ou

optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)

então o segundo adaptador LoRA ("other") não seria treinado. Isso ocorre porque ele está inativo neste momento, o que significa que o atributo requires_grad em seus parâmetros está definido como False e o otimizador o ignorará. Portanto, certifique-se de ativar todos os adaptadores que devem ser treinados antes de inicializar o otimizador:

# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model,  ...)
trainer.train()

Esta seção trata do uso de múltiplos adaptadores do mesmo tipo no mesmo modelo, por exemplo, o uso de vários adaptadores LoRA simultaneamente. Ela não se aplica ao uso de tipos diferentes de adaptadores no mesmo modelo, por exemplo, um adaptador LoRA e um adaptador LoHa. Para isso, consulte PeftMixedModel.

Mesclando apenas alguns dos adaptadores ativos

Quando os pesos do adaptador são mesclados aos pesos base, as camadas afetadas não aplicam mais nenhum adaptador durante a passagem para frente; elas utilizam apenas os pesos base mesclados. Portanto, se vários adaptadores estiverem ativos, mas apenas alguns forem mesclados, os adaptadores que ainda estão ativos, mas não foram mesclados, não são aplicados silenciosamente:

model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)

model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs)  # this is *not* the same as output_both

Aqui, output_merged é a saída do adaptador "default" sozinho, mesmo que "other" ainda esteja listado como ativo:

>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']

O mesmo se aplica quando um adaptador que não está ativo é mesclado: a partir de então, o modelo retorna a saída do adaptador mesclado e o ativo é ignorado. Nenhum erro ou aviso é emitido em nenhum dos casos. Os pesos do adaptador em si não são afetados, portanto, chamar unmerge_adapter() restaura a saída combinada.

Para evitar isso, ou combine todos os adaptadores ativos, ou seja, chame model.merge_adapter() sem adapter_names, ou desfaça a combinação antes de alterar quais adaptadores estão ativos.

Esse comportamento é consistente entre os métodos PEFT que suportam mesclagem (LoRA, LoHa, LoKr, IA³, OFT, etc.), pois todos compartilham a mesma lógica no forward pass.

Esse comportamento é confuso e está previsto para mudar na versão v1.0 do PEFT.

Update on GitHub