2 puntos por GN⁺ 2025-04-26 | 1 comentarios | Compartir por WhatsApp
  • Los LLM grandes son difíciles de desplegar por los límites de memoria de GPU, pero DFloat11 reduce los pesos BFloat16 a alrededor del 70% de su tamaño manteniendo una salida idéntica bit a bit a la original
  • La clave es que el exponente de 8 bits de BFloat16 en realidad contiene solo unos 2.6 bits de información; se conservan el signo y la mantisa, y solo el exponente se comprime con Huffman coding
  • Como la codificación de longitud dinámica suele convertirse en un cuello de botella en GPU, DFloat11 se adapta a la inferencia paralela con LUT jerárquicas, kernels de 2 etapas y descompresión por bloque transformer
  • En Llama 3.3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5 y otros, se confirmó una reducción de alrededor del 30% en el tamaño del modelo y la preservación de la salida original
  • Frente a alternativas con CPU offload, logra un throughput de generación de tokens 2.3–46.2 veces mayor y permite inferencia sin pérdida de Llama 3.1 405B, de 810 GB, en un único nodo con GPU 8×80 GB

El cuello de botella de memoria al que apunta DFloat11

  • Los modelos fundacionales, como los LLM y los Diffusion Model, están creciendo rápidamente, lo que dificulta desplegarlos de manera eficiente en hardware con restricciones de memoria
  • Llama 3.1 405B almacena 405 mil millones de parámetros en formato BFloat16 y requiere alrededor de 810 GB de memoria para la inferencia completa
    • Esto supera la capacidad de un servidor GPU avanzado típico con GPU 8×80 GB
    • Si se necesitan varios nodos, aumentan los costos de despliegue y las barreras de acceso
  • DFloat11 busca comprimir modelos BFloat16 a cerca del 70% de su tamaño original y, aun así, preservar el 100% de la precisión en cualquier tarea

Un enfoque distinto de la cuantización con pérdida

  • La cuantización es un método de compresión con pérdida que reduce la memoria al bajar la precisión de los pesos
    • Puede reducir mucho el uso de memoria y acelerar la inferencia, pero introduce errores de aproximación
    • La pérdida de precisión depende del modelo base, el método de cuantización, el benchmark de evaluación y el bit-width objetivo, por lo que es difícil de predecir
  • Por ejemplo, al aplicar 8-bit SmoothQuant a DeepSeek-R1-Distill-Qwen-1.5B, la precisión promedio en tareas de razonamiento cae 9.09%
  • Aunque las métricas globales de precisión sean parecidas, un modelo cuantizado puede mostrar un comportamiento de respuesta distinto al original
    • Dutta et al. observaron el fenómeno de flips, donde respuestas correctas pasan a ser incorrectas, o respuestas incorrectas pasan a ser correctas
    • Qwen2-1.5B cuantizado con W8A16 GPTQ solo baja 0.3% en precisión GSM8K 8-shot, pero en el 6.37% de las respuestas cambia el estado correcto/incorrecto
  • En áreas como finanzas y salud, los cambios en la salida de un modelo cuantizado pueden dificultar cumplir requisitos de regulación y confiabilidad
  • La compresión sin pérdida de modelos existente se ha enfocado sobre todo en la eficiencia de almacenamiento de checkpoints, la reducción del tiempo de descarga desde hubs de modelos y hardware especializado como FPGA, por lo que aporta pocas ventajas en inferencia sobre GPU generales

Margen de compresión restante en el exponente de BFloat16

  • BFloat16 divide 16 bits en 1 bit de signo, 8 bits de exponente y 7 bits de mantisa
  • El punto de partida de DFloat11 es un análisis de entropía de Shannon por componente BFloat16 de los pesos de LLM
    • La entropía del signo y la mantisa está cerca de sus respectivos bit-width, por lo que el margen de compresión es limitado
    • Aunque el exponente tiene asignados 8 bits, su entropía es de apenas unos 2.6 bits
  • La distribución de los valores del exponente es muy desbalanceada
    • De los 256 valores posibles de 8 bits, solo se usan alrededor de 40
    • Los demás valores no aparecen
    • El ranking de frecuencias también cae rápidamente
  • Gracias a su baja entropía, el exponente se vuelve un objetivo de compresión sin pérdida, y se pueden comprimir cerca de 5.4 bits de información del exponente

Formato DFloat11

  • DFloat11 o DF11 es un formato de punto flotante de longitud dinámica que comprime solo el exponente de los pesos BFloat16 mediante entropy coding
  • El Huffman tree se construye a partir de la distribución de exponentes en los pesos del modelo
    • A los valores de exponente frecuentes se les asignan códigos cortos
    • A los valores raros se les asignan códigos largos
  • El signo y la mantisa se mantienen tal como están en el original
    • El exponente se almacena en forma bit-packed dentro de un byte array EncodedExponent
    • El signo y la mantisa se almacenan por separado en un byte array PackedSignMantissa
  • Como resultado, los pesos BFloat16 se reducen en promedio a alrededor de 11 bits, y pueden restaurarse a los valores BFloat16 originales sin pérdida de precisión

Descompresión adaptada a inferencia en GPU

  • Los pesos con entropy coding usan codificación de longitud dinámica, por lo que no pueden entrar directamente en matrix multiplication
    • La weight matrix necesaria debe descomprimirse de inmediato a su BFloat16 original
    • Cuando termina la matrix multiplication, la matrix BFloat16 se descarta de inmediato para ahorrar memoria de GPU
  • El Huffman decoding común recorre secuencialmente el tree bit a bit, por lo que no encaja con la estructura paralela de la GPU
    • Si un solo thread se encarga de la descompresión, la utilización de la GPU es baja y la latencia aumenta

Decodificación basada en LUT jerárquicas

  • DFloat11 usa decodificación basada en lookup table en lugar de recorrer el Huffman tree
  • Si la longitud máxima del Huffman code es L, una LUT única requiere tamaño 2^L
    • En LLM, L suele estar en el rango 24–32
    • Una LUT de escala 2^32 entradas es difícil de cargar en SRAM de GPU
  • Para evitarlo, el Huffman tree se divide en subtrees no superpuestos de altura 8, y cada subtree se convierte en una LUT compacta de 256 entradas
  • En la LUT jerárquica, algunas entradas deben actuar como referencias que apuntan a LUT inferiores
    • Se aprovecha que hay muchos valores de exponente no usados en LLM
    • Los valores de exponente no usados en el rango 240–255 se reutilizan como punteros internos
    • Estos valores representan magnitudes extremadamente grandes, del orden de ±2^113 a ±2^128, y no aparecen en los pesos de LLM
  • En los experimentos, el número k de LUT compactas del Huffman tree de exponentes BFloat16 está en el rango de 4–8
    • Junto con CodeLengths, usan como máximo (8 + 1) × 256 bytes de memoria
    • Este tamaño cabe en SRAM y permite hacer lookups repetidos rápidamente

Kernels GPU de 2 etapas y metadatos auxiliares

  • Cada thread de GPU se encarga de un tramo continuo de n bytes del exponente codificado
    • En los experimentos se usa n = 8
    • El thread decodifica los Huffman codes que empiezan dentro de su propio tramo
  • Los códigos de longitud dinámica generan dos problemas
    • La posición exacta del bit inicial de cada thread no es clara
    • Excepto para el primer thread, es difícil conocer el índice de salida del elemento decodificado
  • El primer problema se resuelve con el arreglo Gaps
    • Gaps tiene una entrada por thread
    • Cada entrada indica el bit offset del primer Huffman code válido respecto del byte inicial del thread
    • Como la longitud máxima del código es de 32 bits, el offset está en el rango [0, 31] y se almacena en 5 bits
  • El problema de la posición de salida se reduce almacenando solo la posición a nivel de thread block para bajar el overhead de memoria
    • Si se guardara una posición de salida de 32 bits por thread, el overhead crecería mucho con decenas de miles de threads por weight matrix
    • DFloat11 almacena solo la posición de salida del primer elemento de cada thread block
  • El kernel opera en dos etapas
    • En la etapa 1, cada thread decodifica su tramo, solo cuenta la cantidad de elementos y no escribe en HBM
    • Los threads dentro del block ejecutan un prefix sum con el algoritmo de Blelloch para calcular la posición de salida de cada thread
    • En la etapa 2, se vuelve a decodificar el mismo tramo y se escriben los valores decodificados en el write buffer de SRAM según la posición calculada
    • El exponente codificado se carga en SRAM antes del primer pass para evitar accesos duplicados a global memory
    • Una vez escritos todos los exponentes decodificados en SRAM, se realiza una única escritura coalesced a HBM

Descompresión por bloque transformer

  • Descomprimir una sola weight matrix puede no ser suficiente para aprovechar bien los recursos de la GPU porque su tamaño es pequeño
  • A medida que crece el tamaño de la matrix, mejora el throughput de descompresión de DFloat11
  • Se descomprimen varias matrices juntas para aumentar el throughput y ocultar la latencia
    • Todas las weight matrices DFloat11 dentro de un bloque transformer se descomprimen como un solo batch
    • Esta batched decompression se realiza justo antes del forward pass de ese bloque transformer
  • El token embedding y el language modeling head del LLM también se comprimen
    • Estas matrices son lo bastante grandes como para saturar los recursos de la GPU, así que no necesitan batching adicional

Resultados de evaluación y efecto práctico

  • DFloat11 se evaluó en LLM y diffusion transformers, incluidos Llama 3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5 y otros
  • Los resultados muestran una reducción de alrededor del 30% en el tamaño del modelo y una preservación completa de la salida original
    • La salida es idéntica bit-for-bit a la del modelo original
    • Como no es compresión con pérdida, mantiene la precisión de los pesos BFloat16 originales
  • En comparación con la alternativa de hacer offload a CPU de partes de un modelo sin comprimir para ajustarse a las restricciones de memoria, DFloat11 logra un throughput 2.3–46.2 veces mayor en generación de tokens
  • Con un presupuesto fijo de memoria de GPU, permite una generation length 5.7–14.9 veces mayor que un modelo sin comprimir
  • Aunque Llama 3.1 405B es un modelo de 810 GB, DFloat11 permite inferencia sin pérdida en un único nodo con GPU A100 8×80 GB
  • Estos resultados muestran que se puede reducir a la mitad el requerimiento de hardware necesario para ejecutar Llama-3.1-405B y hacer inferencia sin accuracy loss

1 comentarios

 
GN⁺ 2025-04-26
Opiniones de Hacker News
  • Esto no es más que una consecuencia de que el rango dinámico de bfloat16 es muy amplio, pero en la práctica no se usa todo ese rango.
    A la gente le gusta que los hiperparámetros se vean como 0.01 y no como 10^10, pero se puede usar la misma precisión relativa para cada exponente. Si multiplicas por 10^6 todos los hiperparámetros de la red, los pesos inicializados, los datos de entrenamiento, etc., en general se comporta de forma parecida porque casi no se usa la zona superior del rango. Aunque algunas funciones especiales pueden ser la excepción.
    La entropía típica de los valores bfloat16 que se ve en pesos y activaciones ronda los 10~12 bits, y en la práctica solo se usa alrededor del 65~75% del rango de valores. El signo y los bits de mantisa son casi ruido difícil de comprimir.
    Esta propiedad ya se ha aprovechado varias veces tanto en la computación de alto rendimiento clásica como en IA. Ejemplos de ello son los trabajos de compresión sin pérdida del laboratorio de Martin Burtscher (https://userweb.cs.txstate.edu/~burtscher/), fpzip de LLNL (https://computing.llnl.gov/projects/fpzip) y mi biblioteca dietgpu, creada en 2021 (https://github.com/facebookresearch/dietgpu). En clústeres grandes de GPU, comprimíamos sin pérdida todos los datos antes de transferirlos —por ejemplo, gradientes o pesos provenientes de respaldos— y los descomprimíamos al recibirlos, reduciendo el tiempo total de entrenamiento de pared en alrededor de un 10%; al ser sin pérdida, los resultados de cálculo eran los mismos que antes.
    Además, rANS es más eficiente que la codificación Huffman en conjuntos de instrucciones tipo SIMD y también es más fácil de implementar. En DFloat11 también hay que descomprimir antes de las operaciones aritméticas, así que se pueden reducir las pérdidas de latencia y throughput.

    • Para quienes no se tomen el trabajo de abrir el perfil: Jeff realmente sabe muchísimo de este campo. Meta/FAIR y buena parte de la comunidad se han beneficiado de su código.
    • Me pregunto si alguien puede recomendar un buen texto introductorio sobre rANS. Cuando busco en línea solo me salen soluciones de modelado de turbulencia, y sospecho que no se refiere a eso.
      La cuantización es una herramienta clave para quienes ejecutan LLM localmente, y por lo general el cuello de botella es la RAM. También me interesa saber si existe una mejor compresión sin pérdida para pesos BF16.
      DFloat11 parece relativamente fácil de encajar en los flujos de trabajo de cuantización existentes, pero el artículo parece bastante escéptico, así que quiero entender qué me estoy perdiendo.
    • Me parece muy dudosa la afirmación de que multiplicar por 10^6 todo en la red haría que se comporte casi igual.
      En una capa de una red neuronal, se multiplican las entradas por los pesos y se suman; luego esa salida pasa a ser la entrada de la siguiente capa, y este proceso puede repetirse más de cien veces. Al llegar a la capa final de salida, ese factor de 10^6 se habría aplicado muchas veces y crecería como una bola de nieve hasta el orden de 10^600.
    • Si miras la última página del apéndice, el artículo original informa que DFloat11 reduce los tokens por segundo alrededor de 2~3 veces en los modelos Llama-3.1-8b, Qwen-2.5-14b/32b y Mistral-small-24b. No se informa la pérdida de throughput en otros modelos.
      Los únicos casos en que DFloat11 tuvo más tokens por segundo fueron al compararlo con inferencia en la que algunas capas se descargaban a la CPU.
      Es el típico intercambio espacio-velocidad de la informática; no hay almuerzo gratis.
    • Entonces, ¿bfloat fue un error? ¿No era justamente el objetivo aumentar el rango dinámico?
      Aun así, el costo de recortar y rellenar con ceros es bajo.
  • Lo más llamativo son las implicaciones prácticas. Que permita hacer inferencia sin pérdida con un modelo de 405B parámetros en un solo nodo de 8×80 GB de GPU es bastante sorprendente.
    Puede ser un gran desbloqueo tanto para laboratorios como para startups que quieren correr modelos de frontera sin costos enormes de infraestructura.

    • O puedes dejarle el costo de infraestructura a un proveedor de neocloud y alquilarlo ahí. Como declaración de interés: dirijo una de esas empresas.
    • No soy especialista en este campo, así que pregunto: ¿el número 405B tiene algún significado especial?
    • Los modelos cuantizados a 4 bits de DeepSeek o Llama 3 405B ya caben en esas GPU, y se dice que la pérdida frente al modelo completo es casi cero. Considerando eso, no parece algo tan enorme.
    • Ahora mismo es útil, pero en un mundo donde el tamaño de los modelos, la memoria de las GPU y el soporte para distintas precisiones cambian rápidamente, no llega a ser un desbloqueo gigantesco.
  • Qué agradecido estoy de vivir en una época tan interesante. Cada vez que abro HN veo novedades sobre machine learning y modelos transformer.
    Tendría que leer más a fondo, pero me pregunto si llama.cpp usa una especie de kernel personalizado junto con cuBLAS, o si simplemente aprovecha bien los kernels de cuBLAS.

    • Es divertido que en la frase falte la unidad de tiempo.
      ¿2 semanas? ¿Dos meses? ¿Dos días? ¿2 minutos?
      A veces todas son correctas. De verdad es una época interesante.
  • Cuando se estabilice esta guerra de formatos de pesos, se podrá fabricar hardware que los soporte. Sea cual sea el formato de pesos que termine siendo razonablemente óptimo, hará falta hardware de multiplicación de matrices optimizado para él.

    • Aquí la optimización es posterior. Para hacer codificación Huffman primero hay que entrenar, así que no es un problema puramente de formato.
  • En casos reales de uso con agentes, a menudo es difícil equilibrar calidad, costo y rendimiento. Esta técnica puede ayudar a evitar los compromisos que introducen las técnicas de cuantización, incluidos resultados difíciles de predecir al intentar optimizar el costo de los agentes.
    Si con DFloat11 se puede hacer que quepa en GPU más baratas, en algunos casos el ahorro de costos puede ser considerable. Trabajo en xmad.ai

  • En comparación con la alternativa de descargar a la CPU parte de un modelo sin comprimir debido a restricciones de memoria, DFloat11 supuestamente ofrece un throughput de generación de tokens entre 1.9 y 38.8 veces mayor. Con un presupuesto fijo de memoria de GPU, permite longitudes de contexto entre 5.3 y 13.17 veces más largas que un modelo sin comprimir.
    Solo por la longitud de contexto ya parece útil aunque el modelo quepa en memoria, pero partiendo de mi comprensión básica de que los LLM suelen estar limitados por el ancho de banda de memoria, me pregunto si también mejora la cantidad de tokens por segundo cuando todo está en la GPU.

    • No. La descompresión se hace moviendo cada tensor de memoria a memoria, así que es peor.
      El paper afirma que en A100 está por debajo de 200 GB/s y, según los benchmarks, parece ser entre 1.5 y 4 veces más lento con tamaño de batch 1, dependiendo de la GPU y el modelo. Claro que, cuando el tamaño de batch crece lo suficiente, la mayor parte de ese overhead desaparece.
      Otros códecs sin pérdida pueden llegar a 600 GB/s en el mismo hardware, así que parece haber margen de mejora. Pero el ancho de banda de memoria bruto de una A100 es de 1.6 TB/s.
    • Según mi modelo mental, parece que podría ser posible. Algo parecido a cómo DoubleSpace de DOS hacía que la carga desde discos duros lentos fuera un poco más rápida.
    • Si el tamaño del modelo pasa a ser 70%, la velocidad sería 1/0.7, es decir, 1.43 veces.
  • ¿Esto significa que se pueden reducir en 30% de forma general los requisitos de memoria de los LLM sin cuantizar? Si es cierto, es bastante importante.

    • No es tan importante si la cuantización Q8, que ya se considera excesiva, reduce el tamaño al 50% y da limpiamente una mejora de velocidad de 2x sin overhead de cómputo adicional. La Q4KM, más común, está aproximadamente en el 30%.
      Si se pudiera sumar a la cuantización existente, sin duda sería interesante, pero la cuantización K ya usa distintas precisiones por capa según su impacto general en la perplejidad. Por ejemplo, Q6 mezcla 4 bits y 8 bits, lo cual se parece al indicador de entropía que se usa aquí. Si además se considera imatrix calibrado, conceptualmente comprime de forma más agresiva de un modo similar a FFT.
  • ¿Esto es distinto de ZipNN? https://arxiv.org/pdf/2411.05239
    Veo que se menciona, pero no entiendo si se basa en eso o si es diferente o mejor.

    • Lo encontré. Esta noticia me hizo recordar este paper: https://proceedings.neurips.cc/paper/2020/file/747e32ab0fea7...
    • No mucho. Básicamente agrega un poco de transposición de datos, es decir, juntar bytes individuales de las palabras de datos, y la opción de usar un compresor LZ/basado en diccionario para comprimir duplicados.
      Pero los compresores tipo LZ no parecen tener mucho sentido para pesos de redes neuronales. No tienen tanta redundancia como la mayoría de los datos de texto con muchas repeticiones y, si los datos no son muy dispersos, puede que no haya suficientes repeticiones aprovechables como para compensar el overhead del diccionario.
      Agregar un compresor tipo LZ y ponerlo en la ruta crítica de la inferencia haría que la descompresión fuera mucho más lenta. Lo mejor sería fusionar la descompresión con el kernel de cómputo. Por ejemplo, se podría hacer como una GEMM que descomprime cada tile antes de las operaciones aritméticas, y mientras más simple sea la rutina de descompresión, más fácil será hacerlo.
  • Es bastante genial ver lo rápido que se mueve todo esto. Se siente como si cada semana apareciera una nueva técnica de eficiencia o una actualización de hardware.
    Es fácil distraerse con estas mejoras.

  • ¿Esto se puede ejecutar también en modelos nuevos? Si no entendí mal, el código parece ser solo para inferencia.