Module 03 · Chapter 10
QLoRA: Quantize Base Weights to 4-Bit
Budget VRAM, compress base models with 4-bit NormalFloat and Double Quantization, and use paged optimizers to fine-tune on consumer GPUs.
Learning objectives
- Derive the complete VRAM memory budget for 4-bit QLoRA training across weights, optimizers, and activations.
- Explain the mathematical foundation of 4-bit NormalFloat (NF4) and why it outperforms standard INT4 quantization.
- Calculate memory savings from Double Quantization (DQ) across primary and secondary quantization scale blocks.
- Configure gradient checkpointing and paged optimizers to eliminate out-of-memory activation spikes.
Before you start
- Understanding of Low-Rank Adaptation (LoRA) matrix decomposition from Lesson 9
- Familiarity with post-training quantization and FP16/INT4 data formats from Inference Optimization
Section 1: The Practical Outcome First
Section titled “Section 1: The Practical Outcome First”While standard 16-bit LoRA (Lesson 9) reduces trainable parameters by over 99%, the frozen base model itself still resides in GPU VRAM in full 16-bit floating point precision (BF16 or FP16). For an 8B parameter model, the frozen base weights alone consume 14.96 GB of VRAM.
When you add adapter parameters, AdamW optimizer states, and forward-pass activation buffers, a standard 16-bit LoRA run requires 17.5 GB to 19.8 GB of VRAM. On a standard 24 GB workstation GPU (such as an RTX 4090 or cloud A10G), this leaves fewer than 4 GB of free memory—causing Out-of-Memory (OOM) crashes the moment batch sizes increase or sequence lengths exceed 2,048 tokens.
QLoRA (Quantized Low-Rank Adaptation) (Dettmers et al., 2023) eliminates this bottleneck by compressing the frozen base model weights into a specialized 4-bit format while keeping the trainable LoRA adapter matrices in 16-bit precision.
Look at the memory allocation difference when adapting Llama 3.1 8B on a 24 GB GPU:
| Memory Component | Standard 16-bit LoRA | 4-bit QLoRA (NF4 + DQ) | VRAM Reduction |
|---|---|---|---|
| Base Model Weights | 14.96 GB (BF16) | 3.86 GB (NF4 + DQ) | -74.2% |
| LoRA Weights + Gradients | 0.156 GB | 0.156 GB (BF16) | 0.0% |
| AdamW Optimizer States | 0.469 GB | 0.469 GB (FP32 master) | 0.0% |
| Activations (Checkpointing) | 2.50 GB | 2.50 GB (BF16) | 0.0% |
| CUDA Workspace & Buffers | 1.25 GB | 1.25 GB | 0.0% |
| Total Peak Training VRAM | 19.34 GB | 8.23 GB | -57.4% |
| Headroom on 24 GB GPU | 4.66 GB (Fragile) | 15.77 GB (Robust) | +238% Headroom |
By shrinking the base model footprint to just 3.86 GB, QLoRA frees up over 15.7 GB of VRAM. This headroom enables teams to increase sequence lengths beyond 2,048 tokens or expand micro-batch sizes within the analytical budget; actual fit for 8,192 tokens requires a separate activation memory budget or measured GPU telemetry (activation memory scales with sequence length).
Section 2: The Three Core Innovations of QLoRA
Section titled “Section 2: The Three Core Innovations of QLoRA”QLoRA achieves 4-bit base model compression without degrading task accuracy through three distinct mathematical and systems innovations:
Read this diagram as text
Base model weights are mapped into information-theoretically optimal 4-bit NormalFloat (NF4) bins. Scale factors undergo secondary 8-bit FP8 quantization (Double Quantization). During forward and backward passes, weights are dequantized to BF16 for matrix operations, while CUDA Unified Memory pages optimizer states to CPU RAM during activation spikes.
1. 4-bit NormalFloat (NF4)
Section titled “1. 4-bit NormalFloat (NF4)”Standard integer quantization (INT4) divides the dynamic range into 16 evenly spaced intervals. However, the weights of pretrained neural network layers are not uniformly distributed; they follow a zero-mean Gaussian normal distribution W ~ N(0, σ^2).
Under uniform INT4 quantization, most weights cluster in the central bins while the outer bins remain largely empty, resulting in high quantization error.
NormalFloat (NF4) constructs an information-theoretically optimal quantile codebook. The 16 discrete representation values are chosen such that each bin contains an equal probability mass under an empirical standard normal distribution:
q_i = 0.5 * (Q_X(i / (2^k + 1)) + Q_X((i + 1) / (2^k + 1)))Where Q_X is the quantile function of the standard normal distribution N(0, 1), and k = 4 bits. This formula uses (2^k + 1) in the denominator to ensure 0 is represented exactly and q_15 is finite, matching the Dettmers et al. (2023) specification. Because every discrete state carries equal information, NF4 achieves higher empirical reconstruction fidelity than both standard INT4 and floating-point FP4 across every transformer benchmark.
2. Double Quantization (DQ)
Section titled “2. Double Quantization (DQ)”In block-wise quantization, weights are segmented into blocks of size B_1 = 64 to localize dynamic range and handle outlier activations. Each block requires a 32-bit floating-point scale factor c_1:
Memory overhead = 32 bits / 64 weights = 0.500 bits per parameterAcross an 8B model, scale constants alone consume 500 MB of memory.
Double Quantization applies a second layer of quantization to the scale factors themselves. The 32-bit scale factors c_1 are grouped into secondary blocks of size B_2 = 256 and quantized into 8-bit FP8 numbers with secondary scale c_2:
DQ Overhead = (8 bits / 64) + (32 bits / (64 * 256)) = 0.125 + 0.00195 ≈ 0.127 bits per parameterThis reduces scale overhead from 0.500 to 0.127 bits per parameter, saving 0.373 bits per parameter—stripping approximately 370 MB of pure static memory overhead from an 8B model.
3. Paged Optimizers
Section titled “3. Paged Optimizers”During training, memory consumption is not flat. While forward passes consume moderate activation memory, the backward pass triggers sharp, transient allocation spikes when computing attention gradients.
If peak allocation exceeds GPU capacity by even a single megabyte, PyTorch halts with a fatal CUDA Out-of-Memory exception.
QLoRA leverages CUDA Unified Memory to register AdamW optimizer states in shared virtual memory. When PyTorch detects an impending VRAM allocation spike during backward activation processing, non-active optimizer page tables are transparently paged out to CPU host system memory. Once the gradient spike resolves, pages are paged back into high-speed GPU memory. Because page transfers occur only during peak spikes, training speed is virtually unaffected while OOM crashes are completely eliminated.
Section 3: The Dequantize-Compute Forward/Backward Loop
Section titled “Section 3: The Dequantize-Compute Forward/Backward Loop”A critical engineering detail of QLoRA is that computation is never performed in 4-bit precision.
Tensor cores in modern NVIDIA architectures (Ampere, Ada Lovelace, Hopper) do not have native hardware ALUs for 4-bit NormalFloat matrix multiplication. Instead, QLoRA operates as an on-the-fly dequantization pipeline:
[4-bit NF4 Weights in GPU Memory: 3.86 GB] │ ▼ (On-the-fly Dequantization Kernel)[Dequantized 16-bit BF16 Weights in SRAM Registers] │ ├───► BF16 Matrix Multiplication: (W_BF16 @ x) │ ▼[16-bit LoRA Adapter Path: (B @ (A @ x)) * (alpha / r)] │ ▼[Accumulate Outputs in BF16: h = W_BF16 @ x + Adapter_BF16]- Storage: Base model parameters remain compressed in 4-bit NF4 in GPU global memory (HBM/VRAM).
- Execution: When a transformer layer executes, a lightweight CUDA dequantization kernel expands the 4-bit weights into 16-bit BF16 representations directly in on-chip SRAM cache / registers.
- Multiplication: The matrix multiplication
W @ xis executed in high-throughput 16-bit BF16 tensor cores. - Adapter Branch: The input
xsimultaneously passes through the BF16 LoRA adapter branch (AandB). - Backpropagation: Gradients are computed strictly with respect to the trainable BF16 LoRA adapter parameters. The 4-bit base weights receive zero gradients and remain frozen.
Because dequantization happens in on-chip memory bandwidth, the computational overhead of dequantization is negligible, typically introducing less than a 15% throughput penalty compared to unquantized training.
Section 4: Exact VRAM Budget Formulation & Sizing
Section titled “Section 4: Exact VRAM Budget Formulation & Sizing”Before launching a fine-tuning job, Staff engineers calculate the exact total memory budget:
M_total = M_base + M_lora + M_optimizer + M_activations + M_workspaceWhere:
M_base:N_params * (0.5 + 0.016) bytesfor NF4 + DQ (3.86 GBfor Llama 3.1 8B).M_lora:N_adapter * 2 bytes (BF16 weights) + N_adapter * 2 bytes (BF16 grads)(0.156 GBfor 41.9M params).M_optimizer:N_adapter * 12 bytes(FP32 master copy + 1st momentum + 2nd variance in AdamW =0.469 GB).M_activations: Memory required to hold forward activations for backward pass. With gradient checkpointing enabled, analytical memory model approximation for transformer architectures. ForL = 2048and micro-batchB = 4, typical activations consume approximately2.50 GB:Note: This is an analytical memory model (not measured GPU telemetry). Actual measured GPU telemetry would require dedicated instrumentation.M_activations ≈ 2 * B * L * d_model * num_layers * 2 bytes / checkpoint_ratioM_workspace: PyTorch allocator workspace and CUDA runtime caching buffer (1.25 GB).
Analytical Note: The output logits buffer (FP16/BF16) would be B * L * V * 2 bytes ≈ 2.0 GiB at B=4, L=2048, or reduced via fused chunked cross-entropy.
Total Peak VRAM = 3.86 + 0.156 + 0.469 + 2.50 + 1.25 = 8.23 GBHeadroom on 24 GB GPU = 24.00 - 8.23 = 15.77 GBSection 5: Executable Verification & Arithmetic Parity
Section titled “Section 5: Executable Verification & Arithmetic Parity”To verify the complete QLoRA VRAM budget calculations in your environment, run the standalone verification script [scripts/verify-qlora-budget.py]:
def calculate_qlora_vram(base_params_b=8.03, lora_params_m=41.94, seq_len=2048, batch_size=4): # 1. Base weights: NF4 (0.5 B/param) + DQ (0.016 B/param) m_base_gb = (base_params_b * 1e9 * (0.5 + 0.016)) / (1024**3) # 2. LoRA weights & gradients (BF16 = 2 B/param each) m_lora_gb = (lora_params_m * 1e6 * 4) / (1024**3) # 3. AdamW optimizer states (12 B/param) m_opt_gb = (lora_params_m * 1e6 * 12) / (1024**3) # 4. Activations (with selective checkpointing) m_act_gb = 2.50 # 5. CUDA workspace buffer m_work_gb = 1.25
total = m_base_gb + m_lora_gb + m_opt_gb + m_act_gb + m_work_gb return round(total, 2)Execution produces the exact, verified numerical outputs:
=== Running Topic 10 QLoRA VRAM Budget Verifier ===[*] Base Model Weights (NF4 + DQ): 3.86 GB[*] LoRA Weights + Gradients: 0.156 GB[*] AdamW Optimizer States: 0.469 GB[*] Activations (w/ Checkpointing): 2.50 GB[*] CUDA Workspace & Cache Buffer: 1.25 GB[*] ----------------------------------------[*] Total Peak VRAM Required: 8.23 GB[*] Headroom on 24 GB GPU: 15.77 GB (Fits: True)
[+] QLoRA VRAM Budget Verification Passed Cleanly.Try it yourself
Calculate the base weight memory (in GB) for a 14B parameter model stored in 4-bit NF4 with Double Quantization (0.516 bytes per parameter total).
Show a hint
M_base = (14 * 10^9 * 0.516) / (1024^3) ≈ 7.224 * 10^9 / 1,073,741,824 ≈ 6.73 GB.
Why does QLoRA dequantize 4-bit weights to 16-bit BF16 before executing matrix multiplication rather than performing 4-bit tensor math directly?
Show a hint
Modern NVIDIA GPU tensor cores lack native hardware ALUs for 4-bit NormalFloat arithmetic, and intermediate activation products require 16-bit dynamic range to maintain numerical precision.
An engineer disables gradient checkpointing during a QLoRA run on sequence length 4,096 to increase training speed. What will happen to VRAM consumption?
Show a hint
Activation memory will explode from ~2.5GB to over 20GB, immediately triggering an Out-of-Memory (OOM) crash despite 4-bit base weight compression.
Interview questions
What are the three core innovations introduced in the QLoRA paper by Dettmers et al. (2023)?
The three innovations are: (1) 4-bit NormalFloat (NF4), an information-theoretically optimal quantile quantization scheme for normally distributed weights; (2) Double Quantization (DQ), which quantizes the first-stage scale constants into 8-bit FP8 numbers to save 0.373 bits per parameter; and (3) Paged Optimizers, which leverage CUDA Unified Memory to page AdamW states to host CPU memory during transient backward activation spikes.
Why does NormalFloat (NF4) preserve model capability better than standard uniform INT4 quantization?
Neural network weights follow a bell-shaped Gaussian distribution centered at zero. Uniform INT4 bins are evenly spaced, meaning many bins at the extremes are empty while central bins suffer high quantization error. NF4 sets bin boundaries such that each of the 16 bins contains an equal probability mass (1/16th of the distribution), maximizing entropy and minimizing reconstruction error.
How does Double Quantization reduce memory overhead without affecting model precision?
Standard block quantization uses a 32-bit float scale factor for every 64 weights (0.500 bits/param). Double Quantization treats these scale factors as a new tensor, grouping them into blocks of 256 and quantizing them into 8-bit floats with a secondary 32-bit scale. This shrinks scale overhead to 0.127 bits/param, saving ~370MB on an 8B model with zero perceptible impact on model accuracy.
Does QLoRA introduce inference latency overhead once the model is fine-tuned and deployed?
No. For deployment, you can either keep the model in 4-bit NF4/AWQ for memory-efficient inference, or dequantize the 4-bit base weights back to 16-bit and merge the trained LoRA adapter weights (W = W_dequant + (alpha/r) * B @ A). Merging produces a standard 16-bit checkpoint with zero inference latency overhead.