LoRA y QLoRA explicados: Ajuste fino de modelos grandes con presupuestos reducidos
La última vez, Rae decidió que un ligero ajuste fino sobre su pipeline RAG existente era el movimiento correcto para su bot de soporte — RAG seguiría manejando los hechos de su manual del producto, y un ajuste fino consolidaría el tono, el formato y el razonamiento específico del dominio que su equipo de soporte utilizaba. Pero está liderando una startup, no un laboratorio de investigación: tiene una sola GPU de consumo, un presupuesto pequeño y cero interés en alquilar A100s por hora. Necesita hacerle un ajuste fino a un modelo grande sin arruinarse — y ese es exactamente el muro con el que está a punto de chocar.
Rae enciende su GPU y la apunta a un modelo de 7B parámetros — lo suficientemente pequeño para seguir siendo de código abierto, lo suficientemente grande para ser útil para su bot de soporte. Ha recopilado cientos de resoluciones de tickets de soporte anteriores como datos de entrenamiento: cada uno una pregunta y la respuesta que su equipo realmente escribió. Presiona “entrenar”. En cuestión de segundos, su pantalla se llena de texto rojo: CUDA out of memory. Su GPU — la misma que ejecuta su pipeline de inferencia RAG sin problemas — ni siquiera puede comenzar un paso de ajuste fino.
Si alguna vez has intentado hacer un ajuste fino a un Modelo de Lenguaje Grande (LLM) como Llama 3 o Mistral en tu propia computadora, solo para ver cómo tu GPU arroja un “Out of Memory” antes de que termine el primer paso, conoces la sensación. Es un frustrante rito de iniciación para todo científico de datos hoy en día — y para Rae, es la brecha entre ella y la única mejora que su bot de soporte aún necesita.
Se supone que estos modelos son el futuro, sin embargo, su mero tamaño hace que parezca que solo pertenecen a las grandes tecnológicas con salas de servidores de un millón de dólares. Pero la cuestión es esta: no necesitas cambiar todo el modelo para que siga tus instrucciones.
Esta guía cubre LoRA y QLoRA. Estos son los “hacks” que nos permiten entrenar modelos masivos en hardware de consumo. Piénsalo como enseñarle nuevos trucos a un gigante sin realizar una cirugía cerebral en cada uno de sus miles de millones de neuronas.
1. El problema: por qué el fine-tuning parece imposible
Un modelo “7B” tiene 7 mil millones de parámetros. En la precisión estándar de 32 bits (float32), cada parámetro ocupa 4 bytes.
Solo para cargar el modelo en tu GPU, necesitas 28GB de VRAM. El entrenamiento es otra historia. Con el Full Fine-Tuning, no solo estás almacenando los pesos. También necesitas los gradientes (las direcciones en las que el modelo necesita moverse) y los estados del optimizador (la memoria de los pasos anteriores).
Veamos el cálculo para un modelo de 7B:
# Memory calculation for Full Fine-Tuning (7B model)
params = 7e9
bytes_per_param = 4 # float32
model_weights = params * bytes_per_param / 1e9
gradients = params * bytes_per_param / 1e9
optimizer_states = params * bytes_per_param * 2 / 1e9 # Adam optimizer stores 2 values per param
total_vram_needed = model_weights + gradients + optimizer_states
print(f"Model Weights: {model_weights} GB")
print(f"Gradients: {gradients} GB")
print(f"Optimizer States: {optimizer_states} GB")
print(f"Total VRAM required: {total_vram_needed} GB")
# Output: Total VRAM required: 112.0 GB
Este bloque calcula la VRAM total necesaria para hacer un full fine-tune de un modelo de 7B, desglosando los tres componentes que consumen la memoria de la GPU. params = 7e9 establece la cantidad de parámetros en 7 mil millones (7 × 10⁹). bytes_per_param = 4 especifica que float32 (punto flotante de 32 bits) usa 4 bytes por parámetro — la precisión estándar para el entrenamiento. model_weights = params * bytes_per_param / 1e9 calcula los pesos del modelo en GB: 7e9 × 4 / 1e9 = 28 GB — este es el costo de solo cargar el modelo. gradients = params * bytes_per_param / 1e9 calcula los gradientes en GB (otros 28 GB) — durante la retropropagación, cada parámetro necesita un gradiente del mismo tamaño, por lo que esto duplica la memoria. optimizer_states = params * bytes_per_param * 2 / 1e9 calcula los estados del optimizador en GB (56 GB) — el * 2 se debe a que el optimizador Adam almacena dos valores por parámetro: el primer momento (promedio móvil exponencial de los gradientes) y el segundo momento (promedio móvil exponencial de los gradientes al cuadrado). total_vram_needed = model_weights + gradients + optimizer_states suma los tres: 28 + 28 + 56 = 112 GB. El comentario # Adam optimizer stores 2 values per param explica el multiplicador * 2 — si usaras SGD en lugar de Adam, solo necesitarías los pesos y los gradientes (56 GB), pero el seguimiento del momentum de Adam añade otros 56 GB. Para Rae, por esto su GPU de consumo —que maneja la inferencia RAG sin problemas con 28 GB para los pesos— se satura en el momento en que intenta entrenar en ella: el entrenamiento necesita 4× la memoria de la inferencia, no 2× o 1×.
Lo que esto significa en realidad: Una RTX 4090, la GPU de gama alta de consumo estándar, tiene 24GB de VRAM. Una A100 empresarial tiene 80GB. Ninguna puede manejar los 112GB que requiere un full fine-tune de un modelo 7B relativamente “pequeño”. Ese es el verdadero cuello de botella: el costo de memoria del entrenamiento, no solo del modelo en sí.
2. La intuición: ¿qué pasaría si solo cambiáramos una pequeña parte?
El ajuste fino completo reescribe un libro de 1,000 páginas solo para cambiar el final. Lento, costoso, desproporcionado.
LoRA (Low-Rank Adaptation) toma un enfoque diferente: notas adhesivas en los márgenes. El modelo base permanece congelado. Solo escribimos en los adaptadores. Durante la inferencia, leemos el texto original junto con las notas para obtener el resultado final.
Técnicamente, añadimos pequeñas matrices adaptadoras junto a las matrices de pesos originales. Estos adaptadores son minúsculos—a menudo por debajo del 1% del tamaño total—, por lo que solo necesitamos gradientes y estados del optimizador para ellos, y no para los miles de millones de parámetros del modelo base.
3. Cómo funciona LoRA: descomposición de bajo rango
Esta es la parte más difícil de visualizar. La actualización de pesos es normalmente una matriz grande. LoRA apuesta a que los cambios no son tan complejos.
En lugar de aprender una matriz grande , aprendemos dos delgadas, y . Al multiplicarlas () se obtiene una matriz del mismo tamaño que — pero con un rango mucho menor, lo que significa menor complejidad.
En el fine-tuning completo, la actualización de pesos se aplica directamente a la matriz de pesos completa:
donde es una matriz de rango completo con las mismas dimensiones que . LoRA reemplaza esto con una aproximación de bajo rango:
donde , , y . El forward pass se convierte en:
Los pesos originales se congelan (no se calculan gradientes) y solo se entrenan y . Los parámetros entrenables totales se reducen de a .
| Lenguaje sencillo | Símbolo estadístico | Equivalente en Python |
|---|---|---|
| Matriz de pesos original congelada | model.weight (no actualizada) | |
| Actualización completa de pesos (fine-tuning completo) | grad (mismo shape que ) | |
| Matriz adaptadora de bajo rango A | lora_A.weight | |
| Matriz adaptadora de bajo rango B | lora_B.weight | |
| Rango (dimensión del bottleneck) | r or rank in LoraConfig | |
| Dimensión de entrada | in_dim | |
| Dimensión de salida | out_dim | |
| Forward pass con adaptador | h = base_layer(x) + lora_B(lora_A(x)) |
La idea clave es que (el rango) es el bottleneck: controla el balance entre expresividad (cuántos cambios diferentes puede representar el adaptador) y memoria (cuántos parámetros necesitan gradientes). Con y , la matriz completa necesita parámetros, pero necesita solo — una reducción de 256×.
Esto es lo que eso hace con el conteo de parámetros:
import torch
import torch.nn as nn
# Imagine a layer with 4096 inputs and 4096 outputs
in_dim, out_dim = 4096, 4096
rank = 8 # This is our 'r' hyperparameter
# Full Fine-Tuning parameters
full_params = in_dim * out_dim
# LoRA parameters (Matrix A and Matrix B)
matrix_a = in_dim * rank
matrix_b = rank * out_dim
lora_params = matrix_a + matrix_b
reduction = full_params / lora_params
print(f"Full parameters: {full_params:,}")
print(f"LoRA parameters: {lora_params:,}")
print(f"Reduction factor: {reduction:.1f}x")
# Output: Reduction factor: 256.0x
Este bloque calcula el conteo de parámetros para el fine-tuning completo frente a LoRA en una sola capa de 4096×4096, demostrando la reducción dramática. import torch y import torch.nn as nn importan PyTorch y su módulo de redes neuronales — aunque ninguno se usa realmente en este cálculo, se importan porque este fragmento normalmente sería parte de un script de entrenamiento más grande. in_dim, out_dim = 4096, 4096 establece las dimensiones de entrada y salida de una capa típica de un transformer (esto coincide con el tamaño oculto de modelos como Llama-7B). rank = 8 establece el rango de LoRA — el hiperparámetro r que controla el bottleneck. full_params = in_dim * out_dim calcula el número de parámetros para el fine-tuning completo: 4096 × 4096 = 16,777,216 (aproximadamente 16.8M). matrix_a = in_dim * rank calcula los parámetros en la matriz : 4096 × 8 = 32,768. matrix_b = rank * out_dim calcula los parámetros en la matriz : 8 × 4096 = 32,768. lora_params = matrix_a + matrix_b suma ambos: 32,768 + 32,768 = 65,536 parámetros entrenables totales. reduction = full_params / lora_params calcula la razón: 16,777,216 / 65,536 = 256.0. print(f"Full parameters: {full_params:,}") usa el especificador de formato :, para añadir separadores de miles para mayor legibilidad. print(f"Reduction factor: {reduction:.1f}x") formatea la reducción a un decimal con un sufijo literal “x”. La salida muestra una reducción de 256× — lo que significa que calculamos y almacenamos gradientes para 65K parámetros en lugar de 16.8M, que es lo que transforma “imposible en hardware de consumo” a “trivial.” Para Rae, esta es la razón por la que su GPU deja de gritar: los gradientes y los estados del optimizador ahora son 256× más pequeños, cabiendo cómodamente en su VRAM.
Con un rango de 8, estamos entrenando 256 veces menos parámetros para esa capa. El ahorro de memoria se da porque solo almacenamos gradientes para las matrices pequeñas.
4. LoRA en la práctica: adaptadores, rangos y objetivos
En la práctica, utilizamos la biblioteca peft (Parameter-Efficient Fine-Tuning) de HuggingFace. No tienes que escribir las matemáticas de matrices tú mismo. Solo tienes que definir una configuración.
Aplicar LoRA a un modelo se ve así:
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
# 1. Load a base model (frozen)
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")
# 2. Define LoRA Config
config = LoraConfig(
r=16, # The rank. Higher = more capacity, more memory
lora_alpha=32, # Scaling factor
target_modules=["q_proj", "v_proj"], # Which layers to 'stick' notes on
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# 3. Wrap the model
lora_model = get_peft_model(model, config)
lora_model.print_trainable_parameters()
# Output: trainable params: 786,432 || all params: 331,982,848 || trainable%: 0.236
Este bloque carga un modelo pre-entrenado y lo envuelve con adaptadores LoRA usando la biblioteca peft de HuggingFace. from peft import LoraConfig, get_peft_model importa la clase de configuración y la función envolvente (wrapper) de la biblioteca peft (Parameter-Efficient Fine-Tuning). from transformers import AutoModelForCausalLM importa la auto-clase para cargar modelos de lenguaje causales (modelos que predicen el siguiente token). model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m") carga el modelo OPT-350M de Facebook — un modelo relativamente pequeño de 350M de parámetros usado aquí para demostración (en la práctica, Rae usaría un modelo más grande como Llama-7B o Mistral-7B). config = LoraConfig(...) crea un objeto de configuración LoRA con seis parámetros. r=16 establece el rango en 16 — un rango más alto significa adaptadores más expresivos pero más memoria; el comentario explica este compromiso. lora_alpha=32 es un factor de escala que controla qué tan fuertemente el adaptador influye en la salida — el escalamiento efectivo es alpha / r, por lo que aquí 32/16 = 2.0. target_modules=["q_proj", "v_proj"] especifica a qué matrices de peso en el transformer se deben conectar los adaptadores — q_proj (proyección de consulta) y v_proj (proyección de valor) son las capas de atención, los objetivos estándar para LoRA; también podrías agregar k_proj, o_proj, o las capas MLP. lora_dropout=0.05 aplica un 5% de dropout a las salidas del adaptador para prevenir el sobreajuste. bias="none" significa que no se entrenan parámetros de sesgo — manteniendo el adaptador aún más pequeño. task_type="CAUSAL_LM" le indica a la biblioteca que esta es una tarea de modelado de lenguaje causal (predicción del siguiente token). lora_model = get_peft_model(model, config) envuelve el modelo base con la configuración de LoRA — esto congela todos los pesos originales e inyecta las matrices de adaptadores entrenables en los módulos objetivo. lora_model.print_trainable_parameters() imprime un resumen que muestra cuántos parámetros son entrenables frente a los congelados. La salida trainable params: 786,432 || all params: 331,982,848 || trainable%: 0.236 significa que solo 786K de 332M de parámetros están siendo entrenados — menos de un cuarto de por ciento. Para Rae, esto significa que su GPU solo necesita almacenar gradientes y estados del optimizador para ~786K parámetros, no 332M — que es lo que hace que el ajuste fino sea factible en su hardware.
Interpretación: Solo estamos entrenando el 0.23% del modelo. El resto permanece congelado, lo que mantiene bajo el uso de VRAM.
5. El ahorro de memoria: cifras que de verdad puedes creer
Entonces, ¿cómo se sostienen los números? Fine-tuning de un modelo Llama-7B:
- Fine-Tuning completo: ~112GB VRAM (requiere 2x A100 GPUs).
- LoRA (Rango 8): ~16GB - 20GB VRAM (cabe en una sola RTX 3090 o 4090).
Esa es la diferencia entre lo imposible y lo factible en casa.
6. QLoRA: LoRA + cuantización = aún más pequeño
Si LoRA entrena menos parámetros, QLoRA reduce los que ya tenemos.
Utiliza cuantización de 4 bits. Los números normalmente viven en 16 o 32 bits. QLoRA comprime los pesos del modelo base a solo 4 bits, reduciendo la huella de memoria de 4x a 8x.
El problema es: los pesos de 4 bits son de “baja calidad”. QLoRA soluciona esto: los descuantiza just-in-time para cada cálculo y luego usa los adaptadores de LoRA para “corregir” los errores de la baja precisión.
from transformers import BitsAndBytesConfig
# Configure 4-bit loading
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
# Load model in 4-bit
model_4bit = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-v0.1",
quantization_config=quant_config,
device_map="auto"
)
Este bloque configura la cuantización de 4 bits para cargar un modelo a una fracción de su costo normal de memoria, la técnica clave que hace que QLoRA funcione en GPUs de consumo. from transformers import BitsAndBytesConfig importa la clase de configuración de cuantización de HuggingFace Transformers. quant_config = BitsAndBytesConfig(...) crea un objeto de configuración con cuatro parámetros que controlan cómo se comprimen los pesos del modelo. load_in_4bit=True le indica al cargador que cuantice todos los pesos a 4 bits, reduciendo cada parámetro de 16 bits (2 bytes) o 32 bits (4 bytes) a solo 0.5 bytes, una reducción de 4× a 8×. bnb_4bit_quant_type="nf4" especifica el tipo de cuantización como NormalFloat 4 (NF4), un esquema de cuantización diseñado por los autores de QLoRA específicamente para valores de pesos con distribución normal; es mejor que la cuantización uniforme porque asigna más precisión a los valores de peso más comunes. bnb_4bit_compute_dtype=torch.float16 especifica que durante el forward pass, los pesos se descuantizan de nuevo a float16 (16 bits) para el cálculo; esta es la “descuantización just-in-time” que describe el artículo. bnb_4bit_use_double_quant=True habilita la doble cuantización: cuantizar las propias constantes de cuantización, ahorrando ~0.4 bits adicionales por parámetro. model_4bit = AutoModelForCausalLM.from_pretrained(...) carga Mistral-7B con la configuración de cuantización aplicada. quantization_config=quant_config pasa la configuración de 4 bits. device_map="auto" permite a HuggingFace colocar automáticamente las capas del modelo a través de las GPUs disponibles, lo cual es esencial cuando un modelo es demasiado grande para un solo dispositivo pero se puede dividir. Para Rae, esto es lo que permite que su modelo de 7B (que necesita 28 GB en float32) quepa en aproximadamente 5 GB de VRAM, dejando mucho espacio para los adaptadores de LoRA y sus gradientes en su única GPU de consumo.
Con QLoRA, un modelo de 7B necesita solo ~5GB de VRAM para sus pesos. Puedes hacer fine-tuning de un modelo de 7B en una GPU económica de 12GB.
7. Integrándolo todo: un pipeline completo
Aquí tienes un flujo de trabajo sencillo para hacer fine-tuning de tu propio modelo:
# 1. Load model in 4-bit (QLoRA)
# 2. Add LoRA adapters
# 3. Load your dataset (e.g., instructions)
# 4. Use the HuggingFace Trainer
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model=lora_model,
train_dataset=my_data,
dataset_text_field="text",
max_seq_length=512,
args=TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
learning_rate=2e-4,
logging_steps=10,
output_dir="./outputs"
),
)
trainer.train()
Este bloque construye un pipeline de entrenamiento completo utilizando el SFTTrainer (Supervised Fine-Tuning Trainer) de la librería trl — la pieza final que une la carga de QLoRA, los adaptadores LoRA y los datos de entrenamiento. from trl import SFTTrainer importa el entrenador de ajuste fino supervisado, que maneja el bucle de entrenamiento para los datasets de ajuste de instrucciones. from transformers import TrainingArguments importa la clase de configuración para los argumentos de entrenamiento de HuggingFace. trainer = SFTTrainer(...) crea el entrenador con seis argumentos. model=lora_model pasa el modelo envuelto en LoRA (el modelo base cuantizado en 4 bits con adaptadores LoRA de los bloques de código anteriores) — esto es lo que lo hace QLoRA: el modelo está cuantizado y tiene adaptadores entrenables. train_dataset=my_data pasa el dataset de entrenamiento — para Rae, estos serían sus cientos de pares (pregunta, respuesta) de tickets de soporte anteriores. dataset_text_field="text" le indica al entrenador qué campo del dataset contiene el texto con el que se va a entrenar. max_seq_length=512 limita la longitud de la secuencia a 512 tokens — las secuencias más cortas consumen menos VRAM, lo cual es importante en las GPU de consumo; los pares de Q&A del bot de soporte de Rae son lo suficientemente cortos para caber cómodamente. args=TrainingArguments(...) crea una subconfiguración para los hiperparámetros de entrenamiento. per_device_train_batch_size=1 establece el tamaño del lote (batch size) en 1 — esta es la configuración más conservadora de memoria, esencial para GPUs pequeñas. gradient_accumulation_steps=4 acumula los gradientes durante 4 pasos antes de actualizar los pesos — esto simula un tamaño de lote de 4 (1 × 4) sin necesitar la memoria para 4 ejemplos simultáneos. learning_rate=2e-4 establece una tasa de aprendizaje de 0.0002 — ten en cuenta que esto es mayor que el 2e-5 utilizado en el fine-tuning completo (cubierto en el ejemplo conceptual del artículo anterior), porque los adaptadores LoRA necesitan un impulso más fuerte para aprender de manera efectiva con menos parámetros. logging_steps=10 registra las métricas de entrenamiento cada 10 pasos. output_dir="./outputs" especifica dónde guardar los checkpoints. trainer.train() inicia el bucle de entrenamiento. Para Rae, este es el pipeline exacto que ejecuta en su única GPU: cargar Mistral-7B en 4 bits, adjuntar los adaptadores LoRA, introducir sus pares de Q&A de tickets de soporte y entrenar — todo el proceso cabe en menos de 8GB de VRAM.
8. ¿Cuándo usar cuál?
- Fine-Tuning completo: Opta por esto solo con un dataset masivo (millones de filas) y un clúster de H100s. La mayoría de usuarios nunca lo necesitará.
- LoRA: La opción por defecto. Si tienes 24GB+ de VRAM, ve con esta. Entrena más rápido que QLoRA y resulta ligeramente más precisa.
- QLoRA: La opción económica. ¿Ejecutando modelos grandes en 8GB o 12GB de VRAM? Este es tu camino — realmente no hay otra opción.
Fine-Tuning completo vs. LoRA vs. QLoRA: ¿Cuál debería elegir Rae?
El startup de Rae tiene restricciones específicas que reducen el campo a una sola opción: una GPU de consumo única (digamos 8–12GB VRAM), un presupuesto reducido (sin alquiler de GPUs en la nube) y algunos cientos de ejemplos de entrenamiento de alta calidad (sus resoluciones de tickets de soporte). Así es como se comparan los tres enfoques:
| Factor | Full Fine-Tuning | LoRA | QLoRA |
|---|---|---|---|
| VRAM para modelo 7B | ~112 GB (pesos + gradientes + optimizador) | ~16–20 GB (pesos en fp16 + gradientes pequeños del adaptador) | ~5–8 GB (pesos de 4 bits + gradientes pequeños del adaptador) |
| Parámetros entrenables | 100% — todos los 7B | ~0.2–1% — solo los adaptadores | ~0.2–1% — solo los adaptadores (modelo base congelado a 4 bits) |
| Techo de calidad | Más alto — expresividad completa de la actualización de pesos | Alto — muy cercano al completo, leve restricción de rango | Alto — leve ruido de cuantización, pero los adaptadores LoRA compensan |
| Velocidad de entrenamiento | Más lento — cómputo masivo de gradientes | Rápido — gradientes pequeños, backprop rápido | Ligeramente más lento que LoRA — sobrecarga de descuantización por pasada forward |
| Hardware necesario | Múltiples A100s o H100s (clúster empresarial) | Una sola RTX 3090/4090 (24 GB VRAM) | Una sola RTX 3060/4060 Ti (8–12 GB VRAM) |
| Inferencia tras merge | Nativa — sin sobrecarga de adaptador | Nativa — fusionar adaptadores de vuelta en los pesos | Nativa — fusionar adaptadores de vuelta en los pesos |
| Ideal para | Millones de ejemplos, cómputo ilimitado, calidad máxima | La mayoría de casos de uso con una GPU de consumo decente | Startups con presupuesto limitado, una sola GPU de consumo |
La decisión para el startup de Rae:
Rae tiene una GPU de consumo única con 8–12 GB de VRAM y algunos cientos de ejemplos de entrenamiento. QLoRA es su única opción realista. Le permite hacer fine-tuning de un modelo 7B (que necesita para calidad) en hardware que ya posee, sin alquilar GPUs en la nube por hora. El modelo base de 4 bits ocupa ~5 GB, y los gradientes del adaptador LoRA añaden solo unos cientos de MB — todo el pipeline cabe en su VRAM con margen de sobra.
Cuándo NO usar QLoRA (y qué usar en su lugar):
- Si Rae tuviera 24 GB+ de VRAM (p. ej., una RTX 4090): LoRA sin cuantización sería preferible — es ligeramente más rápida (sin sobrecarga de descuantización) y evita cualquier ruido de cuantización en el modelo base. La diferencia de calidad es pequeña pero medible en benchmarks.
- Si Rae tuviera millones de ejemplos y un clúster de H100s: El fine-tuning completo podría exprimir calidad adicional — la actualización de pesos de rango completo puede representar cambios que un adaptador rank-8 o rank-16 no puede. Pero esto casi nunca es el caso para startups, y la ganancia marginal de calidad rara vez justifica el costo de 10×+.
- Si Rae necesitara calidad máxima y tuviera 24 GB de VRAM pero no millones de ejemplos: LoRA con un rango mayor (p. ej.,
r=64or=128) le daría más capacidad de adaptador sin necesitar fine-tuning completo — el rango controla qué tan expresivo puede ser el adaptador, y aumentarlo es más económico que pasar a fine-tuning completo.
La compensación clave a entender: QLoRA añade ruido de cuantización al modelo base — los pesos de 4 bits son ligeramente menos precisos que sus originales de 16 bits. En la práctica, los adaptadores LoRA compensan este ruido durante el entrenamiento, y la brecha de calidad entre QLoRA y el fine-tuning completo es lo suficientemente pequeña como para que rara vez importe en casos de uso de producción como bots de soporte. La compensación es: aceptar una pequeña pérdida de calidad a cambio de funcionar en hardware que ya posees.
9. Errores comunes
- Rango demasiado bajo: Establece y es probable que el modelo no aprenda lo suficiente. o suelen ser el punto ideal.
- Tasa de aprendizaje: LoRA requiere una tasa de aprendizaje mayor que el ajuste fino completo. Solo estás ajustando un puñado de parámetros, así que cada pequeño ajuste debe contar — prueba con
2e-4. - Olvidar guardar: Guardar un modelo LoRA solo guarda las “notas adhesivas”. Para usarlo posteriormente, carga el modelo base y los adaptadores juntos.
10. ¿Qué sigue?
Una vez que tus adaptadores estén entrenados, puedes fusionarlos. Esto combina las matrices de nuevo en los pesos principales, por lo que el modelo vuelve a ser un solo archivo — sin penalización de velocidad durante la inferencia.
# Merging the adapters into the base model
merged_model = lora_model.merge_and_unload()
merged_model.save_pretrained("my-final-model")
Este bloque fusiona los adaptadores LoRA entrenados de nuevo en los pesos del modelo base, produciendo un único modelo autónomo sin sobrecarga de inferencia. merged_model = lora_model.merge_and_unload() realiza la fusión: multiplica las matrices y entrenadas (), suma el resultado a los pesos base congelados () y luego elimina las capas del adaptador del modelo — el método merge_and_unload() hace ambas cosas en una sola llamada. Después de la fusión, el modelo se comporta exactamente como un modelo ajustado estándar: la aproximación de bajo rango está integrada en los pesos y no hay ningún adaptador separado que cargar en el momento de la inferencia. merged_model.save_pretrained("my-final-model") guarda el modelo fusionado en el disco como un punto de control estándar de HuggingFace — un único directorio que contiene los pesos y la configuración del modelo, que se puede cargar con AutoModelForCausalLM.from_pretrained("my-final-model") sin ninguna configuración específica de LoRA. Para Rae, este es el paso final: ella entrena sus adaptadores QLoRA con sus datos de tickets de soporte, los fusiona con el modelo base, guarda el resultado y despliega el modelo fusionado junto con su pipeline RAG — en el momento de la inferencia, no hay sobrecarga del adaptador, solo un modelo que resulta hablar con la voz del equipo de soporte de su empresa.
Cerrando el ciclo
Rae ejecuta el pipeline de QLoRA en su única GPU de consumo — la misma tarjeta que arrojó “CUDA out of memory” al principio de este artículo. Con la cuantización de 4 bits y los adaptadores de LoRA, los pesos del modelo 7B ocupan solo ~5GB de VRAM. Los gradientes del adaptador añaden apenas unos pocos cientos de megabytes más. Entrena con sus cientos de resoluciones de tickets de soporte pasados — las preguntas que sus clientes hicieron, las respuestas que su equipo realmente escribió. El adaptador capta el tono de su empresa: cómo se expresa su equipo de soporte, el formato que utilizan, la empatía que muestran. No aprende el contenido del manual de su producto. Para eso sirve RAG, y RAG todavía se está ejecutando justo a su lado.
Luego, lo despliega. El modelo fine-tuned se sitúa sobre su pipeline de RAG existente: RAG recupera el pasaje correcto del manual de su producto, y el modelo fine-tuned responde con la voz de su equipo. Las alucinaciones estilo reembolso de burrito — respuestas seguras a preguntas que el bot no tenía por qué responder — finalmente se detienen. No porque el modelo se haya vuelto más inteligente, sino porque ahora tiene los hechos correctos de RAG y el tono correcto del fine-tune, trabajando juntos.
Rae ha recorrido un largo camino para llegar hasta aquí. No sabía qué era un embedding — solo sabía que su búsqueda por palabras clave no podía encontrar respuestas semánticamente similares en su manual de producto. Aprendió sobre embeddings, levantó una base de datos vectorial, ensambló su primer pipeline de RAG sobre el manual del producto, descubrió que los LLM olvidan la parte media de los contextos largos, debatió si usar una ventana de contexto más grande o apegarse a RAG, superó la crisis de alucinación del reembolso de burrito, construyó un framework de evaluación sistemático, iteró sobre patrones de prompt engineering, delineó sus tres palancas para guiar el modelo, decidió entre fine-tuning y RAG, y finalmente — en una sola GPU de consumo con un presupuesto de startup — hizo fine-tuning de un pequeño adaptador que hizo que su bot de soporte sonara como su equipo.
Su bot de soporte está en producción. RAG maneja los hechos, la evaluación mantiene la calidad bajo control, y el prompt engineering establece la estructura. Un fine-tune económico con QLoRA logra el tono correcto. No se necesita una sala de servidores de un millón de dólares. Requirió entender las herramientas, saber cuándo usar cada una, y una única GPU que seguía arrojando “out of memory” hasta que Rae encontró la técnica adecuada.
Comprueba tu comprensión
Las preguntas a continuación avanzan desde el simple recuerdo hasta el diseño abierto, siguiendo aproximadamente la Taxonomía de Bloom.
Recordar ¿Qué añade QLoRA además de LoRA, y qué ahorros de memoria específicos persigue?
Comprender Con tus propias palabras, explica por qué el ajuste fino completo necesita 112GB de VRAM para un modelo de 7B, a pesar de que los pesos por sí solos son solo 28GB: ¿cuáles son los otros dos componentes que consumen los 84GB restantes?
Aplicar
Usando la fórmula de conteo de parámetros de LoRA del artículo (in_dim * rank + rank * out_dim), calcula el número de parámetros entrenables para una capa con in_dim = 2048, out_dim = 2048 y rank = 16, y compáralo con el conteo de parámetros 2048 * 2048 del ajuste fino completo.
Analizar El artículo dice que QLoRA “descuantiza [pesos de 4 bits] justo a tiempo para el cálculo y luego usa adaptadores LoRA para corregir los errores introducidos por la baja precisión”. Explica paso a paso por qué los adaptadores LoRA — que originalmente se diseñaron solo para reducir el número de parámetros entrenables — resultan ser también un buen mecanismo para corregir el error de cuantización, en lugar de necesitar una solución independiente para ese problema.
Evaluar
El obstáculo #2 del artículo dice que LoRA necesita una tasa de aprendizaje mayor que el ajuste fino completo “dado que solo estás cambiando unos pocos parámetros, necesitas empujarlos con más fuerza”. Critica este razonamiento: ¿son “menos parámetros” por sí solos una buena justificación para una tasa de aprendizaje más alta, o hay una explicación más precisa ligada a cómo se inicializan y escalan las matrices A y B de LoRA (mediante lora_alpha)?
Crear Diseña una decisión de hardware y técnica para un nuevo equipo: una startup tiene una RTX 4090 (24GB VRAM) y quiere hacer un ajuste fino de un modelo de 13B parámetros. Usando la guía “Cuándo usar qué” y las cifras de VRAM del artículo, ¿recomendarías LoRA o QLoRA, y qué les dirías que esperarían si probaran la otra opción primero?
Artículos relacionados
- Fine-Tuning vs. RAG: Cómo decidir realmente
- ¿Qué son los embeddings (y qué puedes hacer realmente con ellos)?
Referencias y lecturas adicionales
- Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2021). LoRA: Low-Rank Adaptation of Large Language Models. arXiv:2106.09685. arxiv.org/abs/2106.09685 — el artículo fundamental que introdujo la adaptación de bajo rango como una alternativa eficiente en parámetros al ajuste fino completo.
- Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023). QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314. arxiv.org/abs/2305.14314 — el artículo que combinó la cuantización de 4 bits con LoRA para permitir el ajuste fino de modelos grandes en GPUs de consumo.
- Hugging Face. Documentación de PEFT. huggingface.co/docs/peft — la documentación oficial de la biblioteca
peftutilizada en los ejemplos de código de este artículo. - Hugging Face. Documentación de Transformers. huggingface.co/docs/transformers — la biblioteca que proporciona
BitsAndBytesConfig,AutoModelForCausalLMyTrainingArgumentsutilizados a lo largo del artículo.
Esta traducción fue generada automáticamente y puede contener errores. Si el idioma inglés es tu preferencia, puedes leer el artículo original en inglés .
«Aplica lo que aprendiste» es para suscriptores Supporter e Insider.
Suscríbete para desbloquear los ejercicios de este artículo.
Ver planesArtículos relacionados
- LLMs y GenAI En revisión
Comparativa de bases de datos vectoriales: cuándo realmente necesitas una
Aprende cuándo realmente necesitas una base de datos vectorial dedicada frente a pgvector o FAISS, con un marco de decisión práctico basado en la escala, la latencia y la complejidad.
- LLMs y GenAI En revisión
Por qué los LLM olvidan el medio: Entendiendo las ventanas de contexto y el fenómeno Lost in the Middle
Descubre por qué los LLM ignoran la información del medio en prompts largos, cómo el fenómeno Lost in the Middle afecta a RAG y cómo el reordenamiento y el reranking lo solucionan.
- LLMs y GenAI En revisión
Por qué los LLM inventan cosas con confianza: comprender y detectar las alucinaciones
Aprende por qué los LLM alucinan mediante la predicción del siguiente token y usa log-probs y RAG para detectar y prevenir la fabricación con confianza en tus aplicaciones de IA.
- LLMs y GenAI En revisión
RAG vs. Modelos de Contexto Largo: ¿Cuál gana en 2026?
¿Deberías usar RAG o LLMs de contexto largo en 2026? Compara el costo, la precisión y los compromisos del fenómeno 'Lost-in-the-Middle' para descubrir por qué un enfoque de recuperación híbrido resulta ganador.
¿Buscas otra cosa?
Busca en todos los artículos por título, resumen o tema.