# Hardware y rendimiento real
El objetivo declarado del proyecto es un **Lenovo ThinkPad L470** con 16 GB de RAM
y gráficos Intel HD 520/530. Este documento dice qué se puede esperar de verdad en
esa máquina, porque la documentación de partida prometía cifras que no se sostienen
y conviene tenerlo claro antes de instalar nada.
## Dos correcciones a la premisa original
### La GPU integrada no libera memoria
El documento de partida sostiene que la GPU Intel «reduce el uso de RAM de CPU en
3-4 GB» y que **sin ella hay OOM inmediato**, hasta llamarla «necesidad
existencial».
Es falso para gráficos integrados. Una HD 520/530 **no tiene memoria propia**:
reserva un bloque de la misma RAM del sistema. Mover los embeddings a la iGPU
cambia quién hace el cálculo, no de dónde sale la memoria. Los 16 GB siguen siendo
16 GB, y el bloque reservado por el controlador gráfico se resta del total
disponible, no se suma.
Lo que sí aporta descargar los embeddings: la CPU queda libre para la inferencia
del modelo. Eso es una mejora real de latencia, no de memoria.
Por eso aquí la GPU es **opcional**. `resolve_device()` prueba XPU → CUDA → CPU y
degrada con un aviso en lugar de reventar. La especificación original pedía
`device="cuda"` para una Intel HD, y esa llamada falla en cuanto se toca: esas GPU
no tienen backend CUDA. En la práctica, en una HD 520/530 los embeddings van a ir
por CPU, porque tampoco hay soporte de PyTorch para ese hardware.
### Los «6-8 segundos» no salen
El documento promete latencia de 6-8 segundos con 48 feeds, 7 agentes y Mistral 7B.
La cuenta:
| Concepto | Valor |
| -------- | ----- |
| Mistral 7B cuantizado a q4 en un i5-6200U | 2-4 tokens/s |
| Tokens por dictamen (`LLM_MAX_TOKENS`) | 384 |
| Tiempo por agente | ~100-190 s |
| **Siete agentes, en serie** | **~12-22 minutos** |
Y en serie es lo que ocurre aunque el código use `asyncio.gather`: un único proceso
de Ollama con un modelo cargado atiende las peticiones de una en una. La
concurrencia del cliente no multiplica la CPU.
De ahí el cambio de diseño principal de este proyecto: **una sola llamada
multi-persona**. El modelo encarna a los siete analistas y devuelve los siete
dictámenes en un JSON. Se pasa de siete generaciones a una.
## Qué esperar de verdad
### Medición real
Ejecutada durante el desarrollo, sobre un modelo 7B cuantizado en CPU, con los
siete agentes y el prompt multi-persona:
```text
=== LATENCIA REAL: 478.6s ===
confianza : 0.3714
discrepancia : 0.0937
votos : 7 agentes
```
**Ocho minutos para una predicción**, y eso ya *con* la optimización de la llamada
única. Siete llamadas separadas habrían sido cerca de una hora. Es la medida que
justifica todo el diseño del enjambre.
### Tabla de referencia
Las cifras de lectura están medidas; las de generación en otras máquinas son
estimaciones escaladas desde la medición anterior.
| Operación | 7B en CPU | Máquina moderna (8 núcleos) |
| --------- | --------- | --------------------------- |
| Arranque de la API | < 5 s ✅ medido | < 2 s |
| Ronda de ingesta (8 fuentes) | ~15 s ✅ medido, 233 eventos | 5-30 s |
| `/agent/view` | < 100 ms ✅ medido | < 50 ms |
| `/predict` (enjambre completo) | **~8 min** ✅ medido | 30-90 s |
| `/chat` (un agente) | 1-3 min | 10-30 s |
La ingesta y las consultas de lectura son inmediatas. Lo lento es exclusivamente la
generación del modelo. Si el uso que le vas a dar es interactivo, **cambia de
modelo**: es la única palanca que mueve esto de verdad.
### Medir tu latencia
```bash
time curl -s -X POST localhost:8088/predict \
-H 'Content-Type: application/json' \
-d '{"query":"¿Anomalías globales?","horizon":"24h"}' | jq '.latency_ms'
```
## Presupuesto de memoria
Con la configuración por defecto y SQLite:
| Componente | RSS aproximado |
| ---------- | -------------- |
| Ollama con Mistral 7B q4 cargado | 4.5-5.5 GB |
| API de Pythia (1 worker, sin embeddings) | 150-250 MB |
| PostgreSQL, si se usa en vez de SQLite | 300 MB-1 GB |
| Embeddings, si se instala el extra | +600 MB-1.2 GB (arrastra torch) |
| Bloque reservado por la iGPU | 256 MB-2 GB, según BIOS |
En 16 GB con SQLite y sin embeddings queda holgura de sobra. El escenario apretado
es PostgreSQL + embeddings + navegador abierto.
### Si vas justo de memoria
Por orden de eficacia:
1. `DATABASE_URL=sqlite:///./pythia.db` en lugar de PostgreSQL.
2. No instalar el extra `[embeddings]`. No hace falta para predecir.
3. `LLM_MAX_TOKENS=256` y `LLM_CONTEXT_LENGTH=2048`.
4. `MIROFISH_AGENTS=4`: menos agentes, prompt más corto, respuesta más corta.
5. `FEEDS_CONCURRENCY=2`.
6. Un modelo más pequeño: `LLM_MODEL=qwen3:1.7b`. Es el cambio con más impacto
sobre la latencia, a costa de calidad de razonamiento.
## Configuración recomendada para el L470
```bash
DATABASE_URL=sqlite:///./pythia.db
USE_GPU=false # la HD 520/530 no tiene backend de PyTorch
LLM_MODEL=mistral:7b-instruct-q4_K_M
LLM_MAX_TOKENS=384
LLM_CONTEXT_LENGTH=4096
LLM_INFERENCE_TIMEOUT=600 # el techo de 20 s del documento original
# provocaría un 503 en cada predicción
API_WORKERS=1 # cada worker duplica el residente
FEEDS_CONCURRENCY=2
```
El `LLM_INFERENCE_TIMEOUT` es el ajuste que más importa: con el valor de 20 s que
traía la documentación de partida, **ninguna predicción llegaría a completarse**
en esta máquina.
## Comprobar el hardware
```bash
free -h # memoria total
nproc # núcleos
lspci | grep -i vga # gráficos
df -h / # espacio libre (el modelo ocupa ~4.4 GB)
```
Ninguna de estas comprobaciones es un requisito bloqueante. Pythia arranca en
cualquier máquina con Python 3.11+; lo único que cambia es cuánto hay que esperar.