Skip to content
Ashish.
All posts
Diagram illustrating vLLM's multi-LoRA architecture with base model weights and swappable adapter matrices.
6 min readDevelopmentML engineers, platform engineersFeatured#vllm#lora#llm-serving#multi-tenant#inference#machine-learning#adapters

LoRA Adapters at Serve Time with vLLM Multi-LoRA

Learn how to serve multiple LoRA adapters concurrently in vLLM for efficient multi-tenant inference.

By Ashish KumarPart 7 of Production vLLM

In production Large Language Model (LLM) serving, the primary constraint is not compute throughput alone, but GPU memory bandwidth and capacity. While quantization and continuous batching address throughput, multi-tenancy often requires serving different model variants or fine-tunes to different clients simultaneously. Loading a full base model for each tenant is impossible; however, loading multiple Low-Rank Adaptation (LoRA) adapters concurrently is a viable strategy. vLLM implements this via a specific memory management architecture that decouples base model weights from adapter weights, allowing dynamic swapping without reloading the entire model.

This guide explores the mechanics of vLLM's multi-LoRA support, detailing the memory bottlenecks, configuration requirements, and inference lifecycle that enable efficient multi-tenant inference.

The Memory Bottleneck

Schematic diagram of GPU memory layout during LLM inference showing three distinct regions : Base Model Weights, KV Cache, and Adapter Weights.

To understand why multi-LoRA serving is complex, we must analyze the GPU memory layout during inference. A standard LLM inference request consumes three distinct memory regions:

  1. Base Model Weights: These are static. For a 7B parameter model in FP16, this is ~14 GB. These weights rarely change between requests unless you are switching between entirely different base models (e.g., Llama-2 vs. Mistral).
  2. KV Cache: This is dynamic and scales with sequence length and batch size. It stores the Key and Value tensors for every token generated so far.
  3. Adapter Weights: LoRA adapters introduce two low-rank matrices, AA and BB, where the rank rr is typically small (e.g., 8, 16, 32). For a 7B model with rank 16, a single adapter might occupy only ~200 MB.

The challenge arises when you want to serve Adapter A to Tenant 1 and Adapter B to Tenant 2 simultaneously. You cannot keep both adapter weights in the high-bandwidth HBM (High Bandwidth Memory) if they exceed available VRAM, nor can you afford the latency of loading them from disk for every single request. vLLM solves this by treating adapter weights as a swappable resource, similar to how operating systems swap pages, but optimized for the linear algebra operations of the transformer. This approach allows lora serving to be efficient, avoiding the need to reload base weights for every tenant.

Architecture of Multi-LoRA in vLLM

vLLM’s multi-LoRA implementation does not load all adapters at startup. Instead, it uses a lazy-loading mechanism. The base model is loaded once into VRAM. When a request arrives specifying a specific LoRA adapter, vLLM checks if that adapter’s weights are currently resident in GPU memory.

If the adapter is present, the forward pass proceeds immediately. By "immediate," we mean there is no weight-swapping latency; however, KV cache setup remains a prerequisite for execution. The computation follows the LoRA residual formula:

h=W0x+B(Ax)h = W_0 x + B(Ax)

Here, W0W_0 is the base weight matrix. Matrix AA projects the input xx to a low-dimensional space, and matrix BB projects it back. It is critical to note that AA and BB are NOT multiplied together into a single dense matrix BABA before application, as this would defeat the purpose of low-rank adaptation by increasing memory footprint and compute cost. The GPU computes the base matrix multiplication and adds the adapter contribution B(Ax)B(Ax).

If the adapter is not present, vLLM must swap it in. This involves transferring the adapter weights from CPU memory (or disk cache) to GPU VRAM. If VRAM is full, vLLM may evict an unused adapter to make room for the new one (vLLM’s multi-LoRA feature operates on adapters for a single base model within a given server instance, so the base model weights themselves are never swapped). Crucially, each request maintains its own KV cache. This means memory fragmentation can occur if you have many small requests with different adapters, as the KV cache slots are allocated per-request, not per-adapter.

Configuration and CLI Setup

Configuring vLLM to handle multiple adapters requires specific flags during server initialization. These configurations enable multi-lora serving by allowing the scheduler to manage distinct adapter contexts. The key parameter is --enable-lora, which activates the LoRA support. However, enabling it is not enough; you must define the adapters.

Consider a scenario where you have two adapters: legal-brief and creative-story. Both are stored in separate directories under a local path /models/adapters/.

vllm serve meta-llama/Llama-2-7b-chat-hf \
  --enable-lora \
  --max-loras 4 \
  --max-lora-rank 16 \
  --lora-modules \
    legal-brief=/models/adapters/legal-brief \
    creative-story=/models/adapters/creative-story

Let’s break down the mechanism behind these flags:

  • --enable-lora: Tells vLLM to initialize the LoRA context manager and allocate memory for adapter weight swapping.
  • --max-loras: Sets the upper limit on the number of distinct adapters that can be used within a single batch. This is effectively a batch-size limit on adapter diversity, not the total number of adapters cached in GPU memory. If you have 10 tenants using 5 different adapters, --max-loras must be at least 5 for all of them to be served in the same batch.
  • --max-lora-rank: Defines the maximum rank rr for any adapter. This pre-allocates buffer sizes for the AA and BB matrices. Setting this higher than necessary wastes memory; setting it lower will cause an error if an adapter exceeds the rank.
  • --lora-modules: Maps a logical name (used in API requests) to the physical path of the adapter weights.

The Inference Mechanism: A Worked Scenario

Flowchart showing the vLLM multi-LoRA inference lifecycle : Request Arrival, Weight Check, Swap In/Out, Forward Pass, and KV Cache Allocation.

Let’s trace the lifecycle of two concurrent requests to understand the data flow and latency implications.

Scenario:

  • Request 1: Tenant A sends a prompt for legal-brief.
  • Request 2: Tenant B sends a prompt for creative-story.

Step 1: Request Arrival Both requests hit the vLLM API endpoint. The scheduler identifies the lora_request_id for each. At this moment, assume neither adapter is in VRAM.

Step 2: Weight Swapping and Slot Management vLLM’s scheduler determines that it can serve both requests in the same batch. It manages LoRA slots; if slots are available, swapping is fast. It initiates a memory transfer, loading legal-brief’s AA and BB matrices into a reserved region of VRAM, and then creative-story’s matrices. This happens asynchronously relative to the CPU, utilizing PCIe bandwidth. Note that the 'one-time latency penalty' applies primarily to the first request hitting a cold adapter slot. If other adapters in the batch are already cached, their processing is not delayed by this swap.

Step 3: KV Cache Allocation Before the forward pass begins, vLLM allocates KV cache blocks for both requests. These blocks are indexed by request ID, not adapter ID. This is critical: if Tenant A sends another request with legal-brief, vLLM reuses the same adapter weights in VRAM (no swap needed) but allocates new KV cache blocks.

Step 4: Forward Pass The GPU executes the kernel. For the linear layers affected by LoRA (typically q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj), the CUDA kernel performs:

Output=BaseWeight×Input+B(A×Input)Output = BaseWeight \times Input + B(A \times Input)

The base weights are read once. The adapter weights are read twice (once for A, once for B). The results are summed.

Step 5: Eviction If a third request arrives for medical-summary and --max-loras is reached, vLLM must evict an existing adapter. It typically uses a Least Recently Used (LRU) policy. If creative-story hasn’t been used in the last few batches, its weights are swapped out to CPU memory to make room for medical-summary. This swap-out is fast but adds latency to the next request from Tenant B.

Multi-Tenant Implications and Tradeoffs

Serving multiple LoRA adapters is an effective tool for multi-tenancy, but it introduces specific operational tradeoffs in multi-lora configurations.

1. Memory Fragmentation Each request consumes KV cache slots. If you have 100 tenants, each using a different adapter, you might hit the --max-loras limit quickly. However, even if you stay under the limit, the KV cache fragmentation can reduce effective throughput. vLLM’s continuous batching helps mitigate this by packing requests efficiently, but the overhead of managing many distinct adapter contexts exists.

2. Swapping Latency The primary enemy of low-latency multi-LoRA serving is weight swapping. If your tenant distribution is highly skewed (e.g., 90% of traffic uses legal-brief, 10% rotates through 10 other adapters), you should increase --max-loras to keep the popular adapters pinned in VRAM. If traffic is uniform across many rare adapters, swapping overhead will dominate, and you may see higher p99 latency.

3. Rank Sizing Choosing --max-lora-rank is a memory vs. quality tradeoff. Higher ranks improve adapter capacity but increase VRAM usage and swap time. Monitor the size of your adapters. A rank-64 adapter for a 7B model is significantly larger than rank-8. Ensure your GPU has enough headroom for the base model, the KV cache, and the maximum number of concurrent adapters.

4. Isolation While LoRA adapters share the base model, they do not share the KV cache. This provides natural isolation: Tenant A’s context does not leak into Tenant B’s generation. However, if you need strict resource isolation (e.g., preventing one tenant from consuming all GPU memory via long sequences), you must rely on vLLM’s --max-num-seqs and --max-model-len parameters to cap the KV cache usage per batch or per tenant.

Conclusion

vLLM’s multi-LoRA support transforms the GPU from a static model host into a dynamic adapter hub. By keeping base weights static and swapping only the low-rank adapter matrices, it enables cost-effective multi-tenancy. The key to success lies in tuning --max-loras and --max-lora-rank based on your tenant’s access patterns. Understand that while the base model is shared, the KV cache is isolated, and the adapter weights are swappable. This architecture allows you to serve dozens of fine-tuned models on a single GPU, provided you manage the memory boundaries carefully.

Related posts