docs.na.id.au

Why two servers, not one

03 — Primary vs Secondary llama-server

The two-server pattern: a big “brain” card and a small “worker” card, and how to decide what lives on each.

Why two servers, not one

The temptation is one llama-server that sees both GPUs and holds everything. The two-server split is better for three reasons:

  1. Hardware isolation. CUDA_VISIBLE_DEVICES pins each server to one card, so a context-heavy request on the big card can never evict or starve the small one.
  2. Independent roles. The two cards do genuinely different jobs, with different models, context sizes, and loading behaviour — see below.
  3. Independent lifecycle. Each is its own systemd unit. You can restart, upgrade, or stop one without touching the other — and on a dual-use box, stop both to hand the GPUs to a desktop.

The division of labour

Primary (big card)Secondary (small card)
CardRTX 3090 (24 GB), CUDA_VISIBLE_DEVICES=0RTX 3070 (8 GB), CUDA_VISIBLE_DEVICES=1
Port80828083
RoleMain agent — the model the user primarily chats withWorker — sub-agent chat + the embedding model
Modelse.g. a 12B and a 27B (big weights, big context)a small chat model + bge-m3 (embeddings)
Context~185k–256k8k (chat) / 2k (embeddings)
LoadingSwap-in (--models-max 1, load-on-startup = false)Resident (load-on-startup = true)
Restarton-failurealways

The split is by job, not by model count. The primary is where “big brain” work belongs — long documents, long tool conversations, spec-writing. The secondary handles high-volume, cheap, latency-sensitive work (short sub-agent turns, embeddings) without ever touching the primary’s context memory.

The primary server (swap-in)

ExecStart=/usr/local/bin/llama-server \
    --models-preset /etc/llama/models-primary.ini \
    --models-max 1 \
    --no-mmproj \
    --metrics \
    --host :: \
    --port 8082

The secondary server (resident + embeddings)

ExecStart=/usr/local/bin/llama-server \
    --models-preset /etc/llama/models-secondary.ini \
    --no-mmproj \
    --embeddings \
    --metrics \
    --host :: \
    --port 8083

The embedding endpoint

A dedicated small embedding model (bge-m3, ~567M params, 1024-dim vectors, CLS pooling) on the worker card is the clean way to serve memory/RAG. Two things must both be true:

  1. The server was started with --embeddings.
  2. The model profile has pooling = cls (BGE-M3 pools on the CLS token).

The memory service then calls http://host.docker.internal:8083/v1/embeddings with model: "bge-m3". Because it’s a small, resident, fast model, embeddings are essentially free and never compete with the primary’s big context.

Choosing the split for your own box

The general principle: put the model that must be fast and cheap on the small card; put the model that must be smart and deep on the big card. Concretely:


Source Disclaimer