
# Multi-LoRA adapters are now available on GraphN

_A fine-tune per customer usually means a GPU per customer, mostly idle. On GraphN, up to eight LoRA adapters share one base deployment. Each one is its own model name, and you pay for the base's GPU time only._

![GraphN playground comparing a Qwen3-8B base with two LoRA adapters attached to it. The base gives a generic hint; each adapter hints at its own secret word.](/blog/lora-compare-base-vs-adapters.png)

## Nine models, one GPU, one second

Below is one Qwen3-8B base with eight public adapters attached. Each adapter was fine-tuned to hint at a different secret word without saying it. We sent all nine the same prompt at the same moment: every reply came back within 0.9 seconds, from one GPU. Select a row to see its request.

```graphn-theatre
lora-one-gpu
```

Every reply, latency and token count above is a real capture from 24 October 2026, at temperature 0.

## A GPU per fine-tune is mostly idle time

A fine-tune usually ships as its own deployment: its own GPU, its own cold start, its own line on the bill. That's fine for one model. With one fine-tune per customer, per language or per experiment, you end up paying for eight GPUs that each sit mostly idle.

A LoRA adapter is a small set of low-rank weights trained on top of a base model. It's a few hundred megabytes, not tens of gigabytes. So one base can keep several adapters loaded and pick the right one for each request.

```mermaid
flowchart LR
  A["model: custom:cm_…book"] --> R[Your base deployment]
  B["model: custom:cm_…moon"] --> R
  C["model: custom:cm_…snow"] --> R
  D["model: custom:cm_… (the base)"] --> R
  R --> G["One GPU per replica<br/>Qwen3-8B + up to 8 adapters"]
```

The model name in each request picks the adapter. Requests for different adapters are batched together on the same replica.

|                     | Eight dedicated deployments | One base, eight adapters           |
| ------------------- | --------------------------- | ---------------------------------- |
| GPUs                | 8                           | 1                                  |
| Price while running | 8 × $6.50 per GPU-hour      | $6.50 per GPU-hour                 |
| Model names         | 8                           | 9 (the base, plus one per adapter) |

## Use one base for customers, tasks and experiments

- **One fine-tune per customer.** Route each tenant to its own adapter by model name, without a GPU per tenant.
- **Task and language variants.** A support adapter, a SQL adapter and a summarizer, all on the same base.
- **Safe upgrades.** Attach `v2` next to `v1`, compare them side by side, move traffic, then delete `v1`. The base never restarts.
- **Experiments.** Try eight training runs against live prompts for the price of one deployment.

## Each adapter is its own model, at no GPU cost

- **Up to 8 adapters per base.** Each gets its own `custom:<id>` model name, works with every OpenAI-compatible client, and has its own requests, tokens, latency and logs.
- **No GPU cost per adapter.** The base bills $6.50 per GPU-hour, per second, while a replica runs. Adapters share it.
- **Checks before you attach.** GraphN reads the adapter's config, checks its rank against the base and flags an adapter trained on a different base, before anything is loaded.
- **Hot attach and delete.** The base and its other adapters keep serving while one is added or removed.
- **Hugging Face, presigned URLs and S3.** Gated and private repos take a token stored as a workspace secret; S3 buckets use an IAM role you control.

> **Note:** LoRA adapters are in public beta. Limits and pricing are listed at the end of this post.

## Eight adapters attach in 199 seconds

On a Qwen3-8B base like the one above, in our final tests before launch:

| What we did                               | Result                                            |
| ----------------------------------------- | ------------------------------------------------- |
| Attach one adapter                        | Ready in 157 seconds                              |
| Attach six at once, under traffic         | All six ready in 188 seconds                      |
| Attach eight at once                      | All eight ready in 199 seconds                    |
| Delete one adapter under traffic          | Gone in about 3 minutes                           |
| Requests to every model not being deleted | 13,991 served, 0 errors                           |
| Billing during attaches and deletes       | One GPU, billed continuously; nothing per adapter |

Adapters attached together load together, so eight take barely longer than one.

## In the console

**1. Deploy a base with LoRA adapters on.** Open **Deployments → Deploy a model**, choose **Check weights** for a supported base such as `Qwen/Qwen3-8B`, then turn on **Serve LoRA adapters on this base** and pick the highest adapter rank it should accept: 16 leaves more GPU memory for context, 64 takes larger adapters. The rank is fixed once deployed.

![Deploy form with the LoRA adapters toggle on and the highest adapter rank set](/blog/lora-deploy-toggle.png)

**2. Attach an adapter.** On the base's **Adapters** tab, choose **Attach adapter**, enter the repository and a name, and choose **Check adapter**.

![Attach dialog with preflight passed: adapter found, rank 32 fits the base, trained on Qwen/Qwen3-8B](/blog/lora-attach-preflight.png)

If the adapter names a different base, the check says so, and you have to confirm before attaching:

![Attach dialog warning that an adapter was trained on Qwen2.5-7B-Instruct while this base runs Qwen3-8B](/blog/lora-attach-wrong-base.png)

Attaching takes about three minutes. Meanwhile the base and every other adapter keep serving.

![Adapters tab with eight of eight attached: seven ready with per-adapter requests and tokens, one deploying](/blog/lora-adapters-tab.png)

**3. Call it.** Each adapter has its own page with an endpoint, a model name, and its own traffic and latency. **Try in playground** opens it in Chat, and **Compare** puts it next to its base, as in the screenshot at the top of this post.

![Adapter detail page: no GPU cost of its own, requests, TTFT, error rate, and the base's GPU-hours](/blog/lora-adapter-detail.png)

**4. Remove it.** **Hold to delete** on an adapter's page finishes the requests already in flight, then unloads it. The base keeps serving.

## From the CLI

Install the [GraphN CLI](/docs/developing-with-agents) (1.0.34 or later), then deploy the base:

```bash
graphn model custom deploy \
  --name qwen3-8b \
  --hf-model-id Qwen/Qwen3-8B \
  --lora-enabled \
  --max-lora-rank 64
```

Attach an adapter and wait until it's ready:

```bash
graphn model custom adapter attach qwen3-8b \
  --hf-model-id adamkarvonen/Qwen3-8B-taboo-book_50_mix \
  --name taboo-book \
  --wait
```

![Terminal: graphn model custom adapter attach --wait going from pending to deploying to ready, then the adapter list](/blog/lora-cli-attach.png)

List, inspect and remove adapters:

```bash
graphn model custom adapter list qwen3-8b
graphn model custom adapter get taboo-book
graphn model custom adapter delete taboo-book
```

Coding agents get the same operations as MCP tools through `graphn mcp-serve`: `graphn_custom_model_adapter_attach`, `_list`, `_get` and `_delete`.

## From your code

Each adapter is an ordinary model on the OpenAI-compatible API. Use the `custom:<id>` name from its page:

```bash
curl https://model.graphn.ai/v1/chat/completions \
  -H "Authorization: Bearer $GRAPHN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "custom:cm_6421d0490795",
    "messages": [{"role": "user", "content": "Give me a hint about your secret word."}]
  }'
```

Routing each customer to their own fine-tune is a lookup:

```python
from openai import OpenAI

client = OpenAI(base_url="https://model.graphn.ai/v1", api_key=GRAPHN_API_KEY)

ADAPTERS = {
    "acme": "custom:cm_6421d0490795",
    "globex": "custom:cm_003c7e675806",
}
BASE = "custom:cm_5eaa1948908e"


def answer(customer: str, question: str) -> str:
    reply = client.chat.completions.create(
        model=ADAPTERS.get(customer, BASE),
        messages=[{"role": "user", "content": question}],
    )
    return reply.choices[0].message.content
```

## Limits and pricing

- 8 adapters per base, at a rank no higher than the base's maximum (16 or 64).
- The base runs on one GPU per replica, without prefill/decode split, speculative decoding or quantization.
- Adapters must be plain LoRA in PEFT format: `adapter_config.json` plus `adapter_model.safetensors`, up to 4 GiB. DoRA, trained biases and added embeddings aren't supported.
- The base bills $6.50 per GPU-hour, per second, while a replica runs. Adapters are free, and their requests and tokens show up on their own pages.

The [LoRA adapters guide](/docs/lora-adapters) covers sources, the API and what each refusal means.

## Primary sources

- [LoRA adapters on a shared base](https://graphn.ai/docs/lora-adapters)
- [LoRA: Low-Rank Adaptation of Large Language Models](https://arxiv.org/abs/2106.09685)
- [PEFT LoRA configuration](https://huggingface.co/docs/peft/main/en/package_reference/lora)
- [Qwen3-8B model card](https://huggingface.co/Qwen/Qwen3-8B)
- [Qwen3-8B taboo adapters](https://huggingface.co/adamkarvonen/Qwen3-8B-taboo-book_50_mix)
