# Modelos entrenados — Sistema B

Los adaptadores QLoRA del Sistema B generan retroalimentación pedagógica de
código Python en español dentro del proyecto EKG-Python. Se ejecutan de dos
maneras, con PEFT/Transformers y con Ollama.

El material de esta carpeta (`material/modelos/`) contiene el comprimido
`adaptadores-qlora.zip` (~347 MB). Al descomprimirlo se obtiene:

```
adaptadores/
├── qlora-feedback-v2/        # SFT, solo módulos de atención
├── qlora-feedback-v3/        # SFT diversificado, all-linear  (adaptador de referencia)
├── qlora-feedback-orpo/      # alineación por preferencias (ORPO)
└── qlora-feedback-v4-dora/   # DoRA (LoRA con descomposición de magnitud)
```

```bash
unzip adaptadores-qlora.zip
```

---

## Modelo base

Todos los adaptadores se entrenaron sobre `Qwen/Qwen2.5-Coder-7B-Instruct`. La
base no se redistribuye con el proyecto; se descarga de Hugging Face la primera
vez que se carga, con `transformers` o, en su versión cuantizada GGUF, con
`ollama pull`. Los adaptadores solo contienen las matrices LoRA y el
tokenizador, así que sin la base no funcionan.

---

## Los 4 adaptadores

Todos comparten `r = 16`, `lora_alpha = 32` y `task_type = CAUSAL_LM` sobre la
misma base. Diferencias relevantes:

| Adaptador | Técnica | Módulos objetivo | dropout | DoRA | Tamaño (`adapter_model.safetensors`) |
|---|---|---|---|---|---|
| `qlora-feedback-v2` | SFT | atención (`q,k,v,o`) | 0.05 | no | ~38 MB |
| `qlora-feedback-v3` | SFT diversificado | all-linear (`q,k,v,o,gate,up,down`) | 0.10 | no | ~154 MB |
| `qlora-feedback-orpo` | ORPO (preferencias) | all-linear | 0.00 | no | ~154 MB |
| `qlora-feedback-v4-dora` | DoRA | all-linear | 0.10 | sí | ~159 MB |

- El total descomprimido ronda ~0,5 GB (los cuatro adaptadores con su
  tokenizador); el zip `adaptadores-qlora.zip` ocupa ~347 MB.
- Cada carpeta de adaptador incluye `adapter_config.json`,
  `adapter_model.safetensors`, `tokenizer.json`, `tokenizer_config.json` y
  `chat_template.jinja`, necesario para construir el prompt de chat de Qwen.
- El adaptador de referencia para inferencia es `qlora-feedback-v3`, que es el
  que carga el script `inferir_b.py` del proyecto.

### Requisitos de GPU

- Base Qwen2.5-Coder-7B-Instruct en 4 bits NF4: ~6 GB de VRAM.
- En precisión `bf16` completa: ~16 GB de VRAM.
- Recomendado / probado: GPU NVIDIA RTX 5090 (32 GB, Blackwell `sm_120`,
  CUDA 12.8). Con 32 GB caben holgadamente la base más cualquier adaptador, e
  incluso el modelo juez `qwen2.5:32b` cuantizado.

> La instalación de PyTorch para la RTX 5090 (índice CUDA `cu128`) y del resto
> de dependencias está en `../proyecto/EJECUTAR.md`, apartado (c).

---

## Forma (i): PEFT / Transformers

Carga la base en 4 bits y aplica el adaptador con `PeftModel.from_pretrained`.
Bloque mínimo y completo; cambia `ADAPTER` por `qlora-feedback-v2`,
`qlora-feedback-orpo` o `qlora-feedback-v4-dora` para probar otra variante:

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

BASE = "Qwen/Qwen2.5-Coder-7B-Instruct"
ADAPTER = "adaptadores/qlora-feedback-v3"     # adaptador entrenado (local)

# el tokenizador y la plantilla de chat van con el adaptador
tok = AutoTokenizer.from_pretrained(ADAPTER)

quant = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
base = AutoModelForCausalLM.from_pretrained(BASE, quantization_config=quant, device_map="auto")
model = PeftModel.from_pretrained(base, ADAPTER).eval()

SISTEMA = ("Eres un asistente docente experto en programación en Python. "
           "Evalúas el código de estudiantes y proporcionas retroalimentación "
           "formativa precisa, fundamentada y respetuosa, identificando el tipo "
           "de error, el concepto implicado y cómo corregirlo.")

codigo = "def suma(xs):\n    total = 0\n    for i in range(1, len(xs)):\n        total += xs[i]\n    return total"
user = ("Analiza el siguiente código Python y proporciona retroalimentación "
        "diagnóstica formativa. Indica el tipo de error (sintáctico, semántico, "
        "conceptual o ninguno), el concepto implicado y una explicación "
        f"divulgativa y otra técnica.\n\n```python\n{codigo}\n```")

msgs = [{"role": "system", "content": SISTEMA},
        {"role": "user", "content": user}]
prompt = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
inputs = tok(prompt, return_tensors="pt").to(model.device)

with torch.no_grad():
    out = model.generate(**inputs, max_new_tokens=220, do_sample=False)
print(tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
```

Para que los acentos salgan bien en consola, fija antes de ejecutar:

```bash
export PYTHONIOENCODING=utf-8        # Windows PowerShell: $env:PYTHONIOENCODING = "utf-8"
```

El proyecto trae este flujo ya empaquetado en
`sistema-rag/scripts/inferir_b.py`, que usa `qlora-feedback-v3` por defecto.

---

## Forma (ii): Ollama

Ollama sirve el modelo base más el adaptador mediante un Modelfile. Necesita
Ollama instalado y el servicio en marcha (`http://localhost:11434`).

### 1. Crear el Modelfile

Crea un fichero `Modelfile` junto a la carpeta del adaptador:

```dockerfile
# Modelfile — Sistema B (Qwen2.5-Coder-7B + QLoRA)
FROM qwen2.5-coder:7b-instruct
ADAPTER ./qlora-feedback-v3

SYSTEM """Eres un asistente docente experto en programación en Python. Evalúas el código de estudiantes y proporcionas retroalimentación formativa precisa, fundamentada y respetuosa, identificando el tipo de error, el concepto implicado y cómo corregirlo."""

PARAMETER temperature 0
PARAMETER num_ctx 4096
```

- `FROM` importa la base, que Ollama descarga la primera vez.
- `ADAPTER` apunta a la carpeta del adaptador entrenado, que debe coincidir con
  la arquitectura del `FROM`. Cambia la ruta para usar `qlora-feedback-v2`,
  `qlora-feedback-orpo` o `qlora-feedback-v4-dora`.

### 2. Registrar y ejecutar el modelo

```bash
ollama create ekg-sistema-b-v3 -f Modelfile
ollama run ekg-sistema-b-v3 "Analiza este código:\n```python\ndef f(x): return x/0\n```"
```

### 3. Modelos del juez

La evaluación del proyecto usa, también por Ollama, dos modelos como juez y
panel multifamilia. Descárgalos con:

```bash
ollama pull llama3.1:8b      # segundo juez / panel (familia Llama)  — ~5 GB
ollama pull qwen2.5:32b      # juez principal (familia Qwen)         — ~20 GB (Q4)
```

- `qwen2.5:32b`: juez principal de la evaluación.
- `llama3.1:8b`: segundo juez de familia distinta, para medir el acuerdo
  inter-juez y neutralizar el sesgo intrafamilia.

Con 32 GB de VRAM (RTX 5090) el juez `qwen2.5:32b` cuantizado cabe en GPU; con
menos memoria, Ollama lo reparte entre GPU y CPU, más lento pero funcional.
