Cuántiza modelos Llama con GGML y llama.cpp
Cuántiza modelos con GGML y llama.cpp
GGML vs. GPTQ vs. NF4

Debido al tamaño masivo de los Modelos de Lenguaje Grande (LLMs, por sus siglas en inglés), la cuantización se ha convertido en una técnica esencial para ejecutarlos de manera eficiente. Al reducir la precisión de sus pesos, puedes ahorrar memoria y acelerar la inferencia mientras se preserva la mayor parte del rendimiento del modelo. Recientemente, la cuantización de 8 bits y 4 bits ha desbloqueado la posibilidad de ejecutar LLMs en hardware de consumo. Junto con el lanzamiento de los modelos Llama y técnicas de ajuste de parámetros eficientes (LoRA, QLoRA), esto ha creado un rico ecosistema de LLMs locales que ahora compiten con GPT-3.5 y GPT-4 de OpenAI.
Además del enfoque ingenuo cubierto en este artículo, existen tres técnicas principales de cuantización: NF4, GPTQ y GGML. NF4 es un método estático utilizado por QLoRA para cargar un modelo con una precisión de 4 bits para realizar el ajuste fino. En un artículo anterior, exploramos el método GPTQ y cuantizamos nuestro propio modelo para ejecutarlo en una GPU de consumo. En este artículo, presentaremos la técnica GGML, veremos cómo cuantizar modelos Llama y proporcionaremos consejos y trucos para lograr los mejores resultados.
Puedes encontrar el código en Google Colab y GitHub.
¿Qué es GGML?
GGML es una biblioteca de C enfocada en el aprendizaje automático. Fue creada por Georgi Gerganov, que es lo que representan las iniciales “GG”. Esta biblioteca no solo proporciona elementos fundamentales para el aprendizaje automático, como tensores, sino también un formato binario único para distribuir LLMs.
Recientemente, este formato cambió a GGUF. Este nuevo formato está diseñado para ser extensible, de modo que las nuevas características no deberían romper la compatibilidad con los modelos existentes. También centraliza todos los metadatos en un solo archivo, como tokens especiales, parámetros de escala de RoPE, etc. En resumen, responde a algunos problemas históricos y debería ser a prueba de futuro. Para obtener más información, puedes leer la especificación en esta dirección. En el resto del artículo, llamaremos “modelos GGML” a todos los modelos que utilizan GGUF o formatos anteriores.
- Modelos de lenguaje y amigos Gorilla, HuggingGPT, TaskMatrix y más
- Análisis de la complejidad de series temporales utilizando entropía
- Estimación de profundidad monocular para predecir los relieves supe...
GGML fue diseñado para ser utilizado en conjunto con la biblioteca llama.cpp, también creada por Georgi Gerganov. La biblioteca está escrita en C/C++ para una inferencia eficiente de modelos Llama. Puede cargar modelos GGML y ejecutarlos en una CPU. Originalmente, esta era la diferencia principal con los modelos GPTQ, que se cargan y se ejecutan en una GPU. Sin embargo, ahora puedes transferir algunas capas de tu LLM a la GPU con llama.cpp. Para darte un ejemplo, hay 35 capas para un modelo de 7 mil millones de parámetros. Esto acelera drásticamente la inferencia y te permite ejecutar LLMs que no caben en tu VRAM.

Si las herramientas de línea de comandos son lo tuyo, llama.cpp y el soporte GGUF se han integrado en muchas interfaces gráficas de usuario, como la interfaz web de generación de texto de oobabooga, koboldcpp, LM Studio o ctransformers. Puedes simplemente cargar tus modelos GGML con estas herramientas e interactuar con ellos de manera similar a ChatGPT. Afortunadamente, muchos modelos cuantizados están disponibles directamente en el Hugging Face Hub. Rápidamente notarás que la mayoría de ellos son cuantizados por TheBloke, una figura popular en la comunidad de LLM.
En la próxima sección, veremos cómo cuantizar nuestros propios modelos y ejecutarlos en una GPU de consumo.
¿Cómo cuantizar LLMs con GGML?
Echemos un vistazo a los archivos dentro del repositorio TheBloke/Llama-2-13B-chat-GGML. Podemos ver 14 modelos GGML diferentes, correspondientes a diferentes tipos de cuantización. Siguen una convención de nomenclatura particular: “q” + el número de bits utilizados para almacenar los pesos (precisión) + una variante particular. Aquí tienes una lista de todos los posibles métodos de cuantización y sus casos de uso correspondientes, basada en las tarjetas de modelo creadas por TheBloke:
q2_k
: Utiliza Q4_K para los tensores attention.vw y feed_forward.w2, y Q2_K para los demás tensores.q3_k_l
: Utiliza Q5_K para los tensores attention.wv, attention.wo y feed_forward.w2, de lo contrario utiliza Q3_Kq3_k_m
: Utiliza Q4_K para los tensores attention.wv, attention.wo y feed_forward.w2, de lo contrario utiliza Q3_Kq3_k_s
: Utiliza Q3_K para todos los tensoresq4_0
: Método de cuantificación original, 4 bits.q4_1
: Mayor precisión que q4_0 pero no tan alta como q5_0. Sin embargo, tiene inferencia más rápida que los modelos q5.q4_k_m
: Utiliza Q6_K para la mitad de los tensores attention.wv y feed_forward.w2, de lo contrario utiliza Q4_Kq4_k_s
: Utiliza Q4_K para todos los tensoresq5_0
: Mayor precisión, mayor uso de recursos y inferencia más lenta.q5_1
: Aún mayor precisión, uso de recursos e inferencia más lenta.q5_k_m
: Utiliza Q6_K para la mitad de los tensores attention.wv y feed_forward.w2, de lo contrario utiliza Q5_Kq5_k_s
: Utiliza Q5_K para todos los tensoresq6_k
: Utiliza Q8_K para todos los tensoresq8_0
: Casi indistinguible de float16. Alto uso de recursos y lento. No se recomienda para la mayoría de los usuarios.
Como regla general, recomiendo utilizar Q5_K_M ya que preserva la mayor parte del rendimiento del modelo. Alternativamente, puedes utilizar Q4_K_M si quieres ahorrar algo de memoria. En general, las versiones K_M son mejores que las versiones K_S. No puedo recomendar las versiones Q2 o Q3, ya que disminuyen drásticamente el rendimiento del modelo.
Ahora que conocemos más sobre los tipos de cuantificación disponibles, veamos cómo utilizarlos en un modelo real. Puedes ejecutar el siguiente código en una GPU T4 gratuita en Google Colab. El primer paso consiste en compilar llama.cpp e instalar las bibliotecas requeridas en nuestro entorno de Python.
# Instalar llama.cpp!git clone https://github.com/ggerganov/llama.cpp!cd llama.cpp && git pull && make clean && LLAMA_CUBLAS=1 make!pip install -r llama.cpp/requirements.txt
Ahora podemos descargar nuestro modelo. Utilizaremos el modelo que ajustamos en el artículo anterior, mlabonne/EvolCodeLlama-7b
.
MODEL_ID = "mlabonne/EvolCodeLlama-7b"# Descargar modelo!git lfs install!git clone https://huggingface.co/{MODEL_ID}
Este paso puede llevar un tiempo. Una vez que haya terminado, necesitamos convertir nuestros pesos al formato GGML FP16.
MODEL_NAME = MODEL_ID.split('/')[-1]GGML_VERSION = "gguf"# Convertir a fp16fp16 = f"{MODEL_NAME}/{MODEL_NAME.lower()}.{GGML_VERSION}.fp16.bin"!python llama.cpp/convert.py {MODEL_NAME} --outtype f16 --outfile {fp16}
Finalmente, podemos cuantificar el modelo utilizando uno o varios métodos. En este caso, utilizaremos los métodos Q4_K_M y Q5_K_M que recomendé anteriormente. Este es el único paso que realmente requiere una GPU.
QUANTIZATION_METHODS = ["q4_k_m", "q5_k_m"]for method in QUANTIZATION_METHODS: qtype = f"{MODEL_NAME}/{MODEL_NAME.lower()}.{GGML_VERSION}.{method}.bin" !./llama.cpp/quantize {fp16} {qtype} {method}
Nuestros dos modelos cuantificados ahora están listos para la inferencia. Podemos verificar el tamaño de los archivos bin para ver cuánto los hemos comprimido. El modelo FP16 ocupa 13.5 GB, mientras que el modelo Q4_K_M ocupa 4.08 GB (3.3 veces más pequeño) y el modelo Q5_K_M ocupa 4.78 GB (2.8 veces más pequeño).
Utilicemos llama.cpp para ejecutarlos de manera eficiente. Dado que estamos utilizando una GPU con 16 GB de VRAM, podemos cargar cada capa en la GPU. En este caso, representa 35 capas (modelo con 7b de parámetros), por lo que utilizaremos el parámetro -ngl 35
. En el siguiente bloque de código, también ingresaremos un indicador y el método de cuantización que deseamos utilizar.
import osmodel_list = [archivo for archivo in os.listdir(NOMBRE_MODELO) if VERSION_GGML in archivo]indicador = input("Ingrese su indicador: ")metodo_elegido = input("Especifique el método de cuantización para ejecutar el modelo (opciones: " + ", ".join(model_list) + "): ")# Verificar que el método elegido esté en la listaif metodo_elegido not in model_list: print("¡Método elegido no válido!")else: tipo_cuanti = f"{NOMBRE_MODELO}/{NOMBRE_MODELO.lower()}.{VERSION_GGML}.{metodo}.bin" !./llama.cpp/main -m {tipo_cuanti} -n 128 --color -ngl 35 -p "{indicador}"
Pidámosle al modelo “Escribe una función en Python para imprimir los primeros n números de Fibonacci” utilizando el método Q5_K_M. Si observamos los registros, podemos confirmar que hemos cargado correctamente nuestras capas gracias a la línea “llm_load_tensors: offloaded 35/35 layers to GPU”. Aquí está el código que generó el modelo:
def fib(n): if n == 0 or n == 1: return n return fib(n - 2) + fib(n - 1)for i in range(1, 10): print(fib(i))
No fue un indicador muy complejo, pero produjo rápidamente un código funcional. Con este GGML, puede utilizar su LLM local como asistente en una terminal utilizando el modo interactivo (-i
). Tenga en cuenta que esto también funciona en Macbooks con Metal Performance Shaders (MPS) de Apple, que es una excelente opción para ejecutar LLMs.
Finalmente, podemos enviar nuestro modelo cuantizado a un nuevo repositorio en Hugging Face Hub con el sufijo “-GGUF”. Primero, inicie sesión y modifique el siguiente bloque de código para que coincida con su nombre de usuario.
!pip install -q huggingface_hubnombre_usuario = "mlabonne"from huggingface_hub import notebook_login, create_repo, HfApinotebook_login()
Ahora podemos crear el repositorio y cargar nuestros modelos. Utilizamos el parámetro allow_patterns
para filtrar qué archivos cargar, de modo que no subamos todo el directorio.
api = HfApi()# Crear repositoriocreate_repo( repo_id=f"{nombre_usuario}/{NOMBRE_MODELO}-GGML", repo_type="modelo", exist_ok=True)# Cargar modelos binariosapi.upload_folder( folder_path=NOMBRE_MODELO, repo_id=f"{nombre_usuario}/{NOMBRE_MODELO}-GGML", allow_patterns=f"*{VERSION_GGML}*",)
¡Hemos cuantizado, ejecutado y subido modelos GGML al Hugging Face Hub! En la siguiente sección, exploraremos cómo GGML realmente cuantiza estos modelos.
Cuantización con GGML
La forma en que GGML cuantiza los pesos no es tan sofisticada como la de GPTQ. Básicamente, agrupa bloques de valores y los redondea a una precisión inferior. Algunas técnicas, como Q4_K_M y Q5_K_M, implementan una mayor precisión para capas críticas. En este caso, cada peso se almacena con una precisión de 4 bits, con la excepción de la mitad de los tensores attention.wv y feed_forward.w2. Experimentalmente, esta precisión mixta demuestra ser un buen equilibrio entre precisión y uso de recursos.
Si observamos el archivo ggml.c, podemos ver cómo se definen los bloques. Por ejemplo, la estructura block_q4_0
se define como:
#define QK4_0 32typedef struct { ggml_fp16_t d; // delta uint8_t qs[QK4_0 / 2]; // nibbles / quants} block_q4_0;
En GGML, los pesos se procesan en bloques, cada uno compuesto por 32 valores. Para cada bloque, se deriva un factor de escala (delta) del valor de peso más grande. Luego, todos los pesos del bloque se escalan, cuantizan y empaquetan eficientemente para su almacenamiento (nibbles). Este enfoque reduce significativamente los requisitos de almacenamiento al tiempo que permite una conversión relativamente simple y determinista entre los pesos originales y cuantizados.
Ahora que conocemos más sobre el proceso de cuantización, podemos comparar los resultados con NF4 y GPTQ.
NF4 vs. GGML vs. GPTQ
¿Qué técnica es mejor para la cuantización de 4 bits? Para responder a esta pregunta, necesitamos presentar los diferentes backends que ejecutan estos LLM cuantizados. Para los modelos GGML, llama.cpp con modelos Q4_K_M es la opción a seguir. Para los modelos GPTQ, tenemos dos opciones: AutoGPTQ o ExLlama. Finalmente, los modelos NF4 se pueden ejecutar directamente en transformers con la bandera --load-in-4bit
.
Oobabooga realizó múltiples experimentos en una excelente publicación de blog que compara diferentes modelos en términos de perplejidad (menor es mejor):
Basándonos en estos resultados, podemos decir que los modelos GGML tienen una ligera ventaja en términos de perplejidad. La diferencia no es particularmente significativa, por lo que es mejor enfocarse en la velocidad de generación en términos de tokens/segundo. La mejor técnica depende de su GPU: si tiene suficiente VRAM para ajustar el modelo cuantizado completo, GPTQ con ExLlama será el más rápido. Si ese no es el caso, puede descargar algunas capas y usar modelos GGML con llama.cpp para ejecutar su LLM.
Conclusión
En este artículo, presentamos la biblioteca GGML y el nuevo formato GGUF para almacenar eficientemente estos modelos cuantizados. Lo usamos para cuantizar nuestro propio modelo Llama en diferentes formatos (Q4_K_M y Q5_K_M). Luego ejecutamos el modelo GGML y subimos nuestros archivos binarios al Hugging Face Hub. Finalmente, profundizamos en el código de GGML para entender cómo realmente cuantiza los pesos y lo comparamos con NF4 y GPTQ.
La cuantización es un vector formidable para democratizar los LLM al reducir el costo de ejecutarlos. En el futuro, la precisión mixta y otras técnicas seguirán mejorando el rendimiento que podemos lograr con pesos cuantizados. Hasta entonces, espero que hayas disfrutado leyendo este artículo y hayas aprendido algo nuevo.
Si estás interesado en contenido técnico sobre LLMs, sígueme en VoAGI.
Artículos sobre cuantización
Parte 1: Introducción a la cuantización de pesos
Reduciendo el tamaño de los modelos de lenguaje grandes con cuantización de 8 bits
towardsdatascience.com
Parte 2: Cuantización de 4 bits con GPTQ
Cuantiza tus propios LLM usando AutoGPTQ
towardsdatascience.com
Obtén más información sobre el aprendizaje automático y apoya mi trabajo con un clic, conviértete en miembro de VoAGI aquí:
Únete a VoAGI con mi enlace de referencia – Maxime Labonne
Como miembro de VoAGI, una parte de tu tarifa de membresía va a los escritores que lees y obtienes acceso completo a cada historia…
VoAGI.com