GPUs, memory, and quantization
Why does GPU memory rule over everything else?
Lesson complete
If you like:
In the last lesson we counted operations. Today, a more basic question: a model with seventy billion parameters, where is it kept while it works?
No voice: the lesson advances on its own with subtitles.
The voice for this lesson is being recorded: for now it advances on its own with subtitles.
Review modeComment on this stepI finished the lesson
What you will achieve
- Calculate how much memory a model takes from its parameters and the bytes per parameter (FP32, FP16, INT8, INT4), and decide which GPU it fits in.
- Explain why training with Adam takes about sixteen bytes per parameter and why, when writing, the limit is usually memory bandwidth.
- Recognize the three basic ways to split a model across several GPUs, and what quantization gains and risks, with real measurements.
Key ideas
- A model works out of the GPU’s memory, its VRAM. It takes parameters × bytes per parameter: 70 billion in FP16 is 140 GB, more than an H100’s 80.
- Each format stores a number with more or less detail: FP32 (4 bytes, about 7 digits), FP16 (2 bytes, about 3), INT8 (1 byte), and INT4 (½ byte), which round to the marks of a ruler.
- Training with Adam takes about 16 bytes per parameter: the parameter and its gradient in FP16, a copy in FP32, and Adam’s two averages. Eight times what using it takes, not counting activations.
- Fitting is not enough: when writing, every token reads all the parameters, and memory bandwidth usually sets the speed, not computation.
- If it does not fit, you split it (data, pipeline, tensor; Llama 3.1 405B used 8 × 16 × 128 = 16,384 H100s) or quantize it. In Pythia, 8 and 4 bits barely change the loss; 2 bits break it.
What many people believe
Common belief If a model fits in my GPU’s memory, I can also train it there.
What is true Using it takes about 2 bytes per parameter in FP16; training it with Adam, about 16, plus activations. A model that barely fits for use needs about eight times more memory to train. That is why training is split across many GPUs, or only a small part of the parameters is tuned (lesson 27).
Check yourself
Four questions. No grade, no account: they just show whether the idea stuck.
Under the hoodFor those who want the math and the code. You do not need it to follow the course.
Bytes per parameter and formats
The memory the weights take is a multiplication:
with parameters and bytes per parameter. For Llama 3.1 70B in FP16: bytes GB.
Decimal formats (the IEEE 754 standard) store a sign, an exponent (the range), and a mantissa (the digits):
| format | bytes | sign · exponent · mantissa | decimal digits | largest value |
|---|---|---|---|---|
| FP32 | 4 | 1 · 8 · 23 | ||
| FP16 | 2 | 1 · 5 · 10 | 65,504 | |
| BF16 | 2 | 1 · 8 · 7 | same as FP32 |
BF16 (Kalamkar et al., 2019) gives up digits to keep FP32’s range; Llama 3 was trained in BF16. INT8 and INT4 have no exponent: they store an integer plus a shared scale (see below).
A detail about units: datasheets say “80 GB” of VRAM, but that is bytes (GiB), 7% more than . In this lesson, 1 GB = bytes, as with bandwidth; the difference changes no conclusion.
Training: sixteen bytes per parameter
With Adam in mixed precision (Micikevicius et al., 2018), each parameter needs the weight and gradient in FP16 and, in FP32, a copy of the weight and Adam’s two averages (Kingma and Ba, 2015). ZeRO (Rajbhandari et al., 2020, §3.1) sums it up:
The FP32 copy exists because each step’s update (gradient times learning rate) is often thousands of times smaller than the weight: added in FP16, with its 10 mantissa bits, it would be lost to rounding. We measured it on an RTX 3060 (measure-memory.py, the Pythia-160M architecture with random weights): 16.19 bytes per parameter in full FP32 and 16.05 in mixed precision, after AdamW’s first step.
On top of that come the activations, the intermediate results backpropagation needs. Per layer of a 16-bit transformer with linear-memory attention they are about bytes ( = tokens per sequence, = sequences per batch, = width; Korthikanti et al., 2022). On the same RTX 3060 we measured 0.949 times that formula. They can be reduced by recomputing them on the way back, at the cost of more compute.
ZeRO and its PyTorch version, FSDP (the one Meta used), split those 16 bytes across the data copies, so each GPU stores only part of the states.
Bandwidth: the ceiling when writing
When generating one token at a time, every step reads all the weights:
An H100 SXM moves 3,350 GB/s; an 8-billion model in FP16 (16 GB) stays under about 209 tokens per second, however fast it computes. Its dense FP16 compute peak is about 989 trillion operations per second (the datasheet says 1,979 “with sparsity”): operations per byte, about 590 per 2-byte parameter, which a matrix–vector product uses for only 2 (multiply and add).
Measured on an RTX 3060 (measure-memory.py): copying 1 GiB, 333 GB/s; multiplying a FP16 matrix by one vector, 1.57 ms, 341 GB/s, and just 0.34 trillion operations per second; by 4,096 vectors at once, 27.7 trillion: 81 times more on the same chip, because each weight read is reused. Batching requests is the way out, which we will see in lesson 26.
Parallelism
The number of GPUs is the product of the three splits: (tensor, pipeline, data).
- Data: each copy sees another slice of the batch; at the end of the step the gradients are averaged (a sum across all of them, an all-reduce).
- Pipeline (GPipe, Huang et al., 2019): stages of layers; the batch is cut into micro-batches so the stages work at the same time. The idle fraction (the “bubble”) is on the order of ; GPipe found it negligible with .
- Tensor (Megatron-LM, Shoeybi et al., 2019): in each block’s network, the first table is split by columns and the second by rows, so one sum across GPUs per block in each direction is enough. Since it happens at every layer, it stays inside the server, where the GPUs are joined by NVLink (900 GB/s on the H100).
Llama 3.1 405B (Llama Team, 2024, table 4): 8 tensor × 16 pipeline × 128 data = 16,384 H100s, using 41% of their compute; the long-context stage added a fourth split, by context (8 × 16 context × 16 × 8). And to use it: in BF16 it does not fit in one 8-H100 server (§6.1), which is why Meta serves it split across two servers or quantized to FP8.
Group quantization
The lesson’s rule (symmetric round-to-nearest, no calibration), for a group of 32 weights and bits:
With 8 bits there are 255 marks (from −127 to 127); with 4, 15. Each group also stores its scale in FP16: extra bits per weight, so “INT4” is really 4.5 bits (the 70-billion model takes about 39 GB, not 35).
Measured on Pythia-1.4B (build-data.py: the 96 matrices of its 24 layers, 1.208 billion weights; loss in nats per token over 287,744 tokens of WikiText-2):
| bits | FP16 | 8 | 6 | 5 | 4 | 3 | 2 |
|---|---|---|---|---|---|---|---|
| one scale per group of 32 | 2.5805 | 2.5806 | 2.5858 | 2.5975 | 2.6565 | 3.1589 | 11.045 |
| one scale per matrix | 2.5805 | 2.6002 | 3.2172 | 5.3017 | 10.692 | 10.445 | 21.347 |
With a single scale per matrix, the model already breaks at 4 bits: a few outlier weights, up to 13 times the standard deviation in the median matrix, stretch the ruler, and in that matrix two out of three weights land on the zero mark at 4 bits. That is why small groups are used. Production methods go further: LLM.int8() (Dettmers et al., 2022) separates the dimensions with outliers and keeps them in 16 bits; GPTQ (Frantar et al., 2023) corrects the rounding with second-order information and brings 175-billion models to 3 or 4 bits with negligible accuracy loss. Caveat: Pythia saw Wikipedia during training, so the absolute level of the loss is optimistic; what is compared is the difference, measured on the same text.
In code
import numpy as np
def gb(params, bytes_per_param):
return params * bytes_per_param / 1e9
for fmt, b in [("FP32", 4), ("FP16", 2), ("INT8", 1), ("INT4", 0.5)]:
print(f"70B in {fmt}: {gb(70e9, b):5.0f} GB")
print(f"training with Adam (16 B): {gb(70e9, 16):.0f} GB")
print(f"writing ceiling, 8B in FP16 on an H100: {3350 / gb(8e9, 2):.0f} tokens/s")
# quantize a group of 32 weights: a ruler with 2^(b-1) - 1 marks per side
rng = np.random.default_rng(0)
w = rng.normal(0, 0.02, 32)
for bits in (8, 4, 2):
qmax = 2 ** (bits - 1) - 1
s = np.abs(w).max() / qmax # the group's scale
q = np.clip(np.round(w / s), -qmax, qmax) # the integers that are stored
err = np.linalg.norm(w - q * s) / np.linalg.norm(w)
print(f"{bits} bits: relative error {err:.3f}, weights at zero {np.mean(q == 0):.0%}")
Output:
70B in FP32: 280 GB
70B in FP16: 140 GB
70B in INT8: 70 GB
70B in INT4: 35 GB
training with Adam (16 B): 1120 GB
writing ceiling, 8B in FP16 on an H100: 209 tokens/s
8 bits: relative error 0.006, weights at zero 0%
4 bits: relative error 0.100, weights at zero 19%
2 bits: relative error 0.792, weights at zero 84%
What comes next
When the model is used, another memory appears, the attention cache, which grows with every token of the conversation, along with the way to make the most of bandwidth: serving many people at once. That is lesson 26, inference.
Decision lens
What this idea means if your job is to decide, not to code.
- To size hardware: memory ≈ parameters × bytes per parameter, plus a margin for computation (the cache in lesson 26). An 8-billion model in INT8 fits on a 12 GB card; a 70-billion model in FP16 needs at least two H100s.
- Ask what format a model is served in: the same name can be offered in FP16 or quantized to 4 bits, with different costs, speed, and quality.
- If someone proposes training or fully fine-tuning a model, count about 16 bytes per parameter before buying: training takes about eight times the memory of using it.
- A quantized version can pass standard benchmarks and still fail on rare cases (Meta saw this with Llama 3). Evaluate it on your own data before switching.
Terms in this lesson
- VRAM (GPU memory) A GPU’s own memory, on the same board as its cores. The parameters and data it computes with must be there. It is measured in gigabytes: 80 on an H100, 12 on an RTX 3060. On data-center GPUs it is a very fast type called HBM.
- FP16 (and FP32, INT8, INT4) Formats for storing each number in a model. FP16 uses 2 bytes and keeps about 3 significant digits; FP32, 4 bytes and about 7. INT8 (1 byte) and INT4 (½ byte) store an integer that, multiplied by a scale, approximates the value. BF16 is a 2-byte variant with FP32’s range.
- Quantization Storing a trained model’s parameters with fewer bits (8 or 4 instead of 16), rounding each one to the nearest mark of a scale shared by its group. It reduces memory and read time, with a loss of quality that has to be measured.
- Memory bandwidth How many bytes per second can travel from memory to the cores: 3,350 GB/s on an H100. When generating text token by token it is usually the speed limit, because every token requires reading all the parameters.
- Parallelism (data, pipeline, tensor) Splitting the training or use of a model across several GPUs: full copies that see different data (data), groups of layers in a row (pipeline), or each table of parameters cut into pieces (tensor). They are combined: Llama 3.1 405B used 8 × 16 × 128 = 16,384 GPUs.
- GPU Graphics processing unit: a chip with hundreds or thousands of simple cores, built to draw graphics and very efficient at multiplying matrices in parallel, the central operation of training.
- Parameter Each of the numbers a model adjusts while training and in which it stores what it learned.
- Gradient The list of the slopes of the error, one for each weight. Seen as an arrow, it points to where the error rises fastest.
- Adam An optimizer that combines momentum with a step size of its own for each weight, computed from moving averages of the gradient and its square (Kingma and Ba, 2015).
- Backpropagation An algorithm that computes the gradient of the loss with respect to every weight of a network in a single backward sweep, applying the chain rule layer by layer. It computes the blames; it does not move the weights (in the 1986 paper, the name covered the whole learning procedure).
- Layer A group of neurons that receive the same inputs and compute at the same time. A network is a chain of layers: input, one or more hidden ones, and output.
- Token A piece of text (a word, a word fragment, a symbol, or a basic code) taken from the vocabulary. It is the unit the model reads and generates, and the unit you are billed for.
- Loss function A single number that summarizes how wrong a model is on its data. Training is the search for the parameters that make it as small as possible.
- Compute The amount of arithmetic operations needed to train or run a model. Along with data and algorithms, it is one of the three engines of progress in machine learning.
- Pretraining The first stage of training a language model: over a huge corpus, the model guesses the next piece of text again and again and adjusts its parameters to lower the error. The result is a base model.
- Inference Using an already trained model to answer a new case, with frozen parameters.
Full transcript
Sources
- NVIDIA (2022). NVIDIA H100 Tensor Core GPU: especificaciones (H100 SXM: 80 GB, 3.35 TB/s, NVLink 900 GB/s)
- Andersch, M., Palmer, G., Krashinsky, R., Stam, N., Mehta, V., Brito, G., Ramaswamy, S. (2022). NVIDIA Hopper Architecture In-Depth (16 896 núcleos FP32, 80 GB HBM3 en 5 pilas)
- NVIDIA (2021). GeForce RTX 3060 family: especificaciones (12 GB GDDR6, bus de 192 bits)
- Llama Team, AI @ Meta (2024). The Llama 3 Herd of Models
- Rajbhandari, S., Rasley, J., Ruwase, O., He, Y. (2020). ZeRO: Memory Optimizations Toward Training Trillion Parameter Models
- Micikevicius, P., Narang, S., Alben, J., Diamos, G., Elsen, E., Garcia, D., et al. (2018). Mixed Precision Training
- Kingma, D. P., Ba, J. (2015). Adam: A Method for Stochastic Optimization
- IEEE (2019). IEEE Standard for Floating-Point Arithmetic (IEEE 754-2019)
- Kalamkar, D., Mudigere, D., Mellempudi, N., Das, D., Banerjee, K., Avancha, S., et al. (2019). A Study of BFLOAT16 for Deep Learning Training
- Korthikanti, V., Casper, J., Lym, S., McAfee, L., Andersch, M., Shoeybi, M., Catanzaro, B. (2022). Reducing Activation Recomputation in Large Transformer Models
- Huang, Y., Cheng, Y., Bapna, A., Firat, O., Chen, M. X., Chen, D., et al. (2019). GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism
- Shoeybi, M., Patwary, M., Puri, R., LeGresley, P., Casper, J., Catanzaro, B. (2019). Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism
- Narayanan, D., Shoeybi, M., Casper, J., LeGresley, P., Patwary, M., Korthikanti, V., et al. (2021). Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM
- Dettmers, T., Lewis, M., Belkada, Y., Zettlemoyer, L. (2022). LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale
- Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. (2023). GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers
- Biderman, S., Schoelkopf, H., Anthony, Q., Bradley, H., O’Brien, K., Hallahan, E., et al. (2023). Pythia: A Suite for Analyzing Large Language Models Across Training and Scaling
- EleutherAI (2023). EleutherAI/pythia-1.4b (ficha del modelo, licencia Apache-2.0)
- Merity, S., Xiong, C., Bradbury, J., Socher, R. (2016). Pointer Sentinel Mixture Models (presenta WikiText-2)
CommentsThis lesson’s public conversation: questions, facts to check, ideas.
Comments live in GitHub Discussions, in the course’s public repository; writing one requires a GitHub account. They load from giscus.app when you open this section. The course reviewers read them, and if a change is accepted, it is applied in both languages. Open the thread on GitHub