Spúšťanie LLM pomocou vLLM¶
Tento dokument opisuje, ako spustiť jazykový model na GPU uzle a komunikovať s ním prostredníctvom API kompatibilného s OpenAI API. Model beží v SLURM úlohe, ktorú sami spúšťate a ukončujete. API je dostupné iba cez váš SSH tunel, takže komunikácia neprechádza mimo clustra.
Táto stránka opisuje server modelu, ktorý tvorí základ pre:
- Qwen Code a ďalších AI programovacích agentov;
- notebooky a skripty využívajúce OpenAI Python SDK;
- dávkovú inferenciu a vyhodnocovacie úlohy;
- všetkých ostatných klientov podporujúcich OpenAI API.
Ak chcete server používať z programovacieho agenta, pozrite dokument Qwen Code — jeho spúšťací skript vytvorí tunel automaticky. Pri použití iného klienta postupujte podľa časti Pripojenie klienta.
Rýchly štart¶
Po dokončení jednorazového nastavenia:
cd ~/llm
sbatch submit_llm.sh # start the server (takes a few minutes)
Po skončení práce uvoľnite GPU:
scancel JOB_ID
Ak chcete server používať z programovacieho agenta, pozrite dokument Qwen Code — jeho spúšťací skript vytvorí tunel automaticky. Pri použití iného klienta postupujte podľa časti Pripojenie klienta.
Ako to funguje¶
Login node (x86_64) GPU compute node (aarch64)
+-----------------------------+ +------------------------------+
| your client | | SLURM job: run_vllm.sh |
| agent / notebook / script | | +----------------------+ |
| | | | vLLM SIF | |
| | | | vllm serve | |
| | | +----------+-----------+ |
| 127.0.0.1:LOCAL_PORT | | 127.0.0.1:SERVER_PORT |
+--------------|--------------+ +--------------|---------------+
| |
+---------- ssh -L tunnel -------------+
- Server nie je verejne dostupný. vLLM počúva na adrese
127.0.0.1na výpočtovom uzle, takže je dostupný iba cez váš SSH tunel. Nie je priamo vystavený do siete. - Životnosť modelu určuje úloha. Keď sa SLURM úloha ukončí z akéhokoľvek dôvodu vrátane dosiahnutia časového limitu, nasledujúca požiadavka klienta zlyhá.
- Jeden server, viacero klientov. Ak má server dostatočnú kapacitu, môže ho súčasne používať viacero klientov. Každý klient nepotrebuje vlastnú GPU.
- Uzly používajú odlišné architektúry. Prihlasovacie a CPU uzly používajú x86_64, GPU uzly aarch64. Obraz Singularity vytvorený pre jednu architektúru nebude fungovať na druhej.
Jednorazové nastavenie¶
1. Vytvorenie adresárovej štruktúry¶
mkdir -p ~/llm/jobs ~/llm/run
chmod 700 ~/llm/run
| Cesta | Obsah |
|---|---|
~/llm/config.env |
Spoločná konfigurácia používaná všetkými skriptami |
~/llm/submit_llm.sh |
Skript na odoslanie úlohy do SLURM |
~/llm/run_vllm.sh |
Spúšťací skript servera, ktorý beží v rámci úlohy |
~/llm/jobs/ |
Logy úloh, vllm-JOBID.log |
~/llm/run/ |
Metadáta pripojenia pre jednotlivé úlohy, režim 0700 |
2. Vytvorenie API kľúča¶
vLLM vyžaduje API kľúč pri každej požiadavke. Tým sa zabráni ostatným používateľom výpočtového uzla používať vašu GPU prostredníctvom vášho portu.
openssl rand -hex 24 > ~/.llm_api_key
chmod 600 ~/.llm_api_key
Súbor config.env načítava kľúč z tohto súboru. Kľúč sa preto nenachádza
priamo v konfigurácii, v obraze SIF ani v repozitári.
3. Inštalácia skriptov¶
Skopírujte súbory config.env, submit_llm.sh a run_vllm.sh z časti
Skripty do adresára ~/llm a následne upravte:
config.env— Modify theLLM_PROJECTandVLLM_SIFvariables.
chmod +x ~/llm/*.sh
4. Stiahnutie modelu vopred¶
Pred prvým spustením úlohy stiahnite model na prihlasovacom uzle, ktorý má prístup do externej siete:
source ~/llm/config.env
singularity exec "$VLLM_SIF" \
hf download "$MODEL_NAME" --cache-dir "$HF_HUB_CACHE"
Sťahovanie v rámci úlohy zbytočne spotrebúva čas GPU a úplne zlyhá, ak
výpočtové uzly nemajú prístup do externej siete. Modely s obmedzeným prístupom
navyše vyžadujú huggingface-cli login alebo nastavenú premennú HF_TOKEN.
Model s 27 miliardami parametrov vo formáte FP8 potrebuje približne 30 GB
úložného priestoru a vyrovnávacia pamäť sa postupne zväčšuje s ďalšími modelmi.
Skripty¶
Riešenie pozostáva z troch súborov: config.env obsahuje nastavenia,
submit_llm.sh požiada SLURM o GPU a run_vllm.sh spustí server modelu.
config.env — spoločná konfigurácia¶
Každý skript na tejto stránke aj na stránkach agentov načítava tento súbor,
takže všetky používajú rovnaký model, vyrovnávaciu pamäť a kontajner.
Súbor neobsahuje tajné údaje: API kľúč sa načítava na konci zo súboru
~/.llm_api_key, ktorý má oprávnenia 0600 a nachádza sa mimo adresára
so skriptmi. Vďaka tomuto oddeleniu môžete config.env skopírovať kolegovi,
uložiť do repozitára alebo vložiť do požiadavky bez rizika zverejnenia kľúča.
~/llm/config.env
# Shared configuration for vLLM and its clients on Perun.
# Sourced by run_vllm.sh and agent.sh: source ~/llm/config.env
#
# This file holds no secrets. The API key lives in ~/.llm_api_key (0600).
# ---- Paths -------------------------------------------------------------
export LLM_HOME="${LLM_HOME:-$HOME/llm}"
export LLM_PROJECT="" # LLM storage - adviced to use `/project` or `/scratch` due to quota limit on `/home`
export LLM_RUNTIME="$LLM_HOME/run" # per-job connection metadata
export LLM_LOGS="$LLM_HOME/jobs" # SLURM job logs
# ---- Caches ------------------------------------------------------------
# Model weights land here. Keep it off $HOME.
export AI_CACHE="$LLM_PROJECT/llm/cache"
export XDG_CACHE_HOME="$AI_CACHE"
export HF_HOME="$AI_CACHE/huggingface"
export HF_HUB_CACHE="$AI_CACHE/huggingface"
export TORCH_HOME="$AI_CACHE/torch"
export TRITON_CACHE_DIR="$AI_CACHE/triton"
export CUDA_CACHE_PATH="$AI_CACHE/cuda"
export VLLM_CONFIG_ROOT="$AI_CACHE/vllm"
export FLASHINFER_CACHE_DIR="$AI_CACHE/flashinfer"
# ---- Model - Change these for other models -----------------------------
export MODEL_NAME="Qwen/Qwen3.8-27B-FP8" # what vLLM loads - see `https://huggingface.co` for various models
export SERVED_MODEL="qwen3.8" # what the API advertises
export MODEL_LENGTH="262144" # Context window length
export GPU_MEMORY_UTILIZATION="0.92" # Set hard limit to GPU memory to avoid OOM
export TENSOR_PARALLEL_SIZE="1" # Number of used GPUs - must be same as number of GPUs requested in slurm
# Model-specific parsers - applies specifically to Qwen Code
export REASONING_PARSER="qwen3"
export TOOL_PARSER="qwen3_coder"
# ---- Server container - Change here for other images/clusters ----------
export VLLM_SIF="/apps/images/vllm/aarch64/vllm_26.04-py3-xxhash.sif"
export QWEN_SIF_X86="/apps/images/vllm/x86_64/Qwen_code_x86_64_Nov2026.sif"
# ---- API key -----------------------------------------------------------
# Create once:
# openssl rand -hex 24 > ~/.llm_api_key && chmod 600 ~/.llm_api_key
if [[ -r "$HOME/.llm_api_key" ]]; then
KEY=$(< "$HOME/.llm_api_key")
export KEY
fi
# The server port is chosen by run_vllm.sh and published in $LLM_RUNTIME.
# The local tunnel port is chosen by the client. Neither is configured here.
Devana cluster
On Devana cluster singularity containers can be found at /
submit_llm.sh — požiadavka na výpočtové zdroje¶
Tento skript iba požiada SLURM o GPU a odovzdá riadenie skriptu
run_vllm.sh. Vďaka jednoduchému obsahu môžete meniť spôsob spúšťania
servera bez potreby upravovať odosielací skript. Skript run_vllm.sh môžete
na účely ladenia spustiť aj pomocou srun.
~/llm/submit_llm.sh
#!/bin/bash
# Submit with: sbatch submit_llm.sh
#
# #SBATCH lines are read by SLURM before any shell expansion, so $HOME and
# $USER do NOT work here. Edit the absolute paths below to your own login.
#SBATCH --account= # Slurm account
#SBATCH --job-name=vllm
#SBATCH --partition=gpu_short
#SBATCH --gres=gpu:1 # Request one GPU
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=72 # Corresponds to one GPU
#SBATCH --time=04:00:00
#SBATCH --output=jobs/%x-%j.log
#SBATCH --error=jobs/%x-%j.log
bash run_vllm.sh
run_vllm.sh — server modelu¶
Skript beží v rámci alokácie na GPU uzle. Vykonáva tieto kroky:
- Sources the configuration and refuses to continue without an API key or
a
SLURM_JOB_ID— the latter catches the common mistake of running it directly on a login node, where it would find no GPU. - Nastaví automatické čistenie metadátového súboru, aby po zrušení alebo páde úlohy nezostal súbor nesprávne indikujúci, že server stále beží.
- Požiada jadro systému o voľný port, čím zabráni kolíziám medzi úlohami.
- Zverejní údaje o pripojení v súbore
~/llm/run/JOBID.jobpri použitíumask 077, takže informácie o serveri môžete čítať iba vy. - Zostaví zoznam argumentov a pridá parser uvažovania a parser volania nástrojov iba vtedy, ak sú nakonfigurované.
~/llm/run_vllm.sh
#!/bin/bash
# Runs vLLM inside a detached tmux session within the SLURM allocation.
# SLURM job remains active while the tmux session is running.
set -euo pipefail
# Load configuration
source "${LLM_HOME:-$HOME/llm02}/config.env"
# Validate required variables
: "${KEY:?No API key. Create ~/.llm_api_key -- see config.env.}"
: "${SLURM_JOB_ID:?Run this through sbatch, not directly.}"
# Prepare runtime directories
mkdir -p "$LLM_RUNTIME" "$LLM_LOGS"
chmod 700 "$LLM_RUNTIME"
# Use a job-specific session name to allow multiple vLLM jobs per user
SESSION="vllm-${SLURM_JOB_ID}"
# Metadata file
META="$LLM_RUNTIME/$SLURM_JOB_ID.job"
# Remove metadata when the SLURM job exits
trap 'rm -f "$META"' EXIT
# Let the kernel pick a free port on this compute node
SERVER_PORT=$(python3 -c '
import socket
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])
')
# Export the selected port for other scripts
export SERVER_PORT
# Write job metadata
umask 077
cat > "$META" <<EOF
JOB_ID=$SLURM_JOB_ID
NODE=$(hostname -s)
SERVER_PORT=$SERVER_PORT
SERVED_MODEL=$SERVED_MODEL
MODEL_NAME=$MODEL_NAME
STARTED=$(date -Is)
EOF
echo "Starting vLLM: $(hostname -s):$SERVER_PORT"
echo "Model: $MODEL_NAME"
echo "Session: $SESSION"
# Build vLLM arguments
args=(
--served-model-name "$SERVED_MODEL"
--host 127.0.0.1
--port "$SERVER_PORT"
--tensor-parallel-size "$TENSOR_PARALLEL_SIZE"
--gpu-memory-utilization "$GPU_MEMORY_UTILIZATION"
--max-model-len "$MODEL_LENGTH"
--enable-prefix-caching
--prefix-caching-hash-algo xxhash
)
# Optional reasoning parser
if [[ -n "${REASONING_PARSER:-}" ]]; then
args+=(--reasoning-parser "$REASONING_PARSER")
fi
# Optional tool calling support
if [[ -n "${TOOL_PARSER:-}" ]]; then
args+=(--enable-auto-tool-choice)
args+=(--tool-call-parser "$TOOL_PARSER")
fi
# Optional chat template
if [[ -n "${CHAT_TEMPLATE:-}" ]]; then
args+=(--chat-template "$CHAT_TEMPLATE")
fi
# Export API key through the tmux session environment
# This avoids exposing the key in the process command line
export VLLM_API_KEY="$KEY"
# Prepare command with proper shell escaping
printf -v MODEL_Q '%q' "$MODEL_NAME"
printf -v IMAGE_Q '%q' "$VLLM_SIF"
printf -v CACHE_Q '%q' "$AI_CACHE"
CMD="
module load singularity
unset LD_PRELOAD
singularity exec --nv \
--env PYTHONNOUSERSITE=1 \
--bind ${CACHE_Q}:${CACHE_Q} \
${IMAGE_Q} \
vllm serve ${MODEL_Q}"
# Append arguments with shell-safe quoting
for arg in "${args[@]}"; do
printf -v ARG_Q '%q' "$arg"
CMD+=" $ARG_Q"
done
# Replace any existing session with the same job ID
tmux kill-session -t "$SESSION" 2>/dev/null || true
# Start detached tmux session with the API key in its environment
tmux new-session -d -s "$SESSION" -e "VLLM_API_KEY=$VLLM_API_KEY"
# Execute vLLM inside tmux
tmux send-keys -t "$SESSION" "$CMD" C-m
echo "vLLM running in tmux session: $SESSION"
echo "Node: $(hostname -s)"
echo "Port: $SERVER_PORT"
echo "Metadata: $META"
# Keep SLURM allocation alive while tmux session exists
while tmux has-session -t "$SESSION" 2>/dev/null; do
sleep 30
done
echo "vLLM tmux session terminated."
Spustenie servera¶
Spustenie¶
cd ~/llm
sbatch submit_llm.sh
squeue -u "$USER"
Načítanie trvá niekoľko minút; pri prázdnej vyrovnávacej pamäti aj dlhšie. Priebeh sledujte pomocou:
tail -f ~/llm/jobs/vllm-JOBID.log
Server je pripravený, keď log zobrazí API routes a hlásenie
Application startup complete.
Overenie funkčnosti¶
Pred ladením klienta overte, či model odpovedá. Takto jednoznačne odlíšite problém servera od problému klienta.
Health check from a login node
source ~/llm/config.env
# Read the node and port the job published.
NODE=$(sed -n 's/^NODE=//p' ~/llm/run/JOBID.job)
PORT=$(sed -n 's/^SERVER_PORT=//p' ~/llm/run/JOBID.job)
ssh -N -L 9999:127.0.0.1:$PORT $NODE &
TUNNEL=$!
# 1. Is the model loaded and advertised?
curl -s -H "Authorization: Bearer $KEY" http://127.0.0.1:9999/v1/models
# 2. Does it generate?
curl -s http://127.0.0.1:9999/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H 'Content-Type: application/json' \
-d "{\"model\":\"$SERVED_MODEL\",
\"messages\":[{\"role\":\"user\",\"content\":\"Say hello.\"}]}"
kill $TUNNEL
Ak /v1/models vypíše váš model a požiadavka na vytvorenie odpovede vráti
text, server funguje správne.
Zastavenie¶
scancel JOB_ID
Porty¶
Oba porty sa prideľujú automaticky. Je vhodné rozumieť tomuto mechanizmu, pretože práve pri práci s portami vzniká väčšina problémov s pripojením.
Port servera: Skript run_vllm.sh požiada na výpočtovom uzle o port 0,
čím nechá jadro systému vybrať voľný port. Výsledok zverejní v metadátovom
súbore úlohy. Kolízie medzi úlohami sú týmto spôsobom vylúčené.
Lokálny port: Každý klient postupuje rovnako na prihlasovacom uzle, takže vedľa seba môže bežať viacero relácií.
Metadátový súbor¶
One file per job, written by the server and read by every client:
~/llm/run/123456.job
JOB_ID=123456
NODE=gn003
SERVER_PORT=18013
SERVED_MODEL=qwen3.8
MODEL_NAME=Qwen/Qwen3.8-27B-FP8
STARTED=2026-10-28T10:14:02+01:00
Súbor sa vytvorí po zistení portu a odstráni sa pri ukončení úlohy. Klienti
ho majú parsovať, nie načítavať pomocou source, a pred použitím majú overiť,
či úloha stále beží. Súbor v adresári s právom zápisu by nemal mať možnosť
spúšťať kód a úloha môže skončiť bez odstránenia svojho metadátového súboru.
Pripojenie klienta¶
Funguje každý klient kompatibilný s OpenAI API. Postup pripojenia je vždy rovnaký:
- Načítajte hodnoty
NODEaSERVER_PORTz metadátového súboru úlohy. - Overte, či úloha stále beží (
squeue -h -j JOBID). - Vyberte voľný lokálny port.
- Otvorte SSH tunel pomocou
ssh -N -L 127.0.0.1:LOCAL:127.0.0.1:SERVER_PORT NODE. - Nastavte klienta tak, aby používal
http://127.0.0.1:LOCAL/v1, hodnotu$KEYako API kľúč a$SERVED_MODELako názov modelu. - Po ukončení klienta zatvorte tunel.
Qwen Code obsahuje spúšťací skript, ktorý vykoná všetkých šesť krokov automaticky. Pri ostatných klientoch postupujte manuálne:
From a Python script or notebook
# Terminal 1 — open the tunnel.
source ~/llm/config.env
NODE=$(sed -n 's/^NODE=//p' ~/llm/run/JOBID.job)
PORT=$(sed -n 's/^SERVER_PORT=//p' ~/llm/run/JOBID.job)
ssh -N -L 9999:127.0.0.1:$PORT $NODE
# Terminal 2 — talk to it.
import os
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:9999/v1",
api_key=os.environ["KEY"],
)
reply = client.chat.completions.create(
model=os.environ["SERVED_MODEL"],
messages=[{"role": "user", "content": "Summarise this log file."}],
)
print(reply.choices[0].message.content)
Výber modelu¶
MODEL_NAME označuje model, ktorý vLLM načíta; SERVED_MODEL je názov,
ktorý API sprístupňuje a ktorý musí klient požadovať. Tieto názvy sa nemusia
zhodovať.
export MODEL_NAME="organization/model-repository"
export SERVED_MODEL="my-model"
Pred výberom modelu overte, či:
- nainštalovaná verzia vLLM podporuje jeho architektúru;
- máte prístup k váham modelu (pri niektorých modeloch je potrebné odsúhlasiť licenciu);
- sa model spolu s KV cache zmestí do pamäte GPU pri požadovanej dĺžke kontextu;
- podporuje volanie nástrojov, ak ho budú používať agenti;
- má dostupnú chat šablónu alebo ju zadáte manuálne.
Spotrebu pamäte určujú najmä váhy modelu a KV cache, ktorá rastie s hodnotou
MODEL_LENGTH. Kontext s dĺžkou 262144 tokenov je náročný na pamäť. Ak sa model
nezmestí do GPU pamäte, najskôr znížte MODEL_LENGTH a až potom upravujte
GPU_MEMORY_UTILIZATION.
Funkčnosť agentov závisí od podpory volania nástrojov
Bez tejto podpory môže model o kóde diskutovať, ale nedokáže čítať ani
upravovať súbory. Volanie nástrojov musí podporovať samotný model, chat
šablóna aj zodpovedajúci parameter --tool-call-parser vo vLLM. Ak model
poskytujete pre agentov, túto podporu vždy overte.
Prispôsobenie obrazu vLLM¶
Vo väčšine prípadov to nie je potrebné. Na zmenu modelu stačí upraviť
config.env a reštartovať server.
Vlastný obraz servera vytvorte vtedy, keď potrebujete inú verziu vLLM, ďalšie Python balíky alebo závislosti špecifické pre model. Začnite kompatibilným základným obrazom CUDA, nainštalujte verziu vLLM vhodnú pre architektúru GPU a obraz vytvorte pre aarch64 (GPU uzly Perun) alebo x86_64 (GPU uzly Devana). Váhy modelu ponechajte mimo obrazu — patria do vyrovnávacej pamäte Hugging Face, ktorá sa pripája pri spustení kontajnera. Obraz tak zostane čo najmenší.
Riešenie problémov¶
Úloha sa spustí a okamžite skončí
Podrobnosti o zlyhaní nájdete v logu:
tail -50 ~/llm/jobs/vllm-JOBID.log
Bežnými príčinami sú model nepodporovaný nainštalovanou verziou vLLM,
nesprávna hodnota MODEL_NAME alebo chýbajúce váhy, ktoré nemožno stiahnuť
pre nedostupné sieťové pripojenie.
Metadátový súbor sa nevytvorí
Úloha ešte nedospela do fázy zverejnenia portu alebo počas spúšťania skončila.
Úloha v stave PD čaká vo fronte; nejde o chybu:
squeue -u "$USER"
Model sa nezmestí do pamäte GPU
Log uvádza alokáciu pamäte, ktorá zlyhala. Odporúčaný postup je: najskôr
znížiť MODEL_LENGTH (zvyčajne dominuje spotreba KV cache), potom znížiť
GPU_MEMORY_UTILIZATION a napokon zvoliť menší alebo viac kvantizovaný model.
Zvýšenie GPU_MEMORY_UTILIZATION nad približne 0.95 často namiesto
čistého zlyhania pri štarte spôsobí pád servera počas záťaže.
Načítanie trvá dlhšie, než sa očakávalo
Načítanie veľkého modelu z prázdnej vyrovnávacej pamäte môže trvať mnoho minút. Stiahnite váhy vopred na prihlasovacom uzle a až potom spustite úlohu. Ak sa log zastaví bez chybového hlásenia, príčinou je zvyčajne nedostatok pamäte — pozrite predchádzajúcu časť.
`401 Unauthorized`
Klient odosiela nesprávny kľúč alebo neposiela žiadny. Obe strany načítavajú
~/.llm_api_key prostredníctvom config.env. Overte, či súbor existuje,
je čitateľný a či ste v testovacom shelli načítali config.env.
`Address already in use`
Pri portoch prideľovaných jadrom systému by k tejto chybe nemalo dochádzať. Ak sa objaví, iný proces pravdepodobne obsadil port v čase medzi jeho uvoľnením a použitím SSH alebo vLLM. Skúste to znova. Ak sa problém opakuje, skontrolujte, či nezostal otvorený starý tunel:
pgrep -af "ssh.*-L 127.0.0.1"
Klient prestane počas relácie fungovať
Úloha pravdepodobne dosiahla časový limit:
sacct -j JOBID --format=JobID,State,Elapsed,Timelimit
Submit a new job and reconnect.
Referenčná príručka¶
Konfiguračné premenné¶
| Variable | Purpose |
|---|---|
MODEL_NAME |
Hugging Face repository vLLM loads |
SERVED_MODEL |
Name the API advertises; clients must request it |
MODEL_LENGTH |
Maximum context in tokens; drives KV cache size |
GPU_MEMORY_UTILIZATION |
Fraction of GPU memory vLLM may use |
TENSOR_PARALLEL_SIZE |
GPUs per server; must match --gres |
REASONING_PARSER |
Model-specific; leave unset unless required |
TOOL_PARSER |
Required for agent file editing; model-specific |
AI_CACHE |
Root of all caches, including model weights |
VLLM_SIF |
Server image, aarch64 |
QWEN_SIF_X86 |
Qwen Code client image, x86_64 |
KEY |
API key, read from ~/.llm_api_key |
Príkazy¶
| Task | Command |
|---|---|
| Start a server | sbatch submit_llm.sh |
| List your jobs | squeue -u "$USER" |
| Follow a job log | tail -f ~/llm/jobs/vllm-JOBID.log |
| See where a server landed | cat ~/llm/run/JOBID.job |
| Stop a server | scancel JOBID |
| Inspect a finished job | sacct -j JOBID --format=JobID,State,Elapsed |