Tu primer modelo local en 10 minutos (Ollama)
Antes de alquilar una sola GPU, vamos a correr un modelo en tu propia máquina. Gratis, sin cuenta, en 10 minutos. El objetivo no es producción: es que entiendas el ciclo completo de request a tokens, que es lo mismo que hará vLLM después con hardware serio.
Ollama es la puerta de entrada estándar a los modelos locales: descarga pesos, los cuantiza y los sirve con una API OpenAI-compatible en localhost:11434. No es para producción, y en el capítulo 4 verás por qué. Es tu laboratorio.
"Ollama... Get up and running with large language models locally." - Source: Ollama en GitHub
Instalar y correr tu primer modelo
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
ollama --version
Ahora el primer modelo. La regla de tamaño es simple: el modelo debe caber en la mitad de tu RAM. Empieza por uno diminuto:
# terminal
ollama run qwen2.5:0.5b
Eso descarga ~400 MB y abre un chat en tu terminal. Pregúntale algo, mira la velocidad, y sal con /bye.
El sufijo es la escala: 0.5b = 500 millones de parámetros, 7b = 7.000 millones. Ahora el grande:
# terminal
ollama run qwen2.5:7b
Descarga ~4,7 GB. Los pesos ya vienen cuantizados a 4 bits (formato GGUF), por eso un modelo de 7B cabe en ~5 GB y no en los 14 GB que le tocan en FP16. La matemática de cuantización la hice con números en Cuantización con números.
El mismo idioma del capítulo 1
Ollama ya está sirviendo una API en el puerto 11434. Y no cualquier API: la OpenAI-compatible. Reconoce este comando:
# terminal
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:7b",
"messages": [
{ "role": "user", "content": "Explica en una frase que es un token" }
],
"max_tokens": 60
}'
Mismo endpoint, mismo formato de messages, mismo usage que el curl de OpenRouter del capítulo 1. Cambió el backend y tu cliente no se enteró. Ese desacople es exactamente el que explotaremos con vLLM.
Streaming: ver los tokens llegar
Corre el mismo request con "stream": true y -N (desactiva el buffering de curl):
# terminal
curl -N http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen2.5:7b","messages":[{"role":"user","content":"Cuenta del 1 al 10"}],"stream":true,"max_tokens":60}'
No ves un JSON al final: ves decenas de fragmentos data: {...}, uno por token. Cada fragmento trae un delta con el pedacito nuevo de texto. Eso es lo que hace ChatGPT cuando "escribe". En el capítulo 4 vamos a medir cuándo llega el primero.
Mide tus primeros tiempos
Guardar tiempos en la memoria no sirve. Este script mide TTFT (tiempo hasta el primer token) y tokens/s contra cualquier endpoint OpenAI-compatible:
# measure.py
import json, sys, time
import requests
URL = "http://localhost:11434/v1/chat/completions"
MODEL = sys.argv[1] if len(sys.argv) > 2 else "qwen2.5:7b"
PROMPT = sys.argv[2] if len(sys.argv) > 2 else sys.argv[1]
def measure(prompt: str) -> None:
start = time.perf_counter()
ttft = None
tokens = 0
with requests.post(URL, json={
"model": MODEL,
"messages": [{"role": "user", "content": prompt}],
"stream": True,
"max_tokens": 200,
}, stream=True) as r:
for line in r.iter_lines():
if not line or not line.startswith(b"data: "):
continue
payload = line[6:]
if payload == b"[DONE]":
break
chunk = json.loads(payload)
delta = chunk["choices"][0]["delta"].get("content")
if delta:
if ttft is None:
ttft = time.perf_counter() - start
tokens += 1
total = time.perf_counter() - start
print(f"TTFT: {ttft*1000:6.0f} ms | tokens: {tokens:3d} | tok/s: {tokens/total:6.1f} | total: {total:.2f}s")
if __name__ == "__main__":
measure(PROMPT)
Nota que cuento fragmentos, no tokens exactos: casi siempre es 1 delta = 1 token, y para comparar modelos es suficiente. Correlo con los dos modelos:
# terminal
pip install requests
python measure.py qwen2.5:0.5b "Explica que es la cuantizacion en 3 frases"
python measure.py qwen2.5:7b "Explica que es la cuantizacion en 3 frases"
Mis números en un MacBook Pro M4 Max, para que compares contra algo real:
| Modelo | TTFT | Decode |
|---|---|---|
| qwen2.5:0.5b | ~1.000-1.200 ms | 308-326 tok/s |
| qwen2.5:7b | ~2.700-3.200 ms | 72-79 tok/s |
| qwen3-coder:30b | 4.007 ms frío / 34,9 ms caliente | 91-93 tok/s |
Dos lecturas: el decode se vuelve ~4× más lento al multiplicar por 14 los parámetros, y el TTFT depende de si el modelo ya está caliente en memoria (34,9 ms contra 4 segundos). El primer request paga la carga; los siguientes vuelan.
Qué NO esperar de Ollama
Ollama atiende un request a la vez y vive en tu laptop. Para ti solo, perfecto. Para un equipo, no: sin batching, sin gestión de memoria de contexto, sin llaves. Cuando quieras que 10 personas trabajen a la vez, ese es el trabajo de vLLM en una GPU de verdad.
Checkpoint y el error típico
Checkpoint: $0, ~15 minutos. Tienes un modelo corriendo, un streaming entendido y tus primeras 2 mediciones.
El error típico: pedirle a una laptop de 8 GB un modelo de 7B. La RAM se llena, el sistema entra en swap y todo se congela. Respeta la regla de la mitad; si tu máquina es chica, quédate con 0.5b o 3b y sigue con la guía igual.
Si quieres ver el camino local completo (LM Studio, MLX, cuándo vLLM en Mac), lo cubrí en Inferencia local en 2026. Y para no engañarte con benchmarks, medir inferencia sin engañarte.
Siguiente capítulo
Alquilamos tu primera GPU: cuenta RunPod, la L40S correcta (y por qué NO una A100), discos, SSH y la regla de oro para no quemar dinero con el pod encendido.
¿Qué números te dieron tus dos modelos? Déjalos en los comentarios: comparar hardware real es la mejor parte.