# Ajustar fino LLM

Entrena tu propio LLM personalizado usando técnicas de ajuste fino eficientes en GPUs de CLORE.AI.

{% hint style="success" %}
Todos los ejemplos se pueden ejecutar en servidores GPU alquilados a través de [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

## Alquiler en CLORE.AI

1. Visita [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Filtrar por tipo de GPU, VRAM y precio
3. Elige **Bajo demanda** (tarifa fija) o **Spot** (precio por puja)
4. Configura tu pedido:
   * Selecciona imagen Docker
   * Establece puertos (TCP para SSH, HTTP para interfaces web)
   * Agrega variables de entorno si es necesario
   * Introduce el comando de inicio
5. Selecciona pago: **CLORE**, **BTC**, o **USDT/USDC**
6. Crea el pedido y espera el despliegue

### Accede a tu servidor

* Encuentra los detalles de conexión en **Mis Pedidos**
* Interfaces web: usa la URL del puerto HTTP
* SSH: `ssh -p <port> root@<proxy-address>`

## ¿Qué es LoRA/QLoRA?

* **LoRA** (Adaptación de baja dimensión) - Entrena pequeñas capas adaptadoras en lugar del modelo completo
* **QLoRA** - LoRA con cuantización para usar aún menos VRAM
* Entrenar modelo 7B en una sola RTX 3090
* Entrenar modelo 70B en una sola A100

## Requisitos

| Modelo | Método        | VRAM mínima | Recomendado |
| ------ | ------------- | ----------- | ----------- |
| 7B     | QLoRA         | 12GB        | RTX 3090    |
| 13B    | QLoRA         | 20GB        | RTX 4090    |
| 70B    | QLoRA         | 48GB        | A100 80GB   |
| 7B     | LoRA completo | 24GB        | RTX 4090    |

## Despliegue rápido

**Imagen Docker:**

```
pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel
```

**Puertos:**

```
22/tcp
8888/http
6006/http
```

**Comando:**

```bash
pip install "transformers>=4.45" "datasets>=2.20" accelerate "peft>=0.14" \
    bitsandbytes "trl>=0.12" wandb jupyterlab && \
jupyter lab --ip=0.0.0.0 --port=8888 --allow-root
```

## Accediendo a tu servicio

Después del despliegue, encuentra tu `http_pub` URL en **Mis Pedidos**:

1. Ir a **Mis Pedidos** página
2. Haz clic en tu pedido
3. Encuentra la `http_pub` URL (por ejemplo, `abc123.clorecloud.net`)

Usa `https://TU_HTTP_PUB_URL` en lugar de `localhost` en los ejemplos a continuación.

## Preparación del conjunto de datos

### Formato de chat (recomendado)

```json
[
  {
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is Python?"},
      {"role": "assistant", "content": "Python is a programming language..."}
    ]
  }
]
```

### Formato de instrucción

```json
[
  {
    "instruction": "Translate to French",
    "input": "Hello, how are you?",
    "output": "Bonjour, comment allez-vous?"
  }
]
```

### Formato Alpaca

```json
[
  {
    "instruction": "Give three tips for staying healthy.",
    "input": "",
    "output": "1. Eat balanced meals..."
  }
]
```

## Modelos modernos compatibles (2025)

| Modelo                      | ID en HF                                  | VRAM mínima (QLoRA) |
| --------------------------- | ----------------------------------------- | ------------------- |
| Llama 3.1 / 3.3 8B          | `meta-llama/Llama-3.1-8B-Instruct`        | 12GB                |
| Qwen 2.5 7B / 14B           | `Qwen/Qwen2.5-7B-Instruct`                | 12GB / 20GB         |
| DeepSeek-R1-Distill (7B/8B) | `deepseek-ai/DeepSeek-R1-Distill-Qwen-7B` | 12GB                |
| Mistral 7B v0.3             | `mistralai/Mistral-7B-Instruct-v0.3`      | 12GB                |
| Gemma 2 9B                  | `google/gemma-2-9b-it`                    | 14GB                |
| Phi-4 14B                   | `microsoft/phi-4`                         | 20GB                |

## Script de fine-tuning QLoRA

Ejemplo moderno con PEFT 0.14+, Flash Attention 2, soporte DoRA y compatibilidad con Qwen2.5 / DeepSeek-R1:

```python
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
from trl import SFTTrainer, SFTConfig

# === Configuración ===
# Elige uno de: Qwen2.5, DeepSeek-R1-Distill, Llama 3.1, Mistral, etc.
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
# MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"

DATASET = "your_dataset.json"  # o nombre de dataset en HuggingFace
OUTPUT_DIR = "./output"
MAX_SEQ_LENGTH = 4096           # Qwen2.5 soporta hasta 32K de contexto
USE_DORA = True                 # DoRA mejora la calidad respecto a LoRA estándar
USE_FLASH_ATTN = True           # Flash Attention 2 ahorra VRAM y acelera

# === Cargar modelo con cuantización de 4 bits ===
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,  # Requerido para Qwen2.5 y DeepSeek
    # Flash Attention 2: requiere GPU Ampere+ (RTX 30/40, A100)
    attn_implementation="flash_attention_2" if USE_FLASH_ATTN else "eager",
)

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

# === Configurar LoRA con DoRA opcional ===
# DoRA (Adaptación de baja dimensión descomponiendo pesos) — PEFT >= 0.14 requerido
# use_dora=True descompone pesos en magnitud + dirección para mejor calidad
lora_config = LoraConfig(
    r=64,                    # Rango (mayor = más capacidad, más VRAM)
    lora_alpha=16,           # Factor de escala (mantener igual o la mitad de r)
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",  # Capas de atención
        "gate_proj", "up_proj", "down_proj",      # Capas MLP
    ],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    use_dora=USE_DORA,        # DoRA: calidad mejorada (PEFT 0.14+)
    # use_rslora=True,        # Opcional: Rank-Stabilized LoRA
)

# Preparar modelo para entrenamiento QLoRA
model = prepare_model_for_kbit_training(
    model,
    use_gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
)
model = get_peft_model(model, lora_config)

# Imprimir resumen de parámetros entrenables
model.print_trainable_parameters()
# Ejemplo de salida: params entrenables: 42,991,616 || todos params: 7,284,891,648 || % entrenable: 0.59

# === Cargar conjunto de datos ===
dataset = load_dataset("json", data_files=DATASET)
# O usar un dataset público:
# dataset = load_dataset("HuggingFaceH4/ultrachat_200k")

# === Formatear dataset para Qwen2.5 / formato ChatML ===
def format_chat_qwen(example):
    """Formato para Qwen2.5 usando la plantilla ChatML."""
    messages = example.get("messages", [])
    if not messages:
        # Manejar datos estilo alpaca
        text = f"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
        text += f"<|im_start|>user\n{example['instruction']}"
        if example.get("input"):
            text += f"\n{example['input']}"
        text += f"<|im_end|>\n<|im_start|>assistant\n{example['output']}<|im_end|>"
    else:
        # Manejar formato de messages (ChatML)
        text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=False,
        )
    return {"text": text}

dataset = dataset.map(format_chat_qwen, remove_columns=dataset["train"].column_names)

# === Argumentos de entrenamiento (PEFT 0.14+ / TRL 0.12+) ===
training_args = SFTConfig(
    output_dir=OUTPUT_DIR,
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,         # Batch efectivo = 2 * 8 = 16
    learning_rate=2e-4,
    weight_decay=0.001,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine",
    logging_steps=10,
    save_steps=100,
    save_total_limit=3,
    bf16=True,                             # Usar bf16 para GPUs modernas (A100, RTX 30/40)
    # fp16=True,                           # Usar fp16 para GPUs más antiguas
    optim="paged_adamw_8bit",
    max_grad_norm=0.3,
    group_by_length=True,
    report_to="wandb",                     # o "tensorboard"
    # Específico de SFTConfig:
    max_seq_length=MAX_SEQ_LENGTH,
    dataset_text_field="text",
    packing=True,                          # Empaquetar múltiples ejemplos para mayor eficiencia
)

# === Entrenar ===
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    tokenizer=tokenizer,
    args=training_args,
)

trainer.train()

# === Guardar adaptador LoRA ===
trainer.save_model(f"{OUTPUT_DIR}/final")
tokenizer.save_pretrained(f"{OUTPUT_DIR}/final")
print(f"Model saved to {OUTPUT_DIR}/final")
```

## Flash Attention 2

Flash Attention 2 reduce el uso de VRAM y acelera significativamente el entrenamiento. Requiere GPU Ampere+ (RTX 3090, RTX 4090, A100).

```bash
# Instalar Flash Attention 2
pip install flash-attn --no-build-isolation
```

```python
# Habilitar al cargar el modelo:
model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    attn_implementation="flash_attention_2",  # <-- añadir esto
    torch_dtype=torch.bfloat16,               # FA2 requiere bf16 o fp16
    device_map="auto",
)
```

| Configuración             | VRAM (7B) | Velocidad  |
| ------------------------- | --------- | ---------- |
| Atención estándar (fp16)  | \~22GB    | línea base |
| Flash Attention 2 (bf16)  | \~16GB    | +30%       |
| Flash Attention 2 + QLoRA | \~12GB    | +30%       |

## DoRA (LoRA descompuesto por peso)

DoRA (PEFT >= 0.14) descompone los pesos preentrenados en componentes de magnitud y dirección. Mejora la calidad del fine-tuning, especialmente para rangos más pequeños.

```python
from peft import LoraConfig

# LoRA estándar
lora_config = LoraConfig(r=64, lora_alpha=16, use_dora=False, ...)

# DoRA — mismos parámetros, mejor calidad
lora_config = LoraConfig(r=64, lora_alpha=16, use_dora=True, ...)
# Nota: DoRA añade ~5-10% de sobrecarga de VRAM frente a LoRA estándar
# Nota: No compatible con modelos cuantizados (4-bit/8-bit) en todos los casos
```

## Ejemplos Qwen2.5 & DeepSeek-R1-Distill

### Fine-tuning Qwen2.5

```python
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
# Para 14B: "Qwen/Qwen2.5-14B-Instruct" (necesita 20GB+ VRAM con QLoRA)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,          # Requerido para Qwen2.5
    attn_implementation="flash_attention_2",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)

# Qwen2.5 usa formato ChatML — usar apply_chat_template
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hello!"},
    {"role": "assistant", "content": "Hi there! How can I help?"},
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
```

### Fine-tuning DeepSeek-R1-Distill

Los modelos DeepSeek-R1-Distill (Qwen-7B, Qwen-14B, Llama-8B, Llama-70B) están enfocados en razonamiento. Fine-tunea para adaptar su estilo de cadena de pensamiento a tu dominio.

```python
# Variantes DeepSeek-R1-Distill
MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"   # 7B sobre base Qwen2.5
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B" # 8B sobre base Llama3
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B" # 14B (requiere A100)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
    attn_implementation="flash_attention_2",
)

# DeepSeek-R1 usa etiquetas <think>...</think> para el razonamiento
# Conserva esto en los datos de entrenamiento para preservar la capacidad de cadena de pensamiento
example_format = """<|im_start|>user
Resuelve: ¿Cuál es 15 * 23?<|im_end|>
<|im_start|>assistant
<think>
15 * 23 = 15 * 20 + 15 * 3 = 300 + 45 = 345
</think>
La respuesta es 345.<|im_end|>"""

# Módulos objetivo LoRA para DeepSeek-R1-Distill (base Qwen2.5)
lora_config = LoraConfig(
    r=32,
    lora_alpha=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    use_dora=True,
    task_type="CAUSAL_LM",
)
```

## Usando Axolotl (más fácil)

Axolotl simplifica el fine-tuning con configs YAML:

```bash
pip install axolotl

# Crear config
cat > config.yml << 'EOF'
base_model: mistralai/Mistral-7B-v0.1
model_type: MistralForCausalLM
tokenizer_type: LlamaTokenizer

load_in_4bit: true
adapter: qlora
lora_r: 32
lora_alpha: 16

datasets:
  - path: your_data.json
    type: alpaca

sequence_len: 4096
sample_packing: true

gradient_accumulation_steps: 4
micro_batch_size: 2
num_epochs: 3
learning_rate: 2e-4

output_dir: ./output
EOF

# Entrenar
accelerate launch -m axolotl.cli.train config.yml
```

## Ejemplos de configuración de Axolotl

### Modelo de chat

```yaml
base_model: mistralai/Mistral-7B-Instruct-v0.2
load_in_4bit: true
adapter: qlora

datasets:
  - path: data.json
    type: sharegpt

chat_template: mistral
```

### Modelo de código

```yaml
base_model: codellama/CodeLlama-7b-hf
load_in_4bit: true
adapter: qlora

datasets:
  - path: code_data.json
    type: alpaca

sequence_len: 8192  # Contexto más largo para código
```

## Fusionando pesos LoRA

Después del entrenamiento, fusiona LoRA de nuevo en el modelo base:

```python
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

# Cargar modelo base
base_model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-v0.1",
    torch_dtype=torch.float16,
    device_map="auto",
)

# Cargar LoRA
model = PeftModel.from_pretrained(base_model, "./output/final")

# Fusionar
merged_model = model.merge_and_unload()

# Guardar modelo fusionado
merged_model.save_pretrained("./merged_model")
tokenizer.save_pretrained("./merged_model")
```

## Convertir a GGUF

Para uso con llama.cpp/Ollama:

```bash

# Clonar llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# Convertir
python convert.py ../merged_model --outtype f16 --outfile model-f16.gguf

# Cuantizar
./quantize model-f16.gguf model-q4_k_m.gguf q4_k_m
```

## Monitoreo del entrenamiento

### Weights & Biases

```python
import wandb
wandb.init(project="llm-finetune", name="mistral-7b-lora")
```

### TensorBoard

```python

# En los argumentos de entrenamiento
report_to="tensorboard"
logging_dir="./logs"

# Ver
tensorboard --logdir ./logs --port 6006 --bind_all
```

## Mejores prácticas

### Hiperparámetros

| Parámetro         | Modelo 7B | Modelo 13B | Modelo 70B |
| ----------------- | --------- | ---------- | ---------- |
| tamaño de lote    | 4         | 2          | 1          |
| acumulación\_grad | 4         | 8          | 16         |
| lr                | 2e-4      | 1e-4       | 5e-5       |
| lora\_r           | 64        | 32         | 16         |
| épocas            | 3         | 2-3        | 1-2        |

### Tamaño del dataset

* Mínimo: 1.000 ejemplos
* Bueno: 10.000+ ejemplos
* Calidad > Cantidad

### Evitando el sobreajuste

```python
training_args = TrainingArguments(
    ...
    weight_decay=0.01,
    warmup_ratio=0.03,
    save_total_limit=3,
    load_best_model_at_end=True,
    evaluation_strategy="steps",
    eval_steps=100,
)
```

## Entrenamiento Multi-GPU

```bash

# Con accelerate
accelerate launch --multi_gpu --num_processes 4 train.py

# Con DeepSpeed
accelerate launch --use_deepspeed --num_processes 4 train.py
```

Configuración DeepSpeed:

```json
{
  "bf16": {"enabled": true},
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {"device": "cpu"}
  }
}
```

## Guardado y exportación

```bash

# Guardar adaptador LoRA
trainer.save_model("./lora_adapter")

# Guardar modelo fusionado
merged_model.save_pretrained("./full_model")

# Subir a HuggingFace
huggingface-cli login
merged_model.push_to_hub("username/my-model")
```

## Solución de problemas

### Errores OOM

* Reducir tamaño de lote
* Aumentar acumulación de gradiente
* Usa `gradient_checkpointing=True`
* Reducir lora\_r

### La pérdida de entrenamiento no disminuye

* Comprobar el formato de los datos
* Aumentar la tasa de aprendizaje
* Buscar problemas en los datos

### Pérdida NaN

* Reducir la tasa de aprendizaje
* Usar fp32 en lugar de fp16
* Comprobar si hay datos corruptos

## Estimación de costos

Tarifas típicas del mercado CLORE.AI (a fecha de 2024):

| GPU       | Tarifa por hora | Tarifa diaria | Sesión de 4 horas |
| --------- | --------------- | ------------- | ----------------- |
| RTX 3060  | \~$0.03         | \~$0.70       | \~$0.12           |
| RTX 3090  | \~$0.06         | \~$1.50       | \~$0.25           |
| RTX 4090  | \~$0.10         | \~$2.30       | \~$0.40           |
| A100 40GB | \~$0.17         | \~$4.00       | \~$0.70           |
| A100 80GB | \~$0.25         | \~$6.00       | \~$1.00           |

*Los precios varían según el proveedor y la demanda. Consulte* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *para tarifas actuales.*

> 📚 Véase también: [Cómo ajustar finamente LLaMA 3 en una GPU en la nube — Guía paso a paso](https://blog.clore.ai/how-to-fine-tune-llama-3-cloud-gpu/)

**Ahorrar dinero:**

* Usa **Spot** mercado para cargas de trabajo flexibles (a menudo 30-50% más barato)
* Pagar con **CLORE** fichas
* Comparar precios entre diferentes proveedores
