Cuando el proceso de un motor de inferencia cae, la ruta de recuperación estándar implica un reinicio en frío. Eso significa cargar los pesos desde el almacenamiento hacia la HBM, compilar los kernels y capturar los grafos de CUDA. Para modelos grandes, la inicialización puede tomar varios minutos, y durante ese lapso los trabajadores que sobrevivieron deben absorber todo el tráfico desplazado.

El motor sombra (shadow engine recovery), disponible como función en vista previa en NVIDIA Dynamo, saca casi todo ese trabajo de la ruta de servicio. Mantiene un motor completamente inicializado y ocioso sobre las mismas GPU que el motor activo. El GPU Memory Service (GMS) comparte los pesos ya cargados entre ambos motores sin crear otra copia en HBM. Si el proceso activo falla, la sombra toma el control en segundos, y la reinicialización ocurre en segundo plano.

¿Cuánto se gana realmente?

NVIDIA midió el impacto terminando deliberadamente un trabajador en un despliegue de GLM-5.2 con dos trabajadores sobre nodos B200.

EscenarioTiempo hasta reanudar el servicio
Reinicio en frío (sin motor sombra)283 segundos
Motor sombra7,3 segundos

Sin el motor sombra, el trabajador restante atendió todo el tráfico entrante durante los 283 segundos del reinicio, lo que elevó el tiempo hasta el primer token (TTFT) y redujo la tasa de decodificación por usuario durante toda la interrupción. Con el motor sombra, un segundo trabajador volvió a servir en 7,3 segundos, casi 39 veces más rápido.

Figura 1. Tiempo hasta que un segundo trabajador reanuda el servicio después de que uno de dos falla. El reinicio en frío recarga pesos, dimensiona la caché KV, autoajusta y recaptura los grafos de CUDA
Figura 1. Tiempo hasta que un segundo trabajador reanuda el servicio después de que uno de dos falla. El reinicio en frío recarga pesos, dimensiona la caché KV, autoajusta y recaptura los grafos de CUDA

¿Por qué la recuperación es tan lenta?

Los motores de inferencia en producción sufren con frecuencia fallas de software recuperables: caídas de proceso, errores de CUDA recuperables y fallas transitorias de las colectivas. En esos casos el hardware, los drivers y el nodo siguen sanos, y solo se pierde el proceso que sostenía el estado corrupto. Entonces, ¿por qué el motor nuevo no puede saltarse el costo de inicialización? Hay dos obstáculos.

  • Los pesos están atados al proceso del motor. La memoria de GPU está ligada al contexto CUDA del motor, que a su vez depende de su proceso. Cuando el proceso termina, el driver libera todos los recursos, incluidos los pesos ya residentes en memoria. El motor de reemplazo tiene que repetir el procedimiento completo de carga.
  • Algunos estados de inicialización no son transferibles. Los comunicadores de NCCL y torch.distributed se enlazan al proceso específico en ejecución, y los grafos de CUDA quedan fijados a las direcciones virtuales presentes al momento de la captura. Nada de eso se puede heredar del motor anterior: hay que recrearlo en cada reinicio.

El motor sombra ataca cada problema por separado. Desacopla la vida útil de los pesos del proceso del motor, y completa la inicialización no transferible antes de que ocurra la falla.

¿Cómo funciona el GPU Memory Service?

El GMS administra regiones específicas de memoria, como los pesos, de forma independiente del proceso del motor. Al usar un proceso distinto para ser dueño de esas regiones, los pesos permanecen residentes en memoria aunque los motores se reinicien, y un motor nuevo sobre la misma GPU puede engancharse a la memoria existente.

Es un sidecar por GPU que en su mayor parte permanece inactivo y que no tiene contexto CUDA propio. Asigna páginas físicas, entrega manejadores y arbitra qué motores pueden leer o escribir en cada momento. Los motores se conectan, importan los manejadores y mapean las páginas subyacentes en direcciones virtuales dentro de sus propios contextos. El mapeo ocurre una sola vez, al inicio, y el GMS no participa en ningún acceso posterior.

La pieza está construida sobre la API de gestión de memoria virtual de CUDA. Con esa API, la memoria física de GPU y sus direcciones virtuales pueden tener vidas independientes. Como las asignaciones físicas llevan conteo de referencias, sobreviven mientras algún proceso mantenga un mapeo. Dos motores que mapean el mismo tensor de pesos acceden a los mismos bytes físicos, cada uno con direcciones virtuales locales a su contexto. Un kernel que lee un peso desreferencia un puntero corriente hacia la misma HBM que el peso habría ocupado de todos modos, así que una lectura respaldada por GMS no cuesta más que una asignada por el motor.

Figura 2. Cada motor mapea los pesos en su propio espacio de direcciones, pero hay una sola copia física en HBM
Figura 2. Cada motor mapea los pesos en su propio espacio de direcciones, pero hay una sola copia física en HBM

De ahí salen dos beneficios. Primero, los pesos persisten más allá de la falla del motor: aunque el kernel elimine el contexto CUDA del motor caído, la referencia del GMS mantiene las páginas físicas residentes. Segundo, los pesos se pueden compartir entre motores concurrentes, así que un motor secundario en la misma GPU tiene costo marginal cero en memoria de pesos.

Integrar el GMS en los frameworks de inferencia requiere un cambio acotado. vLLM, SGLang y NVIDIA TensorRT-LLM lo integran mediante un torch.cuda.CUDAPluggableAllocator personalizado, enlazado al pool de memoria de pesos. Desde adentro del motor, los pesos siguen siendo torch.Tensor corrientes, y adoptar GMS es cuestión de activar una bandera al arranque.

¿Qué guarda y qué posterga la sombra?

Un motor sombra es un proceso completamente inicializado que permanece ocioso y corresidente en las mismas GPU que el activo. Recorre la misma ruta de arranque que un motor normal: se conecta al GMS local, importa los mapeos de pesos, establece comunicadores (NCCL y NIXL para transferencia de caché KV entre trabajadores), captura los grafos de CUDA y hace el calentamiento necesario. Al terminar está listo para servir. Pero en vez de servir, se estaciona: libera las partes materializables de su memoria y se bloquea, esperando su turno.

Lo que tiene precomputado antes de estacionarse es el contexto CUDA, los grafos capturados, los comunicadores y los mapeos de pesos. Lo que posterga es la materialización de la caché KV, que es la asignación recuperable más grande que sostiene un motor: la sombra reserva su rango de direcciones sin respaldo físico y la materializa recién cuando la promueven.

Ese consumo reducido es lo que permite que la sombra conviva con el motor activo en los mismos dispositivos.

Un solo trabajador desplegable

Figura 3. Una flota de trabajadores detrás de un único enrutador. La disposición de dos motores es interna a cada trabajador
Figura 3. Una flota de trabajadores detrás de un único enrutador. La disposición de dos motores es interna a cada trabajador

Todo esto se integra en un mismo pod. El trabajador contiene dos contenedores de motor, un sidecar de GMS que media el acceso a la memoria de GPU y un cerrojo compartido para elegir cuál motor está activo. En estado estable, un motor sostiene el cerrojo y está despierto, conectado al GMS, con la caché KV materializada y registrado en el enrutador. El otro está completamente inicializado y conectado al GMS, pero dormido, sin caché KV y esperando el cerrojo.

Como la disposición de dos motores es interna al trabajador, el enrutador, el frontend y el orquestador no necesitan ningún cambio para beneficiarse.

Las cuatro fases de una recuperación

Figura 4. Las cuatro fases de una recuperación. Los roles activo y sombra se intercambian entre el motor A y el motor B
Figura 4. Las cuatro fases de una recuperación. Los roles activo y sombra se intercambian entre el motor A y el motor B
  • T₀, estable. El motor A tiene el cerrojo y está despierto, registrado en el enrutador. El motor B está dormido, bloqueado esperando el cerrojo.
  • T₁, falla. El proceso del motor A termina, sea porque se cayó o porque una sonda de vitalidad lo encontró colgado y lo mató. En cualquier caso el kernel libera su cerrojo. El trabajador queda brevemente sin ruta hasta que la sombra se registre.
  • T₂, traspaso. El motor B toma el cerrojo, despierta, remapea los pesos a través del GMS, materializa su caché KV y se vuelve a registrar en el enrutador. El orquestador reinicia el contenedor del motor A.
  • T₃, reiniciado. El motor A termina su inicialización y entra en estado de sombra. El sistema vuelve al estado estable, con los roles invertidos.

La ventaja de la sombra es que llega a T₂ ya inicializada. Lo único que queda en la ruta crítica es tomar el cerrojo, remapear los pesos y materializar la caché KV.

¿Cómo se garantiza que solo uno sirva a la vez?

El trabajador necesita exclusión mutua y liberación confiable. Un flock de POSIX sobre un archivo compartido entrega ambas garantías: cuando el proceso activo termina por apagado, segfault o SIGKILL, el kernel recoge sus descriptores de archivo y la sombra toma el cerrojo para empezar a servir. La ruta de arranque de cada motor es, en el fondo, una elección de líder breve:

Código
await engine.initialize()  # carga de pesos, torch.compile, autoajuste, captura de grafos CUDA
...
# el motor se duerme mientras espera el cerrojo
await engine.sleep()
lock = FlockFailoverLock(lock_path)
await lock.acquire(engine_id=engine.id)  # espera el cerrojo para despertar
await engine.wake()

Un motor bloqueado cuyo proceso sigue vivo cae por la sonda de vitalidad de Kubernetes, que deriva en un SIGKILL y dispara la misma liberación gestionada por el kernel.

Qué implica para operaciones fuera de los grandes centros de datos

El detalle que vale la pena retener para quien opera inferencia con presupuesto acotado es que el costo de esta redundancia no es una GPU extra. Al compartir los pesos vía GMS, el motor de respaldo no duplica el consumo de HBM, que es el recurso escaso cuando se sirve un modelo grande. La contrapartida es un poco de memoria residual para contexto, grafos y comunicadores, y la caché KV que se materializa recién al promover. Para despliegues regionales pequeños, donde no siempre hay un nodo entero de repuesto esperando, esa aritmética importa más que en una flota con capacidad ociosa de sobra.