Aplicado a tus proyectos · Pipeline-cloud
Tu pipeline NVFP4: meter 62 GB de modelo en 24 GB de laptop
El Capítulo 1 te dio la teoría de arquitecturas e inferencia en abstracto. Esto es esa misma teoría, más el pedazo que el curso oficial no cubre — cuantización — mirando directamente lo que ya corre en tu RTX 5090 y en "La Bestia".
0. Qué construiste
main_nvfp4.py orquesta un pipeline de 5 etapas (parser LLM → generación de imagen → video →
narración → edición final), todo corriendo con pesos en NVFP4 para caber en hardware Blackwell. Pero la
pieza que más vale la pena entender a fondo — porque la reusás en otro proyecto tuyo,
interpretante-lacaniano — es cómo se produce el
checkpoint cuantizado de Gemma-4 en sí. Esa parte vive en
gemma4_quant_strategy.md y los scripts bootstrap_gemma4_qat.sh /
bootstrap_gemma4_finetuning.sh.
Tu pipeline produce tres checkpoints, cada uno con su propio origen:
| Checkpoint | Viene de |
|---|---|
gemma-4-31B-it-qat-NVFP4-Blackwell | Modelo base de Google, sin fine-tune |
gemma-4-31B-it-qat-assistant-NVFP4-Blackwell | El draft MTP del modelo base |
gemma-4-31B-analyst-NVFP4-Blackwell | Tu fine-tune propio (LoRA con Unsloth, mergeado a FP16 antes de cuantizar) |
🔧 En tu proyecto
El mismo pipeline de cuantización es compartido entre Pipeline-cloud y
interpretante-lacaniano — así se produjo también gemma-4-31B-lacan-NVFP4-Blackwell, el
"cuerpo" del sistema lacaniano. No son proyectos separados a nivel de infraestructura de modelos: es
una sola fábrica de checkpoints con distintos consumidores.
1. La arquitectura: Gemma-4 + su asistente MTP
Del Capítulo 1 ya sabés que Gemma es decoder-only. Tu documentación agrega el detalle real: Gemma-4 es
densa (no MoE, no Mamba — a diferencia de lo que asumía un borrador viejo de tu propia
estrategia, corregido en el commit 0b8b647), con atención local/global estilo Gemma-3.
Lo que el Capítulo 1 no menciona porque es demasiado reciente: al lado del modelo grande vive un segundo
modelo, de apenas 0.5B de parámetros, dedicado exclusivamente a predecir por
adelantado los próximos tokens — Gemma4AssistantForCausalLM. No es un modelo separado
que "adivina" por su cuenta: consume
directamente los shared_kv_states (los mismos estados de atención full_attention /
sliding_attention) que ya calculó el modelo grande. Es, literalmente, una segunda cabeza de
predicción pegada al mismo backbone — por eso se llama MTP, Multi-Token Prediction.
🔧 En tu proyecto
serve_lacan_vllm.sh carga los dos a la vez: MODEL_DIR (el cuerpo, 31B) y
DRAFT_DIR (el asistente MTP). Verlos como "un modelo" es el error más fácil de cometer acá
— son dos checkpoints con reglas de cuantización distintas, como vas a ver en la sección 3.
2. Qué es realmente "NVFP4"
El Capítulo 1 nunca explica cuantización porque el curso original de HF asume que el modelo entra en memoria tal cual. Tu laptop te obligó a resolver este problema de verdad: Gemma-4 31B en BF16 pesa ~62 GB, y tu RTX 5090 tiene 24 GB de VRAM.
La idea de fondo de cualquier cuantización es simple: en vez de guardar cada peso del modelo con 16 bits de precisión (BF16), lo guardás con muchos menos bits, aceptando algo de error a cambio de un modelo drásticamente más chico. NVFP4 es el formato de 4 bits que NVIDIA diseñó específicamente para que las GPU Blackwell (tu RTX 5090, y la RTX 6000 de "La Bestia") lo puedan multiplicar en hardware sin pasar por un paso de conversión lento.
E2M1
Cada peso individual se guarda en 4 bits: 1 de signo, 2 de exponente, 1 de mantisa. Muy pocos valores representables — por sí solo sería demasiado impreciso.
Bloques de 16
Los pesos no se cuantizan uno por uno: se agrupan de a 16, y ese bloque comparte una única escala.
Escala E4M3 por bloque
Esa escala compartida sí se guarda con más precisión (FP8, 4 bits de exponente + 3 de mantisa) — es lo que le devuelve rango dinámico al bloque entero.
La parte interesante — y la que tu documentación registra con más cuidado que cualquier paper — es cómo se elige esa escala por bloque. No es un detalle menor: es la diferencia entre un modelo que sirve y uno que no.
| Algoritmo | Cómo elige la escala | En tu pipeline |
|---|---|---|
| Max / absmax | El valor más grande del bloque define la escala — un solo outlier aplasta el resto del bloque hacia cero | Lo que usabas antes |
MSE (nvfp4_mse) | Prueba distintas escalas y elige la que minimiza el error de reconstrucción del bloque | Lo que usás ahora — adoptado de la receta de NVIDIA para Nemotron 3 Super |
| Four-over-six | Cada bloque elige entre dos valores máximos posibles (4 o 6), el que minimice error | Evaluado, no adoptado — necesita ModelOpt ≥0.46 (aún en dev) |
🔧 En tu proyecto
El cambio de --qformat nvfp4 a --qformat nvfp4_mse en
hf_ptq.py fue la única línea que tocaste para este upgrade — nada de código propio, solo
un flag de ModelOpt. Costo real medido: calibración de ~3 min (absmax) a ~7 min (MSE) para el 31B en la
RTX 6000, porque MSE además barre escalas candidatas de activación en FP8. Diferencia despreciable
frente al tiempo de descarga/subida del checkpoint.
3. Por qué no se cuantiza todo
Si cuantizaras literalmente cada matriz del modelo, algunas partes se rompen mucho más fácil que otras. Tu pipeline excluye tres tipos de capas, cada una por una razón distinta:
- Embeddings (
embed_tokens) ylm_head: excluidas por default en ModelOpt en cualquier preset NVFP4 — son las capas que traducen entre tokens discretos y espacio vectorial, y son especialmente sensibles a la pérdida de precisión. pre_projection/post_projectiondel asistente MTP: el "empalme" entre el backbone grande y el draft chico. Necesitaste un monkeypatch propio (patch_modelopt.py) para que la calibración corra conshared_kv_statessintéticos, e inyectar estos dos nombres enhf_ptq.pypara que nunca se cuanticen — sin importar qué modelo se esté procesando.- El propio draft model, completo: ver sección 4 — no es que se excluya una capa, es que el modelo entero se sirve en BF16.
🔧 Bug real que ya pisaste
El 2026-07-10 un re-quant corrió contra un hf_ptq.py sin parchear porque los
marcadores .done del clone/patch eran de junio (previos a la exclusión MTP). Resultado:
pre_projection/post_projection quedaron cuantizadas a FP4 con las dimensiones
a la mitad — el mismo bug de shape-mismatch reportado públicamente por AQLabs. Se detectó inspeccionando
los headers de los safetensors antes de publicar. La regla que quedó: para re-cuantizar,
borrar todos los marcadores (rm -f data/.gemma4_qat_bootstrap/*.done), no
solo los de cuantización — los pasos de patch no tienen output verificable, así que un marcador viejo
se salta en silencio.
4. Servir rápido: KV cache + decoding especulativo
El Capítulo 1 explicó que generar texto es autoregresivo: un token depende de todos los anteriores, y el
KV cache evita recalcular la atención sobre toda la conversación en cada paso. Tu configuración de
serve_lacan_vllm.sh usa --kv-cache-dtype fp8 — el cache también se comprime,
con escalas dinámicas calculadas al vuelo durante el serving.
🔧 Un hallazgo tuyo que no está en ningún paper
Probaste exportar el checkpoint con escalas FP8 calibradas y guardadas para el KV cache
(--kv_cache_qformat fp8). Resultado: salida degenerada, byte-idéntica entre
el checkpoint absmax y el MSE — la prueba de que la falla estaba en las escalas de KV, no en los pesos.
Hipótesis más probable: las dimensiones heterogéneas de cabeza de Gemma-4
(head_dim=256, global_head_dim=512) fuerzan el backend TRITON_ATTN de vLLM,
que aplica mal esas escalas por capa. Conclusión operativa: --kv_cache_qformat none en el
checkpoint, y dejar que vLLM calcule las escalas dinámicamente en runtime. Sin pérdida real de ahorro
de memoria.
La parte que el Capítulo 1 ni siquiera menciona porque no la necesita para explicar transformers en general: decoding especulativo. La idea es contra-intuitiva — para generar más rápido, primero generás de más.
- El modelo chico (el draft MTP, 0.5B) propone varios tokens seguidos de una, rápido y barato — justamente su único trabajo es predecir tokens por adelantado, nada más.
- El modelo grande verifica esos tokens propuestos en paralelo, en una sola pasada — verificar es mucho más barato que generar token a token.
- Los tokens que el modelo grande acepta se guardan; en el primer rechazo, el grande genera él mismo ese token y el ciclo vuelve a empezar.
Medido por vos en La Bestia (vLLM 0.22.1, 300 tokens, temperatura 0.2, una sola request):
| Configuración | tok/s |
|---|---|
| Sin decoding especulativo | 23.0 |
| Con draft MTP, 3 tokens especulativos | 52.1 (2.26×) |
Con una tasa de aceptación real de 210/387 tokens (54%, 78% en la primera posición del lote propuesto).
🔧 La trampa que ya te costó un error en runtime
El draft tiene que quedarse sin cuantizar (0.5B parámetros, BF16, ~927 MB — la
cuenta cierra: 0.5×10⁹ parámetros × 2 bytes ≈ 1 GB). Si le pasás la versión
NVFP4 como draft, vLLM la carga sin aplicar la config de ModelOpt y explota con
RuntimeError: start (0) + length (8192) exceeds dimension size (4096) — está leyendo un
peso empaquetado en 4 bits como si fuera BF16. El ahorro de VRAM del draft nunca fue el punto: es tan
chico que no importa: lo que importa es que decodifique rápido y sin bugs de dtype.
5. La lección de ingeniería: verificá el artefacto, no el log
Tu propia documentación deja una regla que vale para cualquier pipeline de ML, no solo este: los logs de
hf_ptq.py imprimen texto degenerado en sus "example outputs" incluso para checkpoints que
después sirven perfecto — porque esa prueba interna no aplica el chat template. Un log que se ve mal no
significa un checkpoint roto, y viceversa: el bug del §3 pasó una corrida "exitosa" según el log.
Lo único que de verdad confirma que la cuantización salió bien es inspeccionar el artefacto: que
hf_quant_config.json liste pre_projection/post_projection en
exclude_modules, y que el header de safetensors muestre esas capas en BF16 con
sus dimensiones completas — un dtype U8 o dimensiones a la mitad es la señal inequívoca de que el patch
no corrió.
6. Mapa de tu propio pipeline
Uniendo todo lo de arriba con la teoría del Capítulo 1:
- Gemma-4 es un decoder-only denso — la arquitectura dominante del Capítulo 1, sin sorpresas ahí.
- Un segundo modelo (MTP) vive pegado al primero para acelerar generación — una pieza que el curso oficial no cubre porque es demasiado nueva.
- NVFP4 comprime cada peso a 4 bits en bloques de 16 con una escala FP8 compartida; cómo se elige esa escala (MSE > absmax) fue tu único cambio real este ciclo.
- Embeddings,
lm_head, las proyecciones MTP y el draft completo se salvan de la cuantización — cada uno por una razón distinta y verificable. - El decoding especulativo te dio 2.26× de velocidad real, a costa de mantener el draft en BF16 y las escalas de KV en modo dinámico, no calibrado.
Este es exactamente el mismo cuerpo de conocimiento que sostiene a interpretante-lacaniano — con el añadido de RAG, fine-tuning generacional, y un motor de razonamiento simbólico encima.