Files
tomasz-syn-grzegorza 359afb3a59 Initial import: bare-metal stacks, Server UI, GPU fan, tutorials.
Infrastructure configs for GMKtec K11 (Docker, vLLM, LocalAI, ComfyUI,
control-plane, gpu-fan agent, Server UI with CLI/file explorer/GPU fan curve).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 12:02:04 +00:00

20 KiB
Raw Permalink Blame History

04 — Dysk danych 1 TB + vLLM stack

Cel rozdziału: dodać dysk 1 TB jako /data (aplikacje, modele, Docker), a na dysku systemowym 128 GB zostawić wyłącznie Ubuntu. Następnie uruchomić vLLM z mountami na /data.

Szacowany czas:

  • Dysk 1 TB + migracja Docker: 2040 minut
  • Pierwsze uruchomienie vLLM: 2060 minut (pobieranie obrazu i modelu)

Wymagania: ukończone rozdziały 0103 oraz 03b — Narzędzia bazowe


Spis treści

Część A — dysk danych

  1. Architektura dysków
  2. Wykrycie i partycjonowanie dysku 1 TB
  3. Montowanie /data i wpis w fstab
  4. Struktura katalogów na /data
  5. Przeniesienie Docker data-root na /data

Część B — vLLM

  1. Jak działa vLLM (bez UI)
  2. Mapowanie ustawień z LM Studio
  3. Struktura plików stacku
  4. Przygotowanie stacku (bez modelu)
  5. Wybór modelu i start
  6. Test API
  7. Tuning po starcie (jeśli OOM)
  8. Zmiana modelu
  9. Zarządzanie stackiem
  10. Weryfikacja
  11. Troubleshooting
  12. Czego nie robimy w tym kroku
  13. Następny krok

Część A — Dysk danych 1 TB

1. Architektura dysków

Dysk Rozmiar Mount Przeznaczenie
NVMe systemowy 128 GB (nvme1n1) / Ubuntu, konfiguracja, repo tutoriala
Dysk danych 1 TB (nvme0n1) /data Docker, modele LLM, ComfyUI, cache
flowchart TB
    subgraph systemDisk ["128GB NVMe — /"]
        os["Ubuntu 26.04"]
        etc["/etc /boot"]
        home["/home"]
        repo["cursor/ubuntu-bare-metal"]
    end

    subgraph dataDisk ["1TB — /data"]
        docker["/data/docker — Docker images/volumes"]
        vllm["/data/apps/vllm/huggingface"]
        comfyui["/data/apps/comfyui/*"]
    end

    docker --> vllm
    docker --> comfyui

Zasada: Na dysku systemowym nie trzymamy modeli ani obrazów Docker. Wszystko ciężkie idzie na /data.

Uwaga: Fizycznie zamontuj dysk 1 TB w GMKtec K11 przed wykonaniem sekcji 2. Po podłączeniu zrób reboot lub rescann PCI/NVMe.


2. Wykrycie i partycjonowanie dysku 1 TB

2.1 Wykrycie nowego dysku

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL

Oczekiwany układ po podłączeniu dysku 1 TB na GMKtec K11:

nvme1n1     119G   # dysk SYSTEMOWY — NIE DOTYKAĆ
├─nvme1n1p1   1G   /boot/efi
└─nvme1n1p2 118G   /
nvme0n1     931G   # dysk DANYCH 1 TB — ten partycjonujemy

Uwaga: Na tym modelu GMKtec K11 numeracja bywa odwrotna niż intuicyjnie — nvme0n1 to 1 TB, nvme1n1 to 128 GB systemu. Zawsze weryfikuj po SIZE i MOUNTPOINT, nie po numerze.

KRYTYCZNE: Partycjonuj wyłącznie dysk ~1 TB bez mountpointu /. Nigdy nvme1n1.

Zapisz nazwę urządzenia:

# GMKtec K11 — dysk DANYCH 1 TB (sprawdź lsblk!)
export DATA_DISK=/dev/nvme0n1
export DATA_PART=/dev/nvme0n1p1

echo "DATA_DISK=$DATA_DISK"
echo "DATA_PART=$DATA_PART"
lsblk $DATA_DISK

# weryfikacja: DATA_DISK nie może mieć mountpoint /
lsblk -n -o MOUNTPOINT $DATA_DISK | grep -q '^/$' && echo "BŁĄD: to dysk systemowy!" && exit 1

Szybka instalacja (skrypt)

Jeśli partycja nvme0n1p1 już istnieje (lub po ręcznym parted), uruchom w swoim terminalu SSH (wymaga hasła sudo):

sudo bash /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/scripts/setup-data-disk.sh

Skrypt: formatuje nvme0n1p1, montuje /data, fstab, katalogi, migracja Docker → /data/docker.


2.2 Partycjonowanie ręczne (jeśli bez skryptu)

Partycja: /dev/nvme0n1p1 (na GMKtec K11).

# OBOWIĄZKOWO ustaw obie zmienne przed każdą komendą!
export DATA_DISK=/dev/nvme0n1
export DATA_PART=/dev/nvme0n1p1

# sprawdź jeszcze raz przed zapisem!
lsblk $DATA_DISK
echo "Partycja: $DATA_PART"

sudo parted -s $DATA_DISK mklabel gpt
sudo parted -s $DATA_DISK mkpart primary ext4 0% 100%
sudo partprobe $DATA_DISK
sleep 2
lsblk $DATA_DISK

2.3 Formatowanie ext4

sudo mkfs.ext4 -L data1tb $DATA_PART

3. Montowanie /data i wpis w fstab

3.1 Pobranie UUID (stabilniejsze niż /dev/sdX)

sudo blkid $DATA_PART

Zapisz UUID, np. UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.

3.2 Montowanie tymczasowe i test

sudo mkdir -p /data
sudo mount $DATA_PART /data
df -h /data

Oczekiwane: ~1 TB dostępne na /data.

3.3 Wpis w /etc/fstab (montowanie przy bootcie)

# zamień YOUR-UUID na rzeczywisty UUID z blkid
echo 'UUID=YOUR-UUID /data ext4 defaults,noatime 0 2' | sudo tee -a /etc/fstab

Lepiej edytować ręcznie — sprawdź składnię:

sudo nano /etc/fstab

Dodaj linię (przykład):

UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /data ext4 defaults,noatime 0 2

Test fstab (montuje wszystko bez rebootu):

sudo umount /data
sudo mount -a
df -h /data

Jeśli mount -a nie zwraca błędu — fstab jest poprawny.


4. Struktura katalogów na /data

# Docker data-root (własność root)
sudo mkdir -p /data/docker

# Katalogi aplikacji (własność użytkownika — łatwiejszy dostęp)
sudo mkdir -p /data/apps/vllm/huggingface
sudo mkdir -p /data/apps/gguf/{qwen3.6-27b,gemma-4-12b}
sudo mkdir -p /data/apps/localai/{models,backends,configuration,images,data}
sudo mkdir -p /data/apps/comfyui/{models,input,output,custom_nodes}

sudo chown -R $USER:$USER /data/apps

Docelowa struktura:

/data/
├── docker/                          # Docker data-root (obrazy, warstwy, volumes)
└── apps/
    ├── vllm/
    │   └── huggingface/             # cache modeli Hugging Face (AWQ)
    ├── gguf/                        # przyszłe GGUF (llama.cpp)
    │   ├── qwen3.6-27b/
    │   └── gemma-4-12b/
    └── comfyui/                     # przygotowane pod rozdział 05
        ├── models/
        ├── input/
        ├── output/
        └── custom_nodes/

Sprawdź:

tree -L 3 /data 2>/dev/null || find /data -maxdepth 3 -type d
df -h / /data

5. Przeniesienie Docker data-root na /data

Domyślnie Docker trzyma dane w /var/lib/docker na dysku systemowym. Przenosimy na /data/docker.

5.1 Zatrzymanie Docker

sudo systemctl stop docker
sudo systemctl stop docker.socket 2>/dev/null || true

Upewnij się, że nie ma uruchomionych kontenerów:

docker ps  # powinno być puste lub błąd „cannot connect” — OK

5.2 Kopia istniejących danych Docker

Jeśli już pobierałeś obrazy (np. hello-world, nvidia/cuda):

sudo rsync -aHAX --progress /var/lib/docker/ /data/docker/

Jeśli /var/lib/docker jest pusty lub mało znaczący:

sudo mkdir -p /data/docker

5.3 Aktualizacja /etc/docker/daemon.json

sudo python3 -c "
import json
from pathlib import Path
p = Path('/etc/docker/daemon.json')
cfg = json.loads(p.read_text()) if p.exists() else {}
cfg['data-root'] = '/data/docker'
p.write_text(json.dumps(cfg, indent=2) + '\n')
print(p.read_text())
"

Oczekiwany fragment:

{
  "data-root": "/data/docker",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  },
  "runtimes": {
    "nvidia": {
      "args": [],
      "path": "nvidia-container-runtime"
    }
  }
}

5.4 Uruchomienie Docker i weryfikacja

sudo systemctl start docker
docker info | grep "Docker Root Dir"

Oczekiwane: Docker Root Dir: /data/docker

docker run --rm hello-world
docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu24.04 nvidia-smi

5.5 Opcjonalnie: zwolnienie miejsca na dysku systemowym

Dopiero po potwierdzeniu, że Docker działa z nowego data-root:

# UWAGA: nieodwracalne — usuwa stare dane Dockera z dysku systemowego
sudo rm -rf /var/lib/docker

Sprawdź, że dysk systemowy ma więcej wolnego miejsca:

df -h /
df -h /data

Checklist — część A (dysk)

  • Dysk 1 TB widoczny w lsblk
  • Partycja sformatowana ext4
  • /data zamontowany, ~1 TB wolne
  • Wpis w /etc/fstab (UUID)
  • mount -a bez błędów
  • Katalogi /data/docker i /data/apps/* utworzone
  • Docker Root Dir: /data/docker
  • hello-world i test GPU w Dockerze działają

Nie przechodź do vLLM, dopóki checklista części A nie jest ukończona.


Część B — vLLM stack

6. Jak działa vLLM (bez UI)

vLLM to serwer API — nie ma panelu administracyjnego jak LM Studio.

LM Studio vLLM na serwerze
Panel UI, suwaki Plik .env + profile + skrypty
GGUF Q4 (lmstudio-community) Nie w vLLM — przyszły stack stacks/llamacpp/; interim: AWQ z HF
Hugging Face AWQ Kontener Docker vllm/vllm-openai
Lokalny chat HTTP API OpenAI (/v1/chat/completions)

Konfiguracja = zmienne w .env przekazywane jako flagi vllm serve. Test: curl + jq lub klient OpenAI.

Model nie jest pobierany przy instalacji stacku — katalog models.catalog.yaml + download-model.sh na żądanie. VLLM_MODEL ustawiasz przez profil dopiero gdy jesteś gotowy.

GGUF z lmstudio-community — nie działa w standardowym vLLM

Linki typu lmstudio-community/Qwen3.6-27B-GGUF to pliki .gguf. Obraz vllm/vllm-openai ich nie obsługuje (wymagałby eksperymentalnego vllm-gguf-plugin lub osobnego hosta).

Co chcesz Co robisz
Q4 jak w LM Studio (GGUF) download-model.sh <id-gguf>/data/apps/gguf/ → później stacks/llamacpp/
Q4-odpowiednik teraz na vLLM download-model.sh qwen3.6-27b-awq-vllm + profil AWQ

7. Mapowanie ustawień z LM Studio

Jeśli testowałeś Qwen3.6-27B na Windows (LM Studio / Ollama):

LM Studio / Ollama vLLM (.env / flagi)
Model GGUF Q4 (lmstudio) Katalog runtime: llamacppnie ten stack vLLM
Odpowiednik Q4 na vLLM (interim) VLLM_MODEL=Qwen/Qwen3.6-27B-Instruct-AWQ, QUANTIZATION=awq
K Cache Q4_0 KV_CACHE_DTYPE=fp8
V Cache Q4_0 j.w. — vLLM nie ma Q4_0 (format GGUF/llama.cpp)
Context 128K MAX_MODEL_LEN=131072
1 wątek MAX_NUM_SEQS=1
GPU layers max GPU_MEMORY_UTILIZATION=0.95

Alternatywa KV cache (więcej miejsca, bliżej Q4): KV_CACHE_DTYPE=turboquant_k8v4 — testuj po udanym starcie z fp8.

Później porównamy z kopią ustawień LM Studio na Windows i doprecyzujemy parametry.


8. Struktura plików stacku

stacks/vllm/
├── README.md
├── models.catalog.yaml      # GGUF + vLLM AWQ, bez auto-pobierania
├── docker-compose.yml
├── .env.example
├── profiles/
│   ├── _template.env
│   └── qwen3.6-27b-awq-128k.env
└── scripts/
    ├── list-models.sh
    ├── download-model.sh
    ├── switch-model.sh
    ├── start.sh
    └── vllm-entrypoint.sh

Dane na dysku 1 TB:

/data/apps/vllm/huggingface/    ← cache AWQ (Hugging Face)
/data/apps/gguf/                ← przyszłe GGUF (tworzone przez skrypty)
/data/docker/                   ← obrazy Docker (vLLM ~10 GB)
cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/vllm

Kluczowe elementy docker-compose.yml

Element Po co
profiles: [vllm] Serwis nie startuje przypadkowo bez --profile vllm
ipc: host Shared memory — wymagane przez PyTorch
vllm-entrypoint.sh Buduje flagi z .env (QUANTIZATION, VLLM_EXTRA_ARGS)
QUANTIZATION Opcjonalne — puste = model pełnej precyzji
VLLM_EXTRA_ARGS Flagi per profil (Qwen: --reasoning-parser qwen3)

Domyślne flagi w profilu Qwen (przekazywane przez VLLM_EXTRA_ARGS):

Flaga Po co
--language-model-only Bez vision encoder — więcej VRAM na KV cache
--enforce-eager Mniej overhead CUDA graphs na 24 GB
--max-num-seqs 1 Jedna sekwencja naraz (jak 1 wątek w LM Studio)
--kv-cache-dtype fp8 Kompresja KV cache (KV_CACHE_DTYPE)
--max-model-len 131072 Okno kontekstu 128K (MAX_MODEL_LEN)

9. Przygotowanie stacku (bez modelu)

9.1 Utwórz .env z szablonu

cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/vllm
cp .env.example .env
cat .env

Oczekiwane — VLLM_MODEL pusty:

DATA_ROOT=/data
VLLM_MODEL=
SERVED_MODEL_NAME=qwen3.6-27b
MAX_MODEL_LEN=131072
MAX_NUM_SEQS=1
GPU_MEMORY_UTILIZATION=0.95
KV_CACHE_DTYPE=fp8
QUANTIZATION=awq
VLLM_EXTRA_ARGS=--language-model-only --enforce-eager --reasoning-parser qwen3

Na tym etapie nic nie pobierasz — stack jest gotowy do konfiguracji.

9.2 Katalog modeli

./scripts/list-models.sh

Pokazuje wpisy z models.catalog.yaml i czy pliki są już na dysku (ON DISK).

9.3 Sprawdź katalogi cache

mkdir -p /data/apps/vllm/huggingface
df -h /data

10. Wybór modelu i start

10.1 Pobierz model (on demand)

cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/vllm

# vLLM interim — AWQ (~15 GB) do cache HF
./scripts/download-model.sh qwen3.6-27b-awq-vllm

GGUF (na później, pod llama.cpp):

# ./scripts/download-model.sh qwen3.6-27b-q4-gguf
# ./scripts/download-model.sh gemma-4-12b-q4-gguf

10.2 Preset Qwen3.6-27B AWQ @ 128K

./scripts/switch-model.sh qwen3.6-27b-awq-128k

Alternatywa — ręcznie:

cp profiles/qwen3.6-27b-awq-128k.env .env

10.3 Uruchomienie

./scripts/start.sh
# lub profil w jednej komendzie:
# ./scripts/start.sh qwen3.6-27b-awq-128k

Skrypt sprawdza: VLLM_MODEL ustawiony, brak .gguf, /data zamontowany, Docker działa.

Alternatywa ręczna:

docker compose --profile vllm pull
docker compose --profile vllm up -d

10.4 Pierwszy start — czego się spodziewać

  1. Pobranie obrazu vllm/vllm-openai (~812 GB) → /data/docker
  2. Pobranie modelu AWQ (~15 GB) → /data/apps/vllm/huggingface
  3. Ładowanie wag do VRAM — 1030+ minut
docker compose --profile vllm logs -f vllm

Szukaj:

INFO:     Uvicorn running on http://0.0.0.0:8000

10.5 Monitorowanie

# terminal 1
watch -n 1 nvidia-smi

# terminal 2
watch -n 5 'df -h /data; du -sh /data/apps/vllm/huggingface 2>/dev/null'

11. Test API

Użyj SERVED_MODEL_NAME z .env (domyślnie qwen3.6-27b-awq w presecie).

11.1 Lista modeli

curl -s http://localhost:8000/v1/models | jq .

11.2 Health check

curl -s http://localhost:8000/health

11.3 Chat completion

curl -s http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.6-27b-awq",
    "messages": [{"role": "user", "content": "Say hello in one word."}],
    "max_tokens": 32
  }' | jq .

Pole "model" musi odpowiadać SERVED_MODEL_NAME z .env.


12. Tuning po starcie (jeśli OOM)

Kolejność — zmieniaj w .env i restartuj:

docker compose --profile vllm down
# edytuj .env
docker compose --profile vllm up -d
Krok Zmiana Kiedy
1 MAX_MODEL_LEN=98304 lub 65536 OOM przy ładowaniu lub długim prompcie
2 GPU_MEMORY_UTILIZATION=0.90 Nadal OOM
3 KV_CACHE_DTYPE=turboquant_k8v4 Potrzeba więcej miejsca na KV (bliżej Q4_0)

13. Zmiana modelu (A / B na dysku, jeden w VRAM)

Kilka modeli może leżeć na /dataaktywny jest tylko jeden w VRAM. Przełączenie = profil + restart kontenera.

cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/vllm

# sprawdź co jest na dysku
./scripts/list-models.sh

# pobierz drugi model (jeśli potrzeba)
# ./scripts/download-model.sh <catalog-id>

# przełącz profil vLLM i zrestartuj
./scripts/switch-model.sh qwen3.6-27b-awq-128k

Nowy profil: skopiuj profiles/_template.env, dostosuj VLLM_MODEL, QUANTIZATION, VLLM_EXTRA_ARGS.

start.sh odrzuca .gguf w VLLM_MODEL — GGUF wymaga przyszłego stacks/llamacpp/.

Sprawdź miejsce przed większym modelem:

df -h /data
du -sh /data/apps/vllm/huggingface

14. Zarządzanie stackiem

cd /home/tomasz-syn-grzegorza/cursor/ubuntu-bare-metal/stacks/vllm

docker compose --profile vllm ps
docker compose --profile vllm logs -f vllm
docker compose --profile vllm restart vllm
docker compose --profile vllm down

15. Weryfikacja

Checklist — cały rozdział 04

Dysk:

  • /data ~1 TB zamontowany (fstab)
  • Docker Root Dir: /data/docker

vLLM:

  • .env z ustawionym VLLM_MODEL
  • docker compose --profile vllm psvllm running
  • nvidia-smi — proces vLLM, VRAM w użyciu
  • curl http://localhost:8000/v1/models — JSON z modelem
  • curl .../v1/chat/completions — odpowiedź tekstowa
  • Model na /data/apps/vllm/huggingface

Szybki test końcowy

df -h / /data
docker info | grep "Docker Root Dir"
curl -s http://localhost:8000/v1/models | jq .
du -sh /data/apps/vllm/huggingface /data/docker

16. Troubleshooting

16.1 /data nie montuje się po reboot

sudo mount -a
cat /etc/fstab

16.2 VLLM_MODEL is empty lub .gguf rejected

./scripts/list-models.sh
./scripts/download-model.sh qwen3.6-27b-awq-vllm
./scripts/switch-model.sh qwen3.6-27b-awq-128k

Jeśli wpisałeś ścieżkę .gguf — użyj AWQ (vLLM) lub poczekaj na stack llama.cpp.

16.3 OOM na GPU (CUDA out of memory)

Zobacz sekcję 12 (tuning). Typowy zestaw na start po OOM:

MAX_MODEL_LEN=65536
GPU_MEMORY_UTILIZATION=0.90
KV_CACHE_DTYPE=fp8

16.4 Błąd kwantyzacji AWQ

Upewnij się, że model ma suffix -AWQ i QUANTIZATION=awq w .env.

16.5 connection refused na :8000

Model jeszcze się ładuje — docker compose --profile vllm logs -f vllm.

16.6 Wolny pierwszy prefill przy długim kontekście

Normalne przy 128K — pierwsze żądanie z długim promptem trwa dłużej.

16.7 Brak miejsca na /data

df -h /data
docker system df

17. Czego nie robimy w tym kroku

Element Gdzie
ComfyUI Rozdział 06
LocalAI (UI + GGUF) Rozdział 05
Open WebUI (panel do vLLM) później, opcjonalnie
Pełny Docker llama.cpp / GGUF placeholder: stacks/llamacpp/ — LocalAI może obsłużyć GGUF
Context 262K start 128K; tuning później
Firewall Rozdział 07

18. Następny krok

Po przejściu checklisty z sekcji 15:

  1. Zgłoś w rozmowie z Cursorem, że krok 04 jest gotowy.
  2. Przejdź do rozdziału 05 — LocalAI stack (05-localai-stack.md).

Podsumowanie wykonanych zmian

Po ukończeniu tego rozdziału:

  • Dysk 1 TB: /data (Docker + modele)
  • Stack vLLM gotowy bez domyślnego modelu
  • Katalog modeli (models.catalog.yaml) + skrypty list/download/switch
  • Po wyborze: Qwen3.6-27B AWQ (interim Q4), kontekst 128K, KV cache fp8
  • GGUF lmstudio — ścieżki na /data/apps/gguf/, host llama.cpp planowany
  • API OpenAI na porcie 8000