Ajuste fino de LLM local en Mac (M1 16 GB) | por Shaw Talebi

1) Configuración del entorno

Antes de ejecutar el código de ejemplo, necesitaremos configurar nuestro entorno Python. El primer paso es descargar el código desde el sitio Repositorio de GitHub.

git clone https://github.com/ShawhinT/YouTube-Blog.git

El código para este ejemplo está en el LLM/qlora-mlx subdirectorio. Podemos navegar a esta carpeta y crear un nuevo entorno Python (aquí lo llamo mlx-env).

# change dir
cd LLMs/qlora-mlx

# create py venv
python -m venv mlx-env

A continuación, activamos el entorno e instalamos los requisitos desde el archivo requirements.txt. Nota: mlx requiere que su sistema tenga un chip de la serie M, Python >= 3.8 y macOS >= 13.5.

# activate venv
source mlx-env/bin/activate

# install requirements
pip install -r requirements.txt

2) Inferencia con modelo no ajustado

Ahora que tenemos mlx y otras dependencias instaladas, ¡ejecutemos algo de código Python! Comenzamos importando bibliotecas útiles.

# import modules (this is Python code now)
import subprocess
from mlx_lm import load, generate

Usaremos el subproceso módulo para ejecutar comandos de terminal a través de Python y el mlx-lm biblioteca para ejecutar inferencias en nuestro modelo entrenado previamente.

mlx-lm Está construido sobre mlx y es Diseñado específicamente para modelos en ejecución del centro Hugging FaceA continuación se explica cómo podemos usarlo para generar texto a partir de un modelo existente.

# define inputs
model_path = "mlx-community/Mistral-7B-Instruct-v0.2-4bit"
prompt = prompt_builder("Great content, thank you!")
max_tokens = 140

# load model
model, tokenizer = load(model_path)

# generate response
response = generate(model, tokenizer, prompt=prompt,
max_tokens = max_tokens,
verbose=True)

Nota: Cualquiera de los cientos de modelos de Hugging Face Página de la comunidad mlx se puede utilizar fácilmente para inferencias. Si desea utilizar un modelo que no está disponible (poco probable), puede utilizar el scripts/convertir.py script para convertirlo a un formato compatible.

El constructor_de_avisos() La función toma un comentario de YouTube y lo integra en una plantilla de mensaje, como se muestra a continuación.

# prompt format
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.
"""

# define lambda function
prompt_builder = lambda comment: f'''<s>[INST] {intstructions_string} \n{comment} \n[/INST]\n'''

Así responde la modelo al comentario “¡Excelente contenido, gracias!Sin ajustes finos.

–ShawGPT: Thank you for your kind words! I'm glad you found the content helpful
and enjoyable. If you have any specific questions or topics you'd like me to
cover in more detail, feel free to ask!

Si bien la respuesta es coherente, hay 2 problemas principales con él. 1) la firma “-ShawGPT” se coloca al principio de la respuesta en lugar del final (como se indica), y 2) la respuesta es mucho más larga de lo que yo hubiera deseado. de hecho Responder a un comentario como este.

3) Preparación de datos de entrenamiento

Antes de poder ejecutar el trabajo de ajuste fino, debemos Preparar conjuntos de datos de entrenamiento, prueba y validación.Aquí, utilizo 50 comentarios y respuestas reales de mi canal de YouTube para la capacitación y 10 comentarios/respuestas para la validación y las pruebas (70 ejemplos en total).

A continuación se muestra un ejemplo de capacitación. Está en formato JSON, es decir, un par clave-valor donde la clave = “texto” y el valor = la solicitud, el comentario y la respuesta fusionados.

{"text": "<s>[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 '\u2013ShawGPT'. 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.\n\nPlease respond to the following comment.\n \nThis was a very
thorough introduction to LLMs and answered many questions I had. Thank you.
\n[/INST]\nGreat to hear, glad it was helpful :) -ShawGPT</s>"}

El código para generar los conjuntos de datos de entrenamiento, prueba y validación a partir de un archivo .csv está disponible en GitHub.

4) Modelo de ajuste fino

Con nuestros datos de entrenamiento preparados, podemos ajustar nuestro modelo. Aquí, utilizo el lora.py Ejemplo de script creado por el mlx equipo.

Este script se guarda en el guiones carpeta del repositorio que clonamos, y los datos de entrenamiento/prueba/val se guardan en el datos carpeta. Para ejecutar el trabajo de ajuste fino, podemos ejecutar el siguiente comando de terminal.

python scripts/lora.py --model mlx-community/Mistral-7B-Instruct-v0.2-4bit \
--train \
--iters 100 \
--steps-per-eval 10 \
--val-batches -1 \
--learning-rate 1e-5 \
--lora-layers 16 \
--test

# --train = runs LoRA training
# --iters = number of training steps
# --steps-per-eval = number steps to do before computing val loss
# --val-batches = number val dataset examples to use in val loss (-1 = all)
# --learning-rate (same as default)
# --lora-layers (same as default)
# --test = computes test loss at the end of training

Para que el entrenamiento se ejecutara lo más rápido posible, cerré todos los demás procesos en mi máquina para asignar la mayor cantidad de memoria posible al proceso de ajuste fino. En mi M1 con 16 GB de memoria, esto tomó aproximadamente 15–20 minutos correr y alcanzó su punto máximo alrededor de 13–14 GB de memoria.

Nota: Tuve que hacer un cambio en las líneas 340-341 del secuencia de comandos lora.py para evitar el sobreajuste, que implicaba cambiar el rango de los adaptadores LoRA de r=8 a r=4.

5) Inferencia con modelo ajustado

Una vez finalizado el entrenamiento, se crea un archivo llamado adaptadores.npz Aparecerá en el directorio de trabajo. Contiene los pesos del adaptador LoRA.

Para realizar inferencias con estos, podemos utilizar nuevamente el lora.py. Esta vez, sin embargo, en lugar de ejecutar el script directamente desde la terminal, utilicé el subproceso módulo para ejecutar el script en Python. Esto me permite usar el constructor_de_avisos() función definida anteriormente.

# define inputs
adapter_path = "adapters.npz" # same as default
max_tokens_str = "140" # must be string

# define command
command = ['python', 'scripts/lora.py', '--model', model_path,
'--adapter-file', adapter_path,
'--max-tokens', max_tokens_str,
'--prompt', prompt]

# run command and print results continuously
run_command_with_live_output(command)

El ejecutar_comando_con_salida_en_vivo() es una función auxiliar (cortesía de ChatGPT) que Imprime continuamente las salidas del proceso desde el comando de terminalEsto evita tener que esperar hasta que se realice la inferencia para ver los resultados.

def run_command_with_live_output(command: list[str]) -> None:
"""
Courtesy of ChatGPT:
Runs a command and prints its output line by line as it executes.

Args:
command (List[str]): The command and its arguments to be executed.

Returns:
None
"""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

# Print the output line by line
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())

# Print the error output, if any
err_output = process.stderr.read()
if err_output:
print(err_output)

Así es como el modelo responde al mismo comentario (¡Excelente contenido, gracias!), pero ahora Después del ajuste fino.

Glad you enjoyed it! -ShawGPT

Esta respuesta es mucho mejor que antes del ajuste fino. La firma “-ShawGPT” está en el lugar correcto y suena como algo que yo haría. de hecho decir.

Pero es un comentario fácil de responder. Veamos algo más desafiante, como el que se muestra a continuación.

Comment: 
I discovered your channel yesterday and I am hucked, great job.
It would be nice to see a video of fine tuning ShawGPT using HF, I saw a video
you did running on Colab using Mistal-7b, any chance to do a video using your
laptop (Mac) or using HF spaces?
Response:
Thanks, glad you enjoyed it! I'm looking forward to doing a fine tuning video
on my laptop. I've got an M1 Mac Mini that runs the latest versions of the HF
API. -ShawGPT

A primera vista, es una respuesta excelente. El modelo responde de forma adecuada y se despide como es debido. También tiene suerte al decir que tengo una Mac Mini con procesador M1 😉

Sin embargo, esto presenta dos problemas. En primer lugar, las Mac Mini son computadoras de escritorio, no portátiles. En segundo lugar, el ejemplo no utiliza directamente la API HF.

Aquí compartí un ejemplo simple de ajuste fino local para Macs de la serie M. Los datos y el código para este ejemplo están disponibles gratuitamente en Repositorio de GitHub.

Espero que esto pueda ser un punto de partida útil para sus casos de uso. Si tiene ¿Alguna sugerencia para contenido futuro? En esta serie, por favor déjame saber en los comentarios 🙂

Más sobre LLM 👇

Modelos de lenguaje extensos (LLM)