BF16 (BFloat16)
BF16 (BFloat16, ou Brain Floating Point 16) est un format de nombre flottant sur 16 bits développé par Google Brain, qui conserve la plage dynamique du FP32 (8 bits d’exposant) tout en réduisant la précision (7 bits de mantisse), éliminant les problèmes d’overflow/underflow qui affectent le FP16 et rendant l’entraînement en précision réduite quasi-transparent.
BF16 est le format standard de fait pour l’entraînement de tous les LLM modernes. GPT-5.4, Claude Opus 4.6, Gemini 3.1 Pro, Llama 3, Mistral Large 3 : tous sont entraînés en BF16 mixed precision. Son adoption massive s’explique par un avantage simple : BF16 est un remplacement quasi-direct (drop-in) de FP32 qui ne nécessite ni loss scaling, ni ajustement d’hyperparamètres, ni GradScaler. Vous activez BF16 et ça fonctionne.
- Nom complet
- Brain Floating Point 16 (BFloat16)
- Origine
- Google Brain (2018, pour les TPU)
- Taille
- 16 bits (2 octets)
- Structure
- 1 bit signe | 8 bits exposant | 7 bits mantisse
- Plage
- ~1,18 × 10⁻³⁸ à ~3,39 × 10³⁸ (identique à FP32)
- Précision
- ~2-3 chiffres significatifs
- GPU NVIDIA
- Ampere+ (A100, H100, RTX 3090+)
- Autres HW
- Google TPU v2+, AMD MI250+, Intel Sapphire Rapids+
- PyTorch
torch.bfloat16- Standard IEEE 754
- Format valide mais non standard (pas dans IEEE 754-2008)
Pourquoi Google a inventé BF16
Au milieu des années 2010, les chercheurs de Google Brain entraînaient des réseaux de neurones en FP32 et cherchaient à réduire les coûts. FP16 existait déjà (format IEEE 754 standard), mais posait des problèmes de stabilité : sa plage dynamique limitée (max ~65 504) provoquait des overflow et underflow fréquents, nécessitant un loss scaling complexe.
L’observation clé de l’équipe Google Brain a été que les réseaux de neurones sont bien plus sensibles à la taille de l’exposant (qui détermine la plage de valeurs) qu’à celle de la mantisse (qui détermine la précision). En d’autres termes, il vaut mieux pouvoir représenter des nombres très grands et très petits avec peu de précision, que des nombres d’une plage limitée avec beaucoup de précision.
Partant de cette observation, ils ont créé BF16 en tronquant simplement les 16 bits les plus significatifs d’un nombre FP32. Résultat : un format qui conserve les 8 bits d’exposant de FP32 (plage identique) mais réduit la mantisse de 23 à 7 bits. La conversion entre FP32 et BF16 est triviale : on tronque (ou arrondit) les 16 bits de poids faible de la mantisse FP32. C’est cette simplicité de conversion qui fait de BF16 un vrai « drop-in replacement » pour FP32.
Structure du format BF16
Les 16 bits de BF16 sont répartis ainsi :
1 bit de signe (S). Identique à FP32 et FP16.
8 bits d’exposant (E). Avec un biais de 127 (identique à FP32). L’exposant réel va de -126 à +127. C’est ce qui donne à BF16 sa plage dynamique de ~1,18 × 10⁻³⁸ à ~3,39 × 10³⁸, exactement comme FP32.
7 bits de mantisse (M). Avec un « 1 » implicite en tête pour les nombres normalisés, cela donne 8 bits de significande au total. La précision est d’environ 2 à 3 chiffres décimaux.
| Propriété | BF16 | FP16 | FP32 |
|---|---|---|---|
| Taille totale | 16 bits | 16 bits | 32 bits |
| Bits d’exposant | 8 | 5 | 8 |
| Bits de mantisse | 7 | 10 | 23 |
| Valeur max | ~3,39 × 10³⁸ | 65 504 | ~3,40 × 10³⁸ |
| Plus petit normalisé | ~1,18 × 10⁻³⁸ | ~6,10 × 10⁻⁵ | ~1,18 × 10⁻³⁸ |
| Précision relative | ~0,8% | ~0,1% | ~0,00001% |
| Loss scaling requis | Non | Oui | Non |
Le point essentiel : BF16 a moins de précision que FP16 (7 bits de mantisse vs 10), mais une plage dynamique immensément plus grande. Pour le deep learning, cette plage est bien plus importante que la précision, car les overflow et underflow provoquent des NaN et des divergences catastrophiques, tandis que de légères erreurs d’arrondi sont absorbées sans effet par la nature stochastique de l’entraînement.
Pourquoi BF16 est le standard pour les LLM
Remplacement quasi-direct de FP32
L’avantage principal de BF16 est l’absence de « surprises numériques ». Grâce à sa plage dynamique identique à FP32, les gradients ne font pas d’underflow, les activations ne font pas d’overflow, et les valeurs spéciales (NaN, inf) se comportent comme en FP32. Vous pouvez prendre un script d’entraînement FP32 qui fonctionne, activer BF16, et il fonctionnera sans aucun ajustement.
L’étude de Kalamkar et al. (Intel, 2019) a démontré cette propriété sur une large variété de workloads : classification d’images (ResNet, VGG), reconnaissance vocale (GNMT), modèles de langage, GANs (DC-GAN, SR-GAN) et systèmes de recommandation industriels. Dans tous les cas, BF16 a atteint une parité numérique avec FP32 sans aucun ajustement d’hyperparamètres, là où FP16 nécessitait un tuning du loss scaling pour chaque modèle.
Pas de GradScaler nécessaire
En FP16, le GradScaler de PyTorch est indispensable pour éviter l’underflow des gradients. C’est une source de complexité et de bugs : le facteur d’échelle qui s’effondre, les steps sautés, les interactions avec le gradient clipping. En BF16, tout cela disparaît. Le code d’entraînement est plus simple, plus fiable et plus facile à débugger.
# BF16 : simple et propre, pas de GradScaler
from torch.amp import autocast
for batch in dataloader:
optimizer.zero_grad()
with autocast(device_type="cuda", dtype=torch.bfloat16):
loss = model(batch)
loss.backward()
optimizer.step()
Conversion FP32 ↔ BF16 triviale
Convertir un nombre FP32 en BF16 revient à tronquer les 16 bits de poids faible de la mantisse (ou à arrondir au plus proche pair, selon l’implémentation matérielle). L’inverse (BF16 → FP32) consiste à ajouter 16 zéros. Cette simplicité réduit le coût matériel de la conversion et facilite l’interopérabilité entre les formats.
Support matériel
BF16 bénéficie d’un support matériel large et croissant :
| Fabricant | Architecture/Produit | Support BF16 | Débit Tensor Core BF16 |
|---|---|---|---|
| TPU v2/v3/v4/v5 | Natif (format d’origine) | Jusqu’à ~460 TFLOPS (TPU v5e) | |
| NVIDIA | Ampere (A100) | Oui (Tensor Cores 3e gen) | ~312 TFLOPS |
| NVIDIA | Hopper (H100) | Oui (Tensor Cores 4e gen) | ~990 TFLOPS |
| NVIDIA | Ada Lovelace (RTX 4090) | Oui | ~330 TFLOPS |
| AMD | CDNA 2/3 (MI250/MI300) | Oui (Matrix Cores) | ~383 TFLOPS (MI300X) |
| Intel | Sapphire Rapids+ (CPU) | Oui (AVX-512 BF16) | Accélération CPU (~2x vs FP32) |
| Apple | M2+ (Neural Engine) | Oui | Variable selon le chip |
Les limites de la précision BF16
Avec seulement 7 bits de mantisse, BF16 a une précision limitée. Quelques exemples concrets de son comportement :
Entre 1 et 2, BF16 ne distingue que 128 valeurs (2⁷). Le pas entre deux nombres consécutifs est de 1/128 ≈ 0,0078. Cela signifie que 1.0 + 0.003 = 1.0 en BF16 (l’addition est arrondie).
Entre 256 et 512, le pas est de 2 (256 × 2⁻⁷). Les nombres 257 et 258 ne sont pas distinguables en BF16.
Pour les poids du modèle, cette précision est largement suffisante. Les poids d’un réseau de neurones varient typiquement entre -2 et +2, et les différences significatives sont de l’ordre de 10⁻² à 10⁻³, bien au-dessus de la résolution BF16 dans cette plage.
Pour les accumulations (sommes de nombreuses valeurs), la précision limitée peut poser problème. C’est pourquoi les opérations de réduction (somme des gradients, moyennes dans les normalisations, calcul de loss) sont effectuées en FP32 même quand le reste du calcul est en BF16. Les Tensor Cores de NVIDIA effectuent les multiplications en BF16 mais accumulent les résultats en FP32, combinant le débit du 16 bits avec la précision du 32 bits.
Usage pratique
PyTorch
# Vérifier le support BF16
print(torch.cuda.is_bf16_supported()) # True sur Ampere+
# Entraînement avec autocast BF16
from torch.amp import autocast
with autocast(device_type="cuda", dtype=torch.bfloat16):
output = model(input_tensor)
loss = criterion(output, target)
# Conversion explicite
tensor_bf16 = tensor_fp32.to(torch.bfloat16)
model_bf16 = model.to(torch.bfloat16)
Hugging Face
from transformers import TrainingArguments
args = TrainingArguments(
output_dir="./output",
bf16=True, # Active BF16 mixed precision
# fp16=False, # Mutuellement exclusif avec bf16
)
DeepSpeed
{
"bf16": {
"enabled": true
},
"zero_optimization": {
"stage": 2
}
}
Avec DeepSpeed, BF16 est compatible avec tous les stages de ZeRO et avec l’offloading CPU. Les gradients communiqués en mode distribué sont en BF16, réduisant le volume de communication de 50% par rapport à FP32.
BF16 pour l’inférence
BF16 est aussi utilisé pour l’inférence, mais moins systématiquement que pour l’entraînement. La raison : pour l’inférence, il n’y a pas de gradients ni de mise à jour de poids, donc les problèmes d’overflow/underflow de FP16 sont moins critiques. FP16 et BF16 offrent des performances d’inférence similaires sur Tensor Cores.
Pour l’inférence sur des GPU consommateurs plus anciens (RTX 2080, Turing), FP16 est parfois préféré car mieux supporté. Sur Ampere+ (A100, RTX 3090/4090), les deux formats ont le même débit.
La quantization (INT8, INT4) est souvent préférée à BF16 pour l’inférence en production, car elle offre des gains de mémoire et de vitesse supérieurs. Un modèle 7B en BF16 pèse ~14 Go, en INT8 ~7 Go, en INT4 ~3,5 Go. Les formats GGUF utilisés par llama.cpp et Ollama supportent nativement la conversion depuis BF16.
BF16 vs FP8 : le futur de la précision
FP8 (8 bits) est le format émergent pour le deep learning, supporté par les GPU NVIDIA Hopper (H100) et au-delà. Il offre un débit ~2x supérieur à BF16 sur Tensor Cores. DeepSeek V3 a utilisé FP8 pour les couches linéaires lors de son entraînement.
Cependant, FP8 ne réduit pas significativement la mémoire d’entraînement (les poids maîtres et les états optimiseur restent en FP32). Son avantage est purement en débit de calcul, et il ne devient rentable qu’à très grande échelle. Pour la majorité des praticiens, BF16 reste le choix optimal : il combine simplicité, stabilité et gains de performance substantiels sans les complications du FP8 (choix de format E4M3/E5M2, calibration de scaling par tenseur).
FP8 existe en deux variantes : E4M3 (4 bits exposant, 3 bits mantisse, plage max ~448) pour le forward pass, et E5M2 (5 bits exposant, 2 bits mantisse, plage max ~57 344) pour le backward. Cette dualité illustre bien le compromis fondamental entre plage et précision que BF16 a résolu de façon plus élégante en conservant les 8 bits d’exposant du FP32.
BF16 en entraînement distribué
En data parallelism distribué, BF16 réduit le volume de communication de 50% par rapport à FP32. Les gradients communiqués via all-reduce entre GPU sont en BF16 (16 bits au lieu de 32), ce qui est un avantage significatif sur les clusters multi-noeuds où la bande passante réseau est le facteur limitant.
Avec FSDP (PyTorch) ou ZeRO de DeepSpeed, les paramètres fragmentés sont stockés et communiqués en BF16. Seule la copie maître utilisée pour les mises à jour est en FP32. Cette approche maximise les économies de mémoire et de bande passante tout en préservant la précision des mises à jour.
Avec le tensor parallelism, les all-reduce internes (entre les GPU qui se partagent une couche) sont aussi en BF16. Megatron-LM et NeMo supportent nativement la mixed precision BF16 combinée avec le TP, le PP et le DP.
BF16 pour le fine-tuning de LLM
Le fine-tuning en BF16 est le scénario le plus courant pour les praticiens. Que vous utilisiez un full fine-tuning ou des méthodes à faible rang comme LoRA, BF16 est le format recommandé :
Full fine-tuning en BF16 : les poids du modèle sont chargés en BF16, les calculs s’exécutent en BF16 via autocast, et les poids maîtres sont mis à jour en FP32. L’économie mémoire sur les activations et gradients (par rapport à FP32) permet d’utiliser un batch size plus grand ou un modèle plus grand.
LoRA en BF16 : le modèle de base est chargé en BF16 (gelé), seuls les adaptateurs LoRA sont entraînés. Les calculs passent par les Tensor Cores BF16 pour les couches linéaires du modèle de base et les adaptateurs. Cette combinaison (LoRA + BF16) est la configuration standard pour le fine-tuning de modèles 7B à 70B sur du matériel consommateur.
QLoRA en BF16 : le modèle de base est quantizé en NF4 (4 bits), les adaptateurs LoRA sont en BF16, et les calculs sont effectués après déquantization vers BF16. Cela permet d’entraîner des modèles 70B sur un seul GPU 24 Go.
Questions fréquentes sur le BF16
Pourquoi BF16 s’appelle « Brain » Floating Point ?
Le nom vient de « Google Brain », le groupe de recherche en IA de Google (aujourd’hui intégré dans Google DeepMind) qui a conçu le format pour les TPU Cloud. L’idée initiale a émergé du constat que les 16 bits de poids faible de la mantisse FP32 n’étaient pas nécessaires pour l’entraînement de réseaux de neurones. Le format a été introduit publiquement avec les TPU v2 en 2018.
BF16 a-t-il moins de précision que FP16 ?
Oui. BF16 a 7 bits de mantisse contre 10 pour FP16, ce qui lui donne une résolution environ 8x moins fine. Concrètement, entre 1 et 2, BF16 distingue 128 valeurs tandis que FP16 en distingue 1024. En revanche, BF16 peut représenter des nombres jusqu’à ~3,4 × 10³⁸ tandis que FP16 sature à 65 504. Pour le deep learning, la plage (exponent) est bien plus critique que la précision (mantissa), ce qui fait de BF16 le meilleur choix malgré sa précision moindre.
Peut-on mélanger BF16 et FP16 dans le même entraînement ?
C’est techniquement possible mais rarement souhaitable. PyTorch AMP (autocast) vous demande de choisir un dtype (torch.bfloat16 ou torch.float16), et il n’est pas prévu de mélanger les deux dans le même modèle. Si vous utilisez BF16, restez entièrement en BF16 pour la partie 16 bits. Le mélange compliquerait le code sans bénéfice clair, car BF16 et FP16 ont le même débit sur Tensor Cores Ampere+.
Les modèles Hugging Face en « torch.float16 » sont-ils en FP16 ou BF16 ?
En FP16. Sur Hugging Face, torch_dtype=torch.float16 charge le modèle en FP16, et torch_dtype=torch.bfloat16 le charge en BF16. Beaucoup de modèles sont distribués en FP16 pour compatibilité maximale (tous les GPU modernes supportent FP16). Pour l’entraînement, vous pouvez charger un modèle FP16 et activer BF16 via autocast : les poids seront castés en BF16 pour le calcul.
Mon GPU RTX 3060 supporte-t-il BF16 ?
Les RTX 3060, 3070, 3080 et 3090 (architecture Ampere) supportent toutes BF16 en calcul CUDA et via autocast. Cependant, le support BF16 sur les Tensor Cores des cartes GA10x (3060/3070/3080) est parfois moins optimisé que sur les GPU data center (A100) ou les RTX 3090 (GA102). En pratique, activez BF16 et mesurez le throughput : si le gain est inférieur à celui de FP16 sur votre carte spécifique, FP16 peut être marginalement meilleur. Vérifiez avec torch.cuda.is_bf16_supported().