Ajuste fino da LoRA com o LLM Granite

Ajuste fino do Granite com a LoRA

Adaptação de baixa classificação (LoRA) é um método eficiente de [ajuste fino] (https://www.ibm.com/br-pt/think/topics/fine-tuning) que reduz o número de parâmetros treináveis, aumentando a velocidade e o uso de recursos do treinamento, mantendo a mesma qualidade da saída. Em vez de atualizar todos os parâmetros em uma rede neural durante o ajuste fino, a LoRA congela os pesos pré-treinados originais e adiciona matrizes pequenas e treináveis de baixa classificação que se aproximam das mudanças necessárias para a nova tarefa. Essa abordagem se baseia na hipótese de que as atualizações de peso durante a adaptação têm uma "classificação intrínseca" baixa.

Um benefício adicional da LoRA é que, como os pesos pré-treinados são mantidos congelados, o adaptador gerado é leve e portátil e pode ser facilmente armazenado.

Neste tutorial, você usará o LLaMa Factory. O LLaMa Factory é uma plataforma de grande modelo de linguagem (LLM) de pouco código e no-code de ajuste fino que permite aos usuários ajustar LLMs em conjuntos de dados personalizados, avaliar o desempenho e servir modelos. Ele tem uma IU da web e CLI fáceis de usar e é compatível com mais de 100 LLMs. A plataforma é compatível com conjuntos de dados nos formatos Alpaca e ShareGPT. O LLaMa Factory não é a única maneira de ajuste fino de LLMs; a biblioteca PEFT para ajuste fino com eficiência de parâmetros é outra opção para atualizar grandes modelos. A PEFT oferece a capacidade de realizar LoRA quantizadas (QLoRA) para compactar ainda mais o modelo ajustado. Neste tutorial, você usará uma versão não quantizada do Granite 3.3.

Embora o LLaMa Factory possa ser executado sem o uso de recursos de computação extensivos, ele requer uma GPU e recursos significativos de memória. Neste tutorial, você usará o LLaMa Factory no watsonx para fornecer recursos de GPU e armazenamento para o adaptador gerado.

Configuração

Configuração do Watson Studio

a. Faça login no watsonx.ai usando sua conta do IBM® Cloud.

b. Crie um projeto do watsonx.ai. Anote a ID de seu projeto em projeto > Gerenciar > Geral > ID do projeto.  
Você precisará dessa ID para este tutorial.

c. Crie uma instância do serviço de tempo de execução do watsonx.ai. Para este tutorial, você precisará criar uma instância paga para acessar uma GPU.

d. Gere uma interface de programação de aplicativos (chave de API) do watsonx.

e. Associe o serviço de tempo de execução do watsonx.ai ao projeto que você criou no watsonx.ai.

Cloud Object Storage

a. Para criar o Cloud Object Storage para seu Notebook, acesse https://cloud.ibm.com/ e selecione “Criar instância”.

b. Isso o levará a uma caixa de diálogo de criação, onde você poderá selecionar um plano de preços. Para este tutorial, um plano padrão será adequado.

c. Em seguida, dê um nome à sua instância do Cloud Object Storage.

d. Depois de criar sua instância, volte ao projeto e selecione “Novo ativo” e, em seguida, selecione “Conectar a uma fonte de dados”.

Imagem mostrando a conexão de dados do watsonx para o Cloud Object Storage Configuração da conexão de dados para o Cloud Object Storage

e. Selecione "Cloud Object Storage" f. Na próxima caixa de diálogo, selecione a Instância que você criou nas etapas ad por nome.

g. Selecione "Criar".

Criar um Jupyter Notebook Crie um Jupyter Notebook.

a. Selecione a guia Ativos no seu ambiente de projeto.

b. Clique em Novo ativo.

c. Selecione a opção Trabalhando com modelos no painel esquerdo.

d. Clique em Trabalhar com dados e modelos usando notebooks Python e R.

e. Insira um nome para o bloco de notas no campo Nome. Escolha tempo de execução 23.1 no Python (4 vCPU 16 GB RAM) para definir a configuração.

f. Selecione Criar.

Configuração

Em seguida, você instalará as dependências no tempo de execução. Primeiro, o llama-factory para gerar os adaptadores de classificação baixa e, em seguida, o Pandas para formatar o conjunto de dados no formato Alpaca.

!pip install -q llamafactory 2>/dev/null
# pandas needed to format the dataset
!pip install -q --upgrade pandas 2>/dev/null

Verifique o ambiente da GPU

Em seguida, você vai garantir que seu ambiente watsonx tenha fornecido uma GPU compatível com Torch que será necessária para usar o LLaMa-Factory.

import torch

try:
  assert torch.cuda.is_available() is True
except AssertionError:
  print("No GPU found, please set up a GPU before using LLaMA Factory.")

Se o fragmento de código anterior não mostrar "Nenhuma GPU encontrada", então você você está pronto para começar.

Em seguida, você importará bibliotecas para manipular dados e criar o arquivo de configuração do LLaMa Factory usado para treinamento.

# Import libraries
import pandas as pd
import json
import yaml

Fazer download e processar o conjunto de dados MedReason

Neste tutorial, você usará uma parte do conjunto de dados MedReason O MedReason é um conjunto de dados de raciocínio médico de alta qualidade em grande escala projetado para auxiliar na resolução explicável de problemas médicos em LLMs. Enquanto o MedReason se concentra no raciocínio de um modelo e na validação das cadeias de pensamento que um modelo usa, neste caso também é útil fornecer um conjunto de dados que seja muito recente para ser incluído nos dados de treinamento do IBM Granite 3.3.

O Granite 3.3 foi projetado para aprender por meio de ajustes finos, que serão executados com o LLaMa Factory. Os modelos Granite podem ser ajustados finamente com eficiência, mesmo com recursos computacionais limitados.

Você carregará uma seleção do conjunto de dados MedReason do GitHub:

from datasets import load_dataset

training = pd.read_json("https://raw.githubusercontent.com/UCSC-VLAA/MedReason/refs/heads/main/eval_data/medbullets_op4.jsonl", lines=True)

A LLaMa Factory requer que o conjunto de dados seja pré-formatado nos formatos Alpaca ou ShareGPT. Assim, formatamos de novo os campos de pergunta e resposta do conjunto de dados legal original para conter campos de instrução, entrada e saída de acordo com o formato Alpaca.

Alpaca é um formato JSON para representar instruções, entrada de usuário e saída do sistema da seguinte forma:

{
    "instruction": "user instruction (required)",
    "input": "user input (optional)",
    "output": "model response (required)",
    "system": "system prompt (optional)",
}

Como o MedReason não está formatado no Alpaca, você criará um conjunto de dados do Alpaca na próxima célula:

!mkdir -p data

# Format Med Dataset to Alpaca Format
formatted_data = [
    {
        "instruction": row["question"] + str(row["options"]),
        "input": "",
        "output": row["answer"]
    }
    for _, row in training.iterrows()
]

# output formatted MedReason dataset
with open("data/med.json", "w", encoding="utf-8") as f:
  json.dump(formatted_data, f, indent=2, ensure_ascii=False)

o Llama Factory usa uma pasta específica para entender como carregar conjuntos de dados para treinamento. Este arquivo deve existir no caminho "data/dataset_info.json". Assim, devemos criar um arquivo "dataset_info.json" que inclui o caminho para o novo conjunto de dados médicos formatado que criamos para a CLI do Llama Factory para acessar o conjunto de dados. Para detalhes sobre o arquivo "dataset_info.json" consulte a documentação. No repositório do Llama Factory, há conjuntos de dados disponíveis para uso, no entanto, como estamos usando nosso próprio conjunto de dados personalizado, devemos adicionar nosso conjunto de dados ao arquivo JSON.

# "med" will be the identifier for the dataset 
# which points to the local file that contains the dataset
dataset_info = {
  "med": {
    "file_name": "med.json",
  }
}

# Create dataset_info.json with legal dataset so can reference with llama factory
with open("data/dataset_info.json", "w", encoding="utf-8") as f:
  json.dump(dataset_info, f, indent=2, ensure_ascii=False)

Agora que o objeto JSON formatado em Alpaca foi salvo no ambiente, você já está pronto para começar o treinamento.

Ajuste fino

A próxima etapa é definir as configurações de treinamento e, em seguida, escrever as configurações em um arquivo YAML que o LLaMa usa para executar o treinamento.

Agora você executará o ajuste fino supervisionado (SFT) no subconjunto do conjunto de dados MedReason. O LLaMa Factory é compatível com vários tipos diferentes de treinamento. Alguns dos mais comumente usados são:

  • Pré-treinamento: quando um modelo passa por um treinamento inicial usando um extenso conjunto de dados para gerar respostas para a linguagem e as ideias fundamentais.

  • Ajuste fino supervisionado (SFT): quando um modelo recebe treinamento adicional com dados anotados para aumentar a precisão em uma função específica ou em um tópico específico.

  • Modelagem de recompensa: quando o modelo adquire conhecimento sobre como alcançar um incentivo ou recompensa específico que informará sua otimização de política proximal (PPO) de saída.

  • Treinamento: uma técnica de aprendizado por reforço (RL) em que o modelo é aprimorado ainda mais por meio de técnicas de gradiente de políticas para aumentar sua eficácia em um ambiente específico.

Há muitas configurações usadas na configuração da LoRA, mas algumas das mais importantes e comumente usadas são: - Taxa de aprendizado (LR): A taxa de aprendizado determina o quão significativamente cada parâmetro do modelo é atualizado durante cada iteração de treinamento. Uma LR mais alta pode acelerar a convergência, permitindo atualizações maiores, mas corre o risco de ultrapassar a solução ideal ou oscilar em torno dela. Uma LR mais baixa leva a uma convergência mais lenta, porém mais estável, reduzindo o risco de instabilidade perto da solução ótima.

  • loraplus_lr_ratio: essa etapa define a proporção das taxas de aprendizado. Geralmente, deve ser > 1, mas a escolha ideal de loraplus_lr_ratio depende do modelo e da tarefa. Como orientação, loraplus_lr_ratio deve ser maior quando a tarefa é mais difícil e o modelo precisa atualizar suas funcionalidades para aprender bem. Nesse caso, ajuda a tornar a taxa de aprendizado um pouco menor (por exemplo, por um fator de 2) do que as taxas de aprendizado típicas da LoRA.

  • Tamanho de lote efetivo: configurar corretamente o tamanho do lote é crítico para equilibrar a estabilidade do treinamento com as limitações de VRAM da GPU que você está usando. O tamanho de lote efetivo é definido pelo produto de "per_device_train_batch_size" * "gradient_accumulation_steps". Um tamanho de lote efetivo maior geralmente leva a um treinamento mais suave e estável, mas também pode exigir mais VRAM do que sua GPU contém. Um tamanho de lote efetivo menor pode introduzir mais variância.

Aqui está o código que configura o treinamento:

# setup training configurations
args = dict(
  stage="sft",  # do supervised fine-tuning
  do_train=True,  # we're actually training
  model_name_or_path="ibm-granite/granite-3.3-2b-instruct",  # use IBM Granite 3.3 2b instruct model
  dataset="med",  # use medical datasets we created
  template="granite3",   # use granite3 prompt template
  finetuning_type="lora", # use LoRA adapters to save memory
  lora_target="all",  # attach LoRA adapters to all linear layers
  loraplus_lr_ratio=16.0,  # use LoRA+ algorithm with lambda=16.0
  output_dir="granite3_lora",  # the path to save LoRA adapters
  per_device_train_batch_size=4,  # the batch size
  gradient_accumulation_steps=2,  # the gradient accumulation steps
  learning_rate=1e-4,  # the learning rate
  num_train_epochs=3.0, # the epochs of training
  max_samples=500,  # use 500 examples in each dataset
  fp16=True,  # use float16 mixed precision training
  report_to="none", # disable wandb logging
)

# create training config file to run with llama factory
with open("train_granite3_lora_med.yaml", "w", encoding="utf-8") as file:
  yaml.dump(args, file, indent=2)

A próxima célula treinará o modelo e pode levar até 10 minutos para ser executada:

!llamafactory-cli train train_granite3_lora_med.yaml;

Uso do Cloud Object Storage

Em seguida, você criará dois métodos para fazer upload e download de dados do IBM Cloud Object Storage:

from ibm_botocore.client import Config
import ibm_boto3

def upload_file_cos(credentials, local_file_name, key):  
    cos = ibm_boto3.client(service_name='s3',
    ibm_api_key_id=credentials['IBM_API_KEY_ID'],
    ibm_service_instance_id=credentials['IAM_SERVICE_ID'],
    ibm_auth_endpoint=credentials['IBM_AUTH_ENDPOINT'],
    config=Config(signature_version='oauth'),
    endpoint_url=credentials['ENDPOINT'])
    try:
        res=cos.upload_file(Filename=local_file_name, Bucket=credentials['BUCKET'],Key=key)
    except Exception as e:
        print(Exception, e)
    else:
        print(' File Uploaded')


def download_file_cos(credentials,local_file_name,key):  
    cos = ibm_boto3.client(service_name='s3',
    ibm_api_key_id=credentials['IBM_API_KEY_ID'],
    ibm_service_instance_id=credentials['IAM_SERVICE_ID'],
    ibm_auth_endpoint=credentials['IBM_AUTH_ENDPOINT'],
    config=Config(signature_version='oauth'),
    endpoint_url=credentials['ENDPOINT'])
    try:
        res=cos.download_file(Bucket=credentials['BUCKET'],Key=key,Filename=local_file_name)
    except Exception as e:
        print(Exception, e)
    else:
        print('File Downloaded')

A próxima célula contém as credenciais do Cloud Object Storage.

No seu notebook, clique na guia Code Snippets (Trechos de código) no canto direito. Essa etapa abre um menu com várias opções para trechos de código gerados. Selecione "Read Data" (Ler dados):

Caixa de diálogo para usar um trecho de código no Watson Studio Uso de um trecho de código preparado no Watson Studio

Essa etapa abre um menu para selecionar um arquivo de dados. Se você não fez upload de nada para sua instância do Cloud Object Storage, precisará fazer upload de algo para gerar credenciais e que pode ser um conjunto de dados clássico como "wine.csv".

Seleção de um ativo de dados no Watson Studio Seleção de um ativo de dados no Watson Studio

Depois de clicar em "Selecionar", agora você pode gerar o trecho de credencial na opção "Carregar como". Escolha "Inserir código na célula":

Inserção de um trecho de código gerado no Watson Studio Inserção de um trecho de código gerado no Watson Studio

Essa etapa gera uma célula como a seguinte com credenciais contendo os IDs e endpoints corretos gerados:

# @hidden_cell
# The following code contains metadata for a file in your project storage.
# You might want to remove secret properties before you share your notebook.

storage_metadata = {
    'IAM_SERVICE_ID': '',
    'IBM_API_KEY_ID': '',
    'ENDPOINT': '',
    'IBM_AUTH_ENDPOINT': '',
    'BUCKET': '',
    'FILE': ''
}

Agora, a pasta zip contendo o adaptador e as informações sobre o próprio adaptador:

!zip -r "granite3_lora.zip" "granite3_lora"

Verifique se você criou o zip corretamente:

!ls

Inferência

Chegou o momento de executar a inferência. A inferência será feita com base na geração do HuggingFace, que fornece um "model.generate()" para geração de texto usando PyTorch.

Este tutorial mostra como fazer uma pergunta médica ao modelo base, extraída do conjunto de dados MedReason. É razoável que o modelo de base não consiga responder a essa pergunta, pois se trata de um modelo de uso geral treinado em conjuntos de dados grandes e diversificados.

Primeiro, defina as configurações de inferência:

# setup inference configurations
args = dict(
  model_name_or_path="ibm-granite/granite-3.3-2b-instruct",  # use IBM Granite 3.3 2b instruct model
  template="granite3",  # set to the same one used in training, template for constructing prompts
  infer_backend="huggingface"  # choices: [huggingface, vllm]
)

# create inference config file to run with llama factory
with open("inference_config.yaml", "w", encoding="utf-8") as file:
  yaml.dump(args, file, indent=2)

Agora você fará ao chatbot uma das perguntas do conjunto de dados MedReason:

from llamafactory.chat import ChatModel
chat_model = ChatModel(args)
messages = []

# run inference chatbot
question = '''
A 1-year-old girl is brought to a neurologist due to increasing seizure frequency over the past 2 months. 
She recently underwent a neurology evaluation which revealed hypsarrhythmia on electroencephalography (EEG) with a mix of slow waves, multifocal spikes, and asynchrony. 
Her parents have noticed the patient occasionally stiffens and spreads her arms at home. She was born at 38-weeks gestational age without complications. 
She has no other medical problems. Her medications consist of lamotrigine and valproic acid. Her temperature is 98.3\u00b0F (36.8\u00b0C), blood pressure is 90/75 mmHg, pulse is 94/min, and respirations are 22/min. 
Physical exam reveals innumerable hypopigmented macules on the skin and an irregularly shaped, thickened, and elevated plaque on the lower back. 
Which of the following is most strongly associated with this patient's condition?"
"A": "Cardiac rhabdomyoma", "B": "Glaucoma", "C": "Optic glioma", "D": "Polyostotic fibrous dysplasia"
'''

Crie uma nova mensagem usando a pergunta e passe-a para o modelo de base:

messages.append({"role": "user", "content": question})

response = ""
for new_text in chat_model.stream_chat(messages):
    response += new_text

print(response)
messages.append({"role": "assistant", "content": response})

Aqui está o exemplo de saída do modelo de base Granite 3.3:

Usuário:

A 1-year-old girl is brought to a neurologist due to increasing seizure frequency over the past 2 months. 
She recently underwent a neurology evaluation which revealed hypsarrhythmia on electroencephalography (EEG) with a mix of slow waves, multifocal spikes, and asynchrony. 
Her parents have noticed the patient occasionally stiffens and spreads her arms at home. She was born at 38-weeks gestational age without complications. 
She has no other medical problems. Her medications consist of lamotrigine and valproic acid. Her temperature is 98.3\F (36.8\C), blood pressure is 90/75 mmHg, pulse is 94/min, and respirations are 22/min. 
Physical exam reveals innumerable hypopigmented macules on the skin and an irregularly shaped, thickened, and elevated plaque on the lower back. 
Which of the following is most strongly associated with this patient's condition?"
"A": "Cardiac rhabdomyoma", "B": "Glaucoma", "C": "Optic glioma", "D": "Polyostotic fibrous dysplasia"

Resposta:

The most strongly associated condition with this patient's condition is "C": "Optic glioma".

The patient's symptoms of hypsarrhythmia on EEG, seizure frequency increase, and the presence of hypopigmented macules and a thickened plaque on the lower back are indicative of a neurological disorder. Optic glioma is a type of brain tumor that can present with these symptoms, including seizures and visual disturbances.

Option A, "Cardiac rhabdomyoma", typically presents with cardiac involvement and is not associated with the described EEG findings or skin manifestations.

Option B, "Glaucoma", is an eye disease that can lead to vision loss but is not associated with the EEG findings or skin lesions described.

Option D, "Polyostotic fibrous dysplasia", is a bone disorder characterized by multiple bone lesions and is not associated with the neurological symptoms and EEG findings presented.

Therefore, based on the clinical presentation, the most likely diagnosis is an optic glioma.

A resposta correta do conjunto de dados é:

answer: Cardiac rhabdomyoma

Portanto, o modelo de base não gera a resposta correta.

Inferir com o adaptador LoRa finamente ajustado

Comparamos os resultados executando o teste com o modelo base e com o adaptador otimizado para LoRa. Em seguida, fazemos a mesma pergunta para ver como o ajuste com o conjunto de dados legais permitiu que o modelo entendesse e respondesse melhor às perguntas médicas.

A célula seguinte não será necessária se você já tiver executado o LoRA na mesma sessão. No entanto, se você estiver voltando para o Jupyter Notebook e não quiser treinar novamente, faça o download dos adaptadores finamente ajustados da sua instância do COS.

download_file_cos(credentials, "granite3_lora.zip", "granite3_lora.zip")
!unzip granite3_lora.zip

Agora você configurará as opções do ChatModel para incorporar os adaptadores.

# setup inference configurations
args = dict(
  model_name_or_path="ibm-granite/granite-3.3-2b-instruct",  # use IBM Granite 3.3 2b instruct model
  adapter_name_or_path="granite3_lora", # load the saved LoRA adapters
  template="granite3", # set to the same one used in training, template for constructing prompts
  finetuning_type="lora", # which fine-tuning technique used in training
  infer_backend="huggingface" # choices: [huggingface, vllm]
)

# create inference config file to run with llama factory
with open("inference_config.yaml", "w", encoding="utf-8") as file:
  yaml.dump(args, file, indent=2)


from llamafactory.chat import ChatModel
chat_model = ChatModel(args)

Agora podemos testar o mesmo desafio de raciocínio para o modelo finamente ajustado:

messages = []

# run inference chatbot
question = '''
A 1-year-old girl is brought to a neurologist due to increasing seizure frequency over the past 2 months. 
She recently underwent a neurology evaluation which revealed hypsarrhythmia on electroencephalography (EEG) with a mix of slow waves, multifocal spikes, and asynchrony. 
Her parents have noticed the patient occasionally stiffens and spreads her arms at home. She was born at 38-weeks gestational age without complications. 
She has no other medical problems. Her medications consist of lamotrigine and valproic acid. Her temperature is 98.3\u00b0F (36.8\u00b0C), blood pressure is 90/75 mmHg, pulse is 94/min, and respirations are 22/min. 
Physical exam reveals innumerable hypopigmented macules on the skin and an irregularly shaped, thickened, and elevated plaque on the lower back. 
Which of the following is most strongly associated with this patient's condition?"
"A": "Cardiac rhabdomyoma", "B": "Glaucoma", "C": "Optic glioma", "D": "Polyostotic fibrous dysplasia"
'''

Criar uma nova mensagem usando a pergunta e passá-la para o modelo finamente ajustado:

messages.append({"role": "user", "content": question})

response = ""
for new_text in chat_model.stream_chat(messages):
    response += new_text

print(response)
messages.append({"role": "assistant", "content": response})
Cardiac rhabdomyoma

Amostra de saída do modelo finamente ajustado:

Usuário:

A 1-year-old girl is brought to a neurologist due to increasing seizure frequency over the past 2 months. 
She recently underwent a neurology evaluation which revealed hypsarrhythmia on electroencephalography (EEG) with a mix of slow waves, multifocal spikes, and asynchrony. 
Her parents have noticed the patient occasionally stiffens and spreads her arms at home. She was born at 38-weeks gestational age without complications. 
She has no other medical problems. Her medications consist of lamotrigine and valproic acid. Her temperature is 98.3\u00b0F (36.8\u00b0C), blood pressure is 90/75 mmHg, pulse is 94/min, and respirations are 22/min. 
Physical exam reveals innumerable hypopigmented macules on the skin and an irregularly shaped, thickened, and elevated plaque on the lower back. 
Which of the following is most strongly associated with this patient's condition?"
"A": "Cardiac rhabdomyoma", "B": "Glaucoma", "C": "Optic glioma", "D": "Polyostotic fibrous dysplasia"

Resposta:

Cardiac rhabdomyoma

Desta vez, o modelo gerou a resposta correta, graças ao adaptador treinado.

Um aspecto a ser observado é que o modelo já não responde com o seu raciocínio. Esse resultado se deve ao fato de que o conjunto de dados utilizado para o LoRA tem apenas a resposta correta como saída esperada do modelo. O ajuste fino do LoRA pode ser usado para fornecer novas informações mas também para instruir o modelo sobre como responder.

Resumo

Neste tutorial, você realizou o ajuste fino com a LoRA do modelo IBM Granite-3.3-2b-Instruct com novos conhecimentos médicos e um modelo detalhado de como responder. Você viu a capacidade de aprendizado do Granite 3.3, mesmo com um modelo pequeno e amostras limitadas do conjunto de dados.

Soluções relacionadas
Modelos de base

Explore a biblioteca de modelos de base da IBM no portfólio do watsonx para escalar a IA generativa com confiança na sua empresa.

Conheça o watsonx.ai
Soluções de inteligência artificial

Use a IA a serviço da sua empresa com a experiência e o portfólio de soluções líder do setor da IBM à sua disposição.

Explore as soluções de IA
Consultoria e serviços em IA

Reinvente os fluxos de trabalho e as operações críticas adicionando IA para maximizar experiências, tomadas de decisão em tempo real e valor comercial.

Explore os serviços de IA
Dê o próximo passo

Explore a biblioteca de modelos de base da IBM no portfólio do IBM® watsonx para escalar a IA generativa com confiança na sua empresa.

Conheça o watsonx.ai Conheça os modelos de IA do IBM® Granite