Custom AI models for the enterprise · New York · LondonSecurityResearchContact sales
Finetune the World
Research · Experiment

LoRA rank vs full fine-tuning: an experiment on structured extraction

Eight training runs of a 0.5B model on a realistic extraction task, scored in-distribution and on input formats never seen in training. Rank barely mattered on familiar inputs. It mattered a lot on new ones.

Summary#

We trained Qwen2.5-0.5B-Instruct to turn free-text insurance claim notes into a five-field JSON record, comparing zero-shot prompting, LoRA at ranks 1, 4, 16 and 64, and full fine-tuning, with 800 training examples. The headline numbers (exact match on all five fields):

ConfigurationTrainable paramsIn-distributionUnseen formatsJSON valid (unseen)
Base model, zero-shot—18.3%19.3%100%
LoRA r=10.55M96.3%85.0%100%
LoRA r=42.20M100.0%93.0%100%
LoRA r=168.80M100.0%97.0%100%
LoRA r=6435M100.0%98.0%100%
Full fine-tuning494M100.0%91.3%100%

On the in-distribution test set, every fine-tuned configuration from rank 4 upward reached 100%. The difference appeared on the out-of-distribution set — note formats the model never saw in training — where exact match rose from 85.0% at rank 1 to 97.0% at rank 16 and 98.0% at rank 64, against 91.3% for full fine-tuning.

The task and data#

Each example is a claim note in one of five templates (first-notice-of-loss notes, call summaries, agent notes) with a policy number, a date of loss in one of four formats, a loss described in everyday language (“found water coming through the kitchen ceiling”), an amount in one of nine currency formats, and distractors: phone numbers, reference numbers and call dates that must not be confused with the date of loss. The target is:

{"policy_number": "HM-341804", "date_of_loss": "2023-06-15",
 "loss_type": "storm", "amount": 893.0, "currency": "EUR"}

Loss type is one of eight categories that must be inferred from descriptions such as “had a chip-pan fire in the kitchen” or “accidentally damaged a neighbour’s car with a ladder” (liability, not collision). The unseen-formats test set uses three templates that never appear in training: a customer email in the first person, a pipe-delimited system record, and a voicemail transcript. It measures whether the model learned the task or the templates.

Data is generated deterministically by data.py: 800 training examples, 300 in-distribution and 300 unseen-format test examples, with disjoint random seeds.

Training setup#

LoRA was implemented by hand (no adapter library) so the parameter accounting is explicit. Each adapted linear layer computes

with adapters on all seven linear projections (q, k, v, o, gate, up, down) in all 24 layers, , learning rate . Full fine-tuning used . Both: AdamW, no weight decay, 10% linear warm-up then linear decay, batch 16, two epochs (100 steps), gradient clipping at 1.0, fp32, loss on the JSON target only. Decoding was greedy with an 80-token limit. Hardware: an Apple M4 Max GPU via PyTorch MPS, shared with other workloads, so wall-clock times are indicative only.

ΔW (d × d) B (d × r) A (r × d) W₀ + r(d_in + d_out)trainable params
4adapter rank
84%energy of the full update captured
90ktrainable params in one 3,584 × 18,944 MLP matrix
93.0%our measured exact match on unseen formats, nearest rank
Interactive: drag to rotate, move the slider to change rank. The heightfield is a synthetic 24 × 24 update built from orthogonal rank-1 components with decaying singular values; the rank-r adapter captures the r largest. Parameter counts use the real Qwen2.5-7B MLP shape; accuracy figures are from our experiment. Static diagram shown without WebGL or with reduced motion.

Where the gains came from#

Field-level accuracy on unseen formats shows which skills each configuration learned:

Configurationpolicy numberdate of lossloss typeamountcurrency
Base model, zero-shot100.0%81.3%39.3%99.3%59.3%
LoRA r=1100.0%96.7%96.3%99.7%90.3%
LoRA r=4100.0%95.7%99.3%99.7%98.3%
LoRA r=16100.0%97.3%100.0%99.7%100.0%
LoRA r=64100.0%98.7%100.0%99.3%100.0%
Full fine-tuning100.0%93.0%100.0%98.0%100.0%

The base model already copied policy numbers and amounts almost perfectly; prompting was enough for those. It failed on loss type (39%), currency (59%) and date of loss (81%): the fields that require mapping a convention (“pounds” → GBP, “had roof tiles torn off in the storm” → storm, choosing the loss date rather than the call date). Those conventions are exactly what fine-tuning teaches. Rank 1 learned them on familiar templates but transferred less of the currency and date logic to new formats.

70%75%80%85%90%95%100%110Full FT, unseenIn-distributionUnseen formatsLoRA rank (log scale)Exact match (all 5 fields)
Exact match versus LoRA rank. In-distribution accuracy saturates by rank 4; accuracy on unseen formats keeps improving with rank. Source: research/e1_lora_rank/.

Why full fine-tuning generalized worse here#

Full fine-tuning matched LoRA on familiar templates (100%) but dropped to 91.3% on unseen formats, 5.7 points below rank 16. Its errors concentrated in two fields: date of loss (93.0%) and amount (98.0%). Updating all 494M parameters on 800 examples from five templates let the model fit those templates more tightly — where the date sits, how the amount is phrased — and that specificity did not transfer.

Is the gap real? Treating the two runs as independent samples of 300 items, the standard error of the difference is about 1.9 points, so 5.7 points is roughly 3.0 standard errors — unlikely to be test-set noise. It is still one seed and one learning rate per method, and run-to-run variance in fine-tuning can be several points; we would not generalize from it without repeats. What it does show is that “more trainable parameters” is not automatically “better” when training data is narrow, which is consistent with the finding of Biderman et al. that LoRA acts as a regularizer and forgets less.

0.00010.0010.010.11020406080100LoRA r=1LoRA r=16Full FTOptimizer stepTraining loss (log scale)
Training loss (10-step averages) on the JSON target tokens. All configurations drive loss close to zero; training loss says nothing about which one generalizes. Source: research/e1_lora_rank/results/*.json.

How much data was needed?#

We repeated LoRA r=16 with 100 and 200 training examples (four epochs, so the number of optimizer steps is comparable):

Training examplesIn-distributionUnseen formats
10097.0%91.0%
200100.0%93.0%
800100.0%97.0%

With only 100 examples the model reached 97.0% in-distribution and 91.0% on unseen formats. The pattern matches what we see in client work: a few hundred clean examples teach the format and most conventions; robustness to unfamiliar inputs is what the extra data buys.

What it cost#

LoRA r=1: 12.3 min; LoRA r=4: 7.3 min; LoRA r=16: 17.4 min; LoRA r=64: 8.4 min; Full fine-tuning: 22.9 min of training on the shared laptop GPU. Trainable parameters ranged from 0.55M (rank 1, 0.11% of the model) to 494M for full fine-tuning. Our compute model converts these runs to achieved throughput, and our memory model explains why the same recipe on a 7B model needs a 24 GB GPU with LoRA but two 80 GB GPUs for full fine-tuning.

Interpretation#

Three conclusions, with the caveats below:

  1. Fine-tuning is the difference between unusable and production-grade here. Zero-shot exact match was 18% even though every field-level score except three was high. All-fields-correct is what downstream systems need.
  2. Rank is a robustness knob more than an accuracy knob. On inputs like the training data, rank 4 was enough. On new formats, higher rank kept paying. If your production inputs drift — new channels, new templates, new partners — don’t pick rank from an in-distribution validation set.
  3. Full fine-tuning did not beat LoRA r=16 on unseen formats. Full fine-tuning reached 91.3% on unseen formats versus 97.0% for LoRA r=16, training 56× as many parameters. Biderman et al. report that LoRA under-performs full fine-tuning on harder, larger-scale domain adaptation (code and math) while forgetting less; a narrow extraction task sits at the other end of that spectrum.

Reproducing it#

cd research && uv venv .venv -p 3.12 && uv pip install -p .venv/bin/python torch transformers numpy
cd e1_lora_rank
python data.py                  # prints sample examples
../.venv/bin/python run.py --mode base
../.venv/bin/python run.py --mode lora --rank 16
../.venv/bin/python run.py --mode full
./sweep.sh                      # all eight runs, ~2 h on a laptop GPU

Each run writes results/<name>.json with trainable parameter count, wall-clock time, a loss curve and field-level metrics on both test sets. The page you are reading is generated from those files at build time.

What we would test next#

  • Seeds: three seeds per configuration to put error bars on every cell.
  • Learning-rate sweep for full fine-tuning: a lower rate or one epoch may close the generalization gap.
  • Rank-stabilized scaling () to separate the effect of rank from the effect of update magnitude.
  • A 7B model on real, de-identified claim notes, where base-model zero-shot performance is much higher and the headroom smaller.

Limitations#

One seed per configuration, so differences of a point or two are within run-to-run noise (300 test items give a 95% interval of roughly ±2–3 points near 95% accuracy). One small model, one synthetic task, one learning rate per method; a sweep could change the LoRA–full comparison. scaling means higher ranks also have larger effective update magnitude; rank-stabilized scaling () is an alternative. Synthetic templates are cleaner than real claim notes. We present this as a well-instrumented case study, not a general law.

Method and reproducibility

Code: research/e1_lora_rank/ — data.py (task generator), run.py (training, LoRA and evaluation), sweep.sh (all eight runs). Model: Qwen/Qwen2.5-0.5B-Instruct from Hugging Face. Per-run JSON with loss curves in results/.

References#

  1. Hu, E. J. et al. (2021). LoRA: Low-Rank Adaptation of Large Language Models. arXiv:2106.09685.
  2. Biderman, D. et al. (2024). LoRA Learns Less and Forgets Less. TMLR. arXiv:2405.09673.
  3. Kalajdzievski, D. (2023). A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA. arXiv:2312.03732.
  4. Qwen Team (2024). Qwen2.5 Technical Report. arXiv:2412.15115. Model configurations from the official Hugging Face repositories.

Frequently asked

Can’t find your question? Ask a senior engineer.

What LoRA rank should I use?

For narrow tasks whose production inputs look like your training data, low ranks (4–16) are usually enough. If inputs will vary or drift, higher ranks generalize better to unfamiliar formats in our experiment; validate on held-out formats, not just held-out examples.

Is LoRA as good as full fine-tuning?

On narrow tasks like structured extraction it is often within a point or two. Published work finds larger gaps on harder domain adaptation such as code and mathematics, where full fine-tuning learns higher-rank updates.

Bring us one task. We’ll show you what a specialist model can do.

A 45-minute briefing with a senior research engineer. You leave with a feasibility read, a data plan and a fixed quote, whether or not you hire us.

Book a briefing →