QLoRA
QLoRA ajusta finamente los LLM entrenando adaptadores LoRA sobre un modelo base congelado almacenado en formato NF4 de 4 bits, lo que permite ejecutar un modelo de 65.000 millones de parámetros en una sola GPU de 48 GB.
QLoRA (adaptación cuantizada de bajo rango) es un método de ajuste fino que congela un modelo de lenguaje grande (LLM) preentrenado con una precisión de 4 bits y entrena pequeños adaptadores LoRA sobre él, propagando los gradientes hacia atrás a través de los pesos cuantizados hasta los adaptadores. Presentado por Dettmers et al. en el artículo sobre QLoRA de 2023, redujo la memoria de GPU necesaria para ajustar un modelo de 65.000 millones de parámetros de más de 780 GB a menos de 48 GB, suficiente para una GPU de estación de trabajo, y mantuvo el rendimiento del ajuste fino de 16 bits en la tarea. Los modelos Guanaco del artículo, ajustados de esta forma, alcanzaron el 99,3 % de la puntuación de ChatGPT en la evaluación comparativa Vicuna tras 24 horas de entrenamiento en una única GPU.
Cómo funciona QLoRA#
QLoRA combina un modelo base comprimido y congelado con tres técnicas para ahorrar memoria:
- NormalFloat de 4 bits (NF4): Cada peso se almacena como un código de 4 bits que apunta a uno de 16 niveles situados en los cuantiles de una distribución normal, con una escala de valor absoluto máximo por bloque de 64 pesos. Los pesos del modelo preentrenado suelen seguir aproximadamente una distribución normal; por eso, los niveles de los cuantiles ofrecen más resolución cerca de cero, donde se encuentran la mayoría de los pesos.
- Doble cuantización: Las escalas de cada bloque también se cuantizan a 8 bits en bloques de 256, lo que reduce su sobrecarga de 0,5 a unos 0,127 bits por parámetro y ahorra aproximadamente 3 GB en un modelo de 65B.
- Optimizadores paginados: Los estados del optimizador residen en la memoria unificada de NVIDIA y pasan a la RAM de la CPU cuando hay picos de memoria, como ocurre con secuencias largas al usar puntos de control de gradientes. Así se evita que el entrenamiento se interrumpa por errores de falta de memoria.
En la pasada hacia delante, cada capa congelada descuantiza sus pesos NF4 a bfloat16 justo antes de la multiplicación de matrices y suma la salida de la ruta LoRA. La retropropagación calcula los gradientes a través de los pesos descuantizados, pero solo actualiza las dos matrices LoRA de bajo rango. El artículo observó que, para igualar el ajuste fino completo, era necesario añadir adaptadores a todas las capas lineales del bloque Transformer:
El fragmento siguiente construye los niveles NF4 como lo hace la biblioteca bitsandbytes y los compara con 16 niveles espaciados uniformemente en bloques de pesos con distribución normal:
import torch
torch.manual_seed(0)
normal = torch.distributions.Normal(0.0, 1.0)
# NF4: 16 levels at quantiles of a normal distribution, with an exact zero (as built in bitsandbytes)
positive = normal.icdf(torch.linspace(0.9677083, 0.5, 9)[:-1])
negative = -normal.icdf(torch.linspace(0.9677083, 0.5, 8)[:-1])
nf4 = torch.cat([negative, torch.zeros(1), positive]).sort().values
nf4 = nf4 / nf4.max()
int4 = torch.linspace(-1, 1, 16) # evenly spaced 4-bit levels for comparison
weights = torch.randn(4096, 64) * 0.02 # pretrained weights are roughly normal, in blocks of 64
absmax = weights.abs().amax(dim=1, keepdim=True) # one scale per block
for name, levels in (("NF4", nf4), ("INT4", int4)):
codes = ((weights / absmax)[..., None] - levels).abs().argmin(dim=-1) # 4-bit index per weight
restored = levels[codes] * absmax
print(f"{name} mean squared error: {(restored - weights).pow(2).mean():.3e}")Imprime un error cuadrático medio de 3.395e-06 para NF4 y de 4.053e-06 para los niveles espaciados uniformemente. Con los mismos 16 niveles y las mismas escalas de bloque, la distribución por cuantiles de NF4 reduce aproximadamente un 16 % el error de reconstrucción en pesos con distribución normal. En la práctica, Hugging Face Transformers carga un modelo en NF4 mediante bitsandbytes con un BitsAndBytesConfig que configura load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True y bnb_4bit_compute_dtype=torch.bfloat16, y la biblioteca PEFT añade los adaptadores LoRA.
QLoRA y métodos relacionados#
- LoRA: Entrena el mismo tipo de adaptadores en un modelo base que se mantiene con una precisión de 16 bits. Solo los pesos de 16 bits de un modelo de 65B ocupan unos 130 GB, mientras que NF4 los almacena en aproximadamente una cuarta parte de ese espacio.
- Ajuste fino eficiente en parámetros (PEFT): Familia de métodos que entrenan un conjunto pequeño de parámetros, incluidos LoRA, QLoRA, los adaptadores y el ajuste de instrucciones.
- Cuantización del modelo posterior al entrenamiento: Comprime un modelo terminado para la inferencia. A menudo, un modelo ajustado con QLoRA se fusiona con pesos de 16 bits y luego se vuelve a cuantizar para servirlo.
- Entrenamiento consciente de la cuantización (QAT): Simula el redondeo de baja precisión en la pasada hacia delante mientras se entrenan todos los pesos, para que el modelo desplegado mantenga su precisión con baja precisión. Ultralytics YOLO26 ejecuta QAT INT8 cuando se entrena con
quantize=8; inserta cuantización simulada en sus capas convolucionales y lineales mediante NVIDIA ModelOpt para que los pesos aprendidos se conserven al exportar a INT8.
Compromisos#
QLoRA sacrifica velocidad para ahorrar memoria: des cuantizar cada peso congelado en cada pasada hacia delante y hacia atrás hace que los pasos de entrenamiento sean más lentos que con LoRA de 16 bits. Los adaptadores son lo bastante pequeños como para almacenarlos e intercambiarlos según la tarea, pero para fusionarlos con el modelo base primero hay que des cuantizarlo a 16 bits. En el modelo de 65B, los pesos de 4 bits y las escalas con doble cuantización ocupan unos 34 GB, lo que deja el resto de una GPU de 48 GB para los adaptadores, las activaciones y los estados paginados del optimizador.










