Aplicado a tus proyectos · vLLM (fork propio)
Aguas arriba: tres bugs que arreglaste vos, no NVIDIA ni Google
Servir Gemma-4 en una RTX 5090 te puso en la intersección de hardware nuevo (Blackwell, SM 12.x) y una arquitectura de modelo nueva (atención heterogénea por capa). En esa intersección, casi nadie más pisó estos bugs antes que vos.
0. El patrón: sos el primero en pisar esto
vLLM se testea principalmente contra GPUs de datacenter (A100, H100) con meses de uso acumulado. La
RTX 5090 es Blackwell de consumo (SM 12.0), una combinación mucho menos transitada. Y Gemma-4
introdujo algo que casi ningún modelo anterior tenía: geometría de atención distinta por capa (las capas
de ventana deslizante usan head_dim=256 con 8 cabezas KV; las de atención completa usan
head_dim=512 con una sola cabeza KV compartida). Cada uno de los tres bugs de esta página
vive exactamente en esa intersección — hardware de punta + arquitectura nueva — donde el código
existente simplemente nunca fue ejercitado.
1. FlashInfer: un error real disfrazado de otro
El síntoma: al arrancar vLLM en la RTX 5090, el sampler de FlashInfer (activado por default) tira
"FlashInfer requires GPUs with sm75 or higher" — un mensaje que no tiene sentido para una
GPU sm_120. El motor muere durante el profiling de arranque, antes de servir ni un token.
La causa real, encontrada leyendo el código de FlashInfer: su detección de arquitectura
(check_cuda_arch()) traga el error real cuando el toolkit de CUDA es
anterior al 12.9 sobre SM 12.x — deja un set de arquitecturas objetivo vacío, y ese set vacío es lo que
después dispara el mensaje genérico "sm75 or higher" en cualquier intento de compilar un kernel JIT.
El mensaje que ves no es el error — es el síntoma de un error que ya fue silenciado un paso antes.
🔧 El fix real
En vez de confiar en el mensaje genérico, el fix llama a check_cuda_arch() directamente
en el gate del sampler, y si falla, vuelve a llamar al método interno de FlashInfer que sabe derivar
la razón real (CompilationContext._normalize_cuda_arch(major, minor)) para recuperar el
mensaje verdadero — algo como "SM 12.x requires CUDA >= 12.9". Con esa razón real en
mano: si el usuario no pidió FlashInfer explícitamente, vLLM cae a sampling nativo con un warning claro;
si lo pidió a propósito (VLLM_USE_FLASHINFER_SAMPLER=1), lanza el error real — no el
genérico — para que sepa exactamente qué actualizar.
El fix viene con un test que monkey-parchea check_cuda_arch y
_normalize_cuda_arch para simular exactamente esta condición sin necesitar hardware SM12x
real en CI, y referencia el issue público (#42393) — la forma correcta de mandar un fix: reproducible, con test, y atado a un reporte real.
🟢 No fue la única vez con FlashInfer
serve_lacan_vllm.sh ya documenta otra fragilidad de la misma pareja
FlashInfer/CUDA-en-sm_120: forzar CUDA_HOME=/usr/local/cuda-13.1 explícitamente porque
"ninja recompiló el kernel FP4 con 12.8 y falló" (lección propia, 2026-08-01). Es el mismo patrón de
fondo — FlashInfer asume una alineación de versiones de CUDA que en sm_120 todavía no está bien
probada — resuelto una vez operativamente (fijando la versión) y otra vez en el código fuente
(arreglando la detección).
2. Gemma-4 en el backend de Transformers
Este es el caso más largo — cinco commits propios, con revisión real de un maintainer de vLLM
(hmellor) en el medio — y conecta directo con algo que ya viste en
la página de cuantización: la geometría de atención heterogénea
de Gemma-4 (head_dim=256 en capas de ventana, head_dim=512 en capas
completas) es la misma razón por la que tuviste que forzar el backend TRITON_ATTN al servir el modelo
cuantizado. Acá es la causa de fondo, atacada en el código en vez de sorteada en runtime.
El síntoma inicial: el backend nativo de Transformers en vLLM construía cada instancia
de atención con el head count y head size globales del config. Un modelo con geometría
homogénea nunca lo nota. Gemma-4 sí: crash inmediato en el primer forward pass, shape mismatch en
o_proj.
🔧 Dos bugs silenciosos que aparecieron al generalizar
El primer fix funcionó, pero hmellor pidió en review que se generalizara — el código
tenía hardcodeados los nombres de atributo específicos de Gemma-4
({"head_dim", "num_key_value_heads"}), una asunción que se rompe con el próximo modelo
heterogéneo. Al generalizarlo aparecieron dos bugs que nadie había visto:
- Un
get_total_num_kv_heads()sin override propio para configs conattention_k_eq_v=True— cualquier variante de Gemma-4 con ese flag hacía queflash_attn.py,triton_attn.pyy los conectores de transferencia de KV leyeran un valor global degradado en vez del máximo real entre capas. Sin crashear — simplemente corriendo con la cabeza KV equivocada. - La detección de layout viejo vs. nuevo del config estaba atada a la presencia de un mixin que
Transformers agrega a todos los configs desde su versión 5.15 — no a si el modelo
realmente usa el layout nuevo. Resultado: cualquier checkpoint publicado de Gemma-4 (que todavía
usa el layout viejo) se enviaba por el camino nuevo, donde silenciosamente devolvía
head_dim=256para capas que necesitaban 512.
Ambos son la misma clase de bug que ya viste en la página de cuantización: no truenan, simplemente producen el número equivocado. La diferencia es que acá el "número equivocado" es la geometría misma del modelo, no un peso cuantizado.
El mismo trabajo destapó, de yapa, una regresión de performance real: resolver la geometría por capa de la forma ingenua agregaba 8.3 segundos al arranque de un modelo de 48 capas — indexar por tipo de capa copiaba y comparaba cada config de ese tipo en cada llamada. Resuelto indexando por posición en vez de por tipo: 350 milisegundos.
El cierre de la cadena es tan prolijo como el resto: un commit final borra por completo un flag de
compatibilidad temporal (allow_global_per_layer_attribute_access) después de instrumentar
un arranque completo de Gemma-4 y confirmar que quedaba exactamente un lugar que
todavía lo necesitaba — y arreglar ese lugar en vez de mantener el flag indefinidamente.
3. NVFP4 en SM12x: cuando el bug no truena
El tercer caso es el más inquietante de los tres, precisamente porque no hay ningún error en pantalla.
En SM 12.x, el kernel NVFP4 que vLLM auto-selecciona por default
(FlashInferCutlassNvFp4LinearKernel) corre sin quejarse — y produce basura. Medido en un
benchmark real: GSM8K 0.0 con ese kernel, contra 0.8867 con Marlin
(otra implementación de kernel NVFP4), mismo checkpoint, mismo hardware.
🔧 El fix
Excluir ambos kernels basados en CUTLASS (el auto-seleccionado y el nativo — los dos dan mal en SM12x)
de la auto-selección en esa arquitectura, para que la selección automática caiga en Marlin — que sí
es correcto ahí. Un --linear-backend explícito sigue permitiendo forzar cualquiera de
los dos para debug.
Esto es la versión más extrema de una lección que ya apareció dos veces en este curso: un log limpio, o incluso una respuesta que suena coherente, no prueba nada. Acá ni siquiera hace falta un log sospechoso — hace falta correr un eval real para descubrir que el 100% de las respuestas están mal.
4. Qué hace bueno a un fix aguas arriba
Los tres casos comparten la misma forma, y vale la pena nombrarla:
- Diagnóstico hasta la causa real, no hasta el primer síntoma — el mensaje de FlashInfer, el crash de shape, el score de GSM8K son todos síntomas; el fix ataca lo que hay debajo.
- El cambio mínimo que corrige la causa, sin tocar nada que ese diagnóstico no justifique.
- Un test que reproduce el bug sin depender de tener el hardware exacto a mano (el monkeypatch de FlashInfer corre en cualquier CI).
- Atado a un issue o número de PR real, nunca "arreglado porque sí".
- Abierto a la revisión — el caso de Gemma-4 es mejor código después de que
hmellorpidió generalizarlo, no a pesar de eso.
5. Resumen
Tres bugs, un mismo origen: hardware de consumo de última generación corriendo una arquitectura de modelo con una geometría que el código existente nunca anticipó. Ninguno de los tres se resolvió "esperando a que NVIDIA o Google lo arreglaran" — se diagnosticaron y se mandaron aguas arriba, con tests, con referencia al issue real, y en el caso más grande, iterando sobre revisión real de un maintainer del proyecto.