DFloat11 : compression sans perte pour l’inférence GPU qui réduit les LLM à 70 % de leur taille
(arxiv.org)- Les grands LLM sont difficiles à déployer en raison des limites de mémoire GPU, mais DFloat11 réduit les poids BFloat16 à environ 70 % de leur taille tout en conservant des sorties identiques bit à bit à l’original
- L’idée centrale est que l’exposant 8 bits de BFloat16 ne contient en réalité qu’environ 2,6 bits d’information ; le signe et la mantisse sont préservés, et seul l’exposant est compressé par codage de Huffman
- L’encodage à longueur variable pouvant facilement devenir un goulot d’étranglement sur GPU, DFloat11 est adapté à l’inférence parallèle grâce à des LUT hiérarchiques, un noyau en deux étapes et une décompression par bloc transformer
- Sur Llama 3.3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5, etc., il confirme une réduction d’environ 30 % de la taille du modèle tout en préservant les sorties originales
- Par rapport aux alternatives avec offload CPU, le débit de génération de tokens est 2,3 à 46,2 fois supérieur, et permet l’inférence sans perte du Llama 3.1 405B, d’environ 810 Go, sur un seul nœud de 8 GPU de 80 Go
Le goulot d’étranglement mémoire visé par DFloat11
- Les modèles de fondation comme les LLM et les Diffusion Models grossissent rapidement, ce qui les rend difficiles à déployer efficacement sur du matériel contraint en mémoire
- Llama 3.1 405B stocke ses 405 milliards de paramètres au format BFloat16 et nécessite environ 810 Go de mémoire pour une inférence complète
- Cela dépasse la capacité d’un serveur GPU haut de gamme courant équipé de 8 GPU de 80 Go
- Dès que plusieurs nœuds deviennent nécessaires, les coûts de déploiement et les contraintes d’accès augmentent
- DFloat11 vise à compresser les modèles BFloat16 à environ 70 % de leur taille d’origine, tout en préservant 100 % de la précision sur n’importe quelle tâche
Une approche différente de la quantification avec perte
- La quantification est une méthode de compression avec perte qui réduit la mémoire en abaissant la précision des poids
- Elle peut fortement réduire l’usage mémoire et accélérer l’inférence, mais introduit des erreurs d’approximation
- La perte de précision dépend du modèle de base, de la méthode de quantification, du benchmark d’évaluation et de la largeur en bits ciblée, ce qui la rend difficile à prédire
- Par exemple, appliquer SmoothQuant 8-bit à DeepSeek-R1-Distill-Qwen-1.5B entraîne une baisse de 9,09 % de la précision moyenne sur les tâches de raisonnement
- Même lorsque les indicateurs globaux de précision sont similaires, un modèle quantifié peut produire des comportements de réponse différents de l’original
- Dutta et al. ont observé des phénomènes de flips, où une bonne réponse devient mauvaise, ou une mauvaise réponse devient bonne
- Qwen2-1.5B quantifié avec W8A16 GPTQ ne perd que 0,3 % de précision sur GSM8K 8-shot, mais le statut correct/incorrect change pour 6,37 % des réponses
- Dans des domaines comme la finance ou la santé, les changements de sortie des modèles quantifiés peuvent rendre difficile le respect des exigences de réglementation et de fiabilité
- Les méthodes existantes de compression sans perte de modèles se concentrent surtout sur l’efficacité du stockage des checkpoints, la réduction du temps de téléchargement depuis les hubs de modèles, ou du matériel spécialisé comme les FPGA, avec peu d’avantages pour l’inférence sur GPU généraliste
La marge de compression restante dans l’exposant BFloat16
- BFloat16 répartit ses 16 bits entre 1 bit de signe, 8 bits d’exposant et 7 bits de mantisse
- Le point de départ de DFloat11 est une analyse de l’entropie de Shannon des composants BFloat16 des poids de LLM
- L’entropie du signe et de la mantisse est proche de leur largeur en bits respective, ce qui limite la marge de compression
- L’exposant dispose de 8 bits, mais son entropie n’est que d’environ 2,6 bits
- La distribution des valeurs d’exposant est très déséquilibrée
- Sur les 256 valeurs 8 bits possibles, seules environ 40 sont utilisées
- Les autres valeurs n’apparaissent pas
- La fréquence chute également rapidement selon le rang
- Grâce à cette faible entropie, l’exposant devient une cible de compression sans perte, avec environ 5,4 bits d’information d’exposant compressibles
Le format DFloat11
- DFloat11 ou DF11 est un format à virgule flottante de longueur variable qui compresse uniquement l’exposant des poids BFloat16 par codage entropique
- L’arbre de Huffman est construit à partir de la distribution des exposants des poids du modèle
- Les valeurs d’exposant fréquentes reçoivent des codes courts
- Les valeurs rares reçoivent des codes longs
- Le signe et la mantisse sont conservés tels quels
- L’exposant est stocké sous forme bit-packed dans un tableau d’octets
EncodedExponent - Le signe et la mantisse sont stockés séparément dans un tableau d’octets
PackedSignMantissa
- L’exposant est stocké sous forme bit-packed dans un tableau d’octets
- Au final, les poids BFloat16 sont réduits à environ 11 bits en moyenne, et peuvent être restaurés en valeurs BFloat16 d’origine sans perte de précision
Décompression adaptée à l’inférence GPU
- Les poids codés entropiquement utilisent un encodage à longueur variable, ils ne peuvent donc pas être injectés directement dans une multiplication matricielle
- La weight matrix nécessaire doit être décompressée immédiatement dans son format BFloat16 d’origine
- Une fois la multiplication matricielle terminée, la matrice BFloat16 est aussitôt supprimée afin d’économiser la mémoire GPU
- Le décodage Huffman classique parcourt l’arbre séquentiellement bit par bit, ce qui n’est pas adapté à l’architecture parallèle des GPU
- Si un seul thread prend en charge la décompression, l’utilisation du GPU est faible et la latence augmente
Décodage fondé sur des LUT hiérarchiques
- DFloat11 utilise un décodage basé sur des lookup tables au lieu de parcourir l’arbre de Huffman
- Si la longueur maximale des codes de Huffman est L, une LUT unique nécessite une taille de
2^L- Dans les LLM, L se situe généralement entre 24 et 32
- Une LUT de
2^32entrées est difficile à placer dans la SRAM du GPU
- Pour éviter cela, l’arbre de Huffman est divisé en sous-arbres disjoints de hauteur 8, chacun étant transformé en une LUT compacte de 256 entrées
- Dans une LUT hiérarchique, certaines entrées doivent servir de références vers des LUT de niveau inférieur
- DFloat11 exploite le fait que de nombreuses valeurs d’exposant ne sont pas utilisées dans les LLM
- Les valeurs d’exposant inutilisées dans la plage 240–255 sont réutilisées comme pointeurs internes
- Ces valeurs représentent des ordres de grandeur extrêmement élevés, de ±2^113 à ±2^128, et n’apparaissent pas dans les poids de LLM
- Dans les expériences, le nombre k de LUT compactes pour l’arbre de Huffman des exposants BFloat16 se situe entre 4 et 8
- Avec
CodeLengths, cela utilise au maximum(8 + 1) × 256 bytesde mémoire - Cette taille tient dans la SRAM et permet d’effectuer rapidement les lookups répétés
- Avec
Noyau GPU en deux étapes et métadonnées auxiliaires
- Chaque thread GPU prend en charge une plage contiguë de n octets de l’exposant encodé
- Les expériences utilisent n = 8
- Le thread décode les codes de Huffman qui commencent dans sa propre plage
- Les codes à longueur variable créent deux problèmes
- La position exacte du bit de départ de chaque thread n’est pas évidente
- À l’exception du premier thread, il est difficile de connaître l’index de sortie des éléments décodés
- Le premier problème est résolu par le tableau Gaps
- Gaps contient une entrée par thread
- Chaque entrée indique l’offset en bits du premier code de Huffman valide par rapport à l’octet de départ du thread
- Comme la longueur maximale des codes est de 32 bits, l’offset est dans l’intervalle
[0, 31]et stocké sur 5 bits
- Le problème de position de sortie est réduit en ne stockant que la position au niveau du thread block
- Stocker une position de sortie 32 bits pour chaque thread entraînerait un surcoût important, avec des dizaines de milliers de threads par weight matrix
- DFloat11 ne stocke que la position de sortie du premier élément de chaque thread block
- Le noyau fonctionne en deux étapes
- À la première étape, chaque thread décode sa plage et ne fait que compter le nombre d’éléments, sans écrire en HBM
- Les threads à l’intérieur du block effectuent une prefix sum avec l’algorithme de Blelloch pour calculer la position de sortie de chaque thread
- À la deuxième étape, la même plage est décodée à nouveau et les valeurs décodées sont écrites dans le write buffer SRAM selon les positions calculées
- L’exposant encodé est chargé en SRAM avant la première passe afin d’éviter les accès répétés à la mémoire globale
- Une fois tous les exposants décodés écrits en SRAM, une seule écriture coalesced vers la HBM est effectuée
Décompression par bloc transformer
- La décompression d’une seule weight matrix peut être trop petite pour exploiter suffisamment les ressources GPU
- Plus la matrice est grande, plus le débit de décompression de DFloat11 s’améliore
- Décompresser plusieurs matrices ensemble augmente le débit et masque la latence
- Toutes les weight matrices DFloat11 d’un bloc transformer sont décompressées dans un même batch
- Cette décompression batchée est effectuée juste avant le forward pass du bloc transformer correspondant
- Le token embedding et la language modeling head du LLM sont également compressés
- Ces matrices sont suffisamment grandes pour saturer les ressources GPU, sans nécessiter de batching séparé
Résultats d’évaluation et effets pratiques
- DFloat11 a été évalué sur des LLM et des diffusion transformers, notamment Llama 3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1 et Stable Diffusion 3.5
- Les résultats montrent une réduction d’environ 30 % de la taille du modèle et une préservation complète des sorties originales
- Les sorties sont identiques bit-for-bit à celles du modèle original
- Comme il ne s’agit pas d’une compression avec perte, la précision des poids BFloat16 d’origine est conservée
- Par rapport aux alternatives qui offloadent vers le CPU une partie du modèle non compressé pour respecter les contraintes mémoire, DFloat11 atteint un débit 2,3 à 46,2 fois supérieur en génération de tokens
- À budget mémoire GPU fixe, il permet une generation length 5,7 à 14,9 fois plus longue qu’un modèle non compressé
- Llama 3.1 405B est un modèle d’environ 810 Go, mais DFloat11 permet son inférence sans perte sur un seul nœud de 8 GPU A100 de 80 Go
- Ces résultats montrent qu’il est possible de réduire de moitié les exigences matérielles nécessaires à l’exécution de Llama-3.1-405B, tout en réalisant l’inférence sans accuracy loss
1 commentaires
Commentaires sur Hacker News
Ce n’est au fond qu’une conséquence du fait que la plage dynamique de bfloat16 est très large, mais qu’en pratique on n’en utilise pas toute l’étendue
Les gens aiment que les hyperparamètres ressemblent à 0,01 plutôt qu’à 10^10, mais on peut utiliser la même précision relative pour chaque exposant. Même si on multiplie par 10^6 tous les hyperparamètres du réseau, les poids initialisés, les données d’entraînement, etc., le comportement reste globalement similaire, car on exploite à peine la zone haute de la plage. Certaines fonctions spéciales peuvent toutefois faire exception
L’entropie typique des valeurs bfloat16 observées dans les poids et les activations est d’environ 10 à 12 bits, et en pratique seule une partie d’environ 65 à 75 % de la plage de valeurs est utilisée. Les bits de signe et de mantisse ressemblent davantage à du bruit difficile à compresser
Cette propriété a déjà été exploitée à de nombreuses reprises, aussi bien en calcul haute performance classique qu’en IA. On peut citer les travaux de compression sans perte du labo de Martin Burtscher(https://userweb.cs.txstate.edu/~burtscher/), fpzip du LLNL(https://computing.llnl.gov/projects/fpzip), ainsi que ma bibliothèque dietgpu créée en 2021(https://github.com/facebookresearch/dietgpu). Nous compressions sans perte toutes les données avant transfert dans de grands clusters GPU, par exemple les gradients ou les poids venant du backup, puis les décompressions à la réception, ce qui réduisait d’environ 10 % le temps d’entraînement wall-clock total, avec des résultats de calcul identiques puisqu’il n’y avait aucune perte
De plus, rANS est plus efficace et plus simple à implémenter que le codage de Huffman sur des jeux d’instructions de type SIMD. Comme DFloat11 doit aussi décompresser avant les opérations arithmétiques, cela peut réduire la latence et la perte de débit
La quantification est un outil essentiel pour faire tourner des LLM en local, et le goulet d’étranglement est souvent la RAM. Je me demande aussi s’il existe une meilleure compression sans perte pour les poids BF16
DFloat11 semble pouvoir s’intégrer assez facilement dans les workflows de quantification existants, mais le papier paraît assez sceptique, donc j’aimerais comprendre ce que j’ai raté
Dans les couches d’un réseau de neurones, on multiplie les entrées par des poids puis on les additionne, et la sortie devient l’entrée de la couche suivante ; ce processus peut se répéter plus de cent fois. Au moment d’atteindre la couche de sortie finale, ce facteur 10^6 aurait été appliqué plusieurs fois et aurait fait boule de neige jusqu’à des valeurs de l’ordre de 10^600
Le seul cas où DFloat11 obtenait plus de tokens par seconde, c’était en comparaison d’une inférence avec certaines couches offloadées vers le CPU
C’est le compromis espace-vitesse classique en informatique, il n’y a pas de repas gratuit
Cela dit, le coût pour tronquer et remplir avec des zéros reste faible
Ce qui saute le plus aux yeux, c’est l’implication pratique. Le fait de pouvoir faire de l’inférence sans perte sur un modèle de 405B de paramètres sur un nœud unique de 8×80GB GPU est assez impressionnant
Cela pourrait être un gros facteur de déblocage aussi bien pour les labos que pour les startups qui veulent faire tourner des modèles frontier sans coûts d’infrastructure gigantesques
Je suis reconnaissant de vivre à une époque aussi passionnante. À chaque fois que j’ouvre HN, il y a encore des nouveautés sur le machine learning et les modèles Transformer
Il faut que je lise plus en profondeur, mais je me demande si llama.cpp utilise une sorte de kernel personnalisé avec cuBLAS, ou s’il se contente surtout d’exploiter efficacement les kernels de cuBLAS
Deux semaines ? Deux mois ? Deux jours ? Deux minutes ?
Parfois, toutes les réponses sont vraies. C’est vraiment une époque fascinante
Une fois que cette guerre des formats de poids se sera stabilisée, le matériel pourra être conçu pour la prendre en charge. Quel que soit le format de poids jugé raisonnablement optimal, il faudra du matériel de multiplication matricielle optimisé pour celui-ci
Dans les cas d’usage réels d’agents, il est souvent difficile de trouver le bon équilibre entre qualité, coût et performance. Cette technique pourrait aider à éviter les compromis induits par la quantification, y compris ses effets parfois difficiles à prévoir lorsqu’on cherche à optimiser le coût d’un agent
Si DFloat11 permet de faire tenir le modèle sur des GPU moins chers, les économies de coût peuvent être importantes dans certains cas. Je travaille chez xmad.ai
Par rapport à l’alternative qui consiste à déporter vers le CPU une partie du modèle non compressé à cause des contraintes mémoire, DFloat11 offrirait un débit de génération de tokens 1,9 à 38,8 fois plus élevé. À budget mémoire GPU fixe, il permettrait aussi une longueur de contexte 5,3 à 13,17 fois supérieure à celle d’un modèle non compressé
Rien que la longueur de contexte semble déjà rendre le tout utile même si le modèle tient en mémoire, mais avec ma compréhension assez basique selon laquelle les LLM sont souvent limités par la bande passante mémoire, je me demande si le nombre de tokens par seconde s’améliore aussi quand tout reste sur le GPU
L’article affirme moins de 200GB/s sur A100, et les benchmarks semblent montrer que, selon le GPU et le modèle, c’est 1,5 à 4 fois plus lent avec une taille de batch de 1. Bien sûr, si la taille de batch est suffisamment grande, ce surcoût disparaît en grande partie
D’autres codecs sans perte peuvent atteindre 600GB/s sur le même matériel, donc il semble y avoir une marge d’amélioration. Mais la bande passante mémoire brute de l’A100 est de 1,6TB/s
Est-ce que cela signifie qu’on peut réduire de 30% les besoins mémoire des LLM non quantifiés de façon générale ? Si c’est vrai, c’est assez énorme
Si cela peut s’ajouter à la quantification existante, c’est clairement intéressant, mais la quantification K utilise déjà des précisions différentes selon les couches en fonction de l’impact global sur la perplexité. Par exemple, Q6 mélange du 4 bits et du 8 bits, ce qui ressemble à la métrique d’entropie utilisée ici. Si l’on prend même en compte l’imatrix calibrée, cela compresse conceptuellement encore plus fort, d’une manière similaire à une FFT
En quoi est-ce différent de ZipNN ? https://arxiv.org/pdf/2411.05239
Je vois la mention, mais je n’arrive pas à comprendre si c’est basé dessus, ou si c’est différent, ou meilleur
Mais les compresseurs de type LZ ne semblent pas très pertinents pour les poids des réseaux de neurones. Ils ne sont pas aussi redondants que la plupart des données textuelles, où les répétitions sont nombreuses, et à moins que les données ne soient très clairsemées, il se peut qu’il n’y ait pas assez de répétitions exploitables pour compenser le surcoût du dictionnaire
Ajouter un compresseur de type LZ et l’insérer dans le chemin critique de l’inférence rendrait la décompression bien plus lente. Le mieux est de fusionner la décompression avec le kernel de calcul. On pourrait par exemple avoir une sorte de GEMM qui décompresse chaque tile avant l’opération arithmétique, et plus la routine de décompression est simple, plus cela devient facile
C’est assez impressionnant de voir à quelle vitesse tout cela évolue. On a l’impression qu’il sort chaque semaine une nouvelle technique d’optimisation ou une mise à niveau matérielle
C’est facile de se laisser distraire par ce genre d’améliorations
Est-ce qu’on peut exécuter ça aussi sur de nouveaux modèles ? Sauf erreur de ma part, le code semble être uniquement pour l’inférence