STEPQuant: estados recurrentes a «6 bit» y 5x menos memoria en serving
TL;DR
- Verificado (paper + código): STEPQuant (arXiv 2609.38169, CityU HK / Harvard / Zhejiang y otras; código publicado el 29-sep, un día antes del paper) cuantiza el estado recurrente de la atención lineal a un presupuesto nominal de 6 bit y recupera prácticamente toda la precisión en generación larga: 80,59 vs 80,60 de la media FP32 en Qwen3.8-27B, y 61,47 vs 61,52 en Kimi-Linear-48B-A3B.
- Verificado (abstract + apéndices): 5,03x/5,08x de compresión de estado y hasta un 68,7% menos de memoria total de serving — pero con condiciones: solo Qwen, con pesos ya cuantizados a W4 AWQ y batch 512 (419,73 → 131,18 GiB). Con pesos BF16 el ahorro total es menor, porque los pesos dominan la cuenta.
- Verificado: los bits reales son 6,36 por valor en Qwen (los pivots FP16 y las escalas van fuera del presupuesto nominal). La letra pequeña está en los apéndices C y D; el abstract vende «6-bit» a secas.
- Matiz clave: INT8 uniforme no es mala solución universal — en tareas cortas pierde solo 1,53 puntos (86,25 vs 87,78) y STEPQuant@4 se queda a 0,15 de FP32; el colapso de INT8/6/4 es específico de generación larga, que es justo el régimen de agentes.
- Mi lectura: es el primer trabajo que hace viable el régimen <8-bit en estados recurrentes justo cuando DAMP (trabajo concurrente) lo declara difícil. Código, planes de calibración y suite de evaluación ejecutables hoy en SGLang.
Contexto: por qué el estado recurrente se ha convertido en el cuello
La atención lineal (Gated DeltaNet en Qwen3.8-27B, KDA en Kimi-Linear) sustituye el KV cache, que crece con el contexto, por un estado recurrente de tamaño fijo por request. Suena a victoria de memoria garantizada. El problema aparece cuando sirves con concurrencia: cada request mantiene su estado persistente durante toda la conexión, y el pool de estados crece con el batch, no con el contexto.
El paper lo cuantifica en el despliegue oficial de SGLang: con pesos BF16, el pool de estados FP32 de Qwen3.8-27B supera la memoria de sus propios pesos a partir de ~70 requests concurrentes (Fig. 1a). Y el sistema puede reservar slots de estado adicionales para caché y scheduling, lo que agrava la cuenta. Con los pesos ya cuantizados a W4, el estado es el bloque dominante: 419,73 GiB de memoria total a batch 512 en su montaje de referencia. No es un detalle de implementación: es el componente que decide cuántos usuarios caben en tu nodo.
Por qué la cuantización uniforme colapsa en generación larga
Cuantizar el estado a INT8/6/4 uniforme destroza la precisión, pero no de forma uniforme. En tareas cortas, INT8 apenas pierde: 86,25 vs 87,78 de media en las seis tareas cortas del paper. En las siete tareas largas (LCB v6, EvalPlus, AIME 2026, MATH-500, HMMT, GPQA-D, IFBench), el mismo INT8 se hunde a 71,86 desde 80,60. La diferencia es la recurrencia: cada actualización de estado se construye sobre el estado cuantizado del paso anterior, así que el error no se promedia, se acumula. Cuanto más largo el rollout, más pasos para que el veneno circule — exactamente el régimen de agentes, donde un rollout puede correr durante miles de tokens.
El Appendix H añade un detalle incómodo: «With BF16 weights at four bits, KDA loses accuracy on long-generation tasks, while Qwen generates longer outputs despite retaining near-FP32 average accuracy» — con estados INT4, Qwen genera más tokens antes de parar, y no porque los tokens sean mejores. La degradación no siempre parece degradación: el modelo sigue soltando tokens largos, ahora basura.
Las dos ideas: cuándo y dónde importa el error
STEPQuant ataca el problema por dos dimensiones complementarias, con una única calibración offline (32 segmentos de WikiText-2 deciden la asignación de bits, los pivots y el impacto de las filas).
Temporal — cuándo (§3). No todas las celdas del estado viven igual: cabezas con mayor half-life de gate acumulan errores que persisten más pasos de decodificación. La asignación de bits (lifetime-aware bit allocation) da más bits a las unidades con errores de cuantización mayores y memorias más longevas. Las pocas unidades que siguen siendo ingobernables se quedan en FP16 como «pivots»: en Qwen3.8-27B, 32 cabezas de 2.304 (la unidad de asignación es el head completo); en Kimi, 512 filas de 81.920 (la unidad es la key row).
Espacial — dónde (§4). Las magnitudes varían por filas y columnas del estado, y distintas filas de key pesan distinto en el output. El dual-axis fitting ajusta escalas separadas para key-row y value-column, ponderando por el impacto de cada fila en el error de salida.
La pieza que remata el sistema es de ingeniería, no de algoritmo: kernels fused en SGLang con estados packed, writeback en un stream CUDA separado que solapa las actualizaciones de escala con el cómputo del modelo (§4.4). El estado viaja comprimido entre tokens y mover menos bytes compensa con creces el coste de descomprimir: el throughput no baja, sube.
Los números, con su contexto
Media de las 7 tareas de generación larga, estados BF16, 4×A800 TP4 (Tabla 1 del paper; todo autoreportado):
| Método | Qwen3.8-27B | Kimi-Linear-48B-A3B |
|---|---|---|
| FP32 (estado) | 80,60 | 61,52 |
| INT8 uniforme | 71,86 | 56,02 |
| INT6 uniforme | 45,04 | 45,70 |
| INT4 uniforme | 12,73 | — |
| STEPQuant @6 | 80,59 | 61,47 |
| STEPQuant @4 | 80,51 | 58,52 |
(INT4 uniforme en Kimi figura como n/a en la Tabla 1 del paper.)
En tareas cortas el mapa cambia (Tabla 2): INT8 pierde 1,53 puntos (86,25 vs 87,78 en Qwen) y, según el paper, SQ@4 se queda a solo 0,15 — «trails FP32 by only 0.15 points on Qwen and 0.25 points on Kimi» (§5.2). La comparación con DAMP es cross-paper (retención normalizada sobre 3 tareas KDA compartidas): DAMP logra 100,99% de retención con 9,9 bits medios; STEPQuant@6, 100,51% con 6,3 bits. Favorable, pero no es una reproducción propia.
El ablation (Tabla 4, sobre 3 de las 7 tareas largas con pesos BF16) confirma que las dos ideas son necesarias: en Qwen@6, el adaptado del paper Q-Mamba sube INT6 de 38,63 a 75,95, el componente temporal solo se queda en 75,63, el espacial en 80,19, y el sistema completo llega a 84,51 (FP32: 84,61). A 4 bit sin componentes el suelo es de 3,97; completo, 84,72.
Y el bonus que el abstract no cuenta: a batch 512, el throughput de decodificación sube un 20,53% en Qwen (6.040 → 7.280 tok/s) y un 11,80% en KDA (21.241 → 23.748) frente a estado FP32 (Tabla F7). Menos bytes que mover gana, incluso descomprimiendo.
Qué dice realmente el «6-bit» y el «68,7%»
El «6-bit» es nominal. La Tabla D3 desglosa el coste real en Qwen: 6 bits + 16/128 + 16/128 (escalas) + (32/2.304)×(16−8) (pivots) = 6,361 bits por valor (4,621 bits en la configuración a 4 bit). Los pivots FP16 y los metadatos de escala «se contabilizan aparte del presupuesto de bits nominal» (Tabla C1). Honesto en el apéndice; abstract vendedor. La compresión real de estado es 5,03x en Qwen (5,08x en Kimi), no 6/1 — 100,04 → 19,70 GiB solo de estado en Kimi (Fig. F4).
El 68,7% no es general. Son solo Qwen, con pesos W4 AWQ, a batch 512: 419,73 → 131,18 GiB. En Kimi es 53,7% (149,70 → 69,36 GiB, de los cuales el estado baja de 100,04 a 19,70 GiB). Con pesos BF16 el porcentaje de ahorro total sería menor — mismo estado comprimido sobre una base mayor —; el titular del abstract está construido sobre el escenario más favorable.
«Casi iguala FP32» tiene una condición por arquitectura. En Qwen/GDN, @4 pierde 0,09 puntos. En Kimi/KDA, @4 pierde 3,0 (58,52 vs 61,52) — reconocido en el Appendix H: «With BF16 weights at four bits, KDA loses accuracy on long-generation tasks, while Qwen generates longer outputs despite retaining near-FP32 average accuracy».
Todo es autoreportado. Dos modelos, 4×A800 TP4, carga fija. El Appendix H lo admite: «accuracy and systems gains on other architectures and under dynamic serving workloads remain to be verified». El paper tiene 9 días y entró hoy mismo en HF Daily Papers (98 upvotes al 08-oct); los upvotes son interés de la comunidad, no validación.
Cómo probarlo tú
El repositorio es inusualmente completo para un preprint de 9 días: el código salió el 29-sep, un día antes del anuncio del paper. Paquete stepquant/ (calibración, servidor SGLang, suite de evaluación), tests, benchmarks y un REPRODUCTION.md con el stack versionado: SGLang 0.5.12, PyTorch 2.11.0, Triton 3.7.1, Transformers 5.8.1 (Qwen) o 4.57.1 con fla-core 0.4.1 (Kimi). El flujo es calibrate → serve → evaluate, con planes de calibración ya calculados: --state-format stepquant6 --plan artifacts/reproduction/plans/qwen-stepquant6.pt --tp-size 4.
Si sirves Qwen3.8-27B o Kimi-Linear en SGLang con concurrencia real, es de las pocas cosas de este tipo que puedes medir en tu propia carga esta semana. Los planes precalculados permiten probar @6 sin tocar calibración.
Conclusión
Si sirves modelos de atención lineal con concurrencia, el estado recurrente ya es (o será en cuanto cuantices pesos) tu primer cuello de memoria, y cuantizarlo uniformemente es un truco que solo aguanta cargas cortas. STEPQuant es hoy la referencia seria para bajar de 8 bit en ese estado: @6 prácticamente gratis en GDN, @4 viable en GDN y arriesgado en KDA, y throughput que sube en lugar de bajar. Lo que haría: en producción con Qwen3.8-27B, probar @6 con los planes del repo y medir throughput y memoria en tu batch real; con Kimi-Linear, quedarme en @6 y no tocar @4 en generación larga. Lo que miraría: si esto aterriza upstream en SGLang, el duelo DAMP vs STEPQuant sobre la misma implementación y, sobre todo, réplicas independientes — todo lo publicado hasta ahora es del propio equipo.
Fuentes: paper (arXiv 2609.38169) · código (GitHub) · REPRODUCTION.md · página en Hugging Face
Relacionados: análisis de Qwen3.8-27B — el modelo GDN evaluado aquí · Ternary Bonsai-2: cuantización extrema de pesos — el otro lado (pesos, no estados) · guía de self-hosting con llama.cpp y vLLM · la memoria como cuello de botella en agentes