Descargar todos los modelos del enrutador de llama.cpp sin reiniciar

VRAM libre sin detener llama-server.

Índice

Modo router de llama.cpp es uno de los cambios más útiles en llama-server en años. Finalmente ofrece a los operadores de LLM locales una experiencia de gestión de modelos cercana a la que las personas esperan de Ollama, manteniendo al mismo tiempo el rendimiento bruto y el control a bajo nivel que hacen que llama.cpp valga la pena usarlo en primer lugar.

Pero hay un detalle delicado: descargar todo no es un simple botón mágico en la API HTTP.

El router puede listar modelos. Puede cargar un modelo. Puede descargar un modelo. Puede expulsar el modelo menos recientemente usado cuando se alcanza --models-max. Lo que no documenta actualmente como un endpoint de primer nivel es una llamada universal de descargar todos los modelos ahora.

laptop y escritorio con llms

Esto no es un obstáculo real. El patrón correcto es simple, explícito y automatizable:

  1. Preguntar al router qué modelos existen.
  2. Filtrar los modelos cuyo estado es loaded (cargado).
  3. Llamar a /models/unload una vez por cada modelo cargado.

Este es el enfoque que recomiendo para flujos de trabajo de LLM locales serios. Es aburrido, visible y fácil de depurar. Eso es exactamente lo que quieres cuando tu objetivo es liberar VRAM sin reiniciar todo el servicio de inferencia.

Qué hace realmente el modo router de llama.cpp

En el uso clásico de llama-server, inicias un servidor con un modelo:

llama-server \
  --model ./models/qwen3-8b.gguf \
  --port 8080

El modo router cambia eso. En lugar de vincular el servidor a un único archivo GGUF, el router se convierte en un coordinador para múltiples modelos. Puede descubrir modelos desde una caché o desde un directorio de modelos, cargarlos bajo demanda, enrutar las solicitudes al modelo correcto y descargar modelos cuando sea necesario.

Un inicio típico en modo router se ve así:

llama-server \
  --models-dir ./models \
  --models-max 4 \
  --port 8080

La opción importante aquí es --models-max. Controla cuántos modelos pueden estar cargados al mismo tiempo. Si se alcanza el límite, llama.cpp puede expulsar el modelo menos recientemente usado. Eso es útil, pero no es un sustituto para una operación de descarga deliberada. La expulsión por LRU es reactiva. Un script de descarga es control operativo.

Mi opinión: si ejecutas modelos locales para trabajo real, deberías tratar el modo router como un gestor de procesos de inferencia, no como un servidor de chat de juguete. Las operaciones explícitas del ciclo de vida importan.

Los endpoints de gestión de modelos que necesitas

El endpoint principal para descubrimiento es:

curl -s http://localhost:8080/models | jq

Ese endpoint devuelve los modelos conocidos por el router y su estado actual del ciclo de vida. La estructura exacta JSON puede variar ligeramente entre versiones, así que inspecciona tu propia respuesta antes de escribir automatizaciones.

Una forma de respuesta común se ve así:

{
  "data": [
    {
      "id": "qwen3-8b",
      "status": "loaded"
    },
    {
      "id": "llama-3.2-3b",
      "status": "unloaded"
    }
  ]
}

Para descargar un modelo, llama a:

curl -s -X POST http://localhost:8080/models/unload \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3-8b"}' \
  | jq

Esa es la operación primitiva. Todo lo demás en este artículo se basa en eso.

No hay un endpoint documentado para descargar todo

Esta es la parte que suele confundir a la gente.

Podrías esperar algo como esto:

curl -X POST http://localhost:8080/models/unload-all

No construyas sobre esa suposición. La operación documentada es por modelo. Pasas un identificador de modelo a /models/unload, y llama.cpp descarga ese único modelo.

Esto no es necesariamente un mal diseño de API. Una operación por modelo es más segura. Hace que el llamante decida qué debe ser descargado. También evita comportamientos inesperados en producción donde una solicitud de administrador accidentalmente elimina todos los modelos cálidos siendo usados por otros clientes.

Para una estación de trabajo, un atajo para descargar todo sería conveniente. Para una caja de inferencia multiusuario, los bucles explícitos son mejores.

Descarga un modelo primero

Antes de automatizar nada, prueba el identificador de modelo exacto que tu router espera.

Primero, lista los modelos:

curl -s http://localhost:8080/models | jq

Elige un modelo cargado de la salida y luego descárgalo:

curl -s -X POST http://localhost:8080/models/unload \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3-8b"}' \
  | jq

Verifica la lista de modelos nuevamente:

curl -s http://localhost:8080/models | jq

Si el estado del modelo cambia a unloaded (descargado), tu endpoint, puerto e identificador de modelo son correctos.

Si no funciona, no adivines. Inspecciona el JSON. Los alias del router, los nombres de archivo GGUF y los ID de modelo a menudo no son la misma cadena.

Descarga todos los modelos cargados con curl y jq

Una vez que la descarga de un solo modelo funcione, el patrón de descarga total es simplemente un bucle de shell.

Usa esto cuando tu respuesta de /models tenga .data[].id y .data[].status:

curl -s http://localhost:8080/models \
| jq -r '.data[] | select(.status == "loaded") | .id' \
| while IFS= read -r model; do
    echo "Descargando: $model"
    curl -s -X POST http://localhost:8080/models/unload \
      -H "Content-Type: application/json" \
      -d "{\"model\":\"$model\"}" \
      | jq
  done

Este es todo el truco. No es glamuroso, pero tiene la forma correcta para una operación de administrador:

  • Solo descarga modelos que están realmente cargados.
  • Imprime lo que está haciendo.
  • Falla modelo por modelo en lugar de ocultar todo detrás de una acción opaca.
  • Funciona desde cron, ganchos de systemd, SSH o trabajos de CI.

Un script reutilizable para uso en producción

Para cualquier cosa que ejecutes más de dos veces, deja de pegar comandos de una línea. Guarda un script.

Crea llama-router-unload-all.sh:

#!/usr/bin/env bash
set -euo pipefail

LLAMA_SERVER_URL="${LLAMA_SERVER_URL:-http://localhost:8080}"

models_json="$(curl -fsS "$LLAMA_SERVER_URL/models")"

loaded_models="$(printf '%s' "$models_json" \
  | jq -r '.data[] | select(.status == "loaded") | .id')"

if [ -z "$loaded_models" ]; then
  echo "No se encontraron modelos cargados."
  exit 0
fi

printf '%s\n' "$loaded_models" | while IFS= read -r model; do
  [ -z "$model" ] && continue

  echo "Descargando: $model"

  curl -fsS -X POST "$LLAMA_SERVER_URL/models/unload" \
    -H "Content-Type: application/json" \
    -d "{\"model\":\"$model\"}" \
    | jq

done

echo "Hecho. Estado actual del modelo:"
curl -fsS "$LLAMA_SERVER_URL/models" | jq

Hazlo ejecutable:

chmod +x llama-router-unload-all.sh

Ejecútalo contra el servidor local predeterminado:

./llama-router-unload-all.sh

Ejecútalo contra otro host:

LLAMA_SERVER_URL=http://192.168.1.50:8080 ./llama-router-unload-all.sh

Esta es la versión que realmente mantendría en un directorio de herramientas. Usa curl -f para que los errores HTTP fallen el script, y te permite anular la URL del servidor sin editar el archivo.

Adaptando el script a la forma de tu JSON

No asumas ciegamente que cada compilación de llama.cpp devuelve exactamente los mismos campos para siempre. El modo router aún está evolucionando, y tu compilación puede exponer una forma JSON ligeramente diferente.

Comienza inspeccionando la respuesta:

curl -s http://localhost:8080/models | jq

El script usa este filtro:

jq -r '.data[] | select(.status == "loaded") | .id'

Si tu identificador de modelo está en .name, cámbialo a:

jq -r '.data[] | select(.status == "loaded") | .name'

Si tu campo de estado usa otro valor, ajusta el filtro en consecuencia. El principio es lo que importa: selecciona modelos cargados, extrae el identificador aceptado por /models/unload, y luego llama a unload para cada uno.

Por qué los modelos pueden volver a cargarse después de descargarlos

Esta es la fuente más común de confusión.

El modo router soporta carga bajo demanda. Si un cliente envía una solicitud de finalización de chat para un modelo que actualmente está descargado, el router puede cargarlo automáticamente nuevamente.

Esto significa que esta secuencia es posible:

  1. Descargas cada modelo.
  2. Open WebUI, un script de prueba o un agente envía una solicitud.
  3. llama.cpp carga el modelo solicitado nuevamente.
  4. Piensas que la descarga falló, pero no fue así.

La solución es operativa, no técnica. Detén el tráfico de clientes primero si tu objetivo es mantener la VRAM libre.

Por ejemplo:

  • Detén scripts de benchmarks.
  • Pausa agentes y trabajos cron.
  • Cierra o desconecta sesiones de Open WebUI.
  • Deshabilita verificaciones de estado que accidentalmente realizan solicitudes reales de modelos.

Descargar no es un firewall. Si los clientes siguen pidiendo modelos, el modo router está haciendo su trabajo sirviéndolos.

Open WebUI y el botón Eject

Open WebUI puede integrarse con el soporte de descarga de modelos de llama.cpp. Cuando el proveedor está configurado como llama.cpp, Open WebUI puede mostrar el estado de los modelos cargados y exponer una acción Eject para administradores.

Internamente, esa acción llama a su propia API de descarga de Open WebUI, que luego llama al endpoint /models/unload de llama.cpp en la conexión configurada.

Eso es bueno para la operación manual, pero aún así mantendría el script de shell. Un botón de UI es conveniente. Un script es auditable, repetible y usable en una caja sin pantalla a las 2 AM.

Cuándo usar “descargar todo”

Descargar cada modelo cargado es útil cuando quieres:

  • Liberar memoria GPU antes de iniciar un modelo más grande.
  • Reiniciar una caja de desarrollo sin reiniciar llama-server.
  • Prepararse para una ejecución de benchmark con un estado de memoria limpio.
  • Drenar cargas de trabajo de inferencia local antes del mantenimiento.
  • Recuperarse de una sesión desordenada donde se calentaron demasiados modelos.

No es la herramienta correcta cuando usuarios activos dependen de modelos cálidos. En ese caso, ajusta --models-max, usa enrutamiento deliberado y deja que la expulsión LRU haga parte del trabajo. Si necesitas una descarga basada en tiempo de espera más inteligente con control de ciclo de vida por modelo, llama-swap es un proxy diseñado específicamente que superpone exactamente eso sobre cualquier configuración de llama-server.

Mi regla es simple: usa LRU para presión normal, usa descarga explícita para intención del operador.

Solución de problemas

El endpoint de modelos devuelve 404

Es posible que no estés ejecutando una compilación capaz de router, o que estés llamando al puerto incorrecto.

Verifica el proceso del servidor y las opciones disponibles:

llama-server --help | grep -i models

Luego prueba ambos endpoints:

curl -s http://localhost:8080/models | jq
curl -s http://localhost:8080/v1/models | jq

El endpoint /v1/models es la lista de modelos compatible con OpenAI. El endpoint /models es el endpoint de gestión de modelos del router. Están relacionados, pero no son lo mismo.

jq no está instalado

Instálalo antes de scriptear el análisis JSON.

En Ubuntu o Debian:

sudo apt-get update
sudo apt-get install jq

En macOS con Homebrew:

brew install jq

La llamada de descarga devuelve un error

La mayoría de los fallos provienen de pasar el identificador de modelo incorrecto. Usa el identificador exacto devuelto por /models, no el nombre de archivo que crees que debería funcionar.

También verifica si tu nombre de modelo contiene comillas, barras o espacios. El script anterior maneja cadenas normales bien, pero nombres inusuales pueden requerir una construcción JSON más cuidadosa.

Para máxima seguridad, puedes construir el cuerpo POST con jq:

jq -n --arg model "$model" '{model: $model}'

Un bucle de descarga más defensivo usaría ese cuerpo en lugar de JSON escapado a mano.

La VRAM no se libera inmediatamente

Primero confirma que el estado del modelo cambió. Luego verifica si otra solicitud lo recargó. También recuerda que las herramientas de memoria GPU pueden retrasarse o informar el comportamiento del asignador en lugar de la intención instantánea a nivel de aplicación.

La prueba práctica es simple: detén el tráfico, descarga los modelos, lista el estado del modelo y luego inspecciona la memoria GPU. Para el uso medido de VRAM a través de tamaños de modelo y ventanas de contexto en llama.cpp, los benchmarks de llama.cpp de 16 GB VRAM dan cifras concretas para verificar.

Una versión más segura del cuerpo JSON

Si tus identificadores de modelo contienen caracteres inusuales, usa jq para generar el cuerpo de la solicitud JSON:

curl -s http://localhost:8080/models \
| jq -r '.data[] | select(.status == "loaded") | .id' \
| while IFS= read -r model; do
    echo "Descargando: $model"
    body="$(jq -n --arg model "$model" '{model: $model}')"
    curl -s -X POST http://localhost:8080/models/unload \
      -H "Content-Type: application/json" \
      -d "$body" \
      | jq
  done

Esta es la versión a usar si tus modelos tienen nombres con identificadores estilo repositorio, alias personalizados o rutas.

Conclusión final

El modo router de llama.cpp es un gran paso adelante para las operaciones de LLM locales. Te ofrece carga dinámica, cambio de modelos y expulsión consciente de la memoria sin renunciar a la directividad de llama-server.

Pero no esperes por un endpoint perfecto de descarga total. La solución limpia ya existe: lista los modelos cargados y descárgalos uno por uno.

Ese patrón es explícito. Es automatizable. Funciona sobre SSH. Se lleva bien con Open WebUI. Y lo más importante, libera VRAM sin reiniciar el router.

Para la infraestructura de IA local, eso es exactamente el tipo de superficie de control aburrida que quieres.

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.