LoRA and X-LoRA adapters
LoRA (Low-Rank Adaptation) adds fine-tuned weights around a base model without modifying its weights. mistral.rs can keep multiple LoRA adapters loaded, select one independently for every request, and replace an adapter alias without interrupting in-flight requests. Dynamic LoRA supports compatible language-model adapters for text and multimodal models, including GGUF. Vision, audio, and projector adapters are not supported.
X-LoRA is a separate adapter mode that mixes several adapters per token rather than selecting one adapter for a request.
If you are upgrading from mistral.rs 0.9.0 or older, see the migration table.
| Mode | Base model support | Request behavior | Live updates |
|---|---|---|---|
| Dynamic LoRA | Compatible text and multimodal language models, including GGUF | Select zero or one adapter per request | Yes |
| Legacy LoRA | GGML and Phi3 GGUF | One static adapter configuration | No |
| X-LoRA | Compatible plain and GGML text models, plus Phi3 GGUF | Learned per-token mixture | No |
For GGUF file and projector selection, see Run GGUF models. The GGUF support reference summarizes adapter and multimodal boundaries.
Enable and preload LoRA
Section titled “Enable and preload LoRA”Give every preloaded adapter an explicit alias with --lora ALIAS=SOURCE. SOURCE can be a Hugging Face repository ID or local adapter directory. Repeat the option to preload more than one adapter:
mistralrs serve -m <base-model> \ --lora code=<code-lora-repo> \ --lora math=./math-adapterThe vLLM-compatible alias also accepts several modules after one option: --lora-modules code=<code-lora-repo> math=./math-adapter.
Dynamic LoRA is also available for supported GGUF language models:
mistralrs serve \ -m Qwen/Qwen2.5-0.5B-Instruct-GGUF \ --quant 4 \ --lora philosophy=closestfriend/brie-qwen2.5-0.5bThe alias, request-routing, and lifecycle APIs below also apply to GGUF. Model files and companion projectors follow the GGUF selection rules.
Aliases are trimmed, case-sensitive, nonempty UTF-8 strings of at most 256 bytes.
Preloading enables the dynamic LoRA runtime automatically. To start without an adapter and load one later, use --enable-lora:
mistralrs serve -m <base-model> --enable-loraFor multimodal models, the adapter applies only to the language model. Vision, audio, and projector adapters are not supported.
Remote adapters resolve from their own Hugging Face revision. --lora ALIAS=SOURCE uses main, regardless of the base model revision. Use --lora-modules '{"name":"code","path":"org/adapter","revision":"<commit>"}', LoraAdapter(alias=..., source=..., revision="...") in Python, LoraAdapterSpec::new(...).with_revision("...") in Rust, or structured TOML to pin another branch, tag, pull request ref, or commit. A local adapter directory does not use the revision. The JSON form accepts vLLM’s base_model_name as lineage metadata; it does not override or constrain -m, and model cards report the model actually selected by -m.
run and bench use the base model unless --adapter <alias> selects a preloaded adapter. In interactive mode, /adapter <alias> changes the selection, /adapter none selects the base model, and /adapter list shows the available aliases. Use /adapter use <alias> when the alias itself is none or list.
The runtime has three configurable admission limits:
| CLI option | Default | Purpose |
|---|---|---|
--lora-max-adapters | 16 | Maximum loaded aliases and, independently, resident generations, including retired generations still used by in-flight requests. |
--lora-max-rank | 256 | Maximum accepted adapter rank. |
--lora-max-bytes | 8 GiB | Maximum memory used by loaded adapters. |
--lora-max-bytes accepts raw bytes or KB, MB, GB, KiB, MiB, and GiB suffixes.
An adapter that exceeds a limit is rejected. --lora-max-bytes applies only to adapters, not total model memory. Replacing an alias does not interrupt requests already using its previous generation, so leave enough capacity for both versions during a replacement.
Python exposes these limits on Which.Lora and Which.GGUF. Rust uses
LoraModelBuilder::with_runtime_config(...) or
GgufModelBuilder::with_lora_runtime_config(...).
mistralrs run -m <base-model> \ --lora code=<code-lora-repo> \ --adapter codefrom mistralrs import ChatCompletionRequest, LoraAdapter, Runner, Which
runner = Runner( which=Which.Lora( model_id="<base-model>", adapters=[ LoraAdapter( alias="code", source="<code-lora-repo>", revision="<adapter-revision>", ) ], ))
response = runner.send_chat_completion_request( ChatCompletionRequest( model="default", adapter="code", messages=[{"role": "user", "content": "Write a binary search."}], ))use mistralrs::{LoraModelBuilder, RequestBuilder, TextMessageRole, TextModelBuilder};
let model = LoraModelBuilder::from_text_model_builder( TextModelBuilder::new("<base-model>"),).with_adapter_revision("code", "<code-lora-repo>", "<adapter-revision>").build().await?;
let response = model .send_chat_request( RequestBuilder::new() .set_adapter("code") .add_message(TextMessageRole::User, "Write a binary search."), ) .await?;Python and Rust require an exact GGUF filename. The GGUF guide covers automatic CLI selection.
from mistralrs import LoraAdapter, Runner, Which
runner = Runner( which=Which.GGUF( quantized_model_id="<gguf-repo>", quantized_filename="<model.gguf>", adapters=[LoraAdapter(alias="code", source="<code-lora-repo>")], ))use mistralrs::GgufModelBuilder;
let model = GgufModelBuilder::new("<gguf-repo>", vec!["<model.gguf>"]) .with_lora_adapter("code", "<code-lora-repo>") .build() .await?;These builders also support multimodal GGUF. Configure the model and companion assets as described in the GGUF guide; adapters apply only to the language-model component.
Use adapters=[] in Python or .with_lora() in Rust to enable a runtime without preloaded
adapters. In Python, setting any LoRA limit to a non-default value also enables that runtime.
Routed MoE adapters
Section titled “Routed MoE adapters”Routed-expert adapters use the aliases, per-request selection, preload limits, and lifecycle API described above.
mistralrs serve -m Qwen/Qwen3-30B-A3B \ --lora domain=<moe-lora-repo>Routed-expert LoRA is available for the language-model component of these families:
- DeepSeek V2, DeepSeek V3, and DeepSeek R1
- GLM-4.7 and GLM-4.7-Flash
- GPT-OSS
- Hunyuan MoE
- LFM2 and LFM2.5 MoE
- Mixtral
- Phi-3.5-MoE
- Qwen3 MoE
- Qwen3-Next and Qwen3-Coder-Next
- Qwen3-VL MoE, language model only
- Qwen3.5 and Qwen3.6 MoE, language model only
- Llama 4 and Gemma 4, language model only
This list is narrower than general MoE model support. Granite MoE is not supported. AnyMoE cannot be combined with dynamic per-request LoRA. Tensor parallelism supports preloaded adapters.
Multimodal text, image, audio, and video requests may select a compatible language-model adapter. Non-language adapters are not supported.
For example, this starts Qwen3.6 MoE with a public compatible adapter:
mistralrs serve -m Qwen/Qwen3.6-35B-A3B \ --lora domain=jeeejeee/qwen36-35ba3b-moe-all-linear-poken-loraUse the default auto model selection for safetensors multimodal LoRA. Image options such as
--max-edge, --max-num-images, and --max-image-length remain active when LoRA is enabled.
Select an adapter per request
Section titled “Select an adapter per request”Loaded aliases appear as model cards in GET /v1/models, so vLLM-style clients can select an adapter through model:
curl http://localhost:1234/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "code", "messages": [{"role":"user","content":"Write a binary search."}] }'The adapter model card identifies its base model through parent, exposes its current immutable adapter_generation, and uses the public adapter alias in root rather than a local filesystem path. The explicit mistral.rs adapter extension is useful when model and adapter routing should remain separate, especially in a multi-model server:
{ "model": "default", "adapter": "code", "messages": [{"role": "user", "content": "Write a binary search."}]}Use the stable qualified adapter card ID returned by GET /v1/models, such as <base-model>::code. A unique short alias such as code is also accepted for vLLM-style requests, but becomes ambiguous if another base model loads the same alias. Base model IDs take precedence. A request cannot select an adapter model in model and also send adapter.
Inference responses preserve the request-facing model value in both streaming and non-streaming modes. When the explicit adapter field is used with a base model, model remains the base model and adapter_generation identifies the selected adapter.
Chat Completions, Completions, and Responses accept either form. For adapter, pass an alias string, pass {"generation":"<generation-id>"} to require one exact resident generation, or omit the field to use the base model.
Each request keeps the adapter generation it started with, so replacing or unloading the alias cannot change an in-flight sequence.
Chat Completions and Completions expose the resolved adapter_generation on non-streaming responses and streaming chunks. A completed Responses resource exposes the same field. Capture it and send {"generation":"<adapter_generation>"} on a retry. To guarantee the generation even if the original request fails before returning metadata, read it from load or list first and use exact selection on the original request too. Exact selection fails after that generation is no longer resident.
Sending adapter to a model without a dynamic LoRA runtime returns an error. An unknown alias is never silently treated as the base model.
Hot-load, list, and unload
Section titled “Hot-load, list, and unload”The Rust and Python SDKs can load an adapter directory while a single-process model is serving requests. The directory must contain adapter_config.json and one adapter_model.safetensors file. The adapter must target the loaded base model; mistral.rs checks compatibility but cannot prove that an adapter was trained from the correct base weights.
Python lifecycle failures raise LoraAdapterError, a ValueError subclass with a stable code attribute. Core lifecycle failures share their codes with HTTP, so callers can branch on error.code without parsing message text. Python reports a malformed generation argument as invalid_lora_generation; HTTP rejects the same malformed JSON field as invalid_request_body.
For an initially empty SDK runtime, build Rust’s LoraModelBuilder without calling with_adapter, or use Python’s Which.Lora(model_id="<base-model>").
loaded = runner.load_lora_adapter("production", "./production-adapter")status = runner.lora_adapter_status()print(loaded.alias, loaded.generation)print(status.resident_generations, status.resident_bytes)runner.unload_lora_adapter("production")use mistralrs::LoraAdapterLoadPolicy;
let loaded = model .load_lora_adapter("production", "./production-adapter") .await?;
let status = model.lora_adapter_status().await?;for adapter in status.adapters { println!("{} {}", adapter.alias, adapter.generation);}println!("{} bytes resident", status.resident_bytes);
let replaced = model .load_lora_adapter_with_policy( "production", "./production-adapter-v2", LoraAdapterLoadPolicy::CompareAndSwap(loaded.generation), ) .await?;
model .unload_lora_adapter_if_generation("production", replaced.generation) .await?;curl http://localhost:1234/v1/load_lora_adapter \ -H 'Content-Type: application/json' \ -d '{"lora_name":"production","lora_path":"/srv/adapters/production"}'
curl http://localhost:1234/v1/lora_adapters
curl http://localhost:1234/v1/unload_lora_adapter \ -H 'Content-Type: application/json' \ -d '{"lora_name":"production"}'GET /v1/lora_adapters is always registered, including when runtime mutation is disabled. It reports loaded adapters, generation IDs, capacity, and configured limits for a model started with dynamic LoRA. A model without that runtime returns 409 with lora_runtime_unavailable. Source paths are included only while mutation is enabled. In a multi-model server, pass model in load and unload request bodies and ?model=<model-id> when listing.
Loading a new alias does not need load_inplace. To replace an existing alias, set load_inplace to true. Use expected_generation to make the swap compare-and-set rather than overwriting a generation that changed after it was inspected:
curl http://localhost:1234/v1/load_lora_adapter \ -H 'Content-Type: application/json' \ -d '{ "lora_name": "production", "lora_path": "/srv/adapters/production-v2", "load_inplace": true, "expected_generation": "<current-generation>" }'If the expected generation no longer matches, the request returns 409 and leaves the alias unchanged. Invalid files, capacity failures, and load failures also leave the previous alias in place.
Unloading accepts the same optional compare-and-set guard:
curl http://localhost:1234/v1/unload_lora_adapter \ -H 'Content-Type: application/json' \ -d '{ "lora_name": "production", "expected_generation": "<current-generation>" }'A mismatch returns 409 without removing the newer alias. A successful unload prevents new requests from selecting the alias but does not interrupt requests already using it.
Loads are serialized and non-queueing. A concurrent HTTP load returns 429 with code lora_load_busy; retry it with bounded backoff and jitter. A load can complete after the client disconnects, so query the list endpoint after a timeout. Repeating the default create request may return lora_adapter_already_loaded if the first request succeeded; treat the listed generation as authoritative. Replacing an alias with identical files returns the same generation.
Tensor-parallel deployments support preloaded adapters but not live load or unload operations.
Enable HTTP mutation
Section titled “Enable HTTP mutation”The read-only list route does not require mutation to be enabled. Runtime load and unload are available only when MISTRALRS_ALLOW_RUNTIME_LORA_UPDATING is enabled. MISTRALRS_LORA_ADAPTER_ROOT restricts lora_path to a canonical directory tree:
MISTRALRS_ALLOW_RUNTIME_LORA_UPDATING=1 \MISTRALRS_LORA_ADAPTER_ROOT=/srv/adapters \mistralrs serve -m <base-model> --enable-loraProduction rollout and recovery
Section titled “Production rollout and recovery”A safe alias rollout is:
- Publish the new config and weights together at a new immutable directory path.
- Load that directory under a canary alias and test it through the normal inference API.
- Read the production alias generation from
GET /v1/lora_adapters. - Load the tested files under the production alias with
load_inplace: trueand that value asexpected_generation. - Retain the previous files or a rollback alias until old requests have drained.
- Unload temporary aliases and confirm
retired_generationsreturns to zero.
Runtime-loaded aliases do not survive a server restart or model reload; put required startup aliases in CLI, TOML, Python, or Rust preload configuration. resident_bytes covers adapters only, so leave additional memory headroom for the model and serving workload. Tensor-parallel deployments can preload adapters but cannot mutate them live.
Support matrix
Section titled “Support matrix”| Area | Support |
|---|---|
| Model category | Compatible text and multimodal language models, including supported GGUF models. Vision, audio, and projector adapters are unsupported. |
| Request APIs | Chat Completions, Completions, and Responses, including multimodal requests supported by the selected model. Adapter selection is not supported for Embeddings, Anthropic Messages, diffusion, or dedicated speech requests. |
| Adapter files | PEFT adapter_config.json and one adapter_model.safetensors file. Pickle .bin, sharded adapter files, adapter tokenizers, and added vocabulary are not supported. |
| Adapter formats | Standard LoRA and RS-LoRA, including compatible routed expert adapters. PEFT rank_pattern, alpha_pattern, target_modules, target_parameters, and exclude_modules are supported where applicable. |
| Base weights | Plain and supported prequantized safetensors, in-situ quantization, UQFF, and supported GGUF. |
| CUDA | Ordinary and routed expert LoRA are supported. |
| Metal and CPU | Ordinary and routed expert LoRA are supported. |
| Tensor parallelism | Preloading is supported; live load, replace, and unload are not. |
| GGUF and GGML | GGUF supports dynamic LoRA for compatible language-model adapters. GGML uses legacy mode. See GGUF support for legacy and multimodal boundaries. |
| Speculative decoding | Dynamic LoRA attachment is not supported. |
| Unsupported adapter variants | DoRA, aLoRA, QALoRA, BD-LoRA, Arrow, MonteCLoRA, adapter bias, modules_to_save, layer replication, non-expert target_parameters, weight tying, trainable-token indices, Megatron configuration, and adapters that modify embeddings or the output head. |
| Base-transforming initializers | Configurations retaining pissa, pissa_niter_*, olora, corda, loftq, or lora_ga in init_lora_weights are rejected. Convert the result to an ordinary LoRA adapter for the original base before loading it. |
Unsupported adapter contents are rejected rather than partially loaded.
The default rank admission limit is 256. Change it with --lora-max-rank. Base and adapter requests can be served together.
Use the same preload and benchmark shape to measure isolated adapter overhead against the base model:
mistralrs bench -m <base-model> --lora code=<adapter-repo> \ --prompt-len 128,512 --gen-len 128 --iterations 5mistralrs bench -m <base-model> --lora code=<adapter-repo> --adapter code \ --prompt-len 128,512 --gen-len 128 --iterations 5The CLI benchmark reports TTFT and TPOT for one sequence. Its numbers do not characterize mixed-adapter server batching. Measure production workloads through the HTTP server with the same model, adapter ranks, prompt/decode lengths, concurrency, and sampling settings when comparing against another runtime.
Full examples: lora (Python), lora (Rust), and adapter-chat (HTTP).
Migrating from mistral.rs 0.9.0 and older
Section titled “Migrating from mistral.rs 0.9.0 and older”Dynamic LoRA replaces the ordinary-LoRA API from mistral.rs 0.9.0 and older. The previous path loaded one or more adapters as one static stack; the current path assigns every adapter an alias and selects zero or one adapter for each request.
| mistral.rs 0.9.0 and older | Current API |
|---|---|
Plain/safetensors --lora "repo-a;repo-b" | Repeat --lora alias=repo and select one alias with --adapter, the request adapter field, or an adapter model ID. |
Raw GGUF/GGML --lora <source> | For GGUF, use --lora alias=source and select the alias dynamically. For GGML or a compatible static GGUF configuration, use --legacy-lora <source> --legacy-lora-order <ordering-file.json>; the ordering JSON is required. |
Plain/safetensors TOML lora = "repo" | Use the structured adapter.lora list shown in the TOML reference. |
| Raw GGUF/GGML TOML LoRA fields | For GGUF, use the structured dynamic lora entries under [models.adapter]. Use legacy_lora with legacy_lora_order for GGML or a compatible static GGUF configuration. |
Python Which.Lora(adapter_model_ids, model_id=...) | Which.Lora(model_id, adapters=[LoraAdapter(alias=..., source=...)]). |
Rust LoraModelBuilder::from_text_model_builder(builder, adapter_ids) | Start with LoraModelBuilder::from_text_model_builder(builder), then call with_adapter or with_adapter_revision. |
Rust RequestBuilder::set_adapters(names) | Use set_adapter(alias) for one dynamic adapter. |
There is no direct dynamic-LoRA replacement for composing several ordinary adapters in one request. Use X-LoRA to mix adapters per token, or serve separately merged adapter weights when the intended composition is fixed.
Migrating from vLLM
Section titled “Migrating from vLLM”The lifecycle endpoint names and core lora_name/lora_path fields match vLLM, but response bodies, error envelopes, status codes, and extension fields are not wire-compatible. Regenerate or adapt clients instead of treating the lifecycle API as a drop-in replacement. The main mappings are:
| vLLM | mistral.rs |
|---|---|
vllm serve BASE --enable-lora --lora-modules code=SOURCE | mistralrs serve -m BASE --lora-modules code=SOURCE; --lora is the canonical spelling |
--enable-lora with no preload | --enable-lora |
--max-lora-rank N | Same spelling is accepted; --lora-max-rank is canonical |
VLLM_ALLOW_RUNTIME_LORA_UPDATING=True | MISTRALRS_ALLOW_RUNTIME_LORA_UPDATING=1 |
Request with "model":"code" | A unique short alias works. For unambiguous routing, use the qualified model ID returned by GET /v1/models. |
load_inplace: true | Same; add expected_generation for compare-and-set safety |
Adapter discovery through /v1/models | /v1/models model cards plus detailed GET /v1/lora_adapters status |
--max-loras | No direct equivalent: it limits active adapters per vLLM batch, while --lora-max-adapters bounds resident generations and aliases |
--max-cpu-loras | No equivalent; use --lora-max-bytes to limit loaded adapters |
| LoRA resolver plugins | No equivalent; use explicit remote preloads or the local-filesystem lifecycle endpoint |
--lora-dtype | No equivalent; LoRA uses the model execution dtype. |
--fully-sharded-loras | No equivalent. |
--api-key protection for mutation routes | No direct equivalent; use an authenticated reverse proxy. |
| Adapter tokenizer or extra vocabulary | Unsupported; requests use the base tokenizer and vocabulary |
| Multimodal LoRA | Compatible language-model adapters work across the CLI, server, Python, and Rust. Vision, audio, and projector adapters are unsupported. |
| Live updates with tensor parallelism | Preload adapters instead; runtime load and unload are unavailable |
The explicit adapter request field is optional when an alias is used as model. Use it for exact-generation selection or to keep base-model and adapter routing separate.
Troubleshooting
Section titled “Troubleshooting”Common HTTP lifecycle errors use a stable error.code:
| Code | Recovery |
|---|---|
lora_load_busy | Retry with bounded exponential backoff and jitter. |
lora_runtime_unavailable | Target a model with a dynamic LoRA runtime. For mistralrs serve, use a preload or --enable-lora; also check tensor-parallel restrictions. |
invalid_lora_name | Send a nonempty alias no longer than 256 bytes. |
lora_adapter_already_loaded | Inspect the current generation, then retry with load_inplace: true if replacement is intended. |
lora_adapter_not_found | Refresh /v1/lora_adapters; an exact generation may already have left residency. |
lora_generation_mismatch | Refresh the alias generation and decide whether the newer version should be replaced or removed. |
lora_generation_conflict | Refresh status before retrying. |
lora_rank_limit_exceeded | Raise the admission limit or use a lower-rank adapter. |
lora_alias_limit_exceeded | Unload an unused alias. |
lora_adapter_limit_exceeded | Unload unused aliases or wait for retired in-flight generations to drain. |
lora_byte_limit_exceeded | Free adapter capacity or raise the limit only when device headroom permits. |
lora_adapter_file_too_large | Use a smaller adapter file. If you lowered --lora-max-bytes, raise it only when device headroom permits. |
request_body_too_large | Reduce the JSON request size. Embedded servers can raise their body limit for trusted clients. |
invalid_lora_adapter | Check the PEFT format, supported features, and base-model compatibility. |
invalid_lora_load_policy | Send expected_generation only together with load_inplace: true. |
adapter_path_forbidden or adapter_file_forbidden | Move the immutable directory below MISTRALRS_LORA_ADAPTER_ROOT and fix its permissions. |
adapter_path_not_found, adapter_file_not_found, invalid_adapter_path, invalid_adapter_file, or model_not_found | Correct the path or model ID before retrying. |
malformed_json, invalid_request_body, invalid_content_type, or invalid_query | Correct the request encoding before retrying. |
adapter_storage_unavailable, lora_storage_unavailable, or lora_device_load_failed | Recover storage or device capacity before retrying. |
Python uses invalid_lora_generation when expected_generation is not a value returned by load, list, or inference. Pass generation values exactly as returned.
Legacy raw LoRA
Section titled “Legacy raw LoRA”GGUF supports dynamic LoRA for compatible language-model adapters. Legacy mode remains available for GGML and the static GGUF configurations listed in the GGUF support reference.
Use the ordering JSON published with the raw adapter when one is available. For a single adapter without a supplied file, the minimum shape is:
{ "base_model_id": "org/base-model", "order": ["adapter"]}base_model_id must match the selected base model. Each order name must match the name used in that adapter’s config and safetensors filenames. Preserve a publisher-provided layers mapping when present.
X-LoRA
Section titled “X-LoRA”X-LoRA mixes multiple adapters per token. It does not support the per-request selection or hot-loading APIs above.
The ordering file defines the adapter order.
mistralrs run \ -m <base-model> \ --xlora <xlora-repo> \ --xlora-order <ordering-file.json>--xlora and --xlora-order must be provided together. They conflict with dynamic LoRA and legacy raw GGUF/GGML LoRA. --tgt-non-granular-index <n> controls X-LoRA update granularity.
from mistralrs import Runner, Which
runner = Runner( which=Which.XLora( model_id="<base-model>", xlora_model_id="<xlora-repo>", order="<ordering-file.json>", ))use std::fs::File;use mistralrs::{TextModelBuilder, XLoraModelBuilder};
let model = XLoraModelBuilder::from_text_model_builder( TextModelBuilder::new("<base-model>"), "<xlora-repo>", serde_json::from_reader(File::open("<ordering-file.json>")?)?,).build().await?;Full examples: xlora-zephyr (Python), xlora (Rust).
AnyMoE
Section titled “AnyMoE”AnyMoE composes several compatible fine-tunes of the same base model into a MoE (Mixture of Experts) configuration.
- It is exposed through the Rust SDK (
AnyMoeModelBuilder) and the Python SDK (AnyMoeConfig,AnyMoeExpertType); it is not configurable via the CLI. - Expert checkpoints must share the base model architecture, and a small JSON calibration dataset is required to train the router.
- Dynamic per-request LoRA cannot wrap an AnyMoE pipeline. LoRA-backed AnyMoE experts remain supported through the AnyMoE expert configuration.
- See the AnyMoE Python reference for configuration fields.
Full examples: anymoe (Python), anymoe and anymoe-lora (Rust).