Skip to content
Friendly disclaimer: flozi00 TechHub is a solo side-project next to a full-time job — personal learning notes, no official statements. Verify critical steps yourself.

LoRA and QLoRA Fine-Tuning: The Actual Memory Math

Mixed-precision AdamW's 16-byte model-state budget, LoRA's r(d_in+d_out) adapter math, QLoRA's NF4 storage and the limits of activation-memory estimates.

10 min readflozi00
aimachine-learninggpugpu-memoryfine-tuningdeep-learning
Skip to the full article
Watch · explore · understand

Train a small update beside a frozen base

LoRA learns two small matrices; QLoRA also compresses the frozen base. Only trainable parameters need gradient and optimizer state.

AI-assisted learning visualization · schematic article explanation

Step 1 / 4Freeze the large matrix

Ordinary full training needs gradients and optimizer state for every trainable weight. LoRA freezes the base matrix W and adds a small trainable correction, so those extra states belong only to the adapters.

ArchitectureCurrent stepSelect a block to explain it
Layer input x
The same input enters the frozen and trainable branches.
Frozen base: W × x
QLoRA stores W in NF4 with scales and computes from dequantized values.
Adapter: B × A × x
Rank r needs r × (input width + output width) trainable parameters.
Add the scaled adapter update
The layer output combines the base path and learned correction.
  1. Layer input x Frozen base: W × x
    frozen forward path
  2. Layer input x Adapter: B × A × x
    trainable branch
  3. Frozen base: W × x Add the scaled adapter update
    base output
  4. Adapter: B × A × x Add the scaled adapter update
    scaled by α/r
What to remember

LoRA removes most trainable state; QLoRA also shrinks frozen weight storage. Neither removes activation or workspace memory.

The 16-byte Adam budget assumes master weights and specified state precisions. Adapter targets, optimizer implementation and the measured backward peak determine actual fit.

Terms and the complete walkthrough
LoRA rank
The small intermediate dimension that limits the learned update’s matrix rank.
NF4
A four-bit codebook used by QLoRA to store the frozen base weights.
Activation checkpointing
Retaining selected boundaries and recomputing missing intermediates during backward.
  1. Freeze the large matrix

    Ordinary full training needs gradients and optimizer state for every trainable weight. LoRA freezes the base matrix W and adds a small trainable correction, so those extra states belong only to the adapters.

  2. Count the low-rank update

    A maps the input down to rank r and B maps it back to the output width. Their combined size is r times the sum of input and output widths, rather than the product needed for a full weight matrix.

  3. Compress the frozen base with QLoRA

    QLoRA stores frozen weights as NF4 values with block scales; double quantization compresses the scales too. Computation uses dequantized values, while trainable adapter state still follows its own optimizer and precision choices.

  4. Account for the backward peak

    Frozen weights still participate in forward and backward computation. Checkpointing saves selected activation boundaries and recomputes intermediates; boundary storage alone does not include the temporary backward peak.

All connections

  • Layer input x → Frozen base: W × x: frozen forward path
  • Layer input x → Adapter: B × A × x: trainable branch
  • Frozen base: W × x → Add the scaled adapter update: base output
  • Adapter: B × A × x → Add the scaled adapter update: scaled by α/r
  • Add the scaled adapter update → Training loss: evaluate prediction
  • Training loss → Adapter gradients + optimizer: backpropagate
  • Adapter gradients + optimizer → Adapter: B × A × x: update A and B
  • Training loss → Saved or recomputed activations: backward dependencies
Set your own pace. Pause at any time or select a step directly.

"Fine-tune a 32B model on one GPU" threads take the inference number and quietly subtract nothing. Reality: training keeps gradients and optimizer state for every trainable parameter, and under mixed-precision AdamW that budget is 16 bytes per parameter — eight times the 2 bytes a bf16 weight costs at inference. This guide derives that budget term by term, then walks the two standard escapes (LoRA's rank decomposition, QLoRA's 4-bit base) with worked numbers for a real model: Qwen3-32B, 32,762,123,264 parameters (count verified against the Hugging Face safetensors metadata of Qwen/Qwen3-32B; config: 64 layers, hidden size 5,120, 64 attention heads / 8 KV heads, head dim 128, FFN 25,600, untied 151,936-token embeddings) 1. At inference this model needs 65.5 GB just for bf16 weights. This guide ties into our VRAM calculator guide and the quantization guide; the serving-side counterpart is the KV cache guide.

Why training is not inference

TermBytes/paramExists at inference?
bf16/fp16 weight2yes
bf16/fp16 gradient2no
fp32 master weight4no
fp32 Adam momentum (m)4no
fp32 Adam variance (v)4no
Training total16—

The accounting is the ZeRO paper's (arXiv:1910.02054, Sec. 3.1) 2, mixed-precision training keeps a 2-byte fp16/bf16 parameter copy and a 2-byte gradient copy, while the optimizer holds an fp32 copy of the parameters plus fp32 momentum and variance — 4+4+4 bytes — for a total of 2Ψ + 2Ψ + 12Ψ = 16Ψ bytes for a model with Ψ parameters. That 14-of-16 byte share sits on trainable parameters only: PyTorch's mixed-precision recipe and DeepSpeed's docs both keep gradients and fp32 master/optimizer state solely for parameters with requires_grad=True.

Qwen3-32B, full fine-tune, AdamW mixed precision. Model state = 32,762,123,264 × 16 B = 524.2 GB (488.2 GiB) — before activations, gradients of embeddings buffers, or the CUDA context. The same model serves in 65.5 GB. That 8× factor is the entire story of this guide. (ZeRO/FSDP sharding and offloading can split the 524 GB across GPUs and host RAM — the bytes don't disappear, they move.)

LoRA: freeze the 99%, train a low-rank delta

LoRA (arXiv:2106.09685) freezes the pretrained weights and learns an update as a product of two small matrices — for each adapted weight matrix WW, the forward pass computes Wx+αrBAxW x + \frac{\alpha}{r} B A x, with A∈Rr×dinA \in \mathbb{R}^{r \times d_{\text{in}}}, B∈Rdout×rB \in \mathbb{R}^{d_{\text{out}} \times r}, BB initialized to zero. The trainable delta ΔW=BA\Delta W = BA has exactly:

NLoRA=r (din+dout)N_{\text{LoRA}} = r \, (d_{\text{in}} + d_{\text{out}})

parameters per adapted matrix — rank 8 adds 8⋅(5,120+8,192)=106,4968 \cdot (5{,}120 + 8{,}192) = 106{,}496 parameters to a single Qwen3 q_proj, versus 41,943,040 frozen ones (0.25%). Applied to all seven linear matrices per layer (q, k, v, o, gate, up, down), one Qwen3-32B layer carries r×131,072r \times 131{,}072 adapter parameters:

Rank rTrainable params (all-linear)% of 32.76BOptimizer+grad state (16 B/param)Frozen bf16 baseTotal model statevs. full fine-tune
867,108,8640.205%1.07 GB65.5 GB66.6 GB12.7%
16134,217,7280.410%2.15 GB65.5 GB67.7 GB12.9%
64536,870,9121.639%8.59 GB65.5 GB74.1 GB14.1%

Full fine-tune model state for reference: 524.2 GB (16 B × 32.76B params). The 16 B/param column includes the bf16 adapter weights themselves, gradients, and fp32 m/v/master. It applies only to the trainable 0.2–1.6%. Actual optimizer implementations can omit the master copy or use different state types.

Why the number collapses: gradients and Adam states exist only for trainable parameters. The frozen base contributes its forward weights, while the adapters carry the training state. The LoRA paper reports a 3× GPU-memory reduction for its GPT-3 175B setup 3; that result is specific to its configuration, not a universal reduction factor.

Two caveats. First, the total saving is bounded below by the frozen base: LoRA at r=16 still needs 65.5 GB of bf16 Qwen3-32B weights; a 70B bf16 base alone needs roughly 140 GB and cannot fit on one 80 GB A100/H100. Second, the 16 B/param budget assumes fp32 master weights and Adam states; 8-bit optimizers use different accounting and require separate measurement.

QLoRA: 4-bit base, same optimizer math

QLoRA (arXiv:2305.14314) removes LoRA's floor — the 2 B/param frozen base — by storing the frozen weights in 4-bit NormalFloat (NF4) with blockwise scales. The paper's accounting, blocksize 64:

bNF4+DQ=4+864⏟FP8 quantized scales+3264⋅256⏟second-level scales=4.127 bits/paramb_{\text{NF4+DQ}} = 4 + \underbrace{\frac{8}{64}}_{\text{FP8 quantized scales}} + \underbrace{\frac{32}{64 \cdot 256}}_{\text{second-level scales}} = 4.127 \text{ bits/param}

Without double quantization the blockwise scales cost 32/64 = 0.5 bits/param — for a 65B model that is ~0.4 GB of fp32 constants per weight bit saved. Double quantization (quantize the first-level constants themselves to FP8 with blocksize 256) reduces that to 8/64 + 32/16384 = 0.127 bits/param, "a reduction of 0.373 bits per parameter" — the paper's exact number, 4, roughly 3 GB saved on a 65B model. Paged optimizers, QLoRA's third component, do not shrink steady-state memory: they use NVIDIA unified memory to page optimizer state to CPU during the transient spike that gradient checkpointing causes on long sequences.

Qwen3-32B in QLoRA (NF4 + DQ, LoRA r=16 all-linear, batch 4 × 2,048 tokens, checkpointing on):

  • Base weights: 32,762,123,264 × 4.127 bits / 8 = 16.9 GB (vs. 65.5 GB bf16 — a 3.9× cut on the frozen base)
  • Adapter trainable state: 134,217,728 × 16 B = 2.15 GB (identical budget to LoRA — QLoRA quantizes only the frozen part)
  • Base plus adapter model state: ≈ 19.0 GB, before activations, dequantization buffers, CUDA workspace and allocator overhead. A 24 GB card is therefore not guaranteed to fit the stated batch; measure the actual training peak.

Paged 8-bit AdamW can reduce the optimizer-state part of this budget, but its exact memory use depends on the implementation and the number and size of adapted tensors.

Activation memory and the checkpointing trade

Model state is the floor; activations scale with tokens. Korthikanti et al. derive approximately s·b·h·(34 + 5a·s/h) bytes per layer for the specific transformer implementation they analyze 5. Fused attention can avoid storing the quadratic attention matrix, but the remaining 34·s·b·h is an architecture-dependent illustration, not an exact Qwen3 allocation. Gradient checkpointing saves region inputs instead of all intermediates and recomputes them during backward 6. Crucially, the saved boundary tensors are not the peak: temporary activations for the region being recomputed, gradients, and workspaces remain live.

For a 64-layer, 5,120-hidden model at batch 4 × 2,048, merely retaining one bf16 layer-boundary tensor per layer costs 64 × 8,192 × 5,120 × 2 ≈ 5.37 GB before the backward peak. The former 0.67 GB checkpoint estimate counted only eight boundaries and ignored recomputation within an eight-layer segment; it cannot be used as a GPU-fit figure. Full-layer recomputation can approach one extra forward pass, roughly +33% if backward costs twice forward, but actual overhead depends on checkpoint granularity and kernels 7.

Illustrative 64-layer activation accounting (h = 5,120, bf16; the 34 coefficient comes from the cited transformer implementation, not a Qwen3 measurement):

Batch × Context (tokens)Uncheckpointed reference estimate (34·s·b·h·L)One saved bf16 boundary per layer (2·s·b·h·L), excluding recomputation peak
1 × 2,04822.8 GB1.34 GB
4 × 2,04891.3 GB5.37 GB
4 × 4,096182.5 GB10.74 GB
8 × 4,096365.1 GB21.47 GB
1 × 16,384182.5 GB10.74 GB
8 × 8,192730.1 GB42.95 GB

The right column is only a boundary-tensor count. It does not upper-bound peak memory under checkpointing. For a real fit decision, include the recomputation peak and use torch.cuda.max_memory_allocated() on the intended model, sequence length, batch, optimizer, and training stack.

Model-state floors by method

These are weight, gradient, and optimizer-state floors. They exclude activations and temporary buffers, so they do not establish that a particular GPU configuration will fit:

ModelFull fine-tune (16 B/param)LoRA r=16 (bf16 base + adapter state)QLoRA r=16 (NF4+DQ base + adapter state)
Llama-3.1-8B (8.03B params) 8128.5 GBBase 16.1 GB + adapter state (target-module dependent)Base 4.1 GB + adapter state (target-module dependent)
Qwen3-32B (32.76B params)524.2 GB65.5 + 2.15 ≈ 67.7 GB16.9 + 2.15 ≈ 19.0 GB
70B-class model (illustrative)~1,120 GB~140 GB + adapter state~36.1 GB + adapter state

Llama-3.1-8B parameter count is from its Hugging Face metadata (meta-llama/Llama-3.1-8B: 8,030,261,248). The 70B row is rounded; actual checkpoint counts and adapter shapes differ. QLoRA's paper demonstrated a 65B model on a 48 GB GPU under its own training configuration 4, which does not establish a fit for every 70B model or batch.

What fine-tuning does and does not guarantee

Memory savings do not establish task quality. Evaluate the adapted model on the facts and behaviours that matter:

  • Factual updates need evaluation. Fine-tuning can change factual responses, but neither LoRA's low-rank analysis nor QLoRA's instruction-data results prove that a small SFT dataset reliably installs or maintains a body of changing facts 9 10. Retrieval can supply current source material; verify both retrieval and generated answers.
  • Capacity does not price throughput. At batch 4 × 2,048 tokens, a 100M-token one-epoch dataset requires at least 12,208 optimizer steps if every token contributes and no examples are discarded. Checkpointing and dequantization add work. Measure tokens per second on the target stack before estimating run time or cost.

  • No honest defaults without tuning 11. The QLoRA paper's own hyperparameter table uses learning rate 2e-4 for 7B and 13B models and 1e-4 for 33B and up, batch size 16, constant schedule — roughly 10x higher than full fine-tuning rates, because only the small adapter population is being optimized and the base is frozen. LoRA's paper likewise sweeps LR per task. Any "just use" number — rank 8, epochs 3, LR 2e-4 — is a starting grid, not a result; the honest approach is a small sweep of learning rate and rank before believing a loss curve.

  • Quantization error is workload-dependent 10. QLoRA reports strong NF4 results on its evaluation suite; that does not give a universal quality bound for every model, task, or context length. Benchmark against the intended full-precision baseline.

What to remember

  • Training budget per trainable parameter: 16 bytes under mixed-precision AdamW (2 bf16 weight + 2 bf16 grad + 4 fp32 master + 4 m + 4 v), per the ZeRO paper — 8x inference weights. Qwen3-32B full fine-tune: 524.2 GB before activations.
  • Frozen parameters retain their forward weights; the 16-byte mixed-precision Adam budget applies to trainable parameters. Qwen3-32B with all-linear LoRA r=16 has about 134M trainable parameters and a 67.7 GB model-state floor.
  • LoRA adapter size is r(d_in+d_out) per adapted matrix; the model's seven linears per layer give 67M trainable parameters at r=8 and 537M at r=64.
  • QLoRA's frozen NF4 base is about 4.127 bits/parameter under the paper's double-quantization assumptions. Qwen3-32B then has a 16.9 GB base plus 2.15 GB of r=16 adapter state, before activations and workspaces.
  • Checkpoint boundaries are not peak activation memory. At batch 4 × 2,048, one bf16 boundary tensor per Qwen3-32B layer already totals about 5.37 GB. Measure the backward peak before claiming a GPU fit.
  • Factual quality must be tested after fine-tuning; use current retrieval sources where the application needs current or auditable facts.

For serving-side memory math, see the KV cache guide; for weight formats and 4-bit block quantization mechanics, the quantization guide; for the compute-cost side of running what you trained, the VRAM calculator guide and the inference economics tool.

Footnotes

  1. Qwen/Qwen3-32B — Hugging Face model repository, safetensors metadata: 32,762,123,264 BF16 parameters; config.json: 64 layers, hidden 5,120, 64 Q / 8 KV heads, head_dim 128, FFN 25,600, vocab 151,936, untied embeddings. huggingface.co/Qwen/Qwen3-32B. ↩

  2. Rajbhandari et al., ZeRO: Memory Optimizations Toward Training Trillion Parameter Models, arXiv:1910.02054 — Sec. 3.1 derives the 2Ψ+2Ψ+12Ψ = 16Ψ-byte mixed-precision Adam budget. arxiv.org/abs/1910.02054. ↩

  3. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, arXiv:2106.09685 — abstract: 10,000x fewer trainable parameters, 3x VRAM reduction vs. full fine-tuning on GPT-3 175B. arxiv.org/abs/2106.09685. ↩

  4. Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, arXiv:2305.14314 — abstract: 65B model on a single 48 GB GPU; 4.127-bit effective NF4+DQ accounting; paged optimizers for checkpointing spikes. arxiv.org/abs/2305.14314. ↩ ↩2

  5. Korthikanti et al., Reducing Activation Recomputation in Large Transformer Models, arXiv:2205.05198 — derives sbh(34 + 5as/h) for its analyzed transformer; the quadratic term is avoided by fused attention. https://arxiv.org/abs/2205.05198. ↩

  6. PyTorch, Activation Checkpointing Techniques: region inputs are saved and intermediate activations are recomputed during backward; memory and compute depend on checkpoint placement. https://pytorch.org/blog/activation-checkpointing-techniques/. ↩

  7. Chen et al., Training Deep Nets with Sublinear Memory Cost, arXiv:1604.06174 — √L checkpointing turns O(L) activation memory into O(√L) at one extra forward pass (~+33% step compute). arxiv.org/abs/1604.06174. ↩

  8. meta-llama/Llama-3.1-8B — Hugging Face metadata: 8,030,261,248 BF16 parameters. huggingface.co/meta-llama/Llama-3.1-8B. ↩

  9. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, arXiv:2106.09685, Sec. 7: ΔW "amplifies some features that are already in W" and "only amplifies directions that are not emphasized in W", with amplification factor ≈ 21.5 at r=4; the low-intrinsic-rank hypothesis is that weight updates have low rank (Sec. 1). arxiv.org/abs/2106.09685. ↩

  10. Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, arXiv:2305.14314: "we find that data quality is far more important than dataset size, e.g., a 9k sample dataset (OASST1) outperformed a 450k sample dataset (FLAN v2)" (Sec. 6.2) — arxiv.org/abs/2305.14314. ↩ ↩2

  11. The QLoRA paper stresses matching train and inference prompt templates exactly; ours can only flag it. ↩