On the GPU
What is on the GPU
A bf16 weight is a sign bit, 8 exponent bits and 7 mantissa bits. In a trained model the sign and mantissa are noise and the exponent carries about 2.6 bits of information (why). Every format keeps the sign and mantissa byte as it is and codes the exponent:
| Format | The exponent | Bits a weight | Decode |
|---|---|---|---|
mma, the tiered layout |
2-bit digits in tiers: the tensor’s 3 commonest exponents, the next 3, then the next 3, then the exponent itself, in the order the tensor cores take their operand | 10.80 | in registers, straight into the tensor cores |
mma12, the 12-bit layout |
a 4-bit code into the tensor’s 15 commonest exponents, the rest in a step’s exception list | 12.04 | three byte permutes per four weights |
fast |
a 3-bit code into the tensor’s 7 commonest exponents, an escape to the exponent itself | 11.25 | bit operations, every weight in parallel |
huffman |
a per-tensor prefix code, 32 streams a tile | 10.88 | a lane decodes its stream in turn |
Bits a weight on Qwen2.5. No code that sees each tensor’s exponents on their own gets below about 10.6 bits a weight. The two layouts are the ones this site measures; fast is e2e.py’s default format.
For a generated token (a few tokens a step) the product is fused: the kernel reads the packed weights, decodes them in registers and multiplies, and bf16 weights are never written out. Prompts run a tiled kernel that decodes each weight once for many tokens. On Hopper, steps of 17 to 128 tokens in the 12-bit layout go through the copy engine (TMA) and wgmma; on GDDR Ampere and Ada, steps of 17 to 64 tokens through the same plan with that generation’s instructions. Every product sums in a fixed order: the same result every run.
Two layouts, and which GPU runs which
The tiered layout takes the most off and costs the most arithmetic to decode. Where memory is the limit, as on an RTX 4080 SUPER at a few tokens a step, it is also the faster. Where the GPU’s memory outruns the decode (an H100’s HBM3, or many tokens a step), the 12-bit layout keeps up. GPU time a generated token, Qwen2.5-7B-Instruct, q, k, v and gate, up merged, at 1 / 8 / 32 / 64 sequences a step:
| GPU | bf16 | tiered (mma, 10.80 bits) |
12-bit (mma12, 12.04 bits) |
|---|---|---|---|
| RTX 4080 SUPER 16 GB | 21.97 / 22.85 / 26.19 / 27.67 ms | 16.52 / 17.37 / 18.80 / 25.14 | 17.48 / 18.25 / 19.67 / 21.66 |
| A10 24 GB | 33.71 / 34.77 / 35.50 / 37.98 ms | 24.33 / 25.61 / 35.99 / 49.66 | 24.40 / 25.76 / 29.03 / 33.05 |
| A100 40 GB | 14.18 / 14.87 / 16.18 / 17.80 ms | 15.34 / 16.00 / 23.99 / 28.81 | 11.84 / 13.77 / 17.16 / 19.95 |
| H100 SXM 80 GB | 6.90 / 7.50 / 8.02 / 8.55 ms | 6.55 / 7.24 / 8.69 / 9.99 |
On Ada the tiered layout, the smallest, is also the fastest to 32 sequences; on an A10 the 12-bit one is 13 to 28% under bf16’s time at every count; on an A100 and an H100 the 12-bit one is under bf16’s time to 8 sequences and 6 to 17% over at 32 and 64, where their small matrices cost more than bf16’s. glyd_gpu.best_layout(), and e2e.py --format auto, take the tiered layout on Ada and wherever only it fits, the 12-bit one elsewhere. The site’s benchmarks show each GPU with the layout it picks.
e2e.py, option by option
python e2e.py MODEL_DIR [options], from the repository’s gpu/ folder:
| Option | What it does |
|---|---|
--format |
fast (the default), huffman, mma (the tiered layout, up to 64 tokens a step multiplied straight from it), mma12 (the 12-bit layout) or auto (the one for this GPU) |
--fused |
multiply straight from the packed weights |
--baseline |
run the model in bf16 too, in the same process, and compare |
--batch 1,8,32 |
generate for this many copies of the prompt at once, each size measured (default 1) |
--tokens 128 |
new tokens to generate (default 128) |
--merge |
the Linears that take the same input (q, k, v; gate, up) as one product each, for bf16 and Glyd alike, as serving engines run them |
--profile 16 |
GPU time by kernel over this many generated tokens at each --batch size: a step after the prompt, and the prompt’s |
--prefill 64,512,2048 |
prompt lengths to time one forward pass at |
--ppl FILE |
perplexity over windows of --ppl-window tokens (default 64) from the file’s 10th MB, and how often the next-token choice is bf16’s |
--mmlu 300 |
MMLU accuracy over this many questions, 0-shot, by the answer letter’s logit |
--smi PREFIX |
once the model is on its GPUs, nvidia-smi’s report into PREFIX-bf16.txt and PREFIX-glyd.txt |
--gpus 2 |
spread the layers over this many GPUs (bf16 by accelerate’s device map, Glyd balanced by packed size) |
--gpu-mem 40 |
GiB of bf16 weights a GPU may hold in the baseline’s device map (default all but 2 GiB) |
--kv 1024,4096 |
prompt lengths (tokens of enwik8, needs --ppl) to generate after with the KV cache compressed, against the plain cache |
--gemm-max 64 |
with fast, steps of up to this many tokens multiply straight from the packed weights |
Repeating a benchmark
Every number on this site comes from a run of these scripts, and every run’s log is in the repository: benchmarks/gpu. The Qwen3.8 27B run on an H100 PCIe, for one, holds the machine (machine.txt: the GPU, its memory, the driver), the environment (setup.txt: PyTorch and CUDA), the commands (run.log) and each script’s output. It ran:
python e2e.py models/Qwen3.8-27B --fused --tokens 64 --batch 1,8,32 --prefill 64,128,512,2048 \
--ppl enwik8 --mmlu 300 --merge --format auto --baseline --gpus 1 --profile 16 \
--smi results/smi-Qwen3.8-27B-auto-x1
- GPU time a token (benchmarks):
--fused --merge --baseline --batch 1,8,32,64 --profile 16, with--formatfor the layout. The time is a step’s after the prompt: 17 steps less 1, over 16. - Memory in use (Qwen3-32B on one 48 GB GPU):
--smi PREFIX, with--gpus Nwhere bf16 needs more than one GPU. - Quality (the nineteen models):
--baseline --ppl enwik8 --mmlu 300. - Sizes:
python sizes.py MODEL_DIR ..., every Linear layer’s matrix in both layouts, bit for bit. - The products alone:
python gemm.py MODEL_DIR 1,16,64times every product of one layer against bf16’s;python shapes.py MODEL_DIRthe fused product;python check.py model.safetensorspacks, unpacks and compares every tensor;python kv.pychecks the compressed KV cache against the plain one.
The rented-GPU runs use scripts/gpu_lambda.sh: one Lambda Cloud instance a run, terminated at the end, its logs copied into benchmarks/gpu/.