Únete a la beta de Hanzo y obtén 50 % de descuento en cualquier plan de pago

BitDelta: cómo servir 14 modelos Zen desde un solo cluster de GPU

Cómo la compresión de deltas a 1 bit de BitDelta nos deja servir 14 variantes de modelos Zen sobre infraestructura de GPU compartida: la matemática, la arquitectura y los tradeoffs.

En esta página

Servir variantes de un modelo con fine-tuning es, de entrada, un mal negocio.

Un modelo de 70B parámetros en BF16 necesita 140GB de memoria de GPU. Si ustedes tienen 14 variantes con fine-tuning de ese modelo — cada una representa un perfil de capacidad distinto, una especialización de dominio o un ajuste de comportamiento — la arquitectura de serving naive pide 14 cargas de modelo separadas: 1.96TB de memoria de GPU, 14 procesos de inferencia separados y 14x el costo de infraestructura. Por cada variante después de la primera, ustedes pagan el precio completo por algo que es casi puro conocimiento base duplicado.

BitDelta (arXiv:2402.10193) quiebra esa economía. La idea es elegante y la matemática es simple.

La base matemática

Llamemos W_base a la matriz de pesos del modelo base y W_ft a la matriz de pesos que le corresponde en una variante con fine-tuning. La delta de comportamiento es:

Δ = W_ft - W_base

BitDelta comprime esa delta a precisión de 1 bit:

W_ft ≈ W_base + α · sign(Δ)

Ahí α es un factor de escala por matriz, calculado como el valor absoluto promedio de Δ:

α = (1/n) · ||Δ||_1 = mean(|Δ_ij|)

La operación de signo baja cada delta escalar a un solo bit, positivo o negativo. El factor de escala α devuelve la magnitud. Combinadas, la aproximación se queda con la información de dirección de la actualización de fine-tuning a 1/32 del costo de almacenamiento de una delta en precisión total (1 bit contra 32 bits por elemento de parámetro).

¿Por qué resulta? El fine-tuning sobre un modelo preentrenado grande produce deltas que, en promedio, son pequeñas frente a los pesos base. El modelo base captura la estructura dominante del lenguaje; el fine-tuning captura cambios de comportamiento encima de esa estructura. Cuando la delta es pequeña, el signo de cada elemento es la representación con más información de ese elemento: la magnitud queda más o menos pareja en toda la matriz delta, así que α · sign(Δ) es un código eficiente.

Se puede decir con más formalidad. Si modelamos Δ_ij como una variable aleatoria de Laplace con posición 0 y escala b:

Δ_ij ~ Laplace(0, b)
|E[sign(Δ) | |Δ|]| = 1 for all nonzero Δ

El signo preserva la información de dirección por completo. La pérdida de información está solo en la variación de magnitud, y el único factor de escala α la recupera en parte. En las deltas de fine-tuning donde la variación de magnitud es baja (cambio de comportamiento, no transferencia de conocimiento), la aproximación queda ajustada.

Reducción de memoria: en un modelo de 70B en BF16, los pesos base ocupan 140GB. La delta de cada variante con fine-tuning, en BF16 completo, sumaría otros 140GB. En formato BitDelta, cada delta ocupa aproximadamente 8.75GB (70B bits = 8.75GB, más los escalares α, que casi no pesan). El cluster de 14 variantes baja de 14 × 140GB = 1.96TB a 140GB + 14 × 8.75GB ≈ 262GB, una reducción del 87%.

Arquitectura de serving

┌─────────────────────────────────────────────────────────────────┐
│                    GPU CLUSTER (8x H100, 80GB each)             │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              BASE WEIGHTS (140GB, HBM resident)          │   │
│  │  W_base for zen4-70b: attention, FFN, embeddings, norms  │   │
│  └──────────────────────────────┬───────────────────────────┘   │
│                                 │ shared read                    │
│  ┌──────────────────────────────▼───────────────────────────┐   │
│  │           DELTA CACHE (≈8.75GB per variant)              │   │
│  │  zen4-coder:    α_coder,    sign(Δ_coder)    [8.75GB]    │   │
│  │  zen4-ultra:    α_ultra,    sign(Δ_ultra)    [8.75GB]    │   │
│  │  zen4-math:     α_math,     sign(Δ_math)     [8.75GB]    │   │
│  │  zen4-writer:   α_writer,   sign(Δ_writer)   [8.75GB]    │   │
│  │  zen4-legal:    α_legal,    sign(Δ_legal)    [8.75GB]    │   │
│  │  zen4-medical:  α_medical,  sign(Δ_medical)  [8.75GB]    │   │
│  │  ...14 total...                                           │   │
│  └──────────────────────────────┬───────────────────────────┘   │
│                                 │                                │
│  ┌──────────────────────────────▼───────────────────────────┐   │
│  │              REQUEST ROUTER                               │   │
│  │  model_id → delta_load → reconstruct W_ft → inference    │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Al momento de la inferencia, el pipeline de serving es este:

  1. La solicitud llega con model: "zen4-coder"
  2. El router ubica la entrada del cache de deltas de zen4-coder
  3. El peso efectivo se reconstruye sobre la marcha: W_eff = W_base + α_coder · sign(Δ_coder)
  4. Se corre el forward pass con los pesos reconstruidos
  5. Se devuelve la respuesta

El paso 3 es el camino crítico. La reconstrucción es un kernel CUDA fused que lee al mismo tiempo los pesos base y los bits de la delta, y arma el peso efectivo en registros o en memoria compartida sin materializar el W_eff completo en HBM. La reconstrucción y la multiplicación de matrices se pueden fundir en un solo pase del kernel: el peso reconstruido nunca llega a existir entero como tensor independiente.

# Simplified reconstruction kernel (conceptual)
def fused_bitdelta_linear(
    x: torch.Tensor,          # input activations [batch, seq, d_in]
    w_base: torch.Tensor,     # base weight [d_out, d_in] in HBM
    delta_sign: torch.Tensor, # 1-bit delta [d_out, d_in] packed as int8
    alpha: torch.Tensor,      # scale factor [d_out] or scalar
) -> torch.Tensor:
    # In practice: single CUDA kernel, no intermediate w_eff allocation
    # Conceptually equivalent to:
    delta_unpacked = unpack_bits(delta_sign).to(w_base.dtype)  # {-1, +1}
    w_eff = w_base + alpha.unsqueeze(1) * delta_unpacked
    return torch.nn.functional.linear(x, w_eff)

Con el kernel fused, el consumo pico de ancho de banda de HBM se queda en aproximadamente 1.5x los pesos del modelo base, lejos de 2x, porque la delta se lee en formato de bits empaquetados y se desempaca en SRAM rápida durante la operación fused.

Perfil de latencia

En la práctica, en el cluster de serving Zen de 14 variantes:

VarianteP50 TTFTP99 TTFTvs. baseline
zen4 (base)18ms42ms1.0x
zen4-coder21ms48ms1.17x
zen4-ultra22ms51ms1.22x
zen4-math21ms47ms1.17x
zen4-writer20ms45ms1.11x

El overhead de tiempo hasta el primer token por la reconstrucción BitDelta es de 2-4ms, despreciable frente al costo de prefill para largos de prompt típicos. El kernel de reconstrucción es compute-bound, no memory-bound, así que escala bien con la generación en la GPU.

El throughput de generación (tokens/segundo) es idéntico entre el modelo base y cualquier variante BitDelta, porque la reconstrucción solo ocurre en la fase de prefill. Una vez que el cache KV está poblado, el paso de generación autorregresiva usa los caminos matmul estándar.

Calidad: qué se pierde en la compresión de 1 bit

La respuesta honesta: poco, y bien acotado.

La caída de calidad de BitDelta depende de la tarea. En tareas bien representadas en los datos de fine-tuning, la delta de comportamiento es fuerte: el signo captura de manera confiable la dirección dominante del gradiente. En tareas al borde de la distribución de fine-tuning, la delta es más débil: la aproximación mete más ruido respecto de la señal.

En los hechos, sobre modelos Zen evaluados en sus suites de tareas objetivo:

ModeloPrecisión totalBitDeltaDelta
zen4-coder (HumanEval)72.4%71.1%-1.3%
zen4-math (MATH-500)68.9%67.8%-1.1%
zen4-writer (WritingBench)84.2%83.7%-0.5%
zen4-legal (LegalBench)61.3%60.1%-1.2%

La degradación de 1-1.3% en las suites de tareas primarias es el precio de una reducción de memoria del 87%. Para todas las variantes Zen, este tradeoff se acepta: la diferencia de calidad queda debajo del umbral de un cambio de comportamiento que el usuario note en los casos de uso típicos.

MonoSoup: complemento SVD de un solo checkpoint

MonoSoup (arXiv:2602.09689) ataca un problema que complementa al anterior: cuando hay varios checkpoints con fine-tuning del mismo modelo base, ¿cómo se hace merge de todos en un solo checkpoint sin cargarlos al mismo tiempo?

MonoSoup aplica SVD al vector de tarea (Δ = W_ft - W_base) para hallar los componentes de bajo rango que guardan la mayor información de comportamiento, y después los agrega entre checkpoints:

For checkpoints {W_1, W_2, ..., W_k}:
  Δ_i = W_i - W_base
  [U_i, S_i, Vh_i] = truncated_SVD(Δ_i, rank=r)
  τ_i = U_i · diag(S_i) · Vh_i  # rank-r approximation

MonoSoup merge:
  W_merged = W_base + (1/k) · Σ τ_i

El truncado SVD en el rango r funciona como un paso de denoising: el fine-tuning tiende a modificar un modelo a lo largo de pocas direcciones de alta varianza en el espacio de pesos. Capturar esas direcciones con SVD truncada y mergearlas toma la esencia de comportamiento de cada checkpoint, sin el ruido de las direcciones con valores singulares más bajos.

Usamos MonoSoup en las corridas de entrenamiento de Zen como paso de promedio de checkpoints: cada 1000 pasos de gradiente, mergeamos el checkpoint actual con el mejor previo mediante MonoSoup, en vez del promedio naive. Eso baja el ruido de entrenamiento y deja curvas de loss más suaves, con mejor calidad final.

K-Merge: manejo en línea de adapters LoRA para el edge

K-Merge (arXiv:2510.13537) resuelve otra forma del problema de serving: en el edge, donde el almacenamiento está limitado a un presupuesto fijo de B gigabytes, ¿cómo se sirven K adapters LoRA de manera eficiente?

K-Merge mantiene un pool de adapters mergeados con la restricción de que el almacenamiento total nunca supere B. Cuando se pide un adapter nuevo que no está en el pool:

  1. Se calcula la similitud por pares entre el adapter nuevo y todos los miembros del pool (similitud de coseno sobre los vectores de tarea)
  2. Se identifica el adapter existente más parecido
  3. Se mergea el adapter nuevo dentro del existente más parecido vía OPCM (merge por proyección ortogonal)
  4. Si la calidad del adapter mergeado en ambas tareas supera el umbral, se desaloja el original y se guarda la versión mergeada

La política garantiza que el pool tiene a lo sumo K adapters que consumen el presupuesto B, y al mismo tiempo maximiza la cobertura de todos los adapters pedidos mediante un merging con criterio.

Para el despliegue de Zen en el edge (Hanzo Desktop, celular), K-Merge habilita familias de modelos servidas en el dispositivo que, de otro modo, se pasarían de los límites de almacenamiento. Un dispositivo con 16GB de almacenamiento de modelo disponible puede servir 8 perfiles de comportamiento distintos si mantiene un pool K-Merge que mergea adapters parecidos bajo demanda.

Detalles de infraestructura

Formato de almacenamiento de la delta: matrices de signo de 1 bit guardadas como tensores int8 empaquetados (8 bits por byte, así que una capa de 70B parámetros con un peso de 4096×4096 se empaca en 4096×512 int8). Los factores de escala α se guardan como float32, uno por dimensión de salida, y suman aproximadamente el 0.1% del almacenamiento de la delta.

Comportamiento de cold start: cuando la delta de una variante no está en el cache de la SRAM de la GPU (por ejemplo, nadie pidió la variante en 10 minutos), la primera solicitud paga una penalización de latencia de carga de la delta de aproximadamente 200ms en un modelo de 70B, el tiempo para pasar 8.75GB de NVMe a HBM a la velocidad de lectura secuencial de NVMe. Después de esa primera solicitud, la delta se queda en el cache de deltas de la SRAM de la GPU hasta que la política LRU la desaloja.

Política de eviction del cache: LRU con peso por tasa de solicitudes. Las variantes con tasa alta quedan pineadas; las de poco tráfico pueden salir. En la práctica, las 6 variantes top por volumen de tráfico viven siempre en el cache; las 8 restantes comparten el espacio de cache que sobra, con eviction LRU.

Ruteo de solicitudes: el router de serving rastrea las solicitudes en vuelo por variante y evita el bloqueo head-of-line: si la delta de zen4-coder se está cargando, las solicitudes entrantes de zen4-coder hacen fila, en vez de que cada una dispare una carga aparte.

El resultado: 14 variantes de modelos Zen servidas desde nodos 8x H100 que antes habrían pedido nodos 112x H100 para la misma capacidad. La reducción del costo de infraestructura no es incremental. BitDelta vuelve viable, en lo económico, a la familia de modelos multi-tenant.


Los modelos Zen están en zenlm.org. El acceso a la API, en hanzo.ai.

Comentarios

Ingresa con tu Hanzo ID para comentar.

Cargando comentarios…

Leer más