Servir varios modelos en una sola GPU: co-residencia, model-swapping y sleep mode

Esta es la segunda pieza de una serie operativa sobre exprimir un cluster LLM on-premise genérico de 4×H100 SXM 80 GB con NVLink. La hermana Compartir una GPU: time-slicing, MPS y MIG parte la GPU para que varios procesos la usen a la vez; esta parte el problema complementario: tienes más modelos que VRAM y necesitas que convivan en el tiempo, no solo en el espacio. La tercera, El plano de datos del RAG en CPU, saca de la GPU todo lo que no necesita estar; y el asistente soberano end-to-end (cuarta entrega, en preparación) ensambla todo esto detrás de LibreChat y LiteLLM. Aquí asumimos que la VRAM es el recurso escaso y que tener todo cargado a la vez no es opción.

TL;DR

Tienes un LLM de agentes que sirve casi todo el tráfico, uno o dos rerankers para el RAG, un modelo alterno (otra familia, otro idioma, un fine-tune) y, de vez en cuando, un modelo grande para tareas difíciles. Sumados no caben en los 80 GB de una H100. Hay tres maneras de que convivan, y la clave para no confundirlas es entender que son tres estados distintos de dónde viven los pesos:

  • Co-residencia — varios modelos cargados a la vez en la HBM, si la suma de sus presupuestos de VRAM cabe en la física. Es lo ideal cuando entran: cero latencia de cambio. La trampa: cada motor debe autolimitar su VRAM; si dos motores creen que tienen los 80 GB enteros, el segundo en pedir memoria revienta con OOM. En una GPU compartida por time-slicing, el reparto de tiempo no protege la memoria —los procesos siguen compitiendo por la misma HBM— y este problema empeora (lo vemos en la pieza hermana).
  • Model-swapping (con llama-swap) — solo uno (o muy pocos) residente a la vez. El proxy mira el campo model de la request y, si toca otro, descarga el actual y carga el pedido. Como solo hay uno dentro, cada modelo puede usar casi toda la GPU. El coste es un cold start completo: segundos desde NVMe local, minutos desde almacenamiento de red. Mezcla motores: expone endpoints OpenAI-compatible incluido /rerank con llama-server sobre GGUF.
  • Sleep mode de vLLM (--enable-sleep-mode, endpoints /sleep y /wake_up) — el modelo no se descarga, se duerme. Los pesos se aparcan en RAM del host (nivel 1) o se descartan dejando el proceso vivo (nivel 2). El wake es 18–200× más rápido que un cold start porque el proceso sigue vivo y conserva el allocator de CUDA, los CUDA graphs y los kernels JIT compilados —lo único que se reconstruye es el KV-cache, que se descarta al dormir. Requiere RAM suficiente para los pesos dormidos.

El árbol de decisión es corto: ¿caben todos? → co-residencia. ¿No caben, el cambio es poco frecuente y mezclas motores? → llama-swap. ¿Todo es vLLM, hay RAM de sobra y la latencia de wake importa? → sleep mode. ¿Carga sostenida de todos a la vez? → no es swap, es más GPUs o réplicas (ver una grande vs N pequeñas). Este post pone los números a cada uno sobre una H100 de 80 GB.

La analogía: un solo escenario, un solo foco

Imagina un teatro con un único escenario y un solo foco —la GPU. Tienes más actores (modelos) en la compañía que los que caben iluminados a la vez. Hay tres formas de montar la función:

  • Co-residencia: varios actores pequeños en escena. Si el reparto de esta escena son tres actores menudos —el LLM principal, un reranker, otro reranker—, caben todos bajo el foco al mismo tiempo. El cambio de réplica entre ellos es instantáneo: ya están en escena. Pero el escenario tiene un tamaño fijo; si metes a un cuarto actor corpulento (el modelo grande), no caben y alguien se cae del borde —eso es el OOM.

  • Model-swapping: el actor se va a casa. Cuando un actor termina su papel, se va a su casa (el disco). Si la escena siguiente lo requiere otra vez, tiene que volver desde casa: vestirse, maquillarse, llegar al teatro. Eso son minutos. A cambio, mientras está en escena tiene todo el escenario para él. Es el modelo de llama-swap: máxima VRAM por actor, pero la reentrada cuesta un viaje completo.

  • Sleep mode: el actor espera entre bambalinas. En vez de irse a casa, el actor sale de la luz del foco pero se queda entre bastidores (la RAM del host), ya vestido y maquillado. Cuando le toca, entra en dos pasos. No hay viaje, no hay vestuario: solo cruzar la cortina. Ese es el wake de vLLM: los pesos no vuelven del disco sino de la RAM, y todo lo que se tarda en “preparar al actor” —el allocator, los CUDA graphs, los kernels— ya está hecho porque nunca se fue del edificio.

La moraleja operativa es la misma que en el teatro: el coste de un actor no es solo su talento, es cuánto tarda en entrar a escena cuando lo necesitas. Co-residencia paga el sitio permanente; swap paga el viaje; sleep paga la RAM de tenerlo esperando cerca. El resto del post es elegir cuál, con presupuesto de VRAM y reloj en mano.

Los tres estados: dónde viven los pesos

Dónde viven los pesos en cada estrategiaCo-residenciaHBM 80 GBLLM agentesreranker Areranker Bheadroom KV + activacionescambio: 0 ms · suma < 80 GB o OOMModel-swapping (llama-swap)HBM 80 GBun modelo residenteusa casi toda la VRAMlos demás en DISCO (NVMe / red)cambio: descargar + cargar (s–min)Sleep mode (vLLM)HBM 80 GBmodelo despiertoproceso vivoKV-cache (se descarta al dormir)los demás dormidos en RAM del hostwake: RAM→VRAM (cientos de ms–s)El eje es dónde están los pesos de los modelos inactivosco-residencia: en HBM (cuestan sitio) · swap: en disco (cuestan un viaje) · sleep: en RAM (cuestan RAM)y eso decide la latencia de tenerlos disponibles otra vez

Co-residencia: el presupuesto de VRAM

Co-residir es la opción por defecto cuando caben: cero latencia de cambio porque todos los modelos están ya en la HBM. La pregunta entera es ¿caben?, y se responde con un presupuesto de VRAM. La VRAM de un motor de inferencia se reparte, a grandes rasgos, en tres partidas:

$$\text{VRAM}{\text{modelo}} = \underbrace{P \cdot b}{\text{pesos}} + \underbrace{\text{KV}}{\text{caché}} + \underbrace{A}{\text{activaciones + overhead}}$$

donde $P$ es el número de parámetros, $b$ los bytes por parámetro según la cuantización, KV el presupuesto de KV-cache (que escala con concurrencia y longitud de contexto) y $A$ el overhead de activaciones, buffers de CUDA y fragmentación. La regla dura de la co-residencia es:

$$\sum_i \text{VRAM}{\text{modelo } i} + \text{margen} ;<; \text{VRAM}{\text{física}}$$

Si la suma se pasa, no hay reparto que valga: el primer motor que pida memoria por encima del hueco libre muere con OOM. Y aquí está el matiz crítico que conecta con la pieza hermana: partir la GPU por tiempo no parte la memoria. En time-slicing, dos procesos se turnan el cómputo pero comparten la HBM entera sin aislamiento; si ambos asumen que tienen los 80 GB, chocan. Solo MIG da particiones de memoria con frontera real (lo vemos en Compartir una GPU). Para co-residir bien, cada motor debe autolimitarse: en vLLM, --gpu-memory-utilization 0.45 le dice “no uses más del 45 % de la GPU”; en llama-server, controlas las capas en GPU y el tamaño de KV. Si no pones esos límites, vLLM reclama por defecto el 90 % de la GPU para sí solo, y no queda sitio para nadie más.

Ejemplo numérico: qué cabe en 80 GB

Pongamos el escenario realista. Un LLM de agentes de 32B en FP8 ($b = 1$ byte/parám):

$$P \cdot b = 32 \times 10^9 \cdot 1 \text{ B} = 32 \text{ GB de pesos}$$

A eso hay que sumarle KV-cache. Para servir con concurrencia decente y contextos de agente (que son largos: historial, herramientas, documentos), un presupuesto de KV de ~12 GB es razonable, más unos ~2 GB de activaciones y overhead. Total del LLM principal: ~46 GB. Quedan ~34 GB de los 80. Veamos qué entra ahí:

ServicioTamaño (FP8/INT8)KV + overheadVRAM total
LLM agentes 32B32 GB~14 GB~46 GB
Reranker A (cross-encoder ~0.5B)~0,5 GB~0,5 GB~1 GB
Reranker B (cross-encoder ~0.5B)~0,5 GB~0,5 GB~1 GB
Modelo alterno 8B~8 GB~4 GB~12 GB
Suma~60 GB
Margen libre~20 GB

Con esto, co-residen perfectamente: el LLM de 32B, los dos rerankers (que son ligerísimos —cientos de MB a 1 GB cada uno) y hasta un alterno de 8B, dejando 20 GB de colchón. Los rerankers son el caso de manual de co-residencia: tan pequeños que siempre caben junto al LLM principal, y rotarlos sería absurdo. El cambio de unos a otros es instantáneo porque están todos cargados.

¿Cuándo se rompe? Cuando entra el modelo grande ocasional, un 70B en FP8:

$$70 \times 10^9 \cdot 1 \text{ B} = 70 \text{ GB de pesos}$$

Solo los pesos del 70B ya consumen 70 de los 80 GB. No hay forma de co-residirlo con el 32B —ni de lejos—. Ahí termina la co-residencia y empieza la rotación: el 70B solo puede entrar si echamos al 32B de la GPU. Esa es la frontera exacta: co-residir mientras la suma de presupuestos quepa con margen; rotar en cuanto un modelo necesite, él solo, más VRAM de la que sobra.

Model-swapping con llama-swap: el actor se va a casa

Cuando no caben a la vez, la primera respuesta es rotar: tener un solo modelo residente y cambiarlo bajo demanda. La herramienta canónica para esto en el mundo on-premise es llama-swap: un proxy en Go que se pone delante de tus servidores de inferencia (llama.cpp, vLLM, TabbyAPI…) y los arranca y para según haga falta.

El mecanismo es elegante por lo simple. Cada request OpenAI-compatible lleva un campo model. llama-swap lee ese campo, mira qué servidor upstream tiene configurado para ese modelo, y:

  1. Si el modelo pedido ya está cargado, enruta la request directamente.
  2. Si está cargado otro, lo para (libera su VRAM) y arranca el correcto.
  3. Cuando el nuevo servidor responde “listo”, reenvía la request.

El coste de un swap es exactamente un cold start completo del modelo nuevo: descargar el actual (rápido, liberar memoria) más cargar el nuevo (lento, mover los pesos del disco a la HBM). Ese segundo término es el que duele, y su magnitud la decide dónde están los pesos: desde NVMe local son segundos; desde almacenamiento de red (Ceph RGW, NFS) pueden ser minutos. Todo el análisis del camino de carga —y por qué el loader por defecto lo hace lento— está en Del disco a la HBM; aquí basta la consecuencia: un swap solo es viable si es infrecuente, porque cada cambio paga ese peaje entero.

La ventaja a cambio: como solo hay uno residente, ese modelo puede usar casi toda la GPU. El 70B que no co-reside con nadie sí cabe holgado si es el único dentro. Y llama-swap mezcla motores: puedes tener configurado un vLLM para el LLM grande, un llama-server con GGUF para el alterno, y dos llama-server para los rerankers —que exponen /rerank, /v1/rerank y /v1/reranking de forma nativa—. Esa heterogeneidad (GGUF + rerank + vLLM bajo un solo endpoint OpenAI) es justo lo que vLLM solo no te da, y la razón principal para elegir llama-swap.

ConfigMap de ejemplo: dos rerankers que rotan en el mismo puerto

Un caso concreto y útil: tienes dos rerankers —uno multilingüe y uno especializado en código— que no necesitas a la vez y prefieres no tener ambos residentes. Con llama-swap rotan en el mismo endpoint, disparados por el campo model. El ConfigMap (montado como config.yaml del proxy en un despliegue de Kubernetes) sería:

apiVersion: v1
kind: ConfigMap
metadata:
  name: llama-swap-rerankers
data:
  config.yaml: |
    # Tiempo que un modelo sigue cargado tras la última request
    # antes de que llama-swap lo descargue para liberar VRAM
    healthCheckTimeout: 60

    models:
      # Reranker multilingüe (GGUF, vía llama-server)
      "reranker-multilang":
        cmd: >
          /usr/bin/llama-server
          --model /models/bge-reranker-v2-m3.Q8_0.gguf
          --reranking
          --port ${PORT}
          --n-gpu-layers 99
          --ctx-size 8192
        # ttl: tras 300 s ocioso, se descarga y libera la GPU
        ttl: 300

      # Reranker de código (GGUF, vía llama-server)
      "reranker-code":
        cmd: >
          /usr/bin/llama-server
          --model /models/codereranker.Q8_0.gguf
          --reranking
          --port ${PORT}
          --n-gpu-layers 99
          --ctx-size 8192
        ttl: 300    

Una request a /v1/rerank con "model": "reranker-multilang" arranca ese servidor; la siguiente con "model": "reranker-code" para el multilingüe y arranca el de código en el mismo ${PORT} que llama-swap gestiona. Como ambos son pequeños, el swap entre ellos es de uno o dos segundos —los GGUF cuantizados pesan pocos cientos de MB y vienen de NVMe local—. El ttl controla cuánto sigue cargado un modelo tras su última petición: subirlo evita swaps si las peticiones llegan a ráfagas; bajarlo libera la GPU antes para otros usos.

Matiz honesto: este patrón de dos rerankers que rotan tiene sentido cuando no te caben junto al resto, o cuando quieres reservar la VRAM para otra cosa. Si te caben (y un par de rerankers de 0,5 GB caben casi siempre, como vimos arriba), co-residirlos es estrictamente mejor: cero latencia de swap. llama-swap brilla cuando rotas modelos grandes o cuando mezclas motores que no coexisten bien, no para hacer malabares con modelos diminutos que cabrían juntos.

vLLM sleep mode: el actor entre bambalinas

El swap tiene un problema: cada cambio paga el cold start entero, y un cold start no es solo mover pesos. Como vimos en Del disco a la HBM, arrancar un motor de inferencia incluye inicializar el proceso de Python y el contexto CUDA, montar el allocator de memoria, capturar los CUDA graphs y compilar los kernels JIT (DeepGEMM, FlashInfer, TorchInductor). Mover los pesos es solo una de cinco partidas, y a menudo no la mayor.

El sleep mode de vLLM (--enable-sleep-mode) ataca exactamente eso: en vez de matar el proceso cuando un modelo deja de usarse, lo duerme dejando el proceso vivo. Hay dos niveles, y la diferencia es dónde van los pesos:

  • Nivel 1: descarga los pesos a la RAM del host (CPU) y descarta el KV-cache. El proceso sigue vivo. El wake copia los pesos de RAM a VRAM —no del disco—. Wake típico: ~0,1–0,8 s para modelos pequeños, ~3–6 s para grandes. Necesita RAM suficiente para los pesos dormidos (del orden de los GB que pesa el modelo).
  • Nivel 2: descarta los pesos por completo, conservando solo buffers pequeños (tensores de rope scaling, etc.). El wake sí recarga los pesos del disco, pero todo lo demás —proceso, allocator, CUDA graphs, kernels JIT— ya está hecho. Wake típico: ~0,8–2,6 s para modelos pequeños. RAM casi nula (megabytes).

La clave que explica los números: en ambos niveles, mantener el proceso vivo preserva la infraestructura cara. Por eso el benchmark del blog de vLLM (oct 2025) reporta que un wake es 18–200× más rápido que un full reload —y, lo más contraintuitivo, el nivel 2 sigue siendo 23–45× más rápido que un cold start a pesar de recargar los pesos del mismo disco, porque se salta las otras cuatro partidas—. En sus medidas sobre A100, un ciclo completo de 5 cambios de modelo pasa de 357 s sin sleep (≈48 s por cambio) a 112 s con nivel 1 (wake de 0,26 s / 0,82 s) o 125 s con nivel 2 (0,85 s / 2,58 s).

El KV-cache se descarta siempre al dormir. No es un detalle menor: significa que la primera respuesta tras el wake reconstruye el KV desde cero —paga un prefill completo—. Por eso el wake no es “gratis del todo”: el modelo está disponible en sub-segundo, pero la primera petición es un poco más lenta hasta que el KV se repuebla. Un warm-up de una petición tras el wake oculta ese coste.

# Arrancar vLLM con sleep mode (endpoints de admin, solo red de confianza)
export VLLM_SERVER_DEV_MODE=1
vllm serve <modelo> --enable-sleep-mode --port 8001

# Dormir (nivel 1: pesos a RAM del host)
curl -X POST 'localhost:8001/sleep?level=1'

# Despertar
curl -X POST 'localhost:8001/wake_up'

Aviso de seguridad (del propio blog de vLLM): los endpoints /sleep, /wake_up, /collective_rpc y /reset_prefix_cache requieren VLLM_SERVER_DEV_MODE=1 y solo deben exponerse en redes de confianza —pueden tumbar el servicio—. Son para orquestación interna (un controlador que duerme y despierta modelos según la cola), no para el plano público.

Las matemáticas de la latencia: por qué el wake gana

Pongamos números al “el wake llega de RAM, no de disco”. El coste de tener un modelo disponible es, en cada estrategia, el tiempo de mover sus pesos desde donde estén hasta la HBM (más, en swap, los otros cuatro costes del cold start). Tomemos los 34 GB de pesos del LLM de 32B en FP8 y comparemos los tres caminos.

Cold start desde NVMe (swap). Un NVMe Gen4/Gen5 razonable da del orden de ~5 GB/s efectivos por flujo con el loader por defecto (el suelo teórico del disco es mayor, pero el deserializado monohilo no lo satura —ver Del disco a la HBM). Para 34 GB:

$$t_{\text{NVMe→HBM}} \approx \frac{34 \text{ GB}}{5 \text{ GB/s}} \approx 6,8 \text{ s solo de mover bytes}$$

Y eso es antes de sumar la captura de CUDA graphs y la compilación de kernels, que añaden varios segundos más. El cold start real de un 32B ronda los 15–40 s según loader y storage. Desde red (Ceph RGW), multiplica.

Wake nivel 1 desde RAM. Los pesos no vienen del disco sino de la RAM del host, y viajan por PCIe Gen5 x16, cuyo ancho de banda práctico host→GPU es de ~50–64 GB/s. Para los mismos 34 GB:

$$t_{\text{RAM→HBM}} \approx \frac{34 \text{ GB}}{55 \text{ GB/s}} \approx 0,62 \text{ s}$$

Y no hay nada más que pagar: el allocator, los graphs y los kernels ya están. El wake real de un modelo de este tamaño cae en el rango sub-segundo a pocos segundos que reporta vLLM. La aceleración frente al cold start de NVMe es del orden de:

$$\frac{t_{\text{cold start}}}{t_{\text{wake}}} \approx \frac{15\text{–}40 \text{ s}}{0,6\text{–}3 \text{ s}} \approx 10\text{–}60\times$$

consistente con el 18–200× del blog (que en sus medidas incluye modelos más pequeños, donde el peso relativo de los CUDA graphs preservados es aún mayor y el factor sube).

Por qué la diferencia de ancho de banda lo explica casi todo. El salto clave no es 5 vs 55 GB/s (un ~11× en el transporte). Es que el cold start paga además la reconstrucción de infraestructura, que el wake se ahorra entera. La tabla:

CaminoOrigen pesosAncho de banda34 GB solo bytes+ CUDA graphs / JITTotal realista
Cold start (swap) NVMedisco~5 GB/s~6,8 ssí (varios s)15–40 s
Cold start (swap) redred~1–2 GB/s17–34 s30 s – min
Wake nivel 1RAM host~50–64 GB/s~0,6 sno (preservado)0,6–3 s
Wake nivel 2disco~5 GB/s~6,8 sno (preservado)7–10 s

Fíjate en la fila del nivel 2: recarga los pesos del mismo disco que el swap (~6,8 s de bytes), pero como no reconstruye graphs ni kernels, su total (~7–10 s) sigue batiendo al cold start completo (15–40 s). Es la prueba de que mover bytes es solo una parte del coste, y la que el sleep mode explota.

Supuestos, honestamente: los anchos de banda son orientativos. NVMe “5 GB/s efectivos” asume el loader por defecto; con un streamer concurrente sube. PCIe “55 GB/s” asume Gen5 x16 con buffer pinned y NUMA-local; si el buffer cae en el socket equivocado, baja. Y el rango “15–40 s” de cold start depende del modelo, la cuantización y si los ficheros están o no en page cache (la trampa del “la segunda vez cargó rápido”). Los números son para razonar órdenes de magnitud, no para dimensionar sin medir en tu hardware.

El árbol de decisión

Las tres estrategias no compiten: cada una gana en un régimen distinto. El árbol, en orden:

¿Caben TODOS a la vez(suma de presupuestos < VRAM)?Co-residenciacada motor autolimita su VRAMno¿Carga sostenida detodos a la vez?Más GPUs / réplicasno es swap: es capacidadno¿Todo es vLLM y hayRAM de sobra?no (mezclas motores)llama-swaprota por campo `model`GGUF + /rerank + vLLMsí (latencia importa)Sleep modenivel 1: pesos a RAMwake 18–200× vs coldY se combinan: co-residir los ligeros + rotar/dormir los grandesrerankers siempre co-residen · el 70B ocasional duerme o entra por swapsi todo se usa a tope a la vez, ninguna estrategia te salva: necesitas más silicio

El nodo que más se ignora es el de la derecha arriba: "¿carga sostenida de todos a la vez?". Si tus cuatro modelos reciben tráfico constante y simultáneo, ni el swap ni el sleep ayudan —ambos asumen que los modelos se turnan en el tiempo—. Rotar bajo carga sostenida solo añade latencia de cambio sin resolver el problema de fondo: no hay suficiente cómputo. La respuesta entonces es escalar horizontal (más réplicas) o repartir en más GPUs, una decisión de capacidad que se analiza en Una grande vs N pequeñas. El swap y el sleep son herramientas para cargas temporalmente desbalanceadas: muchos modelos, pero rara vez activos a la vez.

Aplicado al cluster genérico 4×H100

Bajemos esto a las 4 H100 SXM de 80 GB con NVLink. La estrategia ganadora no es elegir una de las tres, sino repartir los modelos por GPU según su patrón de uso y aplicar a cada GPU la estrategia que le toca. Un reparto razonable:

H100 #0 — El caballo de batalla (co-residencia). El LLM de agentes de 32B (servicio principal, tráfico constante) co-reside con los dos rerankers y, si caben, el alterno de 8B. Es la GPU que nunca rota: todo lo que vive aquí se usa de continuo y entra holgado en 80 GB (los ~60 GB del ejemplo de arriba). Cero latencia de cambio entre el LLM y sus rerankers, que es justo lo que el RAG necesita —un reranker rápido es inútil si hay que esperar un swap cada vez—.

H100 #1 — Servicios ligeros con MIG. Si tienes muchos servicios pequeños heterogéneos —un modelo de embeddings, un clasificador, un guardrail, un STT/TTS—, partir esta GPU con MIG en instancias aisladas (cada una con su trozo de HBM con frontera real) da co-residencia con aislamiento de memoria, evitando que un servicio que infla su KV tumbe a los demás. El detalle de cuándo MIG bate a time-slicing está en Compartir una GPU; la regla aquí: co-residir servicios ligeros en una GPU tiene sentido cuando caben y conviene aislarlos, y MIG es la herramienta para lo segundo.

H100 #2 — El modelo grande ocasional (sleep mode o swap). El 70B que solo se invoca para tareas difíciles no merece una GPU dedicada despierta —estaría ociosa la mayor parte del tiempo, quemando 700 W para nada—. Dos opciones:

  • Si esta GPU también sirve un modelo mediano de forma habitual y solo eventualmente necesitas el 70B, usa sleep mode: duerme el mediano (nivel 1, pesos a RAM), despierta el 70B… salvo que el 70B no quepa ni solo despierto junto al mediano dormido en VRAM —recuerda que dormir nivel 1 libera la VRAM, los pesos van a RAM, así que sí cabe—. El wake del mediano al volver es sub-segundo.
  • Si el 70B viene en GGUF o mezclas motores, llama-swap rota entre el mediano y el 70B por el campo model. Cada invocación del 70B paga su cold start (segundos desde NVMe local), aceptable si es ocasional.

H100 #3 — Réplica / desbordamiento. La cuarta GPU absorbe picos: una réplica del LLM principal para cuando la cola del #0 crece, o capacidad de reserva. Aquí no hay swap: es capacidad pura, la respuesta al nodo “carga sostenida” del árbol.

El principio transversal: co-residir lo que se usa junto y de continuo (LLM + rerankers); aislar con MIG lo ligero y heterogéneo; dormir o rotar lo grande y ocasional; replicar lo que satura. Las cuatro GPUs no hacen lo mismo —cada una ejecuta la estrategia que su patrón de carga pide—. Y el NVLink entre ellas importa para otra cosa (tensor parallel del 70B si no cupiera ni en una; ver Una grande vs N pequeñas), pero para el problema de este post —muchos modelos, una GPU— la palanca es cuándo cada modelo necesita estar despierto.

Trampas y cosas que no son lo que parecen

“Co-residir es siempre mejor si caben.” Casi, pero ojo al KV-cache: co-residir dos modelos significa partir el presupuesto de KV entre ambos. Si el LLM principal necesita un KV grande para concurrencia alta y contextos largos, meterle un compañero le recorta ese KV y baja su throughput. A veces es mejor darle la GPU entera al principal y rotar el secundario. Co-residir no es gratis: el inquilino le quita sitio a la caché del que importa.

“El sleep mode es como el swap pero más rápido.” No exactamente. El swap libera el proceso; puedes tener N modelos configurados y solo pagas RAM/disco por el residente. El sleep mode mantiene un proceso vivo por modelo dormido —cada vLLM dormido sigue ocupando su slot de proceso, su RAM (nivel 1) y su hueco de gestión—. Sleep escala bien a unos pocos modelos que rotan; para muchos (10+), nivel 2 (RAM mínima) o directamente swap encajan mejor. No metas 15 modelos en sleep nivel 1 esperando que la RAM aguante.

“El wake es instantáneo, no pierdo nada.” El wake del modelo es sub-segundo, pero el KV-cache se descartó al dormir. La primera petición tras el wake paga un prefill completo para repoblar el KV —más lenta de lo normal—. Si tu SLA es estricto en la primera respuesta tras un período ocioso, mete un warm-up automático tras el wake. El prefix caching ayuda a que ese reprefill sea más barato si hay prefijos estables.

“llama-swap con ttl bajo me ahorra VRAM gratis.” Te ahorra VRAM mientras nadie use ese modelo, pero cada vez que vuelve paga el cold start. Un ttl agresivo en un modelo con tráfico a ráfagas convierte cada ráfaga en una espera de carga. El ttl correcto depende del patrón temporal de las peticiones, no de cuánta VRAM quieres liberar. Mídelo.

“Time-slicing me deja co-residir más modelos.” Falso y peligroso. El time-slicing reparte tiempo de cómputo, no memoria —todos los procesos siguen compitiendo por la misma HBM sin aislamiento—. Co-residir vía time-slicing no te da más VRAM efectiva; te da más procesos peleándose por la misma, y un OOM cuando la suma se pasa. Para partición de memoria real, MIG. El detalle, en la pieza hermana.

Conclusión

Tener más modelos que VRAM no es un problema de hardware insuficiente: es un problema de gestión temporal de un recurso escaso. La intuición de “necesito una GPU por modelo” es cara y casi siempre falsa, porque los modelos rara vez se usan todos a la vez. Las tres estrategias son tres respuestas a la misma pregunta —dónde viven los pesos de los modelos que ahora no estás usando—: en la HBM si caben (co-residencia, cero latencia pero cuestan sitio), en el disco si el cambio es raro (swap, máxima VRAM por modelo pero un viaje de vuelta de segundos a minutos), o en la RAM si la latencia de cambio importa (sleep mode, wake de sub-segundo a costa de tener la RAM ocupada). El sleep mode es la incorporación más interesante de 2025 porque rompe el falso dilema “todo cargado vs recargar cada vez”: al mantener el proceso vivo y preservar el allocator, los CUDA graphs y los kernels, convierte un cold start de 30–100 s en un wake de menos de un segundo —y lo hace incluso cuando recarga los pesos del mismo disco (nivel 2), porque mover bytes nunca fue el coste entero—. En el cluster de cuatro H100, la jugada no es elegir una estrategia sino repartir: co-residir lo que va junto, aislar lo ligero con MIG, dormir o rotar lo grande y ocasional, replicar lo que satura. La GPU es el escenario con un solo foco; el arte está en saber qué actor entra a escena, cuál se va a casa y cuál espera entre bambalinas.

Ver también

Referencias