2024-10-11 · 3 min read

Merging a LoRA into the Base Model

Merging a LoRA adapter into the base model creates a single artifact with all weights combined. The tradeoff is simplicity and inference speed against flexibility and hot-swapping.

Why merge?

A LoRA adapter adds computation. During inference, the forward pass must apply the adapter matrices: output = x + (alpha/r) * (BA^T)x. For a 7B model, that's a small but measurable overhead.

Merged adapters skip this step entirely. The weights are baked in: W_merged = W_base + (alpha/r) * B*A^T. One matrix multiply instead of two. Throughput gains are 5–10% depending on batch size and model.

Additional benefits: - Single file to ship and version - Simpler deployment (no peft required at inference) - Easy to export to GGUF, ONNX, or TensorRT - No questions about adapter compatibility

The cost

Once merged, you cannot: - Adjust alpha at runtime - Swap the adapter without reloading - Recover the original base model weights - Mix this adapter with others

If you trained a merged adapter and later want to do A/B testing on alpha values, you must retrain.

How to merge

from peft import PeftModel
from transformers import AutoModelForCausalLM

# Load base + adapter
base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
model = PeftModel.from_pretrained(base_model, "path/to/qwen25-7b-support-tone")

# Merge and unload peft
merged_model = model.merge_and_unload()

# Save as standard safetensors
merged_model.save_pretrained("./merged-support-tone-fp16")

The result is a standard transformer model indistinguishable from any other fine-tuned checkpoint. No peft metadata, no special loading code.

File size comparison

Artifact Size Inference Hot-swap
Base model 14 GB baseline
Base + LoRA (separate) 14 GB + 100 MB baseline + 5% yes
Merged adapter 14 GB baseline + 0% no

The merged adapter is the same size as the base but includes the learned delta.

When to merge

Merge if your workflow is: - Deploy once, don't change - Need absolute minimum inference latency - Will export to GGUF or mobile later - Ship as a self-contained checkpoint

Don't merge if you plan to: - Run A/B tests on alpha values - Combine with other adapters - Quickly iterate and update - Hot-swap during serving

Practical example: sdxl-watercolor-lora

The sdxl-watercolor-lora adapter (223 MB) could be merged into SDXL (6 GB). The result is a 6.2 GB single-file checkpoint:

python merge_adapter.py \
  --base stabilityai/stable-diffusion-xl-base-1.0 \
  --adapter sdxl-watercolor-lora-1.4.0 \
  --output sdxl-watercolor-merged-fp16

This merged checkpoint: - Loads 5% faster than base + adapter - Requires no peft at runtime - Cannot be un-watercolored - Cannot be blended with other styles

Exporting merged adapters

After merging, you can convert to other formats:

# To GGUF (for llama.cpp / Ollama)
python convert_to_gguf.py sdxl-watercolor-merged-fp16 --outfile model.gguf

# To ONNX (for mobile / edge)
python -m transformers.onnx --model-dir sdxl-watercolor-merged-fp16 model_onnx/

Only merged adapters should be exported. LoRA checkpoints cannot be directly converted to GGUF or ONNX; you must merge first.

Versioning merged adapters

If you release a merged adapter, treat it as a distinct artifact: - Slug: sdxl-watercolor-merged-fp16 (different from sdxl-watercolor-lora) - Version: tied to the original adapter version - Metadata: note that it's pre-merged, cannot be disabled

ModelForgeLab can store both: the slim LoRA (100 MB) and the pre-merged version (6.2 GB) as separate downloads. Users choose based on their deployment.

Partial merging

Advanced: merge only certain layers, keep others as adapters. This is rare and usually not worth the complexity. Unless you have a specific reason (extreme memory constraint, mixed precision), stick to full merge or no merge.

The safest policy: keep adapters separate for development, merge only before final production deployment.