sites: Inventar und Zielbild an echte Knoten (pve44–pve61) angepasst; ADR-0001/0002/0006 ergänzt
ci / test (push) Successful in 25s
ci / test (push) Successful in 25s
Aufbauplan Task 0.4: - inventory.yaml: pve44/46/48/52/61 und GODE-PC-05/06 (Ada, Windows, cpu_roles: false) - proxmox.yaml: eine GPU-VM pro Knoten mit allen Karten (D2), local-lvm/tank (D1 B), 192.168.100.0/24 ohne VLAN, Gast-IPs .220–.239 (ANNAHME bis Task 0.2) - registry.yaml/prometheus.yml: ops-Labels auf gpu-pve44/46/48, Ada ohne pve_node/vmid - tofu: dynamische hostpci, Speicher je Gast; Ansible: pve_gpu_hosts/pve_kiosk, Replikation auf mehrere Ziele - Placement: cpu_roles=false schließt Arbeitsplätze/Kiosk von CPU-Rollen aus - PLAN §1.3/§1.4/§9 und ADR-Nachträge (D1 entschieden, D2/D3 Bestätigung offen) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -182,7 +182,7 @@ def plan(nodes: list[NodeProfile], roles: list[RoleSpec], models: list[ModelSpec
|
|||||||
missing = role.count - placed
|
missing = role.count - placed
|
||||||
if missing and (not role.needs.gpu or role.cpu_fallback):
|
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"
|
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
|
# bevorzugt Nodes ohne GPU-Rollen, dann wenig belastete, dann viel RAM
|
||||||
gpu_roles = {a.node for a in result.assignments if a.mode == "gpu"}
|
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))
|
candidates.sort(key=lambda n: (n.name in gpu_roles, cpu_load[n.name], -n.memory_gb, n.name))
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class NodeProfile(BaseModel):
|
|||||||
disks: list[Disk] = Field(default_factory=list)
|
disks: list[Disk] = Field(default_factory=list)
|
||||||
gpus: list[GPU] = Field(default_factory=list)
|
gpus: list[GPU] = Field(default_factory=list)
|
||||||
labels: dict[str, str] = Field(default_factory=dict)
|
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",
|
NVIDIA_SMI_QUERY = ["nvidia-smi", "--query-gpu=index,name,memory.total,compute_cap,driver_version",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def test_healer_labels_available_on_alerts():
|
|||||||
"""Labels, die Healer-Regeln brauchen, müssen von Metrik oder Scrape-Target kommen."""
|
"""Labels, die Healer-Regeln brauchen, müssen von Metrik oder Scrape-Target kommen."""
|
||||||
prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text())
|
prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text())
|
||||||
jobs = {j["job_name"]: j for j in prom["scrape_configs"]}
|
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"]:
|
for sc in jobs[job]["static_configs"]:
|
||||||
assert {"slot", "pve_node", "vmid"} <= set(sc["labels"]), (job, sc)
|
assert {"slot", "pve_node", "vmid"} <= set(sc["labels"]), (job, sc)
|
||||||
from knaster.canary import OPS_LABELS
|
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():
|
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())
|
reg = yaml.safe_load((REPO_ROOT / "sites/knaster/registry.yaml").read_text())
|
||||||
prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text())
|
prom = yaml.safe_load((DEPLOY / "prometheus/prometheus.yml").read_text())
|
||||||
vllm = {sc["labels"]["slot"]: sc["labels"] for sc in
|
first: dict[str, dict] = {}
|
||||||
next(j for j in prom["scrape_configs"] if j["job_name"] == "vllm")["static_configs"]}
|
for sc in next(j for j in prom["scrape_configs"] if j["job_name"] == "llamacpp")["static_configs"]:
|
||||||
for slot, labels in vllm.items():
|
first.setdefault(sc["labels"]["slot"], sc["labels"])
|
||||||
|
assert first
|
||||||
|
for slot, labels in first.items():
|
||||||
ops = reg["slots"][slot]["ops"]
|
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"))
|
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())
|
reg = yaml.safe_load((REPO_ROOT / "sites/knaster/registry.yaml").read_text())
|
||||||
vms = site["vms"]
|
vms = site["vms"]
|
||||||
for vm in vms.values():
|
for vm in vms.values():
|
||||||
assert vm["gpu"] in site["pci_mappings"]
|
for gpu in vm["gpus"]:
|
||||||
assert site["pci_mappings"][vm["gpu"]]["node"] == vm["node"] # Mapping liegt auf dem Host der VM
|
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"}
|
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 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()}
|
by_vmid = {str(vm["vmid"]): (name, vm) for name, vm in {**vms, **site["containers"]}.items()}
|
||||||
for slot, spec in reg["slots"].items():
|
for slot, spec in reg["slots"].items():
|
||||||
ops = spec.get("ops")
|
ops = spec.get("ops") or {}
|
||||||
if not ops:
|
if "vmid" not in ops: # z. B. Ada-Worker unter Windows (D3), kein Proxmox-Gast
|
||||||
|
assert "pve_node" not in ops, slot
|
||||||
continue
|
continue
|
||||||
name, vm = by_vmid[ops["vmid"]]
|
name, vm = by_vmid[ops["vmid"]]
|
||||||
assert vm["node"] == ops["pve_node"], slot
|
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()]
|
vmids = [str(v["vmid"]) for v in vms.values()] + [str(c["vmid"]) for c in site["containers"].values()]
|
||||||
assert len(vmids) == len(set(vmids))
|
assert len(vmids) == len(set(vmids))
|
||||||
# vLLM nur auf modernen Karten
|
# vLLM nur auf modernen Karten
|
||||||
|
|||||||
@@ -28,29 +28,70 @@ def test_parsers():
|
|||||||
assert disks[0].size_gb == 256.1 and not disks[0].rotational
|
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():
|
def test_placement_knaster():
|
||||||
roles, models = load_catalog(REPO_ROOT / "catalog")
|
roles, models = load_catalog(REPO_ROOT / "catalog")
|
||||||
p = plan(load_nodes(REPO_ROOT / "sites/knaster/inventory.yaml"), roles, models)
|
p = plan(load_nodes(REPO_ROOT / "sites/knaster/inventory.yaml"), roles, models)
|
||||||
by_role = {}
|
by_role = {}
|
||||||
for a in p.assignments:
|
for a in p.assignments:
|
||||||
by_role.setdefault(a.role, []).append(a)
|
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 by_role["gpu-main"][0].gpu_name.startswith("NVIDIA RTX 4000 Ada")
|
||||||
assert "qwen3-14b-awq" in by_role["gpu-main"][0].models_fitting
|
assert "qwen3-14b-awq" in by_role["gpu-main"][0].models_fitting
|
||||||
embed_nodes = {a.node for a in by_role["gpu-embed"]}
|
embed_nodes = {a.node for a in by_role["gpu-embed"]}
|
||||||
assert len(embed_nodes) == 2 # Anti-Affinität
|
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 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
|
assert not p.unplaced
|
||||||
# M4000 bewusst außer Betrieb (ADR-0006): weder frei noch belegt
|
# M4000 bewusst außer Betrieb (ADR-0006): weder frei noch belegt
|
||||||
assert p.free_gpus == []
|
assert p.free_gpus == []
|
||||||
assert len(p.disabled_gpus) == 4 and all("M4000" in g for g in p.disabled_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")}
|
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 "qwen3-tts-1.7b" in tts["tts-hq"].models_fitting
|
||||||
assert tts["tts-cpu"].node == by_role["infra"][0].node
|
assert tts["tts-cpu"].node == by_role["infra"][0].node
|
||||||
assert tts["tts-cpu"].models_fitting == ["piper-de-thorsten-medium"]
|
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():
|
def test_placement_other_cluster():
|
||||||
roles, models = load_catalog(REPO_ROOT / "catalog")
|
roles, models = load_catalog(REPO_ROOT / "catalog")
|
||||||
p = plan(load_nodes(REPO_ROOT / "sites/example-single-gpu/inventory.yaml"), roles, models)
|
p = plan(load_nodes(REPO_ROOT / "sites/example-single-gpu/inventory.yaml"), roles, models)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Scrape-Ziele. Die Labels an den GPU-Zielen (slot, docker_host, container, pve_node, vmid) landen in den
|
# 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:
|
global:
|
||||||
scrape_interval: 30s
|
scrape_interval: 30s
|
||||||
evaluation_interval: 30s
|
evaluation_interval: 30s
|
||||||
@@ -15,52 +18,45 @@ scrape_configs:
|
|||||||
metrics_path: /metrics
|
metrics_path: /metrics
|
||||||
static_configs: [{targets: ["router:8080"]}]
|
static_configs: [{targets: ["router:8080"]}]
|
||||||
|
|
||||||
- job_name: vllm
|
# Erstes Ziel je Slot = Healer-Ziel in sites/knaster/registry.yaml (ops)
|
||||||
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"}
|
|
||||||
|
|
||||||
- job_name: llamacpp
|
- job_name: llamacpp
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["gpu-embed-1:8081"]
|
- targets: ["192.168.100.224:8081"]
|
||||||
labels: {slot: embed-default, docker_host: gpu-embed-1, container: llamacpp-embed, pve_node: pve3, vmid: "221"}
|
labels: {slot: embed-default, docker_host: gpu-pve44, container: llamacpp-embed, pve_node: pve44, vmid: "244"}
|
||||||
- targets: ["gpu-embed-1:8082"]
|
- targets: ["192.168.100.226:8081"]
|
||||||
labels: {slot: rerank-default, docker_host: gpu-embed-1, container: llamacpp-rerank, pve_node: pve3, vmid: "221"}
|
labels: {slot: embed-default, docker_host: gpu-pve46, container: llamacpp-embed, pve_node: pve46, vmid: "246"}
|
||||||
- targets: ["gpu-small:8080"]
|
- targets: ["192.168.100.224:8082"]
|
||||||
labels: {slot: verifier-small, docker_host: gpu-small, container: llamacpp-chat, pve_node: pve3, vmid: "231"}
|
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
|
- job_name: gpu
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["gpu-main:9835"]
|
- targets: ["192.168.100.224:9835"]
|
||||||
labels: {slot: tier1-general, pve_node: pve-ada, vmid: "201"}
|
labels: {slot: embed-default, pve_node: pve44, vmid: "244"}
|
||||||
- targets: ["gpu-flex:9835"]
|
- targets: ["192.168.100.226:9835"]
|
||||||
labels: {slot: tier1-flex, pve_node: pve-ada, vmid: "202"}
|
labels: {slot: embed-default, pve_node: pve46, vmid: "246"}
|
||||||
- targets: ["gpu-embed-1:9835"]
|
- targets: ["192.168.100.228:9835"]
|
||||||
labels: {slot: embed-default, pve_node: pve3, vmid: "221"}
|
labels: {slot: verifier-small, pve_node: pve48, vmid: "248"}
|
||||||
- targets: ["gpu-embed-2:9835"]
|
- targets: ["192.168.100.232:9835"]
|
||||||
labels: {slot: embed-default, pve_node: pve4, vmid: "222"}
|
labels: {slot: "", pve_node: pve52, vmid: "252"} # nur M4000, außer Betrieb bis Task 3.3
|
||||||
- targets: ["gpu-small:9835"]
|
|
||||||
labels: {slot: verifier-small, pve_node: pve3, vmid: "231"}
|
|
||||||
|
|
||||||
- job_name: node # Labels werden für GpuXidError → Healer (VM-Neustart) gebraucht
|
- job_name: node # Labels werden für GpuXidError → Healer (VM-Neustart) gebraucht
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["gpu-main:9100"]
|
- targets: ["192.168.100.224:9100"]
|
||||||
labels: {slot: tier1-general, pve_node: pve-ada, vmid: "201"}
|
labels: {slot: embed-default, pve_node: pve44, vmid: "244"}
|
||||||
- targets: ["gpu-flex:9100"]
|
- targets: ["192.168.100.226:9100"]
|
||||||
labels: {slot: tier1-flex, pve_node: pve-ada, vmid: "202"}
|
labels: {slot: embed-default, pve_node: pve46, vmid: "246"}
|
||||||
- targets: ["gpu-embed-1:9100"]
|
- targets: ["192.168.100.228:9100"]
|
||||||
labels: {slot: embed-default, pve_node: pve3, vmid: "221"}
|
labels: {slot: verifier-small, pve_node: pve48, vmid: "248"}
|
||||||
- targets: ["gpu-embed-2:9100"]
|
- targets: ["192.168.100.232:9100"]
|
||||||
labels: {slot: embed-default, pve_node: pve4, vmid: "222"}
|
labels: {slot: "", pve_node: pve52, vmid: "252"}
|
||||||
- targets: ["gpu-small:9100"]
|
|
||||||
labels: {slot: verifier-small, pve_node: pve3, vmid: "231"}
|
|
||||||
|
|
||||||
- job_name: proxmox
|
- job_name: proxmox
|
||||||
metrics_path: /pve
|
metrics_path: /pve
|
||||||
params: {module: [default]}
|
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:
|
relabel_configs:
|
||||||
- {source_labels: [__address__], target_label: __param_target}
|
- {source_labels: [__address__], target_label: __param_target}
|
||||||
- {source_labels: [__param_target], target_label: instance}
|
- {source_labels: [__param_target], target_label: instance}
|
||||||
|
|||||||
+33
-18
@@ -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.
|
- **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.
|
- **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.
|
- 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
|
### 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)
|
### 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 |
|
| Rolle | Hardware | Engine | Aufgabe |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `gpu-main` | RTX 4000 Ada #1 (20 GB) | vLLM, Multi-LoRA | Slot `tier1-general` plus Skill-Adapter |
|
| `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 #2 (20 GB) | vLLM / Unsloth | tagsüber zweiter Slot oder Reserve, nachts LoRA-Training |
|
| `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) | llama.cpp (Embedding/Rerank) | `embed-default`, `rerank-default`, redundant auf zwei Hosts |
|
| `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) | llama.cpp | `verifier-small`, `signature-extractor` (3–4B) |
|
| `gpu-small` | Quadro P2000 (5 GB) in `gpu-pve48` | 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 |
|
| – | 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 RTX 4000 Ada #2 | PyTorch | `tts-de-hq`: Qwen3-TTS 1.7B (ADR-0006) |
|
| `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`: schnelle Sprachausgabe, Fallback |
|
| `tts-cpu` | Infra-LXC (CPU) | Piper (ONNX) | `tts-de`: Sprachausgabe, vorerst einziger TTS-Slot |
|
||||||
| `infra` | i7 6./7. Gen, 32 GB | Docker | n8n, LiteLLM, Postgres+pgvector, Redis, Router, Healer, Langfuse, Prometheus/Grafana |
|
| `infra` | LXC `infra` auf pve44 (`tank`, HA nach pve52/pve48) | Docker | Router, Postgres+pgvector, Healer, Prometheus/Grafana/Alertmanager; **kein eigener LiteLLM** (D4) |
|
||||||
| `strong` | extern | OpenRouter | `tier0-strong`, Skill-Autor (Hermes) |
|
| `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).
|
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.
|
- **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.
|
- **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 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.
|
- **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`, 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.
|
- **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 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.
|
- **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.
|
||||||
- **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.
|
- **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):**
|
**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 |
|
| `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 |
|
| `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)
|
- 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)
|
- 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
|
### Offen
|
||||||
1. Erstes Basismodell für `tier1-general` (8–14B, gut quantisierbar, LoRA-freundlich, gutes Deutsch). Entscheidung per Kalibrierungslauf.
|
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)
|
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
|
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 %)
|
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
|
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)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-0001: GPU-Nodes als VM, Infrastruktur als LXC
|
# 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
|
- Datum: 2026-10-11
|
||||||
|
|
||||||
## Kontext
|
## 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.
|
- 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.
|
- 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.
|
- 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-<typ>-<knoten>` (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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-0002: ZFS lokal + Replikation statt Ceph
|
# ADR-0002: ZFS lokal + Replikation statt Ceph
|
||||||
|
|
||||||
- Status: angenommen
|
- Status: angenommen, ergänzt am 2026-10-13 (Nachtrag D1, entschieden)
|
||||||
- Datum: 2026-10-11
|
- Datum: 2026-10-11
|
||||||
|
|
||||||
## Kontext
|
## 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.
|
- 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.
|
- Modellwechsel verursachen Downloads (10 GB ≈ 90 s über 1 GbE). Das ist akzeptabel.
|
||||||
- Die Speicherbudgets pro Rolle stehen in PLAN.md, Abschnitt 1.4.
|
- 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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-0006: Alte GPUs nur für llama.cpp, M4000 vorerst außer Betrieb, TTS auf Ada und CPU
|
# 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
|
- Datum: 2026-10-12
|
||||||
|
|
||||||
## Kontext
|
## Kontext
|
||||||
@@ -20,3 +20,9 @@
|
|||||||
## Folgen
|
## 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Deterministisch:
|
|||||||
- `prefer: largest` wählt die GPU mit dem meisten VRAM (für LLM-Rollen).
|
- `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.
|
- `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: <rolle>` teilen sich die GPU (bzw. bei `gpu: false` den Node) einer bereits platzierten Rolle. Ein Beispiel ist TTS auf `gpu-flex`.
|
3. Rollen mit `share_with: <rolle>` 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).
|
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}]`.
|
6. Ergebnis: `assignments[{role, node, gpu_index?, models_fitting[]}]` und `unplaced[{role, reason}]`.
|
||||||
|
|
||||||
|
|||||||
@@ -138,14 +138,15 @@ Ersetzt PLAN §8 „0 Fundament“ (Teil Inventar) und IMPLEMENTATION „Gemeins
|
|||||||
**Interfaces:**
|
**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`.
|
- 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}`.
|
- 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`.
|
- `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}`.
|
- 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`).
|
- [x] **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.
|
- [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.
|
||||||
- [ ] **Step 4:** `make test && make validate` → PASS. `knaster placement plan --inventory sites/knaster/inventory.yaml` liefert eine Zuordnung ohne Fehler.
|
- [x] **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 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)
|
**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.
|
- [ ] `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=<liste>` 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`.
|
- [ ] Rolle `pve-host` ergänzen: `/etc/modprobe.d/vfio.conf` mit `options vfio-pci ids=<liste>` 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.
|
- [ ] `ansible-playbook playbooks/site.yml --limit pve_gpu_hosts --check`, danach der echte Lauf. Knoten **einzeln** neu starten.
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ Proxmox-spezifische Schicht (austauschbar, siehe PLAN §7.2). Eingabe ist immer
|
|||||||
## Ablauf
|
## 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).
|
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.
|
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 Node ein Ubuntu-24.04-Cloud-Image als Template mit VMID 9000.
|
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`.
|
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.
|
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/<rolle>` auf den jeweiligen Gästen starten.
|
6. **Dienste:** `deploy/compose/<rolle>` auf den jeweiligen Gästen starten.
|
||||||
|
|||||||
@@ -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:
|
all:
|
||||||
vars:
|
vars:
|
||||||
knaster_site: knaster
|
knaster_site: knaster
|
||||||
children:
|
children:
|
||||||
pve_hosts:
|
pve_all:
|
||||||
hosts:
|
children:
|
||||||
pve-ada: {}
|
pve_gpu_hosts:
|
||||||
pve1: {}
|
hosts:
|
||||||
pve2: {}
|
pve44: {ansible_host: 192.168.100.210}
|
||||||
pve3: {}
|
pve46: {ansible_host: 192.168.100.209}
|
||||||
pve4: {}
|
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:
|
vars:
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
gpu_vms:
|
gpu_vms:
|
||||||
hosts:
|
hosts:
|
||||||
gpu-main: {ansible_host: 10.20.0.201, driver_generation: modern, knaster_role: gpu-vllm}
|
gpu-pve44: {ansible_host: 192.168.100.224}
|
||||||
gpu-flex: {ansible_host: 10.20.0.202, driver_generation: modern, knaster_role: gpu-vllm}
|
gpu-pve46: {ansible_host: 192.168.100.226}
|
||||||
gpu-embed-1: {ansible_host: 10.20.0.221, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: embed}
|
gpu-pve48: {ansible_host: 192.168.100.228}
|
||||||
gpu-embed-2: {ansible_host: 10.20.0.222, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: embed}
|
gpu-pve52: {ansible_host: 192.168.100.232}
|
||||||
gpu-small: {ansible_host: 10.20.0.231, driver_generation: legacy, knaster_role: gpu-llamacpp, llamacpp_profile: chat}
|
|
||||||
vars:
|
vars:
|
||||||
ansible_user: knaster
|
ansible_user: knaster
|
||||||
ansible_become: true
|
ansible_become: true
|
||||||
|
driver_generation: legacy # R580, Maxwell/Pascal
|
||||||
|
knaster_role: gpu-llamacpp
|
||||||
infra:
|
infra:
|
||||||
hosts:
|
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:
|
vars:
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Reihenfolge: Hosts vorbereiten → (tofu apply) → GPU-VMs → Infra
|
# Reihenfolge: GPU-Knoten vorbereiten → (tofu apply) → GPU-VMs → Infra
|
||||||
- name: Proxmox-Hosts (IOMMU, VFIO, ZFS-Replikation)
|
# pve61 (pve_kiosk) bekommt bewusst keine Host-Rolle: keine VFIO-Bindung, keine NVIDIA-Sperre (Review Focus 5).
|
||||||
hosts: pve_hosts
|
- name: Proxmox-GPU-Knoten (IOMMU, VFIO, ZFS-Replikation)
|
||||||
|
hosts: pve_gpu_hosts
|
||||||
roles: [pve-host]
|
roles: [pve-host]
|
||||||
|
|
||||||
- name: GPU-VMs (Treiber gepinnt, Container-Toolkit, Docker, Xid-Exporter)
|
- name: GPU-VMs (Treiber gepinnt, Container-Toolkit, Docker, Xid-Exporter)
|
||||||
|
|||||||
@@ -50,21 +50,28 @@
|
|||||||
|
|
||||||
- name: Hinweis, falls tank fehlt (Pool wird bewusst NICHT automatisch angelegt – Datenverlustgefahr)
|
- name: Hinweis, falls tank fehlt (Pool wird bewusst NICHT automatisch angelegt – Datenverlustgefahr)
|
||||||
ansible.builtin.debug:
|
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/<ssd2> /dev/disk/by-id/<ssd3> && pvesm add zfspool tank -pool tank"
|
msg: >-
|
||||||
when: tank.rc != 0
|
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/<ssd2> && 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
|
- name: Bestehende Replikationsjobs lesen
|
||||||
ansible.builtin.command: pvesr list
|
ansible.builtin.command: pvesr list
|
||||||
register: pvesr_jobs
|
register: pvesr_jobs
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: ZFS-Replikation für Infra-Gäste
|
# Ein Job pro Ziel: <vmid>-0, <vmid>-1, … (Reihenfolge aus replicate_to)
|
||||||
|
- name: ZFS-Replikation für HA-Gäste
|
||||||
ansible.builtin.command: >
|
ansible.builtin.command: >
|
||||||
pvesr create-local-job {{ item.value.vmid }}-0 {{ item.value.replicate_to }}
|
pvesr create-local-job {{ item.0.value.vmid }}-{{ idx }} {{ item.1 }}
|
||||||
--schedule '{{ knaster_pve.replication_schedule }}' --comment 'knaster {{ item.key }}'
|
--schedule '{{ knaster_pve.replication_schedule }}' --comment 'knaster {{ item.0.key }}'
|
||||||
loop: "{{ knaster_pve.containers | dict2items }}"
|
loop: "{{ knaster_pve.containers | dict2items | subelements('value.replicate_to', skip_missing=true) }}"
|
||||||
|
vars:
|
||||||
|
idx: "{{ item.0.value.replicate_to.index(item.1) }}"
|
||||||
when:
|
when:
|
||||||
- item.value.node == inventory_hostname
|
- item.0.value.node == inventory_hostname
|
||||||
- item.value.replicate_to is defined
|
- ((item.0.value.vmid | string) + '-' + (idx | string)) not in pvesr_jobs.stdout
|
||||||
- (item.value.vmid | string) + '-0' not in pvesr_jobs.stdout
|
|
||||||
changed_when: true
|
changed_when: true
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ resource "proxmox_virtual_environment_vm" "gpu" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
efi_disk {
|
efi_disk {
|
||||||
datastore_id = local.site.cluster.storage_vm
|
datastore_id = try(each.value.storage, local.site.cluster.storage_default)
|
||||||
type = "4m"
|
type = "4m"
|
||||||
}
|
}
|
||||||
|
|
||||||
disk {
|
disk {
|
||||||
datastore_id = local.site.cluster.storage_vm
|
datastore_id = try(each.value.storage, local.site.cluster.storage_default)
|
||||||
interface = "scsi0"
|
interface = "scsi0"
|
||||||
size = each.value.disk_gb
|
size = each.value.disk_gb
|
||||||
discard = "on"
|
discard = "on"
|
||||||
@@ -55,7 +55,7 @@ resource "proxmox_virtual_environment_vm" "gpu" {
|
|||||||
|
|
||||||
# Modelle: eigenes Disk-Image, nicht repliziert, nicht gesichert (ADR-0002)
|
# Modelle: eigenes Disk-Image, nicht repliziert, nicht gesichert (ADR-0002)
|
||||||
disk {
|
disk {
|
||||||
datastore_id = local.site.cluster.storage_vm
|
datastore_id = try(each.value.storage, local.site.cluster.storage_default)
|
||||||
interface = "scsi1"
|
interface = "scsi1"
|
||||||
size = each.value.models_disk_gb
|
size = each.value.models_disk_gb
|
||||||
discard = "on"
|
discard = "on"
|
||||||
@@ -64,20 +64,24 @@ resource "proxmox_virtual_environment_vm" "gpu" {
|
|||||||
replicate = false
|
replicate = false
|
||||||
}
|
}
|
||||||
|
|
||||||
hostpci {
|
# Alle Karten des Knotens in eine VM (D2); Reihenfolge = Index in der VM
|
||||||
device = "hostpci0"
|
dynamic "hostpci" {
|
||||||
mapping = proxmox_virtual_environment_hardware_mapping_pci.gpu[each.value.gpu].name
|
for_each = each.value.gpus
|
||||||
pcie = true
|
content {
|
||||||
rombar = true
|
device = "hostpci${hostpci.key}"
|
||||||
|
mapping = proxmox_virtual_environment_hardware_mapping_pci.gpu[hostpci.value].name
|
||||||
|
pcie = true
|
||||||
|
rombar = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
network_device {
|
network_device {
|
||||||
bridge = local.site.cluster.bridge
|
bridge = local.site.cluster.bridge
|
||||||
vlan_id = local.site.cluster.vlan_svc
|
vlan_id = local.site.cluster.vlan_svc # null = flaches Netz
|
||||||
}
|
}
|
||||||
|
|
||||||
initialization {
|
initialization {
|
||||||
datastore_id = local.site.cluster.storage_vm
|
datastore_id = try(each.value.storage, local.site.cluster.storage_default)
|
||||||
ip_config {
|
ip_config {
|
||||||
ipv4 {
|
ipv4 {
|
||||||
address = each.value.ip
|
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
|
for_each = local.site.containers
|
||||||
node_name = each.value.node
|
node_name = each.value.node
|
||||||
vm_id = each.value.vmid
|
vm_id = each.value.vmid
|
||||||
tags = ["knaster", "infra"]
|
tags = ["knaster", each.key]
|
||||||
unprivileged = true
|
unprivileged = true
|
||||||
start_on_boot = true
|
start_on_boot = true
|
||||||
|
|
||||||
features {
|
features {
|
||||||
nesting = true # Docker im LXC
|
nesting = try(each.value.nesting, true) # Docker im LXC
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu {
|
cpu {
|
||||||
@@ -115,12 +119,12 @@ resource "proxmox_virtual_environment_container" "infra" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disk {
|
disk {
|
||||||
datastore_id = local.site.cluster.storage_vm
|
datastore_id = try(each.value.storage, local.site.cluster.storage_default)
|
||||||
size = each.value.disk_gb
|
size = each.value.disk_gb
|
||||||
}
|
}
|
||||||
|
|
||||||
operating_system {
|
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"
|
type = "debian"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +136,9 @@ resource "proxmox_virtual_environment_container" "infra" {
|
|||||||
|
|
||||||
initialization {
|
initialization {
|
||||||
hostname = each.key
|
hostname = each.key
|
||||||
|
dns {
|
||||||
|
servers = [local.site.cluster.dns]
|
||||||
|
}
|
||||||
ip_config {
|
ip_config {
|
||||||
ipv4 {
|
ipv4 {
|
||||||
address = each.value.ip
|
address = each.value.ip
|
||||||
@@ -149,5 +156,5 @@ resource "proxmox_virtual_environment_haresource" "infra" {
|
|||||||
resource_id = "ct:${each.value.vmid}"
|
resource_id = "ct:${each.value.vmid}"
|
||||||
state = "started"
|
state = "started"
|
||||||
comment = "knaster ${each.key}"
|
comment = "knaster ${each.key}"
|
||||||
depends_on = [proxmox_virtual_environment_container.infra]
|
depends_on = [proxmox_virtual_environment_container.lxc]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 } }
|
value = { for k, v in proxmox_virtual_environment_vm.gpu : k => { node = v.node_name, vmid = v.vm_id } }
|
||||||
}
|
}
|
||||||
|
|
||||||
output "infra_containers" {
|
output "containers" {
|
||||||
value = { for k, v in proxmox_virtual_environment_container.infra : k => { node = v.node_name, vmid = v.vm_id } }
|
value = { for k, v in proxmox_virtual_environment_container.lxc : k => { node = v.node_name, vmid = v.vm_id } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,55 @@
|
|||||||
# Inventar des Clusters "knaster".
|
# Inventar des Clusters "knaster" – Stand 12.10.2026 (Aufbauplan docs/superpowers/plans/2026-10-12-…, Ausgangslage).
|
||||||
# ANNAHME: Die Verteilung der GPUs und SSDs auf die Hosts ist noch nicht erfasst. Bitte beim
|
# Quelle: Healthcheck 08.10. + Inventar pve48 12.10. + Angaben des Nutzers. Beim Bootstrap mit
|
||||||
# gemeinsamen Deployment mit `knaster inventory collect` auf jedem Node ersetzen.
|
# `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:
|
nodes:
|
||||||
- name: pve-ada
|
- name: pve44 # 192.168.100.210, ThinkStation P310
|
||||||
cpu: {model: "unbekannt", cores: 6, threads: 12}
|
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
|
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:
|
gpus:
|
||||||
- {index: 0, name: "NVIDIA RTX 4000 Ada Generation", memory_gb: 20, compute_cap: 8.9}
|
- {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: gode-pc-06
|
||||||
- name: pve1
|
cpu: {model: "unbekannt (Task 0.1 e2)", cores: 0, threads: 0}
|
||||||
cpu: {model: "Intel Core i7 (6. Gen) 3.4 GHz", cores: 4, threads: 8}
|
memory_gb: 64
|
||||||
memory_gb: 32
|
cpu_roles: false
|
||||||
disks: [{name: sda, size_gb: 128}, {name: sdb, size_gb: 256}]
|
labels: {platform: windows, worker: worker-registry-v2}
|
||||||
gpus:
|
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: 0, name: "NVIDIA RTX 4000 Ada Generation", memory_gb: 20, compute_cap: 8.9}
|
||||||
- {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}
|
|
||||||
|
|||||||
+34
-21
@@ -1,31 +1,44 @@
|
|||||||
# Proxmox-Zielbild für "knaster" (Eingabe für platform/proxmox/tofu und ansible).
|
# 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:
|
cluster:
|
||||||
endpoint: https://pve1:8006/
|
endpoint: https://192.168.100.210:8006/ # pve44, IP-Literal (Global Constraints)
|
||||||
storage_vm: tank # ZFS-Pool auf SSD 2+3 (ADR-0002)
|
storage_default: local-lvm # D1 B: GPU-VMs auf der System-SSD; HA-Gäste auf `tank` (pve44/48/52)
|
||||||
storage_iso: local
|
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
|
bridge: vmbr0
|
||||||
vlan_svc: 20
|
vlan_svc: null # flaches Netz, vorerst kein VLAN (D5)
|
||||||
gateway: 10.20.0.1
|
gateway: 192.168.100.1
|
||||||
dns: 10.20.0.1
|
dns: 192.168.100.120
|
||||||
ssh_public_key_file: ~/.ssh/id_ed25519.pub
|
ssh_public_key_file: ~/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
pci_mappings: # Proxmox Resource Mappings (Datacenter → Resource Mappings → PCI)
|
nodes: # Proxmox-Knoten (Cluster-Mitglieder), Ansible-Gruppen siehe platform/proxmox/ansible
|
||||||
gpu-ada-1: {node: pve-ada, path: "0000:01:00", id: "10de:27b2", subsystem_id: "", iommu_group: 0}
|
pve44: {ip: 192.168.100.210, gpu_host: true, zfs_tank: true}
|
||||||
gpu-ada-2: {node: pve-ada, path: "0000:02:00", id: "10de:27b2", subsystem_id: "", iommu_group: 0}
|
pve46: {ip: 192.168.100.209, gpu_host: true, zfs_tank: false} # eine Platte
|
||||||
gpu-p2000-1: {node: pve3, path: "0000:01:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0}
|
pve48: {ip: 192.168.100.212, gpu_host: true, zfs_tank: true}
|
||||||
gpu-p2000-2: {node: pve3, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0}
|
pve52: {ip: 192.168.100.207, gpu_host: true, zfs_tank: true}
|
||||||
gpu-p2000-3: {node: pve4, path: "0000:01:00", id: "10de:1c30", subsystem_id: "", iommu_group: 0}
|
pve61: {ip: 192.168.100.214, gpu_host: false, zfs_tank: false} # Surface, Kiosk, keine Gäste
|
||||||
|
|
||||||
vms: # GPU-Rollen als VM (ADR-0001)
|
pci_mappings: # Datacenter → Resource Mappings → PCI; Pfad ohne Funktion = Grafik + Audio
|
||||||
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-m4000-pve44: {node: pve44, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1}
|
||||||
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-p2000-pve44: {node: pve44, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 8}
|
||||||
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-m4000-pve46: {node: pve46, path: "0000:01:00", id: "10de:13f1", subsystem_id: "", iommu_group: 1}
|
||||||
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-p2000-pve46: {node: pve46, path: "0000:02:00", id: "10de:1c30", subsystem_id: "", iommu_group: 8}
|
||||||
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}
|
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)
|
vms: # eine GPU-VM pro Knoten mit allen Karten (D2), 12 GB fest (RAM-Budget)
|
||||||
infra: {node: pve1, vmid: 101, cores: 4, memory_mb: 12288, disk_gb: 60, ip: 10.20.0.101/24, ha: true, replicate_to: pve2}
|
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"
|
replication_schedule: "*/15"
|
||||||
|
|||||||
+16
-13
@@ -1,6 +1,9 @@
|
|||||||
# Registry für den Cluster "knaster" (SPEC-core §1).
|
# 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.
|
# 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).
|
# 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:
|
slots:
|
||||||
tier0-strong:
|
tier0-strong:
|
||||||
tier: 0
|
tier: 0
|
||||||
@@ -15,37 +18,37 @@ slots:
|
|||||||
tier: 1
|
tier: 1
|
||||||
device: gpu
|
device: gpu
|
||||||
engine: vllm
|
engine: vllm
|
||||||
node: gpu-main # RTX 4000 Ada #1
|
node: gpu-main # RTX 4000 Ada, GODE-PC-05 (Windows, D3)
|
||||||
litellm_model: tier1-general
|
litellm_model: tier1-general
|
||||||
base_model: qwen3-14b-awq # Startkandidat, Entscheidung per Kalibrierungslauf
|
base_model: qwen3-14b-awq # Startkandidat, Entscheidung per Kalibrierungslauf
|
||||||
max_context: 32768
|
max_context: 32768
|
||||||
effective_context: 16384
|
effective_context: 16384
|
||||||
supports_lora: true
|
supports_lora: true
|
||||||
fallbacks: [tier1-flex]
|
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:
|
tier1-flex:
|
||||||
tier: 1
|
tier: 1
|
||||||
device: gpu
|
device: gpu
|
||||||
engine: vllm
|
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
|
litellm_model: tier1-flex
|
||||||
base_model: qwen3-14b-awq
|
base_model: qwen3-14b-awq
|
||||||
max_context: 32768
|
max_context: 32768
|
||||||
effective_context: 16384
|
effective_context: 16384
|
||||||
supports_lora: true
|
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:
|
verifier-small:
|
||||||
tier: 1
|
tier: 1
|
||||||
device: gpu
|
device: gpu
|
||||||
engine: llamacpp
|
engine: llamacpp
|
||||||
node: gpu-small # Quadro M4000 (Nutzen per Benchmark prüfen)
|
node: gpu-small # Quadro P2000 in gpu-pve48
|
||||||
litellm_model: verifier-small
|
litellm_model: verifier-small
|
||||||
base_model: qwen3-4b-q4km
|
base_model: qwen3-4b-q4km
|
||||||
max_context: 8192
|
max_context: 8192
|
||||||
effective_context: 6144
|
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:
|
signature-extractor:
|
||||||
tier: 1
|
tier: 1
|
||||||
@@ -56,18 +59,18 @@ slots:
|
|||||||
base_model: qwen3-4b-q4km
|
base_model: qwen3-4b-q4km
|
||||||
max_context: 8192
|
max_context: 8192
|
||||||
effective_context: 6144
|
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:
|
embed-default:
|
||||||
tier: 1
|
tier: 1
|
||||||
device: gpu
|
device: gpu
|
||||||
engine: llamacpp
|
engine: llamacpp
|
||||||
node: gpu-embed # Quadro P2000
|
node: gpu-embed # Quadro P2000, redundant in gpu-pve44 und gpu-pve46
|
||||||
litellm_model: embed-default
|
litellm_model: embed-default
|
||||||
base_model: qwen3-embedding-0.6b-q8
|
base_model: qwen3-embedding-0.6b-q8
|
||||||
max_context: 8192
|
max_context: 8192
|
||||||
kind: embedding
|
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:
|
rerank-default:
|
||||||
tier: 1
|
tier: 1
|
||||||
@@ -78,7 +81,7 @@ slots:
|
|||||||
base_model: bge-reranker-v2-m3-q8
|
base_model: bge-reranker-v2-m3-q8
|
||||||
max_context: 8192
|
max_context: 8192
|
||||||
kind: rerank
|
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) ---
|
# --- Sprachausgabe (ADR-0006) ---
|
||||||
tts-de:
|
tts-de:
|
||||||
@@ -90,17 +93,17 @@ slots:
|
|||||||
base_model: piper-de-thorsten-medium
|
base_model: piper-de-thorsten-medium
|
||||||
max_context: 5000 # Zeichen pro Anfrage (TTS-Dienst-Limit)
|
max_context: 5000 # Zeichen pro Anfrage (TTS-Dienst-Limit)
|
||||||
kind: tts
|
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:
|
tts-de-hq:
|
||||||
tier: 1
|
tier: 1
|
||||||
device: gpu
|
device: gpu
|
||||||
engine: pytorch
|
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
|
litellm_model: tts-de-hq
|
||||||
base_model: qwen3-tts-1.7b
|
base_model: qwen3-tts-1.7b
|
||||||
max_context: 5000
|
max_context: 5000
|
||||||
kind: tts
|
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: []
|
adapters: []
|
||||||
|
|||||||
Reference in New Issue
Block a user