GRATÍCULAinstrumento de maestría
BancoRTX 5090 · GB202
Rev2026.06
Entrar
N2 · Post-training + RL/L2

PEFT: LoRA, QLoRA, DoRA (y Checkpoint C2a)

Objetivo de maestría

entender por qué LoRA funciona (no solo cómo usarlo), elegir hiperparámetros con criterio, y producir un experimento controlado que compare los métodos con TUS números. Cierra C2a.


2.1La intuición de LoRA: las actualizaciones son de bajo rango

Fine-tunear todo un modelo significa actualizar W → W + ΔW para cada matriz de pesos W (de dimensión, p.ej., d×d). Eso es full fine-tuning: caro en memoria (N0·L3, el "×8" del optimizador).

Observación clave (Hu et al. 2021): la actualización ΔW que necesita una tarea concreta tiene rango efectivo bajo — se puede aproximar bien con el producto de dos matrices flacas:

ΔW ≈ B · A    con   B ∈ ℝ^{d×r},  A ∈ ℝ^{r×d},  r ≪ d

r (el rank) es típicamente 8–64. En lugar de entrenar d×d parámetros, entrenas 2·d·r (mucho menos). El forward queda:

h = W·x + (B·A)·x      # W congelado; solo B y A se entrenan

Se suele inicializar A con ruido gaussiano y B a cero → al empezar ΔW=0 (el modelo arranca idéntico al base) y se escala por alpha/r.

Por qué importa para ti: congelar W significa que no necesita gradiente ni estados de optimizador — solo A y B los pagan. De ahí que LoRA entrene modelos 10× mayores que full FT en la misma VRAM (N0·L3).


2.2Los hiperparámetros que de verdad importan

  • r (rank): capacidad del adaptador. Más alto = más expresivo pero más VRAM y más riesgo de overfit. Punto de partida 2026: r=16 para la mayoría; r=32–64 para tareas complejas o datasets grandes.
  • lora_alpha: factor de escala (ΔW se multiplica por alpha/r). Convención común: alpha = r o alpha = 2r. Si cambias r, ajusta alpha proporcionalmente para mantener la escala efectiva.
  • target_modules: a qué matrices aplicar LoRA. El estándar moderno es todas las lineales: atención (q,k,v,o) + MLP (gate,up,down). Aplicarlo solo a atención es más barato pero deja rendimiento en la mesa.
  • lora_dropout: regularización; 0–0.1.

2.3QLoRA y DoRA en una frase cada uno

  • QLoRA (Dettmers et al. 2023): el base se congela en 4-bit (NF4) en lugar de 16-bit → 4× menos VRAM de pesos → entrenas ~14B en una 5090. Los adaptadores siguen en bf16. Con paged optimizers para picos. Pérdida de calidad vs LoRA bf16: pequeña, medible (la mides en C2a).
  • DoRA (Liu et al. 2024): descompone el peso en magnitud y dirección y aplica LoRA solo a la dirección. Suele ganar a LoRA puro en rank bajo (r pequeño), con coste extra mínimo.

2.4Laboratorio L2.1 — Fine-tune QLoRA base (la plantilla que reusarás)

python
1# lab_n2l2_qlora.py — SFT con QLoRA sobre Qwen3-4B usando Unsloth (rápido y eficiente en 5090)
2from unsloth import FastLanguageModel
3from datasets import load_dataset
4from trl import SFTTrainer, SFTConfig
5
6MAX_SEQ = 2048
7model, tokenizer = FastLanguageModel.from_pretrained(
8    model_name="unsloth/Qwen3-4B-Base",
9    max_seq_length=MAX_SEQ,
10    load_in_4bit=True,          # QLoRA: base en 4-bit NF4
11    dtype=None,                 # autodetecta bf16 en Blackwell
12)
13
14model = FastLanguageModel.get_peft_model(
15    model,
16    r=16, lora_alpha=16, lora_dropout=0.0,
17    target_modules=["q_proj","k_proj","v_proj","o_proj",
18                    "gate_proj","up_proj","down_proj"],   # todas las lineales
19    use_gradient_checkpointing="unsloth",                 # estira VRAM (N0·L3.4)
20    random_state=42,
21)
22
23# Dataset de instrucción genérico (Alpaca limpio). Formato ChatML vía template.
24ds = load_dataset("yahma/alpaca-cleaned", split="train[:5000]")
25def to_chat(ex):
26    msgs = [{"role":"user","content": ex["instruction"] + ("\n"+ex["input"] if ex["input"] else "")},
27            {"role":"assistant","content": ex["output"]}]
28    return {"text": tokenizer.apply_chat_template(msgs, tokenize=False)}
29ds = ds.map(to_chat)
30
31trainer = SFTTrainer(
32    model=model, tokenizer=tokenizer, train_dataset=ds,
33    args=SFTConfig(
34        per_device_train_batch_size=2,
35        gradient_accumulation_steps=4,      # batch efectivo = 8
36        warmup_steps=10, num_train_epochs=1,
37        learning_rate=2e-4,                 # LR típico de LoRA (mayor que full FT)
38        logging_steps=10, optim="paged_adamw_8bit",
39        seed=42, max_seq_length=MAX_SEQ, output_dir="./qlora_out",
40    ),
41)
42trainer.train()
43model.save_pretrained("adapters/qwen3-4b-alpaca")   # guarda SOLO el adaptador (MBs)

Líneas no triviales:

  • unsloth/Qwen3-4B-Base + load_in_4bit=True: base ya cuantizado, QLoRA real.
  • apply_chat_template: convierte mensajes al formato exacto que el modelo espera (tokens especiales de rol). Hacerlo a mano es la fuente nº1 de fine-tunes que "no aprenden".
  • learning_rate=2e-4: LoRA tolera LR mucho mayores que full FT (1e-5) porque entrena pocos parámetros.
  • save_pretrained sobre el modelo PEFT guarda solo los adaptadores → los servirás con multi-LoRA (N1·L6).

2.5Laboratorio L2.2 — El experimento controlado (entregable de C2a)

Mismo modelo, mismo dataset, misma seed; varía solo el método. Mide score, VRAM pico y wall-clock.

python
1# lab_n2l2_controlado.py — compara full / LoRA / QLoRA / DoRA en igualdad de condiciones
2import time, torch, json
3from unsloth import FastLanguageModel
4from datasets import load_dataset
5from trl import SFTTrainer, SFTConfig
6
7MAX_SEQ, MODEL, STEPS = 1024, "unsloth/Qwen3-1.7B-Base", 200  # 1.7B para que full FT quepa
8
9def run(method):
10    load_4bit = (method == "qlora")
11    model, tok = FastLanguageModel.from_pretrained(MODEL, max_seq_length=MAX_SEQ, load_in_4bit=load_4bit)
12    if method != "full":
13        model = FastLanguageModel.get_peft_model(
14            model, r=16, lora_alpha=16,
15            target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
16            use_gradient_checkpointing="unsloth",
17            use_dora=(method == "dora"),   # DoRA: solo cambia esta bandera
18            random_state=42,
19        )
20    ds = load_dataset("yahma/alpaca-cleaned", split="train[:3000]").map(
21        lambda e: {"text": tok.apply_chat_template(
22            [{"role":"user","content":e["instruction"]},{"role":"assistant","content":e["output"]}],
23            tokenize=False)})
24    torch.cuda.reset_peak_memory_stats()
25    t0 = time.perf_counter()
26    SFTTrainer(model=model, tokenizer=tok, train_dataset=ds,
27        args=SFTConfig(per_device_train_batch_size=2, gradient_accumulation_steps=4,
28                       max_steps=STEPS, learning_rate=(1e-5 if method=="full" else 2e-4),
29                       logging_steps=50, optim="adamw_torch" if method=="full" else "paged_adamw_8bit",
30                       seed=42, max_seq_length=MAX_SEQ, output_dir=f"./ctl_{method}")).train()
31    return {"method": method,
32            "wall_s": round(time.perf_counter()-t0, 1),
33            "vram_gb": round(torch.cuda.max_memory_allocated()/1e9, 1)}
34
35results = []
36for m in ("qlora", "lora", "dora", "full"):   # full el último (más VRAM)
37    try: results.append(run(m))
38    except torch.cuda.OutOfMemoryError: results.append({"method": m, "error": "OOM"})
39    torch.cuda.empty_cache()
40print(json.dumps(results, indent=2))
41# Luego evalúa cada adaptador con una métrica (siguiente bloque) y completa la tabla.

Para la columna "score": evalúa cada modelo resultante en una métrica fija (p.ej. exact-match en un set de validación de tu tarea, o perplexity en un holdout). Reusa el patrón lm_eval de N1·L4 o un script de exact-match propio. Lo importante es que sea la misma métrica para los cuatro.


2.6CHECKPOINT C2a — criterio de aprobado

  • Tabla con los 4 métodos: score · VRAM_pico · wall_clock · params_entrenables, misma seed/modelo/dataset, reproducible.
  • Sabes explicar el tradeoff con TUS números: cuánta calidad pierde QLoRA vs LoRA vs full, cuánta VRAM ahorra, cuánto más rápido. Y sabes decir cuándo elegirías cada uno.
  • Sabes por qué full FT de un 1.7B ya estresa la VRAM y un 7B es inviable (conecta con N0·L3).

Rúbrica: Nivel 3 (reproductor) cuando la tabla existe y la explicas; Nivel 4 si además variaste r/alpha/target_modules y mediste su efecto.


2.7Ejercicios

E1. Repite el experimento con r=8 y r=64 (ajustando alpha). ¿Cómo cambian score y VRAM? ¿Dónde está el punto de rendimientos decrecientes para tu tarea?

E2. Entrena LoRA solo en atención (q,k,v,o) vs en todas las lineales. ¿Cuánta calidad pierdes por el ahorro?

E3. Verifica empíricamente que DoRA gana más a LoRA en r=8 que en r=64. ¿Por qué tiene sentido?

2.8Trampas comunes

  • Formatear el chat a mano en vez de apply_chat_template.
  • Usar LR de full FT (1e-5) con LoRA → no aprende; LoRA quiere ~2e-4.
  • Comparar métodos con datasets/seeds distintos → la tabla no dice nada.

2.9Referencias

  • LoRA (Hu et al. 2021), QLoRA (Dettmers et al. 2023), DoRA (Liu et al. 2024). Docs Unsloth (Qwen3 fine-tune), HF PEFT, TRL SFTTrainer.