Enhance LLM runtime and installation script for Linux support

- Added a new function `pick_llamacpp_linux_asset_url` to select appropriate Linux release assets, prioritizing Ubuntu CUDA and Vulkan options while excluding Windows and macOS binaries.
- Updated the installation script to build `llama-server` from source when Linux CUDA binaries are unavailable, improving compatibility and flexibility.
- Revised documentation to reflect changes in asset handling and installation procedures.
- Added tests to validate the new asset selection logic, ensuring correct behavior in various scenarios.
This commit is contained in:
Leonid Pershin
2026-08-21 07:51:17 +03:00
parent 15b95f04c7
commit 242f5b7c89
4 changed files with 283 additions and 63 deletions
+3 -1
View File
@@ -120,6 +120,8 @@ Unit `gpu-rent-ollama` читает `/mnt/swarm_data/.gpu-rent-gpu.json`:
На `up`: скачать GGUF → `/mnt/swarm_data/llamacpp/models``llama-server` + systemd. Уже скачанные крупные файлы не трогает. На `up`: скачать GGUF → `/mnt/swarm_data/llamacpp/models``llama-server` + systemd. Уже скачанные крупные файлы не трогает.
**Бинарник:** у upstream нет Linux CUDA в GitHub Releases (только Windows). `install_llamacpp.sh` собирает `llama-server` из исходников (`GGML_CUDA=ON`, arch из GPU probe). Если `nvcc` недоступен — fallback на Ubuntu Vulkan/CPU asset (без Windows). Pin: `LLAMACPP_TAG=b10545`. Override: `LLAMACPP_ASSET_URL` + `LLAMACPP_SHA256`.
### Пресеты (меню) ### Пресеты (меню)
| # | ключ | что | | # | ключ | что |
@@ -154,6 +156,6 @@ Busy (не гасить GPU):
```bash ```bash
OLLAMA_VERSION=0.6.5 OLLAMA_VERSION=0.6.5
OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz> OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
LLAMACPP_TAG=b4690 LLAMACPP_TAG=b10545
# или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=... # или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=...
``` ```
+36
View File
@@ -357,6 +357,42 @@ def llm_remote_port(runtime: str) -> int | None:
return None return None
def pick_llamacpp_linux_asset_url(assets: list[dict[str, Any]]) -> str:
"""Choose a Linux llama.cpp release asset URL.
Upstream ships Windows CUDA zips first; never pick win/macos/cudart-only.
Prefer ubuntu+cuda → linux+cuda → ubuntu vulkan x64 → ubuntu x64 CPU.
Mirrored in remote/install_llamacpp.sh (pick_linux_asset_url).
"""
cands: list[tuple[int, str]] = []
for a in assets:
name = str(a.get("name") or "").lower()
url = str(a.get("browser_download_url") or "")
if not (url.endswith(".zip") or url.endswith(".tar.gz")):
continue
if any(x in name for x in ("win", "macos", "android", "darwin", "xcframework", "-ui.")):
continue
if "cudart" in name:
continue
score = 0
if "ubuntu" in name and "x64" in name and "cuda" in name:
score = 100
elif "linux" in name and "cuda" in name:
score = 90
elif "ubuntu" in name and "vulkan" in name and "x64" in name:
score = 50
elif "ubuntu" in name and "x64" in name and not any(
x in name for x in ("sycl", "openvino", "arm", "s390", "rocm")
):
score = 30
elif "ubuntu" in name or "linux" in name:
score = 10
if score:
cands.append((score, url))
cands.sort(key=lambda t: t[0], reverse=True)
return cands[0][1] if cands else ""
def append_vars_llm_runtime(vars_file: Path, runtime: str) -> None: def append_vars_llm_runtime(vars_file: Path, runtime: str) -> None:
from gpu_rent.varsfile import upsert_vars from gpu_rent.varsfile import upsert_vars
+191 -46
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Install llama-server (CUDA) for OpenAI-compatible API on loopback :8080. # Install llama-server (CUDA) for OpenAI-compatible API on loopback :8080.
# Official GitHub releases ship Windows CUDA only — on Linux we build from source.
set -euo pipefail set -euo pipefail
SWARM_USER="${SWARM_USER:-ubuntu}" SWARM_USER="${SWARM_USER:-ubuntu}"
@@ -7,7 +8,11 @@ DATA_ROOT="/mnt/swarm_data"
LLAMA_ROOT="${DATA_ROOT}/llamacpp" LLAMA_ROOT="${DATA_ROOT}/llamacpp"
MODELS_DIR="${LLAMA_ROOT}/models" MODELS_DIR="${LLAMA_ROOT}/models"
BIN_DIR="${LLAMA_ROOT}/bin" BIN_DIR="${LLAMA_ROOT}/bin"
SRC_DIR="${LLAMA_ROOT}/src"
STAMP="${BIN_DIR}/.build-id"
UNIT="gpu-rent-llamacpp" UNIT="gpu-rent-llamacpp"
REPO="https://github.com/ggml-org/llama.cpp.git"
API_BASE="https://api.github.com/repos/ggml-org/llama.cpp"
log() { echo "[gpu-rent-llamacpp] $*"; } log() { echo "[gpu-rent-llamacpp] $*"; }
@@ -20,61 +25,108 @@ mkdir -p "$MODELS_DIR" "$BIN_DIR"
chown -R "${SWARM_USER}:${SWARM_USER}" "$LLAMA_ROOT" chown -R "${SWARM_USER}:${SWARM_USER}" "$LLAMA_ROOT"
SERVER_BIN="${BIN_DIR}/llama-server" SERVER_BIN="${BIN_DIR}/llama-server"
if [[ ! -x "$SERVER_BIN" ]]; then
log "скачиваю llama-server (cuda) release…"
# Pin: LLAMACPP_TAG=b4690 LLAMACPP_ASSET_URL=... LLAMACPP_SHA256=...
# Без pin — latest release (supply-chain risk; docs/llm.md).
TMP="$(mktemp -d)"
cd "$TMP"
LLAMACPP_TAG="${LLAMACPP_TAG:-}" LLAMACPP_TAG="${LLAMACPP_TAG:-}"
LLAMACPP_ASSET_URL="${LLAMACPP_ASSET_URL:-}" LLAMACPP_ASSET_URL="${LLAMACPP_ASSET_URL:-}"
LLAMACPP_SHA256="${LLAMACPP_SHA256:-}" LLAMACPP_SHA256="${LLAMACPP_SHA256:-}"
if [[ -n "$LLAMACPP_ASSET_URL" ]]; then
URL="$LLAMACPP_ASSET_URL" # Pick a Linux release asset (never Windows/macOS). Prefer ubuntu CUDA → vulkan → cpu.
elif [[ -n "$LLAMACPP_TAG" ]]; then pick_linux_asset_url() {
API="https://api.github.com/repos/ggerganov/llama.cpp/releases/tags/${LLAMACPP_TAG}" python3 -c '
URL="$(curl -fsSL "$API" | python3 -c '
import json,sys import json,sys
data=json.load(sys.stdin) data=json.load(sys.stdin)
assets=data.get("assets") or [] assets=data.get("assets") or []
prefer=[] cands=[]
for a in assets: for a in assets:
n=(a.get("name") or "").lower() n=(a.get("name") or "").lower()
u=a.get("browser_download_url") or "" u=a.get("browser_download_url") or ""
if not (u.endswith(".zip") or u.endswith(".tar.gz")): if not (u.endswith(".zip") or u.endswith(".tar.gz")):
continue continue
if "cuda" in n or "cu12" in n or "cu11" in n: if any(x in n for x in ("win","macos","android","darwin","xcframework","-ui.")):
prefer.append(u) continue
if "cudart" in n:
continue
score=0
if "ubuntu" in n and "x64" in n and "cuda" in n:
score=100
elif "linux" in n and "cuda" in n:
score=90
elif "ubuntu" in n and "vulkan" in n and "x64" in n:
score=50
elif "ubuntu" in n and "x64" in n and not any(
x in n for x in ("sycl","openvino","arm","s390","rocm")
):
score=30
elif "ubuntu" in n or "linux" in n: elif "ubuntu" in n or "linux" in n:
prefer.append(u) score=10
print(prefer[0] if prefer else "") if score:
')" cands.append((score, u, n))
else cands.sort(reverse=True)
print(cands[0][1] if cands else "")
'
}
resolve_release_tag() {
if [[ -n "$LLAMACPP_TAG" ]]; then
echo "$LLAMACPP_TAG"
return
fi
log "WARN: LLAMACPP_TAG/ASSET_URL не заданы — берём latest (нет pin). См. docs/llm.md" log "WARN: LLAMACPP_TAG/ASSET_URL не заданы — берём latest (нет pin). См. docs/llm.md"
API="https://api.github.com/repos/ggerganov/llama.cpp/releases/latest" curl -fsSL "${API_BASE}/releases/latest" | python3 -c \
URL="$(curl -fsSL "$API" | python3 -c ' 'import json,sys; print(json.load(sys.stdin).get("tag_name") or "")'
import json,sys }
data=json.load(sys.stdin)
assets=data.get("assets") or [] cuda_architectures() {
prefer=[] python3 - <<'PY'
for a in assets: import json
n=(a.get("name") or "").lower() from pathlib import Path
u=a.get("browser_download_url") or "" p = Path("/mnt/swarm_data/.gpu-rent-gpu.json")
if not (u.endswith(".zip") or u.endswith(".tar.gz")): cap = "8.9"
continue if p.is_file():
if "cuda" in n or "cu12" in n or "cu11" in n: try:
prefer.append(u) cap = str(json.loads(p.read_text()).get("compute_cap") or cap)
elif "ubuntu" in n or "linux" in n: except Exception:
prefer.append(u) pass
print(prefer[0] if prefer else "") parts = cap.split(".")
')" try:
maj, mnr = int(parts[0]), int(parts[1]) if len(parts) > 1 else 0
print(f"{maj}{mnr}")
except ValueError:
print("89")
PY
}
ensure_build_deps() {
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -qq \
cmake build-essential git curl ca-certificates \
libcurl4-openssl-dev >/dev/null
if command -v nvcc >/dev/null 2>&1; then
return 0
fi fi
if [[ -z "$URL" ]]; then if [[ -x /usr/local/cuda/bin/nvcc ]]; then
log "не нашёл бинарь в release — поставь llama-server вручную в ${SERVER_BIN}" export PATH="/usr/local/cuda/bin:${PATH}"
exit 1 return 0
fi fi
log "asset $URL" log "ставлю nvidia-cuda-toolkit (нужен nvcc для сборки)…"
curl -fL "$URL" -o pkg.bin apt-get install -y -qq nvidia-cuda-toolkit >/dev/null
if command -v nvcc >/dev/null 2>&1; then
return 0
fi
if [[ -x /usr/local/cuda/bin/nvcc ]]; then
export PATH="/usr/local/cuda/bin:${PATH}"
return 0
fi
return 1
}
install_from_archive_url() {
local url="$1"
local tmp
tmp="$(mktemp -d)"
(
cd "$tmp"
log "asset $url"
curl -fL "$url" -o pkg.bin
if [[ -n "$LLAMACPP_SHA256" ]]; then if [[ -n "$LLAMACPP_SHA256" ]]; then
echo "${LLAMACPP_SHA256} pkg.bin" | sha256sum -c - echo "${LLAMACPP_SHA256} pkg.bin" | sha256sum -c -
else else
@@ -87,17 +139,110 @@ print(prefer[0] if prefer else "")
mkdir -p out mkdir -p out
tar -xaf pkg.bin -C out 2>/dev/null || tar -xzf pkg.bin -C out tar -xaf pkg.bin -C out 2>/dev/null || tar -xzf pkg.bin -C out
fi fi
FOUND="$(find out -type f -name 'llama-server' | head -n1 || true)" local found
if [[ -z "$FOUND" ]]; then found="$(find out -type f -name 'llama-server' | head -n1 || true)"
FOUND="$(find out -type f -name 'server' | head -n1 || true)" if [[ -z "$found" ]]; then
found="$(find out -type f -name 'server' | head -n1 || true)"
fi fi
if [[ -z "$FOUND" ]]; then if [[ -z "$found" ]]; then
log "в архиве нет llama-server" log "в архиве нет llama-server"
exit 1 exit 1
fi fi
install -m 755 "$FOUND" "$SERVER_BIN" install -m 755 "$found" "$SERVER_BIN"
chown "${SWARM_USER}:${SWARM_USER}" "$SERVER_BIN" chown "${SWARM_USER}:${SWARM_USER}" "$SERVER_BIN"
rm -rf "$TMP" )
local rc=$?
rm -rf "$tmp"
return "$rc"
}
build_cuda_from_source() {
local tag="$1"
local arch
arch="$(cuda_architectures)"
log "собираю llama-server CUDA из исходников (tag=${tag}, arch=${arch})…"
if ! ensure_build_deps; then
log "нет nvcc — CUDA-сборку пропускаем"
return 1
fi
log "nvcc $(nvcc --version 2>/dev/null | tail -n1 || echo '?')"
mkdir -p "$SRC_DIR"
if [[ -d "${SRC_DIR}/.git" ]]; then
git -C "$SRC_DIR" fetch --depth 1 origin tag "$tag" 2>/dev/null || true
if ! git -C "$SRC_DIR" checkout -f "$tag" 2>/dev/null; then
rm -rf "$SRC_DIR"
git clone --depth 1 --branch "$tag" "$REPO" "$SRC_DIR"
fi
else
rm -rf "$SRC_DIR"
git clone --depth 1 --branch "$tag" "$REPO" "$SRC_DIR"
fi
cmake -S "$SRC_DIR" -B "${SRC_DIR}/build" \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES="${arch}" \
-DLLAMA_CURL=ON \
-DLLAMA_BUILD_SERVER=ON
cmake --build "${SRC_DIR}/build" -j"$(nproc)" --target llama-server
local built="${SRC_DIR}/build/bin/llama-server"
if [[ ! -x "$built" ]]; then
log "сборка не дала ${built}"
return 1
fi
install -m 755 "$built" "$SERVER_BIN"
chown "${SWARM_USER}:${SWARM_USER}" "$SERVER_BIN"
echo "cuda:${tag}:${arch}" >"$STAMP"
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
log "CUDA binary → ${SERVER_BIN}"
return 0
}
install_linux_release_fallback() {
local tag="$1"
local api url
api="${API_BASE}/releases/tags/${tag}"
url="$(curl -fsSL "$api" | pick_linux_asset_url)"
if [[ -z "$url" ]]; then
log "в release ${tag} нет Linux-ассета"
return 1
fi
if [[ "$url" == *vulkan* ]]; then
log "WARN: официального Linux CUDA нет — беру Ubuntu Vulkan prebuilt"
elif [[ "$url" != *cuda* ]]; then
log "WARN: беру Linux prebuilt без CUDA (CPU) — лучше собрать с nvcc"
fi
install_from_archive_url "$url"
echo "asset:${tag}" >"$STAMP"
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
}
if [[ -x "$SERVER_BIN" ]]; then
log "llama-server уже есть: ${SERVER_BIN}"
else
if [[ -n "$LLAMACPP_ASSET_URL" ]]; then
log "скачиваю llama-server по LLAMACPP_ASSET_URL…"
install_from_archive_url "$LLAMACPP_ASSET_URL"
echo "asset-url" >"$STAMP"
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
else
tag="$(resolve_release_tag)"
if [[ -z "$tag" ]]; then
log "не удалось определить release tag"
exit 1
fi
if ! build_cuda_from_source "$tag"; then
log "CUDA-сборка не удалась — fallback на Linux release asset"
install_linux_release_fallback "$tag"
fi
fi
fi
if [[ ! -x "$SERVER_BIN" ]]; then
log "нет исполняемого ${SERVER_BIN}"
exit 1
fi fi
# Prefer a weights GGUF (skip mmproj), then attach --mmproj if present. # Prefer a weights GGUF (skip mmproj), then attach --mmproj if present.
+37
View File
@@ -7,6 +7,7 @@ from gpu_rent.llm_runtime import (
normalize_runtime, normalize_runtime,
parse_llamacpp_models, parse_llamacpp_models,
parse_ollama_models, parse_ollama_models,
pick_llamacpp_linux_asset_url,
remap_llamacpp_url, remap_llamacpp_url,
write_ollama_models_preset, write_ollama_models_preset,
) )
@@ -86,3 +87,39 @@ def test_remap_dead_bartowski_abliterate_url(tmp_path: Path):
entries = parse_llamacpp_models(path) entries = parse_llamacpp_models(path)
assert len(entries) == 1 assert len(entries) == 1
assert entries[0].url == fixed assert entries[0].url == fixed
def test_pick_llamacpp_linux_asset_skips_windows_cuda():
assets = [
{
"name": "cudart-llama-bin-win-cuda-12.4-x64.zip",
"browser_download_url": "https://example/cudart-win.zip",
},
{
"name": "llama-b10545-bin-win-cuda-12.4-x64.zip",
"browser_download_url": "https://example/win-cuda.zip",
},
{
"name": "llama-b10545-bin-ubuntu-x64.tar.gz",
"browser_download_url": "https://example/ubuntu-cpu.tar.gz",
},
{
"name": "llama-b10545-bin-ubuntu-vulkan-x64.tar.gz",
"browser_download_url": "https://example/ubuntu-vulkan.tar.gz",
},
]
assert pick_llamacpp_linux_asset_url(assets) == "https://example/ubuntu-vulkan.tar.gz"
def test_pick_llamacpp_linux_asset_prefers_ubuntu_cuda():
assets = [
{
"name": "llama-b1-bin-ubuntu-vulkan-x64.tar.gz",
"browser_download_url": "https://example/vulkan.tar.gz",
},
{
"name": "llama-b1-bin-ubuntu-cuda-12.4-x64.tar.gz",
"browser_download_url": "https://example/cuda.tar.gz",
},
]
assert pick_llamacpp_linux_asset_url(assets) == "https://example/cuda.tar.gz"