Serving Many LoRA Adapters at Once
Every enterprise customer wanted their own fine-tuned support bot, but launching a full Llama-8B copy per tenant would have consumed 40 GB per instance times two hundred tenants. LoRA adapters shrink the per-customer delta to tens of megabytes — the hard problem becomes serving them concurrently on shared GPUs without loading a separate base model for each. Multi-LoRA serving is how platform teams offer personalized models economically: one base weights tensor, many low-rank overlays, routed per request.
LoRA recap for serving context
LoRA adds trainable low-rank matrices to attention and MLP layers:
W' = W + BA (B ∈ R^{d×r}, A ∈ R^{r×k}, r << d)
At inference, either merge W' offline or apply BA dynamically. Multi-adapter serving keeps W frozen and swaps (B, A) pairs per request.
Architecture overview
┌─────────────────────────┐
Request ──adapter_id──→ Router ──→ Batch scheduler
└──────────┬──────────────┘
↓
Base model (shared, GPU)
+ Adapter pool (A₁,B₁)...(Aₙ,Bₙ)
↓
Token stream out
Components:
- Adapter registry — metadata, object storage paths, base model compatibility
- Loader — mmap adapter weights from S3/GCS into GPU or CPU pin memory
- Scheduler — group requests by adapter to minimize switching
- Inference engine — vLLM, LoRAX, or custom Triton backend
vLLM multi-LoRA example
vLLM supports --enable-lora with multiple adapters:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--enable-lora \
--max-loras 32 \
--max-lora-rank 64 \
--lora-modules support-v1=./adapters/support-v1 \
legal-v2=./adapters/legal-v2
Request with adapter:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
resp = client.chat.completions.create(
model="support-v1", # LoRA module name
messages=[{"role": "user", "content": "Reset my password"}],
)
vLLM loads adapters on demand up to max-loras; LRU evicts cold adapters from GPU.
LoRAX and S-LoRA patterns
LoRAX (Predibase) — production server designed for heterogeneous LoRA batches, dynamic adapter loading from HuggingFace Hub or S3.
S-LoRA — research system optimizing GPU memory for thousands of adapters via unified paging and custom CUDA kernels — influences commercial implementations.
Key optimization: batch by adapter — mixing 20 different adapters in one micro-batch without specialized kernels causes sequential adapter application and kills throughput.
# Scheduler pseudocode
def schedule_batch(pending: Queue) -> list[Request]:
by_adapter = groupby(pending, key=lambda r: r.adapter_id)
# Pick largest group that fits batch size budget
return max(by_adapter, key=len)
Memory budgeting
Rough sizing:
GPU memory = base_model + kv_cache_batch + sum(loaded_adapters) + overhead
Llama-3-8B fp16 base ≈ 16 GB. Each LoRA adapter (rank 64, targeted modules) ≈ 50–200 MB depending on layers targeted.
With 24 GB GPU:
- Base + KV for batch: ~18 GB
- Room for ~10–30 hot adapters simultaneously
- Cold adapters stream from host or NVMe on first request (add latency)
Monitor adapter_load_latency and adapter_cache_hit_rate.
Adapter registry and versioning
adapters:
- id: tenant_442_support
base_model: meta-llama/Llama-3.1-8B-Instruct@rev_abc123
path: s3://models/lora/tenant_442/v3.safetensors
rank: 32
target_modules: [q_proj, v_proj]
created: 2025-03-01
Reject requests where adapter base revision mismatches server — silent wrong answers beat loud errors, but both are bad; validate at load time.
Training-to-serving pipeline
- Fine-tune with PEFT, save adapter only
- Validate on holdout set in eval harness
- Upload to registry, sign artifact
- Rolling deploy — register adapter without restarting base if engine supports hot load
- Canary traffic 5% → 100%
Keep adapter training rank consistent with serving max-lora-rank config.
Multi-tenant isolation
Adapters provide logical separation, not cryptographic isolation — shared base model and shared process mean side-channel and timing attacks are out of scope for most threats, but prompt data from tenant A must never log into tenant B's analytics.
Separate API keys mapping to adapter IDs. Rate limit per tenant. Consider dedicated GPU pools for regulated tenants despite shared-base efficiency.
When merging beats multi-LoRA
Merge adapters offline when:
- Single tenant dominates traffic (>80%)
- Adapter is permanent production config
- Every millisecond of latency matters
Serve merged model as standard deployment; keep multi-LoRA for long tail of tenants.
Benchmark adapter swap latency — loading adapters per request adds 100-500ms unless pre-warmed in GPU memory.
LoRA adapter hot-swapping
Pre-load top-N adapters in GPU memory by traffic forecast. Cold adapter load adds 200–500ms — unacceptable for interactive chat. Monitor adapter hit rate; demote cold adapters to CPU preload queue.
Common production mistakes
Teams get serving multi lora adapters wrong in predictable ways:
- Skipping failure-mode rehearsal — run a game day or fault injection exercise before peak traffic, not after the first outage.
- Missing correlation context — every error path should carry request, trace, or tenant identifiers so incidents are debuggable.
- Optimizing for demo, not steady state — load tests, cache warm-up, and cold-start paths matter more than local dev latency.
- Undocumented trade-offs — if you chose speed over strict correctness (or vice versa), write that down for the next engineer.
LLM features around serving multi lora adapters break in production when prompts assume deterministic output, context windows are sized for dev datasets, or token costs are never budgeted per user session. Always log prompt hash, model version, and latency—not raw prompts with PII.
Debugging and triage workflow
When serving multi lora adapters misbehaves in production, work top-down instead of guessing:
- Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
- Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
- Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
- Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
- Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
- Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.
Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.
Resources
- vLLM LoRA documentation
- LoRAX — LoRA Exchange
- S-LoRA paper
- Hugging Face PEFT library
- LoRA original paper (Hu et al.)
Frequently asked questions
Can I serve hundreds of LoRA adapters on one GPU?
Yes, with specialized serving systems like S-LoRA, LoRAX, and vLLM multi-LoRA support. LoRA adapters are small (often 10–100 MB) compared to base models (several GB), so many fit in GPU memory simultaneously. Throughput depends on batch composition — requests using different adapters in one batch require multi-adapter kernels or grouping by adapter.
How does the server know which LoRA adapter to use per request?
Pass an adapter ID in the request metadata — HTTP header, API field, or model name suffix (e.g., llama-3-8b:support-ticket-v2). The router loads or selects the adapter weights and applies them to the base model forward pass for that request. Register adapters in a catalog with version, tenant, and compatibility constraints matching the base model revision.
What is the difference between merging LoRA weights and runtime adapter switching?
Merging bakes adapter weights into the base model offline — faster inference, one model per adapter, no runtime switching. Runtime switching loads low-rank matrices separately and composes during forward pass — one base serves many adapters, essential for multi-tenant SaaS. Merge for single-tenant high-throughput; runtime switch for many tenants with sparse traffic each.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →