Ajuste fino de un modelo Llama-2 7B para la generación de código en Python

Ajuste fino de un modelo Llama-2 7B para generación de código en Python

Una demostración sobre cómo ajustar el nuevo Llama-2 usando PEFT, QLoRa y las utilidades de Huggingface

Imagen creada por el autor en Leonardo.ai

Hace aproximadamente 2 semanas, el mundo de la IA generativa se sorprendió con el lanzamiento del nuevo modelo de IA Llama-2 por parte de la compañía Meta. Su predecesor, Llama-1, fue un punto de inflexión en la industria de LLM, ya que con el lanzamiento de sus pesos junto con nuevas técnicas de ajuste fino, se produjo una gran creación de modelos LLM de código abierto que condujo a la aparición de modelos de alto rendimiento como Vicuna, Koala, …

En este artículo, discutiremos brevemente algunos puntos relevantes de este modelo, pero nos centraremos en mostrar cómo podemos entrenar rápidamente el modelo para una tarea específica utilizando bibliotecas y herramientas estándar en este mundo. No haremos un análisis exhaustivo del nuevo modelo, ya que ya existen numerosos artículos publicados sobre el tema.

Nuevo modelo Llama-2

A mediados de julio, Meta lanzó su nueva familia de modelos preentrenados y ajustados llamada Llama-2, con un carácter de código abierto y comercial para facilitar su uso y expansión. El modelo base se lanzó con una versión de chat y tamaños de 7B, 13B y 70B. Junto con los modelos, se publicaron los documentos correspondientes que describen sus características y puntos relevantes del proceso de aprendizaje, los cuales proporcionan información muy interesante sobre el tema.

Una versión actualizada de Llama 1, entrenada con una nueva mezcla de datos disponibles públicamente. El tamaño del corpus de preentrenamiento se incrementó en un 40%, la longitud del contexto del modelo se duplicó y se adoptó la atención de consulta agrupada. Se lanzan variantes con 7B, 13B y 70B de parámetros, junto con variantes de 34B mencionadas en el documento pero no lanzadas.[1]

Para el preentrenamiento, se utilizaron un 40% más de tokens, alcanzando 2T, se duplicó la longitud del contexto y se aplicó la técnica de atención de consulta agrupada (GQA) para acelerar la inferencia en el modelo más pesado de 70B. En la arquitectura estándar del transformador, se utilizan la normalización RMSNorm, la activación SwiGLU y la incrustación posicional rotatoria, la longitud del contexto alcanza los 4096 tokens y se aplica un optimizador Adam con un programa de tasas de aprendizaje coseno, una decaimiento de peso de 0.1 y recorte de gradientes.

La etapa de Ajuste Fino Supervisado (SFT) se caracteriza por priorizar ejemplos de calidad sobre cantidad, ya que numerosos informes muestran que el uso de datos de alta calidad resulta en un mejor rendimiento del modelo final. Por último, se aplica un paso de Aprendizaje por Reforzamiento con Retroalimentación Humana (RLHF) para alinear el modelo con las preferencias del usuario. Se recopilan una multitud de ejemplos donde los anotadores seleccionan su salida de modelo preferida en una comparación binaria. Estos datos se utilizan para entrenar un modelo de recompensa, donde el enfoque se centra en la utilidad y la seguridad.

En resumen:

· Entrenado con 2T Tokens

· Uso comercial permitido

· Modelos de chat para casos de uso de diálogo

· Ventana de contexto predeterminada de 4096 (se puede aumentar)

· Versión de parámetros 7B, 13B y 70B

· El modelo de 70B adoptó la atención de consulta agrupada (GQA)

· Los modelos de chat pueden utilizar herramientas y complementos

· LLaMA 2-CHAT tan bueno como OpenAI ChatGPT

El conjunto de datos para el ajuste

Para nuestro proceso de ajuste, tomaremos un conjunto de datos que contiene aproximadamente 18,000 ejemplos en los que se le pide al modelo que construya un código Python que resuelva una tarea determinada. Esta es una extracción del conjunto de datos original [2], donde solo se seleccionan los ejemplos de lenguaje Python. Cada fila contiene la descripción de la tarea a resolver, un ejemplo de entrada de datos para la tarea si corresponde, y se proporciona el fragmento de código generado que resuelve la tarea [3].

# Cargar conjunto de datos desde el hubdataset = load_dataset(dataset_name, split=dataset_split)# Mostrar tamaño del conjunto de datosprint(f"tamaño del conjunto de datos: {len(dataset)}")# Mostrar un ejemploprint(dataset[randrange(len(dataset))])

Creando el prompt

Para llevar a cabo el ajuste fino de una instrucción, debemos transformar cada uno de nuestros ejemplos de datos como si fuera una instrucción, resaltando sus secciones principales de la siguiente manera:

def format_instruction(sample): return f"""### Instrucción: Utiliza la siguiente Tarea y la Entrada dada para escribir la Respuesta, que es un código de programación que puede resolver la siguiente Tarea:### Tarea:{sample['instruction']}### Entrada:{sample['input']}### Respuesta:{sample['output']}"""

Salida:

### Instrucción: Utiliza la siguiente Tarea y la Entrada dada para escribir la Respuesta, que es un código de programación que puede resolver la siguiente Tarea:### Tarea:Desarrolla un programa en Python que imprima "¡Hola, mundo!" cada vez que se ejecute.### Entrada:### Respuesta:#Programa en Python para imprimir "¡Hola, mundo!"print("Hola, mundo!")
Imagen de Irvan Smith de Unsplash

Ajuste fino del modelo

Para llevar a cabo esta etapa, hemos utilizado el entorno de Google Colab, donde hemos desarrollado un cuaderno que nos permite ejecutar el entrenamiento de manera interactiva y también un script de Python para ejecutar el entrenamiento en modo no supervisado. Para las primeras pruebas, una instancia T4 con una alta capacidad de RAM es suficiente, pero cuando se trata de ejecutar todo el conjunto de datos y las épocas, hemos optado por utilizar una instancia A100 para acelerar el entrenamiento y asegurarnos de que su tiempo de ejecución sea razonable.

Para poder compartir el modelo, iniciaremos sesión en el Huggingface hub utilizando el token correspondiente, de modo que al final de todo el proceso, subiremos los archivos del modelo para que puedan ser compartidos con el resto de los usuarios.

from huggingface_hub import loginfrom dotenv import load_dotenvimport os# Cargar las variables de entornoload_dotenv()# Iniciar sesión en el Hugging Face Hublogin(token=os.getenv("HF_HUB_TOKEN"))

Técnicas de ajuste fino: PEFT, Lora y QLora

En los últimos meses, han aparecido algunos artículos que muestran cómo se pueden utilizar las técnicas de PEFT para entrenar modelos de lenguaje grandes con una reducción drástica de los requisitos de RAM y, en consecuencia, permitir el ajuste fino de estos modelos en una sola GPU de tamaño razonable. Los pasos habituales para entrenar un LLM consisten, primero, en un entrenamiento previo intensivo en miles de millones o billones de tokens para obtener un modelo base, y luego se realiza un ajuste fino en este modelo para especializarlo en una tarea específica. En esta fase de ajuste fino es donde tiene su propósito la técnica PEFT.

El Ajuste Fino Eficiente de Parámetros (PEFT) nos permite reducir considerablemente los requisitos de RAM y almacenamiento al ajustar solo un pequeño número de parámetros adicionales, manteniendo prácticamente todos los parámetros del modelo congelados. Se ha comprobado que PEFT produce una buena generalización con conjuntos de datos de volumen relativamente bajo. Además, mejora la reutilización y portabilidad del modelo, ya que los pequeños puntos de control obtenidos se pueden agregar fácilmente al modelo base, y el modelo base se puede ajustar y reutilizar fácilmente en múltiples escenarios mediante la adición de los parámetros PEFT. Por último, dado que el modelo base no se ajusta, se preserva todo el conocimiento adquirido en la fase de entrenamiento previo, evitando así el olvido catastrófico.

La mayoría de las técnicas de PEFT más utilizadas tienen como objetivo mantener intacto el modelo base preentrenado y agregar nuevas capas o parámetros encima de él. Estas capas se llaman “Adaptadores” y la técnica de su ajuste se llama “ajuste de adaptadores”, agregamos estas capas al modelo base preentrenado y solo entrenamos los parámetros de estas nuevas capas. Sin embargo, un problema grave con este enfoque es que estas capas aumentan la latencia en la fase de inferencia, lo que hace que el proceso sea ineficiente en muchos escenarios. En la técnica LoRa, una Adaptación de Baja Rango de Modelos de Lenguaje Grandes, la idea no es incluir nuevas capas, sino agregar valores a los parámetros de una manera que evite este problema de latencia en la fase de inferencia. LoRa entrena y almacena los cambios de los pesos adicionales mientras congela todos los pesos del modelo preentrenado. Por lo tanto, entrenamos una nueva matriz de pesos con los cambios en la matriz del modelo preentrenado, y esta nueva matriz se descompone en 2 matrices de bajo rango como se explica aquí:

Deje que todos los parámetros de un LLM estén en la matriz W0 y los cambios de peso adicionales en la matriz ∆W, los pesos finales se convierten en W0 + ∆W. Los autores de LoRA [1] propusieron que el cambio en la matriz de cambio de peso ∆W se puede descomponer en dos matrices de bajo rango A y B. LoRA no entrena directamente los parámetros en ∆W, sino los parámetros en A y B. Por lo tanto, el número de parámetros entrenables es mucho menor. Supongamos hipotéticamente que la dimensión de A es de 100 * 1 y la de B es de 1 * 100, el número de parámetros en ∆W será de 100 * 100 = 10000. Solo hay 100 + 100 = 200 para entrenar en A y B, en lugar de 10000 para entrenar en ∆W

[4]. Explicación del Dr. Dataman en Ajuste fino de un GPT – LoRA

El tamaño de estas matrices de bajo rango está definido por el parámetro r. Cuanto menor sea este valor, menos parámetros se deben entrenar, por lo tanto, menos esfuerzo y más rápido, pero por otro lado, una posible pérdida de información y rendimiento. Si desea una explicación más detallada, puede consultar el artículo original o hay muchos artículos que lo explican en detalle, como [4].

Finalmente, QLoRa [6] consiste en aplicar cuantización al método LoRa permitiendo una cuantización normal de 4 bits, nf4, un tipo optimizado para pesos distribuidos normalmente; doble cuantización para reducir la huella de memoria y la optimización de la memoria unificada de NVIDIA. Estas son técnicas para optimizar el uso de memoria para lograr un entrenamiento “más ligero” y menos costoso.

Imagen del autor de Leonardo.ai

Implementar QLoRa en nuestro experimento requiere especificar la configuración BitsAndBytes, descargar el modelo preentrenado en cuantización de 4 bits y definir una LoraConfig. Finalmente, necesitamos recuperar el tokenizador.

# Obtener el tipo de cálculo de precisión de 4 bitscompute_dtype = getattr(torch, bnb_4bit_compute_dtype)# Configuración BitsAndBytesConfig int-4configbnb_config = BitsAndBytesConfig( load_in_4bit=use_4bit, bnb_4bit_use_double_quant=use_double_nested_quant, bnb_4bit_quant_type=bnb_4bit_quant_type, bnb_4bit_compute_dtype=compute_dtype)# Cargar modelo y tokenizadormodel = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, use_cache = False, device_map=device_map)model.config.pretraining_tp = 1# Cargar el tokenizadortokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"

Parámetros definidos,

# Activar precisión de 4 bits cargando el modelo baseuse_4bit = True# Tipo de cálculo para modelos base de 4 bitsbnb_4bit_compute_dtype = "float16"# Tipo de cuantización (fp4 o nf4)bnb_4bit_quant_type = "nf4"# Activar cuantización anidada para modelos base de 4 bits (doble cuantización)use_double_nested_quant = False# Dimensión de atención LoRAlora_r = 64# Parámetro alfa para escala LoRAlora_alpha = 16# Probabilidad de deserción para capas LoRAlora_dropout = 0.1

Y los siguientes pasos son conocidos para todos los usuarios de Hugging Face, configurando los argumentos de entrenamiento y creando un entrenador. Como estamos ejecutando un ajuste fino de instrucciones, llamamos al método SFTTrainer que encapsula la definición del modelo PEFT y otros pasos.

# Definir los argumentos de entrenamientoargs = TrainingArguments( output_dir=output_dir, num_train_epochs=num_train_epochs, per_device_train_batch_size=per_device_train_batch_size, # 6 if use_flash_attention else 4, gradient_accumulation_steps=gradient_accumulation_steps, gradient_checkpointing=gradient_checkpointing, optim=optim, logging_steps=logging_steps, save_strategy="epoch", learning_rate=learning_rate, weight_decay=weight_decay, fp16=fp16, bf16=bf16, max_grad_norm=max_grad_norm, warmup_ratio=warmup_ratio, group_by_length=group_by_length, lr_scheduler_type=lr_scheduler_type, disable_tqdm=disable_tqdm, report_to="tensorboard", seed=42)# Crear el entrenadortrainer = SFTTrainer( model=model, train_dataset=dataset, peft_config=peft_config, max_seq_length=max_seq_length, tokenizer=tokenizer, packing=packing, formatting_func=format_instruction, args=args,)# entrenar el modelotrainer.train() # no habrá una barra de progreso ya que tqdm está desactivado# guardar el modelo en localtrainer.save_model()

Los parámetros se pueden encontrar en mi repositorio de GitHub, la mayoría de ellos se usan comúnmente en otros scripts de ajuste fino en LLMs y son los siguientes:

# Número de épocas de entrenamientonum_train_epochs = 1# Habilitar entrenamiento fp16/bf16 (establecer bf16 en True con un A100)fp16 = Falsebf16 = True# Tamaño de lote por GPU para entrenamientoper_device_train_batch_size = 4# Número de pasos de actualización para acumular los gradientesgradient_accumulation_steps = 1# Habilitar comprobación de gradiente en el punto mediogradient_checkpointing = True# Máxima normalización del gradiente (recorte del gradiente)max_grad_norm = 0.3# Tasa de aprendizaje inicial (optimizador AdamW)learning_rate = 2e-4# Decaimiento de peso a aplicar a todas las capas excepto los pesos de sesgo/LayerNormweight_decay = 0.001# Optimizador a utilizaroptim = "paged_adamw_32bit"# Programa de tasa de aprendizajelr_scheduler_type = "cosine" #"constant"# Proporción de pasos para un calentamiento lineal (de 0 a tasa de aprendizaje)warmup_ratio = 0.03# Agrupar secuencias en lotes con la misma longitud# Ahorra memoria y acelera considerablemente el entrenamientogroup_by_length = False# Guardar punto de control cada X pasos de actualizaciónsave_steps = 0# Registrar cada X pasos de actualizaciónlogging_steps = 25# Desactivar tqdmdisable_tqdm= True

Fusionar el modelo base y los pesos del adaptador

Como mencionamos, hemos entrenado “pesos de modificación” en el modelo base, nuestro modelo final requiere fusionar el modelo preentrenado y los adaptadores en un solo modelo.

from peft import AutoPeftModelForCausalLMmodel = AutoPeftModelForCausalLM.from_pretrained(    args.output_dir,    low_cpu_mem_usage=True,    return_dict=True,    torch_dtype=torch.float16,    device_map=device_map,    )# Fusionar LoRA y el modelo basemerged_model = model.merge_and_unload()# Guardar el modelo fusionadomerged_model.save_pretrained("merged_model",safe_serialization=True)tokenizer.save_pretrained("merged_model")# enviar el modelo fusionado al hubmerged_model.push_to_hub(hf_model_repo)tokenizer.push_to_hub(hf_model_repo)

Puedes encontrar y descargar el modelo en mi cuenta de Hugging Face edumunozsala/llama-2-7b-int4-python-code-20k. ¡Prueba!

Inferencia o generación de código Python

Y finalmente, te mostraremos cómo puedes descargar el modelo desde el Hugging Face Hub y llamar al modelo para generar un resultado preciso:

import torchfrom transformers import AutoModelForCausalLM, AutoTokenizer# Obtener el tokenizadortokenizer = AutoTokenizer.from_pretrained(hf_model_repo)# Cargar el modelomodel = AutoModelForCausalLM.from_pretrained(hf_model_repo, load_in_4bit=True,                                              torch_dtype=torch.float16,                                             device_map=device_map)# Crear una instruccióninstruction="Optimizar un fragmento de código escrito en Python. El fragmento de código debe crear una lista de números del 0 al 10 que sean divisibles por 2."input=""prompt = f"""### Instrucción:Utiliza la Tarea a continuación y la Entrada proporcionada para escribir la Respuesta, que es un código de programación que puede resolver la Tarea.### Tarea:{instruction}### Entrada:{input}### Respuesta:"""# Tokenizar la entradainput_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()# Ejecutar el modelo para inferir una salidaoutputs = model.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)# Imprimir el resultadoprint(f"Instrucción:\n{prompt}\n")print(f"Instrucción generada:\n{tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")

Instrucción:### Instrucción:Utiliza la Tarea a continuación y la Entrada proporcionada para escribir la Respuesta, que es un código de programación que puede resolver la Tarea.### Tarea:Optimizar un fragmento de código escrito en Python. El fragmento de código debe crear una lista de números del 0 al 10 que sean divisibles por 2.### Entrada:arr = []for i in range(10): if i % 2 == 0: arr.append(i)### Respuesta:Instrucción generada:arr = [i for i in range(10) if i % 2 == 0]Resultado esperado:arr = [i for i in range(11) if i % 2 == 0]

Gracias a Maxime Labonne por un excelente artículo [9] y a Philipp Schmid quien proporciona un código inspirador [8]. Sus artículos son lectura obligada para todos los interesados en Llama 2 y el ajuste fino del modelo.

Y eso es todo lo que tengo que mencionar, ¡espero que encuentres útil este artículo y los aplausos son bienvenidos! Puedes seguirme y suscribirte a mis artículos, o incluso conectarte conmigo a través de Linkedin. El código está disponible en mi repositorio de Github.

Referencias

[1] Artículo Llama-2

[2] Enlace al conjunto de datos original en el Huggingface hub

[3] Enlace al conjunto de datos utilizado en el Huggingface hub

[4] Ajuste fino de un GPT – LoRA por Chris Kuo/Dr. Dataman

[5] Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang y Weizhu Chen. (2021). LoRA: Adaptación de bajo rango de modelos de lenguaje grandes. arXiv:2106.09685

[6]. QLoRa: Ajuste fino eficiente de LLMs cuantizados

[7] Ajuste fino de pocos ejemplos con parámetros más eficientes es mejor y más económico que el aprendizaje en contexto

[8] Guía extendida: Ajuste de instrucciones para Llama 2 por Philipp Schmid.

[9] Ajusta tu propio modelo Llama 2 en un cuaderno de Colab por Maxime Labonne

[10]. Mi repositorio de Github

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

El debate sobre la seguridad de la IA está dividiendo Silicon Valley

El drama de liderazgo de OpenAI es el último estallido en el acalorado debate entre los tecnócratas que buscan la seg...

Inteligencia Artificial

Investigación de Google revela Transformadores Generativos de Vocabulario Infinito (GIVT) Pioneros en secuencias de vectores de valor real en IA

Los Transformers fueron introducidos por primera vez y rápidamente se elevaron a la prominencia como la arquitectura ...

Inteligencia Artificial

Estas herramientas podrían ayudar a proteger nuestras imágenes de la IA

Sin embargo, estas herramientas no son perfectas, ni suficientes por sí solas.

Investigación

Investigadores de MIT CSAIL discuten las fronteras del AI generativo.

Expertos se reúnen para examinar el código, lenguaje e imágenes generados por la inteligencia artificial, así como su...

Inteligencia Artificial

El aumento de los costos de los centros de datos vinculados a las demandas de la inteligencia artificial

El uso de energía asociado con la ejecución de cálculos intensivos de IA se está convirtiendo rápidamente en un facto...