Compartir una GPU entre varias cargas: time-slicing, MPS y MIG

Este post abre una serie operativa sobre cómo exprimir un cluster LLM on-premise genérico de 4×H100 SXM. Las piezas hermanas: Servir varios modelos en una GPU: swap y sleep (qué hacer cuando los modelos no caben a la vez y hay que turnarlos en memoria), RAG en CPU: separar plano de datos y generación (mover el retrieval fuera de la GPU para liberarla) y Asistente soberano end-to-end con LibreChat, LiteLLM y RAG —el ensamblaje final, cuarta entrega en preparación—. Aquí empezamos por lo más básico: tienes una GPU y quieres meterle varias cargas encima. ¿Cómo se reparte?

TL;DR

Tienes una GPU —o pocas— y varias cargas que quieren correr encima: un modelo de chat, un servicio de embeddings, un reranker, una cola de jobs de dev. La GPU está infrautilizada si solo corre una cosa, pero meter varias a lo bruto provoca contención, OOM o caídas en cascada. Hay tres mecanismos y reparten cosas distintas. El time-slicing (réplicas del NVIDIA k8s device-plugin) multiplexa en el tiempo: anuncia que la GPU es “N GPUs” y los procesos se turnan el cómputo, pero comparten la VRAM física completa, sin aislamiento de memoria ni de fallos ni QoS. Su trampa es un OOM que no aparece en el scheduler de Kubernetes sino en tiempo de ejecución, cuando la suma de asignaciones de VRAM supera la memoria real. El MPS (Multi-Process Service) multiplexa en el espacio: reparte los SMs entre procesos que ejecutan kernels concurrentemente, reduce el overhead de context-switch y permite limitar SMs y memoria por proceso —sube el throughput cuando hay muchos kernels pequeños, pero el aislamiento de fallos sigue siendo débil. El MIG (Multi-Instance GPU) particiona en hardware: corta la GPU Hopper en hasta siete instancias con SMs, L2, memoria y ancho de banda dedicados, con aislamiento real de memoria, fallo y rendimiento; solo en datacenter (A100/H100/H200/B200), nunca en una RTX 5090. La regla: aislamiento real / multi-tenant / compliance → MIG (si es Hopper); muchos kernels pequeños concurrentes y confianza entre cargas → MPS; dev, ráfagas, GPU de consumo o sin necesidad de aislar → time-slicing. Este post lo trabaja con números: el presupuesto de VRAM de cuatro vLLM sobre una H100 anunciada como cuatro réplicas, y qué cabe en una instancia MIG de 10 GB.

La analogía: un fogón compartido, una cocina con varios cocineros, varias cocinas

Imagina que tienes un único fogón profesional y tres pedidos que cocinar a la vez. Hay tres maneras de organizarlo, y son exactamente los tres mecanismos.

Time-slicing es un solo fogón por turnos, sin despensa propia. Cada cocinero entra, cocina su plato, sale, entra el siguiente. El reparto es temporal: nadie cocina a la vez, se turnan. El problema no es el fogón —se va turnando bien— sino la despensa común: los ingredientes están en una sola alacena compartida y nadie tiene la suya. Si los tres cocineros reservan más harina de la que hay en total, no es que esperen turno: es que no hay harina. El servicio se cae para todos. Y si un cocinero deja una sartén ardiendo y provoca un incendio, quema la cocina entera, no su rincón.

MPS son varios cocineros coordinados en la misma encimera. Ahora sí cocinan a la vez, repartiéndose el espacio de la encimera (los SMs). Un jefe de cocina (el daemon MPS) coordina para que no choquen y para que la encimera no se quede vacía mientras uno espera a que hierva el agua. Puedes asignarle a cada cocinero un porcentaje de la encimera y un límite de despensa. Trabajan más rápido en conjunto porque la encimera no queda ociosa entre tareas pequeñas. Pero siguen compartiendo la cocina: si uno provoca un incendio grave, los demás lo notan.

MIG son varias cocinas independientes en el mismo edificio. Una pared de hormigón separa cada cocina: su propio fogón, su propia despensa, su propia puerta y su propio cuadro eléctrico. Lo que pasa en la cocina 3 —un incendio, una despensa vacía, un cocinero lento— no toca a la cocina 1. Es el único reparto con aislamiento de verdad. El precio: tienes que decidir de antemano cuántas cocinas y de qué tamaño, las paredes son fijas, y solo los edificios caros (datacenter) vienen preparados para levantarlas.

El resto del post es, esencialmente, cuándo quieres turnos baratos, cuándo quieres cocineros coordinados y cuándo necesitas paredes de hormigón.

Por qué compartir: el problema operativo

Una H100 SXM 80 GB no se llena con cualquier carga. Un reranker bge-reranker-v2-m3 ocupa unos cientos de MB y satura unos pocos SMs; un servicio de embeddings bge-m3 es igual de pequeño; un modelo guardrail de 1B en INT4 cabe en un par de GB. Dedicar 80 GB de HBM3 y 132 SMs a servir embeddings es usar una prensa hidráulica para clavar una chincheta —el mismo argumento de los entornos mixtos, pero ahora dentro de la GPU en lugar de moviendo la carga a otro silicio.

El objetivo de compartir es subir la utilización útil del capital fijo. Pero compartir mal introduce tres patologías:

  • Contención de cómputo: dos cargas pelean por los mismos SMs y ambas van lentas, con jitter de latencia impredecible.
  • Contención de memoria: la suma de VRAM solicitada supera la física y algo muere con un CUDA out of memory.
  • Fallo en cascada: una carga que peta (un kernel ilegal, un OOM) puede arrastrar a las vecinas si comparten contexto.

Los tres mecanismos atacan estas patologías con distinta profundidad. Ninguno las resuelve todas salvo MIG, y MIG cuesta hardware concreto. Veámoslos uno a uno.

Time-slicing: turnos de cómputo, despensa compartida

El time-slicing es multiplexación temporal por software. En Kubernetes, el NVIDIA GPU Operator configura el device-plugin para anunciar N réplicas de cada GPU física. Una H100 declarada con replicas: 4 aparece ante el scheduler como cuatro recursos nvidia.com/gpu, y Kubernetes puede colocar cuatro pods sobre ella. Internamente, el planificador de la GPU va dando turnos de cómputo a cada proceso: ejecuta un poco del proceso A, cambia al B, al C, al D, vuelve al A. Es el mismo time-sharing que un sistema operativo hace con la CPU.

La idea clave, y la que más confusión genera, es esta: una réplica NO es una fracción de la GPU. Es un turno de cómputo. La documentación de NVIDIA es explícita: a diferencia de MIG, no hay aislamiento de memoria ni de fallos entre réplicas. Las cuatro réplicas de la H100 ven los 80 GB completos de VRAM, sin partición. No hay 20 GB por réplica. Hay 80 GB para los cuatro, repartidos por orden de llegada de cudaMalloc.

Esto tiene tres consecuencias que hay que interiorizar:

  1. No aísla memoria. Si la suma de lo que reservan los cuatro procesos supera 80 GB, el cuarto cudaMalloc falla con OOM. El scheduler de Kubernetes no lo ve venir: él contó cuatro recursos nvidia.com/gpu disponibles y colocó cuatro pods felizmente. El OOM aparece en tiempo de ejecución, no en scheduling. Esta es la trampa número uno del time-slicing.
  2. No aísla fallos. Un proceso que dispara un error de CUDA irrecuperable puede dejar el contexto de la GPU en un estado que afecta a los vecinos. Comparten el mismo dispositivo sin barreras.
  3. No da QoS de cómputo. Bajo contención, el reparto de turnos no garantiza una fracción mínima a nadie. La latencia de cada carga sufre jitter proporcional a cuántas réplicas activas peleen por la GPU en ese instante. Una carga sensible a latencia (un chat interactivo) puede ver su TTFT bailar según lo que hagan las vecinas.

¿Para qué sirve entonces? Para dev, ráfagas y baja utilización. Si tienes cuatro desarrolladores que tocan la GPU esporádicamente, anunciar cuatro réplicas deja que los cuatro tengan acceso sin pelearse casi nunca (rara vez coinciden activos). Para cargas batch tolerantes a jitter. Y, ventaja decisiva, funciona en GPUs de consumo: una RTX 5090 32 GB no soporta MIG, pero sí time-slicing. Es la única forma “Kubernetes-native” de compartir una 5090 entre varios pods.

Cómo reparte cada mecanismoTime-slicingreparto en el TIEMPOturno Aturno Bturno Cturno DVRAM compartida: 80 GB para los 4sin aislamiento · OOM si suma > VRAMMPSreparto en el ESPACIO (SMs)A 40%B 30%C 20%D 10%kernels concurrentes en SMs distintoslímite SM y mem por proceso · fallo débilMIGpartición HARDWARE1g.10gb1g.10gb3g.40gbSMs, L2, VRAM y BW dedicadosaislamiento real · paredes de hormigónUn fogón por turnos · varios cocineros en la encimera · varias cocinas con su paredconsumo + datacenter · datacenter (CUDA) · solo datacenter Hopper/Ampere/Blackwellsoftware · software (contexto CUDA) · hardware (fusibles físicos)

El presupuesto de VRAM en time-slicing (el cálculo que evita el OOM)

Aquí está la matemática que hay que hacer antes de desplegar, porque Kubernetes no la hará por ti. Supongamos una H100 80 GB anunciada como 4 réplicas y queremos correr cuatro instancias de vLLM encima, una por réplica.

vLLM reserva memoria con el parámetro --gpu-memory-utilization, que es la fracción de la VRAM física total que cada instancia se queda (para pesos del modelo más KV-cache). El detalle que mata: esa fracción se calcula sobre los 80 GB físicos, no sobre un supuesto “20 GB de mi réplica” —porque la réplica no tiene 20 GB, recordemos que no hay partición de memoria. Cada vLLM ve los 80 GB y reserva su fracción de ellos.

La restricción de no-OOM es entonces que la suma de fracciones sea menor que 1:

$$\sum_{i=1}^{N} g_i < 1 \quad\Longleftrightarrow\quad \sum_{i=1}^{N} g_i \cdot V_{\text{HBM}} < V_{\text{HBM}}$$

donde $g_i$ es el --gpu-memory-utilization de la instancia $i$ y $V_{\text{HBM}} = 80$ GB. Conviene dejar margen (overhead del runtime, fragmentación, contexto CUDA), así que en la práctica se busca que la suma quede holgadamente por debajo de 1, digamos $\le 0.9$.

Caso que funciona. Cuatro vLLM a $g_i = 0.20$:

$$\sum_{i=1}^{4} 0.20 = 0.80 \quad\Rightarrow\quad 0.80 \times 80\ \text{GB} = 64\ \text{GB} < 80\ \text{GB} \quad\checkmark$$

Cada instancia reserva $0.20 \times 80 = 16$ GB. Cuatro instancias suman 64 GB, dejando 16 GB de colchón. No hay OOM. Cada vLLM tiene 16 GB para pesos más KV-cache: suficiente para un modelo de 7B–8B en FP8/INT4 con un KV-cache modesto.

Caso que revienta. Las mismas cuatro instancias, pero alguien sube $g_i = 0.30$ pensando “tengo cuatro réplicas, puedo darle más a cada una”:

$$\sum_{i=1}^{4} 0.30 = 1.20 \quad\Rightarrow\quad 1.20 \times 80\ \text{GB} = 96\ \text{GB} > 80\ \text{GB} \quad\times$$

Las primeras instancias arrancan y reservan $0.30 \times 80 = 24$ GB cada una. Tres instancias ya van por $72$ GB. La cuarta intenta reservar otros 24 GB, no quedan, y muere con CUDA out of memory. Y lo peor: Kubernetes la reprogramará sobre la misma GPU (sigue viendo cuatro réplicas), donde volverá a morir, en un CrashLoopBackOff que no se explica mirando solo el manifiesto del pod.

La regla operativa es brutalmente simple: en time-slicing, el presupuesto de VRAM lo gestionas tú a mano, sumando los --gpu-memory-utilization. El número de réplicas controla cuántos pods caben por turnos de cómputo, pero no reserva ni un byte de memoria. Confundir las dos cosas es el error recurrente.

MPS: cocineros coordinados en la misma encimera

El Multi-Process Service (MPS) ataca un problema distinto. Por defecto, cuando varios procesos usan la misma GPU sin MPS, cada uno tiene su propio contexto CUDA, y la GPU alterna entre contextos (time-slicing a nivel de driver): no ejecutan kernels a la vez, se turnan, con overhead de cambio de contexto. Si tus kernels son pequeños y no llenan la GPU ellos solos, esto deja SMs ociosos: el proceso A usa el 30 % de los SMs durante su turno y el otro 70 % se desperdicia.

MPS introduce un daemon que comparte un único contexto CUDA entre procesos, de modo que sus kernels pueden ejecutarse concurrentemente ocupando SMs distintos a la vez. Es reparto espacial de cómputo: en lugar de turnarse la encimera entera, cada cocinero ocupa una parte y trabajan en paralelo. Esto reduce el overhead de context-switch y sube el throughput cuando hay muchos kernels pequeños concurrentes que individualmente no saturan la GPU.

Y, a diferencia del time-slicing puro, MPS permite poner límites por proceso, lo que da una forma de QoS:

  • CUDA_MPS_ACTIVE_THREAD_PERCENTAGE limita el porcentaje de SMs que un cliente MPS puede usar. Por defecto cada cliente recibe $100 / \text{MaxSharedClients}$. Fijarlo a, digamos, 40 % cierra el techo de cómputo de ese proceso (docs MPS).
  • CUDA_MPS_PINNED_DEVICE_MEM_LIMIT impone un tope de memoria por cliente (válido desde CUDA 11.5). Esto es lo que el time-slicing no tiene: un límite de VRAM por proceso que el runtime hace cumplir.

Estos dos límites convierten a MPS en un mecanismo de provisioning de recursos que mitiga el noisy neighbor: puedes garantizar que un proceso no se coma más del X % de SMs ni más de Y GB. La combinación da una QoS razonable —no perfecta, pero real.

La limitación que MPS no resuelve: el aislamiento de fallos es débil. Como los clientes comparten el contexto CUDA del daemon, un error fatal en un cliente puede afectar al daemon y, por tanto, a los demás clientes (históricamente, un cliente que muere de forma sucia podía requerir reiniciar el daemon). Es mejor que el time-slicing en este aspecto, pero está lejos del aislamiento por hardware. Por eso MPS encaja cuando hay confianza entre las cargas —procesos de tu propio equipo, no tenants ajenos.

El caso de uso canónico: muchas peticiones de inferencia pequeñas y concurrentes que individualmente dejan la GPU medio vacía. MPS las solapa y sube el throughput agregado. Servir varios modelos pequeños o varias réplicas ligeras del mismo modelo en una GPU de datacenter, donde confías en todas las cargas, es territorio MPS.

MIG: paredes de hormigón

El Multi-Instance GPU (MIG) es el único de los tres que da aislamiento de verdad, porque corta la GPU en hardware. Disponible en las GPU de datacenter modernas —A100 (Ampere), H100/H200 (Hopper), B200 (Blackwell)— y nunca en las de consumo: una RTX 5090 (Blackwell de consumo) no soporta MIG, igual que las GeForce en general.

MIG divide la GPU en hasta siete instancias (GPU Instances), y cada instancia recibe una porción dedicada de:

  • SMs (compute slices): el cómputo se reparte en 7 slices, cada uno ~1/7 de los SMs.
  • L2 cache y memoria: cada instancia tiene su trozo de HBM y su porción de caché L2.
  • Ancho de banda de memoria: dedicado, no compartido.
  • Caminos de datos y motores: con barreras de fallo entre instancias.

El resultado es que una instancia MIG se comporta como una GPU más pequeña e independiente: lo que pase en una —un OOM, un kernel que peta, una carga que satura su cómputo— no afecta a las vecinas. Aislamiento de memoria, de fallo y de rendimiento (QoS), las tres cosas que el time-slicing no da y que MPS solo da a medias.

Los perfiles de la H100 80GB

MIG no permite tamaños arbitrarios: tiene perfiles fijos. En la H100 80GB, el catálogo de perfiles (notación <compute>g.<memoria>gb) es:

PerfilCompute (slices)MemoriaInstancias máx.
1g.10gb1/710 GB7
1g.20gb1/720 GB4
2g.20gb2/720 GB3
3g.40gb3/740 GB2
4g.40gb4/740 GB1
7g.80gb7/780 GB1 (GPU entera)

(Existe además 1g.10gb+me, una variante con media engines para codificación de vídeo.) La unidad base de memoria en la H100 80GB es de 10 GB por slice (80 GB / 8, con un slice reservado), y la de cómputo es 1/7 de los SMs. Los perfiles combinan estas unidades. Fíjate en 1g.20gb: misma fracción de cómputo que 1g.10gb (1/7 de SMs) pero el doble de memoria —útil cuando una carga necesita más VRAM que cómputo.

Un detalle importante: las particiones MIG no se mezclan libremente. La GPU se divide siguiendo una geometría válida (los perfiles encajan como piezas en una rejilla), y los perfiles se fijan al configurar la GPU; cambiarlos requiere drenar y reparticionar. Son paredes de hormigón: sólidas, pero no se mueven en caliente.

El cálculo: 7×1g.10gb frente a 1×7g.80gb

Comparemos los dos extremos. A la izquierda, siete instancias 1g.10gb: siete GPU aisladas de 10 GB cada una. A la derecha, una sola 7g.80gb: la H100 entera sin particionar.

La pregunta operativa es qué cabe en 10 GB. El presupuesto de VRAM de una instancia se reparte entre pesos del modelo y KV-cache:

$$V_{\text{inst}} = V_{\text{pesos}} + V_{\text{KV}} + V_{\text{overhead}}$$

Tomemos un modelo de 7B parámetros en FP8 (1 byte/parámetro):

$$V_{\text{pesos}} \approx 7 \times 10^9 \times 1\ \text{byte} = 7\ \text{GB}$$

En una instancia 1g.10gb (10 GB), tras los 7 GB de pesos y restando ~0.5–1 GB de overhead del runtime, quedan ~2 GB para KV-cache. Eso da para una ventana de contexto modesta y poca concurrencia —correcto para un servicio guardrail, un clasificador o un modelo de extracción que procesa prompts cortos uno a uno. Un 7B en INT4 (~3.5 GB de pesos) deja ~5.5 GB de KV-cache, mucho más holgado. Pero un modelo de 13B en FP8 (~13 GB de pesos) no cabe en una instancia de 10 GB: ni siquiera entran los pesos. Para él necesitas 1g.20gb, 2g.20gb o mayor.

Frente a esto, la 7g.80gb (GPU entera) te da los 80 GB para un modelo grande: un 70B en FP8 (~70 GB de pesos) cabe con KV-cache justo, o un 70B con más holgura repartido en tensor-parallel sobre varias GPU enteras (ver TP frente a réplicas: una grande contra N pequeñas).

La lectura es clara: particionar fino (7×1g.10gb) maximiza el número de cargas aisladas pequeñas; no particionar (1×7g.80gb) maximiza el tamaño del modelo que cabe. El KV-cache disponible por instancia se reduce proporcionalmente al particionar, así que MIG fino sirve para muchos servicios ligeros aislados, no para un modelo grande troceado. Si tu carga es un solo modelo grande, MIG no es para ti —usa la GPU entera o varias en TP.

El árbol de decisión

Las tres preguntas, en orden:

¿Necesitas aislamiento REAL?
(multi-tenant, compliance, fallo de una carga no debe tocar otra)
        │
   ┌────┴────┐
  SÍ        NO
   │         │
¿Es Hopper/  ¿Muchos kernels PEQUEÑOS concurrentes
 Ampere/      Y confías en todas las cargas?
 Blackwell?         │
   │           ┌────┴────┐
┌──┴──┐       SÍ        NO
SÍ   NO        │         │
│     │       MPS     ¿Dev / ráfagas / GPU de consumo
MIG   │     (espacial,  / sin necesidad de aislar?
│     │      QoS por        │
│   no hay   proceso)      SÍ
│   aislam.                 │
│   real:               TIME-SLICING
│   replantea          (temporal, barato,
│   (mueve a            funciona en 5090)
│   CPU, otra GPU,
│   o asume riesgo
│   con time-slicing)

Y en una frase cada rama:

  • MIG cuando el aislamiento es un requisito (compliance, multi-tenant, SLA duro) y tienes hardware de datacenter que lo soporta. Las paredes de hormigón cuestan, pero si las necesitas no hay sustituto.
  • MPS cuando tienes muchas cargas pequeñas concurrentes que dejan la GPU medio vacía y confías en todas (mismo equipo, no tenants ajenos). Subes throughput con QoS razonable, asumiendo aislamiento de fallos imperfecto.
  • Time-slicing cuando es dev, ráfagas, baja utilización, GPU de consumo, o simplemente no necesitas aislar nada. Barato y universal, pero gestiona el presupuesto de VRAM a mano.

Un matiz que la documentación reciente recoge: se pueden combinar. Puedes hacer time-slicing sobre una instancia MIG (aislamiento de hardware en la frontera de la instancia, turnos de software dentro), o usar MPS dentro de una instancia MIG. Las capas no son excluyentes; el árbol elige la estrategia primaria.

Aplicado al cluster genérico 4×H100

Bajemos a números con un cluster on-premise genérico de 4×H100 SXM 80 GB con NVLink. Es habitual tener un menú de cargas heterogéneo: un modelo grande de chat, servicios ligeros (embeddings, reranker, guardrails) y una cola de dev/experimentación. Cada tipo pide un mecanismo distinto. Un reparto razonado:

GPU 0 y GPU 1 — modelo grande en tensor-parallel (sin compartir). Un modelo de 70B en FP8 ocupa ~70 GB de pesos; servido cómodo con KV-cache generoso necesita más de una H100. Lo repartimos en tensor-parallel sobre dos H100 enteras unidas por NVLink (el ancho de banda intra-nodo es lo que hace viable el TP; el detalle está en TP frente a réplicas). Aquí no compartimos: estas dos GPU son del modelo grande y punto. Aislamiento total por dedicación.

$$V_{\text{disponible}} = 2 \times 80 = 160\ \text{GB};\quad V_{\text{pesos}} \approx 70\ \text{GB};\quad V_{\text{KV}} \approx 80\ \text{GB de KV-cache}$$

Sobra memoria para una cola de peticiones larga y mucha concurrencia.

GPU 2 — partida con MIG en instancias pequeñas para servicios ligeros. Embeddings (bge-m3), reranker (bge-reranker-v2-m3) y un par de modelos guardrail (1B–3B) son cargas distintas, de equipos potencialmente distintos, y quieres que un fallo o un pico en una no toque a las demás. Multi-tenant ligero con aislamiento → MIG. Una partición razonable de la H100:

$$\underbrace{3 \times \texttt{1g.10gb}}{\text{30 GB, 3/7 SMs}} ;+; \underbrace{1 \times \texttt{4g.40gb}}{\text{40 GB, 4/7 SMs}}$$

Las tres 1g.10gb (10 GB, 1/7 SMs cada una) alojan embeddings, reranker y un guardrail 1B INT4 —cada uno aislado, sin noisy neighbor. La 4g.40gb (40 GB, 4/7 SMs) aloja un modelo intermedio de 7B–13B con KV-cache decente para un servicio de apoyo. Cada servicio tiene su despensa y su pared; si el reranker peta, el chat no se entera.

GPU 3 — time-slicing para dev y ráfagas. Los desarrolladores tocan la GPU esporádicamente: experimentos, fine-tunes cortos, pruebas de modelos. No necesitan aislamiento (es el mismo equipo) y rara vez coinciden activos. La anunciamos como 4 réplicas vía device-plugin. Cuatro pods de dev caben por turnos. Presupuesto de VRAM con la fórmula de arriba: si cada dev levanta un vLLM a --gpu-memory-utilization 0.20, suman $4 \times 16 = 64$ GB < 80 GB, sin OOM. Si alguien necesita más, baja el número de réplicas o coordina con el equipo —el coste de la flexibilidad es la disciplina manual.

Resumen del reparto:

RecursoMecanismoCargaAislamiento
GPU 0 + GPU 1Dedicación (TP)70B chat en tensor-paralleltotal (dedicado)
GPU 2MIG (3×1g.10gb + 1×4g.40gb)embeddings, reranker, guardrails, 7B–13Bhardware real
GPU 3Time-slicing (4 réplicas)dev, ráfagas, experimentosninguno (confianza)

La lógica es siempre la misma: gasta aislamiento (MIG) donde lo necesitas, gasta concurrencia barata (time-slicing) donde no, y reserva las GPU enteras para lo que de verdad las llena. Una H100 sirviendo embeddings con 7g.80gb sería tan absurdo como una RTX 5090 intentando MIG: la herramienta no encaja con la carga.

Lo que no hemos cubierto

  • Qué pasa cuando ni siquiera caben a la vez: si tienes más modelos que VRAM y hay que turnarlos en memoria (cargar/descargar pesos, no solo turnar cómputo), entras en territorio de swap y sleep —la pieza hermana Servir varios modelos en una GPU.
  • Scheduling NUMA-aware: en nodos multi-socket, qué GPU toca a qué CPU/memoria importa para la latencia; ver Kubelet resource managers en RKE2.
  • Autoscaling de las réplicas: cuántas instancias levantar según carga real, con KEDA y métricas de cola; ver Autoscaling de LLM en Kubernetes con KEDA.
  • Benchmark de jitter bajo contención: cuánto baila realmente el TTFT en time-slicing con 4 réplicas activas frente a MIG —material que merece medición propia, no estimación.

Ver también

Referencias