← All courses ← Course home

Lesson 05 of 05

Several agents on one box

Running three or four agents against one local server is common now. More slots give more total speed, but each agent gets slower, and the context is shared or split.

Agent brief (llms.md)

Environment setup

Use the same local-lab folder from lesson 01 (uv, Python 3.11+, and uv add numpy). If you skipped it, do the setup in lesson 01 first.

Install llama.cpp. On macOS: brew install llama.cpp. On Linux or Windows, download a release from github.com/ggml-org/llama.cpp/releases. Check it with llama-server --version.

Install Ollama from ollama.com/download. Check it with ollama --version.

How llama-server shares context

On build b11624 the test box showed three cases in the startup log:

# default (-np auto): 4 slots, one shared pool
llama-server -hf Qwen/Qwen3-0.6B-GGUF:Q8_0
load_model: initializing, n_slots = 4, n_ctx_slot = 40960, kv_unified = 'true'

# -np 4 -c 8192: the context is split, 2048 tokens per slot
load_model: initializing, n_slots = 4, n_ctx_slot = 2048, kv_unified = 'false'

# -np 4 -c 8192 -kvu: one shared pool of 8192 for all slots
load_model: initializing, n_slots = 4, n_ctx_slot = 8192, kv_unified = 'true'

Split: each agent gets -c divided by -np. A long agent prompt fails even when other slots are empty. Shared: any agent can use the whole pool, but when the pool is full new requests are rejected. Nothing is cut to make room. The test box returned:

400 {"error":{"code":400,"message":"request (14298 tokens) exceeds the available context size (8192 tokens), try increasing it","type":"exceed_context_size_error"}}

Always read n_slots, n_ctx_slot, and kv_unified from your own log.

Measure total speed and speed per agent

"""Measure total and per-agent speed when several agents share one llama-server."""
import json
import sys
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor

URL = "http://127.0.0.1:8080/v1/chat/completions"


def one(i):
    body = {"messages": [{"role": "user", "content": f"Agent {i}: write two sentences about rivers."}],
            "max_tokens": 64, "temperature": 0, "chat_template_kwargs": {"enable_thinking": False}}
    req = urllib.request.Request(URL, json.dumps(body).encode(), {"Content-Type": "application/json"})
    start = time.perf_counter()
    r = json.load(urllib.request.urlopen(req))
    return r["usage"]["completion_tokens"], time.perf_counter() - start


for agents in [int(a) for a in sys.argv[1:]] or [1, 2, 4]:
    start = time.perf_counter()
    with ThreadPoolExecutor(agents) as pool:
        results = list(pool.map(one, range(agents)))
    wall = time.perf_counter() - start
    tokens = sum(t for t, _ in results)
    per_agent = sum(t / s for t, s in results) / agents
    print(f"agents={agents} total={tokens / wall:.1f} tok/s per-agent={per_agent:.1f} tok/s wall={wall:.1f}s")
llama-server -hf Qwen/Qwen3-1.7B-GGUF:Q8_0 --port 8080 -c 8192 -np 4
uv run python parallel_bench.py 1 2 4
agents=1 total=31.7 tok/s per-agent=31.7 tok/s wall=1.1s
agents=2 total=45.7 tok/s per-agent=22.9 tok/s wall=1.6s
agents=4 total=77.9 tok/s per-agent=20.7 tok/s wall=1.9s

On the test box, 4 agents got about 2.5 times the total speed of 1 agent, and each agent was about one third slower. Short prompts make this look good. A long prompt from one agent is read in the same batches as the others, so all agents wait while it is read.

Known traps

Practical sizing

Sources

Key concepts

  • -np N with -c splits the context per slot unless you add -kvu.
  • A full context returns an error. Nothing is trimmed for you.
  • More slots raise total speed and lower per-agent speed.
  • Some runtimes force one slot for hybrid models (Ollama #18938).

Takeaways

  • Read n_slots, n_ctx_slot, and kv_unified from your own log.
  • Measure total and per-agent speed with your own prompts.
  • Move to vLLM when you need many users or several GPUs.