Curso / Aplicado a tus proyectos / Pipeline NVFP4 · Gemma 4
🔧 basado en tu código real

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".

Modelo base: google/gemma-4-31B-it-qat Formato: NVFP4-mse (ModelOpt 0.44) Sirve en: vLLM 0.22 · RTX 5090 24GB (sm_120) Calibra en: RTX 6000 96GB "La Bestia" (Blackwell)

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:

CheckpointViene de
gemma-4-31B-it-qat-NVFP4-BlackwellModelo base de Google, sin fine-tune
gemma-4-31B-it-qat-assistant-NVFP4-BlackwellEl draft MTP del modelo base
gemma-4-31B-analyst-NVFP4-BlackwellTu 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.

AlgoritmoCómo elige la escalaEn tu pipeline
Max / absmaxEl valor más grande del bloque define la escala — un solo outlier aplasta el resto del bloque hacia ceroLo que usabas antes
MSE (nvfp4_mse)Prueba distintas escalas y elige la que minimiza el error de reconstrucción del bloqueLo que usás ahora — adoptado de la receta de NVIDIA para Nemotron 3 Super
Four-over-sixCada bloque elige entre dos valores máximos posibles (4 o 6), el que minimice errorEvaluado, 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) y lm_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_projection del 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 con shared_kv_states sintéticos, e inyectar estos dos nombres en hf_ptq.py para 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.

  1. 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.
  2. El modelo grande verifica esos tokens propuestos en paralelo, en una sola pasada — verificar es mucho más barato que generar token a token.
  3. 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óntok/s
Sin decoding especulativo23.0
Con draft MTP, 3 tokens especulativos52.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:

  1. Gemma-4 es un decoder-only denso — la arquitectura dominante del Capítulo 1, sin sorpresas ahí.
  2. Un segundo modelo (MTP) vive pegado al primero para acelerar generación — una pieza que el curso oficial no cubre porque es demasiado nueva.
  3. 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.
  4. 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.
  5. 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.