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>
20 KiB
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: 20–40 minut
- Pierwsze uruchomienie vLLM: 20–60 minut (pobieranie obrazu i modelu)
Wymagania: ukończone rozdziały 01–03 oraz 03b — Narzędzia bazowe
Spis treści
Część A — dysk danych
- Architektura dysków
- Wykrycie i partycjonowanie dysku 1 TB
- Montowanie
/datai wpis w fstab - Struktura katalogów na
/data - Przeniesienie Docker data-root na
/data
Część B — vLLM
- Jak działa vLLM (bez UI)
- Mapowanie ustawień z LM Studio
- Struktura plików stacku
- Przygotowanie stacku (bez modelu)
- Wybór modelu i start
- Test API
- Tuning po starcie (jeśli OOM)
- Zmiana modelu
- Zarządzanie stackiem
- Weryfikacja
- Troubleshooting
- Czego nie robimy w tym kroku
- 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
/datazamontowany, ~1 TB wolne- Wpis w
/etc/fstab(UUID) mount -abez błędów- Katalogi
/data/dockeri/data/apps/*utworzone Docker Root Dir: /data/dockerhello-worldi 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: llamacpp — nie 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ć
- Pobranie obrazu
vllm/vllm-openai(~8–12 GB) →/data/docker - Pobranie modelu AWQ (~15 GB) →
/data/apps/vllm/huggingface - Ładowanie wag do VRAM — 10–30+ 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 /data — aktywny 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:
.envz ustawionymVLLM_MODELdocker compose --profile vllm ps—vllmrunningnvidia-smi— proces vLLM, VRAM w użyciucurl http://localhost:8000/v1/models— JSON z modelemcurl .../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:
- Zgłoś w rozmowie z Cursorem, że krok 04 jest gotowy.
- 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