From beda020cf3cc3396fb25a9fb2f76d8bce6927e55 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 1 Mar 2026 22:40:54 -0500 Subject: [PATCH] Add scripts for managing and testing LLM backends in Proxmox CT --- README.md | 49 +++++++ scripts/manage_llm_backends_in_ct.sh | 211 +++++++++++++++++++++++++++ scripts/test_llm_backends_in_ct.sh | 135 +++++++++++++++++ 3 files changed, 395 insertions(+) create mode 100644 scripts/manage_llm_backends_in_ct.sh create mode 100644 scripts/test_llm_backends_in_ct.sh diff --git a/README.md b/README.md index 28c0d43..75bb05b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ Toolkit for building an **unprivileged Ubuntu 24.04 LXC** on Proxmox and install - Registers ROCm 7.2 `noble` apt repos and installs a chosen ROCm meta package. - `scripts/create_pytorch_venv_in_ct.sh` - Creates a Python 3.12 venv in the CT and installs AMD ROCm 7.2 PyTorch wheels. +- `scripts/manage_llm_backends_in_ct.sh` + - Installs/updates vLLM, Ollama, and llama.cpp and configures systemd services. +- `scripts/test_llm_backends_in_ct.sh` + - Runs health checks for vLLM, Ollama, and llama.cpp services/endpoints. ## Requirements @@ -65,6 +69,48 @@ pct exec 120 -- bash -lc '/opt/rocm/bin/rocm-smi || true' sudo bash ./scripts/create_pytorch_venv_in_ct.sh --ctid 120 --venv-path /opt/rocm-pytorch-venv ``` +6) Install LLM backends + services in CT: + +```bash +sudo bash ./scripts/manage_llm_backends_in_ct.sh --ctid 120 --action install --backend all --venv-path /opt/rocm-pytorch-venv +``` + +Update later: + +```bash +sudo bash ./scripts/manage_llm_backends_in_ct.sh --ctid 120 --action update --backend all --venv-path /opt/rocm-pytorch-venv +``` + +Backend-specific examples: + +```bash +sudo bash ./scripts/manage_llm_backends_in_ct.sh --ctid 120 --action install --backend vllm --venv-path /opt/rocm-pytorch-venv +sudo bash ./scripts/manage_llm_backends_in_ct.sh --ctid 120 --action install --backend ollama +sudo bash ./scripts/manage_llm_backends_in_ct.sh --ctid 120 --action install --backend llama-cpp +``` + +7) Test backend services/endpoints: + +```bash +sudo bash ./scripts/test_llm_backends_in_ct.sh --ctid 120 --backend all +``` + +Verbose diagnostics on failures: + +```bash +sudo bash ./scripts/test_llm_backends_in_ct.sh --ctid 120 --backend all --verbose +``` + +Test one backend only: + +```bash +sudo bash ./scripts/test_llm_backends_in_ct.sh --ctid 120 --backend vllm +sudo bash ./scripts/test_llm_backends_in_ct.sh --ctid 120 --backend ollama +sudo bash ./scripts/test_llm_backends_in_ct.sh --ctid 120 --backend llama-cpp +``` + + + ## ROCm package options `install_rocm_in_ct.sh` defaults to `rocm`, but you can pass alternatives, for example: @@ -114,3 +160,6 @@ Reference: - https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/install-methods/package-manager/package-manager-ubuntu.html - https://rocm.docs.amd.com/projects/radeon-ryzen/en/latest/docs/install/installryz/native_linux/install-pytorch.html +- https://docs.vllm.ai/en/latest/getting_started/installation/gpu/ +- https://docs.ollama.com/linux +- https://github.com/ggml-org/llama.cpp diff --git a/scripts/manage_llm_backends_in_ct.sh b/scripts/manage_llm_backends_in_ct.sh new file mode 100644 index 0000000..1cb644e --- /dev/null +++ b/scripts/manage_llm_backends_in_ct.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Install or update LLM backends (vLLM, Ollama, llama.cpp) and manage systemd services in a Proxmox CT. + +Usage: + sudo ./scripts/manage_llm_backends_in_ct.sh \ + --ctid 120 \ + --action install \ + --backend all + +Options: + --ctid Proxmox CTID (required) + --action + Action to perform (default: install) + --backend + Backend target (default: all) + --venv-path Python venv path for vLLM (default: /opt/rocm-pytorch-venv) + --llama-dir llama.cpp source dir (default: /opt/llama.cpp) + +Notes: +- vLLM is installed/updated into the specified ROCm PyTorch venv. +- Ollama follows official Linux installer flow. +- llama.cpp is built with HIP support (`-DGGML_HIP=ON`) for ROCm. +EOF +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "This script must be run as root." >&2 + exit 1 + fi +} + +CTID="" +ACTION="install" +BACKEND="all" +VENV_PATH="/opt/rocm-pytorch-venv" +LLAMA_DIR="/opt/llama.cpp" + +while [[ $# -gt 0 ]]; do + case "$1" in + --ctid) CTID="$2"; shift 2 ;; + --action) ACTION="$2"; shift 2 ;; + --backend) BACKEND="$2"; shift 2 ;; + --venv-path) VENV_PATH="$2"; shift 2 ;; + --llama-dir) LLAMA_DIR="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) + echo "Unknown argument: $1" >&2 + usage + exit 1 + ;; + esac +done + +if [[ -z "${CTID}" ]]; then + echo "--ctid is required." >&2 + usage + exit 1 +fi + +if [[ "${ACTION}" != "install" && "${ACTION}" != "update" ]]; then + echo "--action must be install or update" >&2 + exit 1 +fi + +case "${BACKEND}" in + vllm|ollama|llama-cpp|all) ;; + *) + echo "--backend must be one of: vllm, ollama, llama-cpp, all" >&2 + exit 1 + ;; +esac + +require_root + +if ! pct status "${CTID}" >/dev/null 2>&1; then + echo "Container ${CTID} does not exist." >&2 + exit 1 +fi + +if ! pct status "${CTID}" | grep -q "status: running"; then + echo "Starting CT ${CTID}..." + pct start "${CTID}" +fi + +echo "Running ${ACTION} for ${BACKEND} in CT ${CTID}..." +pct exec "${CTID}" -- env ACTION="${ACTION}" BACKEND="${BACKEND}" VENV_PATH="${VENV_PATH}" LLAMA_DIR="${LLAMA_DIR}" bash -s <<'IN_CT' +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive + +apt update +apt install -y \ + ca-certificates \ + curl \ + git \ + build-essential \ + cmake \ + ninja-build \ + pkg-config \ + python3 \ + python3-pip + +install_or_update_vllm() { + if [[ ! -x "$VENV_PATH/bin/python" ]]; then + echo "vLLM requires existing venv at $VENV_PATH. Create it first (create_pytorch_venv_in_ct.sh)." >&2 + exit 1 + fi + + "$VENV_PATH/bin/python" -m pip install --upgrade pip setuptools wheel + "$VENV_PATH/bin/python" -m pip install --upgrade vllm + + cat >/etc/default/vllm <<'EOF' +VLLM_MODEL=/opt/models/your-model +VLLM_PORT=8000 +EOF + + cat >/etc/systemd/system/vllm.service </etc/default/llama-cpp <<'EOF' +LLAMA_MODEL=/opt/models/your-model.gguf +LLAMA_PORT=8080 +LLAMA_CTX=4096 +EOF + + cat >/etc/systemd/system/llama-cpp.service < Proxmox CTID (required) + --backend + Backend to test (default: all) + --verbose Print service diagnostics on failures +EOF +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "This script must be run as root." >&2 + exit 1 + fi +} + +CTID="" +BACKEND="all" +VERBOSE="no" + +while [[ $# -gt 0 ]]; do + case "$1" in + --ctid) CTID="$2"; shift 2 ;; + --backend) BACKEND="$2"; shift 2 ;; + --verbose) VERBOSE="yes"; shift 1 ;; + -h|--help) usage; exit 0 ;; + *) + echo "Unknown argument: $1" >&2 + usage + exit 1 + ;; + esac +done + +if [[ -z "${CTID}" ]]; then + echo "--ctid is required." >&2 + usage + exit 1 +fi + +case "${BACKEND}" in + vllm|ollama|llama-cpp|all) ;; + *) + echo "--backend must be one of: vllm, ollama, llama-cpp, all" >&2 + exit 1 + ;; +esac + +require_root + +if ! pct status "${CTID}" >/dev/null 2>&1; then + echo "Container ${CTID} does not exist." >&2 + exit 1 +fi + +if ! pct status "${CTID}" | grep -q "status: running"; then + echo "Starting CT ${CTID}..." + pct start "${CTID}" +fi + +echo "Testing ${BACKEND} backend(s) in CT ${CTID} (verbose=${VERBOSE})..." + +pct exec "${CTID}" -- env BACKEND="${BACKEND}" VERBOSE="${VERBOSE}" bash -s <<'IN_CT' +set -euo pipefail + +if ! command -v curl >/dev/null 2>&1; then + apt update + apt install -y curl +fi + +PASS=0 +FAIL=0 + +run_check() { + local name="$1" + local cmd="$2" + local service="${3:-}" + if eval "$cmd"; then + echo "[PASS] $name" + PASS=$((PASS+1)) + else + echo "[FAIL] $name" + if [[ "$VERBOSE" == "yes" && -n "$service" ]]; then + echo "--- ${service}: systemctl status ---" + systemctl status "$service" --no-pager || true + echo "--- ${service}: journalctl (last 80 lines) ---" + journalctl -u "$service" -n 80 --no-pager || true + fi + FAIL=$((FAIL+1)) + fi +} + +check_vllm() { + run_check "vLLM systemd active" "systemctl is-active --quiet vllm" "vllm" + run_check "vLLM HTTP health" "curl -fsS --max-time 5 http://127.0.0.1:8000/health >/dev/null" "vllm" +} + +check_ollama() { + run_check "Ollama systemd active" "systemctl is-active --quiet ollama" "ollama" + run_check "Ollama version" "ollama --version >/dev/null" "ollama" + run_check "Ollama tags endpoint" "curl -fsS --max-time 5 http://127.0.0.1:11434/api/tags >/dev/null" "ollama" +} + +check_llama_cpp() { + run_check "llama.cpp systemd active" "systemctl is-active --quiet llama-cpp" "llama-cpp" + run_check "llama.cpp health" "curl -fsS --max-time 5 http://127.0.0.1:8080/health >/dev/null" "llama-cpp" +} + +case "$BACKEND" in + vllm) check_vllm ;; + ollama) check_ollama ;; + llama-cpp) check_llama_cpp ;; + all) + check_vllm + check_ollama + check_llama_cpp + ;; +esac + +echo "Summary: pass=$PASS fail=$FAIL" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi +IN_CT + +echo "Done."