Paul FOLTZERSystèmes embarqués · IA
EN
Tous les projets
Projet personnelTerminéMai – Juin 2026

TransformerCpp

Un Transformer encodeur-décodeur (traduction anglais → français) écrit intégralement à la main en C++17, entraîné sur GPU via un backend CUDA maison.

Implémentation from scratch de l'architecture Transformer (« Attention Is All You Need ») pour la traduction anglais → français : toute l'algèbre, les couches, la rétropropagation, l'optimiseur Adam, le tokenizer et le backend GPU sont écrits à la main, sans bibliothèque de deep learning.

Le cœur du projet est une implémentation CPU de référence où chaque couche (attention, multi-têtes, feed-forward, layer norm, embeddings, blocs encodeur/décodeur) expose un forward et un backward dérivés et codés à la main, validés par gradient checking.

TransformerCpp — anatomie d'un bloc BLOC ENCODEUR ×N BLOC DÉCODEUR ×N x — (B·n, d_model) y — (B·n_tgt, d_model) Multi-Head Attention (self) masque bloc-diagonal + padding · strided-batched Dropout → ⊕ résiduelle → LayerNorm Feed-Forward Linear → ReLU → Linear Dropout → ⊕ résiduelle → LayerNorm sortie du bloc Masked Multi-Head Attention masque causal + padding Dropout → ⊕ résiduelle → LayerNorm Cross-Attention Q = décodeur · K, V = enc_out Dropout → ⊕ résiduelle → LayerNorm Feed-Forward Linear → ReLU → Linear Dropout → ⊕ résiduelle → LayerNorm → projection → logits enc_out sortie de l'encodeur
Anatomie d'un bloc : self-attention puis feed-forward, chacun suivi d'un dropout, d'une connexion résiduelle et d'une LayerNorm. Le décodeur insère en plus une attention croisée dont les clés et valeurs proviennent de la sortie de l'encodeur.

Un backend CUDA device-resident reproduit exactement les mêmes calculs mais fait vivre et entraîner le modèle entièrement sur le GPU (cuBLAS pour les produits matriciels, kernels maison pour softmax, layer norm et embedding scatter), chaque couche GPU étant validée par un test d'équivalence contre son homologue CPU. Le passage à un batching bloc-diagonal sur GPU a fait passer le débit d'entraînement d'environ 30 à 900 exemples par seconde.

TransformerCpp — deux backends, une seule logique RÉFÉRENCE CPU Matrix · boucles explicites · gradients dérivés à la main MIROIR DEVICE GpuMatrix · cuBLAS · kernels CUDA Linear y = xW + b MultiHeadAttention W_Q, W_K, W_V par tête · W_O Transformer forward · backward · Adam GpuLinear matmul cuBLAS + add_bias GpuMultiHeadAttention attention strided-batched GpuTransformer + CUDA Graphs · DevicePool construit depuis construit depuis construit depuis — mêmes poids — TEST D'ÉQUIVALENCE mêmes entrées → mêmes sorties (tolérance 1e-2) · exécuté sur chaque couche translate_model.bin write_matrix / read_matrix — format identique interchangeable CPU ↔ GPU
Deux implémentations, une seule logique : chaque couche CPU (Matrix, boucles explicites) a son miroir GPU (GpuMatrix, cuBLAS, kernels CUDA) construit avec les mêmes poids. Un test d'équivalence compare leurs sorties couche par couche, et un format de checkpoint binaire identique rend les deux backends interchangeables.

Le projet inclut aussi un tokenizer BPE byte-level, un pipeline de données (corpus parallèle, teacher forcing, batching, padding), le checkpointing binaire des poids compatible CPU/GPU, la génération auto-régressive (glouton puis beam search), et une suite de tests exhaustive : 140 cas et environ 9000 assertions.

TransformerCpp — le trajet d'un batch HÔTE — RAM fra.txt corpus TSV brut load_parallel_tsv minuscules · filtre BPETokenizer texte → ids · BOS/EOS make_examples teacher forcing bucketed_batches tri par longueur ids de tokens · quelques Ko le corpus reste en RAM DEVICE — VRAM (poids et activations résidents) EAGER GpuEmbedding src → (B·n_src, d) GpuEmbedding dec_in → (B·n_tgt, d) masques bloc · causal · padding RÉGION CAPTURÉE — cudaGraphExec, cache par forme (n_src, n_tgt, B) + PosEnc GpuEncoder ×N self-attention + PosEnc GpuDecoder ×N causal + cross GpuLinear → logits cross-entropy + label smoothing enc_out (K, V) backward_compute — gradients accumulés in-place embedding backward Adam · lr(warmup → 1/√pas) met à jour les poids résidents GpuDropout masque cuRAND généré en eager, le graphe le lit
Le trajet complet d'un batch : le corpus est tokenisé et regroupé en batches triés par longueur côté hôte, puis seuls les identifiants de tokens partent sur le GPU. Une région capturée en CUDA Graph y enchaîne encodeur, décodeur, perte et rétropropagation sans repasser par l'hôte, avant la mise à jour Adam — le mécanisme derrière le gain de débit ×30.

Pour piloter les entraînements et tester le modèle sans ligne de commande, une application compagnon (TrainerApp) a été développée par-dessus la bibliothèque : une interface graphique en C++17/ImGui avec des onglets Modèles, Configuration, Entraînement et Inférence. L'onglet Entraînement affiche en direct la perte train/validation, le score BLEU-4 et les traductions générées à chaque epoch, aux côtés de la mémoire et de l'utilisation GPU. Elle a depuis été étendue pour piloter également le CNN du projet ConvNetwork, devenant une petite plateforme d'entraînement multi-modèles.

Onglet Entraînement de TrainerApp montrant les courbes de perte et le score BLEU en direct
TrainerApp — onglet Entraînement : perte train/val, score BLEU-4 (35 % à l'epoch 31) et traductions générées en direct.
Onglet Inférence de TrainerApp montrant des traductions anglais vers français
TrainerApp — onglet Inférence : traductions générées par le modèle et historique des requêtes.
TransformerCpp sous TrainerApp TrainerApp — C++ · Dear ImGui Modèles Configuration Entraînement Inférence Logs GPU live : température, VRAM, utilisation courbes perte train / val · courbe BLEU-4 · traductions par epoch · export CSV configuration · démarrage · phrase à traduire perte · BLEU · ex/s · traductions transformer_lib — liée statiquement, même processus ENTRAÎNEMENT make_bucketed_batches train_step_graphed update(noam_lr) ÉVALUATION forward (validation) décodage glouton corpus_bleu INFÉRENCE tok.encode (minuscules) beam_search tok.decode kernels · mémoire save-best · load CUDA — VRAM DevicePool CUDA Graphs cuBLAS · cuRAND kernels maison poids, activations et état Adam résidents · allocations recyclées, budget borné DISQUE translate_model.bin translate_tok.bin
TrainerApp pilote transformer_lib dans le même processus pour trois usages : entraînement (batches → passe graphée → Adam), évaluation (BLEU) et inférence (beam search). La bibliothèque s'appuie sur la VRAM (DevicePool, CUDA Graphs, cuBLAS) et sur des checkpoints disque, et renvoie les métriques qui alimentent les courbes de l'interface.

Points clés

  • Rétropropagation dérivée et codée à la main pour chaque couche, vérifiée par gradient checking
  • Backend CUDA device-resident (cuBLAS, kernels custom) validé couche par couche contre le CPU
  • Tokenizer BPE, pipeline de données et checkpointing binaire CPU/GPU écrits from scratch
  • Interface graphique compagnon (TrainerApp, ImGui) : suivi live de l'entraînement (perte, BLEU-4) et inférence interactive