Transformers documentation

Personalizando componentes do modelo

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

and get access to the augmented documentation experience

to get started

Personalizando componentes do modelo

Outra forma de personalizar um modelo é modificar seus componentes, em vez de escrever um novo modelo por completo, permitindo que você adapte o modelo ao seu caso de uso específico. Por exemplo, você pode adicionar novas camadas ou otimizar o mecanismo de atenção de uma arquitetura. As personalizações são aplicadas diretamente a um modelo do Transformers, para que você possa continuar usando recursos como Trainer, PreTrainedModel e a biblioteca PEFT.

Este guia mostrará como personalizar o mecanismo de atenção de um modelo para aplicar Low-Rank Adaptation (LoRA) a ele.

A utilidade clear_import_cache é muito útil quando você está modificando e desenvolvendo iterativamente o código de um modelo. Ela remove todos os módulos do Transformers em cache e permite que o Python recarregue o código modificado sem a necessidade de reiniciar constantemente o seu ambiente.

from transformers import AutoModel
from transformers.utils.import_utils import clear_import_cache

model = AutoModel.from_pretrained("bert-base-uncased")
# modifications to model code
# clear cache to reload modified code
clear_import_cache()
# re-import to use updated code
model = AutoModel.from_pretrained("bert-base-uncased")

Classe de atenção

Segment Anything é um modelo de segmentação de imagem e combina a projeção query-key-value (qkv) em seus mecanismos de atenção. Para reduzir o número de parâmetros treináveis e a sobrecarga computacional, você pode aplicar LoRA à projeção qkv. Isso requer dividir a projeção qkv para que você possa direcionar separadamente o q e o v com LoRA.

  1. Crie uma classe de atenção personalizada, SamVisionAttentionSplit, herdando da classe original SamVisionAttention. No __init__, remova o qkv combinado e crie uma camada linear separada para q, k e v.
import torch
import torch.nn as nn
from transformers.models.sam.modeling_sam import SamVisionAttention

class SamVisionAttentionSplit(SamVisionAttention, nn.Module):
    def __init__(self, config, window_size):
        super().__init__(config, window_size)
        # remove combined qkv
        del self.qkv
        # separate q, k, v projections
        self.q = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
        self.k = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
        self.v = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
        self._register_load_state_dict_pre_hook(self.split_q_k_v_load_hook)
  1. A função split_q_k_v_load_hook divide os pesos pré-treinados qkv em pesos separados q, k e v ao carregar o modelo, garantindo compatibilidade com qualquer modelo pré-treinado.
    def split_q_k_v_load_hook(self, state_dict, prefix, *args):
        keys_to_delete = []
        for key in list(state_dict.keys()):
            if "qkv." in key:
                # split q, k, v from the combined projection
                q, k, v = state_dict[key].chunk(3, dim=0)
                # replace with individual q, k, v projections
                state_dict[key.replace("qkv.", "q.")] = q
                state_dict[key.replace("qkv.", "k.")] = k
                state_dict[key.replace("qkv.", "v.")] = v
                # mark the old qkv key for deletion
                keys_to_delete.append(key)
        
        # remove old qkv keys
        for key in keys_to_delete:
            del state_dict[key]
  1. Na passagem forward, q, k e v são calculados separadamente, enquanto o restante do mecanismo de atenção permanece o mesmo.
    def forward(self, hidden_states: torch.Tensor, output_attentions=False) -> torch.Tensor:
        batch_size, height, width, _ = hidden_states.shape
        qkv_shapes = (batch_size *  self.num_attention_heads,  height * width, -1)
        query = self.q(hidden_states).reshape((batch_size,  height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
        key = self.k(hidden_states).reshape((batch_size,  height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
        value = self.v(hidden_states).reshape((batch_size,  height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)

        attn_weights = (query * self.scale) @ key.transpose(-2, -1)

        attn_weights = torch.nn.functional.softmax(attn_weights, dtype=torch.float32, dim=-1).to(query.dtype)
        attn_probs = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training)
        attn_output = (attn_probs @ value).reshape(batch_size, self.num_attention_heads, height, width, -1)
        attn_output = attn_output.permute(0, 2, 3, 1, 4).reshape(batch_size, height, width, -1)
        attn_output = self.proj(attn_output)

        if output_attentions:
            outputs = (attn_output, attn_weights)
        else:
            outputs = (attn_output, None)
        return outputs

Atribua a classe personalizada SamVisionAttentionSplit ao módulo SamVisionAttention dos modelos originais para substituí-lo. Todas as instâncias de SamVisionAttention no modelo são substituídas pela versão de atenção dividida.

Substitua a classe antes de chamar from_pretrained(). O hook de carregamento só é executado durante o carregamento do checkpoint, portanto, substituir os módulos em um modelo já carregado deixa as novas camadas q, k e v com inicialização aleatória.

from transformers import SamModel
from transformers.models.sam import modeling_sam

# replace the attention class the vision layers instantiate
modeling_sam.SAM_VISION_ATTENTION_CLASSES["eager"] = SamVisionAttentionSplit

# load the pretrained SAM model, the hook splits the qkv weights during loading
model = SamModel.from_pretrained("facebook/sam-vit-base", attn_implementation="eager")

LoRA

Com projeções separadas de q, k e v, aplique LoRA a q e v.

Crie um LoraConfig e especifique o rank r, lora_alpha, lora_dropout, task_type e, mais importante, os módulos a serem alvo.

from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=16,
    lora_alpha=32,
    # apply LoRA to q and v
    target_modules=["q", "v"],
    lora_dropout=0.1,
    task_type="FEATURE_EXTRACTION"
)

Passe o modelo e o LoraConfig para get_peft_model para aplicar o LoRA ao modelo.

model = get_peft_model(model, config)

Chame print_trainable_parameters para visualizar o número de parâmetros que você está treinando em relação ao número total de parâmetros.

model.print_trainable_parameters()
"trainable params: 589,824 || all params: 94,274,096 || trainable%: 0.6256"
Update on GitHub