Transformers documentation
Personalizando componentes do modelo
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.
- Crie uma classe de atenção personalizada,
SamVisionAttentionSplit, herdando da classe originalSamVisionAttention. No__init__, remova oqkvcombinado e crie uma camada linear separada paraq,kev.
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)- A função
split_q_k_v_load_hookdivide os pesos pré-treinadosqkvem pesos separadosq,kevao 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]- Na passagem
forward,q,kevsã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 outputsAtribua 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"