1 pontos por GN⁺ 2024-01-23 | 1 comentários | Compartilhar no WhatsApp
  • LoRA (Low-Rank Adaptation) é uma técnica que reduz o custo de fine-tuning ao atualizar apenas pequenas matrizes de baixa ordem, sem retreinar toda a LLM, e este Studio implementa as camadas LoRA manualmente para verificar seu funcionamento
  • A ideia central é aproximar a mudança de pesos ΔW do fine-tuning convencional pelo produto de duas matrizes pequenas, A e B; quanto menor o rank r, menores são tanto os parâmetros treináveis quanto a capacidade de representação
  • Uma matriz de pesos 5.000×10.000 tem 50 milhões de parâmetros, mas um LoRA com r=8 adiciona apenas B 5.000×8 e A 8×10.000, totalizando 120 mil parâmetros e ficando 400 vezes menor
  • Na classificação de sentimento do IMDb com base em DistilBERT, o LoRA básico registrou Test acc 89,44%, acima dos 86,22% obtidos treinando só as duas últimas camadas, mas abaixo dos 92,31% do fine-tuning completo
  • Após busca de hiperparâmetros, o LoRA alcançou Val acc 92,96% e Test acc 92,39% com cerca de 500 mil parâmetros treináveis, superando ligeiramente a precisão do fine-tuning completo, que treinou 66.955.010 parâmetros

Como o LoRA reduz o custo de fine-tuning

  • LoRA é a sigla para Low-Rank Adaptation, uma técnica para fazer fine-tuning de LLMs com mais eficiência
  • No fine-tuning convencional, todos os parâmetros do modelo são ajustados; no LoRA, apenas um pequeno conjunto de matrizes de baixa ordem é atualizado
  • LLMs pré-treinadas podem ser usadas em várias tarefas, mas o fine-tuning é útil para adaptá-las a datasets ou tarefas específicas
  • À medida que o modelo cresce, atualizar todas as camadas aumenta bastante o custo computacional

Aproximando ΔW com o produto de matrizes pequenas

  • No fine-tuning convencional, a atualização da matriz de pesos W é calculada como ΔW
  • O LoRA aproxima ΔW pelo produto de duas matrizes pequenas, A e B
    • Se você estiver familiarizado com PCA ou SVD, isso pode ser visto como algo próximo de decompor ΔW em A e B
  • O rank r é um hiperparâmetro do LoRA
    • Um r menor reduz o número de parâmetros treináveis, acelera o treinamento e diminui a demanda computacional
    • Ao mesmo tempo, também reduz a capacidade da matriz de baixa ordem de capturar informações específicas da tarefa
  • Exemplo com uma matriz de pesos 5.000×10.000:
    • Atualização convencional ΔW: total de 50 milhões de parâmetros
    • LoRA com r=8: B 5.000×8, A 8×10.000
    • Parâmetros adicionais: 80.000 + 40.000 = 120.000
    • 400 vezes menor que o fine-tuning convencional
  • No uso real, é preciso testar vários valores de r para encontrar o equilíbrio entre desempenho e custo

Implementando uma camada LoRA em PyTorch

  • A LoRALayer básica recebe dimensão de entrada, dimensão de saída, rank e o fator de escala alpha
class LoRALayer(torch.nn.Module):
    def __init__(self, in_dim, out_dim, rank, alpha):
        super().__init__()
        std_dev = 1 / torch.sqrt(torch.tensor(rank).float())
        self.A = torch.nn.Parameter(torch.randn(in_dim, rank) * std_dev)
        self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
        self.alpha = alpha

    def forward(self, x):
        x = self.alpha * (x @ self.A @ self.B)
        return x
  • in_dim é a dimensão de entrada da camada à qual o LoRA será aplicado, e out_dim é a dimensão de saída
  • rank controla a complexidade das matrizes A e B e o número de parâmetros adicionados pelo LoRA
  • alpha determina a magnitude da alteração que o LoRA introduz nos pesos do modelo original
    • Um alpha mais alto ajusta o comportamento do modelo de forma mais intensa
    • Um alpha mais baixo produz mudanças mais sutis
  • A é inicializada com pequenos valores aleatórios, e o desvio padrão é definido pela raiz quadrada do rank
    • Isso ajuda a evitar que os valores iniciais de A fiquem grandes demais
  • B é inicializada com zero
    • Antes do início do treinamento, B=0, então AB=0
    • Até que A e B sejam atualizadas por backpropagation, a LoRALayer não afeta os pesos originais

Trocando uma camada Linear por LinearWithLoRA

  • O LoRA normalmente é aplicado às camadas Linear/feedforward da rede neural
  • Se o forward original chama duas camadas Linear em sequência, após aplicar LoRA soma-se a saída do LoRA à saída de cada Linear
def forward(self, x):
    x = self.linear_1(x) + self.lora_1(x)
    x = F.relu(x)
    x = self.linear_2(x) + self.lora_2(x)
    return logits
  • Ao modificar um modelo PyTorch existente, uma abordagem simples é substituir cada camada Linear por LinearWithLoRA
class LinearWithLoRA(torch.nn.Module):
    def __init__(self, linear, rank, alpha):
        super().__init__()
        self.linear = linear
        self.lora = LoRALayer(
            linear.in_features, linear.out_features, rank, alpha
        )

    def forward(self, x):
        return self.linear(x) + self.lora(x)
  • LinearWithLoRA mantém a camada Linear original junto com a nova LoRALayer
  • Ao substituir as camadas Linear de um modelo pré-treinado por LinearWithLoRA, é possível acoplar LoRA e depois fazer o fine-tuning

Experimento de classificação no IMDb com DistilBERT

  • O exemplo prático usa classificação de texto, que é mais fácil de avaliar em termos de acurácia do que geração de texto
  • O modelo usa o DistilBERT pré-treinado da biblioteca Hugging Face transformers
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(
    "distilbert-base-uncased", num_labels=2)
  • Para treinar apenas os novos pesos do LoRA, requires_grad é definido como False para todos os parâmetros do modelo
for param in model.parameters():
    param.requires_grad = False
  • O DistilBERT tem 6 camadas Transformer, e cada uma contém camadas Linear
    • na attention há q_lin, k_lin, v_lin, out_lin
    • na FFN há lin1, lin2
    • na saída há duas camadas Linear: pre_classifier e classifier

Configuração para aplicar LoRA seletivamente

  • A configuração básica do LoRA aplica LoRA apenas às matrizes de pesos de query e value da attention
lora_r = 8
lora_alpha = 16
lora_dropout = 0.05
lora_query = True
lora_key = False
lora_value = True
lora_projection = False
lora_mlp = False
lora_head = False
  • Em um loop, as camadas Linear selecionadas de cada camada Transformer do DistilBERT são substituídas por LinearWithLoRA
    • se lora_query=True, substitui q_lin
    • se lora_key=True, substitui k_lin
    • se lora_value=True, substitui v_lin
    • se lora_projection=True, substitui out_lin
    • se lora_mlp=True, substitui ffn.lin1 e ffn.lin2
    • se lora_head=True, substitui pre_classifier e classifier
  • Após a substituição, é possível confirmar na saída do modelo que q_lin, v_lin etc. foram trocadas por LinearWithLoRA

Comparando LoRA básico e fine-tuning convencional

  • Resultado do treinamento da classificação de IMDb Movie Reviews com a configuração básica do LoRA:
    • Train acc: 92,15%
    • Val acc: 89,98%
    • Test acc: 89,44%
  • Resultado treinando apenas as duas últimas camadas de saída:
    • Train acc: 86,68%
    • Val acc: 87,26%
    • Test acc: 86,22%
    • Número de parâmetros treináveis: 592.130
  • O LoRA básico teve Test acc maior do que treinar só as duas últimas camadas, com apenas 147.456 parâmetros treináveis
  • Resultado do fine-tuning tradicional de todas as camadas:
    • Train acc: 96,41%
    • Val acc: 92,80%
    • Test acc: 92,31%
    • Número de parâmetros treináveis: 66.955.010
  • O fine-tuning completo teve Test acc cerca de 2% maior que o LoRA básico, mas atualizou cerca de 450 vezes mais parâmetros

Busca de hiperparâmetros do LoRA

  • O desempenho do LoRA pode variar conforme lora_r, lora_alpha e as camadas em que ele é aplicado
  • 03_finetune-lora.py recebe hiperparâmetros como argumentos de linha de comando
python 03_finetune-lora.py --lora_alpha 32 --lora_r 16
  • Também é possível ativar outros alvos de aplicação do LoRA ao mesmo tempo
python 03_finetune-lora.py \
--lora_alpha 32 \
--lora_r 16 \
--lora_query True \
--lora_key True \
--lora_value True \
--lora_projection True \
--lora_mlp True \
--lora_head True
  • 03_gridsearch.py executa o seguinte grid em todas as GPUs disponíveis
    • alpha_values = [1, 4, 8, 16, 32, 64]
    • rank_values = [1, 2, 4, 8, 16, 32]
    • lora_query = ["True"]
    • lora_key = ["False", "True"]
    • lora_value = ["True"]
    • lora_projection = ["False", "True"]
    • lora_mlp = ["False", "True"]
    • lora_head = ["False", "True"]
  • O script pode ser executado no Visual Studio Code, no terminal de linha de comando ou como Job, e o Job é encerrado automaticamente após a conclusão
  • Os resultados são salvos em results.txt

Melhor configuração encontrada na busca em grade

  • Segundo results.txt, a melhor configuração de hiperparâmetros foi a seguinte
lora_r: 8
lora_alpha: 1
lora_query: True
lora_key: False
lora_value: True
lora_projection: False
lora_mlp: True
lora_head: False
  • Resultado dessa configuração:
    • Val acc: 92,96%
    • Test acc: 92,39%
  • Essa configuração de LoRA tem cerca de 500k parâmetros treináveis, muito menos que os 66M do fine-tuning completo
  • A acurácia ficou ligeiramente acima do fine-tuning completo, que teve Val acc 92,80% e Test acc 92,31%

Ambiente de execução e materiais adicionais

  • Ao clicar em Run no topo do Studio, é possível clonar o ambiente com todo o código incluído
  • Depois de clonar o Studio, os arquivos de código podem ser executados sem instalação, download ou configuração adicional
  • Notebooks e scripts relacionados:
    • 00_lora-layer.ipynb: implementação da camada LoRA
    • 01_finetune-last-layers.ipynb: fine-tuning das últimas camadas
    • 02_finetune-with-lora.ipynb: fine-tuning com LoRA
    • 03_finetune-lora.py: execução com argumentos de hiperparâmetros do LoRA
    • 03_gridsearch.py: busca em grade de hiperparâmetros do LoRA
    • 04_finetune-all-layers.ipynb: fine-tuning de todas as camadas
  • Materiais adicionais:

1 comentários

 
GN⁺ 2024-01-23
Opiniões do Hacker News
  • O fluxo da técnica segue o LLMs 101 de Maxime Labonne: https://github.com/mlabonne/llm-course#4-supervised-fine-tun...

  • LoRA != LoRa, então continuo me confundindo. Não gosto de terem reutilizado uma sigla que já existia

    • Eu também. Meu trabalho principal é aprendizado de máquina, mas mesmo assim — ou talvez justamente por isso — toda vez que vejo essa sigla em um lugar com quase nenhum contexto, dou uma travada
      Especialmente em lugares como a primeira página do HN, onde os dois significados são naturais
    • Qual é o outro significado além de “Low-Rank Adaptation”? É até difícil pesquisar a diferença
    • Isso acontece quando as pessoas ficam especializadas demais e não se importam com o que está acontecendo fora da própria bolha
    • Não gosto dessa tendência de gente de software pegar nomes relacionados a hardware
    • É uma pena que duas tecnologias sem relação entre si tenham acabado usando a mesma sigla
  • Ainda acho estranho que, na área de ciência da computação, se diga algo como “não sabemos exatamente como estes números, ou seja, estes hiperparâmetros, afetam o resultado, então vamos testar vários valores e usar o que funcionar melhor”

    • “Testar vários valores e usar o que funcionar melhor” não é parecido com usar simulação de Monte Carlo para encontrar valores?
      Às vezes você pode ficar preso em um máximo local em vez do ótimo/da resposta correta, mas ainda assim funciona. Como não dá para resolver com uma fórmula fechada, você tira amostras aleatórias bilhões de vezes até encontrar o valor desejado; não estou dizendo que LLMs sejam iguais, mas essa abordagem é usada com bastante frequência
    • Parece a diferença entre algo que foi feito por engenharia e algo que foi descoberto
      Até agora, a maior parte da indústria era algo projetado por engenharia, enquanto LLMs estão mais para algo descoberto
    • Esse tipo de remendo bottom-up também se parece com a forma como a ciência da computação começou nos EUA, como o próprio Dijkstra observou: https://www.cs.utexas.edu/users/EWD/transcriptions/EWD06xx/E...
      Idealmente seria necessária uma base teórica, mas, para extrair dados suficientes para criar ou verificar a teoria, às vezes é preciso fazer busca aleatória
    • A culpa também é grande de Minsky e outros que desdenharam dos perceptrons por não conseguirem modelar funções não lineares. LLMs provavelmente não teriam surgido sem CPUs e GPUs modernas de qualquer forma, mas isso não significa que não poderíamos ter tido uma base teórica melhor antes
      Estamos alguns anos atrás de onde deveríamos estar. Quando eu trabalhava na indústria de jogos nos anos 1990, era “senso comum” que redes neurais eram, na melhor das hipóteses, um beco sem saída e, na pior, fraude. É realmente uma pena termos perdido tanto tempo porque algumas autoridades desencorajaram todo mundo, e precisamos garantir que isso não se repita desta vez
    • A sensação de pesquisar configurações do Stable Diffusion é parecida com isso. O que se percebe rapidamente é que há muito palpite envolvido
  • Ainda não está claro quando fazer fine-tuning e quando usar RAG
    Antes eu achava que fine-tuning servia principalmente para mudar o comportamento do modelo, mas recentemente algumas empresas parecem usá-lo também para adicionar conhecimento. Tenho curiosidade sobre os principais usos do fine-tuning

    • Acho que o principal uso continua sendo mudança de comportamento. Fine-tuning de instruções, fine-tuning para classificação etc.
      Adicionar conhecimento aos pesos é algo melhor feito com pré-treinamento. Ou, se você tem um banco de dados ou documentos externos para consultar durante a geração, pode usar RAG, como numa pergunta. Para referência, no NeurIPS 2023 LLM Efficiency Challenge, todos os vencedores que fizeram fine-tuning do “melhor” LLM em até 24 horas com uma única GPU usaram LoRA ou QLoRA (LoRA quantizado)

    • Se os dados adicionais não forem concisos ou exigirem contexto, fine-tuning é melhor que RAG
      Se houver contexto demais ou o foco ficar difuso, a aderência ao prompt pode se diluir, e RAG não permite que o modelo aprenda associações de tokens em dimensões mais altas. Assim, você precisa ter sorte para puxar o conteúdo necessário do material suplementar, e aí isso não é muito melhor do que um mecanismo de busca avançado. Isso é especialmente problemático ao lidar com corpora especializados que têm microdialetos próprios, pouco presentes em datasets públicos, como documentos internos de governos ou grandes empresas

    • Pelo que entendi, fine-tuning é anormalmente eficaz [0]. Isso porque o aprendizado em contexto depende muito de quão forte é o modelo-base e de como o RAG é construído, isto é, processamento da consulta, busca por embeddings, ranqueamento dos resultados etc. [1]
      Segundo os papers que li, fine-tuning pode adicionar novo conhecimento de domínio ou reforçar conhecimento específico, enquanto RAG se limita ao reforço. Ainda assim, duas técnicas com trade-offs diferentes às vezes mostram capacidades em nível semelhante [2]

      [0] Fast.ai: Can Models learn from one sample, https://www.fast.ai/posts/2023-09-04-learning-jumps/ / https://archive.is/eJMPR

      [1] LlamaIndex: Advanced RAG, https://blog.llamaindex.ai/a-cheat-sheet-and-some-recipes-fo... / https://archive.is/qtBXX

      [2] Microsoft: RAG vs Fine-tuning: Pipelines, Tradeoffs, and a Case Study, https://arxiv.org/html/2401.08406v2#S6 / https://archive.is/UQ8Sa#S6

    • Eles são modelos autorregressivos. Se há um novo tipo de sequência em que é possível prever os elementos seguintes a partir dos anteriores, e essa forma é diferente do que o modelo já viu, fine-tuning parece fazer sentido
      Como critério para decidir o que fazer em uma situação específica de dados, isso é bastante vago, mas pode bastar como uma heurística aproximada. Se adicionar conhecimento entra nisso talvez seja uma questão de preferência sem experimentos

  • É um bom texto. Não sou da área, mas, quando li o artigo original, entendi que LoRA era aplicado apenas à última camada densa, e não independentemente a todas as camadas. Posso ter lido errado.
    Fui investigar um pouco por que a implementação do link é assim; parece que o QLoRA usou essa abordagem e que ela tem efeitos interessantes. Seria bom acrescentar uma nota sobre a decisão do QLoRA. Ainda assim, não entendo muito bem por que funciona e, do ponto de vista de um iniciante, aplicar LoRA à última camada faz sentido, mas não consigo intuir a justificativa para aplicá-lo repetidamente a cada camada linear. Alguém pode explicar a intuição?

    • Como muita coisa em aprendizado de máquina, a escolha de quais camadas usar depende mais de evidência empírica do que de teoria. Em um pipeline comum de treinamento com LoRA, o modelo base é congelado e apenas as camadas LoRA são ajustadas.
      Quanto mais camadas forem substituídas por camadas LoRA, maior é o grau de liberdade da otimização. Algumas abordagens de ajuste fino recomendam ajustar apenas a última camada, porque se presume que ela contenha a representação de “mais alto nível” da entrada. Outras abordagens ajustam todas as camadas. Na maioria dos casos, depende dos dados e do problema, e o LoRA simplesmente reflete essa convenção.
  • Prefiro a abordagem de começar pela configuração, não “do zero”, do Axolotl. O Axolotl dá suporte ao ajuste fino de Mistral e Llama 2, e também a muitas técnicas modernas como sample packing, FlashAttention e xFormers.
    Em vez de aprender LoRA do zero, foco em coletar e fazer curadoria dos dados de ajuste fino, fazendo ajuste fino centrado em dados.

  • Dar nomes é difícil mesmo. No começo achei que fosse sobre LoRa de “long range” ou sobre LoRaWAN, a comunicação para sensores IoT.

  • Quais são as bibliotecas mais usadas para ajuste fino? Considerando abordagens que não sejam “do zero”.

  • Uau, no começo eu também achei, obviamente, que era sobre LoRa.

  • Qual é o custo de desempenho do LoRA?

    • Durante o treinamento, ele é mais eficiente do que o ajuste fino completo, porque a retropropagação atualiza apenas parte dos parâmetros.
      Durante a inferência, há duas possibilidades. Se os valores do LoRA forem somados dinamicamente durante a passagem direta, em teoria pode ficar um pouco mais lento, mas isso também pode ser uma vantagem se você quiser manter pequenos conjuntos de pesos separados por cliente. Isso porque é possível rodar um único modelo base grande e aplicar, na hora, os pesos LoRA específicos de cada cliente. Se os pesos LoRA forem mesclados de volta ao modelo base, é possível obter exatamente o mesmo desempenho do modelo base.