6 · Les nombres flottants¶
Descendre en précision est le levier de performance le plus puissant de la décennie. C'est aussi celui qui casse les choses de la façon la plus discrète.
6.1 Rappel : comment un flottant est fait¶
Un nombre à virgule flottante est stocké en trois champs :
| Symbole | Nom | Rôle |
|---|---|---|
| \(s\) | signe | 1 bit |
| \(e\) | exposant | détermine l'étendue (le plus grand et le plus petit nombre représentable) |
| \(b\) | biais | décalage constant, \(b = 2^{n_e - 1} - 1\) |
| \(m\) | mantisse | détermine la précision (le nombre de chiffres significatifs) |
Deux propriétés indépendantes, donc :
- l'étendue dépend du nombre de bits d'exposant ;
- la précision dépend du nombre de bits de mantisse.
C'est la distinction clé de tout ce chapitre. Un format peut avoir une grande étendue et une précision misérable (BF16), ou l'inverse (FP16).
6.2 Les formats, en un tableau¶
| Format | Bits | Signe/Exp./Mant. | Étendue max | Précision relative | Support matériel |
|---|---|---|---|---|---|
| FP64 | 64 | 1/11/52 | ~1,8 × 10³⁰⁸ | ~2,2 × 10⁻¹⁶ | tous, bridé sur cartes grand public |
| FP32 | 32 | 1/8/23 | ~3,4 × 10³⁸ | ~1,2 × 10⁻⁷ | universel |
| TF32 | 19 stockés sur 32 | 1/8/10 | ~3,4 × 10³⁸ | ~4,9 × 10⁻⁴ | tensor cores Ampere+ |
| BF16 | 16 | 1/8/7 | ~3,4 × 10³⁸ | ~3,9 × 10⁻³ | Ampere+ |
| FP16 | 16 | 1/5/10 | 65 504 | ~4,9 × 10⁻⁴ | Volta+ |
| FP8 E4M3 | 8 | 1/4/3 | 448 | ~6,3 × 10⁻² | Hopper+ |
| FP8 E5M2 | 8 | 1/5/2 | 57 344 | ~1,3 × 10⁻¹ | Hopper+ |
| FP6 E3M2 / E2M3 | 6 | — | ~28 | — | Blackwell, CDNA4 |
| FP4 E2M1 | 4 | 1/2/1 | 6 | 0,5 | Blackwell, CDNA4 |
| INT8 | 8 | entier | ±127 | — | Turing+ |
Pourquoi BF16 a gagné contre FP16 en apprentissage
BF16 a exactement la même étendue que FP32 (8 bits d'exposant). Un gradient minuscule ne devient donc jamais zéro par sous-dépassement, et une activation énorme ne devient jamais l'infini.
FP16, avec ses 5 bits d'exposant, sature à 65 504 et perd tout en dessous de ~6 × 10⁻⁸. L'entraînement en FP16 exige donc un mécanisme de mise à l'échelle des pertes (loss scaling) qui multiplie la perte par un facteur avant la rétropropagation. BF16 s'en passe.
La précision de BF16 est pourtant bien pire (7 bits de mantisse contre 10). En apprentissage profond, l'étendue compte plus que la précision — résultat empirique, pas théorème.
6.3 TF32 : le format que vous utilisez sans le savoir¶
TF32 est particulier : il n'existe que dans le tensor core. En mémoire, vos tenseurs restent des FP32 de 32 bits. Mais quand le tensor core lit un opérande TF32, il tronque la mantisse à 10 bits.
Conséquence : depuis Ampere, torch.matmul sur des tenseurs FP32 peut donner un
résultat différent de celui d'une implémentation FP32 stricte, parce que
PyTorch active TF32 par défaut sur certaines opérations.
import torch
# Contrôle explicite
torch.backends.cuda.matmul.allow_tf32 = False # FP32 strict, ~8x plus lent
torch.backends.cudnn.allow_tf32 = False
Limite importante
Si vous portez un code scientifique sur GPU et que vos résultats divergent d'une référence CPU au-delà de ce que vous attendiez, vérifiez TF32 en premier. C'est la cause la plus fréquente de « mon GPU ne calcule pas pareil ». Ce n'est pas un bug : c'est un choix par défaut assumé, adapté à l'apprentissage profond et inadapté à beaucoup d'autres domaines.
6.4 FP8 et les deux variantes¶
FP8 se décline en deux formats, et le choix n'est pas anodin :
- E4M3 (4 bits d'exposant, 3 de mantisse) : étendue ±448, meilleure précision. Pour les poids et activations.
- E5M2 (5 bits d'exposant, 2 de mantisse) : étendue ±57 344, précision très faible. Pour les gradients, qui ont une dynamique énorme.
Avec 3 bits de mantisse, E4M3 ne représente que 16 valeurs distinctes par octave. La quantification devient grossière, et il faut un facteur d'échelle.
La mise à l'échelle¶
En pratique, on ne stocke jamais un tenseur en FP8 brut. On stocke :
où \(s\) est un facteur d'échelle en FP32. Trois granularités :
| Granularité | \(s\) par… | Coût mémoire | Qualité |
|---|---|---|---|
| Par tenseur | tout le tenseur | négligeable | faible |
| Par canal | ligne ou colonne | faible | bonne |
| Par bloc | groupe de 32 valeurs | ~3 % | très bonne |
Le format par bloc est celui des standards MX (microscaling) de l'Open Compute Project, adoptés par Blackwell (MXFP8, MXFP6, MXFP4, plus la variante NVIDIA NVFP4 avec échelle FP8 E4M3 sur des blocs de 16) et par CDNA 4.
6.5 FP4 : jusqu'où peut-on descendre ?¶
FP4 E2M1 ne représente que 16 valeurs au total :
Utilisé nu, c'est inexploitable. Utilisé avec une échelle par bloc de 16 ou 32 valeurs, cela fonctionne étonnamment bien pour l'inférence de grands modèles : le débit tensor core double par rapport à FP8 (7 702 contre 3 851 TFLOPS sur B200), et le volume mémoire est divisé par deux — ce qui, sur un problème limité par la bande passante, est le gain qui compte vraiment.
Ce qui ne fonctionne pas en FP4 :
- l'entraînement direct (les gradients n'ont pas la dynamique) ;
- les accumulations longues — l'accumulateur reste toujours en FP32 ou FP16 ;
- les couches sensibles : plongements, dernière couche, normalisations, qu'on garde en précision plus haute.
Erreur fréquente
Confondre « le modèle est en FP4 » et « tout le calcul est en FP4 ». Dans un schéma W4A16 ou NVFP4, seuls les poids sont en 4 bits ; ils sont déquantifiés à la volée vers un format plus large avant la MMA, ou bien la MMA elle-même prend des opérandes 4 bits mais accumule en FP32. Le pipeline numérique est toujours mixte.
6.6 Ce que la précision réduite casse¶
Le non-déterminisme des réductions¶
L'addition flottante n'est pas associative :
Sur GPU, une réduction est parallèle : l'ordre d'addition dépend de
l'ordonnancement, qui peut varier d'une exécution à l'autre (notamment avec
atomicAdd). Deux exécutions du même programme peuvent donner des résultats
différents.
En FP32, l'écart est de l'ordre de 10⁻⁷ et sans conséquence. En BF16 avec des milliers de termes, il peut devenir visible.
Remèdes :
- accumuler en FP32 même quand les entrées sont en BF16 (c'est ce que font les tensor cores par défaut) ;
- utiliser une réduction déterministe à ordre fixe — CCCL 3.1, livré avec CUDA 13.1, ajoute des options de réduction flottante déterministe ;
- pour les tests, comparer avec une tolérance et non avec
==.
La perte de sens des tests d'égalité¶
# Ne fait pas ça pour valider un noyau
assert torch.equal(sortie_gpu, sortie_reference)
# Fais ça
torch.testing.assert_close(sortie_gpu, sortie_reference,
rtol=1e-2, atol=1e-2) # tolérances BF16
Ordres de grandeur de tolérance raisonnables :
| Précision de calcul | rtol typique |
|---|---|
| FP32 | 1e-5 |
| TF32 | 1e-3 |
| FP16 / BF16 | 1e-2 |
| FP8 | 5e-2 |
Ces valeurs sont indicatives ; la bonne tolérance dépend du nombre d'accumulations. Une règle utile : l'erreur relative d'une somme de \(n\) termes croît en \(O(\sqrt{n} \cdot \varepsilon)\) pour des erreurs indépendantes, où \(\varepsilon\) est la précision machine du format.
Les débordements silencieux¶
En FP16, 65505.0 devient inf. En FP8 E4M3, 449.0 devient inf (ou la
valeur maximale selon le mode de saturation). Aucun avertissement n'est émis.
C'est la raison pour laquelle FlashAttention soustrait le maximum avant l'exponentielle : \(\exp(x_i - \max_j x_j)\) ne déborde jamais, alors que \(\exp(x_i)\) déborde dès \(x_i > 11\) en FP16.
6.7 Le cas particulier de FP64¶
En calcul scientifique, la double précision n'est pas négociable pour beaucoup de problèmes (dynamique moléculaire longue, mécanique des fluides, systèmes mal conditionnés).
Or le débit FP64 varie de trois ordres de grandeur selon la carte :
| Carte | FP64 (TFLOPS) | Rapport FP64/FP32 |
|---|---|---|
| A100 | 9,7 (19,5 en tensor) | 1/2 |
| H100 | 34 (67 en tensor) | 1/2 |
| B200 | ~40 (44,8 mesuré en tensor) | ~1/2 |
| RTX 4090 | 1,3 | 1/64 |
| RTX 5090 | ~1,6 | 1/64 |
| MI300X | 81,7 | 1/1 |
Le piège de la carte grand public
Une RTX 5090 est excellente pour l'IA et catastrophique pour le FP64 : le rapport 1/64 est un bridage délibéré de segmentation commerciale. Un code de simulation en double précision tournera 25 fois plus lentement sur une RTX 5090 que sur un H100, alors que la carte grand public est plus rapide en BF16.
Vérifiez toujours le débit FP64 de votre cible avant de dimensionner un projet scientifique.
Notez aussi la performance FP64 remarquable du MI300X, un des rares arguments techniques solides d'AMD en HPC traditionnel.
6.8 Une méthode pour choisir sa précision¶
- Partir de FP32 (ou FP64 en scientifique) et obtenir un résultat de référence.
- Descendre d'un cran et mesurer l'écart sur une métrique qui a du sens pour votre problème — pas l'erreur maximale sur un tenseur intermédiaire, mais la qualité finale.
- Isoler les couches sensibles et les remonter individuellement. Typiquement : normalisations, softmax, accumulateurs, dernière couche.
- Toujours accumuler plus large qu'on ne multiplie. C'est la règle d'or, et elle est câblée dans les tensor cores.
- Mesurer le gain réel, pas le gain théorique. Sur un noyau limité par la mémoire, passer de BF16 à FP8 double la bande passante effective — c'est souvent plus que le gain de calcul.
Résumé du chapitre¶
À retenir
- Étendue (bits d'exposant) et précision (bits de mantisse) sont indépendantes. BF16 troque la précision contre l'étendue, et c'est pourquoi il a gagné en apprentissage.
- TF32 est activé par défaut dans PyTorch et modifie silencieusement vos résultats FP32. Le désactiver si la précision compte.
- En dessous de 8 bits, un facteur d'échelle par bloc est obligatoire : c'est ce que standardisent MXFP et NVFP4.
- L'addition flottante n'est pas associative : les réductions GPU sont non déterministes par défaut. Tester avec des tolérances, jamais avec l'égalité.
- Le débit FP64 varie d'un facteur 25 entre carte grand public et carte de centre de données. À vérifier avant tout projet scientifique.
Vérifiez que vous avez compris¶
Pourquoi accumuler en FP32 un produit de matrices BF16 plutôt qu'en BF16 ?
Une GEMM de dimension interne \(K\) effectue \(K\) additions successives sur chaque élément du résultat. Avec \(K = 4096\) et une précision relative BF16 de \(3{,}9 \times 10^{-3}\), l'erreur accumulée est de l'ordre de \(\sqrt{4096} \times 3{,}9\times 10^{-3} \approx 0{,}25\) en relatif — soit 25 %. Inutilisable.
En accumulant en FP32 (\(\varepsilon = 1{,}2 \times 10^{-7}\)), on obtient \(\sqrt{4096} \times 1{,}2 \times 10^{-7} \approx 7{,}7 \times 10^{-6}\). C'est pour cela que tous les tensor cores accumulent plus large que leurs entrées.
Un modèle quantifié en FP4 occupe 4× moins de mémoire qu'en FP16. Va-t-il pour autant 4× plus vite en décodage à lot 1 ?
En première approximation, oui — et c'est le point important. Le décodage à lot 1 est presque entièrement limité par la lecture des poids depuis la HBM. Diviser leur volume par 4 divise le temps par ~4.
En pratique on n'atteint pas 4× à cause : des facteurs d'échelle (surcoût de ~3 % en mémoire, plus le coût de déquantification), du cache KV qui reste souvent en plus haute précision, des activations et des couches laissées en 16 bits, et du plancher fixé par la latence de lancement des noyaux — qui devient d'autant plus visible que le calcul raccourcit. C'est exactement le régime où les megakernels sont rentables.
Votre noyau donne 3,1415927 sur CPU et 3,1415925 sur GPU. Bug ou pas ?
Probablement pas un bug. Deux causes normales :
- L'ordre des opérations diffère — la réduction parallèle GPU n'additionne pas dans le même ordre que la boucle CPU. L'écart de 2 × 10⁻⁷ est exactement l'ordre de grandeur de la précision FP32.
- Le GPU a fusionné une multiplication et une addition en FMA, qui arrondit une seule fois au lieu de deux — et donne donc un résultat plus précis que le CPU.
Le test correct est assert_close avec rtol=1e-5, pas l'égalité.
Si vous avez besoin d'une reproductibilité bit à bit, compilez avec
-fmad=false (et acceptez la perte de performance).
Partie suivante : Modèle de programmation
Sources de ce chapitre¶
- CUDA C++ Programming Guide — Mathematical Functions et Compute Capabilities
- Train With Mixed Precision, NVIDIA Deep Learning Performance Guide
- OCP Microscaling Formats (MX) Specification
- What is FP8 Quantization? pour l'état du support matériel en 2026.
- Microbenchmarking NVIDIA's Blackwell Architecture, arXiv:2512.02189 pour les débits par précision.
- CUDA 13.1 release notes pour les réductions déterministes de CCCL 3.1.