Eigenes lokales KI-System

vLLM installieren

Installation

  1. GPU prüfen

    Der vorhandene NVIDIA-Treiber muss die GPU erkennen.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Zeigt Grafikkarte, Treiberversion und GPU-Speicherbelegung.
    nvidia-smi
    WSW_SCRIPT
  2. Werkzeuge bereitstellen

    Downloadwerkzeug und TLS-Zertifikate installieren.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Aktualisiert die Paketlisten; installiert noch keine Updates.
    apt-get update
    # Installiert curl und vertrauenswürdige TLS-Zertifikate ohne Rückfragen.
    apt-get install -y curl ca-certificates
    WSW_SCRIPT
  3. Python und vLLM installieren

    Für den vorhandenen Treiber 550: vLLM 0.8.5, CUDA 12.4, Transformers 4.51.3 und Python 3.12.14. Eigene Umgebung unter /opt/wsw-vllm.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Erkennt eine vorhandene oder unvollständig entfernte Installation.
    if [ -e /opt/wsw-vllm ] || [ -L /opt/wsw-vllm ] || [ -e /usr/local/bin/vllm ] || [ -L /usr/local/bin/vllm ] || [ -e /etc/systemd/system/wsw-vllm.service ] || getent passwd wsw-vllm >/dev/null || getent group wsw-vllm >/dev/null; then
      # Nennt den erforderlichen Schritt, statt nur Fehler 1 auszugeben.
      printf 'vLLM ist noch vorhanden. Zuerst Deinstallation gesamt ausführen.\n' >&2
      # Beendet diesen Installationsblock ohne vorhandene Dateien zu überschreiben.
      exit 1
    fi
    # Legt das Installationsverzeichnis an.
    install -d /opt/wsw-vllm
    # Lädt den Installer für die geprüfte uv-Version.
    curl -fL --retry 3 https://astral.sh/uv/0.12.17/install.sh -o /opt/wsw-vllm/install-uv.sh
    # Installiert uv nur in diesen Ordner; verändert keine Shell-Profile.
    UV_UNMANAGED_INSTALL=/opt/wsw-vllm/tools sh /opt/wsw-vllm/install-uv.sh
    # Lädt Python und erstellt eine eigene Umgebung; Downloadcache bleibt unter /opt.
    UV_CACHE_DIR=/opt/wsw-vllm/cache/uv UV_PYTHON_INSTALL_DIR=/opt/wsw-vllm/python /opt/wsw-vllm/tools/uv venv --python 3.12.14 --managed-python /opt/wsw-vllm/venv
    # Installiert vLLM und Transformers mit dem zugehörigen PyTorch für CUDA 12.4.
    UV_CACHE_DIR=/opt/wsw-vllm/cache/uv /opt/wsw-vllm/tools/uv pip install --python /opt/wsw-vllm/venv/bin/python 'vllm==0.8.5' 'transformers==4.51.3' --torch-backend=cu124
    WSW_SCRIPT
  4. Pakete und CUDA prüfen

    Paketabhängigkeiten prüfen und eine kleine Berechnung auf der GPU ausführen.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft, ob die installierten Paketabhängigkeiten zusammenpassen.
    UV_CACHE_DIR=/opt/wsw-vllm/cache/uv /opt/wsw-vllm/tools/uv pip check --python /opt/wsw-vllm/venv/bin/python
    # Prüft CUDA mit einer echten GPU-Berechnung und zeigt die installierten Versionen.
    CUDA_CACHE_PATH=/opt/wsw-vllm/cache/cuda /opt/wsw-vllm/venv/bin/python -c 'import torch; from importlib.metadata import version; assert torch.cuda.is_available(), "CUDA nicht verfügbar"; assert torch.ones(1, device="cuda").sum().item() == 1.0; print("vLLM", version("vllm"), "Transformers", version("transformers"), "PyTorch", torch.__version__, "CUDA", torch.version.cuda, torch.cuda.get_device_name(0))'
    WSW_SCRIPT
  5. GPU für vLLM freigeben

    Stoppt wsw-ollama und deaktiviert dessen Autostart. Für den Vergleich läuft jeweils ein Modell auf der GPU.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft, ob der andere Workshop-Dienst installiert ist.
    if systemctl cat wsw-ollama.service >/dev/null 2>&1; then
      # Stoppt den Dienst samt laufenden Anfragen und deaktiviert seinen Autostart.
      systemctl disable --now wsw-ollama
    fi
    WSW_SCRIPT
  6. Modell-Dienst starten und auf Bereitschaft warten

    Qwen3-4B in FP16, 4.096 Token Kontext, eine Anfrage und 65 % GPU-Speicherbudget. Der erste Start lädt das Modell und kompiliert GPU-Code; das kann mehrere Minuten dauern. Dieser Block wartet auf die betriebsbereite API.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Legt Dienstbenutzer und Gruppe an; erlaubt keine interaktive Anmeldung.
    useradd --system --user-group --home-dir /opt/wsw-vllm --shell /usr/sbin/nologin wsw-vllm
    # Legt den Cacheordner mit passenden Eigentümern an.
    install -d -o wsw-vllm -g wsw-vllm /opt/wsw-vllm/cache /opt/wsw-vllm/cache/tmp
    # Übergibt auch bereits vorhandene Downloadcaches an das Dienstkonto.
    chown -R wsw-vllm:wsw-vllm /opt/wsw-vllm/cache
    # Schreibt die Dienstkonfiguration; der Block endet bei UNIT.
    cat > /etc/systemd/system/wsw-vllm.service <<'UNIT'
    [Unit]
    # Anzeigename des Dienstes.
    Description=WSW vLLM Qwen3-4B FP16
    # Ordnet den Start nach dem Netzwerk-Ziel ein.
    After=network-online.target
    
    [Service]
    # Führt vLLM unter dem eigenen Benutzer und seiner Gruppe aus.
    User=wsw-vllm
    Group=wsw-vllm
    # Hält Benutzerdaten innerhalb der Installation.
    Environment=HOME=/opt/wsw-vllm/cache
    # Deaktiviert die vLLM-Nutzungsstatistik.
    Environment=VLLM_NO_USAGE_STATS=1
    # Deaktiviert Telemetrie der Hugging-Face-Bibliotheken.
    Environment=HF_HUB_DISABLE_TELEMETRY=1
    # Speichert Modelle und Hugging-Face-Zustand im Installationsverzeichnis.
    Environment=HF_HOME=/opt/wsw-vllm/cache/huggingface
    # Hält allgemeine Anwendungscaches innerhalb der Installation.
    Environment=XDG_CACHE_HOME=/opt/wsw-vllm/cache
    # Hält temporäre Dateien und Modell-Downloadlocks innerhalb der Installation.
    Environment=TMPDIR=/opt/wsw-vllm/cache/tmp
    # Legt den vLLM-Cache fest.
    Environment=VLLM_CACHE_ROOT=/opt/wsw-vllm/cache/vllm
    # Hält von PyTorch kompilierte Programme innerhalb der Installation.
    Environment=TORCHINDUCTOR_CACHE_DIR=/opt/wsw-vllm/cache/torchinductor
    # Legt den Cache für kompilierte Triton-Kernel fest.
    Environment=TRITON_CACHE_DIR=/opt/wsw-vllm/cache/triton
    # Legt den Cache des CUDA-Treibers fest.
    Environment=CUDA_CACHE_PATH=/opt/wsw-vllm/cache/cuda
    # Startet Qwen3-4B in FP16 (half) mit festem Modellstand und 4.096 Token Kontext.
    # Eine gleichzeitige Anfrage; 65 % GPU-Speicherbudget für Gewichte und Laufzeit.
    # Prefix-Caching spart wiederholte Eingabeverarbeitung; CUDA-Graphen bleiben aktiv.
    # disable-log-requests verhindert das Protokollieren der Anfragetexte.
    ExecStart=/opt/wsw-vllm/venv/bin/vllm serve Qwen/Qwen3-4B --revision 1cfa9a7208912126459214e8b04321603b3df60c --host 127.0.0.1 --port 8011 --dtype half --max-model-len 4096 --max-num-seqs 1 --gpu-memory-utilization 0.65 --enable-prefix-caching --disable-log-requests
    # Startet den Dienst nach einem Fehler erneut.
    Restart=on-failure
    # Wartet vor einem erneuten Start fünf Sekunden.
    RestartSec=5
    
    [Install]
    # Ordnet den Dienst dem normalen Systemstart zu.
    WantedBy=multi-user.target
    UNIT
    # Liest die neue Dienstkonfiguration ein.
    systemctl daemon-reload
    # Aktiviert den Autostart und startet den Modelldienst sofort.
    systemctl enable --now wsw-vllm
    # Merkt sich den Beginn der Bereitschaftsprüfung.
    wsw_vllm_start=$SECONDS
    # Prüft alle zehn Sekunden die API; ein gestarteter Prozess allein reicht nicht.
    until curl -fsS --max-time 2 http://127.0.0.1:8011/health >/dev/null 2>&1; do
      # Bricht bei gestopptem Dienst, einem Neustart nach Fehler oder nach 30 Minuten ab.
      if ! systemctl is-active --quiet wsw-vllm || [ "$(systemctl show -p NRestarts --value wsw-vllm)" != 0 ] || [ "$((SECONDS - wsw_vllm_start))" -ge 1800 ]; then
        # Meldet den fehlgeschlagenen oder zu langen Start.
        printf 'vLLM ist nicht bereit. Start abgebrochen; Dienstprotokoll:\n' >&2
        # Zeigt die Fehlermeldungen des Modelldienstes.
        journalctl -u wsw-vllm -n 50 --no-pager
        # Beendet nur diesen Installationsblock mit Fehler.
        exit 1
      fi
      # Zeigt, dass Modelldownload oder GPU-Start noch laufen.
      printf 'Warte auf vLLM: Modell laden / GPU vorbereiten (%s Sekunden).\n' "$((SECONDS - wsw_vllm_start))"
      # Wartet zehn Sekunden vor der nächsten Bereitschaftsprüfung.
      sleep 10
    done
    # Bestätigt die betriebsbereite API.
    printf 'vLLM-API ist bereit.\n'
    WSW_SCRIPT
  7. Antwort prüfen

    Dieselbe Testfrage wie bei Ollama: ohne Thinking, Temperatur 0, höchstens 64 Antworttoken.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Zeigt die installierte vLLM-Version.
    /opt/wsw-vllm/venv/bin/vllm --version
    # Zeigt das von der lokalen API bereitgestellte Modell und seine Kontextgrenze.
    curl -fsS --max-time 10 http://127.0.0.1:8011/v1/models
    # Sendet die Testfrage mit denselben Antwortvorgaben wie bei Ollama.
    curl -fsS --max-time 120 http://127.0.0.1:8011/v1/chat/completions \
      -H 'Content-Type: application/json' \
      -d '{"model":"Qwen/Qwen3-4B","messages":[{"role":"user","content":"Antworte nur: Bereit."}],"stream":false,"max_tokens":64,"temperature":0,"repetition_penalty":1,"chat_template_kwargs":{"enable_thinking":false}}'
    # Zeigt die GPU-Belegung nach dem Modellstart.
    nvidia-smi
    WSW_SCRIPT
  8. Befehl vllm einrichten und prüfen

    Erst nach erfolgreichem Modellstart: richtet vllm ein und prüft den Chat-Verbindungsaufbau. Danach startet vllm chat den interaktiven Chat auf Port 8011.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft, dass kein vorhandener vllm-Befehl überschrieben wird.
    test ! -e /usr/local/bin/vllm
    # Prüft zusätzlich auf einen symbolischen Link mit fehlendem Ziel.
    test ! -L /usr/local/bin/vllm
    # Erstellt den direkt aufrufbaren Befehl; der Block endet bei CLI.
    cat > /usr/local/bin/vllm <<'CLI'
    #!/bin/sh
    # Deaktiviert Nutzungsstatistik auch bei direkten CLI-Aufrufen.
    export VLLM_NO_USAGE_STATS=1
    # Deaktiviert Hugging-Face-Telemetrie für direkte CLI-Aufrufe.
    export HF_HUB_DISABLE_TELEMETRY=1
    # Ergänzt für Chat und Completion den Port unseres Dienstes.
    case "${1-}" in
      chat|complete)
        # Merkt sich den Unterbefehl und entfernt ihn vorübergehend aus der Argumentliste.
        unterbefehl=$1
        shift
        # Prüft nur den lokalen Standarddienst; ein ausdrücklich anderes Ziel bleibt erlaubt.
        wsw_vllm_lokal=1
        # Sucht nach einem expliziten --url oder --url=Ziel.
        for wsw_vllm_arg in "$@"; do
          # Überspringt die lokale Prüfung, wenn ein anderes Ziel angegeben wurde.
          case "$wsw_vllm_arg" in --url|--url=*) wsw_vllm_lokal=0 ;; esac
        done
        # Verhindert den Python-Verbindungsfehler, solange die lokale API nicht bereit ist.
        if [ "$wsw_vllm_lokal" = 1 ] && ! curl -fsS --max-time 2 http://127.0.0.1:8011/health >/dev/null 2>&1; then
          # Nennt das Problem und den Befehl für das Startprotokoll.
          printf 'vLLM ist noch nicht bereit oder gestoppt. Prüfen: journalctl -u wsw-vllm -n 30 --no-pager\n' >&2
          # Beendet den Client mit Fehler, ohne eine Anfrage an den Chat zu senden.
          exit 1
        fi
        # Setzt den lokalen Standard; ein späteres --url überschreibt ihn ausdrücklich.
        set -- "$unterbefehl" --url http://127.0.0.1:8011/v1 "$@"
        ;;
    esac
    # Startet die installierte CLI mit den vorbereiteten Argumenten.
    exec /opt/wsw-vllm/venv/bin/vllm "$@"
    CLI
    # Macht den Befehl für alle Benutzer ausführbar; nur root darf ihn ändern.
    chmod 755 /usr/local/bin/vllm
    # Prüft den echten Chat-Client; EOF beendet ihn ohne interaktive Eingabe.
    vllm chat </dev/null
    # Bestätigt den abgeschlossenen Ablauf und nennt den Chat-Aufruf.
    printf 'Installation abgeschlossen. Chat starten: vllm chat\n'
    WSW_SCRIPT
Installation gesamt
bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
nvidia-smi

apt-get update
apt-get install -y curl ca-certificates

if [ -e /opt/wsw-vllm ] || [ -L /opt/wsw-vllm ] || [ -e /usr/local/bin/vllm ] || [ -L /usr/local/bin/vllm ] || [ -e /etc/systemd/system/wsw-vllm.service ] || getent passwd wsw-vllm >/dev/null || getent group wsw-vllm >/dev/null; then
  printf 'vLLM ist noch vorhanden. Zuerst Deinstallation gesamt ausführen.\n' >&2
  exit 1
fi
install -d /opt/wsw-vllm
curl -fL --retry 3 https://astral.sh/uv/0.12.17/install.sh -o /opt/wsw-vllm/install-uv.sh
UV_UNMANAGED_INSTALL=/opt/wsw-vllm/tools sh /opt/wsw-vllm/install-uv.sh
UV_CACHE_DIR=/opt/wsw-vllm/cache/uv UV_PYTHON_INSTALL_DIR=/opt/wsw-vllm/python /opt/wsw-vllm/tools/uv venv --python 3.12.14 --managed-python /opt/wsw-vllm/venv
UV_CACHE_DIR=/opt/wsw-vllm/cache/uv /opt/wsw-vllm/tools/uv pip install --python /opt/wsw-vllm/venv/bin/python 'vllm==0.8.5' 'transformers==4.51.3' --torch-backend=cu124

UV_CACHE_DIR=/opt/wsw-vllm/cache/uv /opt/wsw-vllm/tools/uv pip check --python /opt/wsw-vllm/venv/bin/python
CUDA_CACHE_PATH=/opt/wsw-vllm/cache/cuda /opt/wsw-vllm/venv/bin/python -c 'import torch; from importlib.metadata import version; assert torch.cuda.is_available(), "CUDA nicht verfügbar"; assert torch.ones(1, device="cuda").sum().item() == 1.0; print("vLLM", version("vllm"), "Transformers", version("transformers"), "PyTorch", torch.__version__, "CUDA", torch.version.cuda, torch.cuda.get_device_name(0))'

if systemctl cat wsw-ollama.service >/dev/null 2>&1; then
  systemctl disable --now wsw-ollama
fi

useradd --system --user-group --home-dir /opt/wsw-vllm --shell /usr/sbin/nologin wsw-vllm
install -d -o wsw-vllm -g wsw-vllm /opt/wsw-vllm/cache /opt/wsw-vllm/cache/tmp
chown -R wsw-vllm:wsw-vllm /opt/wsw-vllm/cache
cat > /etc/systemd/system/wsw-vllm.service <<'UNIT'
[Unit]
Description=WSW vLLM Qwen3-4B FP16
After=network-online.target

[Service]
User=wsw-vllm
Group=wsw-vllm
Environment=HOME=/opt/wsw-vllm/cache
Environment=VLLM_NO_USAGE_STATS=1
Environment=HF_HUB_DISABLE_TELEMETRY=1
Environment=HF_HOME=/opt/wsw-vllm/cache/huggingface
Environment=XDG_CACHE_HOME=/opt/wsw-vllm/cache
Environment=TMPDIR=/opt/wsw-vllm/cache/tmp
Environment=VLLM_CACHE_ROOT=/opt/wsw-vllm/cache/vllm
Environment=TORCHINDUCTOR_CACHE_DIR=/opt/wsw-vllm/cache/torchinductor
Environment=TRITON_CACHE_DIR=/opt/wsw-vllm/cache/triton
Environment=CUDA_CACHE_PATH=/opt/wsw-vllm/cache/cuda
ExecStart=/opt/wsw-vllm/venv/bin/vllm serve Qwen/Qwen3-4B --revision 1cfa9a7208912126459214e8b04321603b3df60c --host 127.0.0.1 --port 8011 --dtype half --max-model-len 4096 --max-num-seqs 1 --gpu-memory-utilization 0.65 --enable-prefix-caching --disable-log-requests
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now wsw-vllm
wsw_vllm_start=$SECONDS
until curl -fsS --max-time 2 http://127.0.0.1:8011/health >/dev/null 2>&1; do
  if ! systemctl is-active --quiet wsw-vllm || [ "$(systemctl show -p NRestarts --value wsw-vllm)" != 0 ] || [ "$((SECONDS - wsw_vllm_start))" -ge 1800 ]; then
    printf 'vLLM ist nicht bereit. Start abgebrochen; Dienstprotokoll:\n' >&2
    journalctl -u wsw-vllm -n 50 --no-pager
    exit 1
  fi
  printf 'Warte auf vLLM: Modell laden / GPU vorbereiten (%s Sekunden).\n' "$((SECONDS - wsw_vllm_start))"
  sleep 10
done
printf 'vLLM-API ist bereit.\n'

/opt/wsw-vllm/venv/bin/vllm --version
curl -fsS --max-time 10 http://127.0.0.1:8011/v1/models
curl -fsS --max-time 120 http://127.0.0.1:8011/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"Qwen/Qwen3-4B","messages":[{"role":"user","content":"Antworte nur: Bereit."}],"stream":false,"max_tokens":64,"temperature":0,"repetition_penalty":1,"chat_template_kwargs":{"enable_thinking":false}}'
nvidia-smi

test ! -e /usr/local/bin/vllm
test ! -L /usr/local/bin/vllm
cat > /usr/local/bin/vllm <<'CLI'
#!/bin/sh
export VLLM_NO_USAGE_STATS=1
export HF_HUB_DISABLE_TELEMETRY=1
case "${1-}" in
  chat|complete)
    unterbefehl=$1
    shift
    wsw_vllm_lokal=1
    for wsw_vllm_arg in "$@"; do
      case "$wsw_vllm_arg" in --url|--url=*) wsw_vllm_lokal=0 ;; esac
    done
    if [ "$wsw_vllm_lokal" = 1 ] && ! curl -fsS --max-time 2 http://127.0.0.1:8011/health >/dev/null 2>&1; then
      printf 'vLLM ist noch nicht bereit oder gestoppt. Prüfen: journalctl -u wsw-vllm -n 30 --no-pager\n' >&2
      exit 1
    fi
    set -- "$unterbefehl" --url http://127.0.0.1:8011/v1 "$@"
    ;;
esac
exec /opt/wsw-vllm/venv/bin/vllm "$@"
CLI
chmod 755 /usr/local/bin/vllm
vllm chat </dev/null
printf 'Installation abgeschlossen. Chat starten: vllm chat\n'
WSW_SCRIPT
Leistung einstellen
  1. Prefix-Caching gezielt nutzen

    Bereits aktiviert: Gleiche Promptanfänge können erneut verwendet werden. Stabile Regeln und Dokumentabschnitte vor die wechselnde Frage stellen. Das spart Eingabeverarbeitung; die Erzeugung neuer Antworttoken wird dadurch nicht schneller.

  2. Kontext, Parallelität und Speicher abstimmen

    Für den Vergleich mit Ollama: 4.096 Token und eine gleichzeitige Anfrage. Das 65-%-GPU-Budget umfasst Gewichte und Laufzeit. Für einen Parallelitätstest dieselbe Anzahl Anfragen auf beiden Systemen verwenden und nur ein System auf der GPU betreiben.

  3. Anfragen kurz halten

    Die Testanfrage deaktiviert Thinking und begrenzt die Antwort auf 64 Token. Kontext, Prompt und Antwortlimit für beide Systeme gleich wählen. Ein zu kleines Limit kann die Antwort abschneiden.

  4. Betrieb beobachten

    Metriken zeigen unter anderem Warteschlange, Verarbeitung und Cache-Nutzung. Wartezeit und Zeit bis zur vollständigen Antwort getrennt beurteilen.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Liest die lokal bereitgestellten Prometheus-Metriken.
    curl -fsS --max-time 10 http://127.0.0.1:8011/metrics
    WSW_SCRIPT
  5. Zu Ollama wechseln

    Wechselt zum bereits installierten wsw-ollama; nur dieser Dienst erhält Autostart.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Stoppt den aktuellen Dienst samt laufenden Anfragen und deaktiviert seinen Autostart.
    systemctl disable --now wsw-vllm
    # Aktiviert den Autostart des anderen Dienstes und startet ihn.
    systemctl enable --now wsw-ollama
    WSW_SCRIPT

Deinstallation

  1. Dienst entfernen

    vLLM stoppen und den automatischen Start entfernen.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft, ob eine Dienstkonfiguration vorhanden ist.
    if systemctl cat wsw-vllm.service >/dev/null 2>&1; then
      # Stoppt vLLM und deaktiviert seinen Autostart.
      systemctl disable --now wsw-vllm
    fi
    # Prüft, ob systemd tatsächlich einen Fehlerstatus gespeichert hat.
    if systemctl is-failed --quiet wsw-vllm; then
      # Entfernt nur diesen vorhandenen Fehlerstatus; entladene Dienste brauchen das nicht.
      systemctl reset-failed wsw-vllm
    fi
    # Löscht die Dienstkonfiguration, sofern sie vorhanden ist.
    rm -f -- /etc/systemd/system/wsw-vllm.service
    # Entfernt gegebenenfalls ergänzte Diensteinstellungen dieser Installation.
    rm -rf -- /etc/systemd/system/wsw-vllm.service.d
    # Liest die verbleibenden Dienstkonfigurationen neu ein.
    systemctl daemon-reload
    WSW_SCRIPT
  2. CLI, Umgebung und Modelle löschen

    Entfernt vllm, Python, uv, Qwen3-4B und weitere Modelle im Installationsordner, konfigurierte Caches und den früheren PyTorch-Cache unter /tmp.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Entfernt den direkt aufrufbaren vllm-Befehl.
    rm -f -- /usr/local/bin/vllm
    # Löscht die gesamte Installation einschließlich Modelle und Caches endgültig.
    rm -rf -- /opt/wsw-vllm
    # Entfernt den auf diesem Server vorhandenen Cache der früheren Konfiguration.
    rm -rf -- /tmp/torchinductor_wsw-vllm
    WSW_SCRIPT
  3. Dienstkonto entfernen

    Das eigens angelegte Konto und die gegebenenfalls verbliebene Gruppe entfernen.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft, ob das eigens angelegte Dienstkonto noch existiert.
    if getent passwd wsw-vllm >/dev/null; then
      # Entfernt Qwen3-Downloadlocks früherer Anleitungen, die diesem Dienstkonto gehören.
      find /tmp -maxdepth 1 -type f -user wsw-vllm -name '*Qwen-Qwen3-*.lock' -delete
      # Entfernt das Dienstkonto.
      userdel wsw-vllm
    fi
    # Prüft, ob die zugehörige Gruppe noch existiert.
    if getent group wsw-vllm >/dev/null; then
      # Entfernt die gegebenenfalls verbliebene Dienstgruppe.
      groupdel wsw-vllm
    fi
    WSW_SCRIPT
  4. Deinstallation prüfen

    Prüft Installationsdateien, Dienststatus und Konto. Der gesamte Ablauf ist auch nach einer abgebrochenen Installation oder wiederholt ausführbar.

    # Führt den Block separat aus; bei Fehlern bricht nur dieser Block ab.
    bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
    # Prüft alle von dieser Anleitung angelegten Installationspfade.
    for wsw_vllm_pfad in /opt/wsw-vllm /usr/local/bin/vllm /etc/systemd/system/wsw-vllm.service /etc/systemd/system/wsw-vllm.service.d /etc/systemd/system/multi-user.target.wants/wsw-vllm.service /tmp/torchinductor_wsw-vllm; do
      # Erkennt auch symbolische Links mit fehlendem Ziel.
      if [ -e "$wsw_vllm_pfad" ] || [ -L "$wsw_vllm_pfad" ]; then
        # Meldet den verbliebenen Pfad.
        printf 'Nicht entfernt: %s\n' "$wsw_vllm_pfad" >&2
        # Beendet den Prüfblock mit Fehler.
        exit 1
      fi
    done
    # Prüft, ob Konto, Gruppe oder ein aktiver beziehungsweise fehlgeschlagener Dienst verblieben sind.
    if getent passwd wsw-vllm >/dev/null || getent group wsw-vllm >/dev/null || systemctl is-active --quiet wsw-vllm || systemctl is-failed --quiet wsw-vllm; then
      # Meldet die unvollständige Bereinigung.
      printf 'Deinstallation unvollständig: Dienst oder Konto noch vorhanden.\n' >&2
      # Beendet den Prüfblock mit Fehler.
      exit 1
    fi
    # Bestätigt die geprüfte Deinstallation.
    printf 'vLLM ist deinstalliert.\n'
    WSW_SCRIPT
Deinstallation gesamt
bash -e <<'WSW_SCRIPT' || printf 'Abgebrochen (Fehler %s).\n' "$?" >&2
if systemctl cat wsw-vllm.service >/dev/null 2>&1; then
  systemctl disable --now wsw-vllm
fi
if systemctl is-failed --quiet wsw-vllm; then
  systemctl reset-failed wsw-vllm
fi
rm -f -- /etc/systemd/system/wsw-vllm.service
rm -rf -- /etc/systemd/system/wsw-vllm.service.d
systemctl daemon-reload

rm -f -- /usr/local/bin/vllm
rm -rf -- /opt/wsw-vllm
rm -rf -- /tmp/torchinductor_wsw-vllm

if getent passwd wsw-vllm >/dev/null; then
  find /tmp -maxdepth 1 -type f -user wsw-vllm -name '*Qwen-Qwen3-*.lock' -delete
  userdel wsw-vllm
fi
if getent group wsw-vllm >/dev/null; then
  groupdel wsw-vllm
fi

for wsw_vllm_pfad in /opt/wsw-vllm /usr/local/bin/vllm /etc/systemd/system/wsw-vllm.service /etc/systemd/system/wsw-vllm.service.d /etc/systemd/system/multi-user.target.wants/wsw-vllm.service /tmp/torchinductor_wsw-vllm; do
  if [ -e "$wsw_vllm_pfad" ] || [ -L "$wsw_vllm_pfad" ]; then
    printf 'Nicht entfernt: %s\n' "$wsw_vllm_pfad" >&2
    exit 1
  fi
done
if getent passwd wsw-vllm >/dev/null || getent group wsw-vllm >/dev/null || systemctl is-active --quiet wsw-vllm || systemctl is-failed --quiet wsw-vllm; then
  printf 'Deinstallation unvollständig: Dienst oder Konto noch vorhanden.\n' >&2
  exit 1
fi
printf 'vLLM ist deinstalliert.\n'
WSW_SCRIPT