Skip to main content
Version: 0.9

LoRA Adapters

LoRA adapters let you specialize a base model at runtime without loading a separate copy. A single 1B–8B base can host many adapters and switch between them in milliseconds.

Two common patterns:

  • Per-character adapters — one adapter per NPC, quest line, or language register. The base model handles language; the adapter shapes voice, style, and personality.
  • Per-task adapters — one adapter per cognitive task: planning, decision-making, dialogue generation, summarization, intent classification. A single base model becomes a multi-skill backbone, with the active adapter selected per request based on what the game loop is currently asking for. Behaviour frameworks built on Atelico typically route a planner adapter, a decider adapter, and a dialogue adapter in turn during one NPC tick.

Atelico supports two on-disk adapter formats:

  • Standard LoRA — the original Low-Rank Adaptation format. Each layer ships two factor matrices A and B (low-rank decomposition of the weight delta).
  • LoRA-XS (arXiv:2405.17604) — inserts a small trainable r×r matrix R between two frozen SVD-derived factors of the base weight. Only R is trained, so the per-layer parameter count drops from r·(d_in + d_out) to just (256 floats per layer at rank 16, regardless of model size). On-disk adapters can be ~100× smaller than standard LoRA at comparable quality.

Both formats use the same load/unload/scale API — the engine auto-detects which one you've handed it.

Loading an Adapter

LoRA management lives in the SDK layer — every binding (Godot, Unity, Unreal, Python, C FFI, Rust SDK) exposes the same three operations: load, set scale, unload. The adapter directory must contain an adapter_config.json (HuggingFace PEFT layout) and the safetensors weight files.

Python example:

engine.lora_load(
"in-memory::meta-llama/Llama-3.2-1B-Instruct-Q4_K_M",
"/path/to/pirate-lora",
)

engine.lora_set_scale(
"in-memory::meta-llama/Llama-3.2-1B-Instruct-Q4_K_M",
0.7, # blend at 70% strength
)

engine.lora_unload("in-memory::meta-llama/Llama-3.2-1B-Instruct-Q4_K_M")

Once loaded, request the adapted variant by appending ::adapter-name to the model id in your chat completion call (the adapter name is taken from the adapter_config.json metadata):

response = engine.chat_completion(
model="in-memory::meta-llama/Llama-3.2-1B-Instruct-Q4_K_M::pirate",
messages=[{"role": "user", "content": "Greet the tavern."}],
)

See the per-SDK API references for the exact method signatures in each language.

Adapter Format Detection

The engine reads adapter_config.json and scans the safetensors keys to decide which format you're loading:

  1. If adapter_config.json sets peft_type to "LORA_XS" (case-insensitive), the adapter is treated as LoRA-XS.
  2. Otherwise, the engine inspects the safetensors keys. Any tensor ending in .lora_R.weight (or the reference-implementation alias .default_lora_latent_mapping.weight) marks the adapter as LoRA-XS.
  3. If neither signal is present, the adapter is treated as standard LoRA.

No flag, environment variable, or API change is required to use LoRA-XS — drop the adapter into your asset store and load it like any other.

LoRA-XS On-Disk Variants

Two on-disk layouts are accepted for LoRA-XS adapters:

VariantTensors per layerNotes
A (full)lora_A, lora_B, lora_RSelf-contained. Matches the reference implementation.
B (R-only)lora_R onlySmallest on-disk size. A and B are reconstructed at load time from the base weight via a deterministic randomized truncated SVD. The reconstruction is cached per (model, rank), so subsequent adapter swaps of the same rank are instant.

Variant B is the recommended layout for shipped game assets — a rank-16 R-only adapter on an 8B base model is well under 100 KB per layer.

Runtime Behavior

  • One adapter is active per base model at a time. Loading a second adapter onto the same model replaces the first.
  • lora_set_scale(model, α) blends the adapter at strength α (0.0 = base model only, 1.0 = full adapter, intermediate values interpolate).
  • Switching adapters is in-memory; there is no GPU model reload.
  • Inference throughput with an active adapter is within a few percent of the base model; the adapter forward path is a single fused matmul per attached projection.

See Also

  • Models — list of supported base architectures
  • NPC Dialogue — using per-character adapters to drive NPC personality