Ajuste de LoRA con Granite LLM

Ajuste de Granite con LoRA

Adaptación de bajo rango (LoRA) es un método eficiente de fine-tuning que reduce el número de parámetros entrenables que aumenta la velocidad y el uso de recursos del entrenamiento al tiempo que conserva la misma calidad de output. En lugar de actualizar todos los parámetros en una red neuronal durante el fine-tuning, LoRA congela las ponderaciones originales preentrenadas y añade matrices pequeñas y entrenables de bajo rango que se aproximan a los cambios necesarios para la nueva tarea. Este enfoque se basa en la hipótesis de que las actualizaciones de peso durante la adaptación tienen un "rango intrínseco" bajo.

Un beneficio adicional de LoRA es que, debido a que los pesos preentrenados se mantienen congelados, el adaptador generado es liviano y portátil y se puede almacenar fácilmente.

En este tutorial, utilizará LLaMa Factory. LLaMa Factory es una plataforma de entrenamiento y ajuste de modelos de lenguaje de gran tamaño(LLM) low-code y no-code que permite a los usuarios afinar los LLM en conjuntos de datos personalizados, evaluar el rendimiento y servir modelos. Tiene una interfaz de usuario web y una CLI que son fáciles de usar y admiten más de 100 LLM. La plataforma admite conjuntos de datos en formatos Alpaca y ShareGPT. LLaMa Factory no es la única forma de afinar los LLM, la biblioteca PEFT para el ajuste preciso de los parámetros es otra opción para actualizar modelos de gran tamaño. PEFT ofrece la posibilidad de realizar LoRA [cuantificado] (QLoRA) (https://www.ibm.com/es-es/think/topics/quantization) para compactar aún más el modelo ajustado. En este tutorial, utilizará una versión no cuantificada de Granite 3.3.

Aunque LLaMa Factory puede ejecutarse sin el uso de amplios recursos informáticos, requiere una GPU y recursos de memoria significativos. En este tutorial, utilizará LLaMa Factory en watsonx para proporcionar recursos de GPU y almacenamiento para el adaptador generado.

Configuración

Configuración de watsonx Studio

a. Inicie sesión en watsonx.ai con su cuenta de IBM® Cloud.

b. Cree un proyecto watsonx.ai. Anote su ID de proyecto en Proyecto > Administrar > General > ID de proyecto.  
Necesitará este ID para este tutorial.

c. Cree una instancia de servicio de watsonx.ai Runtime. Para este tutorial, deberá crear una instancia de pago para acceder a una GPU.

d. Genere una interfaz de programación de aplicaciones de watsonx (clave de API).

e. Asocie el servicio de tiempo de ejecución de watsonx.ai al proyecto que creó en watsonx.ai.

Cloud Object Storage

a. Para crear Cloud Object Storage para su notebook, vaya a https://cloud.ibm.com/ y, a continuación, seleccione "Crear instancia".

b. Esto le llevará a un cuadro de diálogo de creación en el que podrá seleccionar un plan de precios. Para este tutorial, un plan estándar será adecuado.

c. A continuación, asigne un nombre a su instancia de Cloud Object Storage.

d. Una vez que haya creado su instancia, vuelva al proyecto y seleccione "Nuevo activo", luego seleccione "Conectar a una fuente de datos".

Imagen que muestra la conexión watsonx.data para Cloud Object Storage Configuración de la conexión de datos para Cloud Object Storage

e. Seleccione "Cloud Object Storage" f. En el siguiente cuadro de diálogo, seleccione la instancia que creó en los pasos ad por nombre.

g. Seleccione "Crear".

Crear un cuaderno Jupyter Notebook

Cree un Jupyter Notebook.

a. Seleccione la pestaña Activos en el entorno de su proyecto.

b. Haga clic en Nuevo activo.

c. Seleccione la opción Trabajar con modelos en el panel izquierdo.

d. Haga clic en Trabajar con datos y modelos mediante notebooks de Python y R.

e. Introduzca un nombre para su notebook en el campo Nombre. Elija tiempo de ejecución 23.1 en Python (4 vCPU 16 GB RAM) para definir la configuración.

f. Seleccione Crear.

Configuración

A continuación, instalará las dependencias en el tiempo de ejecución. Primero, llama-factory para generar los adaptadores de rango bajo y, a continuación, Pandas para formatear el conjunto de datos en formato Alpaca.

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

Comprobar el entorno de GPU

A continuación, se asegurará de que su entorno de watsonx ha proporcionado una GPU compatible con Torch que será necesaria para utilizar 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.")

Si el fragmento de código anterior no muestra el mensaje "No se ha encontrado ninguna GPU", entonces todo está listo.

A continuación, importará bibliotecas para manipular datos y crear el archivo de configuración de LLaMa Factory utilizado para el entrenamiento.

# Import libraries
import pandas as pd
import json
import yaml

Descargar y procesar el conjunto de datos de MedReason

En este tutorial, usará una parte del conjunto de datos MedReason . MedReason es un conjunto de datos de razonamiento médico de alta calidad a gran escala diseñado para ayudar a permitir la resolución de problemas médicos explicables en los LLM. Aunque MedReason se centra en el razonamiento de un modelo y en la validación de las cadenas de pensamiento que utiliza un modelo, en este caso también es útil proporcionar un conjunto de datos que sea demasiado reciente para incluirse en los datos de entrenamiento de IBM® Granite 3.3.

Granite 3.3 se ha diseñado para aprender mediante el ajuste, y ambos se ejecutarán con Llama Factory. Los modelos Granite se pueden ajustar de forma eficiente incluso con recursos informáticos limitados.

Cargará una selección del conjunto de datos de MedReason desde 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)

LLaMa Factory requiere que el conjunto de datos esté preformateado en formatos Alpaca o ShareGPT. Por ello, reformateamos los campos de preguntas y respuestas del conjunto de datos para contener los campos de instrucción, entrada y salida según el formato Alpaca.

Alpaca es un formato JSON para representar una instrucción, una entrada de usuario y una salida del sistema, así:

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

Dado que MedReason no está formateado en Alpaca, creará un conjunto de datos en la siguiente celda:

!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)

Llama Factory utiliza un archivo específico para comprender cómo cargar conjuntos de datos para el entrenamiento. Este archivo debe existir en la ruta data/dataset_info.json. Por lo tanto, debemos crear un dataset_info.json que incluye la ruta hacia el nuevo conjunto de datos médico formateado que creamos para la CLI de Llama Factory para acceder al conjunto de datos. Para obtener más información sobre el archivo dataset_info.json, consulte la documentación. Dentro del repositorio de Llama Factory hay conjuntos de datos disponibles para usar, sin embargo, como estamos utilizando nuestro propio conjunto de datos personalizado, debemos agregar nuestro conjunto de datos al archivo 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)

Ahora que el objeto JSON con formato Alpaca se ha guardado en el entorno, está listo para empezar a entrenar.

afinado

El siguiente paso es establecer las configuraciones de entrenamiento y luego escribir las configuraciones en un archivo YAML que LLaMa utiliza para ejecutar el entrenamiento.

Ahora ejecutará el ajuste fino supervisado (SFT) en el subconjunto del conjunto de datos de MedReason. LLaMa Factory admite varios tipos diferentes de entrenamiento. Algunos de los más utilizados son:

  • Preentrenamiento: cuando un modelo se somete a un entrenamiento inicial utilizando un amplio conjunto de datos para generar respuestas al lenguaje y las ideas fundamentales.

  • Ajuste supervisado (SFT): donde un modelo recibe entrenamiento adicional con datos anotados para mejorar la precisión de una función particular o sobre un tema específico.

  • Modelado de recompensas: donde el modelo adquiere conocimientos sobre cómo lograr un incentivo o recompensa específicos que informarán su política de optimización de output (PPO).

  • Entrenamiento: una técnica de aprendizaje por refuerzo (RL) en la que el modelo se perfecciona aún más mediante técnicas de gradiente de políticas para aumentar su eficacia en un entorno específico.

Se utilizan muchos ajustes para configurar LoRA, pero algunos de los más importantes y de uso común son:

  • Tasa de aprendizaje (LR): la tasa de aprendizaje determina la importancia de la actualización de cada parámetro del modelo durante cada iteración del entrenamiento. Una LR más alta puede acelerar la convergencia al permitir actualizaciones más grandes, pero corre el riesgo de sobrepasar la solución óptima u oscilar en torno a ella. Una LR más baja conduce a una convergencia más lenta pero más estable, lo que reduce el riesgo de inestabilidad cerca de la solución óptima.

  • loraplus_lr_ratio: este paso establece la proporción de las tasas de aprendizaje. En general, debe ser > 1, pero la elección óptima de loraplus_lr_ratio depende del modelo y de la tarea. Como directriz, loraplus_lr_ratio debe ser mayor cuando la tarea es más difícil y el modelo necesita actualizar sus características para aprender bien. En este caso, ayuda a que la tasa de aprendizaje sea ligeramente menor (por ejemplo, por un factor de 2) que las tasas de aprendizaje típicas de LoRA.

  • Tamaño efectivo del lote: configurar correctamente el tamaño del lote es crítico para equilibrar la estabilidad del entrenamiento con las limitaciones de VRAM de la GPU que está utilizando. El tamaño efectivo del lote se establece mediante el producto de per_device_train_batch_size * gradient_accumulation_steps. Un tamaño de lote efectivo mayor generalmente conduce a un entrenamiento más fluido y estable, pero también puede requerir más VRAM de la que contiene su GPU. Un tamaño de lote efectivo más pequeño podría introducir más variación.

Este es el código que configura el entrenamiento:

# 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)

La siguiente celda entrenará el modelo y puede tardar hasta diez minutos en ejecutarse:

!llamafactory-cli train train_granite3_lora_med.yaml;

Uso de Cloud Object Storage

A continuación, creará dos métodos para cargar y descargar datos de 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')

La siguiente celda contiene las credenciales de Cloud Object Storage.

En su notebook, haga clic en la pestaña Code Snippets en la esquina derecha. Este paso abre un menú con varias opciones para los fragmentos de código generados. Seleccione "Leer datos":

El cuadro de diálogo para utilizar un fragmento de código en watsonx Studio Uso de un fragmento de código preparado en watsonx Studio

Este paso abre un menú para seleccionar un archivo de datos. Si no ha subido nada a su instancia de Cloud Object Storage, deberá subir algo para generar credenciales, por ejemplo, un conjunto de datos clásico como «wine.csv».

Selección de un activo de datos en watsonx Studio Selección de un activo de datos en watsonx Studio

Después de hacer clic en "Seleccionar", ahora puede generar el fragmento de credenciales en la opción "Cargar como". Elija "Insertar código en la celda":

Inserción de un fragmento de código generado en watsonx Studio Inserción de un fragmento de código generado en watsonx Studio

Este paso genera una celda como la siguiente con credenciales que contienen los ID correctos y los endpoint generados:

# @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': ''
}

Ahora la carpeta zip que contiene el adaptador y la información sobre el propio adaptador:

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

Compruebe que ha creado el zip correctamente:

!ls

Inferencia

Ahora es el momento de ejecutar la inferencia. La inferencia estará respaldada por la generación HuggingFace, que proporciona un método model.generate() de generación de texto mediante PyTorch.

Este tutorial muestra cómo formular al modelo base una pregunta médica extraída del conjunto de datos de MedReason. Es razonable que el modelo base no pueda responder a esta pregunta, ya que se trata de un modelo de uso general entrenado con conjuntos de datos grandes y diversos.

En primer lugar, establezca las configuraciones de inferencia:

# 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)

Ahora le hará al chatbot una de las preguntas del conjunto de datos de 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"
'''

Cree un nuevo mensaje utilizando la pregunta y pásela al modelo 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})

Este es el resultado de muestra del modelo base Granite 3.3:

Usuario:

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"

Respuesta:

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.

La respuesta correcta del conjunto de datos es:

answer: Cardiac rhabdomyoma

Así que el modelo base no genera la respuesta correcta.

Inferir con el adaptador ajustado a LoRA

Comparamos los resultados con el modelo base y el adaptador ajustado a LoRA. Luego hacemos la misma pregunta para ver cómo el ajuste con el conjunto de datos legales permitió al modelo comprender y responder mejor a las preguntas médicas.

La siguiente celda no será necesaria si ha ejecutado LoRA en la misma sesión. Sin embargo, si vuelve a Jupyter Notebook y no quiere volver a entrenar, puede descargar los adaptadores ajustados desde su instancia COS.

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

Ahora configurará las opciones de ChatModel para que incorpore los 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)

Ahora podemos probar el mismo desafío de razonamiento para el modelo 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"
'''

Cree un nuevo mensaje utilizando la pregunta y páselo al modelo 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

Ejemplo de resultado del modelo ajustado: ** Usuario**:

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"

Respuesta:

Cardiac rhabdomyoma

Esta vez, el modelo generó la respuesta correcta, gracias al adaptador entrenado.

Un aspecto a tener en cuenta es que el modelo ya no responde con su razonamiento. Este resultado se debe a que el conjunto de datos que se utilizó para LoRA solo tiene la respuesta correcta como resultado esperado del modelo. El fine-tuning de LoRA se puede utilizar tanto para proporcionar nueva información como para indicar al modelo cómo responder.

Resumen

En este tutorial, ajustó LoRA al modelo IBM® Granite-3.3-2b-Instruct con nuevos conocimientos médicos y una plantilla detallada sobre cómo responder. Ha visto la capacidad de Granite 3.3 para aprender incluso con un modelo pequeño y muestras limitadas del conjunto de datos.

Soluciones relacionadas
Modelos fundacionales

Explore la biblioteca de modelos fundacionales de IBM en la cartera de watsonx para escalar la IA generativa para su negocio con confianza.

Descubra watsonx.ai
Soluciones de inteligencia artificial

Ponga la IA a trabajar en su negocio con la experiencia líder en IA del sector de IBM y junto a su cartera de soluciones.

Explore las soluciones de IA
Consultoría y servicios de IA

Reinvente las operaciones y flujos de trabajo críticos añadiendo IA para maximizar las experiencias, la toma de decisiones en tiempo real y el valor empresarial.

Explore los servicios de IA
De el siguiente paso

Explore la biblioteca de modelos fundacionales de IBM en la cartera de IBM watsonx para escalar la IA generativa para su negocio con confianza.

Descubra watsonx.ai Explore los modelos de IA de IBM Granite