Posted on
Artificial Intelligence

Running Multiple Local LLMs

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Running Multiple Local LLMs on One Linux Box: A Practical, Bash‑First Guide

Ever wish you could run a coding assistant, a summarizer, and a lightweight Q&A model side by side on the same machine—without shipping your data to the cloud? With the right setup, you can serve multiple local LLMs concurrently on Linux, even on modest hardware. This article shows you how, using command-line tools and straightforward process controls you already know.

Why this matters:

  • Privacy and control: Keep prompts and data on your machine.

  • Cost and reliability: No per-token bills; works offline.

  • Speed and flexibility: Pin specific models to specific tasks; test, benchmark, and swap at will.

Below you’ll set up multiple LLM “micro‑services” using llama.cpp, give them resource boundaries so they don’t fight, and put a single front door in front of them with NGINX. There’s also a quick Ollama alternative if you want something simpler.


1) Prerequisites: Build tools, OpenBLAS, and basics

Install the tools we’ll use to build and run servers, plus optional NGINX and tmux.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y \
  git build-essential cmake ninja-build pkg-config libopenblas-dev \
  python3 python3-venv python3-pip curl wget unzip tmux nginx

Fedora/RHEL/CentOS Stream (dnf):

sudo dnf install -y \
  git gcc gcc-c++ cmake make ninja-build pkgconfig openblas-devel \
  python3 python3-virtualenv python3-pip curl wget unzip tmux nginx

openSUSE (zypper):

sudo zypper refresh
sudo zypper install -y \
  git gcc gcc-c++ cmake make ninja pkg-config openblas-devel \
  python3 python3-virtualenv python3-pip curl wget unzip tmux nginx

Optional GPU checks (only if you have a GPU and drivers installed):

# NVIDIA
nvidia-smi

# AMD ROCm
rocminfo

2) Run two LLM servers with llama.cpp (GGUF), each on its own port

We’ll build llama.cpp with OpenBLAS (CPU-friendly) and run two separate HTTP servers—one “coder”, one “summarizer”.

Build llama.cpp (CPU-first; add GPU flags later if you like):

git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
cmake -B build -S . -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
cmake --build build -j

Optional: Build with GPU support if your drivers/workflow are ready.

  • NVIDIA CUDA:
cmake -B build -S . -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS -DGGML_CUDA=ON
cmake --build build -j
  • AMD ROCm (example targets; adjust as needed):
cmake -B build -S . -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS -DGGML_HIPBLAS=ON -DAMDGPU_TARGETS=gfx1030
cmake --build build -j

Download two example GGUF models (one tiny, one mid-size):

mkdir -p ~/models && cd ~/models

# TinyLlama (tiny, good for tests)
wget -O tinyllama-q4.gguf \
  "https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/TinyLlama-1.1B-Chat-v1.0.Q4_K_M.gguf?download=true"

# Mistral 7B Instruct (mid-size; requires more RAM/VRAM)
wget -O mistral-7b-instruct-q4.gguf \
  "https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf?download=true"

Start two servers (different ports). The server binary is typically build/bin/llama-server or build/bin/server—use what you have:

cd ~/llama.cpp
LLAMA_BIN=$(ls build/bin/*server 2>/dev/null | head -n1)

# 1) Tiny "coder" service, port 8081 (CPU threads: 8, context: 2048)
"$LLAMA_BIN" -m ~/models/tinyllama-q4.gguf \
  --host 127.0.0.1 --port 8081 -c 2048 -t 8

# 2) "summarizer" service, port 8082 (threads: 12, larger context)
# If you built with CUDA/ROCm, consider offloading some layers: --n-gpu-layers 30
"$LLAMA_BIN" -m ~/models/mistral-7b-instruct-q4.gguf \
  --host 127.0.0.1 --port 8082 -c 4096 -t 12

Quick tests via HTTP (try either the simple or OpenAI-compatible endpoint):

# Simple completion endpoint (llama.cpp server)
curl -s http://127.0.0.1:8081/completion \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Write a one-line greeting.", "n_predict": 64 }'

# OpenAI-compatible chat endpoint (many clients already speak this)
curl -s http://127.0.0.1:8082/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{ "model":"local-mistral", "messages":[{"role":"user","content":"Summarize why local LLMs are useful."}] }'

Tips:

  • Prefer quantized models (Q4_K_M, Q5_K_M) for lower RAM/VRAM.

  • Use smaller context sizes (-c) if memory is tight.


3) Make them play nice: CPU, GPU, and memory boundaries

Pin CPU cores and set niceness so the two servers don’t starve each other:

# Bind "coder" to cores 0-7; nicer priority (lower CPU pressure)
taskset -c 0-7 nice -n 5 "$LLAMA_BIN" -m ~/models/tinyllama-q4.gguf \
  --host 127.0.0.1 --port 8081 -c 2048 -t 8

# Bind "summarizer" to cores 8-15; slightly lower prio (higher nice)
taskset -c 8-15 nice -n 10 "$LLAMA_BIN" -m ~/models/mistral-7b-instruct-q4.gguf \
  --host 127.0.0.1 --port 8082 -c 4096 -t 8

If you have multiple GPUs, isolate them:

# NVIDIA
CUDA_VISIBLE_DEVICES=0 "$LLAMA_BIN" -m ~/models/tinyllama-q4.gguf --host 127.0.0.1 --port 8081 -c 2048 -t 8 --n-gpu-layers 20
CUDA_VISIBLE_DEVICES=1 "$LLAMA_BIN" -m ~/models/mistral-7b-instruct-q4.gguf --host 127.0.0.1 --port 8082 -c 4096 -t 8 --n-gpu-layers 35

# AMD ROCm
HIP_VISIBLE_DEVICES=0  "$LLAMA_BIN" ...
HIP_VISIBLE_DEVICES=1  "$LLAMA_BIN" ...

Use systemd-run for quick cgroup quotas (no unit files needed):

systemd-run --user -u llm-coder \
  -p CPUQuota=150% -p MemoryMax=6G -p Nice=5 \
  "$LLAMA_BIN" -m ~/models/tinyllama-q4.gguf --host 127.0.0.1 --port 8081 -c 2048 -t 8

systemd-run --user -u llm-summarizer \
  -p CPUQuota=200% -p MemoryMax=12G -p Nice=10 \
  "$LLAMA_BIN" -m ~/models/mistral-7b-instruct-q4.gguf --host 127.0.0.1 --port 8082 -c 4096 -t 8

Monitor usage in real time:

top -H -p $(pgrep -d, -f "$LLAMA_BIN")
nvidia-smi   # or rocminfo / rocm-smi

4) One front door: route by path with NGINX

Put both services behind a single URL and let clients choose a model by path.

Install/reconfirm NGINX (if you skipped earlier):

  • apt:
sudo apt update && sudo apt install -y nginx
  • dnf:
sudo dnf install -y nginx
  • zypper:
sudo zypper install -y nginx

Create a simple proxy config:

sudo tee /etc/nginx/conf.d/llm.conf >/dev/null <<'EOF'
upstream llm_coder { server 127.0.0.1:8081; }
upstream llm_summarizer { server 127.0.0.1:8082; }

server {
    listen 8000;
    server_name _;

    # /coder/* -> tiny coder model
    location /coder/ {
        proxy_pass http://llm_coder/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }

    # /summarizer/* -> larger summarizer model
    location /summarizer/ {
        proxy_pass http://llm_summarizer/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
}
EOF

sudo nginx -t && sudo systemctl reload nginx

Test through NGINX:

curl -s http://127.0.0.1:8000/coder/completion \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Write a tiny bash oneliner that prints the date.", "n_predict": 64 }'

curl -s http://127.0.0.1:8000/summarizer/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{ "model":"any", "messages":[{"role":"user","content":"Summarize the Unix philosophy."}] }'

5) Make it stick: systemd user services

Turn each LLM into a managed service that starts on login and restarts on failure.

Coder service:

mkdir -p ~/.config/systemd/user
tee ~/.config/systemd/user/llm-coder.service >/dev/null <<'EOF'
[Unit]
Description=Local LLM - coder (llama.cpp)

[Service]
ExecStart=/bin/bash -lc 'LLAMA_BIN=$(ls ~/llama.cpp/build/bin/*server | head -n1); taskset -c 0-7 nice -n 5 "$LLAMA_BIN" -m ~/models/tinyllama-q4.gguf --host 127.0.0.1 --port 8081 -c 2048 -t 8'
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target
EOF

Summarizer service:

tee ~/.config/systemd/user/llm-summarizer.service >/dev/null <<'EOF'
[Unit]
Description=Local LLM - summarizer (llama.cpp)

[Service]
ExecStart=/bin/bash -lc 'LLAMA_BIN=$(ls ~/llama.cpp/build/bin/*server | head -n1); taskset -c 8-15 nice -n 10 "$LLAMA_BIN" -m ~/models/mistral-7b-instruct-q4.gguf --host 127.0.0.1 --port 8082 -c 4096 -t 8'
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable --now llm-coder llm-summarizer
loginctl enable-linger "$USER"  # keep services running after you log out (optional)

Alternative: Quick multi‑model serving with Ollama

Ollama runs multiple models under one daemon and exposes a simple local API. It’s the fastest path to “two models at once” if you don’t want to compile anything.

Install (official script):

curl -fsSL https://ollama.com/install.sh | sh

Pull two models and use them concurrently:

ollama pull llama3:8b
ollama pull mistral:7b-instruct

# Start the server (usually auto-starts)
ollama serve &

# Two terminals (or background jobs), two models:
ollama run llama3:8b
ollama run mistral:7b-instruct

Programmatic calls (same server, different model names):

curl http://localhost:11434/api/generate \
  -d '{ "model": "llama3:8b", "prompt": "Write a POSIX shell one-liner to count lines in *.log" }'

curl http://localhost:11434/api/generate \
  -d '{ "model": "mistral:7b-instruct", "prompt": "Summarize the last git commit in one sentence." }'

Tip: Prefer smaller/quantized variants when running more than one model at once.


Real‑world patterns that work

  • Divide and conquer: a tiny, fast model for intent detection or routing; a mid-size model for the “heavy” reply.

  • Latency tiers: small model produces a quick draft; larger model refines when latency budget allows.

  • Dedicated GPUs: one GPU per model with CUDA_VISIBLE_DEVICES or HIP_VISIBLE_DEVICES, keeping interference low.

  • Safe defaults: cap context sizes (-c), pin CPU cores, and set MemoryMax with systemd to prevent OOM.


Conclusion and next steps

You now have a practical way to run multiple local LLMs at once, with resource boundaries and a clean HTTP front door. Your next step:

  • Pick two models that fit your hardware (one small, one mid).

  • Stand up both with llama.cpp on different ports.

  • Put NGINX in front and route by path.

  • Add systemd user services so the stack survives reboots.

From here, you can add logging, TLS, a vector database, or a simple Bash router that picks models based on prompt size. If you build something cool—or want a follow-up on GPU tuning or streaming—tell me what you’d like to see next!