Paul FOLTZEREmbedded systems · AI
FR
All projects
Personal projectCompletedMay – June 2026

TransformerCpp

An encoder-decoder Transformer (English → French translation) written entirely by hand in C++17, trained on GPU through a home-made CUDA backend.

A from-scratch implementation of the Transformer architecture (“Attention Is All You Need”) for English → French translation: all the algebra, the layers, backpropagation, the Adam optimiser, the tokenizer and the GPU backend are written by hand, with no deep learning library.

At the core sits a reference CPU implementation where every layer (attention, multi-head, feed-forward, layer norm, embeddings, encoder/decoder blocks) exposes a forward and a backward pass derived and coded by hand, validated by gradient checking.

TransformerCpp — anatomy of a block ENCODER BLOCK ×N DECODER BLOCK ×N x — (B·n, d_model) y — (B·n_tgt, d_model) Multi-Head Attention (self) block-diagonal + padding mask · strided-batched Dropout → ⊕ residual → LayerNorm Feed-Forward Linear → ReLU → Linear Dropout → ⊕ residual → LayerNorm block output Masked Multi-Head Attention causal + padding mask Dropout → ⊕ residual → LayerNorm Cross-Attention Q = decoder · K, V = enc_out Dropout → ⊕ residual → LayerNorm Feed-Forward Linear → ReLU → Linear Dropout → ⊕ residual → LayerNorm → projection → logits enc_out encoder output
Anatomy of a block: self-attention then feed-forward, each followed by dropout, a residual connection and a LayerNorm. The decoder additionally inserts cross-attention whose keys and values come from the encoder output.

A device-resident CUDA backend reproduces exactly the same computations but keeps the model living and training entirely on the GPU (cuBLAS for matrix products, custom kernels for softmax, layer norm and embedding scatter), with every GPU layer validated by an equivalence test against its CPU counterpart. Moving to block-diagonal batching on the GPU took training throughput from roughly 30 to 900 examples per second.

TransformerCpp — two backends, one logic CPU REFERENCE Matrix · explicit loops · gradients derived by hand DEVICE MIRROR GpuMatrix · cuBLAS · CUDA kernels Linear y = xW + b MultiHeadAttention W_Q, W_K, W_V per head · W_O Transformer forward · backward · Adam GpuLinear cuBLAS matmul + add_bias GpuMultiHeadAttention strided-batched attention GpuTransformer + CUDA Graphs · DevicePool built from built from built from — same weights — EQUIVALENCE TEST same inputs → same outputs (tolerance 1e-2) · run on every layer translate_model.bin write_matrix / read_matrix — identical format interchangeable CPU ↔ GPU
Two implementations, one logic: every CPU layer (Matrix, explicit loops) has its GPU mirror (GpuMatrix, cuBLAS, CUDA kernels) built from the same weights. An equivalence test compares their outputs layer by layer, and an identical binary checkpoint format makes the two backends interchangeable.

The project also includes a byte-level BPE tokenizer, a data pipeline (parallel corpus, teacher forcing, batching, padding), binary weight checkpointing interchangeable between CPU and GPU, autoregressive generation (greedy, then beam search), and a thorough test suite: 140 cases and around 9,000 assertions.

TransformerCpp — a batch's journey HOST — RAM fra.txt raw TSV corpus load_parallel_tsv lowercase · filter BPETokenizer text → ids · BOS/EOS make_examples teacher forcing bucketed_batches sorted by length token ids · a few KB the corpus stays in RAM DEVICE — VRAM (weights and activations resident) EAGER GpuEmbedding src → (B·n_src, d) GpuEmbedding dec_in → (B·n_tgt, d) masks block · causal · padding CAPTURED REGION — cudaGraphExec, cached per shape (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 accumulated in place embedding backward Adam · lr(warmup → 1/√step) updates the resident weights GpuDropout cuRAND mask generated eagerly, read by the graph
A batch's complete journey: the corpus is tokenized and grouped into length-sorted batches on the host, then only token ids travel to the GPU. A region captured as a CUDA Graph chains encoder, decoder, loss and backpropagation there without returning to the host, before the Adam update — the mechanism behind the ×30 throughput gain.

To drive training runs and try the model without the command line, a companion application (TrainerApp) was built on top of the library: a C++17/ImGui graphical interface with Models, Configuration, Training and Inference tabs. The Training tab shows training and validation loss live, the BLEU-4 score and the translations generated at each epoch, alongside GPU memory and utilisation. It has since been extended to drive the CNN from the ConvNetwork project too, becoming a small multi-model training platform.

TrainerApp's Training tab showing live loss curves and BLEU score
TrainerApp — Training tab: train/validation loss, BLEU-4 score (35 % at epoch 31) and translations generated live.
TrainerApp's Inference tab showing English to French translations
TrainerApp — Inference tab: translations produced by the model, with request history.
TransformerCpp under TrainerApp TrainerApp — C++ · Dear ImGui Models Configuration Training Inference Logs Live GPU: temperature, VRAM, utilisation train / val loss curves · BLEU-4 curve · translations per epoch · CSV export configuration · start · sentence to translate loss · BLEU · ex/s · translations transformer_lib — statically linked, same process TRAINING make_bucketed_batches train_step_graphed update(noam_lr) EVALUATION forward (validation) greedy decoding corpus_bleu INFERENCE tok.encode (lowercase) beam_search tok.decode kernels · memory save-best · load CUDA — VRAM DevicePool CUDA Graphs cuBLAS · cuRAND custom kernels weights, activations and Adam state resident · allocations recycled, bounded budget DISK translate_model.bin translate_tok.bin
TrainerApp drives transformer_lib in the same process for three uses: training (batches → graphed pass → Adam), evaluation (BLEU) and inference (beam search). The library relies on VRAM (DevicePool, CUDA Graphs, cuBLAS) and on disk checkpoints, and returns the metrics that feed the interface's curves.

Key points

  • Backpropagation derived and hand-coded for every layer, verified by gradient checking
  • Device-resident CUDA backend (cuBLAS, custom kernels) validated layer by layer against the CPU
  • BPE tokenizer, data pipeline and CPU/GPU binary checkpointing all written from scratch
  • Companion GUI (TrainerApp, ImGui): live training monitoring (loss, BLEU-4) and interactive inference