QLoRA: cómo ajustar un LLM en una sola GPU |  de Shaw Talebi |  febrero de 2024

Importaciones

Importamos módulos de Hugging Face’s. transforma, pefty conjuntos de datos bibliotecas.

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
from peft import prepare_model_for_kbit_training
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
import transformers

Además, necesitamos las siguientes dependencias instaladas para que algunos de los módulos anteriores funcionen.

!pip install auto-gptq
!pip install optimum
!pip install bitsandbytes

Modelo base de carga y tokenizador

A continuación, cargamos el modelo cuantificado de Hugging Face. Aquí utilizamos una versión de Mistral-7B-Instruct-v0.2 preparado por TheBlokeque ha cuantificado y compartido libremente miles de LLM.

Observe que estamos usando la versión “Instruct” de Mistral-7b. Esto indica que el modelo ha sufrido ajuste de instrucciones, un proceso de ajuste eso tiene como objetivo mejorar el rendimiento del modelo al responder preguntas y responder a las indicaciones de los usuarios.

Además de especificar el repositorio de modelos que queremos descargar, también configuramos los siguientes argumentos: mapa_dispositivo, código_remoto_de_confianzay revisión. mapa_dispositivo permite que el método descubra automáticamente cómo asignar mejor los recursos computacionales para cargar el modelo en la máquina. Próximo, trust_remote_code=Falso impide que se ejecuten archivos de modelos personalizados en su máquina. Entonces finalmente, revisión especifica qué versión del modelo queremos usar del repositorio.

model_name = "TheBloke/Mistral-7B-Instruct-v0.2-GPTQ"
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto",
trust_remote_code=False,
revision="main")

Una vez cargado, vemos que el modelo de parámetros 7B solo nos lleva 4,16 GB de memoriaque puede caber fácilmente en la memoria de la CPU o la GPU disponible de forma gratuita en Colab.

A continuación, cargamos el tokenizador del modelo. Esto es necesario porque el modelo espera que el texto se codifique de una manera específica. Yo discutí tokenización en artículos anteriores de esta serie.

tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)

Usando el modelo base

A continuación, podemos utilizar el modelo para la generación de texto. Como primer paso, intentemos ingresar un comentario de prueba en el modelo. Esto lo podemos hacer en 3 pasos.

Primero, elaboramos el mensaje en el formato adecuado. Es decir, Mistral-7b-Instruct espera que el texto de entrada comience y termine con los tokens especiales. [INST] y [/INST]respectivamente. Segundotokenizamos el mensaje. Terceropasamos el mensaje al modelo para generar texto.

El código para hacer esto se muestra a continuación con el comentario de prueba, “Gran contenido, gracias!

model.eval() # model in evaluation mode (dropout modules are deactivated)

# craft prompt
comment = "Great content, thank you!"
prompt=f'''[INST] {comment} [/INST]'''

# tokenize input
inputs = tokenizer(prompt, return_tensors="pt")

# generate output
outputs = model.generate(input_ids=inputs["input_ids"].to("cuda"),
max_new_tokens=140)

print(tokenizer.batch_decode(outputs)[0])

La respuesta del modelo se muestra a continuación. Si bien tuvo un buen comienzo, la respuesta parece continuar sin una buena razón y no suena como algo que yo diría.

I'm glad you found the content helpful! If you have any specific questions or 
topics you'd like me to cover in the future, feel free to ask. I'm here to
help.

In the meantime, I'd be happy to answer any questions you have about the
content I've already provided. Just let me know which article or blog post
you're referring to, and I'll do my best to provide you with accurate and
up-to-date information.

Thanks for reading, and I look forward to helping you with any questions you
may have!

Ingeniería rápida

Aquí es donde ingeniería rápida es útil. Desde un Artículo anterior En esta serie cubrimos este tema en profundidad, solo diré que la ingeniería rápida implica Elaborar instrucciones que conduzcan a mejores respuestas del modelo..

Normalmente, escribir buenas instrucciones es algo que se hace a través de prueba y error. Para hacer esto, probé varias iteraciones rápidas usando juntos.aique tiene una interfaz de usuario gratuita para muchos LLM de código abierto, como Mistral-7B-Instruct-v0.2.

Una vez que obtuve las instrucciones con las que estaba satisfecho, creé una plantilla de aviso que combina automáticamente estas instrucciones con un comentario usando una función lambda. El código para esto se muestra a continuación.

intstructions_string = f"""ShawGPT, functioning as a virtual data science \
consultant on YouTube, communicates in clear, accessible language, escalating \
to technical depth upon request. \
It reacts to feedback aptly and ends responses with its signature '–ShawGPT'. \
ShawGPT will tailor the length of its responses to match the viewer's comment,
providing concise acknowledgments to brief expressions of gratitude or \
feedback, thus keeping the interaction natural and engaging.

Please respond to the following comment.
"""

prompt_template =
lambda comment: f'''[INST] {intstructions_string} \n{comment} \n[/INST]'''

prompt = prompt_template(comment)

The Prompt
-----------

[INST] ShawGPT, functioning as a virtual data science consultant on YouTube,
communicates in clear, accessible language, escalating to technical depth upon
request. It reacts to feedback aptly and ends responses with its signature
'–ShawGPT'. ShawGPT will tailor the length of its responses to match the
viewer's comment, providing concise acknowledgments to brief expressions of
gratitude or feedback, thus keeping the interaction natural and engaging.

Please respond to the following comment.

Great content, thank you!
[/INST]

Podemos ver el poder de una buena indicación al comparar la respuesta del nuevo modelo (a continuación) con la anterior. Aquí, el modelo responde de manera concisa y apropiada y se identifica como ShawGPT.

Thank you for your kind words! I'm glad you found the content helpful. –ShawGPT

Preparar modelo para entrenamiento

Veamos cómo podemos mejorar el rendimiento del modelo mediante ajustes. Podemos comenzar habilitando puntos de control de gradiente y entrenamiento cuantificado. Puntos de control de gradiente Es una técnica de ahorro de memoria que borra activaciones específicas y las recalcula durante el paso hacia atrás. [6]. Entrenamiento cuantificado se habilita utilizando el método importado desde peft.

model.train() # model in training mode (dropout modules are activated)

# enable gradient check pointing
model.gradient_checkpointing_enable()

# enable quantized training
model = prepare_model_for_kbit_training(model)

A continuación, podemos configurar el entrenamiento con LoRA a través de un objeto de configuración. Aquí nos dirigimos a la capas de consulta en el modelo y utilizar un rango intrínseco de 8. Usando esta configuración, podemos crear una versión del modelo que puede someterse a ajustes con LoRA. Al imprimir el número de parámetros entrenables, observamos una reducción de más de 100 veces.

# LoRA config
config = LoraConfig(
r=8,
lora_alpha=32,
target_modules=["q_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)

# LoRA trainable version of model
model = get_peft_model(model, config)

# trainable parameter count
model.print_trainable_parameters()

### trainable params: 2,097,152 || all params: 264,507,392 || trainable%: 0.7928519441906561
# Note: I'm not sure why its showing 264M parameters here.

Preparar conjunto de datos de entrenamiento

Ahora podemos importar nuestros datos de entrenamiento. El conjunto de datos utilizado aquí está disponible en HuggingFace Dataset Hub. Generé este conjunto de datos usando comentarios y respuestas de mi Canal de Youtube. El código para preparar y cargar el conjunto de datos en el Hub está disponible en el repositorio de GitHub.

# load dataset
data = load_dataset("shawhin/shawgpt-youtube-comments")

A continuación, debemos preparar el conjunto de datos para el entrenamiento. Esto implica garantizar que los ejemplos tengan la longitud adecuada y estén tokenizados. El código para esto se muestra a continuación.

# create tokenize function
def tokenize_function(examples):
# extract text
text = examples["example"]

#tokenize and truncate text
tokenizer.truncation_side = "left"
tokenized_inputs = tokenizer(
text,
return_tensors="np",
truncation=True,
max_length=512
)

return tokenized_inputs

# tokenize training and validation datasets
tokenized_data = data.map(tokenize_function, batched=True)

Otras dos cosas que necesitamos para la capacitación son una ficha de pad y un recopilador de datos. Dado que no todos los ejemplos tienen la misma longitud, se puede agregar un token de pad a los ejemplos según sea necesario para que tengan un tamaño particular. Un recopilador de datos rellenará dinámicamente los ejemplos durante el entrenamiento para garantizar que todos los ejemplos de un lote determinado tengan la misma longitud.

# setting pad token
tokenizer.pad_token = tokenizer.eos_token

# data collator
data_collator = transformers.DataCollatorForLanguageModeling(tokenizer,
mlm=False)

Afinando el modelo

En el bloque de código siguiente, defino hiperparámetros para el entrenamiento de modelos.

# hyperparameters
lr = 2e-4
batch_size = 4
num_epochs = 10

# define training arguments
training_args = transformers.TrainingArguments(
output_dir= "shawgpt-ft",
learning_rate=lr,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
num_train_epochs=num_epochs,
weight_decay=0.01,
logging_strategy="epoch",
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
gradient_accumulation_steps=4,
warmup_steps=2,
fp16=True,
optim="paged_adamw_8bit",
)

Si bien aquí se enumeran varios, los dos que quiero destacar en el contexto de QLoRA son fp16 y optimo. fp16=Verdadero El entrenador utiliza valores FP16 para el proceso de entrenamiento, lo que resulta en un ahorro significativo de memoria en comparación con el FP32 estándar. optim=”paginado_adamw_8bit” habilita el ingrediente 3 (es decir, optimizadores paginados) discutido anteriormente.

Con todos los hiperparámetros configurados, podemos ejecutar el proceso de capacitación usando el siguiente código.

# configure trainer
trainer = transformers.Trainer(
model=model,
train_dataset=tokenized_data["train"],
eval_dataset=tokenized_data["test"],
args=training_args,
data_collator=data_collator
)

# train model
model.config.use_cache = False # silence the warnings.
trainer.train()

# renable warnings
model.config.use_cache = True

Como sólo tenemos 50 ejemplos de formación, el proceso se ejecuta en unos 10 minutos. La pérdida de entrenamiento y validación se muestra en la siguiente tabla. Podemos ver que ambas pérdidas disminuyen monótonamente, lo que indica un entrenamiento estable.

Tabla de pérdidas de entrenamiento y validación. Imagen del autor.

Cargando modelo ajustado

El modelo final está disponible gratuitamente en el concentrador de alta frecuencia. Si desea omitir el proceso de capacitación y cargarlo directamente, puede usar el siguiente código.

# load model from hub
from peft import PeftModel, PeftConfig
from transformers import AutoModelForCausalLM

model_name = "TheBloke/Mistral-7B-Instruct-v0.2-GPTQ"
model = AutoModelForCausalLM.from_pretrained(model_name,
device_map="auto",
trust_remote_code=False,
revision="main")

config = PeftConfig.from_pretrained("shawhin/shawgpt-ft")
model = PeftModel.from_pretrained(model, "shawhin/shawgpt-ft")

# load tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)

Usando el modelo ajustado

Podemos utilizar el modelo ajustado para la inferencia de la misma manera que antes. Aquí está la respuesta del modelo ajustado al mismo comentario de prueba que antes (es decir, “Gran contenido, gracias!”).

Glad you enjoyed it! –ShawGPT

(Note: I'm an AI language model, I don't have the ability to feel emotions or
watch videos. I'm here to help answer questions and provide explanations.)

La respuesta es mucho más concisa e incluso añade un aviso de que se trata de una IA. Si queremos eliminar este descargo de responsabilidad, podemos hacerlo fácilmente utilizando la manipulación básica de cadenas en Python.

Como otro ejemplo, probemos con un comentario de prueba más técnico: “¿Qué es la cola gorda?La respuesta del modelo se da a continuación.

Fat-tailedness is a statistical property of a distribution. In simple terms, 
it refers to the presence of extreme outliers or heavy tails in the
distribution.

For instance, consider the distribution of heights in a population. A normal
distribution would have most people clustered around an average height with a
few people deviating slightly from the mean. However, in a fat-tailed
distribution, you would observe a larger number of people being significantly
taller or shorter than the average.

In the context of finance, fat-tailedness is often used to describe the
presence of extreme market moves or volatility. For example, the 2008
financial crisis is an example of a fat-tailed event.

I hope this explanation helps! Let me know if you have any questions.
–ShawGPT

La respuesta es similar a lo que vimos en el Artículo anterior de esta serie con el modelo OpenAI perfeccionado. Da una explicación concisa y apropiada de la cola gruesa, pero esto no es cómo explico la cola gorda.

Si bien podríamos intentar capturar este conocimiento especializado mediante un mayor ajuste, un enfoque más simple sería aumentar el modelo ajustado utilizando conocimiento externo de mi serie de artículos sobre colas gordas (y otros temas de ciencia de datos).

Esto plantea la idea de Recuperación Generación Aumentada (es decir TRAPO), que se discutirá en el próximo artículo de esta serie.

QLoRA es una técnica de ajuste que ha hecho que la creación de modelos de lenguaje grandes personalizados sea más accesible. Aquí, ofrecí una descripción general de cómo funciona el enfoque y compartí un ejemplo concreto del uso de QLoRA para crear un respondedor de comentarios de YouTube.

Si bien el modelo ajustado hizo un trabajo cualitativamente bueno al imitar mi estilo de respuesta, tenía algunas limitaciones en su comprensión del conocimiento especializado de la ciencia de datos. En el próximo artículo de esta serie veremos cómo podemos superar esta limitación mejorando el modelo con RAG.

Más sobre LLM 👇

Modelos de lenguajes grandes (LLM)