diff --git a/core/src/knaster/inventory/placement.py b/core/src/knaster/inventory/placement.py index cc630ec..cdd257e 100644 --- a/core/src/knaster/inventory/placement.py +++ b/core/src/knaster/inventory/placement.py @@ -182,7 +182,7 @@ def plan(nodes: list[NodeProfile], roles: list[RoleSpec], models: list[ModelSpec missing = role.count - placed if missing and (not role.needs.gpu or role.cpu_fallback): mode: Literal["cpu", "cpu_fallback"] = "cpu" if not role.needs.gpu else "cpu_fallback" - candidates = [n for n in nodes if n.memory_gb >= role.needs.min_ram_gb] + candidates = [n for n in nodes if n.cpu_roles and n.memory_gb >= role.needs.min_ram_gb] # bevorzugt Nodes ohne GPU-Rollen, dann wenig belastete, dann viel RAM gpu_roles = {a.node for a in result.assignments if a.mode == "gpu"} candidates.sort(key=lambda n: (n.name in gpu_roles, cpu_load[n.name], -n.memory_gb, n.name)) diff --git a/core/src/knaster/inventory/profile.py b/core/src/knaster/inventory/profile.py index 41f254c..4908d8c 100644 --- a/core/src/knaster/inventory/profile.py +++ b/core/src/knaster/inventory/profile.py @@ -43,6 +43,7 @@ class NodeProfile(BaseModel): disks: list[Disk] = Field(default_factory=list) gpus: list[GPU] = Field(default_factory=list) labels: dict[str, str] = Field(default_factory=dict) + cpu_roles: bool = True # False: Arbeitsplatz/Kiosk – keine Infra- oder CPU-Fallback-Rollen, nur eigene GPUs NVIDIA_SMI_QUERY = ["nvidia-smi", "--query-gpu=index,name,memory.total,compute_cap,driver_version", diff --git a/core/tests/test_artifacts.py b/core/tests/test_artifacts.py index 86dd906..7521a49 100644 --- a/core/tests/test_artifacts.py +++ b/core/tests/test_artifacts.py @@ -24,7 +24,7 @@ def test_healer_labels_available_on_alerts(): """Labels, die Healer-Regeln brauchen, müssen von Metrik oder Scrape-Target kommen.""" prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text()) jobs = {j["job_name"]: j for j in prom["scrape_configs"]} - for job in ("node", "gpu", "vllm"): + for job in ("node", "gpu", "llamacpp"): for sc in jobs[job]["static_configs"]: assert {"slot", "pve_node", "vmid"} <= set(sc["labels"]), (job, sc) from knaster.canary import OPS_LABELS @@ -46,13 +46,17 @@ def test_compose_files_parse(role): def test_ops_labels_consistent_between_registry_and_prometheus(): + """Das erste Scrape-Ziel eines Slots trägt dieselben ops-Labels wie die Registry (Healer-Ziel).""" reg = yaml.safe_load((REPO_ROOT / "sites/knaster/registry.yaml").read_text()) prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text()) - vllm = {sc["labels"]["slot"]: sc["labels"] for sc in - next(j for j in prom["scrape_configs"] if j["job_name"] == "vllm")["static_configs"]} - for slot, labels in vllm.items(): + first: dict[str, dict] = {} + for sc in next(j for j in prom["scrape_configs"] if j["job_name"] == "llamacpp")["static_configs"]: + first.setdefault(sc["labels"]["slot"], sc["labels"]) + assert first + for slot, labels in first.items(): ops = reg["slots"][slot]["ops"] - assert ops["vmid"] == labels["vmid"] and ops["container"] == labels["container"] + assert {k: ops[k] for k in ("docker_host", "container", "pve_node", "vmid")} == \ + {k: labels[k] for k in ("docker_host", "container", "pve_node", "vmid")}, slot WORKFLOWS = sorted((REPO_ROOT / "n8n/workflows").glob("*.json")) @@ -160,19 +164,27 @@ def test_proxmox_site_consistent_with_registry_and_prometheus(): reg = yaml.safe_load((REPO_ROOT / "sites/knaster/registry.yaml").read_text()) vms = site["vms"] for vm in vms.values(): - assert vm["gpu"] in site["pci_mappings"] - assert site["pci_mappings"][vm["gpu"]]["node"] == vm["node"] # Mapping liegt auf dem Host der VM + for gpu in vm["gpus"]: + assert gpu in site["pci_mappings"] + assert site["pci_mappings"][gpu]["node"] == vm["node"] # Mapping liegt auf dem Host der VM assert vm["driver_generation"] in {"legacy", "modern"} - gpus = [vm["gpu"] for vm in vms.values()] + assert vm["node"] in site["nodes"] and site["nodes"][vm["node"]]["gpu_host"] + gpus = [g for vm in vms.values() for g in vm["gpus"]] assert len(gpus) == len(set(gpus)) # keine GPU doppelt durchgereicht + assert set(gpus) == set(site["pci_mappings"]) # jede gemappte Karte steckt in einer VM by_vmid = {str(vm["vmid"]): (name, vm) for name, vm in {**vms, **site["containers"]}.items()} for slot, spec in reg["slots"].items(): - ops = spec.get("ops") - if not ops: + ops = spec.get("ops") or {} + if "vmid" not in ops: # z. B. Ada-Worker unter Windows (D3), kein Proxmox-Gast + assert "pve_node" not in ops, slot continue name, vm = by_vmid[ops["vmid"]] assert vm["node"] == ops["pve_node"], slot - assert ops["docker_host"].startswith(name.split("-")[0]), slot + assert ops["docker_host"] == name, slot + for c in site["containers"].values(): + if c.get("ha"): + assert site["nodes"][c["node"]]["zfs_tank"] and c["storage"] == "tank" + assert all(site["nodes"][t]["zfs_tank"] for t in c["replicate_to"]) vmids = [str(v["vmid"]) for v in vms.values()] + [str(c["vmid"]) for c in site["containers"].values()] assert len(vmids) == len(set(vmids)) # vLLM nur auf modernen Karten diff --git a/core/tests/test_inventory_validate.py b/core/tests/test_inventory_validate.py index b631dba..0f1c281 100644 --- a/core/tests/test_inventory_validate.py +++ b/core/tests/test_inventory_validate.py @@ -28,29 +28,70 @@ def test_parsers(): assert disks[0].size_gb == 256.1 and not disks[0].rotational +PVE_GPU_HOSTS = {"pve44", "pve46", "pve48", "pve52"} + + +def test_site_knaster_matches_real_nodes(): + """Aufbauplan Task 0.4: Site-Dateien beschreiben die echten Knoten, nicht mehr die Annahmen pve-ada/pve1–4.""" + import ipaddress + + from knaster.util import load_yaml + + nodes = {n.name: n for n in load_nodes(REPO_ROOT / "sites/knaster/inventory.yaml")} + assert set(nodes) == {"pve44", "pve46", "pve48", "pve52", "pve61", "gode-pc-05", "gode-pc-06"} + pve = load_yaml(REPO_ROOT / "sites/knaster/proxmox.yaml") + old_net = ipaddress.ip_network("10.20.0.0/24") + ips = [] + for section in ("vms", "containers"): + ips += [ipaddress.ip_interface(g["ip"]).ip for g in pve[section].values()] + ips += [ipaddress.ip_address(pve["cluster"][k]) for k in ("gateway", "dns")] + assert not [ip for ip in ips if ip in old_net] + assert {m["node"] for m in pve["pci_mappings"].values()} <= PVE_GPU_HOSTS + assert {v["node"] for v in pve["vms"].values()} <= PVE_GPU_HOSTS + # eine GPU-VM pro GPU-Knoten mit allen Karten des Knotens (D2) + assert sorted(v["node"] for v in pve["vms"].values()) == sorted(PVE_GPU_HOSTS) + for vm in pve["vms"].values(): + mapped = {name for name, m in pve["pci_mappings"].items() if m["node"] == vm["node"]} + assert set(vm["gpus"]) == mapped + gpus_inv = [g for g in nodes[vm["node"]].gpus] + assert len(vm["gpus"]) == len(gpus_inv) + # Ada-Rechner bleiben Windows-Arbeitsplätze (D3): keine Infra-/CPU-Rollen + assert not nodes["gode-pc-05"].cpu_roles and not nodes["pve61"].cpu_roles + + def test_placement_knaster(): roles, models = load_catalog(REPO_ROOT / "catalog") p = plan(load_nodes(REPO_ROOT / "sites/knaster/inventory.yaml"), roles, models) by_role = {} for a in p.assignments: by_role.setdefault(a.role, []).append(a) + assert {by_role["gpu-main"][0].node, by_role["gpu-flex"][0].node} == {"gode-pc-05", "gode-pc-06"} assert by_role["gpu-main"][0].gpu_name.startswith("NVIDIA RTX 4000 Ada") assert "qwen3-14b-awq" in by_role["gpu-main"][0].models_fitting embed_nodes = {a.node for a in by_role["gpu-embed"]} assert len(embed_nodes) == 2 # Anti-Affinität assert all("P2000" in a.gpu_name for a in by_role["gpu-embed"] + by_role["gpu-small"]) - assert by_role["infra"][0].mode == "cpu" + assert by_role["infra"][0].mode == "cpu" and by_role["infra"][0].node in PVE_GPU_HOSTS assert not p.unplaced # M4000 bewusst außer Betrieb (ADR-0006): weder frei noch belegt assert p.free_gpus == [] assert len(p.disabled_gpus) == 4 and all("M4000" in g for g in p.disabled_gpus) tts = {a.role: a for a in p.assignments if a.role.startswith("tts")} - assert tts["tts-hq"].mode == "shared" and tts["tts-hq"].node == "pve-ada" + assert tts["tts-hq"].mode == "shared" and tts["tts-hq"].node == by_role["gpu-flex"][0].node assert "qwen3-tts-1.7b" in tts["tts-hq"].models_fitting assert tts["tts-cpu"].node == by_role["infra"][0].node assert tts["tts-cpu"].models_fitting == ["piper-de-thorsten-medium"] +def test_nodes_without_cpu_roles_get_no_infra(): + """Arbeitsplätze und Kiosk nehmen keine CPU-Rollen, auch wenn sie den meisten RAM haben.""" + roles, models = load_catalog(REPO_ROOT / "catalog") + nodes = [NodeProfile(name="workstation", memory_gb=64, cpu_roles=False), + NodeProfile(name="server", memory_gb=32)] + p = plan(nodes, roles, models) + assert {a.node for a in p.assignments if a.mode in ("cpu", "cpu_fallback")} == {"server"} + + def test_placement_other_cluster(): roles, models = load_catalog(REPO_ROOT / "catalog") p = plan(load_nodes(REPO_ROOT / "sites/example-single-gpu/inventory.yaml"), roles, models) diff --git a/deploy/prometheus/prometheus.yml b/deploy/prometheus/prometheus.yml index 31ba743..cb1cd79 100644 --- a/deploy/prometheus/prometheus.yml +++ b/deploy/prometheus/prometheus.yml @@ -1,5 +1,8 @@ # Scrape-Ziele. Die Labels an den GPU-Zielen (slot, docker_host, container, pve_node, vmid) landen in den -# Alerts und damit beim Healer. Hostnamen = DNS-Namen der VMs (ANNAHME bis Deployment). +# Alerts und damit beim Healer. IP-Literale aus sites/knaster/proxmox.yaml (Global Constraints, Aufbauplan). +# ANNAHME bis Task 0.2 (Gast-IPs) und Task 3.3 (welcher Slot auf welcher P2000 läuft). +# Ada-Worker (GODE-PC-05/06, Windows, D3) werden nicht gescrapt: ihre Verfügbarkeit meldet die +# Worker-Registry v2 (Task 6.4), der Canary-Prober des Routers prüft die Slots zusätzlich. global: scrape_interval: 30s evaluation_interval: 30s @@ -15,52 +18,45 @@ scrape_configs: metrics_path: /metrics static_configs: [{targets: ["router:8080"]}] - - job_name: vllm - static_configs: - - targets: ["gpu-main:8000"] - labels: {slot: tier1-general, docker_host: gpu-main, container: vllm, pve_node: pve-ada, vmid: "201"} - - targets: ["gpu-flex:8000"] - labels: {slot: tier1-flex, docker_host: gpu-flex, container: vllm, pve_node: pve-ada, vmid: "202"} - + # Erstes Ziel je Slot = Healer-Ziel in sites/knaster/registry.yaml (ops) - job_name: llamacpp static_configs: - - targets: ["gpu-embed-1:8081"] - labels: {slot: embed-default, docker_host: gpu-embed-1, container: llamacpp-embed, pve_node: pve3, vmid: "221"} - - targets: ["gpu-embed-1:8082"] - labels: {slot: rerank-default, docker_host: gpu-embed-1, container: llamacpp-rerank, pve_node: pve3, vmid: "221"} - - targets: ["gpu-small:8080"] - labels: {slot: verifier-small, docker_host: gpu-small, container: llamacpp-chat, pve_node: pve3, vmid: "231"} + - targets: ["192.168.100.224:8081"] + labels: {slot: embed-default, docker_host: gpu-pve44, container: llamacpp-embed, pve_node: pve44, vmid: "244"} + - targets: ["192.168.100.226:8081"] + labels: {slot: embed-default, docker_host: gpu-pve46, container: llamacpp-embed, pve_node: pve46, vmid: "246"} + - targets: ["192.168.100.224:8082"] + labels: {slot: rerank-default, docker_host: gpu-pve44, container: llamacpp-rerank, pve_node: pve44, vmid: "244"} + - targets: ["192.168.100.228:8080"] + labels: {slot: verifier-small, docker_host: gpu-pve48, container: llamacpp-chat, pve_node: pve48, vmid: "248"} - job_name: gpu static_configs: - - targets: ["gpu-main:9835"] - labels: {slot: tier1-general, pve_node: pve-ada, vmid: "201"} - - targets: ["gpu-flex:9835"] - labels: {slot: tier1-flex, pve_node: pve-ada, vmid: "202"} - - targets: ["gpu-embed-1:9835"] - labels: {slot: embed-default, pve_node: pve3, vmid: "221"} - - targets: ["gpu-embed-2:9835"] - labels: {slot: embed-default, pve_node: pve4, vmid: "222"} - - targets: ["gpu-small:9835"] - labels: {slot: verifier-small, pve_node: pve3, vmid: "231"} + - targets: ["192.168.100.224:9835"] + labels: {slot: embed-default, pve_node: pve44, vmid: "244"} + - targets: ["192.168.100.226:9835"] + labels: {slot: embed-default, pve_node: pve46, vmid: "246"} + - targets: ["192.168.100.228:9835"] + labels: {slot: verifier-small, pve_node: pve48, vmid: "248"} + - targets: ["192.168.100.232:9835"] + labels: {slot: "", pve_node: pve52, vmid: "252"} # nur M4000, außer Betrieb bis Task 3.3 - job_name: node # Labels werden für GpuXidError → Healer (VM-Neustart) gebraucht static_configs: - - targets: ["gpu-main:9100"] - labels: {slot: tier1-general, pve_node: pve-ada, vmid: "201"} - - targets: ["gpu-flex:9100"] - labels: {slot: tier1-flex, pve_node: pve-ada, vmid: "202"} - - targets: ["gpu-embed-1:9100"] - labels: {slot: embed-default, pve_node: pve3, vmid: "221"} - - targets: ["gpu-embed-2:9100"] - labels: {slot: embed-default, pve_node: pve4, vmid: "222"} - - targets: ["gpu-small:9100"] - labels: {slot: verifier-small, pve_node: pve3, vmid: "231"} + - targets: ["192.168.100.224:9100"] + labels: {slot: embed-default, pve_node: pve44, vmid: "244"} + - targets: ["192.168.100.226:9100"] + labels: {slot: embed-default, pve_node: pve46, vmid: "246"} + - targets: ["192.168.100.228:9100"] + labels: {slot: verifier-small, pve_node: pve48, vmid: "248"} + - targets: ["192.168.100.232:9100"] + labels: {slot: "", pve_node: pve52, vmid: "252"} - job_name: proxmox metrics_path: /pve params: {module: [default]} - static_configs: [{targets: ["pve1", "pve2", "pve3", "pve4", "pve-ada"]}] + static_configs: + - targets: ["192.168.100.210", "192.168.100.209", "192.168.100.212", "192.168.100.207", "192.168.100.214"] relabel_configs: - {source_labels: [__address__], target_label: __param_target} - {source_labels: [__param_target], target_label: instance} diff --git a/docs/PLAN.md b/docs/PLAN.md index 782dc73..4283765 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -33,6 +33,7 @@ Ziel: Ein starkes LLM (OpenRouter) erarbeitet neue Aufgaben einmalig und bricht - **GPU-Nodes als VM.** Grund: Die alten Karten (Maxwell M4000, Pascal P2000) brauchen fest gepinnte Treiber mit CUDA 12.x, die Ada-Karten profitieren von aktuellen Treibern. Mit VMs lebt beides sauber getrennt auf einem Cluster. Treiber-Updates werden per Snapshot rückrollbar. - **CPU-Infrastruktur als LXC oder leichte VM** (n8n, Postgres, LiteLLM, Monitoring). Das spart RAM auf den i7-Nodes. - Innerhalb der VMs bzw. LXCs läuft Docker Compose, ein Compose-Projekt pro Rolle. +- **Stand Aufbauplan (D2):** eine GPU-VM pro Knoten mit allen Karten des Knotens statt einer VM pro Rolle. Im Cluster stecken nur noch Maxwell und Pascal, die Ada-Rechner bleiben Windows-Arbeitsplätze (ADR-0001 und ADR-0006, jeweils Nachtrag). ### 1.2 Was Proxmox zusätzlich ermöglicht @@ -47,30 +48,35 @@ Ziel: Ein starkes LLM (OpenRouter) erarbeitet neue Aufgaben einmalig und bricht ### 1.3 Rollenverteilung (aktuelle Hardware) +Stand: Aufbauplan vom 12.10.2026 (`docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md`). Die Knoten stehen in `sites/knaster/inventory.yaml`. Welche P2000 welche Rolle bekommt, legt erst der Benchmark-Tag fest (Task 3.3). + | Rolle | Hardware | Engine | Aufgabe | |---|---|---|---| -| `gpu-main` | RTX 4000 Ada #1 (20 GB) | vLLM, Multi-LoRA | Slot `tier1-general` plus Skill-Adapter | -| `gpu-flex` | RTX 4000 Ada #2 (20 GB) | vLLM / Unsloth | tagsüber zweiter Slot oder Reserve, nachts LoRA-Training | -| `gpu-embed` | 2× Quadro P2000 (5 GB) | llama.cpp (Embedding/Rerank) | `embed-default`, `rerank-default`, redundant auf zwei Hosts | -| `gpu-small` | Quadro P2000 (5 GB) | llama.cpp | `verifier-small`, `signature-extractor` (3–4B) | -| – | 4× Quadro M4000 (8 GB) | – | **vorerst außer Betrieb** (ADR-0006), nur nach Benchmark für llama.cpp-Rollen | -| `tts-hq` | teilt RTX 4000 Ada #2 | PyTorch | `tts-de-hq`: Qwen3-TTS 1.7B (ADR-0006) | -| `tts-cpu` | Infra-LXC (CPU) | Piper (ONNX) | `tts-de`: schnelle Sprachausgabe, Fallback | -| `infra` | i7 6./7. Gen, 32 GB | Docker | n8n, LiteLLM, Postgres+pgvector, Redis, Router, Healer, Langfuse, Prometheus/Grafana | -| `strong` | extern | OpenRouter | `tier0-strong`, Skill-Autor (Hermes) | +| `gpu-main` | RTX 4000 Ada (20 GB) in GODE-PC-05, Windows | llama-server über Worker-Registry v2 (D3); vLLM/Multi-LoRA erst ab Phase 9 | Slot `tier1-general`, wenn der Worker online ist, sonst tier0 | +| `gpu-flex` | RTX 4000 Ada (20 GB) in GODE-PC-06, Windows | wie `gpu-main` | `tier1-flex`; Training erst ab Phase 9 | +| `gpu-embed` | 2× Quadro P2000 (5 GB) in `gpu-pve44` und `gpu-pve46` | llama.cpp (Embedding/Rerank) | `embed-default`, `rerank-default`, redundant auf zwei Knoten | +| `gpu-small` | Quadro P2000 (5 GB) in `gpu-pve48` | llama.cpp | `verifier-small`, `signature-extractor` (3–4B) | +| – | 4× Quadro M4000 (8 GB), je eine in pve44/46/48/52 | – | **vorerst außer Betrieb** (ADR-0006), werden aber an die GPU-VM durchgereicht; nach dem Benchmark nur für llama.cpp-Rollen | +| `tts-hq` | teilt die Ada in GODE-PC-06 | PyTorch | `tts-de-hq`: Qwen3-TTS 1.7B, erst ab Phase 9 (WSL2, D3) | +| `tts-cpu` | Infra-LXC (CPU) | Piper (ONNX) | `tts-de`: Sprachausgabe, vorerst einziger TTS-Slot | +| `infra` | LXC `infra` auf pve44 (`tank`, HA nach pve52/pve48) | Docker | Router, Postgres+pgvector, Healer, Prometheus/Grafana/Alertmanager; **kein eigener LiteLLM** (D4) | +| `ops` | LXC auf pve46 | – | Ansible, OpenTofu, Repo-Clone | +| `strong` | extern | OpenRouter **über den LiteLLM mit PSE auf appsrv08** | `tier0-strong`, Skill-Autor (Hermes) | + +pve61 (Surface Book, 8 GB) nimmt keine Gäste auf und zeigt nur das Grafana-Dashboard als Kiosk. n8n läuft weiter auf n8n.mindboost.app, LiteLLM auf appsrv08 (D4). Wichtig: Auf Maxwell und Pascal läuft kein vLLM (das braucht Compute Capability ≥ 7.0), und CUDA 13 unterstützt sie nicht mehr. Daher auf diesen Nodes Treiber und Images pinnen. Auch PyTorch hat dort keine Zukunft mehr (2.14 ist die letzte Version mit CUDA-12-Wheels). Deshalb laufen auf alten Karten nur llama.cpp-Workloads (ADR-0006). -### 1.4 Netzwerk und Storage (entschieden, siehe ADR-0002) +### 1.4 Netzwerk und Storage (entschieden, siehe ADR-0002 mit Nachtrag D1) -Ausgangslage: **1 GbE** zwischen den Nodes, pro physischer Maschine **2–3 SSDs mit 128–256 GB**. +Ausgangslage: **1 GbE**, flaches Netz `192.168.100.0/24`. Die Knoten sind mit ext4 und LVM-thin auf einer System-SSD installiert, kein ZFS. - **Kein Ceph.** Ceph braucht für brauchbare Latenz und Rebuild-Zeiten mindestens 10 GbE und mehr Platz pro OSD. Mit 1 GbE und kleinen SSDs würde jeder Schreibvorgang über das Netz gebremst und ein Rebuild das Netz tagelang belegen. -- **ZFS lokal pro Node.** SSD 1 trägt das Proxmox-System. SSD 2 und 3 bilden, wo vorhanden, einen ZFS-Mirror `tank` für VM-Disks. Auf Nodes mit nur zwei SSDs ist `tank` eine Einzel-SSD, die über Replikation abgesichert wird. -- **ZFS-Replikation** (Proxmox-Job, alle 15 Min.) für die Infra-VMs auf einen zweiten Node. Zusammen mit Proxmox HA startet die VM nach einem Node-Ausfall dort mit maximal 15 Min. altem Stand. Für Postgres wird das zusätzlich durch PBS-Backups und WAL-Archivierung abgefedert. -- **Modelle liegen lokal** auf der GPU-VM, in einem eigenen Disk-Image (`models`, nicht repliziert, nicht gesichert). Quelle ist Hugging Face oder ein optionaler **Modell-Mirror** auf einem Infra-Node (HTTP/NFS, nur lesend). 10 GB über 1 GbE dauern etwa 90 s, das ist für Modellwechsel unkritisch. -- **Proxmox Backup Server** als VM oder LXC auf dem Node mit dem meisten freien SSD-Platz, oder besser auf einer externen Platte bzw. einem NAS. Gesichert werden Postgres, Konfiguration und Datensätze, keine Modellgewichte. -- **Netztrennung per VLAN** (Proxmox SDN): `mgmt` (Proxmox, PBS), `svc` (Dienste untereinander), `ext` (Zugriff von außen auf n8n und Router). Ohne Bandbreitengewinn, aber Replikation und Backups lassen sich zeitlich steuern, damit sie den Inferenz-Traffic nicht stören. +- **Variante B (D1, entschieden 12.10.):** Die System-SSD bleibt (`local`, `local-lvm` für die GPU-VMs). Auf pve44, pve48 und pve52 wird die zweite SSD nach Freigabe zur Einzel-SSD `tank`. pve46 und pve61 bleiben ohne ZFS. Der ARC ist auf 4 GiB begrenzt. +- **ZFS-Replikation** (Proxmox-Job, alle 15 Min.) für HA-Gäste (`infra`) zwischen den `tank`-Knoten. Zusammen mit Proxmox HA startet der Gast nach einem Knotenausfall mit höchstens 15 Min. altem Stand. Für Postgres wird das zusätzlich durch PBS-Backups und WAL-Archivierung abgefedert. +- **Modelle liegen lokal** auf der GPU-VM, in einem eigenen Disk-Image (`models`, 60 GB, nicht repliziert, nicht gesichert). Quelle ist Hugging Face oder ein optionaler **Modell-Mirror** auf einem Infra-Node (HTTP/NFS, nur lesend). 10 GB über 1 GbE dauern etwa 90 s, das ist für Modellwechsel unkritisch. +- **Proxmox Backup Server** als VM `pbs` auf pve52, Datastore auf einem externen Datenträger oder NAS (Task 5.1). Gesichert werden Postgres, Konfiguration und Datensätze, keine Modellgewichte. +- **Netz (D5):** Die Knoten behalten .207–.214. Gäste bekommen feste IPs aus einem Block außerhalb des DHCP-Bereichs, vorgeschlagen ist `.220–.239` (Prüfung in Task 0.2). Konfigurationen nutzen IP-Literale, keine DNS-Namen. **VLANs** (`mgmt`, `svc`, `ext`) kommen erst später, wenn der Switch das zulässt. **Speicherbudget pro Rolle (Richtwerte):** @@ -80,7 +86,7 @@ Ausgangslage: **1 GbE** zwischen den Nodes, pro physischer Maschine **2–3 SSDs | `gpu-embed` / `gpu-small` | 15–25 GB | Embedding-, Rerank- und kleine GGUF-Modelle (je 0,5–3 GB), Images | | `infra` | 40–80 GB | Postgres (Fälle, Traces), Prometheus (15 Tage Retention), Langfuse | -Die genaue SSD-Verteilung wird beim Inventarlauf erfasst (`sites/knaster/inventory.yaml`). +Die SSD-Verteilung steht in `sites/knaster/inventory.yaml` (Bestandsaufnahme vom 12.10.). --- @@ -303,10 +309,19 @@ Die Portabilität beruht also auf den **Daten und der Pipeline**, nicht auf Gewi - Infrastruktur heilt deterministisch, ohne LLM-Entscheidungen (ADR-0005) - Alte GPUs nur llama.cpp, M4000 vorerst aus, TTS mit Piper (CPU) und Qwen3-TTS (Ada) (ADR-0006) +### Entscheidungen 2026-10 (Aufbauplan) +| # | Thema | Stand | +|---|---|---| +| D1 | Speicher: Variante B, keine Neuinstallation, `tank` auf pve44/48/52 | **entschieden** 12.10. Gelöscht wird erst nach ausdrücklicher Freigabe der alten Windows-Platten | +| D2 | Eine Ubuntu-VM pro GPU-Knoten mit allen Karten (ADR-0001, Nachtrag) | empfohlen, Bestätigung offen (Task 0.3) | +| D3 | Ada-Rechner bleiben Windows, Anbindung über die Worker-Registry v2 (ADR-0006, Nachtrag) | empfohlen, Bestätigung offen | +| D4 | Kein eigener LiteLLM, der Router nutzt den LiteLLM mit PSE auf appsrv08 | empfohlen, Bestätigung offen | +| D5 | Knoten-IPs bleiben, Gäste aus `.220–.239`, kein VLAN | empfohlen, Bestätigung offen. Der IP-Block wird in Task 0.2 geprüft | + ### Offen 1. Erstes Basismodell für `tier1-general` (8–14B, gut quantisierbar, LoRA-freundlich, gutes Deutsch). Entscheidung per Kalibrierungslauf. 2. Kommen die M4000 zurück? `llama-bench` plus Strommessung am Deployment-Tag (bis dahin außer Betrieb, ADR-0006) 3. Verifier-Strategie pro Aufgabentyp: Code-Checks, Schema, kleines Judge-Modell, Stichproben durch das starke Modell 4. Schwellwerte: Graduierung (Start 90 %), Fallzahl fürs LoRA-Training (Start 300), Canary-Anteil (Start 10 %) 5. Kanal für den Pflege-Agenten: n8n Chat, Telegram oder Matrix -6. Standort des Proxmox Backup Servers (intern auf SSD oder extern) +6. Ziel-Speicher des Proxmox Backup Servers (Aufbauplan Task 5.1, Empfehlung: externer Datenträger oder NAS) diff --git a/docs/adr/0001-gpu-vms-infra-lxc.md b/docs/adr/0001-gpu-vms-infra-lxc.md index a9bc3b0..47e2e0c 100644 --- a/docs/adr/0001-gpu-vms-infra-lxc.md +++ b/docs/adr/0001-gpu-vms-infra-lxc.md @@ -1,6 +1,6 @@ # ADR-0001: GPU-Nodes als VM, Infrastruktur als LXC -- Status: angenommen +- Status: angenommen, ergänzt am 2026-10-13 (Nachtrag D2, Bestätigung offen) - Datum: 2026-10-11 ## Kontext @@ -15,3 +15,15 @@ Im Cluster stecken GPUs aus drei Generationen: Maxwell (M4000), Pascal (P2000) u - Der VM-RAM ist bei Passthrough vollständig reserviert. Er wird deshalb knapp bemessen: llama.cpp-VMs 6–8 GB, vLLM-VMs 24–32 GB. - Keine Live-Migration für GPU-VMs. Bei einem Ausfall springt der Router auf einen anderen Slot oder auf Stufe 0. - Treiber-Updates laufen pro VM, abgesichert durch einen Snapshot. + +## Nachtrag 2026-10-13: eine GPU-VM pro Knoten (Aufbauplan D2, Bestätigung offen) +Die echte Hardware (Aufbauplan, Ausgangslage) ändert die Voraussetzungen: +- Im Proxmox-Cluster stecken nur Maxwell und Pascal (pve44, pve46, pve48: M4000 + P2000, pve52: nur M4000). Die beiden RTX 4000 Ada sitzen in Windows-Arbeitsplätzen außerhalb des Clusters (D3, ADR-0006). Das Argument „gemischte Generationen in einem Host“ entfällt. +- Die Knoten haben 32 GB RAM. Eine VM pro Rolle würde den RAM mehrfach fest reservieren. + +Deshalb gilt, sobald D2 bestätigt ist: +- **Pro GPU-Knoten eine Ubuntu-24.04-VM** (`gpu-pve44` … `gpu-pve52`, vmid 244–252) mit **allen** Karten des Knotens, 12 GB RAM fest, R580 (`driver_generation: legacy`). Darin laufen die Rollen als Docker-Compose-Dienste. +- Resource Mappings heißen `gpu--` (z. B. `gpu-p2000-pve44`). +- Ausgeschaltete Karten (M4000) werden trotzdem durchgereicht. So braucht ihr Wiedereinsatz nach dem Benchmark keine Änderung an der VM. +- Infra bleibt LXC (`infra`, vmid 101), dazu kommt ein kleiner `ops`-LXC (vmid 100) für Ansible und OpenTofu. +- Die oben genannten RAM-Richtwerte für vLLM-VMs entfallen. Das RAM-Budget je Knoten steht im Aufbauplan. diff --git a/docs/adr/0002-storage-zfs-kein-ceph.md b/docs/adr/0002-storage-zfs-kein-ceph.md index 473c29e..3c27e3b 100644 --- a/docs/adr/0002-storage-zfs-kein-ceph.md +++ b/docs/adr/0002-storage-zfs-kein-ceph.md @@ -1,6 +1,6 @@ # ADR-0002: ZFS lokal + Replikation statt Ceph -- Status: angenommen +- Status: angenommen, ergänzt am 2026-10-13 (Nachtrag D1, entschieden) - Datum: 2026-10-11 ## Kontext @@ -16,3 +16,13 @@ Die Nodes sind mit **1 GbE** verbunden. Jede physische Maschine hat 2–3 SSDs m - Bei einem Node-Ausfall gehen höchstens 15 Min. Daten der Infra-VMs verloren. Postgres sichert sich zusätzlich über WAL-Archivierung bzw. PBS. - Modellwechsel verursachen Downloads (10 GB ≈ 90 s über 1 GbE). Das ist akzeptabel. - Die Speicherbudgets pro Rolle stehen in PLAN.md, Abschnitt 1.4. + +## Nachtrag 2026-10-13: Variante B ohne Neuinstallation (Aufbauplan D1, entschieden am 12.10.) +Die Knoten sind bereits mit ext4 und LVM-thin auf der System-SSD installiert. Entschieden ist **Variante B**: +- Die System-SSD bleibt, wie sie ist (`local` und `local-lvm`). Die GPU-VMs liegen auf `local-lvm`. +- Auf **pve44, pve48 und pve52** wird die zweite SSD (bisher altes Windows) nach ausdrücklicher Freigabe remote zur Einzel-SSD `tank` (`ashift=12`, `lz4`). Auf pve48 bleibt `sdc` unberührt. +- **pve46** (eine Platte) und **pve61** (Kiosk) bekommen kein ZFS. Dort laufen nur Gäste ohne HA (GPU-VM, `ops`). +- HA-Gäste (`infra`) liegen auf `tank` und werden alle 15 Min. auf die anderen `tank`-Knoten repliziert. +- Der ZFS-ARC wird auf 4 GiB begrenzt. +- Einen Spiegel für die Systemplatte gibt es nicht. Fällt eine System-SSD aus, wird der Knoten neu installiert, die HA-Gäste laufen repliziert weiter. +- Variante A (Neuinstallation mit ZFS-RAID1 vor Ort) lässt sich später pro Knoten nachholen. diff --git a/docs/adr/0006-legacy-gpus-llamacpp-tts-auf-ada.md b/docs/adr/0006-legacy-gpus-llamacpp-tts-auf-ada.md index 2a913b3..0ecbd9c 100644 --- a/docs/adr/0006-legacy-gpus-llamacpp-tts-auf-ada.md +++ b/docs/adr/0006-legacy-gpus-llamacpp-tts-auf-ada.md @@ -1,6 +1,6 @@ # ADR-0006: Alte GPUs nur für llama.cpp, M4000 vorerst außer Betrieb, TTS auf Ada und CPU -- Status: angenommen +- Status: angenommen, ergänzt am 2026-10-13 (Nachtrag D3, Bestätigung offen) - Datum: 2026-10-12 ## Kontext @@ -20,3 +20,9 @@ ## Folgen - Bei `gpu-flex` muss sich der Lifecycle die Karte mit TTS teilen. Während des Trainings gehen sowohl `tier1-flex` als auch `tts-de-hq` auf `unavailable`, und LiteLLM fällt auf `tts-de` zurück. - Die tatsächlichen RTF-Werte misst `knaster bench tts` beim Deployment. Sie ersetzen die Schätzungen im Katalog. + +## Nachtrag 2026-10-13: Ada-Rechner bleiben Windows (Aufbauplan D3, Bestätigung offen) +Die beiden RTX 4000 Ada stecken in **GODE-PC-05 und GODE-PC-06** (je eine Karte, 64 GB RAM). Das sind Windows-Arbeitsplätze, tagsüber haben Mitarbeitende Vorrang. Sie laufen schon heute als LLM-Worker (`LocalLLMWorker.ps1`, llama-server mit CUDA). +- Die Slots `tier1-general` und `tier1-flex` lösen künftig über die **Worker-Registry v2** auf einen Worker auf, der gerade online ist. Ist keiner online, geht die Anfrage an tier0 über den PSE-LiteLLM (Aufbauplan Task 6.4). Bis dahin behalten sie in der Registry ihre Engine. +- **vLLM, Multi-LoRA, QLoRA und Qwen3-TTS (`tts-de-hq`)** laufen unter Windows nicht nativ. Sie kommen erst mit einer späteren Entscheidung über WSL2 bzw. nächtliches vLLM (Phase 9). Bis dahin ist `tts-de` (Piper, CPU) der einzige aktive TTS-Slot. +- Im Inventar stehen die Rechner mit `cpu_roles: false`. Das Placement verplant dort nur die GPU und nie Infra- oder CPU-Fallback-Rollen. diff --git a/docs/spec/SPEC-inventory.md b/docs/spec/SPEC-inventory.md index d3fed4a..ea81f69 100644 --- a/docs/spec/SPEC-inventory.md +++ b/docs/spec/SPEC-inventory.md @@ -29,7 +29,7 @@ Deterministisch: - `prefer: largest` wählt die GPU mit dem meisten VRAM (für LLM-Rollen). - `prefer: smallest_fitting` wählt die kleinste passende GPU (für Embedding und Rerank). So bleiben große Karten frei. 3. Rollen mit `share_with: ` teilen sich die GPU (bzw. bei `gpu: false` den Node) einer bereits platzierten Rolle. Ein Beispiel ist TTS auf `gpu-flex`. -4. Rollen mit `gpu: false` bzw. `cpu_fallback: true` werden ohne passende GPU den CPU-Nodes zugewiesen, sortiert nach RAM. +4. Rollen mit `gpu: false` bzw. `cpu_fallback: true` werden ohne passende GPU den CPU-Nodes zugewiesen, sortiert nach RAM. Nodes mit `cpu_roles: false` (Arbeitsplätze, Kiosk) sind davon ausgenommen. Dort wird nur die eigene GPU verplant. 5. Pro zugewiesener Rolle werden die Katalogmodelle gelistet, die passen (Engine, VRAM, CC). 6. Ergebnis: `assignments[{role, node, gpu_index?, models_fitting[]}]` und `unplaced[{role, reason}]`. diff --git a/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md b/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md index eb85949..0341e16 100644 --- a/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md +++ b/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md @@ -138,14 +138,15 @@ Ersetzt PLAN §8 „0 Fundament“ (Teil Inventar) und IMPLEMENTATION „Gemeins **Interfaces:** - Produces: Proxmox-Knoten `pve44`, `pve46`, `pve48`, `pve52`, `pve61`, dazu die Inventar-Knoten `gode-pc-05` und `gode-pc-06` (Ada, ohne Proxmox, nur im Inventar). PCI-Mappings `gpu-m4000-pve44`, `gpu-p2000-pve44`, `gpu-m4000-pve46`, `gpu-p2000-pve46`, `gpu-m4000-pve52`, `gpu-m4000-pve48`, `gpu-p2000-pve48`. GPU-VMs `gpu-pve44` (vmid 244), `gpu-pve46` (246), `gpu-pve48` (248), `gpu-pve52` (252). Infra-LXC `infra` (vmid 101). Ansible-Gruppen `pve_all`, `pve_gpu_hosts`, `pve_kiosk`. -- [ ] **Step 1: Failing test.** In `test_inventory_validate.py` den Test `test_site_knaster_matches_real_nodes` anlegen: +- [x] **Step 1: Failing test.** In `test_inventory_validate.py` den Test `test_site_knaster_matches_real_nodes` anlegen: - Die Inventar-Knoten sind genau `{pve44, pve46, pve48, pve52, pve61, gode-pc-05, gode-pc-06}`. - `proxmox.yaml` enthält keine IP aus `10.20.0.0/24`. - Jedes `pci_mappings[*].node` und jedes `vms[*].node` liegt in `{pve44, pve46, pve48, pve52}`. -- [ ] **Step 2:** `make test` → FAIL (alte Annahmen `pve-ada`, `pve1`–`pve4`). -- [ ] **Step 3:** Site-Dateien umschreiben. Werte für IDs und Pfade aus `lspci -nn` und dem Healthcheck: M4000 `10de:13f1` auf `0000:01:00`, P2000 `10de:1c30` auf `0000:02:00`. Gateway `192.168.100.1`, DNS `192.168.100.120`, keine `vlan_svc`, Gast-IPs aus D5. Die M4000 bleiben `disabled: true` bis zum Benchmark-Tag (Task 3.3). Die Ada-Slots (`tier1-general`, `tier1-flex`, `tts-de-hq`) behalten vorerst ihre Engine. In `registry.yaml` entfallen bei ihnen nur `pve_node` und `vmid`. Die Umstellung auf die Worker-Registry folgt in Task 6.4. -- [ ] **Step 4:** `make test && make validate` → PASS. `knaster placement plan --inventory sites/knaster/inventory.yaml` liefert eine Zuordnung ohne Fehler. -- [ ] **Step 5:** Commit `sites: Inventar und Zielbild an echte Knoten (pve44–pve61) angepasst; ADR-0001/0002/0006 ergänzt`. +- [x] **Step 2:** `make test` → FAIL (alte Annahmen `pve-ada`, `pve1`–`pve4`). +- [x] **Step 3:** Site-Dateien umschreiben. Werte für IDs und Pfade aus `lspci -nn` und dem Healthcheck: M4000 `10de:13f1` auf `0000:01:00`, P2000 `10de:1c30` auf `0000:02:00`. Gateway `192.168.100.1`, DNS `192.168.100.120`, keine `vlan_svc`, Gast-IPs aus D5. Die M4000 bleiben `disabled: true` bis zum Benchmark-Tag (Task 3.3). Die Ada-Slots (`tier1-general`, `tier1-flex`, `tts-de-hq`) behalten vorerst ihre Engine. In `registry.yaml` entfallen bei ihnen nur `pve_node` und `vmid`. Die Umstellung auf die Worker-Registry folgt in Task 6.4. +- [x] **Step 4:** `make test && make validate` → PASS. `knaster placement plan --inventory sites/knaster/inventory.yaml` liefert eine Zuordnung ohne Fehler. +- [x] **Step 5:** Commit `sites: Inventar und Zielbild an echte Knoten (pve44–pve61) angepasst; ADR-0001/0002/0006 ergänzt`. +- Ergebnis (13.10.): Zusätzlich angepasst wurden `platform/proxmox/tofu` (mehrere Karten pro VM, Speicher je Gast, `vlan_svc: null`), `playbooks/site.yml` (`pve-host` nur für `pve_gpu_hosts`), die Replikation in `pve-host` (mehrere Ziele) und `deploy/prometheus/prometheus.yml` (IP-Literale, keine Ada-Ziele). Neues Inventarfeld `cpu_roles: false` für die Ada-Rechner und pve61. Damit verplant das Placement dort keine Infra- und keine CPU-Rollen. Hinweis: Das Placement schlägt `infra` auf pve52 vor, weil dort keine Karte aktiv ist. Laut RAM-Budget steht `infra` auf pve44, so ist es in `proxmox.yaml` eingetragen. Nicht mit `tofu validate` und `ansible-lint` geprüft, beides ist lokal nicht installiert. Das wird im ops-LXC nachgeholt (Task 3.1). --- @@ -234,7 +235,7 @@ Ersetzt GPU-Knoten 3 (Teil Cluster). Mit 5 Stimmen ist **kein QDevice** nötig. **Wo:** remote · **Files:** Modify `platform/proxmox/ansible/inventory.yml`, `platform/proxmox/ansible/playbooks/site.yml`, Create `platform/proxmox/README.md` (Abschnitt ops-LXC) -- [ ] LXC `ops` (Debian 13, vmid 100, auf pve44, IP aus D5) mit `ansible-core`, `opentofu`, `git` und dem Repo-Clone. Ansible läuft nicht auf dem Windows-Laptop. +- [ ] LXC `ops` (Debian 13, vmid 100, auf pve46 laut RAM-Budget, IP aus D5) mit `ansible-core`, `opentofu`, `git` und dem Repo-Clone. Ansible läuft nicht auf dem Windows-Laptop. - [ ] `site.yml`: `pve-host` nur für `pve_gpu_hosts` (pve44, pve46, pve48, pve52). pve61 steht in `pve_kiosk` ohne VFIO und Blacklist. - [ ] Rolle `pve-host` ergänzen: `/etc/modprobe.d/vfio.conf` mit `options vfio-pci ids=` und `softdep nvidia pre: vfio-pci`. Die Liste enthält die Grafik- **und** die Audio-Funktionen der Karten. Die IDs aus `lspci -nn -s 01:00` und `lspci -nn -s 02:00` auf dem Knoten lesen, nicht aus dem Gedächtnis eintragen; bekannt sind bisher nur die VGA-IDs `10de:13f1` und `10de:1c30`. - [ ] `ansible-playbook playbooks/site.yml --limit pve_gpu_hosts --check`, danach der echte Lauf. Knoten **einzeln** neu starten. diff --git a/platform/proxmox/README.md b/platform/proxmox/README.md index 7202c19..34426dd 100644 --- a/platform/proxmox/README.md +++ b/platform/proxmox/README.md @@ -7,8 +7,8 @@ Proxmox-spezifische Schicht (austauschbar, siehe PLAN §7.2). Eingabe ist immer ## Ablauf 1. **Inventar:** auf jedem Node `knaster inventory collect` ausführen und `sites/knaster/inventory.yaml` ersetzen. Danach `knaster placement plan --inventory sites/knaster/inventory.yaml` und die Ergebnisse in `proxmox.yaml` übertragen (PCI-Pfade über `lspci -nn | grep -i nvidia`, IOMMU-Gruppen). -2. **Hosts vorbereiten:** `ansible-playbook playbooks/site.yml --limit pve_hosts`. Setzt IOMMU und VFIO und sperrt die NVIDIA-Treiber auf dem Host. Danach die Hosts neu starten. Der ZFS-Pool `tank` wird **nicht** automatisch angelegt (Datenverlustgefahr), die Rolle gibt dafür den Befehl aus. -3. **Template:** pro Node ein Ubuntu-24.04-Cloud-Image als Template mit VMID 9000. +2. **Hosts vorbereiten:** `ansible-playbook playbooks/site.yml --limit pve_gpu_hosts`. Setzt IOMMU und VFIO und sperrt die NVIDIA-Treiber, nur auf GPU-Knoten (pve61 bleibt unberührt, Kiosk). Danach die Hosts neu starten. Der ZFS-Pool `tank` wird **nicht** automatisch angelegt (Datenverlustgefahr), die Rolle gibt dafür den Befehl aus. +3. **Template:** pro GPU-Knoten ein Ubuntu-24.04-Cloud-Image als Template mit VMID 9000. 4. **VMs/LXC:** `cd tofu && tofu init && tofu plan -var proxmox_api_token=...` und danach `tofu apply`. 5. **Gäste:** `ansible-playbook playbooks/site.yml --limit gpu_vms,infra`. Installiert die Treiber pro Generation gepinnt (legacy/modern), das Container-Toolkit, Docker und den Xid-Exporter. 6. **Dienste:** `deploy/compose/` auf den jeweiligen Gästen starten. diff --git a/platform/proxmox/ansible/inventory.yml b/platform/proxmox/ansible/inventory.yml index f68999c..e1e52e9 100644 --- a/platform/proxmox/ansible/inventory.yml +++ b/platform/proxmox/ansible/inventory.yml @@ -1,29 +1,40 @@ -# ANNAHME bis Deployment – Hosts und IPs aus sites/knaster/proxmox.yaml +# Hosts und IPs aus sites/knaster/proxmox.yaml (Aufbauplan Task 0.4). Ansible läuft im ops-LXC (Task 3.1). +# Gast-IPs: ANNAHME bis Task 0.2 (DHCP-Ausschluss .220–.239). all: vars: knaster_site: knaster children: - pve_hosts: - hosts: - pve-ada: {} - pve1: {} - pve2: {} - pve3: {} - pve4: {} + pve_all: + children: + pve_gpu_hosts: + hosts: + pve44: {ansible_host: 192.168.100.210} + pve46: {ansible_host: 192.168.100.209} + pve48: {ansible_host: 192.168.100.212} + pve52: {ansible_host: 192.168.100.207} + pve_kiosk: # keine VFIO-/NVIDIA-Sperre, iGPU bleibt beim Host (Kiosk) + hosts: + pve61: {ansible_host: 192.168.100.214} vars: ansible_user: root gpu_vms: hosts: - gpu-main: {ansible_host: 10.20.0.201, driver_generation: modern, knaster_role: gpu-vllm} - gpu-flex: {ansible_host: 10.20.0.202, driver_generation: modern, knaster_role: gpu-vllm} - gpu-embed-1: {ansible_host: 10.20.0.221, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: embed} - gpu-embed-2: {ansible_host: 10.20.0.222, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: embed} - gpu-small: {ansible_host: 10.20.0.231, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: chat} + gpu-pve44: {ansible_host: 192.168.100.224} + gpu-pve46: {ansible_host: 192.168.100.226} + gpu-pve48: {ansible_host: 192.168.100.228} + gpu-pve52: {ansible_host: 192.168.100.232} vars: ansible_user: knaster ansible_become: true + driver_generation: legacy # R580, Maxwell/Pascal + knaster_role: gpu-llamacpp infra: hosts: - infra: {ansible_host: 10.20.0.101} + infra: {ansible_host: 192.168.100.221} + vars: + ansible_user: root + ops: + hosts: + ops: {ansible_host: 192.168.100.220} vars: ansible_user: root diff --git a/platform/proxmox/ansible/playbooks/site.yml b/platform/proxmox/ansible/playbooks/site.yml index e41266e..cc35419 100644 --- a/platform/proxmox/ansible/playbooks/site.yml +++ b/platform/proxmox/ansible/playbooks/site.yml @@ -1,6 +1,7 @@ -# Reihenfolge: Hosts vorbereiten → (tofu apply) → GPU-VMs → Infra -- name: Proxmox-Hosts (IOMMU, VFIO, ZFS-Replikation) - hosts: pve_hosts +# Reihenfolge: GPU-Knoten vorbereiten → (tofu apply) → GPU-VMs → Infra +# pve61 (pve_kiosk) bekommt bewusst keine Host-Rolle: keine VFIO-Bindung, keine NVIDIA-Sperre (Review Focus 5). +- name: Proxmox-GPU-Knoten (IOMMU, VFIO, ZFS-Replikation) + hosts: pve_gpu_hosts roles: [pve-host] - name: GPU-VMs (Treiber gepinnt, Container-Toolkit, Docker, Xid-Exporter) diff --git a/platform/proxmox/ansible/roles/pve-host/tasks/main.yml b/platform/proxmox/ansible/roles/pve-host/tasks/main.yml index 70246b6..8aed32a 100644 --- a/platform/proxmox/ansible/roles/pve-host/tasks/main.yml +++ b/platform/proxmox/ansible/roles/pve-host/tasks/main.yml @@ -50,21 +50,28 @@ - name: Hinweis, falls tank fehlt (Pool wird bewusst NICHT automatisch angelegt – Datenverlustgefahr) ansible.builtin.debug: - msg: "Auf {{ inventory_hostname }} fehlt der ZFS-Pool 'tank'. Anlegen z. B.: zpool create -o ashift=12 tank mirror /dev/disk/by-id/ /dev/disk/by-id/ && pvesm add zfspool tank -pool tank" - when: tank.rc != 0 + msg: >- + Auf {{ inventory_hostname }} fehlt der ZFS-Pool 'tank'. Nach Freigabe (D1) und Prüfung mit + `lsblk -o NAME,SIZE,FSTYPE,LABEL /dev/sdb`: zpool create -o ashift=12 -O compression=lz4 -O atime=off + tank /dev/disk/by-id/ && pvesm add zfspool tank --pool tank --content images,rootdir + when: + - tank.rc != 0 + - knaster_pve.nodes[inventory_hostname].zfs_tank | default(false) - name: Bestehende Replikationsjobs lesen ansible.builtin.command: pvesr list register: pvesr_jobs changed_when: false -- name: ZFS-Replikation für Infra-Gäste +# Ein Job pro Ziel: -0, -1, … (Reihenfolge aus replicate_to) +- name: ZFS-Replikation für HA-Gäste ansible.builtin.command: > - pvesr create-local-job {{ item.value.vmid }}-0 {{ item.value.replicate_to }} - --schedule '{{ knaster_pve.replication_schedule }}' --comment 'knaster {{ item.key }}' - loop: "{{ knaster_pve.containers | dict2items }}" + pvesr create-local-job {{ item.0.value.vmid }}-{{ idx }} {{ item.1 }} + --schedule '{{ knaster_pve.replication_schedule }}' --comment 'knaster {{ item.0.key }}' + loop: "{{ knaster_pve.containers | dict2items | subelements('value.replicate_to', skip_missing=true) }}" + vars: + idx: "{{ item.0.value.replicate_to.index(item.1) }}" when: - - item.value.node == inventory_hostname - - item.value.replicate_to is defined - - (item.value.vmid | string) + '-0' not in pvesr_jobs.stdout + - item.0.value.node == inventory_hostname + - ((item.0.value.vmid | string) + '-' + (idx | string)) not in pvesr_jobs.stdout changed_when: true diff --git a/platform/proxmox/tofu/main.tf b/platform/proxmox/tofu/main.tf index a8cc40f..ce3d473 100644 --- a/platform/proxmox/tofu/main.tf +++ b/platform/proxmox/tofu/main.tf @@ -41,12 +41,12 @@ resource "proxmox_virtual_environment_vm" "gpu" { } efi_disk { - datastore_id = local.site.cluster.storage_vm + datastore_id = try(each.value.storage, local.site.cluster.storage_default) type = "4m" } disk { - datastore_id = local.site.cluster.storage_vm + datastore_id = try(each.value.storage, local.site.cluster.storage_default) interface = "scsi0" size = each.value.disk_gb discard = "on" @@ -55,7 +55,7 @@ resource "proxmox_virtual_environment_vm" "gpu" { # Modelle: eigenes Disk-Image, nicht repliziert, nicht gesichert (ADR-0002) disk { - datastore_id = local.site.cluster.storage_vm + datastore_id = try(each.value.storage, local.site.cluster.storage_default) interface = "scsi1" size = each.value.models_disk_gb discard = "on" @@ -64,20 +64,24 @@ resource "proxmox_virtual_environment_vm" "gpu" { replicate = false } - hostpci { - device = "hostpci0" - mapping = proxmox_virtual_environment_hardware_mapping_pci.gpu[each.value.gpu].name - pcie = true - rombar = true + # Alle Karten des Knotens in eine VM (D2); Reihenfolge = Index in der VM + dynamic "hostpci" { + for_each = each.value.gpus + content { + device = "hostpci${hostpci.key}" + mapping = proxmox_virtual_environment_hardware_mapping_pci.gpu[hostpci.value].name + pcie = true + rombar = true + } } network_device { bridge = local.site.cluster.bridge - vlan_id = local.site.cluster.vlan_svc + vlan_id = local.site.cluster.vlan_svc # null = flaches Netz } initialization { - datastore_id = local.site.cluster.storage_vm + datastore_id = try(each.value.storage, local.site.cluster.storage_default) ip_config { ipv4 { address = each.value.ip @@ -94,16 +98,16 @@ resource "proxmox_virtual_environment_vm" "gpu" { } } -resource "proxmox_virtual_environment_container" "infra" { +resource "proxmox_virtual_environment_container" "lxc" { for_each = local.site.containers node_name = each.value.node vm_id = each.value.vmid - tags = ["knaster", "infra"] + tags = ["knaster", each.key] unprivileged = true start_on_boot = true features { - nesting = true # Docker im LXC + nesting = try(each.value.nesting, true) # Docker im LXC } cpu { @@ -115,12 +119,12 @@ resource "proxmox_virtual_environment_container" "infra" { } disk { - datastore_id = local.site.cluster.storage_vm + datastore_id = try(each.value.storage, local.site.cluster.storage_default) size = each.value.disk_gb } operating_system { - template_file_id = "${local.site.cluster.storage_iso}:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst" + template_file_id = local.site.cluster.lxc_template type = "debian" } @@ -132,6 +136,9 @@ resource "proxmox_virtual_environment_container" "infra" { initialization { hostname = each.key + dns { + servers = [local.site.cluster.dns] + } ip_config { ipv4 { address = each.value.ip @@ -149,5 +156,5 @@ resource "proxmox_virtual_environment_haresource" "infra" { resource_id = "ct:${each.value.vmid}" state = "started" comment = "knaster ${each.key}" - depends_on = [proxmox_virtual_environment_container.infra] + depends_on = [proxmox_virtual_environment_container.lxc] } diff --git a/platform/proxmox/tofu/outputs.tf b/platform/proxmox/tofu/outputs.tf index cc906b2..04ac9a7 100644 --- a/platform/proxmox/tofu/outputs.tf +++ b/platform/proxmox/tofu/outputs.tf @@ -2,6 +2,6 @@ output "gpu_vms" { value = { for k, v in proxmox_virtual_environment_vm.gpu : k => { node = v.node_name, vmid = v.vm_id } } } -output "infra_containers" { - value = { for k, v in proxmox_virtual_environment_container.infra : k => { node = v.node_name, vmid = v.vm_id } } +output "containers" { + value = { for k, v in proxmox_virtual_environment_container.lxc : k => { node = v.node_name, vmid = v.vm_id } } } diff --git a/sites/knaster/inventory.yaml b/sites/knaster/inventory.yaml index 8d63107..6c15972 100644 --- a/sites/knaster/inventory.yaml +++ b/sites/knaster/inventory.yaml @@ -1,38 +1,55 @@ -# Inventar des Clusters "knaster". -# ANNAHME: Die Verteilung der GPUs und SSDs auf die Hosts ist noch nicht erfasst. Bitte beim -# gemeinsamen Deployment mit `knaster inventory collect` auf jedem Node ersetzen. +# Inventar des Clusters "knaster" – Stand 12.10.2026 (Aufbauplan docs/superpowers/plans/2026-10-12-…, Ausgangslage). +# Quelle: Healthcheck 08.10. + Inventar pve48 12.10. + Angaben des Nutzers. Beim Bootstrap mit +# `knaster inventory collect` gegenprüfen; Abweichungen hier eintragen. +# Speicher: Variante B (D1) – sda = System (local + local-lvm), sdb wird nach Freigabe zum ZFS-Pool `tank`. nodes: - - name: pve-ada - cpu: {model: "unbekannt", cores: 6, threads: 12} + - name: pve44 # 192.168.100.210, ThinkStation P310 + cpu: {model: "Intel Core i7-6700", cores: 4, threads: 8} + memory_gb: 32 + disks: [{name: sda, size_gb: 256}, {name: sdb, size_gb: 256}] # sdb: altes Windows (NTFS) → tank + gpus: + - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis Benchmark-Tag (Task 3.3, ADR-0006)"} + - {index: 1, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} + - name: pve46 # 192.168.100.209, ThinkStation P310, Legacy-Boot, eine Platte (kein ZFS) + cpu: {model: "Intel Core i7-6700", cores: 4, threads: 8} + memory_gb: 32 + disks: [{name: sda, size_gb: 256}] + gpus: + - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis Benchmark-Tag (Task 3.3, ADR-0006)"} + - {index: 1, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} + - name: pve48 # 192.168.100.212, ThinkStation P310, Legacy-Boot + cpu: {model: "Intel Core i7-6700", cores: 4, threads: 8} + memory_gb: 32 + disks: [{name: sda, size_gb: 256}, {name: sdb, size_gb: 256}, {name: sdc, size_gb: 128}] # sdb → tank, sdc bleibt unberührt + gpus: + - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis Benchmark-Tag (Task 3.3, ADR-0006)"} + - {index: 1, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} + - name: pve52 # 192.168.100.207, ThinkStation P320, nur M4000 verbaut + cpu: {model: "Intel Core i7-7700", cores: 4, threads: 8} + memory_gb: 32 + disks: [{name: sda, size_gb: 256}, {name: sdb, size_gb: 256}] # sdb: altes Windows (BitLocker) → tank + gpus: + - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis Benchmark-Tag (Task 3.3, ADR-0006)"} + - name: pve61 # 192.168.100.214, Surface Book, LAN über USB-NIC im Dock – nur Kiosk + cpu: {model: "Intel Core i5-8350U", cores: 4, threads: 8} + memory_gb: 8 + disks: [{name: nvme0n1, size_gb: 256}] + cpu_roles: false + labels: {platform: proxmox, purpose: kiosk} + + # Außerhalb des Proxmox-Clusters (D3): Windows-Arbeitsplätze, tagsüber haben Mitarbeitende Vorrang. + # Leistung kommt über die Worker-Registry v2 (LocalLLMWorker.ps1, llama-server). Nur die eigene GPU wird verplant. + - name: gode-pc-05 + cpu: {model: "unbekannt (Task 0.1 e2)", cores: 0, threads: 0} memory_gb: 64 - disks: [{name: sda, size_gb: 256}, {name: sdb, size_gb: 256}] + cpu_roles: false + labels: {platform: windows, worker: worker-registry-v2} gpus: - {index: 0, name: "NVIDIA RTX 4000 Ada Generation", memory_gb: 20, compute_cap: 8.9} - - {index: 1, name: "NVIDIA RTX 4000 Ada Generation", memory_gb: 20, compute_cap: 8.9} - - name: pve1 - cpu: {model: "Intel Core i7 (6. Gen) 3.4 GHz", cores: 4, threads: 8} - memory_gb: 32 - disks: [{name: sda, size_gb: 128}, {name: sdb, size_gb: 256}] + - name: gode-pc-06 + cpu: {model: "unbekannt (Task 0.1 e2)", cores: 0, threads: 0} + memory_gb: 64 + cpu_roles: false + labels: {platform: windows, worker: worker-registry-v2} gpus: - - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis llama-bench + Strommessung (ADR-0006)"} - - {index: 1, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis llama-bench + Strommessung (ADR-0006)"} - - name: pve2 - cpu: {model: "Intel Core i7 (7. Gen) 3.6 GHz", cores: 4, threads: 8} - memory_gb: 32 - disks: [{name: sda, size_gb: 128}, {name: sdb, size_gb: 256}, {name: sdc, size_gb: 256}] - gpus: - - {index: 0, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis llama-bench + Strommessung (ADR-0006)"} - - {index: 1, name: "Quadro M4000", memory_gb: 8, compute_cap: 5.2, disabled: true, note: "außer Betrieb bis llama-bench + Strommessung (ADR-0006)"} - - name: pve3 - cpu: {model: "Intel Core i7 (7. Gen) 3.6 GHz", cores: 4, threads: 8} - memory_gb: 32 - disks: [{name: sda, size_gb: 128}, {name: sdb, size_gb: 256}] - gpus: - - {index: 0, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} - - {index: 1, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} - - name: pve4 - cpu: {model: "Intel Core i7 (7. Gen) 3.6 GHz", cores: 4, threads: 8} - memory_gb: 32 - disks: [{name: sda, size_gb: 128}, {name: sdb, size_gb: 256}, {name: sdc, size_gb: 256}] - gpus: - - {index: 0, name: "Quadro P2000", memory_gb: 5, compute_cap: 6.1} + - {index: 0, name: "NVIDIA RTX 4000 Ada Generation", memory_gb: 20, compute_cap: 8.9} diff --git a/sites/knaster/proxmox.yaml b/sites/knaster/proxmox.yaml index bd1a56a..eefeac2 100644 --- a/sites/knaster/proxmox.yaml +++ b/sites/knaster/proxmox.yaml @@ -1,31 +1,44 @@ # Proxmox-Zielbild für "knaster" (Eingabe für platform/proxmox/tofu und ansible). -# ANNAHMEN (Hosts, VM-IDs, IPs, PCI-Adressen) bis zum Inventarlauf – siehe docs/IMPLEMENTATION.md. +# Grundlage: Aufbauplan docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md (D1 B, D2, D5). +# ANNAHME bis Task 0.2: Gast-IPs aus dem vorgeschlagenen Block .220–.239 (DHCP-Ausschluss noch nicht geprüft). +# ANNAHME bis Task 3.1: PCI-Pfade/IOMMU-Gruppen wie auf pve48 geprüft; auf jedem Knoten mit `lspci -nn` bestätigen. cluster: - endpoint: https://pve1:8006/ - storage_vm: tank # ZFS-Pool auf SSD 2+3 (ADR-0002) + endpoint: https://192.168.100.210:8006/ # pve44, IP-Literal (Global Constraints) + storage_default: local-lvm # D1 B: GPU-VMs auf der System-SSD; HA-Gäste auf `tank` (pve44/48/52) storage_iso: local - template_vmid: 9000 # Ubuntu 24.04 Cloud-Image-Template (pro Node vorhanden) + template_vmid: 9000 # Ubuntu 24.04 Cloud-Image-Template (pro GPU-Knoten vorhanden) + lxc_template: "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" # ANNAHME: Dateiname nach `pveam update` prüfen bridge: vmbr0 - vlan_svc: 20 - gateway: 10.20.0.1 - dns: 10.20.0.1 + vlan_svc: null # flaches Netz, vorerst kein VLAN (D5) + gateway: 192.168.100.1 + dns: 192.168.100.120 ssh_public_key_file: ~/.ssh/id_ed25519.pub -pci_mappings: # Proxmox Resource Mappings (Datacenter → Resource Mappings → PCI) - gpu-ada-1: {node: pve-ada, path: "0000:01:00", id: "10de:27b2", subsystem_id: "", iommu_group: 0} - gpu-ada-2: {node: pve-ada, path: "0000:02:00", id: "10de:27b2", subsystem_id: "", iommu_group: 0} - gpu-p2000-1: {node: pve3, path: "0000:01:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0} - gpu-p2000-2: {node: pve3, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0} - gpu-p2000-3: {node: pve4, path: "0000:01:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0} +nodes: # Proxmox-Knoten (Cluster-Mitglieder), Ansible-Gruppen siehe platform/proxmox/ansible + pve44: {ip: 192.168.100.210, gpu_host: true, zfs_tank: true} + pve46: {ip: 192.168.100.209, gpu_host: true, zfs_tank: false} # eine Platte + pve48: {ip: 192.168.100.212, gpu_host: true, zfs_tank: true} + pve52: {ip: 192.168.100.207, gpu_host: true, zfs_tank: true} + pve61: {ip: 192.168.100.214, gpu_host: false, zfs_tank: false} # Surface, Kiosk, keine Gäste -vms: # GPU-Rollen als VM (ADR-0001) - gpu-main: {node: pve-ada, vmid: 201, cores: 6, memory_mb: 28672, disk_gb: 40, models_disk_gb: 100, gpu: gpu-ada-1, ip: 10.20.0.201/24, role: gpu-vllm, driver_generation: modern} - gpu-flex: {node: pve-ada, vmid: 202, cores: 6, memory_mb: 28672, disk_gb: 40, models_disk_gb: 100, gpu: gpu-ada-2, ip: 10.20.0.202/24, role: gpu-vllm, driver_generation: modern} - gpu-embed-1: {node: pve3, vmid: 221, cores: 2, memory_mb: 6144, disk_gb: 30, models_disk_gb: 20, gpu: gpu-p2000-1, ip: 10.20.0.221/24, role: gpu-llamacpp, profile: embed, driver_generation: legacy} - gpu-embed-2: {node: pve4, vmid: 222, cores: 2, memory_mb: 6144, disk_gb: 30, models_disk_gb: 20, gpu: gpu-p2000-3, ip: 10.20.0.222/24, role: gpu-llamacpp, profile: embed, driver_generation: legacy} - gpu-small: {node: pve3, vmid: 231, cores: 2, memory_mb: 8192, disk_gb: 30, models_disk_gb: 20, gpu: gpu-p2000-2, ip: 10.20.0.231/24, role: gpu-llamacpp, profile: chat, driver_generation: legacy} +pci_mappings: # Datacenter → Resource Mappings → PCI; Pfad ohne Funktion = Grafik + Audio + gpu-m4000-pve44: {node: pve44, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1} + gpu-p2000-pve44: {node: pve44, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 8} + gpu-m4000-pve46: {node: pve46, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1} + gpu-p2000-pve46: {node: pve46, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 8} + gpu-m4000-pve48: {node: pve48, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1} + gpu-p2000-pve48: {node: pve48, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 8} + gpu-m4000-pve52: {node: pve52, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1} -containers: # Infrastruktur als LXC (ADR-0001), HA + ZFS-Replikation (ADR-0002) - infra: {node: pve1, vmid: 101, cores: 4, memory_mb: 12288, disk_gb: 60, ip: 10.20.0.101/24, ha: true, replicate_to: pve2} +vms: # eine GPU-VM pro Knoten mit allen Karten (D2), 12 GB fest (RAM-Budget) + gpu-pve44: {node: pve44, vmid: 244, cores: 4, memory_mb: 12288, disk_gb: 40, models_disk_gb: 60, gpus: [gpu-m4000-pve44, gpu-p2000-pve44], ip: 192.168.100.224/24, role: gpu-llamacpp, driver_generation: legacy} + gpu-pve46: {node: pve46, vmid: 246, cores: 4, memory_mb: 12288, disk_gb: 40, models_disk_gb: 60, gpus: [gpu-m4000-pve46, gpu-p2000-pve46], ip: 192.168.100.226/24, role: gpu-llamacpp, driver_generation: legacy} + gpu-pve48: {node: pve48, vmid: 248, cores: 4, memory_mb: 12288, disk_gb: 40, models_disk_gb: 60, gpus: [gpu-m4000-pve48, gpu-p2000-pve48], ip: 192.168.100.228/24, role: gpu-llamacpp, driver_generation: legacy} + gpu-pve52: {node: pve52, vmid: 252, cores: 4, memory_mb: 12288, disk_gb: 40, models_disk_gb: 60, gpus: [gpu-m4000-pve52], ip: 192.168.100.232/24, role: gpu-llamacpp, driver_generation: legacy} +containers: # LXC (Container zuerst); HA + Replikation nur auf `tank`-Knoten (Task 2.2) + ops: {node: pve46, vmid: 100, cores: 2, memory_mb: 1024, disk_gb: 16, ip: 192.168.100.220/24, storage: local-lvm, nesting: false} # Ansible/OpenTofu, kein HA (RAM-Budget) + infra: {node: pve44, vmid: 101, cores: 4, memory_mb: 8192, disk_gb: 60, ip: 192.168.100.221/24, storage: tank, nesting: true, ha: true, replicate_to: [pve52, pve48]} + +# Später, eigene Tasks: pbs (VM 110, pve52, Task 5.1, .222), grp-test (LXC, pve48 auf tank, Phase 8, .223). replication_schedule: "*/15" diff --git a/sites/knaster/registry.yaml b/sites/knaster/registry.yaml index d34751d..662b22d 100644 --- a/sites/knaster/registry.yaml +++ b/sites/knaster/registry.yaml @@ -1,6 +1,9 @@ # Registry für den Cluster "knaster" (SPEC-core §1). # Clients sprechen nur Slot-Namen an; litellm_model ist der Alias in deploy/litellm/config.yaml. # ops: Labels für den Healer (Portainer-Endpoint, Container, Proxmox-Node, VM-ID). +# Stand Aufbauplan Task 0.4: P2000-Slots in den GPU-VMs gpu-pve44/46/48 (Zuordnung ANNAHME bis Benchmark-Tag, +# Task 3.3). Ada-Slots laufen auf Windows-Arbeitsplätzen (D3) ohne Proxmox; Umstellung auf die +# Worker-Registry v2 folgt in Task 6.4, bis dahin behalten sie ihre Engine. slots: tier0-strong: tier: 0 @@ -15,37 +18,37 @@ slots: tier: 1 device: gpu engine: vllm - node: gpu-main # RTX 4000 Ada #1 + node: gpu-main # RTX 4000 Ada, GODE-PC-05 (Windows, D3) litellm_model: tier1-general base_model: qwen3-14b-awq # Startkandidat, Entscheidung per Kalibrierungslauf max_context: 32768 effective_context: 16384 supports_lora: true fallbacks: [tier1-flex] - ops: {docker_host: gpu-main, container: vllm, pve_node: pve-ada, vmid: "201"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-main, container: vllm} # kein Proxmox (D3); bis Task 6.4 tier1-flex: tier: 1 device: gpu engine: vllm - node: gpu-flex # RTX 4000 Ada #2 (nachts Training → unavailable) + node: gpu-flex # RTX 4000 Ada, GODE-PC-06 (Windows, D3) litellm_model: tier1-flex base_model: qwen3-14b-awq max_context: 32768 effective_context: 16384 supports_lora: true - ops: {docker_host: gpu-flex, container: vllm, pve_node: pve-ada, vmid: "202"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-flex, container: vllm} # kein Proxmox (D3); bis Task 6.4 verifier-small: tier: 1 device: gpu engine: llamacpp - node: gpu-small # Quadro M4000 (Nutzen per Benchmark prüfen) + node: gpu-small # Quadro P2000 in gpu-pve48 litellm_model: verifier-small base_model: qwen3-4b-q4km max_context: 8192 effective_context: 6144 - ops: {docker_host: gpu-small, container: llamacpp-chat, pve_node: pve3, vmid: "231"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-pve48, container: llamacpp-chat, pve_node: pve48, vmid: "248"} # ANNAHME bis Task 3.3 signature-extractor: tier: 1 @@ -56,18 +59,18 @@ slots: base_model: qwen3-4b-q4km max_context: 8192 effective_context: 6144 - ops: {docker_host: gpu-small, container: llamacpp-chat, pve_node: pve3, vmid: "231"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-pve48, container: llamacpp-chat, pve_node: pve48, vmid: "248"} # ANNAHME bis Task 3.3 embed-default: tier: 1 device: gpu engine: llamacpp - node: gpu-embed # Quadro P2000 + node: gpu-embed # Quadro P2000, redundant in gpu-pve44 und gpu-pve46 litellm_model: embed-default base_model: qwen3-embedding-0.6b-q8 max_context: 8192 kind: embedding - ops: {docker_host: gpu-embed-1, container: llamacpp-embed, pve_node: pve3, vmid: "221"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-pve44, container: llamacpp-embed, pve_node: pve44, vmid: "244"} # ANNAHME bis Task 3.3 rerank-default: tier: 1 @@ -78,7 +81,7 @@ slots: base_model: bge-reranker-v2-m3-q8 max_context: 8192 kind: rerank - ops: {docker_host: gpu-embed-1, container: llamacpp-rerank, pve_node: pve3, vmid: "221"} # VM-IDs/Hosts: ANNAHME bis Deployment + ops: {docker_host: gpu-pve44, container: llamacpp-rerank, pve_node: pve44, vmid: "244"} # ANNAHME bis Task 3.3 # --- Sprachausgabe (ADR-0006) --- tts-de: @@ -90,17 +93,17 @@ slots: base_model: piper-de-thorsten-medium max_context: 5000 # Zeichen pro Anfrage (TTS-Dienst-Limit) kind: tts - ops: {docker_host: infra, container: tts-piper, pve_node: pve1, vmid: "101"} # ANNAHME bis Deployment + ops: {docker_host: infra, container: tts-piper, pve_node: pve44, vmid: "101"} # HA: kann nach pve52/pve48 wandern tts-de-hq: tier: 1 device: gpu engine: pytorch - node: gpu-flex # teilt sich die Ada mit tier1-flex; während Training unavailable + node: gpu-flex # teilt sich die Ada mit tier1-flex; unter Windows erst ab Phase 9 (WSL2, D3) litellm_model: tts-de-hq base_model: qwen3-tts-1.7b max_context: 5000 kind: tts - ops: {docker_host: gpu-flex, container: qwen-tts, pve_node: pve-ada, vmid: "202"} # ANNAHME bis Deployment + ops: {docker_host: gpu-flex, container: qwen-tts} # kein Proxmox (D3) adapters: []