Pripojenie Qwen Code k vLLM na clustri Perun¶
Toto riešenie umožňuje programu Qwen Code spustenému v lokálnom kontajneri Singularity pripojiť sa k inštancii vLLM bežiacej na výpočtovom GPU uzle HPC clustra Perun.
Komunikácia medzi pracovnou stanicou a clustrom prebieha prostredníctvom
SSH tunela. API vLLM nie je vystavené do externej siete a na pridelenom
výpočtovom uzle počúva výhradne na adrese 127.0.0.1.
Architektúra¶
Pripojenie pozostáva z troch komponentov:
- Pracovná stanica: Spúšťa
qwen_remote.sha Qwen Code v lokálnom kontajneri Singularity. - Perun login node: Provides SSH access to the cluster, SLURM job discovery and an intermediate SSH tunnel.
- Výpočtový uzol: Spúšťa server vLLM v rámci SLURM alokácie.
Schéma pripojenia:
Workstation Perun login node Compute node
------------------------ ---------------- ------------
qwen_remote.sh SSH vLLM
Singularity container ---> tunnel ---> 127.0.0.1:<port>
127.0.0.1:<local_port>
SSH tunel pozostáva z dvoch stupňov presmerovania:
- Pracovná stanica presmeruje svoj lokálny port na port prihlasovacieho uzla Perun.
- Prihlasovací uzol presmeruje tento port na server vLLM na pridelenom výpočtovom uzle.
Všetky parametre pripojenia sa zisťujú počas behu. Klient automaticky identifikuje:
- bežiacu SLURM úlohu vLLM;
- pridelený výpočtový uzol;
- port servera vLLM;
- názov sprístupneného modelu;
- API kľúč.
Vďaka tomu nie je pri zmene modelu, opätovnom spustení úlohy ani pridelení iného výpočtového uzla potrebné meniť konfiguráciu pracovnej stanice.
1. Požiadavky¶
Pracovná stanica¶
Pracovná stanica potrebuje:
- SSH klienta;
- Singularity/Apptainer;
- nástroj
curl; - Bash;
- lokálny obraz Singularity s Qwen Code.
Perun¶
Cluster vyžaduje:
- SSH prístup k prihlasovaciemu uzlu Perun;
- SLURM;
- bežiacu úlohu vLLM;
- Python 3 na prihlasovacom uzle (používa sa na výber voľného sprostredkujúceho portu);
- SSH pripojenie bez zadávania hesla z prihlasovacieho uzla na výpočtové uzly.
Štruktúra súborov¶
Súbory na pracovnej stanici majú nasledujúcu štruktúru:
~/llm/
├── config.env
├── qwen_remote.sh
└── Containers/
└── Qwen_code_x86_64_Nov2026.sif
Skripty možno uložiť aj na iné miesto. Skript qwen_remote.sh predvolene
hľadá súbor config.env v rovnakom adresári.
Cestu ku konfiguračnému súboru možno zmeniť pomocou:
export QWEN_REMOTE_CONFIG=/path/to/config.env
Obraz Singularity a integrácia so SLURM¶
Dodávaný obraz Singularity je určený na spúšťanie Qwen Code na pracovnej stanici a na pripojenie k vzdialenej službe vLLM na clustri Perun.
Nemusí však obsahovať klientske nástroje SLURM ani konfiguráciu potrebnú na komunikáciu s lokálnym HPC clusterom.
Ak má Qwen Code komunikovať s lokálnym prostredím SLURM, je potrebná ďalšia konfigurácia.
Existujú dva možné postupy:
-
Pripojenie existujúcej inštalácie SLURM: Do kontajnera pripojte klientske binárne súbory SLURM, knižnice a konfiguračné súbory z hostiteľa. Kontajner musí mať kompatibilné závislosti a prístup k príslušným autentifikačným mechanizmom.
-
Vytvorenie samostatného obrazu: Vytvorte vlastný obraz Singularity, ktorý bude obsahovať potrebné klientske nástroje SLURM, knižnice a konfiguráciu.
Potrebné nastavenie závisí od lokálnej inštalácie SLURM, autentifikačného mechanizmu a konfigurácie clustra.
Pri vzdialenej práci s clustrom Perun cez uvedený SSH tunel nemusí byť SLURM nainštalovaný v kontajneri na pracovnej stanici. Príkazy SLURM sa vykonávajú vzdialene prostredníctvom SSH.
2. Konfigurácia SSH¶
Vyžaduje sa SSH prístup bez zadávania hesla, pretože spúšťací skript
pri automatizovaných operáciách SSH používa parameter BatchMode=yes.
2.1. Vytvorenie SSH kľúča¶
Na pracovnej stanici vytvorte samostatný SSH kľúč:
ssh-keygen -t ed25519 -C "user@workstation -> perun" \
-f ~/.ssh/id_ed25519_perun
Kľúč môže byť chránený heslovou frázou. V takom prípade musí pred spustením Qwen Code bežať SSH agent a kľúč musí byť doň načítaný.
Príkaz vytvorí tieto súbory:
~/.ssh/id_ed25519_perun– súkromný kľúč;~/.ssh/id_ed25519_perun.pub– verejný kľúč.
Súkromný kľúč musí zostať na pracovnej stanici.
2.2. Inštalácia verejného kľúča¶
Skopírujte verejný kľúč na prihlasovací uzol Perun:
ssh-copy-id -i ~/.ssh/id_ed25519_perun.pub \
user@<perun-login-hostname>
Prípadne ho môžete nainštalovať manuálne:
cat ~/.ssh/id_ed25519_perun.pub | \
ssh user@<perun-login-hostname> \
'mkdir -p ~/.ssh && chmod 700 ~/.ssh &&
cat >> ~/.ssh/authorized_keys &&
chmod 600 ~/.ssh/authorized_keys'
2.3. Konfigurácia SSH¶
Na pracovnej stanici pridajte do súboru ~/.ssh/config nasledujúcu položku:
Host perun
HostName <perun-login-hostname>
User <username>
IdentityFile ~/.ssh/id_ed25519_perun
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
# Reuse an existing SSH connection.
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
Nastavte príslušné oprávnenia:
chmod 600 ~/.ssh/config
Otestujte pripojenie:
ssh perun true && echo "login ok"
Príkaz sa musí dokončiť bez požiadavky na zadanie hesla.
Alias SSH (v tomto príklade perun) sa používa v súbore config.env
na pracovnej stanici.
2.4. Konfigurácia SSH z prihlasovacieho uzla na výpočtové uzly¶
Na rozdiel od priameho pripojenia SSH ProxyJump používa aktuálna implementácia prihlasovací uzol Perun na vytvorenie druhého SSH spojenia.
Prihlasovací uzol preto musí byť schopný pripojiť sa k prideleným výpočtovým uzlom bez požiadavky na zadanie hesla.
Otestujte pripojenie:
ssh perun "ssh -o BatchMode=yes <compute-node> hostname"
Ak sa príkaz úspešne dokončí, ďalšia konfigurácia nie je potrebná.
Ak sa vyžaduje heslo, nakonfigurujte na clustri Perun samostatný interný SSH kľúč.
Prihláste sa na cluster:
ssh perun
Vytvorte interný kľúč:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_internal -N ""
Nainštalujte verejný kľúč:
cat ~/.ssh/id_ed25519_internal.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Nakonfigurujte SSH tak, aby používalo interný kľúč:
cat >> ~/.ssh/config <<'EOF'
Host *
IdentityFile ~/.ssh/id_ed25519_internal
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
EOF
chmod 600 ~/.ssh/config
Z pracovnej stanice znovu otestujte pripojenie:
ssh perun "ssh -o BatchMode=yes <compute-node> hostname"
Príkaz sa musí dokončiť bez požiadavky na zadanie hesla.
Výpočtové uzly môžu opätovne generovať svoje SSH hostiteľské kľúče. Spúšťací skript preto pri internom spojení vypína overovanie hostiteľského kľúča.
3. Konfigurácia API kľúča¶
API kľúč je spoločný pre server vLLM a klienta Qwen Code.
Na clustri Perun je uložený v súbore:
~/.llm_api_key
Kľúč vytvorte jednorazovo:
openssl rand -hex 24 > ~/.llm_api_key
chmod 600 ~/.llm_api_key
Serverový skript run_vllm.sh načíta tento súbor pri spustení vLLM.
Skript qwen_remote.sh na pracovnej stanici načíta ten istý kľúč cez SSH
pri každom spustení.
Tým sa zabezpečí jediný zdroj pravdy a API kľúče sa nemusia ukladať do konfigurácie pracovnej stanice.
Voliteľne možno v config.env nastaviť lokálny súbor s kľúčom pomocou
premennej LOCAL_KEY_FILE. Ak je nastavený a čitateľný, má prednosť
pred kľúčom načítaným zo vzdialeného systému.
Dôležité: API kľúč použitý klientom sa musí zhodovať s kľúčom nastaveným pre bežiaci server vLLM. V opačnom prípade vLLM odmietne požiadavky s chybou HTTP 401.
4. Konfigurácia pracovnej stanice¶
Konfigurácia pracovnej stanice je uložená v súbore config.env.
Súbor definuje:
- parametre SSH pripojenia;
- názov SLURM úlohy;
- umiestnenie vzdialených runtime metadát;
- umiestnenie API kľúča;
- lokálny port;
- lokálny obraz Singularity;
- ďalšie pripojenia adresárov do kontajnera;
- nastavenia terminálu.
Konfigurácia nezávisí od sprístupneného modelu. Názov modelu sa načíta
z metadát vytvorených skriptom run_vllm.sh.
config.env¶
# Workstation-side configuration for connecting Qwen Code to a vLLM job
# running on Perun. Sourced by qwen_remote.sh.
# Nothing here is a secret: the API key is read from the cluster at runtime.
# ---- Cluster access ----------------------------------------------------
# LOGIN_ALIAS is the Host entry in ~/.ssh/config.
export LOGIN_ALIAS="perun"
export LOGIN_USER="username"
# Name of the SLURM job that runs vLLM.
export VLLM_JOB_NAME="vllm"
# Remote runtime metadata directory.
# Must match LLM_RUNTIME in the Perun-side config.env.
export REMOTE_RUNTIME="\$HOME/llm/run"
# Remote API key file.
export REMOTE_KEY_FILE="\$HOME/.llm_api_key"
# Optional local API key file.
# When readable, this takes precedence over the remote key.
# export LOCAL_KEY_FILE="$HOME/.llm_api_key"
# ---- Tunnel ------------------------------------------------------------
# Local port Qwen Code talks to.
# Set to 0 to let the script pick a free one.
export LOCAL_PORT="44044"
# SSH options used for the compute node.
# Compute nodes often regenerate their host keys.
export NODE_SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
# Maximum time to wait for the vLLM API endpoint.
export WAIT_TIMEOUT="60"
# ---- Model -------------------------------------------------------------
# The served model is retrieved from the job metadata.
# Set this variable only when an explicit override is required.
# export SERVED_MODEL="qwen3.8"
# ---- Local Qwen Code container -----------------------------------------
# Local Singularity image containing Qwen Code.
export QWEN_SIF_LOCAL="$HOME/llm/Containers/Qwen_code_x86_64_Nov2026.sif"
# Additional bind mounts (space-separated paths).
export QWEN_BINDS="/apps"
# ---- Terminal ----------------------------------------------------------
# Flickering bug fix.
export QWEN_CODE_FORCE_SYNCHRONIZED_OUTPUT=1
export TERM=xterm-256color
5. Spustenie servera vLLM¶
Server vLLM sa spúšťa prostredníctvom SLURM pomocou odosielacích skriptov umiestnených na clustri.
Napríklad:
ssh perun 'cd ~/llm && sbatch submit_llm.sh'
Skontrolujte stav úlohy:
ssh perun 'squeue -u username -n vllm'
Počkajte, kým úloha dosiahne stav R (RUNNING).
Prvé spustenie môže trvať dlhšie, pretože váhy modelu sa musia stiahnuť alebo načítať do pamäte GPU.
Sledujte log úlohy:
ssh perun 'tail -f ~/llm/jobs/vllm-<jobid>.log'
Runtime metadáta¶
Server zverejňuje informácie o svojom behu v súbore:
~/llm/run/<jobid>.job
Metadátový súbor vytvorí skript run_vllm.sh pri spustení servera
a odstráni ho pri ukončení úlohy.
It contains the following information:
| Premenná | Opis |
|---|---|
JOB_ID |
ID SLURM úlohy. |
NODE |
Pridelený výpočtový uzol. |
SERVER_PORT |
Port používaný vLLM. |
SERVED_MODEL |
Identifikátor modelu sprístupnený prostredníctvom API. |
MODEL_NAME |
Názov modelu používaný v logoch a pri zobrazovaní. |
STARTED |
Časová značka spustenia servera. |
Klient používa tento súbor ako jediný zdroj údajov o parametroch pripojenia.
Fronta SLURM sa používa iba na zistenie ID bežiacej úlohy.
6. Spúšťací skript na pracovnej stanici¶
Spúšťacím skriptom na pracovnej stanici je qwen_remote.sh.
Jeho hlavné úlohy sú:
- Načítať konfiguráciu pracovnej stanice.
- Identifikovať bežiacu SLURM úlohu vLLM.
- Načítať metadáta úlohy z clustra Perun.
- Načítať API kľúč.
- Vybrať voľný lokálny port.
- Prideliť sprostredkujúci port na clustri Perun.
- Vytvoriť dvojúrovňový SSH tunel.
- Počkať, kým bude API vLLM dostupné.
- Spustiť Qwen Code v lokálnom kontajneri Singularity.
Spúšťací skript nevyžaduje manuálne nastavenie výpočtového uzla, vzdialeného portu ani sprístupneného modelu.
qwen_remote.sh¶
#!/bin/bash
# Connect Qwen Code on this workstation to a vLLM job running on Perun.
#
# Usage:
# qwen_remote.sh [workspace] [--continue|--new|--resume|--list]
#
# The script finds the running vLLM job, reads the node, port and served
# model from the job's metadata file, opens an SSH tunnel to it and starts
# Qwen Code inside the local Singularity container.
#
# The tunnel is two hops in one SSH process:
#
# workstation:LOCAL_PORT -> perun:MID_PORT -> node:SERVER_PORT
#
# The second hop is made by Perun, so it uses the key stored on Perun.
# A direct 'ssh -J perun node' would instead make the node authenticate
# this workstation, which the compute nodes do not allow.
set -Eeuo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
CONFIG="${QWEN_REMOTE_CONFIG:-$SCRIPT_DIR/config.env}"
if [[ ! -r "$CONFIG" ]]; then
echo "ERROR: Cannot read configuration: $CONFIG" >&2
exit 1
fi
# Load workstation configuration.
# shellcheck disable=SC1090
source "$CONFIG"
: "${LOGIN_ALIAS:?LOGIN_ALIAS not set in $CONFIG}"
: "${QWEN_SIF_LOCAL:?QWEN_SIF_LOCAL not set in $CONFIG}"
VLLM_JOB_NAME="${VLLM_JOB_NAME:-vllm}"
REMOTE_RUNTIME="${REMOTE_RUNTIME:-\$HOME/llm/run}"
REMOTE_KEY_FILE="${REMOTE_KEY_FILE:-\$HOME/.llm_api_key}"
LOCAL_PORT="${LOCAL_PORT:-0}"
WAIT_TIMEOUT="${WAIT_TIMEOUT:-60}"
QWEN_BINDS="${QWEN_BINDS:-}"
# Compute nodes regenerate their host keys, so the login node must not
# check them against its known_hosts.
NODE_SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
usage() {
cat <<USAGE
Usage:
$0 [workspace] interactive session menu
$0 [workspace] --continue resume the most recent session
$0 [workspace] --new start a new session
$0 [workspace] --resume open the Qwen session selector
$0 [workspace] --list list sessions
USAGE
}
# ---------------------------------------------------------------------------
# Arguments: a workspace path and/or a mode flag, in any order.
# ---------------------------------------------------------------------------
WORKSPACE=""
MODE=""
for arg in "$@"; do
case "$arg" in
--continue|--new|--resume|--list)
MODE="$arg"
;;
-h|--help)
usage
exit 0
;;
-*)
echo "ERROR: Unknown option: $arg" >&2
usage >&2
exit 1
;;
*)
WORKSPACE="$arg"
;;
esac
done
WORKSPACE=${WORKSPACE:-$PWD}
if [[ ! -d "$WORKSPACE" ]]; then
echo "ERROR: Workspace does not exist: $WORKSPACE" >&2
exit 1
fi
WORKSPACE=$(cd "$WORKSPACE" && pwd)
case "$MODE" in
--continue) QWEN_ARGS=(--continue) ;;
--new) QWEN_ARGS=() ;;
--resume) QWEN_ARGS=(--resume) ;;
--list) QWEN_ARGS=(sessions list) ;;
"")
echo
echo "Qwen session:"
echo
echo " 1) Continue last session"
echo " 2) Start new session"
echo " 3) Select previous session"
echo " 4) List sessions"
echo " 5) Exit"
echo
read -r -p "Select [1]: " CHOICE
CHOICE=${CHOICE:-1}
case "$CHOICE" in
1) QWEN_ARGS=(--continue) ;;
2) QWEN_ARGS=() ;;
3) QWEN_ARGS=(--resume) ;;
4) QWEN_ARGS=(sessions list) ;;
5) exit 0 ;;
*) echo "ERROR: Invalid selection." >&2; exit 1 ;;
esac
;;
esac
# ---------------------------------------------------------------------------
# Ask Perun about the running vLLM job.
#
# run_vllm.sh writes $LLM_RUNTIME/<jobid>.job with NODE, SERVER_PORT and
# SERVED_MODEL, and removes it when the job ends. That file is the source
# of truth; squeue is only used to find the job id.
# ---------------------------------------------------------------------------
echo "Looking for a running vLLM job on $LOGIN_ALIAS ..."
REMOTE_SCRIPT=$(cat <<REMOTE
set -eu
RUNTIME="$REMOTE_RUNTIME"
KEYFILE="$REMOTE_KEY_FILE"
JOB=\$(squeue -u "\$USER" -h -n "$VLLM_JOB_NAME" -t R -o '%i' | head -1)
if [ -z "\$JOB" ]; then
echo "ERR=nojob"
exit 0
fi
META="\$RUNTIME/\$JOB.job"
if [ ! -r "\$META" ]; then
echo "ERR=nometa"
echo "JOB_ID=\$JOB"
echo "NODE=\$(squeue -j "\$JOB" -h -o '%N')"
exit 0
fi
cat "\$META"
# A free port on the login node, for the middle of the tunnel.
echo "MID_PORT=\$(python3 -c "import socket; s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()")"
if [ -r "\$KEYFILE" ]; then
echo "API_KEY=\$(cat "\$KEYFILE")"
else
echo "ERR=nokey"
fi
REMOTE
)
REMOTE_INFO=$(ssh -o BatchMode=yes "$LOGIN_ALIAS" "bash -s" <<<"$REMOTE_SCRIPT") || {
echo "ERROR: Cannot reach $LOGIN_ALIAS over SSH (passwordless login not working?)." >&2
echo " Try: ssh -v $LOGIN_ALIAS true" >&2
exit 1
}
JOB_ID=""; NODE=""; SERVER_PORT=""; MID_PORT=""; API_KEY=""; ERR=""
SERVED_MODEL=""; MODEL_NAME=""; STARTED=""
while IFS='=' read -r key value; do
case "$key" in
JOB_ID) JOB_ID="$value" ;;
NODE) NODE="$value" ;;
SERVER_PORT) SERVER_PORT="$value" ;;
MID_PORT) MID_PORT="$value" ;;
SERVED_MODEL) SERVED_MODEL="$value" ;;
MODEL_NAME) MODEL_NAME="$value" ;;
STARTED) STARTED="$value" ;;
API_KEY) API_KEY="$value" ;;
ERR) ERR="$value" ;;
esac
done <<<"$REMOTE_INFO"
case "$ERR" in
nojob)
echo "ERROR: No running '$VLLM_JOB_NAME' job found on $LOGIN_ALIAS." >&2
echo " Start one with: ssh $LOGIN_ALIAS 'cd ~/llm && sbatch submit_llm.sh'" >&2
exit 1
;;
nometa)
echo "ERROR: Job $JOB_ID runs on ${NODE:-?} but its metadata file is missing." >&2
echo " vLLM may still be starting, or run_vllm.sh failed. Check:" >&2
echo " ssh $LOGIN_ALIAS 'tail -n 40 ~/llm/jobs/vllm-$JOB_ID.log'" >&2
exit 1
;;
nokey)
echo "ERROR: The API key file does not exist on the cluster." >&2
echo " Create it with:" >&2
echo " ssh $LOGIN_ALIAS 'openssl rand -hex 24 > ~/.llm_api_key && chmod 600 ~/.llm_api_key'" >&2
echo " then restart the vLLM job so it picks the key up." >&2
exit 1
;;
esac
: "${NODE:?Could not determine the compute node}"
: "${SERVER_PORT:?Could not determine the vLLM port}"
: "${MID_PORT:?Could not get a free port on $LOGIN_ALIAS}"
: "${API_KEY:?Could not determine the API key}"
: "${SERVED_MODEL:?Could not determine the served model name}"
echo "Job $JOB_ID on $NODE, port $SERVER_PORT, model $SERVED_MODEL (${MODEL_NAME:-?})"
# ---------------------------------------------------------------------------
# Pick a free local port.
# ---------------------------------------------------------------------------
port_free() {
! (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null
}
if [[ "$LOCAL_PORT" == "0" ]] || ! port_free "$LOCAL_PORT"; then
if [[ "$LOCAL_PORT" != "0" ]]; then
echo "Local port $LOCAL_PORT is busy, picking another one."
fi
for candidate in $(seq 44044 44144); do
if port_free "$candidate"; then
LOCAL_PORT="$candidate"
break
fi
done
fi
echo "Local endpoint: http://127.0.0.1:${LOCAL_PORT}/v1"
# ---------------------------------------------------------------------------
# Open the tunnel.
#
# One local SSH process does both hops: it forwards
# 127.0.0.1:LOCAL_PORT -> perun:MID_PORT, and the command it runs on Perun
# forwards perun:MID_PORT -> node:SERVER_PORT. -tt gives that inner ssh a
# pty, so it dies with this process instead of lingering on the login node.
# ---------------------------------------------------------------------------
TUNNEL_PID=""
TUNNEL_LOG=$(mktemp "${TMPDIR:-/tmp}/qwen-tunnel.XXXXXX")
cleanup() {
if [[ -n "$TUNNEL_PID" ]]; then
kill "$TUNNEL_PID" 2>/dev/null || true
fi
# Make sure no forwarding process is left holding MID_PORT.
ssh -o BatchMode=yes -o ConnectTimeout=5 "$LOGIN_ALIAS" \
"pkill -f 'ssh -N .*-L 127.0.0.1:${MID_PORT}:' || true" \
>/dev/null 2>&1 || true
rm -f "$TUNNEL_LOG"
}
trap cleanup EXIT
echo "Opening ${LOGIN_ALIAS} -> ${NODE} tunnel ..."
ssh -tt \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 \
-L "127.0.0.1:${LOCAL_PORT}:127.0.0.1:${MID_PORT}" \
"$LOGIN_ALIAS" \
"ssh -N $NODE_SSH_OPTS -o ExitOnForwardFailure=yes -L 127.0.0.1:${MID_PORT}:127.0.0.1:${SERVER_PORT} ${NODE}" \
>>"$TUNNEL_LOG" 2>&1 </dev/null &
TUNNEL_PID=$!
# ---------------------------------------------------------------------------
# Wait until vLLM answers through the tunnel.
# ---------------------------------------------------------------------------
printf 'Waiting for vLLM'
ready=0
for (( i = 0; i < WAIT_TIMEOUT; i++ )); do
if ! kill -0 "$TUNNEL_PID" 2>/dev/null; then
echo
echo "ERROR: The SSH tunnel died:" >&2
sed 's/^/ | /' "$TUNNEL_LOG" >&2
echo " Check that Perun can reach the node without a password:" >&2
echo " ssh $LOGIN_ALIAS \"ssh -o BatchMode=yes $NODE hostname\"" >&2
exit 1
fi
if curl -fsS -m 3 -o /dev/null \
-H "Authorization: Bearer ${API_KEY}" \
"http://127.0.0.1:${LOCAL_PORT}/v1/models" 2>/dev/null; then
ready=1
break
fi
printf '.'
sleep 1
done
echo
if (( ready == 0 )); then
echo "ERROR: vLLM did not answer within ${WAIT_TIMEOUT}s." >&2
echo " The model may still be loading. Watch the job log:" >&2
echo " ssh $LOGIN_ALIAS 'tail -f ~/llm/jobs/vllm-${JOB_ID}.log'" >&2
if [[ -s "$TUNNEL_LOG" ]]; then
echo " Tunnel output:" >&2
sed 's/^/ | /' "$TUNNEL_LOG" >&2
fi
exit 1
fi
echo "vLLM is reachable."
# ---------------------------------------------------------------------------
# Launch Qwen Code in the local Singularity container.
# ---------------------------------------------------------------------------
BIND_ARGS=()
for b in $QWEN_BINDS; do
BIND_ARGS+=(--bind "$b")
done
echo "Launching Qwen in $WORKSPACE ..."
singularity exec \
"${BIND_ARGS[@]}" \
--env OPENAI_BASE_URL="http://127.0.0.1:${LOCAL_PORT}/v1" \
--env OPENAI_API_KEY="${API_KEY}" \
--env OPENAI_MODEL="${SERVED_MODEL}" \
--env QWEN_CODE_FORCE_SYNCHRONIZED_OUTPUT="${QWEN_CODE_FORCE_SYNCHRONIZED_OUTPUT:-1}" \
--env TERM="${TERM:-xterm-256color}" \
"${QWEN_SIF_LOCAL}" \
bash -lc "cd '$WORKSPACE' && qwen ${QWEN_ARGS[*]}"
7. Spustenie Qwen Code¶
Po konfigurácii SSH a príprave servera vLLM nastavte spúšťaciemu skriptu právo na spustenie:
chmod +x ~/llm/qwen_remote.sh
Spustite interaktívnu reláciu:
~/llm/qwen_remote.sh
Spúšťací skript zobrazí menu relácií:
Qwen session:
1) Continue last session
2) Start new session
3) Select previous session
4) List sessions
5) Exit
Režim relácie môžete zadať aj priamo.
| Príkaz | Opis |
|---|---|
qwen_remote.sh |
Interaktívne menu relácií. |
qwen_remote.sh . --continue |
Pokračovať v najnovšej relácii v aktuálnom adresári. |
qwen_remote.sh . --new |
Spustiť novú reláciu. |
qwen_remote.sh ~/projects/foo --new |
Spustiť novú reláciu v zadanom pracovnom adresári. |
qwen_remote.sh . --resume |
Otvoriť výber relácie. |
qwen_remote.sh . --list |
Zobraziť dostupné relácie. |
Predvoleným pracovným adresárom je aktuálny adresár.
8. Správa pripojenia počas behu¶
Pripojenie využíva niekoľko mechanizmov, vďaka ktorým nezávisí od konkrétnej SLURM alokácie.
Dynamický vzdialený port¶
Server vLLM si pri spustení vyberie voľný port.
Port sa uloží do metadátového súboru úlohy a spúšťací skript na pracovnej stanici ho následne načíta.
Nie je potrebné konfigurovať pevný vzdialený port.
Dynamický sprostredkujúci port¶
Spúšťací skript požiada prihlasovací uzol Perun, aby vybral voľný port pre sprostredkujúcu fázu SSH presmerovania.
Port sa vyberá dynamicky pomocou Pythonu a neukladá sa do konfigurácie pracovnej stanice.
Dynamický lokálny port¶
Pracovná stanica používa premennú LOCAL_PORT zo súboru config.env.
Ak je nakonfigurovaný port obsadený, spúšťací skript hľadá iný voľný port v rozsahu:
44044-44144
Nastavením LOCAL_PORT="0" sa zapne automatický výber portu.
Kontrola dostupnosti API¶
Spúšťací skript nepredpokladá, že vLLM bude pripravené okamžite po vytvorení SSH tunela.
Namiesto toho v pravidelných intervaloch odosiela požiadavku na:
http://127.0.0.1:<local_port>/v1/models
Požiadavka obsahuje API kľúč.
Qwen Code sa spustí až po úspešnej odpovedi endpointu.
Maximálny čas čakania určuje premenná WAIT_TIMEOUT.
Ukončenie tunela¶
SSH tunel sa ukončí po skončení Qwen Code.
Spúšťací skript sa zároveň pokúsi ukončiť sprostredkujúci proces SSH na clustri Perun.
Tým sa zabráni tomu, aby opustené tunely zbytočne obsadzovali porty.
9. Riešenie problémov¶
Perun nie je dostupný cez SSH
Spúšťací skript používa BatchMode=yes, ktorý zabraňuje interaktívnym
výzvam na zadanie hesla.
Skontrolujte SSH pripojenie:
ssh -v perun true
Overte SSH kľúč, oprávnenia a konfiguráciu opísanú v časti 2.
Nenašla sa žiadna bežiaca úloha vLLM
Skontrolujte frontu SLURM:
ssh perun 'squeue -u username'
Úloha môže stále čakať vo fronte (PD), mohla sa ukončiť alebo dosiahla
časový limit.
Metadátový súbor úlohy chýba¶
Skontrolujte, či metadátový súbor existuje:
ssh perun 'ls -l ~/llm/run/'
Ak úloha beží, ale metadátový súbor chýba, vLLM sa ešte môže spúšťať
alebo skript run_vllm.sh zlyhal.
Skontrolujte log úlohy:
ssh perun 'tail -n 50 ~/llm/jobs/vllm-<jobid>.log'
Výpočtový uzol požaduje heslo
Aktuálna implementácia vyžaduje SSH pripojenie bez hesla z prihlasovacieho uzla Perun na pridelený výpočtový uzol.
Otestujte pripojenie:
ssh perun "ssh -o BatchMode=yes <compute-node> hostname"
Ak príkaz zlyhá, nakonfigurujte interný SSH kľúč podľa časti 2.4.
SSH tunel sa neočakávane ukončil
Po ukončení spojenia spúšťací skript vypíše výstup SSH tunela.
Skontrolujte uvedenú chybu SSH.
Medzi bežné príčiny patria:
- zlyhanie SSH autentifikácie bez hesla;
- nedostupný výpočtový uzol;
- nesprávne presmerovanie sprostredkujúceho portu;
- ukončený server vLLM;
- časový limit SSH spojenia.
vLLM neodpovedalo v nastavenom časovom limite
Model sa ešte môže načítavať do pamäte GPU.
Skontrolujte log úlohy:
ssh perun 'tail -f ~/llm/jobs/vllm-<jobid>.log'
V logu vyhľadajte hlásenie:
Application startup complete
Ak inicializácia modelu vyžaduje viac času, zvýšte hodnotu:
WAIT_TIMEOUT="120"
v súbore config.env.
Odpoveď HTTP 401 znamená, že sa API kľúče nezhodujú. Overte, či
~/.llm_api_key na clustri Perun obsahuje kľúč použitý bežiacim
serverom vLLM.
Po zmene kľúča reštartujte úlohu vLLM.
Manuálne otestovanie API endpointu
Po vytvorení SSH tunela otestujte lokálny endpoint:
curl -s \
-H "Authorization: Bearer $(ssh perun cat ~/.llm_api_key)" \
http://127.0.0.1:44044/v1/models
Hodnotu 44044 nahraďte skutočným lokálnym portom vybraným spúšťacím skriptom.
10. Zhrnutie¶
Celý postup pozostáva z nasledujúcich krokov:
- Nakonfigurujte SSH prístup bez hesla z pracovnej stanice na Perun.
- Nakonfigurujte SSH prístup bez hesla z Perun na výpočtové uzly.
- Vytvorte API kľúč na clustri Perun.
- Nakonfigurujte súbor
config.envna pracovnej stanici. - Odošlite SLURM úlohu vLLM na clustri Perun.
- Počkajte, kým sa úloha spustí a vLLM zverejní svoje metadáta.
- Spustite
qwen_remote.shna pracovnej stanici. - Spúšťací skript nájde bežiacu úlohu a načíta jej metadáta.
- Spúšťací skript načíta API kľúč a vytvorí SSH tunel.
- Lokálny API endpoint sa kontroluje, kým vLLM nezačne odpovedať.
- Qwen Code sa spustí v lokálnom kontajneri Singularity.
- Po ukončení Qwen Code sa SSH tunel ukončí.
Konfigurácia pracovnej stanice zostáva rovnaká pri zmene úloh, pridelených výpočtových uzlov aj modelov.
Pri zmene modelu alebo konfigurácie servera je potrebné upraviť iba konfiguráciu vLLM a parametre odosielania úlohy na clustri.