# Ajustar finamente LLM

Entrena tu propio LLM personalizado usando técnicas eficientes de fine-tuning en GPUs de CLORE.AI.

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

## Alquilar en CLORE.AI

1. Visita [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Filtra por tipo de GPU, VRAM y precio
3. Elige **Bajo demanda** (tarifa fija) o **Spot** (precio de oferta)
4. Configura tu pedido:
   * Selecciona la imagen Docker
   * Configura los puertos (TCP para SSH, HTTP para interfaces web)
   * Añade variables de entorno si es necesario
   * Introduce el comando de inicio
5. Selecciona el 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 bajo rango) - Entrena pequeñas capas adaptadoras en lugar del modelo completo
* **QLoRA** - LoRA con cuantización para usar aún menos VRAM
* Entrena un modelo 7B en una sola RTX 3090
* Entrena un modelo 70B en una sola A100

## Requisitos

| Modelo | Método        | VRAM mínima | Recomendado                                                                                              |
| ------ | ------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
| 7B     | QLoRA         | 12GB        | [RTX 3090](https://clore.ai/rent-3090.html?utm_source=docs\&utm_medium=guide\&utm_campaign=finetune-llm) |
| 13B    | QLoRA         | 20GB        | [RTX 4090](https://clore.ai/rent-4090.html?utm_source=docs\&utm_medium=guide\&utm_campaign=finetune-llm) |
| 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. Ve a la **Mis pedidos** página
2. Haz clic en tu pedido
3. Encuentra la `http_pub` URL (por ejemplo, `abc123.clorecloud.net`)

Usa `https://YOUR_HTTP_PUB_URL` en lugar de `localhost` en los ejemplos de abajo.

## Preparación del conjunto de datos

### Formato de chat (recomendado)

```json
[
  {
    "messages": [
      {"role": "system", "content": "Eres un asistente útil."},
      {"role": "user", "content": "¿Qué es Python?"},
      {"role": "assistant", "content": "Python es un lenguaje de programación..."}
    ]
  }
]
```

### Formato de instrucciones

```json
[
  {
    "instruction": "Traducir al francés",
    "input": "Hola, ¿cómo estás?",
    "output": "Bonjour, comment allez-vous?"
  }
]
```

### Formato Alpaca

```json
[
  {
    "instruction": "Da tres consejos para mantenerte saludable.",
    "input": "",
    "output": "1. Come comidas equilibradas..."
  }
]
```

## Modelos modernos compatibles (2025)

| Modelo                      | ID de 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 conjunto de datos de HuggingFace
OUTPUT_DIR = "./output"
MAX_SEQ_LENGTH = 4096           # Qwen2.5 admite hasta 32K de contexto
USE_DORA = True                 # DoRA mejora la calidad frente al LoRA estándar
USE_FLASH_ATTN = True           # Flash Attention 2 ahorra VRAM y acelera

# === Cargar el 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 bajo rango descompuesta por pesos) — se requiere PEFT >= 0.14
# use_dora=True descompone los pesos en magnitud + dirección para mejor calidad
lora_config = LoraConfig(
    r=64,                    # Rango (más alto = más capacidad, más VRAM)
    lora_alpha=16,           # Factor de escalado (mantener igual o a 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: LoRA estabilizado por rango
)

# Preparar el 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: parámetros entrenables: 42,991,616 || todos los parámetros: 7,284,891,648 || % entrenable: 0.59

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

# === Formatear el conjunto de datos 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\nEres un asistente útil.<|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 el formato de mensajes (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,         # Lote 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,                             # Usa bf16 para GPUs modernas (A100, RTX 30/40)
    # fp16=True,                           # Usa 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,                          # Empaqueta varios 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"Modelo guardado en {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
# Activar en la carga del modelo:
model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    attn_implementation="flash_attention_2",  # <-- añade 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 pesos)

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 al LoRA estándar
# Nota: No es compatible con modelos cuantizados (4 bits/8 bits) en todos los casos
```

## Ejemplos de Qwen2.5 y DeepSeek-R1-Distill

### Fine-tuning de Qwen2.5

```python
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
# Para 14B: "Qwen/Qwen2.5-14B-Instruct" (necesita 20GB+ de 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 — usa apply_chat_template
messages = [
    {"role": "system", "content": "Eres un asistente útil."},
    {"role": "user", "content": "¡Hola!"},
    {"role": "assistant", "content": "¡Hola! ¿En qué puedo ayudarte?"},
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
```

### Fine-tuning de DeepSeek-R1-Distill

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

```python
# Variantes de 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 (necesita 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
# Mantén esto en los datos de entrenamiento para conservar la capacidad de cadena de pensamiento
example_format = """<|im_start|>user
Resuelve: ¿Cuánto 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 configuraciones YAML:

```bash
pip install axolotl

# Crear configuración
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 usar 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
```

## Monitorización 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          |
| grad\_accum      | 4         | 8          | 16         |
| lr               | 2e-4      | 1e-4       | 5e-5       |
| lora\_r          | 64        | 32         | 16         |
| epochs           | 3         | 2-3        | 1-2        |

### Tamaño del conjunto de datos

* Mínimo: 1.000 ejemplos
* Bueno: 10.000+ ejemplos
* La calidad > la cantidad

### Evitar 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 de 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

* Reduce el tamaño del lote
* Aumenta la acumulación de gradiente
* Usa `gradient_checkpointing=True`
* Reduce lora\_r

### La pérdida de entrenamiento no disminuye

* Verificar el formato de los datos
* Aumentar la tasa de aprendizaje
* Comprobar problemas con 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 de CLORE.AI (a fecha de 2026):

| 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 40 GB | \~$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. Consulta* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *las tarifas actuales.*

> 📚 Ver 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/)

**Ahorra dinero:**

* Usa **Spot** mercado para cargas de trabajo flexibles (a menudo entre un 30 % y un 50 % más barato)
* Paga con **CLORE** tokens
* Compara precios entre diferentes proveedores


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/entrenamiento/finetune-llm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
