Aller au contenu

FP8, FP4 et quantification

Ce que « quantifier » veut dire

Un nombre flottant en 16 bits occupe deux octets. Le quantifier en 4 bits, c'est le remplacer par une approximation qui n'occupe qu'un demi-octet — quatre fois moins de mémoire, et quatre fois moins de bande passante pour le lire.

La question est toujours la même : combien d'information perd-on ?

Anatomie d'un flottant court

Un format flottant se décrit par le nombre de bits alloués à l'exposant (E) et à la mantisse (M), plus un bit de signe.

Format Bits Notation Valeurs distinctes Usage dans V4.1-Flash
BF16 16 E8M7 65 536 calcul, quelques poids
FP8 8 E4M3 256 poids denses, cache de fenêtre glissante, tables Engram
FP4 4 E2M1 16 poids des experts, cache principal, clés d'indexeur
E8M0 8 exposant seul 256 puissances de 2 facteurs d'échelle MXFP4

Le format E2M1 ne peut représenter que seize valeurs : \(0, \pm 0{,}5, \pm 1, \pm 1{,}5, \pm 2, \pm 3, \pm 4, \pm 6\).

Utilisé tel quel, il serait inexploitable : un vecteur dont les composantes valent \(10^{-3}\) serait entièrement arrondi à zéro.

L'échelle par bloc

La solution universelle est le facteur d'échelle par bloc. On découpe le vecteur en blocs de \(B\) canaux consécutifs. Pour chaque bloc, on stocke une valeur d'échelle \(s\) en pleine précision, et chaque élément du bloc est encodé comme \(\hat{x}_i \times s\) où \(\hat{x}_i\) tient sur 4 bits.

Le coût en mémoire devient :

\[ \text{bits par canal} = 4 + \frac{\text{bits de l'échelle}}{B} \]

C'est cette formule qui donne les 890 octets par jeton de DeepSeek-V4.1-Flash. Deux réglages coexistent dans le modèle, et le choix du bloc change le résultat :

Usage Format Échelle Bloc Bits/canal
Cache principal E2M1 E4M3 (8 bits) 16 \(4 + 8/16 = 4{,}5\)
Clés d'indexeur E2M1 E8M0 (8 bits) 32 \(4 + 8/32 = 4{,}25\)

Ces deux valeurs sont lisibles directement dans le code d'inférence publié :

# inference/model.py, méthode Attention._compress_kv
# Compressed KV uses groups of 16 with E4M3 scales; the indexer uses 32 with E8M0.
fp4_act_quant(latent, 16, True, scale_dtype=torch.float8_e4m3fn)

Erreur fréquente

« FP4 » ne veut pas dire « 4 bits par valeur ». Avec l'échelle, le coût réel est de 4,25 à 4,5 bits selon le bloc. Ignorer cette surcharge fait manquer les 890 octets de 11 % — et empêche donc de retrouver le chiffre annoncé.

MXFP4 et NVFP4

Deux conventions se disputent le terrain :

  • MXFP4, standard de l'Open Compute Project : E2M1, échelle E8M0 (une puissance de deux) par bloc de 32. Simple, largement supporté matériellement ;
  • NVFP4, de NVIDIA : E2M1, échelle E4M3 par bloc de 16, plus une seconde échelle globale par tenseur. Plus précis, mais plus contraignant.

DeepSeek fait un choix hybride et l'explique. Pour le cache principal, il adopte la structure de NVFP4 — bloc de 16, échelle E4M3 — mais supprime la seconde échelle globale. Le rapport technique justifie cette suppression par un argument de dynamique borné :

  • après normalisation RMS, la norme \(\ell_2\) du latent de 512 canaux vaut au plus \(\sqrt{512} \approx 22{,}6\) ;
  • la RoPE est une rotation, donc elle préserve cette norme ;
  • la valeur absolue maximale sur un canal est donc bornée par 22,6 environ, et le maximum réellement observé pendant l'entraînement est d'environ 10 ;
  • or le format sans échelle globale couvre les magnitudes jusqu'à \(448 \times 6 = 2\,688\).

La marge est de deux ordres de grandeur. La seconde échelle est donc inutile ici — et la supprimer simplifie la disposition mémoire du cache.

Pour les clés d'indexeur, en revanche, DeepSeek retient le MXFP4 standard, en assumant explicitement qu'il est moins précis que les alternatives : le choix est motivé par la compatibilité matérielle la plus large possible.

Entraînement conscient de la quantification

Quantifier un modèle après l'entraînement dégrade toujours sa qualité, parfois brutalement. L'alternative est la quantification consciente de l'entraînement (quantization-aware training, QAT) : on simule la quantification pendant l'entraînement, de sorte que le modèle apprenne des représentations robustes à l'arrondi.

DeepSeek-V4 utilisait déjà le QAT pour les requêtes et clés de l'indexeur. V4.1-Flash l'étend au cache principal, mais seulement pendant le post-entraînement, pas depuis le début.

Deux détails d'implémentation méritent d'être notés :

  • la quantification est appliquée après la RoPE. La quantifier avant n'apporterait qu'un gain marginal de précision et coûterait un traitement supplémentaire à chaque étape de décodage ;
  • le cache de fenêtre glissante reste en FP8, DeepSeek le jugeant plus sensible à la quantification que le cache principal.

Pourquoi le FP4 sur le cache n'accélère rien

Sur les poids, le FP4 accélère les multiplications matricielles quand le matériel le supporte nativement. Sur le cache, DeepSeek déquantifie avant l'attention. Le gain est donc purement en stockage et en bande passante, pas en calcul — ce qui, en contrepartie, permet d'utiliser un format plus précis sans exiger de support matériel spécifique.

À retenir

Le FP4 divise par deux le cache par rapport au FP8 de DeepSeek-V4. C'est le dernier des quatre facteurs, appliqué après les réductions structurelles de CSA2. L'ordre importe : on ne divise par deux que ce qui reste.


Chapitre suivant : La lignée DeepSeek