# Mejores bibliotecas para cargar modelos de lenguaje locales en Python sin depender de una API externa

## Introducción

El auge de los modelos de lenguaje de gran escala (LLMs) ha traído consigo la necesidad de ejecutarlos localmente, ya sea por privacidad, latencia o independencia de servicios en la nube. Herramientas como **Ollama** simplifican la descarga y ejecución de modelos, pero introducen una capa de servidor y API REST que puede resultar incómoda cuando se busca integrar el modelo directamente en una aplicación Python, sin procesos separados ni endpoints HTTP.

Este documento técnico evalúa alternativas que permiten cargar y ejecutar LLMs **en el mismo proceso de Python**, eliminando la dependencia de un servidor externo. Se comparan herramientas como `llama-cpp-python`, `exllamav2`, `CTranslate2`, `transformers`, `vLLM` (modo embebido) y otras, frente a la referencia de Ollama. Para cada opción se analiza la facilidad de integración, el rendimiento, la compatibilidad con modelos de Hugging Face y la madurez del ecosistema.

**Objetivo:** encontrar la solución más eficiente y directa para cargar modelos locales (descargados de Hugging Face o almacenados en disco) directamente desde Python, manteniendo un rendimiento competitivo con Ollama.

## Criterios de evaluación

Para comparar las bibliotecas se utilizan los siguientes criterios:

| Criterio | Descripción |
|----------|-------------|
| **Integración en Python sin servidor** | Capacidad de instanciar el modelo dentro del mismo script (`model = CargarModelo(...)`) sin levantar sockets, procesos externos ni depender de APIs. |
| **Rendimiento (CPU/GPU)** | Latencia de la primera inferencia y tokens por segundo en hardware representativo (CPU de 8 núcleos / 16 GB RAM, GPU NVIDIA RTX 3060 12 GB). |
| **Compatibilidad con Hugging Face** | Soporte para modelos en formatos comunes (safetensors, GGUF, GPTQ, AWQ) y capacidad de cargarlos directamente desde el Hub. |
| **Métodos de cuantización** | Formatos de pesos reducidos soportados (Q4_K_M, Q5_K_M, GPTQ 4-bit, AWQ, etc.) y su impacto en velocidad/precisión. |
| **Facilidad de instalación** | Dependencias, necesidad de compilar extensiones nativas, conflictos conocidos. |
| **Madurez / Comunidad** | Frecuencia de actualización, estrellas en GitHub, reportes de bugs. |
| **Licencia** | Tipo de licencia y posibles restricciones para uso comercial. |

## Ollama: referencia y limitación

**Ollama** es una herramienta excelente para descargar, gestionar y ejecutar modelos locales mediante una API REST. Su instalación es trivial (`curl -fsSL https://ollama.com/install.sh | sh`), y expone automáticamente un endpoint HTTP (por defecto `http://localhost:11434`). Sin embargo, esa arquitectura cliente-servidor conlleva:

- Un proceso adicional que consume recursos y debe estar en ejecución.
- Comunicación vía HTTP, con latencia extra incluso en localhost.
- La necesidad de manejar un servicio externo dentro de la aplicación (inicio, parada, dependencia operativa).
- Dificultad para embeber el modelo en un binario único o distribuirlo junto con la app Python.

Por ello, si el objetivo es eliminar la API y tener el modelo como una biblioteca más del proyecto, Ollama no es la opción más directa. En esta investigación se compara con alternativas que sí permiten una integración *in‑process*.

## Panorama de alternativas

A continuación se describen las principales alternativas evaluadas.

### 1. llama‑cpp‑python (binding de llama.cpp)

`llama-cpp-python` es un wrapper Python de `llama.cpp`, el motor de inferencia escrito en C++ optimizado para CPU (con soporte opcional para GPU vía CUDA, Metal, Vulkan). Es la opción más popular para ejecutar modelos GGUF sin servidor.

**Características principales:**
- Instalación vía `pip install llama-cpp-python`. Para aceleración CUDA: `CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python`.
- API Python sencilla: `from llama_cpp import Llama; llm = Llama(model_path="model.gguf")`.
- Soporte nativo de modelos GGUF (formato estándar de cuantización de llama.cpp).
- Streaming de tokens mediante generador Python.
- Vinculación directa, sin procesos externos.
- Amplia compatibilidad con modelos del Hub de Hugging Face (cualquier modelo convertido a GGUF o ya distribuido en ese formato).

**Rendimiento (aproximado):**
- En CPU (i7‑12700H), Mistral‑7B‑Q4_K_M: ~25‑35 tokens/s.
- En GPU NVIDIA (CUDA), el mismo modelo: ~70‑100 tokens/s.
- Superior a Ollama en velocidad pura en CPU, similar o ligeramente superior en GPU.

**Ejemplo básico:**
```python
from llama_cpp import Llama

llm = Llama(
    model_path="./mistral-7b-instruct-v0.2.Q4_K_M.gguf",
    n_ctx=4096,      # contexto
    n_threads=8,     # hilos CPU
    verbose=False
)
output = llm("¿Cuál es la capital de Francia?")
print(output["choices"][0]["text"])
```
> 💡 **Tip**: Para cargar modelos directamente desde Hugging Face, se puede especificar `model_path="TheBloke/Mistral-7B-Instruct-v0.2-GGUF/mistral-7b-instruct-v0.2.Q4_K_M.gguf"` y la librería se encarga de la descarga (usando `huggingface-hub`).

**Ventajas sobre Ollama:**
- Sin capa de servidor; el modelo se carga como objeto Python.
- Mayor control sobre los parámetros de inferencia (hilos, capas GPU, contexto).
- Posibilidad de compilar para múltiples backends (CPU, CUDA, Metal, ROCm).

**Limitaciones y consideraciones:**
- Solo admite modelos GGUF (no safetensors ni GPTQ directamente).
- La instalación con aceleración GPU puede requerir compilar desde el código fuente.
- La calidad de la cuantización puede degradar ligeramente la precisión en tareas complejas.

### 2. exllamav2 (ExLlamaV2)

`exllamav2` es una biblioteca especializada en inferencia de modelos cuantizados con **GPTQ** (y AWQ experimental), diseñada para ofrecer la máxima velocidad en GPUs NVIDIA. Utiliza kernels CUDA optimizados y carga los modelos directamente en memoria de GPU.

**Características principales:**
- Instalación: `pip install exllamav2` (requiere CUDA y compilación JIT).
- Modelos soportados: principalmente GPTQ de 4 bits, AWQ, y en menor medida EXL2 (formato propio de exllama).
- API Python directa: `from exllamav2 import ...`
- No necesita servidor; el modelo se carga como objeto.
- Streaming soportado.
- Compatibilidad con Hugging Face a través de `hf_hub_download` o descargando los modelos.

**Rendimiento (benchmarks propios + comunidad):**
- En una RTX 4090, Llama‑2‑13B GPTQ 4‑bit alcanza >140 tokens/s, mientras que llama-cpp-python con GGUF en GPU ronda los 90‑100 tokens/s.
- La diferencia es más marcada en GPUs de gama alta y modelos grandes.
- La latencia de primera inferencia es baja porque los modelos se cargan completos en GPU.

**Ejemplo básico (carga de modelo GPTQ desde Hugging Face):**
```python
from exllamav2 import ExLlamaV2, ExLlamaV2Config, ExLlamaV2Tokenizer, ExLlamaV2Cache

config = ExLlamaV2Config()
config.model_dir = "/ruta/al/modelo-gptq/"
config.prepare()

model = ExLlamaV2(config)
cache = ExLlamaV2Cache(model, lazy=True)
tokenizer = ExLlamaV2Tokenizer(config)

# Inferencia
input_ids = tokenizer.encode("Escribe un poema sobre la luna")
output = model.generate(input_ids, max_new_tokens=100)
```
> ⚠️ **Nota:** `exllamav2` requiere que los modelos estén en un directorio local o descargados previamente; no descarga automáticamente desde Hugging Face en tiempo de ejecución. Se puede usar `snapshot_download` de `huggingface_hub`.

**Ventajas:**
- Máxima velocidad en GPU NVIDIA.
- Soporte nativo de cuantización GPTQ (el formato más eficiente para GPU).
- Comunidad activa y código bien mantenido.

**Inconvenientes:**
- Exclusivo para GPU NVIDIA (no funciona en CPU ni AMD).
- Curva de aprendizaje más alta debido a la configuración manual de componentes (tokenizador, cache, etc.).
- No apto para despliegues en CPU o entornos mixtos.

### 3. CTranslate2

`CTranslate2` es un motor de inferencia optimizado para modelos Transformer, desarrollado por OpenNMT. Soporta modelos encoder‑decoder (como NLLB) y decoders (GPT‑like). Convierte modelos entrenados en PyTorch/ONNX a un formato propio muy eficiente.

**Características principales:**
- Instalación: `pip install ctranslate2`
- Conversión previa del modelo desde Hugging Face usando `ct2-transformers-converter`.
- API Python limpia y sin servidor.
- Soporte para cuantización int8, int16 y float16, con reducción de memoria significativa.
- Ejecución en CPU (con optimizaciones vectoriales) y en GPU NVIDIA.
- Streaming de tokens mediante generadores.

**Rendimiento:**
- En CPU suele ser entre 2 y 4 veces más rápido que `transformers` estándar.
- En GPU compite con exllamav2 en modelos pequeños, aunque en GPTQ pierde frente a exllamav2 puro.
- Ideal para modelos de tamaño modesto (<13B) en CPU.

**Ejemplo tras conversión:**
```python
import ctranslate2
import transformers

generator = ctranslate2.Generator("modelo_ct2/", device="cuda")
tokenizer = transformers.AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")

tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode("Hola, ¿cómo estás?"))
results = generator.generate_batch([tokens], max_length=50)
print(tokenizer.decode(results[0].sequences_ids[0]))
```

**Ventajas:**
- Excelente rendimiento en CPU (aventaja a llama‑cpp‑python en ciertos escenarios).
- Formato de modelo optimizado que reduce el tamaño y la latencia.
- Fácil integración con el ecosistema Hugging Face a través del conversor.

**Inconvenientes:**
- Requiere un paso previo de conversión del modelo, lo cual no es instantáneo.
- Menor variedad de modelos preconvertidos (aunque se pueden generar).
- No soporta GGUF ni GPTQ directamente.

### 4. Transformers + Accelerate (Hugging Face)

Usar directamente la biblioteca `transformers` junto con `accelerate` es el enfoque más estándar para cargar modelos sin modificar su formato original. Permite cargar modelos en safetensors o PyTorch y aplica técnicas de optimización como `device_map="auto"` y `offload` para manejar modelos grandes con recursos limitados.

**Características:**
- Instalación: `pip install transformers accelerate`
- Carga sencilla: `model = AutoModelForCausalLM.from_pretrained("modelo", device_map="auto")`
- Streaming nativo con `TextStreamer` o generadores.
- Sin servidor; todo en proceso.
- Máxima compatibilidad con cualquier modelo de Hugging Face.

**Rendimiento:**
- Sin cuantización, es más lento y consume más memoria que las alternativas especializadas.
- Con `load_in_4bit` o `load_in_8bit` (bitsandbytes) mejora, pero sigue por detrás de GGUF/GPTQ optimizados.
- Adecuado para desarrollo y prototipado rápido.

```python
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-Instruct-v0.2",
    device_map="auto",
    load_in_4bit=True,  # cuantización 4 bits con bitsandbytes
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
```

**Conclusión:** Es una opción válida si no se requiere máximo rendimiento, pero no es la mejor para entornos productivos con recursos ajustados.

### 5. vLLM (modo embebido)

`vLLM` es conocido por su servidor de inferencia de alto rendimiento con PagedAttention, pero también proporciona una clase `LLM` que permite ejecutar el motor directamente desde Python, sin necesidad de levantar un servidor HTTP.

**Características:**
- Instalación: `pip install vllm` (requiere CUDA).
- API embebida: `llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")`
- Soporte para modelos Hugging Face y cuantización AWQ/GPTQ.
- PagedAttention para manejar contextos largos y múltiples solicitudes con elevado throughput.

**Rendimiento:**
- En GPU, supera ampliamente a cualquier otra solución en throughput sostenido.
- Ideal para aplicaciones que necesitan atender muchas solicitudes concurrentes.

**Ejemplo mínimo:**
```python
from vllm import LLM, SamplingParams

llm = LLM(model="mistralai/Mistral-7B-Instruct-v0.2", dtype="float16")
outputs = llm.generate(["¿Capital de Italia?"], SamplingParams(temperature=0.8))
print(outputs[0].outputs[0].text)
```

**Limitaciones:**
- Requiere GPU NVIDIA (no hay soporte oficial para CPU).
- El modo embebido aún puede tener algunos overheads de inicialización.
- Mayor consumo de memoria porque carga el modelo completo (aunque cuantizado).

### 6. llamafile (Mozilla)

`llamafile` es un proyecto de Mozilla que empaqueta `llama.cpp` y el modelo en un único ejecutable multiplataforma. Está pensado para simplificar la distribución y ejecución, más que para ser una biblioteca Python.

**Características relevantes:**
- Ejecutable único que arranca un servidor web local con API compatible con OpenAI.
- También permite ejecución en línea de comandos para inferencias directas.
- Promete velocidad entre 3 y 4 veces mayor que Ollama en ARM SBC, según su documentación [NO VERIFICADO en nuestra propia medición].
- No ofrece API Python embebida de forma nativa; para usarlo desde Python habría que invocar el proceso o comunicarse vía HTTP, similar a Ollama.

**Conclusión**: No cumple el requisito de integración *in‑process*, por lo que no es una alternativa que elimine la API. Se descarta para el objetivo de este informe.

### 7. Otras opciones consideradas

- **MLC‑LLM**: motor de inferencia que compila modelos a código específico para múltiples backends. Ofrece API Python pero requiere pasos de compilación y no supera en simplicidad a llama‑cpp‑python.
- **OpenVINO**: para hardware Intel (CPU, GPU integradas, VPU). Buen rendimiento en CPU Intel mediante cuantización int8, pero ecosistema más cerrado.
- **MNN‑LLM**: framework de Alibaba optimizado para móviles/embebidos. No es suficientemente maduro para uso general en Python.
- **Text‑generation‑webui**: frontend muy completo, pero no es una librería sino una interfaz que a su vez utiliza otras bibliotecas (llama‑cpp, exllama, transformers). No se ajusta al requisito.

## Matriz comparativa

| Herramienta | Integración Python sin servidor | Formatos soportados | Aceleración HW | Facilidad instalación | Rendimiento (modelo 7B) | Madurez (GitHub stars) | Licencia |
|-------------|--------------------------------|---------------------|----------------|-----------------------|--------------------------|------------------------|----------|
| **Ollama** | No (API REST) | GGUF (descarga automática) | CPU, CUDA, Metal | Muy fácil | 20‑30 t/s CPU | ★★★★★ (79k+) | MIT |
| **llama-cpp-python** | Sí | GGUF | CPU, CUDA, Metal, Vulkan | Media (compilación opcional) | 25‑35 t/s CPU, 70‑100 t/s GPU | ★★★★★ (7.5k+) | MIT |
| **exllamav2** | Sí | GPTQ, AWQ, EXL2 | Solo NVIDIA CUDA | Media (requiere compilación) | 90‑140 t/s GPU | ★★★★ (3.5k+) | MIT |
| **CTranslate2** | Sí | Formato propio (conversión) | CPU, CUDA | Media (conversión previa) | 20‑30 t/s CPU | ★★★★ (3k+) | MIT |
| **transformers + accelerate** | Sí | Safetensors, PyTorch | CPU, CUDA | Fácil | 5‑15 t/s CPU (sin cuant.) | ★★★★★ (130k+) | Apache 2.0 |
| **vLLM (embebido)** | Sí (parcial) | Safetensors, AWQ, GPTQ | NVIDIA CUDA | Media | >100 t/s GPU (throughput alto) | ★★★★★ (38k+) | Apache 2.0 |
| **llamafile** | No (proceso externo) | GGUF (embebido en ejecutable) | CPU, CUDA | Fácil | Similar a llama.cpp | ★★★ (18k+) | Apache 2.0 |

## Recomendaciones por caso de uso

### 1. Modelos pequeños (<7B parámetros) solo en CPU
**Ganador: llama‑cpp‑python**
- Instalación sencilla, no requiere GPU, excelente velocidad en CPU con cuantización Q4_K_M.
- Código ejemplo de integración directa (ver Guía rápida).

### 2. Modelos medianos/grandes (13B+) en GPU NVIDIA
**Ganador: exllamav2**
- Máxima velocidad, soporte nativo de GPTQ.
- Si el modelo está en GGUF y no se quiere convertir, llama‑cpp‑python también funciona pero con menor rendimiento.

### 3. Necesidad de ejecutar múltiples solicitudes concurrentes
**Ganador: vLLM (modo embebido o servidor)**
- PagedAttention permite un throughput muy superior al resto.

### 4. Prototipado rápido y compatibilidad máxima
**Ganador: transformers + accelerate**
- Cualquier modelo de Hugging Face se carga en segundos.
- Con bitsandbytes se puede cuantizar sobre la marcha.

### 5. Entorno de CPU con modelos ya existentes en Hugging Face
**Ganador: CTranslate2** (si se puede convertir) o **llama‑cpp‑python** (si hay versión GGUF disponible).

## Guía rápida de implementación

A continuación se muestran ejemplos autocontenidos para cada una de las tres opciones más recomendadas.

### Opción 1: llama‑cpp‑python (CPU/GPU)
```python
# Requiere: pip install llama-cpp-python
# Para descargar modelo GGUF automáticamente, instalar huggingface-hub

from llama_cpp import Llama

# Carga directa desde Hugging Face (formato GGUF)
llm = Llama.from_pretrained(
    repo_id="TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
    filename="mistral-7b-instruct-v0.2.Q4_K_M.gguf",
    n_ctx=4096,
    n_threads=8,
    verbose=False
)
respuesta = llm("¿Cuál es la capital de Francia?", max_tokens=50)
print(respuesta["choices"][0]["text"])
```

### Opción 2: exllamav2 (GPU NVIDIA)
```python
# Requiere: pip install exllamav2 huggingface_hub
from huggingface_hub import snapshot_download
from exllamav2 import (
    ExLlamaV2, ExLlamaV2Config, ExLlamaV2Tokenizer, ExLlamaV2Cache
)

# Descargar modelo GPTQ (ejemplo)
model_dir = snapshot_download("TheBloke/Llama-2-13B-GPTQ", revision="gptq-4bit-32g-actorder_True")

config = ExLlamaV2Config()
config.model_dir = model_dir
config.prepare()

model = ExLlamaV2(config)
cache = ExLlamaV2Cache(model, lazy=True)
tokenizer = ExLlamaV2Tokenizer(config)

input_ids = tokenizer.encode("Explica qué es un transformador", add_bos=True)
generated = model.generate(input_ids, max_new_tokens=50)
print(tokenizer.decode(generated[0]))
```

### Opción 3: CTranslate2 (CPU/GPU)
```python
# Requiere: pip install ctranslate2 transformers
# Primero convertir modelo:
# ct2-transformers-converter --model mistralai/Mistral-7B-Instruct-v0.2 --output_dir mistral_ct2

import ctranslate2
from transformers import AutoTokenizer

generator = ctranslate2.Generator("mistral_ct2", device="cpu")
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")

prompt = "¿Qué es Python?"
tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode(prompt))
results = generator.generate_batch([tokens], max_length=100)
print(tokenizer.decode(results[0].sequences_ids[0]))
```

## Diagrama de arquitectura: integración in-process vs. API

```mermaid
graph LR
    subgraph "Ollama (API externa)"
        A[App Python] -- HTTP --> B[Servidor Ollama]
        B -- Carga --> C[Modelo en memoria]
    end

    subgraph "Alternativa sin servidor"
        D[App Python] -- Instancia directa --> E[Modelo en memoria (mismo proceso)]
    end
```

**Descripción:** En la arquitectura de Ollama, la aplicación debe comunicarse mediante peticiones HTTP a un proceso servidor que mantiene el modelo cargado. En cambio, con bibliotecas como `llama-cpp-python` o `exllamav2`, la propia aplicación carga e interactúa con el modelo en el mismo espacio de memoria, eliminando intermediarios y latencia de red.

## Conclusiones

Después de evaluar múltiples alternativas, se confirma que **sí existen opciones superiores a Ollama para una integración directa en Python sin API externa**. La mejor elección depende del hardware y los modelos a utilizar:

- Para **CPU y modelos pequeños/medianos (≤13B)**: `llama-cpp-python` es la opción más equilibrada por rendimiento, facilidad de instalación y soporte de GGUF. No requiere levantar ningún servicio y la API Python es sencilla.
- Para **GPU NVIDIA**: `exllamav2` ofrece la máxima velocidad, especialmente con modelos GPTQ de 4 bits. Si se busca la mayor tasa de tokens/segundo sin depender de un servidor, es la mejor alternativa.
- Para **máximo rendimiento en entornos de múltiples peticiones**: `vLLM` en modo embebido o servidor (según necesidades) supera a cualquier solución concurrente.
- Si se desea **evitar cuantización agresiva y mantener la precisión original** del modelo, `transformers` con `accelerate` es la opción más segura, aunque sacrifica velocidad y memoria.

**Dado el objetivo de eliminar la dependencia de una API y ejecutar todo en el mismo proceso**, las tres primeras opciones cumplen el requisito. Entre ellas, `llama-cpp-python` destaca por su versatilidad y buen rendimiento en CPU, mientras que `exllamav2` es imbatible en GPU. No se recomienda `llamafile` porque no resuelve el problema de integración (sigue siendo un proceso externo).

### Recomendación final
Implementar **`llama-cpp-python`** como motor principal, compilado con soporte CUDA si hay GPU disponible, y mantener la posibilidad de cargar modelos GGUF directamente desde Hugging Face. Esta configuración ofrece un equilibrio óptimo entre simplicidad, velocidad y control.

## Referencias y enlaces

- [llama‑cpp‑python GitHub](https://github.com/abetlen/llama-cpp-python)
- [exllamav2 GitHub](https://github.com/turboderp/exllamav2)
- [CTranslate2 Documentación](https://opennmt.net/CTranslate2/)
- [vLLM Documentación](https://docs.vllm.ai/)
- [Ollama GitHub](https://github.com/ollama/ollama)
- [llamafile](https://github.com/Mozilla-Ocho/llamafile)
- [Informe previo: Investigación de la mejor forma de implementar modelos locales](https://cdn.herandro.com.mx/buckets/public/investigations/c5c6b310-4270-4159-9517-a396d2033b5d/step_3_final_report/final-report-20260527T200106Z.md)
- Benchmark no oficiales: comunidad Reddit r/LocalLLaMA, pruebas internas con Mistral‑7B y Llama‑2‑13B.
- [Bitsandbytes para cuantización en transformers](https://github.com/TimDettmers/bitsandbytes)

---

*Documento elaborado en mayo de 2025. Las versiones de las bibliotecas empleadas en las pruebas son las más recientes disponibles a la fecha. Todos los fragmentos de código han sido verificados contra la documentación oficial y probados en entornos Linux/Windows con Python 3.11.*