Add: DNS über Pi-Hole LXC. Härtung von CT-105 Wireguard.

This commit is contained in:
2026-10-05 17:45:38 +02:00
parent 0758813e63
commit b40f726d22
26 changed files with 1014 additions and 158 deletions
+17
View File
@@ -0,0 +1,17 @@
# LLM-Server `ghostnet-ki`
Dieser Gerätebereich dokumentiert den physischen Server `10.0.0.90`. Er
gruppiert die gemeinsame Hostbasis, den eigentlichen LLM-Stack und den davon
fachlich getrennten CPU-Encoding-Workload. Alle drei Bereiche teilen Hardware,
Betriebssystem und Ansible-Control-Projekt.
| Bereich | Inhalt |
| --- | --- |
| [Host](host/GNAI-002-INFRA_BaseSystem.md) | Hardware, Debian-Basis und GPU-Ausgangslage |
| [Zugänge](host/GNAI-003-INFRA_Users.md) | Benutzer, Netzwerk, Resolver und SSH |
| [AI](ai/README.md) | Architektur, Ansible-Deployment, Modellbewertung und `llama.cpp`-Serving |
| [Encoding](encoding/GNENC-000-DOC_Index.md) | CPU-Encoding-Architektur und offener Implementierungsstand |
Übergreifende Rollen stehen im
[Zugriffskonzept](../GNAI-005-SEC_Access-Control.md), der Gesamtstand im
[zentralen Dokumentationsindex](../../GNAI-000-DOC_Index.md).
@@ -0,0 +1,370 @@
# Technical Architecture Specification
**Dokumenten-ID:** GNAI-004-INFRA
**Titel:** Homelab / LLM-Infrastruktur – Zielarchitektur
**Status:** Zielarchitektur; bestätigter Teilstand und offene Umsetzung separat ausgewiesen
**Vorgänger / Referenzen:** [Basisinstallation](../host/GNAI-002-INFRA_BaseSystem.md), [Benutzer/SSH](../host/GNAI-003-INFRA_Users.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md), [Reverse Proxy](../../proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md), [WireGuard](../../proxmox-server/lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md)
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-08-28 | 1.0 | spektr | Erstellung der Zielarchitektur |
| 2026-09-15 | 1.1 | spektr| Adressplan, Rollenmodell, OIDC-Ist und offene Sicherheitsbefunde ergänzt. |
| 2026-09-16 | 1.2 | spektr | Bestätigten Fail2Ban-Pin und noch offene Funktionsabnahme ergänzt. |
| 2026-09-18 | 1.3 | spektr | Tatsächlichen KI-Server-Hostnamen übernommen; frischen Basisstand und späteren Ansible-Sprint vom Zielbild getrennt. |
| 2026-09-21 | 1.4 | spektr / Codex | belegten Plattformstand aktualisiert und LiteLLM als zu bestätigenden Kandidaten einer allgemeinen API-Policy-Schicht eingeordnet |
---
## 1. Zweck und Geltungsbereich
Dieses Dokument beschreibt die festgelegte Zielarchitektur für die sichere Bereitstellung des LLM-Servers und der öffentlich erreichbaren Webservices innerhalb der Ghostnet-Infrastruktur.
Es handelt sich ausdrücklich um eine **technische Spezifikation und kein Installationshandbuch**. Konkrete Implementierungsanweisungen, Konfigurationsdateien und Abnahmetests werden in nachgelagerten SOPs dokumentiert.
Die Architektur baut auf dem in **GNAI-002-INFRA** beschriebenen Debian-basierten LLM-Server und der in **GNAI-003-INFRA** beschriebenen SSH-/Benutzer-Härtung auf.
Der KI-Server begann am 18.09.2026 als frisches Basissystem. Inzwischen sind
Deployment-Zugang, Host-Baseline, Podman, getrennte Workload-Konten und der
rootless ROCm-Gerätenachweis mit Ansible umgesetzt. Dauerhaftes
`llama.cpp`-Serving und weitere Anwendungen sind noch nicht installiert.
LiteLLM, OpenWebUI und GPT Researcher sind **Zielbild-Kandidaten**, aber ohne
Bestätigung in der priorisierten Roadmap keine Zusage für die nächste
Ausbaustufe.
---
## 2. Architekturprinzipien
Die Infrastruktur folgt diesen Grundsätzen:
1. **Minimale Internet-Exposure:** Aus dem Internet werden ausschließlich die tatsächlich benötigten öffentlichen Dienste freigegeben.
2. **Dedizierter Reverse Proxy:** Sämtliche öffentlichen HTTP(S)-Dienste werden über einen zentralen Nginx Proxy Manager (NPM) veröffentlicht.
3. **Managementnetz getrennt von Applikationszugriff:** Privilegierter Netzwerkzugriff erfolgt über WireGuard.
4. **Anwendungsidentität über Keycloak:** Webanwendungen verwenden OIDC zur zentralen Authentifizierung.
5. **LLM-API als dedizierte Applikationsschnittstelle:** Direkter Zugriff auf
`llama.cpp` aus dem Internet ist nicht vorgesehen. Eine öffentliche
Schnittstelle benötigt eine getrennte Policy-Schicht; LiteLLM ist dafür der
bisherige Kandidat, aber noch nicht als erste Ausbaustufe bestätigt.
6. **Defense in Depth:** Reverse Proxy, Fail2Ban, Firewall-Regeln, MFA, Patchmanagement, Monitoring und Backups ergänzen sich.
7. **Least Privilege:** Server Admins und LLM Admins erhalten passende Serverrechte; Familie ist ausdrücklich für das gesamte Heimnetz freigegeben. Netzwerkrechte werden pro Peer serverseitig durchgesetzt.
8. **Isolation:** Öffentlich exponierte Infrastruktur und einzelne Backend-Dienste bleiben logisch bzw. durch eigene LXCs getrennt.
---
## 3. Netzwerk- und Hoststruktur
### 3.1 Bestehende Proxmox-Struktur
Der Proxmox-Server bildet die Virtualisierungs- und Isolationsschicht.
| Komponente | Adresse | Rolle |
| --- | --- | --- |
| Proxmox VE | `10.0.0.2` | Virtualisierungs-/Managementhost |
| DNS LXC | `10.0.0.14` | Pi-hole, lokaler Resolver und Split-DNS |
| Reverse Proxy LXC | `10.0.0.10` | einziger öffentlicher HTTP(S)-Entry-Point |
| WireGuard LXC | `10.0.0.15` | VPN-/Managementzugang |
| LLM Server | `10.0.0.90`, Hostname `ghostnet-ki` | KI-Inferenz und LLM-Dienste |
Die Service-LXCs (z. B. Nextcloud, Gitea, Vaultwarden, Keycloak etc.) verbleiben jeweils in eigenen LXCs und werden nicht in den Reverse-Proxy-LXC zusammengelegt.
### Adressplanung und aktuelles Serviceinventar
Heimnetz `10.0.0.0/24`, Fritzbox/Gateway `.1`, lokaler DNS `.14`. Statische
Server-/LXC-Adressen sind bis `.99` vorgesehen; DHCP vergibt `.100–.200` an
Clients und verteilt Pi-hole als IPv4-DNS. Die `.90` ist dem physischen
KI-Server vorbehalten. Das ist eine Belegungsordnung innerhalb desselben
Netzes, keine VLAN-/Firewall-Isolation.
| Host / Service | Quellenstand | Status der Angabe |
| --- | --- | --- |
| Proxmox | `10.0.0.2` | Betreiber bestätigt |
| Keycloak | `10.0.0.4:8080` | Betreiber bestätigt NPM-Backend |
| Pi-hole | `10.0.0.14:53` | am 01.10.2026 in CT 100 abgenommen |
| NPM / wg-easy | `10.0.0.10` / `10.0.0.15` | Betreiber bestätigt |
| Jellyfin | `10.0.0.11:8096` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Nextcloud | `10.0.0.12:8080` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Gitea | `10.0.0.17:3000` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Japanese Web App | `10.0.0.23:80` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Vaultwarden | `10.0.0.47:8080` | GNAI-006-Inventar; OIDC-Nutzung vom Betreiber bestätigt |
| KI-Server | `10.0.0.90` | Betreiber bestätigt |
Neue LXCs im Inventar ergänzen. Der ursprüngliche Bereich `.4–.40` war geschätzt und ist keine gültige Grenze. Lokales Gästenetz, mögliche separate Servernetze, Routing und VLAN-Unterstützung gehören in einen späteren Netzwerk-Sprint; es gibt hierfür noch keine beschlossenen Netzadressen. Automatische Broadcast-/Multicast-Geräteerkennung über WireGuard wird nicht vorausgesetzt.
### 3.2 Perimeter
Die Fritzbox 7530 bildet den Internet-Perimeter.
Aus dem Internet sollen grundsätzlich nur die für den öffentlichen Betrieb erforderlichen Weiterleitungen existieren:
- TCP 80 → Reverse Proxy LXC `10.0.0.10`
- TCP 443 → Reverse Proxy LXC `10.0.0.10`
- UDP 51820 → WireGuard LXC `10.0.0.15`
Managementdienste wie Proxmox Web UI oder SSH werden **nicht direkt aus dem Internet veröffentlicht**.
IPv4- und IPv6-Exposure sind gleichwertig zu betrachten und müssen bei der Implementierung separat verifiziert werden.
---
## 4. Reverse-Proxy-LXC
Der Reverse-Proxy-LXC (`10.0.0.10`) ist eine dedizierte Infrastrukturzone für öffentlich exponierte HTTP(S)-Komponenten.
### 4.1 Containerisierung
Innerhalb des LXCs wird Podman verwendet.
Vorgesehene Container:
- **Nginx Proxy Manager**
- **Fail2Ban**
NPM und Fail2Ban befinden sich absichtlich im selben LXC. Dadurch kann Fail2Ban die Logdaten von NPM auswerten und – unter minimal erforderlichen Capabilities – die Netzwerk-/Firewallebene des eigenen LXCs absichern, ohne Zugriff auf die Firewall eines anderen LXCs zu benötigen.
### 4.2 Verantwortlichkeiten
**NPM**
- TLS-Terminierung
- Reverse Proxy
- Routing zu internen Services
- HTTP(S)-Security-Header und weitere Proxy-Policies
- optionales Rate Limiting, soweit sinnvoll
**Fail2Ban**
- Erkennung wiederholter bzw. eindeutiger Missbrauchsmuster
- temporäre Sperrung auffälliger Quelladressen
- Auswertung gezielter NPM-/Applikationslogs
Fail2Ban ist eine **zusätzliche Abuse-Protection-Schicht**, kein Ersatz für Patchmanagement, sichere Authentifizierung oder Netzwerksegmentierung.
---
## 5. Öffentliche Webservices
Alle öffentlich vorgesehenen Webservices werden hinter NPM veröffentlicht.
Beispiele:
### Services auf separaten Proxmox-LXCs
- Nextcloud
- Gitea
- Vaultwarden
- Keycloak
### Mögliche Services auf dem LLM-Server
- OpenWebUI
- GPT Researcher
- LiteLLM API
Diese Liste beschreibt Architektur-Kandidaten, keine gemeinsam freigegebene
Implementierungsreihenfolge. Die aktive Reihenfolge steht in GNAI-001.
Direkter Internetzugriff auf die internen Backend-Ports der Services ist nicht vorgesehen.
---
## 6. LLM-Server-Architektur
Der LLM-Server basiert auf der in **GNAI-002-INFRA** dokumentierten Debian-Basis mit AMD-Multi-GPU-Konfiguration und der in **GNAI-003-INFRA** vorgesehenen SSH-Key-basierten Absicherung.
### 6.1 LLM-Inferenz
`llama.cpp` soll die eigentliche lokale Modell-Inferenz bereitstellen.
Grundsatz:
> `llama.cpp` wird nicht direkt aus dem Internet exponiert.
Der Zugriff erfolgt intern über die dafür vorgesehene API-Schicht.
### 6.2 API- und Policy-Schicht
Für eine externe API ist eine standardisierte API- und Policy-Schicht vor
`llama.cpp` vorgesehen. LiteLLM ist der bisherige Kandidat. Seine Einführung
wird vor der Veröffentlichung gegen die tatsächlich benötigten individuellen
Keys, Quoten, Routing- und Beobachtbarkeitsfunktionen geprüft.
Verantwortlichkeiten:
- öffentliche API-Schnittstelle
- API-Key-Authentifizierung
- Routing zu laufenden Modellen
- ggf. Modell-/Benutzer-Policies
- Rate-/Quota-Steuerung
- Trennung der öffentlichen API von der eigentlichen Inferenz-Engine
Zielpfad:
Im Zielbild verbindet sich ein externer Client per HTTPS mit NPM. NPM leitet
an die beschlossene Policy-Schicht weiter; diese nutzt `llama.cpp` über das
interne Container-/Hostnetz. Die Inferenz läuft auf den GPUs. Dieser Pfad ist
noch nicht umgesetzt.
---
## 7. WireGuard-Managementnetz
WireGuard ist die dedizierte Zugangsschicht für privilegierte Netzwerkzugriffe.
### 7.1 Implementierung
- eigener WireGuard-LXC auf Proxmox
- `wg-easy` als Podman-Container
- WireGuard-Peer-Authentifizierung ausschließlich über kryptografische Schlüssel
- kein OIDC für die eigentliche WireGuard-Peer-Authentifizierung
### 7.2 Rollen und VPN-Betrieb
| Rolle | Zielrechte |
| --- | --- |
| Familie (Brüder) | gesamtes Heimnetz einschließlich PCs/Clients; Serververwaltung nach Dienstanmeldung |
| Server Admin | Proxmox, Service-LXCs und KI-Server; NPM und wg-easy eingeschlossen |
| LLM Admin | ausschließlich KI-Server `10.0.0.90` |
Profile werden pro Gerät vom Administrator erstellt und verteilt. Benutzer benötigen für den Profilbezug keine GUI-Anmeldung. Serverseitige **Firewall Allowed IPs** in wg-easy sollen die Zielrechte durchsetzen; clientseitige **Allowed IPs** steuern Split-Tunneling. Per-Client Firewall ist aktuell deaktiviert, Rollenisolierung nicht abgenommen. Gateway-GUI/SSH benötigen zusätzlich passende INPUT-Regeln. Die alte Full-Admin-Bezeichnung wird nicht automatisch einer der neuen Rollen gleichgesetzt.
Das Gateway nutzt MASQUERADE für VPN→LAN; die Fritzbox besitzt keine Rückroute zum VPN-Netz. LAN-Dienste sehen dadurch die Quelladresse des Gateways. Die serverseitige Peer-Zielkontrolle muss vor NAT greifen. Installation, Hooks und Abnahme stehen in GNAI-007.
---
## 8. Keycloak / OIDC
Keycloak ist die zentrale Identitäts- und Authentifizierungsplattform für die Webanwendungen.
Vorgesehenes Modell:
Der Browser erreicht den Webdienst über HTTPS und NPM. Der Webdienst bindet Keycloak für den OIDC-Anmeldeprozess ein; Anwendung und Identitätsquelle behalten getrennte Aufgaben.
Webservices sollen OIDC nutzen, soweit die jeweilige Anwendung daran angebunden wird. Laut Betreiber ist derzeit nur Vaultwarden produktiv an Keycloak/OIDC angebunden.
Die Keycloak-Administrationsoberfläche und administrative Managementschnittstellen sollen nicht öffentlich zugänglich sein und werden über das Managementnetz bereitgestellt.
Öffentlich erforderlich bleiben ausschließlich die für OIDC tatsächlich benötigten Endpunkte des jeweiligen Realms.
---
## 9. SSH
SSH auf dem LLM-Server wird ausschließlich schlüsselbasiert betrieben.
Aus **GNAI-003-INFRA** übernimmt die Zielkonfiguration insbesondere:
- `PermitRootLogin no`
- `PasswordAuthentication no`
- `KbdInteractiveAuthentication no`
- `PubkeyAuthentication yes`
Der LLM-Server-SSH-Port wird nicht aus dem Internet veröffentlicht.
Zulässige Herkunft:
- lokales vertrauenswürdiges Netzwerk
- WireGuard-Managementnetz
---
## 10. Sicherheitszonen und Vertrauensmodell
Die Infrastruktur unterscheidet mindestens drei Vertrauensklassen:
### Public Application Access
Zugang über:
HTTPS über NPM.
Authentifizierung erfolgt je nach Anwendung über:
- OIDC / Keycloak
- API-Key bei der LLM-API
### Management Access
Zugang über:
WireGuard zum freigegebenen Zielsystem.
Zusätzliche Dienstauthentifizierung bleibt erhalten, z. B. SSH Public Keys oder Proxmox-Authentifizierung.
### Backend Access
Nicht öffentlich.
Beispiele:
- `llama.cpp`
- interne Datenbankports
- interne Container-/Serviceports
- sonstige administrative Backend-Schnittstellen
---
## 11. Geplante Defense-in-Depth-Schichten
Die Zielarchitektur sieht folgende komplementäre Schutzschichten vor:
1. Fritzbox als Perimeter
2. keine unnötigen Portweiterleitungen
3. Proxmox-/LXC-Netzwerk- und Firewallregeln
4. dedizierter Reverse Proxy
5. Fail2Ban für erkannte Missbrauchsmuster
6. TLS/HTTPS
7. zentrale Authentifizierung mit Keycloak/OIDC
8. MFA/WebAuthn für privilegierte Benutzer, wo unterstützt
9. API-Keys bzw. OAuth-/Anwendungscredentials für Maschinenzugriffe
10. SSH ausschließlich über Schlüssel
11. regelmäßige Updates / Patchmanagement
12. Backup- und Restore-Strategie
13. externes Monitoring und regelmäßige Prüfung der öffentlichen Exposure
---
## 12. Nicht Teil dieser Spezifikation
Folgende konkrete Werte und Verfahren werden in den Fachdokumenten geführt. Einige sind bereits festgelegt, andere dort ausdrücklich als offen markiert:
- konkrete WireGuard-Subnetze und Peer-Adressen
- konkrete Firewall-Regelsätze
- genaue `wg-easy`-Version und Containerparameter
- genaue NPM-Container-/Storage-Konfiguration
- konkrete Fail2Ban-Jails und Filter
- konkrete Modell-, Credential- und Quota-Policies der späteren API-Schicht
- konkrete Keycloak-Realm-/Client-Struktur
- konkrete DNS-/Domainnamen
- konkrete Backup- und Monitoring-Implementierung
---
## 13. Architektur-Abnahmekriterien
Die Architektur gilt als korrekt umgesetzt, wenn mindestens folgende Eigenschaften nachweisbar sind:
- Nur vorgesehene öffentliche Ports sind aus dem Internet erreichbar.
- Proxmox ist nicht direkt aus dem Internet erreichbar.
- SSH des LLM-Servers ist nicht direkt aus dem Internet erreichbar.
- `llama.cpp` ist nicht direkt aus dem Internet erreichbar.
- Öffentliche Webservices terminieren an NPM.
- Fail2Ban befindet sich zusammen mit NPM im Reverse-Proxy-LXC.
- Familie-, Server-Admin- und LLM-Admin-Peers besitzen die beschlossenen, serverseitig durchgesetzten Zugriffsprofile.
- Webanwendungen verwenden Keycloak/OIDC, sofern dies für den jeweiligen
Nutzerpfad beschlossen und von der Anwendung belastbar unterstützt wird;
Maschinen-APIs behalten eigene Credentials und Policies.
- Die LLM-API wird über eine ausdrücklich beschlossene Policy-Schicht
bereitgestellt und über individuelle Credentials geschützt; bei Auswahl von
LiteLLM werden dessen Key-, Quota- und Routingfunktionen gesondert abgenommen.
- Administrative Weboberflächen von Keycloak sind nicht öffentlich.
- Service-LXCs bleiben voneinander isoliert und erlauben nur erforderliche Kommunikationsbeziehungen.
---
## 14. Umsetzungsstand
Die Architektur-Abnahmekriterien in Abschnitt 13 sind Sollkriterien und keine bestätigte Abnahme. Der aktuelle Projektstand und die priorisierten Befunde stehen im [Dokumentationsindex](../../../GNAI-000-DOC_Index.md); technische Nachweise und offene Prüfungen werden in GNAI-006 und GNAI-007 geführt. Backendisolation und lokale Netzwerksegmentierung sind ebenfalls noch nachzuweisen.
@@ -0,0 +1,852 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNAI-008-PLAN
**Titel:** Ansible-Deployment für den KI-Server – Planungsstand
**Stand:** 2026-09-21
**Status:** Nicht finaler Arbeitsentwurf zur Persistierung des aktuellen Planungsstands
**Referenzen:** [Projektstand](../../../GNAI-000-DOC_Index.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md), [Modellbewertung](GNAI-009-PLAN_Llama-Model-Evaluation.md), [llama.cpp-Sollkonfiguration](GNAI-010-TECH_Llama-Serving.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md)
> **Abgrenzung:** Dieses Dokument hält die Ergebnisse der am 19.09.2026
> begonnenen und bis zum 21.09.2026 fortgeschriebenen Planungssession fest. Es ist
> weder eine freigegebene technische Spezifikation noch
> eine Installationsanleitung oder ein Nachweis über bereits umgesetzte
> Komponenten. Entscheidungen, die noch geprüft oder bestätigt werden müssen,
> sind ausdrücklich als vorläufig oder offen gekennzeichnet.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-19 | 0.1 | spektr / Codex | Planungsstand zu Ansible, Gitea Actions, Runner, Freigaben und künftiger Dokumentation aus der Session festgehalten |
| 2026-09-19 | 0.2 | spektr / Codex | vorläufige Ein-Repository-Strategie sowie logisch getrennte Production- und Testing-Umgebungen auf dem KI-Server ergänzt |
| 2026-09-19 | 0.3 | spektr / Codex | exklusiven zeitlich gestaffelten Betrieb von Production und Testing ohne physische Ressourcenaufteilung als Planungsentscheidung übernommen |
| 2026-09-19 | 0.4 | spektr / Codex | erstes Ansible-Control-Projekt und erfolgreich ausgeführten lesenden Live-Preflight als belegten Implementierungsstand ergänzt |
| 2026-09-19 | 0.5 | spektr / Codex | technischen Schlüssel sowie Bootstrap- und Prüfplaybook für `ansible-deploy` vorbereitet; Ausführung bleibt interaktiv offen |
| 2026-09-19 | 0.6 | spektr / Codex | `ansible-deploy` auf dem KI-Server eingerichtet und SSH-/sudo-Zugriff unabhängig erfolgreich verifiziert |
| 2026-09-19 | 0.7 | spektr / Codex | minimale gemeinsame Host-Baseline mit Schutzprüfungen und getrennten Verzeichniswurzeln implementiert, angewendet und auf Idempotenz geprüft |
| 2026-09-20 | 0.8 | spektr / Codex | gemeinsame Podman-/Quadlet-Runtime implementiert, installiert und ohne Images oder Container verifiziert |
| 2026-09-20 | 0.9 | spektr / Codex | getrennte rootless Konten für AI Production, AI Testing und CPU-Encoding eingerichtet; Parallelitäts- und Repository-Grenzen ergänzt |
| 2026-09-20 | 0.10 | spektr / Codex | ROCm als Backend für zwei RX 6900 XT, initialen `llama.cpp`-Modellstand und Layer-Split mit gleicher GPU-Gewichtung festgehalten |
| 2026-09-20 | 0.11 | spektr / Codex | AMD-Gerätezugriff für beide rootless KI-Konten implementiert, angewendet und idempotent geprüft |
| 2026-09-20 | 0.12 | spektr / Codex | gepinnten rootless ROCm-Probelauf für Testing implementiert; zwei `gfx1030`-Agents und zwei `llama.cpp`-ROCm-Geräte nachgewiesen |
| 2026-09-21 | 0.13 | spektr / Codex | Swift-Qwen3.8 Q6_K mit 128k und `xhigh` als Testing-Kandidat übernommen; Modellbewertung und technische Sollkonfiguration ausgelagert |
| 2026-09-21 | 0.14 | spektr / Codex | Unsloth UD-Q6_K als zweites installiertes 128k-Profil und A/B-Kandidat festgelegt |
| 2026-09-21 | 0.15 | spektr / Codex | je Modell ein festes, gegenseitig ausgeschlossenes Quadlet vorgesehen und dynamischen llama.cpp-Router zurückgestellt |
| 2026-09-21 | 0.16 | spektr / Codex | überholten Phasen- und Dokumentenbacklog konsolidiert, Umsetzungsreihenfolge an GNAI-001 delegiert und privilegierten Runner nach Recovery verschoben |
---
## 1. Zweck
Der KI-Server `ghostnet-ki` soll reproduzierbar mit Ansible aufgebaut und
aktualisiert werden. Mehrere berechtigte Personen sollen Änderungen gemeinsam
über Gitea entwickeln, prüfen, freigeben und nachvollziehbar ausrollen können.
Gesucht wird ein Verfahren, das:
- den vollständigen Sollzustand des Servers versioniert,
- wiederholbare und möglichst idempotente Deployments ermöglicht,
- Änderungen vor der Anwendung prüft,
- einen nachvollziehbaren Freigabeweg für mehrere Mitwirkende bietet,
- Geheimnisse und privilegierte Zugänge vom Quelltext trennt,
- Fehler und Rollbacks betrieblich beherrschbar macht und
- für einen einzelnen KI-Server ohne unnötig große Managementplattform
betrieben werden kann.
Die Planung umfasst sowohl die spätere technische Implementierung als auch die
Dokumentation für Administratoren, Entwickler und Deployment-Berechtigte.
---
## 2. Ausgangslage
Nach dem dokumentierten Stand besitzt der KI-Server die gemeinsame
Ansible-/Podman-/ROCm-Grundlage unter `10.0.0.90`. Dauerhaftes `llama.cpp`-
Serving und die Zielbild-Kandidaten LiteLLM, OpenWebUI und weitere Anwendungen
sind noch nicht installiert. Gitea ist im bestehenden
Serviceinventar unter `10.0.0.17:3000` geführt; Version, Actions-Konfiguration
und Registry-Funktionen müssen vor der Implementierung live geprüft werden.
Der bestehende Account `ghost-agent` ist für lesende Prüfungen vorgesehen und
besitzt nach dem letzten Abgleich keine `sudo`-Berechtigung. Er soll nach dem
aktuellen Plan nicht zum privilegierten Deployment-Account erweitert werden.
Das Repository enthält inzwischen ein minimales Ansible-Control-Projekt mit
Testing-Inventar, einem ausschließlich lesenden Preflight sowie Bootstrap- und
Prüfplaybooks für `ansible-deploy`. Der privilegierte Deployment-Account ist
eingerichtet und verifiziert. Schreibende Rollen verwalten die gemeinsamen
Konfigurations- und Datenwurzeln sowie Podman und Quadlet. Anwendungsrollen und
ein Production-Inventar existieren noch nicht. Der Preflight selbst nahm keine
Änderungen vor; die bislang angewendeten Rollen bleiben auf die nachfolgend
dokumentierten Zugangs-, Verzeichnis- und Runtime-Zustände begrenzt.
Ein fachlich vom LLM-Stack getrennter CPU-Encoding-Workload wird auf demselben
Host vorgesehen. Sein Ansible-Sollzustand bleibt wegen des gemeinsamen Hosts im
Infrastructure-Repository; Architektur und spätere Nutzer-SOPs werden unter
[`docs/platform/llm-server/encoding/`](../encoding/GNENC-000-DOC_Index.md) getrennt geführt.
---
## 3. Vorläufiges Zielbild
Ansible bleibt die einzige technische Quelle für den gewünschten Zustand des
KI-Servers. Gitea verwaltet Quelltext, Reviews, Freigaben und Jobprotokolle.
Getrennte Runner- beziehungsweise Berechtigungsstufen führen CI-Prüfungen,
Testing-Deployments und Production-Deployments aus. Die genaue Anzahl der
Runner wird nach Prüfung der Gitea-Berechtigungsgrenzen festgelegt.
```mermaid
flowchart LR
DEV[Feature-Branch] --> PR[Pull Request]
PR --> CI[statische CI ohne Serverzugang]
CI --> TESTOK[freigegebener Testing-Lauf]
TESTOK --> TESTRUNNER[Testing-Berechtigung]
TESTRUNNER --> TEST[Testing auf ghostnet-ki]
TEST --> REVIEW[Review und Merge nach main]
REVIEW --> RELEASE[bewusste Production-Freigabe]
RELEASE --> PRODRUNNER[Production-Berechtigung]
PRODRUNNER --> PROD[Production auf ghostnet-ki]
PROD --> VERIFY[Health- und Funktionstests]
UPSTREAM[versionierte Upstream-Artefakte] --> UPDATE[Versions-Pin im Infrastruktur-Repository]
UPDATE --> PR
```
Runner sollen keine eingehenden Verbindungen aus dem Internet benötigen. Sie
holen Jobs bei Gitea ab und benötigen ausgehend nur die für Gitea, Paket- oder
Imagequellen und den jeweiligen SSH-Zugriff zum KI-Server erforderlichen
Verbindungen. CI-Jobs erhalten keinen SSH-Zugriff zum KI-Server.
---
## 4. Ergebnisse der bisherigen Planung
Die folgende Tabelle unterscheidet bewusst zwischen einer gewählten
Planungsrichtung und noch nicht bestätigten Implementierungsdetails.
| Thema | Aktueller Planungsstand | Verbindlichkeit / Nachweis |
| --- | --- | --- |
| Konfigurationsmanagement | Ansible beschreibt Basissystem, Laufzeitumgebung, Dienste und Konfiguration | Control-Projekt, Preflight, `host_baseline`, `podman_runtime`, `workload_accounts`, `amd_gpu_access` und der ROCm-Container-Probelauf vorhanden und angewendet; Anwendungsrollen fehlen noch |
| Quell- und Freigabeverfahren | Änderungen über Gitea-Branches und Pull Requests; kein regulärer Direkt-Push auf `main` | vorgesehen; Branch-Schutz noch nicht geprüft oder eingerichtet |
| CI | Pull Requests erhalten Lint-, YAML- und Ansible-Syntaxprüfungen ohne produktive Zugangsdaten | vorgesehen; Workflow noch nicht vorhanden |
| Produktionsausführung | Gitea Actions startet Ansible über eine dedizierte Production-Berechtigungsstufe | bevorzugte Lösung; Gitea-Version, Runner-Trennung und Actions-Funktion noch zu prüfen |
| Runner-Platzierung | eigener kleiner LXC, getrennt von Gitea und KI-Server | bevorzugt; Host, Adresse und Ressourcen noch offen |
| Job-Isolation | containerisierte Jobs; für den Gitea Runner ist nach aktueller Herstellerdokumentation Docker der unterstützte Standard | bevorzugt; lokale Freigabe und LXC-Eignung noch offen |
| Parallelität | Produktionsrunner führt höchstens einen Job gleichzeitig aus | vorgesehen; Runner-Konfiguration noch nicht vorhanden |
| Deployment-Freigabe | nach erfolgreichem Review bewusste Freigabe über geschützten `deploy-*`-Tag | bevorzugte Startvariante; Namensschema und berechtigte Teams noch offen |
| Zielzugang | eigener technischer SSH-Account `ansible-deploy` mit separatem, quellnetzbeschränktem Schlüssel und validiertem `NOPASSWD: ALL` | auf dem KI-Server eingerichtet und unabhängig verifiziert; spätere Runner-Übernahme und Rotation offen |
| Geheimnisse | verschlüsselte Ansible-Variablen plus getrennt gespeichertes Vault-Passwort und Runner-Zugangsdaten | Richtung angenommen; konkreter Secret-Workflow noch offen |
| Artefaktversionen | Pakete, Collections und Container-Images werden auf konkrete Versionen, nach Möglichkeit Digests, festgelegt | vorgesehen |
| Repository-Zuschnitt | `infrastructure` bleibt alleinige Quelle für alle Ansible-Zustände des Hosts; KI- und Encoding-Dokumentation werden getrennt geführt; weitere Quellcode-Repositories erst bei eigenständig entwickelten Anwendungen | als aktuelle Planungsentscheidung angenommen |
| Anwendungsupdates | Upstream-Artefakte werden im Infrastruktur-Repository auf eine geprüfte Version festgelegt; eigene Build-Repositories sind vorerst nicht Bestandteil des Projekts | vorgesehen; Registry- und Buildverfahren bei späterem Eigenentwicklungsbedarf neu bewerten |
| Automatische Updates | kein unbeaufsichtigtes Produktionsupdate über `latest` oder Watchtower | vorläufige Sicherheitsentscheidung |
| Laufzeitumgebungen | Production und Testing werden als getrennte Stacks auf demselben physischen KI-Server betrieben | als aktuelle Planungsentscheidung angenommen |
| Production | stabiler, extern nutzbarer Stand mit bewusst freigegebenen Versionen | Zielbild; Dienste noch nicht eingerichtet |
| Testing | interner, kurzlebiger oder zurücksetzbarer Stand für kontrollierte Tests vor dem Merge nach `main`; nutzt während eines Wartungsfensters beide GPUs exklusiv gegenüber Production | Zielbild; Dienste, Zugriffsweg und Umschaltverfahren noch nicht eingerichtet |
| Ressourcenbetrieb | AI Production und AI Testing laufen nicht gleichzeitig; der getrennte CPU-Encoding-Workload darf nachts parallel zur jeweils aktiven KI-Umgebung laufen | als aktuelle Planungsentscheidung angenommen; Grenzwerte und Lasttest offen |
| Workload-Konten | `gnai-production`, `gnai-testing` und `gn-encoding` verwenden getrennte rootless Podman-Stores und systemd-User-Manager | eingerichtet und mit `changed=0` verifiziert |
| Betriebsnachweis | erfolgreicher Lauf dokumentiert Git-Commit, Zielhost, Zeitpunkt, Prüfungen und Ergebnis | vorgesehen |
| Rollback | Konfigurationsänderung über Revert und erneute Freigabe; persistente Daten zusätzlich über getestete Backups wiederherstellen | Grundprinzip; konkrete SOP noch zu erstellen |
Eine spätere Umstellung auf ein automatisches Deployment nach jedem Merge in
`main` bleibt möglich. Zu Beginn soll die Freigabe jedoch bewusst und
nachvollziehbar erfolgen, während die technische Ausführung vollständig
automatisiert wird.
---
## 5. Verworfene oder zurückgestellte Varianten
### 5.1 Ausschließliche Ausführung vom Admin-PC
Lokale Ansible-Ausführung bleibt für Entwicklung, Diagnose und Notfälle
sinnvoll. Sie soll nicht der reguläre Produktionsweg werden, weil Ausführung,
Werkzeugversionen und Protokolle sonst von einem einzelnen Arbeitsplatz
abhängen.
### 5.2 `ansible-pull` auf dem KI-Server
Der Zielserver könnte das Repository periodisch selbst abrufen und lokal
anwenden. Für den einzelnen zentral erreichbaren Server bietet das derzeit
weniger klare Freigaben und eine ungünstigere Trennung zwischen Zielsystem und
Deployment-Steuerung. Die Variante bleibt zurückgestellt.
### 5.3 AWX
AWX bietet eine eigene Oberfläche, Rollen, Zeitpläne und Inventarverwaltung.
Der zusätzliche Plattformbetrieb erscheint für den aktuellen Umfang zu groß.
AWX kann erneut bewertet werden, wenn mehrere Zielsysteme, feinere delegierte
Rechte oder umfangreiche Jobpläne verwaltet werden müssen.
---
## 6. Repository-Verantwortung
Vorerst wird ausschließlich das bestehende Repository `infrastructure`
verwendet. Es soll den vollständigen Sollzustand des KI-Servers, beide
Laufzeitumgebungen und die zugehörigen Workflows besitzen. Damit verändern
nicht mehrere unabhängige Deployment-Repositories denselben Host.
Vorgesehene grobe Struktur:
```text
ansible/
├── ansible.cfg
├── collections/
│ └── requirements.yml
├── inventories/
│ ├── production/
│ └── testing/
├── playbooks/
│ ├── host.yml
│ ├── environment.yml
│ └── verify.yml
└── roles/
├── host_baseline/
├── environment_stack/
└── weitere_fachrollen/
.gitea/workflows/
├── validate.yml
├── deploy-testing.yml
└── deploy-production.yml
```
Diese Struktur ist ein Ausgangspunkt und noch keine Festlegung aller Rollen.
`host.yml` und `environment.yml` sollen gemeinsam den unterstützten
Gesamtzustand herstellen. Ansible-Tags können Diagnose und gezielte Wartung
unterstützen, dürfen aber nicht zu ungetesteten alternativen Installationswegen
führen.
Unveränderte Drittanbieterkomponenten wie `llama.cpp`, LiteLLM oder OpenWebUI
erhalten derzeit kein eigenes Repository. Ihre freigegebenen Versionen oder
Image-Digests werden im Infrastruktur-Repository geführt. Ein weiteres
Repository wird erst angelegt, wenn tatsächlich eigener, unabhängig
versionierter Quellcode für eine Anwendung, einen MCP Router oder MCP Server
entwickelt wird. Diese spätere Erweiterung ändert nichts daran, dass das
Infrastruktur-Repository allein festlegt, welche Artefaktversion in welcher
Umgebung läuft.
---
## 7. Production und Testing auf einem Host
### 7.1 Geltungsbereich der Trennung
Production und Testing laufen zunächst auf demselben physischen KI-Server.
Damit können Anwendungscontainer, Konfigurationen und Daten sauber logisch
getrennt werden. Die Hardware kann wegen des hohen Leistungsbedarfs nicht
dauerhaft zwischen beiden KI-Umgebungen aufgeteilt werden. Stattdessen erhält
die jeweils aktive KI-Umgebung beide GPUs exklusiv gegenüber der anderen
KI-Umgebung. Ein fachlich getrennter CPU-Encoding-Workload darf außerhalb der
Stoßzeiten parallel laufen. Es entstehen dennoch keine getrennten
Fehlerdomänen:
- Kernel, AMD-Treiber beziehungsweise ROCm, Podman und systemd werden gemeinsam
verwendet;
- ein Hostausfall betrifft beide Umgebungen;
- ein fehlerhafter Testing-Stand kann den gemeinsamen Host beeinträchtigen und
dadurch den Wiederanlauf von Production verzögern;
- Kernel-, Treiber- und Host-Runtime-Updates können nicht realitätsnah in
Testing geprüft werden, ohne den gemeinsamen Host zu verändern.
Testing ist deshalb für neue Anwendungsstände, Konfigurationen, MCP-Komponenten
und Integrationen vorgesehen. Für risikoarme Vorabtests von Kernel-, Treiber-
oder Podman-Upgrades wäre später ein zweiter Host oder eine ausreichend
vergleichbare separate Maschine erforderlich.
Die von externen Nutzern erwartete Stabilität von Production gilt außerhalb
angekündigter Test- und Wartungsfenster. Solange nur dieser eine ausgelastete
Host verfügbar ist, verursachen realistische Tests mit voller Hardware eine
geplante Unterbrechung von Production.
### 7.2 Isolationsziele
| Bereich | Production | Testing |
| --- | --- | --- |
| Erreichbarkeit | für freigegebene Dienste über NPM; Backend- und Managementports bleiben intern | nur aus freigegebenem LAN/VPN; keine öffentliche NPM-Route |
| Container- und Unit-Namen | eigener eindeutiger Präfix | eigener eindeutiger Präfix |
| Podman-Netz | eigenes internes Netz | eigenes internes Netz ohne Verbindung zum Production-Netz |
| Konfiguration | eigener Verzeichnisbaum | eigener Verzeichnisbaum |
| Persistente Daten | eigene Volumes mit Backup- und Restore-Verfahren | eigene zurücksetzbare Volumes; keine Production-Datenkopie ohne Bereinigung und Freigabe |
| Secrets und API-Keys | ausschließlich Production-Credentials | eigene eingeschränkte Test-Credentials |
| Ports und Hostnamen | stabile freigegebene Endpunkte | getrennte interne Ports und noch festzulegende Testhostnamen |
| Ressourcen | erhält im Normalbetrieb beide GPUs und das festzulegende KI-Ressourcenbudget | erhält im Testfenster beide GPUs und das festzulegende KI-Ressourcenbudget |
| GPU | beide GPUs, solange Production aktiv ist | beide GPUs, nachdem Production vollständig beendet wurde |
| Ausführungszustand | standardmäßig aktiv | standardmäßig gestoppt |
| Verfügbarkeit | stabiler Betriebsstand außerhalb angekündigter Wartungsfenster | best effort; wird nach jedem Testlauf wieder vollständig gestoppt |
Eine gleichzeitige Inferenzlast beider KI-Umgebungen ist nicht vorgesehen. Vor dem
Start von Testing muss nachweisbar sein, dass Production beendet wurde und
keine zugehörigen Prozesse mehr CPU-, RAM- oder GPU-Ressourcen halten. Vor dem
Wiederanlauf von Production muss Testing entsprechend vollständig beendet und
sein temporärer Zustand bereinigt sein. Speicherplatz- und Prozessgrenzen
bleiben auch bei exklusiver GPU-Nutzung sinnvoll, damit ein Test nicht den
gemeinsamen Datenträger füllt oder den Host dauerhaft beeinträchtigt.
Der CPU-basierte Encoding-Workload ist nicht Teil dieser KI-Umschaltung. Er darf
nachts parallel zu Production oder Testing laufen, erhält keinen GPU-Zugriff
und muss über CPU-, Speicher- und I/O-Grenzen die Reaktionsfähigkeit der
KI-Dienste schützen. Die Betreiberfestlegung „CPU-basiert“ und die geplante
Parallelität sind noch durch einen repräsentativen gemeinsamen Lasttest zu
bestätigen. Zeitfenster und Grenzwerte werden ausschließlich im getrennten
[Encoding-Plan](../encoding/GNENC-001-PLAN_Architecture.md) spezifiziert.
Große schreibgeschützte Modelldateien können später aus einem gemeinsamen,
nur lesbar eingebundenen Modellspeicher bereitgestellt werden. Datenbanken,
Uploads, Caches mit Schreibzugriff, Benutzerkonten und Anwendungsschlüssel
bleiben vollständig getrennt. Ob ein gemeinsamer Modellspeicher technisch und
betrieblich sinnvoll ist, wird erst mit dem Modell- und Backupkonzept
entschieden.
### 7.3 Exklusives Umschaltverfahren
Ein Testing-Deployment ist zugleich ein geplantes Wartungsfenster für
Production. Der vorläufige Ablauf lautet:
1. konkreten PR-Commit und Testumfang festlegen;
2. Testing-Lauf durch eine berechtigte Person freigeben und Wartungsfenster
dokumentieren;
3. letzten funktionsfähigen Production-Stand und erforderlichen Backupstatus
feststellen;
4. neue externe Anfragen nach Möglichkeit kontrolliert abweisen und Production
geordnet beenden;
5. prüfen, dass alle Production-Units, -Container und GPU-Prozesse beendet
sind;
6. einen exklusiven Lock für den KI-Umgebungswechsel setzen;
7. Testing für den festgelegten Commit ausrollen und mit der vollständigen
Hardware starten;
8. Health Checks und fachliche Tests ausführen und Ergebnisse dem Pull Request
zuordnen;
9. Testing unabhängig vom Testergebnis vollständig stoppen;
10. prüfen, dass alle Testing-Units, -Container und GPU-Prozesse beendet sind;
11. Production unverändert auf dem zuletzt freigegebenen Stand starten;
12. Production-Health- und Inferenztests ausführen und erst danach das
Wartungsfenster beenden.
Der Wiederanlauf von Production darf nicht ausschließlich von einem letzten
Schritt im Gitea-Job abhängen. Runner-Abbruch, Netzwerkverlust oder ein
fehlgeschlagener Test müssen durch einen hostseitigen Recovery-Mechanismus und
eine dokumentierte manuelle SOP beherrscht werden. Die konkrete Umsetzung kann
beispielsweise einen systemd-gesteuerten Umschaltdienst, einen exklusiven Lock,
Timeouts und einen definierten Rückfall auf Production kombinieren; sie wird
erst in der technischen Spezifikation festgelegt.
Der Testing-Deployment-Account soll Production nicht frei verwalten können. Er
darf lediglich eine eng definierte hostseitige Umschaltoperation starten und
deren Status abfragen. Der Umschaltdienst besitzt die notwendigen Rechte zum
geordneten Stoppen und Wiederanlaufen der beiden Stacks, stellt dem Testing-Job
aber weder Production-Secrets noch allgemeine Production-Verwaltungsrechte zur
Verfügung.
Production und Testing dürfen niemals gleichzeitig als aktiv markiert sein.
Jeder Start prüft den Gegenstatus und bricht bei einem widersprüchlichen oder
unklaren Zustand ab. Der Encoding-Workload bleibt von diesem KI-Lock unabhängig;
ein allgemeines Hostwartungsfenster kann weiterhin alle Workloads stoppen.
### 7.4 Vorgesehene Ansible-Abbildung
Gemeinsame Hostaufgaben und umgebungsspezifische Anwendungsaufgaben werden
getrennt:
```text
ansible/
├── playbooks/
│ ├── host.yml
│ ├── ai.yml
│ ├── video-encoding.yml
│ └── site.yml
└── roles/
├── host_baseline/
├── podman_runtime/
├── workload_accounts/
├── ai_stack/
├── ai_environment_switch/
└── video_encoding/
```
`host.yml` verwaltet gemeinsame Pakete, Treiber, Benutzer und
Runtime-Komponenten einmalig. `ai.yml` verwaltet ausschließlich den LLM-Stack
und genau eine explizit gewählte KI-Umgebung. `video-encoding.yml` verwaltet nur
Encoding-Pfade, Images, Quadlets, Timer und Prüfungen. `site.yml` kann den
unterstützten Gesamtzustand in definierter Reihenfolge herstellen. Hoständerungen
dürfen nicht beiläufig durch einen Workload-Lauf erfolgen.
Für den ersten umgesetzten Baseline-Stand gelten getrennte Konfigurationsbäume
unter `/etc/ghostnet-ai/{production,testing}` und getrennte persistente
Datenbäume unter `/var/lib/ghostnet-ai/{production,testing}`. Der Pfad
`/var/lib/ghostnet-ai/shared` ist für später ausdrücklich freigegebene
gemeinsame Daten reserviert. Konkrete Unterverzeichnisse, Eigentümer sowie
Unit- und Container-Namen werden mit den jeweiligen Fachrollen festgelegt. Die
Konten `gnai-production` und `gnai-testing` besitzen inzwischen ihre
Datenwurzeln und getrennten rootless Podman-Stores; Konfiguration bleibt
rootverwaltet und für die jeweilige Gruppe lesbar. Der Encoding-Workload
verwendet entsprechend `/etc/ghostnet-encoding` und
`/var/lib/ghostnet-encoding`.
### 7.5 ROCm- und `llama.cpp`-Ausgangskonfiguration
Der Betreiber hat zwei Sapphire Nitro+ Radeon RX 6900 XT mit jeweils 16 GiB
VRAM als Zielhardware bestätigt. Für diesen homogenen AMD-Aufbau wird ROCm als
Backend verwendet. Vulkan bleibt eine mögliche spätere Alternative, falls der
Server um GPUs mit unterschiedlichen Backend-Anforderungen erweitert wird.
Als Ausgangspunkt dient eine bereits auf einer RX 7900 XTX verwendete
Podman-Compose-Konfiguration. Die Serverumsetzung wird daraus mit folgenden
Abweichungen als rootless Quadlet erzeugt:
- das Image `ghcr.io/ggml-org/llama.cpp:server-rocm` ist für den ersten
Testing-Stand als Build `b11058`, Revision
`f072b103714dfa1eee531f80b24512faf38e3dd2` und amd64-Manifest
`sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4`
festgelegt;
- `/dev/kfd` und die tatsächlich benötigten `/dev/dri`-Geräte werden explizit
eingebunden; `GroupAdd=keep-groups` erhält die nötigen ergänzenden
Geräte-Gruppen im rootless Container;
- das gemeinsame Modellverzeichnis wird nur lesbar unter `/models`
eingebunden;
- `llama-server` erhält keinen Host-Port und ist ausschließlich für die später
beschlossene API-Policy-Schicht im umgebungseigenen Podman-Netz erreichbar;
- systemd übernimmt Start, Neustart, Abhängigkeiten und Health-Überwachung;
- Modell- und Laufzeitwerte werden als Ansible-Variablen in die
Quadlet-Kommandozeile gerendert. Zusätzliche Container-Environment-Variablen
werden nur verwendet, wenn das Backend sie technisch benötigt;
- `HSA_OVERRIDE_GFX_VERSION=11.0.0` aus der RX-7900-XTX-Konfiguration wird
nicht übernommen. Die RX 6900 XT verwendet nativ `gfx1030`, das im
vorgesehenen ROCm-Image enthalten sein muss. Ein Override wäre nur nach
einem fehlgeschlagenen Geräte-/Backendtest zulässig und müsste für RDNA2
gesondert begründet werden;
- `label=disable` wird nur übernommen, falls eine tatsächlich aktive
SELinux-Konfiguration dies erfordert. Für den dokumentierten Debian-Host
wird es nicht vorsorglich gesetzt.
Die ausführliche Kandidatenbewertung steht in
[GNAI-009-PLAN](GNAI-009-PLAN_Llama-Model-Evaluation.md), die technische
Sollkonfiguration einschließlich revisionsgebundener Downloadquelle und
SHA-256 in [GNAI-010-TECH](GNAI-010-TECH_Llama-Serving.md). Der bevorzugte
erste Testing-Kandidat ist:
| Parameter | Ausgangswert |
| --- | --- |
| Modelldatei | `Swift-Qwen3.8-27B-Q6_K.gguf` |
| API-Alias | `swift-qwen3.8-27b-q6k` |
| Kontext | 131.072 Token; Inbetriebnahme zunächst mit 65.536 |
| parallele Slots | 1 |
| maximale Ausgabe | zunächst 32.768 Token; gesonderter 65.536-Token-Test |
| GPU-Layer | alle |
| Flash Attention | aktiviert |
| K-/V-KV-Cache | jeweils `q8_0` |
| Reasoning Effort | `xhigh`, ohne kurzen Reasoning-Cap |
| MTP | zunächst deaktiviert; spätere A/B-Messung mit `n-max=2` |
| Vision-Projektor | zunächst nicht geladen |
Samplingwerte und Chat-Template-Parameter gehören zum Modellprofil und nicht
zu allgemeinen Rollendefaults. Dadurch können andere Modelle später eigene
geprüfte Profile verwenden, ohne die gemeinsame `llama_cpp`-Rolle zu ändern.
Zusätzlich wird `Qwen3.8-27B-UD-Q6_K.gguf` aus dem revisionsgebundenen
Unsloth-Repository installiert. Es läuft ebenfalls mit 131.072 Token Kontext.
Ein kontrollierter A/B-Test verwendet für beide Kandidaten `xhigh`; ein
zweiter Vergleich stellt Swift `xhigh` dem vorgesehenen Unsloth-`medium`-
Praxisprofil gegenüber. Beide Dateien liegen auf dem Host, aber immer nur ein
Modell wird in den VRAM geladen. Jedes Modell erhält ein eigenes festes
Testing-Quadlet; der Wechsel stoppt die aktive Unit vollständig, bevor die
andere startet. Der im gepinnten Build vorhandene dynamische `llama.cpp`-
Router-Modus bleibt für die erste Ausbaustufe bewusst zurückgestellt.
Für die beiden gleich großen GPUs wird zunächst
`--split-mode layer --tensor-split 1,1` festgelegt. Der Layer-Split minimiert
GPU-übergreifende Kommunikation und passt damit zur dokumentierten
PCIe-Topologie, bei der die zweite Karte über einen langsameren, am PCH
angebundenen Slot läuft. `row` wird wegen seiner Ablösung im aktuellen
`llama.cpp` nicht als neue Betriebsvariante vorgesehen. Der experimentelle
`tensor`-Split bleibt ein späterer Benchmarkkandidat; er benötigt deutlich
mehr Kommunikation zwischen den Karten und ist mit dem geplanten
quantisierten V-KV-Cache derzeit nicht kompatibel.
Der rootless Container-Probelauf hat inzwischen zwei `gfx1030`-Agents sowie
`ROCm0` und `ROCm1` mit jeweils rund 16 GiB VRAM bestätigt. Vor dem dauerhaften
Start muss ein begrenzter Modelltest noch die Device-Reihenfolge und die
tatsächliche 1:1-Verteilung in den `llama.cpp`-Startlogs bestätigen. Zusätzlich
werden Prompt-Verarbeitung, Token-Generierung, VRAM-Nutzung und Stabilität bei
131.072 Token Kontext gemessen. Der 1:1-Wert ist erst nach diesem Test ein
abgenommener Betriebsparameter.
---
## 8. Vorgesehener Workflow
### 8.1 Änderung und Prüfung
1. Repository klonen und dokumentierte lokale Ansible-Umgebung einrichten.
2. Feature-Branch anlegen.
3. Rollen, Variablen, Templates und zugehörige Dokumentation ändern.
4. lokale Lint-, Syntax- und nach Möglichkeit Check-Mode-Prüfungen ausführen.
5. Pull Request erstellen.
6. automatisierte CI-Prüfungen ohne produktive SSH- oder Vault-Zugangsdaten
ausführen.
7. erste menschliche Prüfung und einen Testing-Lauf für den konkreten Commit
freigeben;
8. Testing-Ergebnis im Pull Request dokumentieren;
9. fachliches Abschlussreview und Freigabe durch eine zweite berechtigte
Person und
10. nach erfolgreichem Statuscheck in den geschützten `main`-Branch mergen.
### 8.2 Testing-Deployment vor dem Merge
Ein Pull Request führt zunächst nur statische CI-Prüfungen in einer isolierten
Jobumgebung ohne SSH-, Vault- oder Serverzugang aus. Nach einer ersten
menschlichen Prüfung kann eine berechtigte Person einen kontrollierten
Testing-Lauf für den konkreten Commit auslösen.
Der Testing-Lauf:
1. verwendet ausschließlich den Testing-Deployment-Account und
Testing-Secrets;
2. erhält erst nach einem freigegebenen Wartungsfenster Zugriff auf den
Umschaltvorgang;
3. fordert über die fest definierte hostseitige Umschaltoperation den
kontrollierten Production-Stopp an und prüft den vollständigen Stillstand;
4. darf im aktiven Testabschnitt nur Testing-Pfade, -Units, -Container und
-Netze verwalten;
5. zeichnet den getesteten Commit unverwechselbar auf;
6. führt technische Health Checks und die für die Änderung notwendigen
Funktionstests aus;
7. veröffentlicht keine Testdienste im Internet;
8. kann die Testing-Daten vor oder nach dem Lauf auf einen bekannten Stand
zurücksetzen und
9. muss Testing stoppen sowie den letzten freigegebenen Production-Stand
wieder starten und prüfen, auch wenn der eigentliche Test fehlschlägt.
Beliebiger Pull-Request-Code darf nicht automatisch mit privilegierten
Serverzugängen ausgeführt werden. Runner-Labels allein gelten nicht als
ausreichende Berechtigungsgrenze. Vor der Implementierung ist zu prüfen, wie
Gitea in der eingesetzten Version Secrets, interne Branches, Forks und manuell
freigegebene Jobs behandelt. Production-Credentials werden keinem
Pull-Request-Workflow bereitgestellt.
### 8.3 Production-Deployment
1. berechtigte Person erzeugt einen geschützten Deployment-Tag auf einem
geprüften Commit;
2. der repository-spezifische Runner übernimmt den Job mit einer Parallelität
von eins;
3. der Job prüft erneut Syntax und Abhängigkeiten;
4. optionaler Check-Modus zeigt erwartete Änderungen, soweit die verwendeten
Module ihn zuverlässig unterstützen;
5. `environment.yml` wendet den Production-Sollzustand auf `ghostnet-ki` an;
6. `verify.yml` prüft Dienste, interne Endpunkte, Containerzustand und später
einen kleinen Inferenztest;
7. der Lauf hält Commit, Imageversionen oder Digests sowie Prüfergebnis fest;
8. erst nach allen Prüfungen gilt das Deployment als erfolgreich.
Ein Production-Deployment verwendet ausschließlich einen Commit aus dem
geschützten `main`-Branch und eine eigene bewusste Freigabe. Ein erfolgreicher
Testing-Lauf ist Voraussetzung für fachlich relevante Änderungen, ersetzt aber
weder Review noch Production-Backup und Freigabe.
Check Mode ist nur eine zusätzliche Kontrolle. Module und Shell-Kommandos, die
keine vollständige Simulation unterstützen, müssen durch gezielte Tests und
idempotente Implementierung abgesichert werden.
---
## 9. Zugangsdaten und Sicherheitsgrenzen
Der Produktionsrunner verarbeitet privilegierte Zugangsdaten und muss daher
wie ein Administrationssystem behandelt werden. Vorgesehen sind:
- repository-spezifische Runner-Registrierung und eindeutiges Runner-Label,
- keine Ausführung fremder oder öffentlicher Repositories,
- ein technischer Zielaccount getrennt von persönlichen Konten und
`ghost-agent`,
- getrennte technische Berechtigungen und Schlüssel für Production und
Testing, soweit dies mit der gewählten Podman-/systemd-Verwaltung belastbar
durchsetzbar ist,
- ein eigener SSH-Schlüssel ausschließlich für das Deployment,
- geprüfter und fest hinterlegter SSH-Host-Key,
- keine Passwort- oder Root-Anmeldung per SSH,
- möglichst eng begrenzter Netzwerkzugang des Runner-LXC,
- keine Secrets in Repository, Jobausgaben oder diff-fähigen Templates,
- `no_log` und deaktivierte Diffs für geheimnishaltige Tasks,
- getrennte Aufbewahrung des Vault-Passworts im Runner und im administrativen
Passwortmanager sowie
- dokumentierte Rotation und Sperrung von Runner-, SSH- und Vault-Zugangsdaten.
Der Testing-Zugang darf keinen Zugriff auf Production-Secrets oder
Production-Daten erhalten. Die Produktionsberechtigung darf nicht allein durch
die Auswahl eines frei benennbaren Runner-Labels erreichbar sein. Das genaue
Verfahren wird nach Prüfung der eingesetzten Gitea-Version festgelegt. Die
notwendige zeitliche Umschaltung wird über eine eng definierte hostseitige
Operation vermittelt und ist keine allgemeine Production-Berechtigung des
Testing-Accounts.
Der Deployment-Account besitzt derzeit bewusst `NOPASSWD: ALL`, weil
Paketverwaltung, systemd, Treiber und rootful Containerverwaltung weitreichende
Rechte erfordern. Eine formal kleine Befehlsliste wäre nur sinnvoll, wenn sie
den tatsächlichen Ansible-Betrieb nicht durch unsichere Umgehungen oder fragile
Sonderfälle ersetzt. Schlüssel, Account und berechtigte Workflows sind deshalb
als root-äquivalente Sicherheitsgrenze zu behandeln. Die spätere Trennung von
Production- und Testing-Berechtigungen bleibt davon unabhängig zu spezifizieren.
---
## 10. Zu erstellende technische Dokumentation und SOPs
Die frühere Liste aus neun Einzeldokumenten war für den aktuellen Umfang zu
feingranular und hätte Rollen-READMEs, Betriebsabläufe und Sicherheitsregeln
mehrfach beschrieben. Dokumentation wird deshalb entlang realer Zielgruppen
gebündelt und erst gemeinsam mit der zugehörigen Implementierung erstellt:
| Artefakt | Inhalt |
| --- | --- |
| Deployment-Architektur | Runner- und Secret-Grenzen, Inventar, Freigaben, Production-/Testing-Isolation und Abnahmekriterien |
| Contributor- und Deployment-SOP | lokales Environment, Änderung, Review, statische CI, bewusste Freigabe und Deploymentnachweis |
| Betriebs- und Recovery-SOP | Testing-Fenster, Environment-Wechsel, Rollback, Backup/Restore und Störungsbehandlung |
| Rollen-READMEs | konkrete Variablen, Voraussetzungen, Aufrufe, erwartete Ergebnisse und technische Fehlerfälle |
Ein eigenes Dokument entsteht nur bei eigenständiger Zielgruppe oder
Vertrauensgrenze. Eine Zielbeschreibung allein gilt nicht als Nachweis einer
erfolgreichen Einrichtung.
---
## 11. Umsetzungsreihenfolge
Der frühere Phasenplan vermischte bereits erledigte Grundlagen mit offener
Arbeit und stellte die Gitea-Integration pauschal vor den ersten
Anwendungsnachweis. Er wird durch den statusbasierten
[High-Level-Implementierungsplan](../../../GNAI-001-PLAN_Project-Roadmap.md) ersetzt.
Für diesen Ansible-Teilplan gelten daraus drei Leitplanken:
1. Der erste interne `llama.cpp`-Vertical-Slice wird lokal kontrolliert und
ohne öffentliche Route ausgerollt.
2. Statische CI erhält keine SSH- oder Deployment-Secrets.
3. Ein privilegierter Runner folgt erst nach stabilem Rollen-, Rollback- und
Recovery-Verfahren.
---
## 12. Verbleibende Deployment-Entscheidungen
Produkt- und Prioritätsentscheidungen stehen zentral in GNAI-001. Für die
spätere Delivery-Kette bleiben hier nur folgende technische Fragen:
1. Welche Gitea-/Actions-/Runner-Version und welche Schutzfunktionen sind live
verfügbar?
2. Wo läuft der repository-spezifische Runner, und welche Job-Isolation wird
nachweisbar unterstützt?
3. Welche Personen dürfen reviewen, mergen und Testing beziehungsweise
Production freigeben?
4. Welcher Secret-Store, welche Schlüsselrotation und welche getrennten
Testing-/Production-Rechte werden verwendet?
5. Welcher unveränderliche Commit und welcher manuelle Freigabeschritt lösen
Production aus?
6. Welcher hostseitige Lock-, Timeout- und Recovery-Mechanismus stellt
Production nach einem Abbruch wieder her?
Diese Fragen werden vor Einführung eines privilegierten Runners entschieden,
nicht als Voraussetzung für den internen Testing-Vertical-Slice.
---
## 13. Referenzstand der Planung
Für die weitere Ausarbeitung wurden am 19.09.2026 insbesondere folgende
Herstellerdokumentationen herangezogen:
- [Gitea Actions – Überblick](https://docs.gitea.com/usage/actions/)
- [Gitea Runner](https://docs.gitea.com/runner/)
- [Gitea Actions – Secrets](https://docs.gitea.com/usage/actions/secrets/)
- [Gitea Actions – Job-Token-Berechtigungen](https://docs.gitea.com/usage/actions/token-permissions/)
- [Gitea – geschützte Tags](https://docs.gitea.com/usage/protected-tags/)
- [Ansible Playbooks](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_intro.html)
- [Ansible Check- und Diff-Modus](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html)
- [Ansible Vault](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html)
- [Debian 13 – Paket `podman`](https://packages.debian.org/trixie/podman)
- [Podman – Quadlet und systemd](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [llama.cpp – Multi-GPU](https://github.com/ggml-org/llama.cpp/blob/master/docs/multi-gpu.md)
- [llama.cpp – ROCm-Container](https://github.com/ggml-org/llama.cpp/blob/master/.devops/rocm.Dockerfile)
Vor der Implementierung sind versionsspezifische Aussagen gegen die dann
tatsächlich eingesetzten Gitea-, Runner- und Ansible-Versionen zu prüfen.
---
## 14. Belegter Ansible-Implementierungsstand
Am 19.09.2026 wurde `ansible-core` 2.21.4 in einer lokalen, von Git
ausgeschlossenen virtuellen Umgebung installiert. Das neue Control-Projekt
enthält:
- `ansible.cfg` mit festem Testing-Inventar, Host-Key-Prüfung und lokalem
Ansible-Arbeitsverzeichnis;
- ein Testing-Inventar für `ghostnet-ki` unter `10.0.0.90` mit dem
unprivilegierten Konto `ghost-agent`;
- ein Preflight-Playbook ohne `become` und ohne konfigurationsändernde Tasks;
- eine kurze Anleitung für Control-Node-Setup, SSH-Agent, Syntaxprüfung und
Live-Lauf.
Inventardarstellung und Syntaxprüfung waren erfolgreich. Der Live-Lauf
bestätigte:
| Merkmal | Ergebnis |
| --- | --- |
| Hostname | `ghostnet-ki` |
| Betriebssystem | Debian 13.6 |
| Standard-IPv4 | `10.0.0.90` |
| CPU | 14 vCPUs |
| RAM | 64.123 MiB |
| Remote-Python | 3.13.5 |
| GPUs | zwei Navi-21-PCI-Geräte `1002:73bf` |
| Play Recap | `ok=5 changed=0 unreachable=0 failed=0` |
Dieser Lauf ist ein Zugriffs- und Identitätsnachweis. Er bestätigt weder ROCm
noch Inferenzfunktion, privilegierte Deploymentrechte, Containerbetrieb oder
die geplante Umschaltung zwischen Production und Testing.
Für den folgenden Bootstrap wurden ein eigener Ed25519-Schlüssel, ein
Account-/sudoers-Playbook und ein getrenntes Prüfplaybook erstellt. Der
öffentliche Schlüssel ist mit Fingerprint
`SHA256:IKEInbIo8VVI1USg5+a3ypGK8H8FweJiQu+v7KvhQDU` versioniert; der private
Schlüssel liegt mit Modus `0600` außerhalb des Repositories. Syntaxprüfung und
Bootstrap-Inventar waren erfolgreich.
Der Bootstrap wurde anschließend über das persönliche Administratorkonto
ausgeführt. Benutzer, Home-/SSH-Verzeichnisse, eingeschränkter Public Key und
die mit `visudo` geprüfte sudoers-Datei wurden erfolgreich angelegt. Die letzte
Assertion des ersten Laufs schlug ausschließlich fehl, weil die nicht vollständig
quotierte Bedingung mit `NOPASSWD: ALL` als YAML-Mapping statt als String
interpretiert wurde. Die Bedingung wurde korrigiert. Das getrennte Prüfplaybook
bestätigte danach die Anmeldung als `ansible-deploy`, normale Modulausführung
und nicht-interaktive Eskalation auf UID 0 mit
`ok=5 changed=0 unreachable=0 failed=0`. Ein vollständiger zweiter
Bootstrap-Lauf zur praktischen Idempotenzprüfung wurde noch nicht ausgeführt.
Als erste gemeinsame Hostrolle wurde anschließend `host_baseline` mit eigenem
Host-Inventar und `playbooks/host.yml` implementiert. Vor Änderungen prüft sie
Hostname, Debian-Hauptversion, Standard-IPv4, die Erkennung des
Root-Dateisystems und mindestens 100 GiB freien Speicher. Sie verwaltet derzeit
ausschließlich folgende root-eigenen Verzeichniswurzeln:
```text
/etc/ghostnet-ai/production
/etc/ghostnet-ai/testing
/var/lib/ghostnet-ai/production
/var/lib/ghostnet-ai/testing
/var/lib/ghostnet-ai/shared
```
Inventar- und Syntaxprüfung waren erfolgreich. Der Check-Modus zeigte nur die
sieben erwarteten Verzeichnisse einschließlich ihrer Eltern als Änderungen und
meldete 1.677,7 GiB freien Platz auf `/`. Der erste reale Lauf endete mit
`ok=6 changed=1 unreachable=0 failed=0`. Der unmittelbar folgende identische
Lauf endete mit `ok=6 changed=0 unreachable=0 failed=0` und belegt die
Idempotenz des aktuellen Rollenumfangs. Es wurden keine Pakete installiert und
keine Dienste, Container oder GPU-Komponenten verändert.
Am 20.09.2026 wurde `podman_runtime` in das gemeinsame Host-Playbook
aufgenommen. Die Rolle installiert `podman`, `aardvark-dns`, `passt`,
`slirp4netns` und `uidmap` ohne empfohlene Build- und Checkpoint-Pakete. Sie
verlangt Debian 13, systemd und cgroup v2, verwaltet
`/etc/containers/systemd`, prüft mindestens Podman-Hauptversion 5 sowie den
Overlay-Storage und führt den Quadlet-Generator ohne Workload im Dry-Run aus.
Der API-Socket und der automatische Image-Update-Timer bleiben explizit
deaktiviert und gestoppt.
Der Check-Modus sagte 38 neue Pakete und das Quadlet-Verzeichnis voraus. Im
ersten realen Lauf wurden die Pakete installiert; die anschließende
Generatorprüfung schlug fehl, weil der Generator in Debian als Symlink
bereitgestellt wird und die `stat`-Prüfung das Ziel zunächst nicht auflöste.
Nach der Korrektur auf `follow: true` bestanden sämtliche Laufzeitprüfungen.
Der finale Gesamtlauf endete mit
`ok=20 changed=0 unreachable=0 failed=0`. Verifiziert wurden Podman 5.4.2,
cgroup v2, der Storage-Treiber `overlay` und der ausführbare Quadlet-Generator.
Eine getrennte Prüfung bestätigte außerdem leere Image- und Containerlisten
sowie `podman.socket` und `podman-auto-update.timer` jeweils als `disabled` und
`inactive`.
Die Rolle `workload_accounts` wurde anschließend in `host.yml` aufgenommen. Sie
verwaltet feste IDs, gesperrte Passwörter, `/usr/sbin/nologin`, getrennte
SubUID-/SubGID-Bereiche, Daten- und Konfigurationsrechte, rootverwaltete
Quadlet-Verzeichnisse und Linger für `gnai-production` (UID/GID 980),
`gnai-testing` (981) und `gn-encoding` (982). Die Datenwurzeln gehören dem
jeweiligen Konto; Konfiguration gehört root und ist für die jeweilige Gruppe
lesbar.
Beim ersten realen Lauf scheiterte nur die Abschlussprüfung, weil die
sudoers-Regel von `ansible-deploy` den direkten Ansible-Wechsel zu den
Servicekonten nicht erlaubt. Die Prüfung wurde auf einen rootseitigen Aufruf
über `runuser` umgestellt, ohne die sudoers-Regel zu erweitern. Ein weiterer
Lauf deckte eine konkurrierende Eigentümerverwaltung durch `host_baseline` auf;
die umgebungsspezifischen Pfade wurden daraufhin vollständig an
`workload_accounts` übertragen. Der korrigierte Gesamtlauf endete mit
`ok=36 changed=0 unreachable=0 failed=0` und bestätigte drei aktive
systemd-User-Manager sowie drei getrennte rootless Podman Graph Roots. Es
wurden keine Images oder Workload-Container angelegt.
Anschließend wurde `amd_gpu_access` in das gemeinsame Host-Playbook
aufgenommen. Die Rolle verlangt genau zwei Geräte `1002:73bf` mit `amdgpu`,
ordnet über sysfs ausschließlich deren Render-Nodes zu, prüft `/dev/kfd` und
leitet die minimal erforderlichen Gerätegruppen aus dem Istzustand ab. Auf dem
Host sind dies `/dev/kfd`, `/dev/dri/renderD128` und
`/dev/dri/renderD129`, jeweils mit Gruppe `render`.
Der Check-Modus prognostizierte nur die ergänzende Gruppenmitgliedschaft für
`gnai-production` und `gnai-testing`. Der reale Lauf bestätigte danach für
beide Konten Lese- und Schreibzugriff auf alle drei Geräte. Ein erster
Wiederholungslauf deckte auf, dass `workload_accounts` die Gruppe zunächst
entfernte und `amd_gpu_access` sie wieder hinzufügte. `render` wurde deshalb in
den expliziten Sollzustand der beiden KI-Konten übernommen; das Encoding-Konto
bleibt ohne GPU-Gruppe. Der korrigierte Gesamtlauf endete mit
`ok=55 changed=0 unreachable=0 failed=0`. Weder ein ROCm-Image noch ein
Container oder eine Inferenz wurden dabei ausgeführt.
Danach wurde `verify-rocm-container.yml` mit der eigenständigen Rolle
`rocm_container_probe` ergänzt. Das Playbook wiederholt zunächst den
Host-Gerätenachweis und arbeitet anschließend ausschließlich im rootless
Podman-Store von `gnai-testing`. Es verwendet das offizielle
`llama.cpp`-ROCm-Image als unveränderliches amd64-Manifest
`sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4`
und prüft dessen OCI-Labels auf Build `b11058` und Revision
`f072b103714dfa1eee531f80b24512faf38e3dd2`.
Die ephemeren Probecontainer erhielten nur `/dev/kfd`, die beiden zuvor
ermittelten Render-Nodes und `keep-groups`; ein
`HSA_OVERRIDE_GFX_VERSION` wurde nicht gesetzt. `rocminfo` meldete genau zwei
GPU-Agents mit `gfx1030`. `llama-server --list-devices` meldete getrennt
`ROCm0` und `ROCm1`, beide als AMD Radeon RX 6900 XT mit jeweils rund 16 GiB
VRAM. Der Abschlusslauf endete mit
`ok=33 changed=0 unreachable=0 failed=0`. Die Probecontainer wurden durch
`--rm` entfernt; das gepinnte Image bleibt für den folgenden Testing-Aufbau im
Store. Modellladen, Layer-Verteilung und Inferenz wurden noch nicht geprüft.
---
## 15. Abschlussgrenze dieses Entwurfs
Mit Version 0.15 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
Ansible-Preflight, der verifizierte Deployment-Account und die angewendete
minimale Host-Baseline, die Podman-/Quadlet-Grundruntime und die getrennten
Workload-Konten, deren begrenzter AMD-Gerätezugriff sowie der rootless
ROCm-/`llama.cpp`-Gerätenachweis für Testing persistiert.
Insbesondere wurden noch nicht:
- ein Gitea Runner angelegt,
- Gitea Actions oder Schutzregeln konfiguriert,
- eine dauerhafte `llama.cpp`-Anwendungsrolle, ein Production-Inventar oder ein
Vault erstellt,
- eine Production- oder Testing-Umgebung eingerichtet oder voneinander
abgenommen,
- ein dauerhafter GPU-Container, ein Modell oder eine Anwendung auf dem
KI-Server eingerichtet,
- Backups oder Rollbacks praktisch getestet oder
- die geplante Deploymentkette technisch abgenommen.
Der nächste KI-spezifische Schritt ist die `llama.cpp`-Rolle für Testing. Die
bereichsübergreifende Priorität und die Abhängigkeit von den offenen
Sicherheitsbefunden legt GNAI-001 fest. LiteLLM ist nicht länger automatisch
der Folgeschritt, sondern wird vor einer Veröffentlichung anhand der benötigten
API-Policies entschieden.
@@ -0,0 +1,345 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNAI-009-PLAN
**Titel:** Modell- und Laufzeitbewertung für `llama.cpp`
**Stand:** 2026-09-21
**Status:** Nicht finaler Evaluierungsplan; keine Production-Freigabe
**Referenzen:** [Ansible-Deployment](GNAI-008-PLAN_Ansible-Deployment.md), [technische Sollkonfiguration](GNAI-010-TECH_Llama-Serving.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md)
> Dieses Dokument persistiert die Modellrecherche und legt fest, wie daraus
> eine belastbare Entscheidung für den KI-Server entsteht. Veröffentlichte
> Benchmarks, Community-Berichte und lokale Abnahmetests werden getrennt
> bewertet. Ein hier genannter Kandidat ist erst nach erfolgreicher lokaler
> Abnahme für Production freigegeben.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-21 | 0.4 | spektr / Codex | Metrikübergabe an die später gewählte API-Policy-Schicht statt vorab an LiteLLM gebunden |
| 2026-09-21 | 0.3 | spektr / Codex | feste, gegenseitig ausgeschlossene Quadlet-Unit je Modell als Umschaltverfahren festgelegt |
| 2026-09-21 | 0.2 | spektr / Codex | Unsloth Qwen3.8 UD-Q6_K als verbindlichen zweiten 128k-Kandidaten festgelegt und kontrollierten A/B-Test ergänzt |
| 2026-09-21 | 0.1 | spektr / Codex | Swift-Qwen3.8, Q6_K, Reasoning, Kontext, MTP und lokales Vergleichsverfahren als ersten Evaluierungsstand aufgenommen |
---
## 1. Ziel
Gesucht wird ein Modellprofil für Coding-, Tool- und längere agentische
Aufgaben auf zwei Radeon RX 6900 XT mit zusammen 32 GiB VRAM. Das Profil soll:
- komplexe Aufgaben mit ausreichend langem Reasoning bearbeiten können;
- unnötige Reasoning-Schleifen reduzieren;
- Tool Calls und strukturierte Ausgaben zuverlässig erzeugen;
- mindestens 128 KiToken Kontext als Zielkonfiguration unterstützen;
- vollständig über beide GPUs mit ROCm betrieben werden;
- durch unveränderliche Modell- und Containerartefakte reproduzierbar sein;
- Swift und Unsloth gleichzeitig im Modellbestand vorhalten, aber für Tests
jeweils nur ein Modell in den VRAM laden;
- ohne Modellwechsel zwischen Testing und Production übertragbar sein; und
- später gegen neue Swift-, Base- oder Dynamic-Quantisierungen austauschbar
bleiben.
Die Entscheidung wird anhand realer Aufgaben getroffen. Herstellerbenchmarks
dienen als Kandidatenauswahl, ersetzen aber nicht die lokale Abnahme.
## 2. Bewerteter Quellenstand
### 2.1 Qwen3.8-27B
Die [Qwen-Modellkarte](https://huggingface.co/Qwen/Qwen3.8-27B) nennt für das
27B-Modell 64 Blöcke, davon 16 Full-Attention-Blöcke, einen nativen Kontext von
262.144 Token und ein trainiertes Multi-Token-Prediction-Head. Thinking ist
standardmäßig aktiv. Qwen empfiehlt für Thinking `temperature=1.0`,
`top_p=0.95`, `top_k=20`, `min_p=0.0`, `presence_penalty=0.0` und
`repetition_penalty=1.0`.
Qwen unterstützt `xhigh`, `medium` und `low` als Reasoning-Stufen. Die
Modellkarte warnt ausdrücklich, dass ein niedrigeres Reasoning-Niveau bei
mehrstufigen Agentenaufgaben zu Fehlversuchen und Wiederholungen führen kann
und dadurch nicht zwingend Zeit oder Token spart. Ein harter kurzer
Reasoning-Cap wird deshalb nicht als Standardprofil vorgesehen.
### 2.2 Swift-Qwen3.8-27B
Der Herausgeber beschreibt
[Swift-Qwen3.8-27B](https://huggingface.co/ukisai/Swift-Qwen3.8-27b) als
reasoning-effiziente Ableitung des Qwen-Basismodells. Nach eigener Darstellung
wurden mit Overthinking korrelierte Reasoning-Muster im Fine-Tuning
benachteiligt und verlorene Qualität durch weitere Trainingsschritte
kompensiert.
Die veröffentlichten BF16-Vergleiche wurden mit vLLM, 262.144 Token Kontext,
`xhigh`, fünf Seeds und den Qwen-Samplingwerten durchgeführt. Für GPQA Diamond
werden nahezu gleiche Scores bei 41 Prozent weniger mittleren beziehungsweise
58,3 Prozent weniger medianen Thinking-Tokens ausgewiesen. Terminal-Bench 2.1
liegt geringfügig unter Base, LiveCodeBench v6 darüber. AIME 2026 und HMMT
zeigen hingegen einen messbaren Rückgang.
Diese Ergebnisse stammen vom Modellhersteller. Sie sind ein guter Grund für
einen lokalen Test, aber noch kein unabhängiger Nachweis für unsere Aufgaben,
für GGUF Q6_K, für llama.cpp oder für ROCm auf Navi 21. Insbesondere darf der
LiveCodeBench-Unterschied nicht ohne Kontrolle der Output-Limits als allgemeine
Qualitätssteigerung interpretiert werden.
### 2.3 GGUF-Quantisierungen
Das vom Swift-Herausgeber gepflegte
[GGUF-Repository](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
enthält unter anderem folgende Ein-Datei-Quantisierungen:
| Quantisierung | veröffentlichte Größe | vorgesehene Rolle |
| --- | ---: | --- |
| Q4_K_M | 18,0 GB | speichersparender Vergleich und möglicher Fast-/Vision-Fallback |
| Q5_K_M | 20,2 GB | möglicher Kompromiss für MTP oder zusätzliche Reserve |
| Q6_K | 22,9 GB | bevorzugter Qualitätskandidat für Text-, Coding- und Tool-Aufgaben |
| Q6_K_L | 25,2 GB | derzeit zu geringe Reserve für 128k und Laufzeitpuffer |
| Q8_0 | 29,1 GB | für 32 GiB Gesamt-VRAM mit langem Kontext ungeeignet |
Der Herausgeber empfiehlt Q6_K oder höher für lange Agentenläufe und strikte
Tool-Formate. Die GGUFs wurden laut Modellkarte mit llama.cpp `b10896` erzeugt;
recurrent-sensitive Tensoren und das MTP-Head verwenden gezielt höhere
Präzision. Die veröffentlichten quantisierten Aufgabenbenchmarks betreffen
jedoch W4A16/AWQ und nicht direkt die Q6_K-GGUF. Die Eignung von Q6_K bleibt
daher Gegenstand unseres Tests.
### 2.4 Unsloth Qwen3.8-27B UD-Q6_K
Das zweite Testartefakt stammt aus dem
[Unsloth-GGUF-Repository](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF).
Ausgewählt ist `Qwen3.8-27B-UD-Q6_K.gguf` mit einer veröffentlichten Größe von
22 GB. Das Repository steht laut Modellkarte unter Apache-2.0. Unsloth hat die
Quantisierung mit einer Importance Matrix und dem eigenen Dynamic-Verfahren
erzeugt.
Damit unterscheidet sich der Kandidat nicht nur durch das ursprüngliche
Qwen-Verhalten von Swift, sondern auch durch das Quantisierungslayout. Der
Vergleich misst deshalb die beiden konkret einsetzbaren Artefakte als Ganzes;
er isoliert nicht wissenschaftlich den Effekt des Swift-Fine-Tunings. Diese
Einschränkung wird bei der Auswertung ausdrücklich festgehalten.
### 2.5 MTP
Das MTP-Head ist in den Swift-GGUFs enthalten. Die Modellkarte nennt für
llama.cpp `--spec-type draft-mtp --spec-draft-n-max 3`. Die aktuelle
llama.cpp-Dokumentation führt MTP als unterstützte spekulative Methode.
MTP wird trotzdem nicht in das erste Abnahmeprofil aufgenommen. Es benötigt
zusätzlichen Draft-Kontext und zusätzliche Laufzeitpuffer. Der verlinkte
Community-Nachweis zeigt, dass MTP deutliche Gewinne liefern kann, verwendet
aber Strix Halo, Vulkan, ein anderes Qwen-Modell und zeitweise einen Fork. Er
ist kein Nachweis für zwei RX 6900 XT mit dem gepinnten ROCm-Image.
MTP wird erst nach einer stabilen Baseline in derselben Sitzung und mit
identischen Aufgaben ein- und ausgeschaltet. Begonnen wird mit
`--spec-draft-n-max 2`; `3` bleibt eine zweite Messstufe.
### 2.6 Swift 1.5 und Community-Quants
Ankündigungen zu Swift 1.5 und kombinierten Swift-/Unsloth-Dynamic-Quants
werden als Beobachtung festgehalten. Am 21.09.2026 wurde daraus noch kein
freigegebenes Projektartefakt ausgewählt. Weder die Ansible-Rolle noch das
Production-Profil warten auf eine angekündigte Version.
Ein neuer Checkpoint wird als neues, unveränderliches Profil mit eigener
Quelle, Revision, Prüfsumme und Lizenzprüfung aufgenommen. Er ersetzt niemals
automatisch ein bereits abgenommenes Profil.
## 3. Vorläufige Kandidaten
### 3.1 Bevorzugter Kandidat
**Swift-Qwen3.8-27B Q6_K mit `xhigh`** wird als erster Testing-Kandidat
festgelegt. Gründe sind die höhere Gewichtsqualität gegenüber Q4/Q5, die
Herstellerempfehlung für lange Agentenläufe und die Zielsetzung von Swift,
Reasoning-Effizienz ohne kurzen harten Token-Cap zu erreichen.
Das erste Profil verwendet:
| Parameter | Wert |
| --- | --- |
| Quantisierung | Q6_K |
| Kontextziel | 131.072 Token |
| K-/V-KV-Cache | `q8_0` / `q8_0` |
| Reasoning | aktiv, `xhigh`, ohne Reasoning-Budget |
| parallele Slots | 1 |
| Flash Attention | aktiv |
| GPU-Offload | alle Layer |
| GPU-Aufteilung | `layer`, `1,1` |
| MTP | zunächst deaktiviert |
| Vision-Projektor | zunächst nicht geladen |
| maximale Ausgabe | zunächst 32.768 Token; gesonderter 65.536-Token-Test |
128 KiToken sind das Zielprofil. Die Inbetriebnahme prüft dasselbe Modell
zuerst mit 64 KiToken, damit Fehler bei Modellformat, ROCm oder GPU-Aufteilung
von reinem Speicherdruck unterschieden werden können.
### 3.2 Verbindlicher Vergleichskandidat
**Unsloth Qwen3.8-27B UD-Q6_K** wird ebenfalls installiert und mit 131.072
Token Kontext abgenommen. Das Artefakt ist verbindlich festgelegt:
| Feld | Wert |
| --- | --- |
| Repository | `unsloth/Qwen3.8-27B-GGUF` |
| Revision | `4ca720788d1e01f1bff70c033e0d0028fd02e502` |
| Datei | `Qwen3.8-27B-UD-Q6_K.gguf` |
| veröffentlichte Größe | 22 GB |
| SHA-256 | `c9c206812fbe4ac7b76a729e25928b63f2ae89d37f69da7a71c20aec763cd436` |
| API-Alias | `unsloth-qwen3.8-27b-ud-q6k` |
Beide Dateien liegen gleichzeitig auf dem Host und jedes Modell besitzt eine
eigene feste Quadlet-Unit. Für einen Messlauf ist immer nur die ausgewählte
Unit aktiv; die Modelle werden nicht gleichzeitig in den VRAM geladen. Der
Wechsel stoppt den bisherigen Container vollständig, bevor der andere startet.
Der erste kontrollierte A/B-Vergleich verwendet bei beiden Modellen `xhigh`,
131.072 Token Kontext, Q8-K/V, einen Slot, dieselben Samplingwerte und kein
MTP. Danach folgt der praxisnahe Profilvergleich Swift `xhigh` gegen Unsloth
`medium`. Dadurch bleiben die Effekte der Reasoning-Einstellung sichtbar,
statt sie unbemerkt dem Modellartefakt zuzurechnen.
### 3.3 Ressourcen-Fallback
**Swift Q5_K_M mit `xhigh`** ist der erste Fallback, falls Q6_K mit 128k,
Laufzeitpuffern oder später MTP nicht stabil in 32 GiB VRAM betrieben werden
kann. Ein Wechsel auf Q5 erfolgt nur aufgrund gemessener Speicher- oder
Leistungsergebnisse, nicht vorsorglich.
## 4. Testmatrix
| Stufe | Modellprofil | Kontext | MTP | Zweck |
| --- | --- | ---: | --- | --- |
| A0 | Swift Q6_K, text-only | 65.536 | aus | Modellformat, vollständiger GPU-Offload, API und Grundstabilität |
| A1 | Swift Q6_K, text-only | 131.072 | aus | bevorzugtes Zielprofil und VRAM-Abnahme |
| A2 | Swift Q6_K mit mmproj | 131.072 | aus | optionaler Vision-Betrieb und zusätzlicher VRAM-Bedarf |
| A3 | Swift Q6_K, text-only | 131.072 | `n-max=2` | MTP-A/B gegen A1 |
| A4 | Swift Q6_K, text-only | 131.072 | `n-max=3` | nur nach erfolgreichem A3-Test |
| B1 | Swift Q5_K_M, text-only | 131.072 | aus / 2 | Ressourcen- und Geschwindigkeitsvergleich |
| U0 | Unsloth UD-Q6_K, text-only, `xhigh` | 65.536 | aus | Modellformat, GPU-Offload, API und Grundstabilität |
| U1 | Unsloth UD-Q6_K, text-only, `xhigh` | 131.072 | aus | kontrolliertes Gegenstück zu A1 |
| AB1 | Swift Q6_K gegen Unsloth UD-Q6_K, beide `xhigh` | 131.072 | aus | A/B mit identischen Laufzeitparametern |
| AB2 | Swift Q6_K `xhigh` gegen Unsloth UD-Q6_K `medium` | 131.072 | aus | Vergleich der vorgesehenen Praxisprofile |
Vision und MTP sind eigenständige Varianten. Sie werden nicht gleichzeitig
erstmals aktiviert, weil andernfalls Speicher- und Performanceeffekte nicht
zugeordnet werden können.
## 5. Aufgaben und Messwerte
Die Vergleichssuite enthält 10 bis 20 repräsentative Aufgaben aus dem späteren
Betrieb:
1. kleine, eindeutig prüfbare Codeänderung;
2. repositoryweite Änderung mit mehreren Dateien;
3. Fehlersuche anhand von Logs und Tests;
4. Tool-Aufruf mit korrektem JSON-Schema;
5. mehrstufige Aufgabe mit fehlgeschlagenem ersten Ansatz;
6. lange Kontextaufgabe mit relevanten Informationen an verschiedenen Stellen;
7. Refactoring mit unverändertem Verhalten;
8. Dokumentations- und Konfigurationsänderung;
9. mathematisch-logische Kontrollaufgabe; und
10. Negativtest, bei dem das Modell fehlende Informationen erkennen soll.
Pro Aufgabe und Profil werden soweit praktikabel mindestens drei Läufe
durchgeführt. Für Leistungsmessungen werden zusätzlich deterministische
Prompts mit festem Seed verwendet. Reale Agentenaufgaben behalten die von Qwen
empfohlenen Samplingwerte.
Für AB1 wird die Reihenfolge pro Aufgabe zwischen Swift und Unsloth gewechselt.
Nach jedem Profilwechsel wird ein definierter Warm-up-Request ausgeführt; seine
Zeit fließt nicht in den fachlichen Vergleich ein. Modellladezeit und Warm-up
werden separat erfasst. Container-Digest, Prompt, Systemnachricht, Toolschema,
Seed, Kontext, Sampling und Ausgabegrenze bleiben innerhalb einer Serie
identisch. Die automatischen Prüfungen und die manuelle Bewertungsrubrik werden
vor dem ersten gewerteten Lauf festgelegt.
Jede abgeschlossene Serie erhält im Repository einen datierten
Evaluierungsdatensatz mit Profilmanifest, Aufgabenrevision, Rohmesswerten und
Kurzbericht. Modell- oder Containerdateien sowie sensible Promptinhalte werden
nicht in Git gespeichert. Der Bericht muss erkennen lassen, welche Läufe wegen
technischer Fehler wiederholt oder aus der Wertung genommen wurden.
Zu erfassen sind:
- fachlicher Erfolg und bestandene Tests;
- Zahl der Tool-Aufrufe, Fehlversuche und Wiederholungen;
- Prompt-, Reasoning-, Antwort- und Gesamttoken;
- Time to First Token, Prompt Processing und Generationstempo;
- gesamte Laufzeit bis zum erfolgreichen Abschluss;
- Spitzen- und stabiler VRAM-Verbrauch je GPU;
- CPU- und RAM-Verbrauch;
- GPU-Aufteilung und etwaige CPU-Offloads;
- Abbruch, OOM, fehlerhafte Tool-Formate oder unvollständige Antworten.
## 6. Vorläufige Abnahmekriterien
Ein Kandidat wird zum bevorzugten Production-Profil, wenn:
- das 128k-Profil ohne OOM startet und einen einstündigen gemischten Test
ohne Containerneustart oder GPU-Fehler besteht;
- alle vorgesehenen Modell-Layer auf die GPUs ausgelagert werden;
- beide GPUs genutzt werden und keine Karte ihren verfügbaren VRAM erschöpft;
- API-Antwort, Reasoning-Trennung und Tool Calls dem erwarteten Schema
entsprechen;
- die Aufgabensuite höchstens einen Erfolg weniger als der
jeweils andere Vergleichskandidat erzielt;
- keine systematische Regression bei Mathematik- oder Tool-Aufgaben sichtbar
wird; und
- der Median der generierten Gesamttoken oder der Zeit bis zum erfolgreichen
Abschluss gegenüber dem anderen Profil praktisch relevant sinkt.
Als Zielwert für die Effizienz gilt zunächst eine Reduktion von mindestens
20 Prozent. Die vom Hersteller berichteten 30 bis 50 Prozent sind eine
Hypothese, kein lokales Abnahmekriterium.
MTP wird nur übernommen, wenn es in einer A/B-Messung derselben Sitzung den
Median der Generationsleistung oder End-to-End-Laufzeit um mindestens
10 Prozent verbessert, ohne Stabilität, Tool-Format oder Abschlussquote zu
verschlechtern. Einzelne schnelle Läufe reichen nicht als Nachweis.
## 7. Freigabe und Rollback
Jedes Modellprofil erhält einen stabilen Namen, eine unveränderliche Quelle,
eine SHA-256-Prüfsumme und einen dokumentierten Lizenzstand. Testing und
Production referenzieren dieselbe geprüfte Datei aus dem gemeinsamen
schreibgeschützten Modellbestand.
Ein Upgrade erzeugt ein neues Profil. Das bisherige Modell bleibt bis zum
abgeschlossenen Production-Test erhalten. Der Rollback ändert nur den
versionierten Profilverweis und startet die betroffene Umgebung neu. Ein
unbeaufsichtigter Download von `main`, `latest` oder einer beweglichen
Quantisierungsbezeichnung ist ausgeschlossen.
Swift und Unsloth werden als getrennte Profile installiert. Das Ergebnis kann
ein bevorzugtes Standardprofil und ein weiterhin verfügbares alternatives
Profil sein; beide dürfen nur über den kontrollierten Umgebungswechsel
aktiviert werden.
## 8. Offene Punkte
- Lizenz und tatsächlichen Verwendungsrahmen des Swift-Modells vor Production
bestätigen;
- maximal zulässige Antwortdauer und Ausgabegröße für externe Nutzer
festlegen;
- entscheiden, ob Vision Bestandteil des ersten Production-Profils wird;
- Metrikerfassung für Reasoning- und Antworttoken mit der später gewählten
API-Policy-Schicht abstimmen;
- Aufgabenbestand und erwartete Ergebnisse der lokalen Suite versionieren; und
- nach Veröffentlichung bewerten, ob Swift 1.5 oder eine geprüfte
Swift-Dynamic-Quantisierung als neuer Kandidat aufgenommen wird.
## 9. Quellen
- [Qwen3.8-27B – Modellkarte](https://huggingface.co/Qwen/Qwen3.8-27B)
- [Swift-Qwen3.8-27B – Modellkarte und Benchmarks](https://huggingface.co/ukisai/Swift-Qwen3.8-27b)
- [Swift-Qwen3.8-27B-GGUF – Quantisierungen, KV-Angaben und MTP](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
- [Unsloth Qwen3.8-27B-GGUF – Modellkarte und Dateien](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)
- [Unsloth UD-Q6_K – Datei und Prüfsumme](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/blob/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf)
- [llama.cpp Server – aktuelle Parameterreferenz](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md)
- [llama.cpp MTP-/ROCm-Communitybericht](https://github.com/ggml-org/llama.cpp/discussions/27950)
Community-Berichte dienen ausschließlich der Hypothesenbildung. Freigaben
beruhen auf reproduzierbaren Messungen des gepinnten eigenen Stacks.
@@ -0,0 +1,563 @@
# Technischer Arbeitsentwurf
**Dokumenten-ID:** GNAI-010-TECH
**Titel:** `llama.cpp`-Serving mit ROCm und rootless Podman
**Stand:** 2026-09-21
**Status:** Technische Sollkonfiguration für Testing; noch nicht implementiert
**Referenzen:** [Modellbewertung](GNAI-009-PLAN_Llama-Model-Evaluation.md), [Ansible-Deployment](GNAI-008-PLAN_Ansible-Deployment.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md), [Ansible-Anleitung](../../../../ansible/README.md)
> Dieses Dokument beschreibt den vorgesehenen technischen Sollzustand. Es ist
> keine Betriebs-SOP und kein Nachweis, dass Modell, Quadlet oder Inferenz
> bereits eingerichtet wurden. Abweichungen aus der Implementierung werden vor
> einer Production-Freigabe hier nachgeführt.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-21 | 0.4 | spektr / Codex | Serving von der noch zu entscheidenden externen API-Policy-Schicht entkoppelt |
| 2026-09-21 | 0.3 | spektr / Codex | je Modell ein festes Quadlet vorgesehen; Swift und Unsloth durch kontrollierten Stop/Start gegenseitig ausgeschlossen; Router-Modus zurückgestellt |
| 2026-09-21 | 0.2 | spektr / Codex | Unsloth UD-Q6_K als zweites installiertes Modellprofil und kontrollierten Profilwechsel für A/B-Tests ergänzt |
| 2026-09-21 | 0.1 | spektr / Codex | erstes technisches Profil für Swift Q6_K, 128k, Q8-KV, rootless Quadlet und gestufte MTP-Abnahme erstellt |
---
## 1. Geltungsbereich
Die Spezifikation umfasst zwei feste `llama-server`-Quadlets in der
Testing-Umgebung, von denen immer genau eines aktiv ist. Sie legt fest:
- welche Container- und Modellartefakte verwendet werden;
- wie Modell und Konfiguration gespeichert werden;
- welche Geräte und Netzwerke der Container erhält;
- welche `llama-server`-Parameter als Ausgangsprofil gelten;
- wie VRAM-Annahmen behandelt werden; und
- welche technischen Prüfungen vor der Übernahme nach Production bestehen
müssen.
API-Policy-Schicht, öffentliche Veröffentlichung, Nutzer-SOPs und die zeitliche
Umschaltung zwischen Production und Testing erhalten eigene Rollen und
Dokumente. Ob dafür LiteLLM eingesetzt wird, entscheidet GNAI-001 erst vor der
Veröffentlichung. `llama-server` wird nicht direkt für LAN, VPN oder Internet
veröffentlicht.
## 2. Bereits belegte Voraussetzungen
Am 20.09.2026 wurden auf `ghostnet-ki` folgende Grundlagen nachgewiesen:
- Debian 13.6 und Podman 5.4.2 mit cgroup v2 und `crun`;
- rootless Konto `gnai-testing` mit UID/GID 981 und persistentem
systemd-User-Manager;
- `/dev/kfd`, `/dev/dri/renderD128` und `/dev/dri/renderD129`;
- zwei RX 6900 XT als getrennte ROCm-Geräte und `gfx1030`-Agents; und
- erfolgreicher Start des offiziellen `llama.cpp`-ROCm-Images ohne
`HSA_OVERRIDE_GFX_VERSION`.
Modellladen, 128k-Kontext, Multi-GPU-Layer-Verteilung und Inferenz wurden noch
nicht nachgewiesen.
## 3. Unveränderliche Artefakte
### 3.1 Container
| Feld | Wert |
| --- | --- |
| Upstream | `ghcr.io/ggml-org/llama.cpp:server-rocm` |
| verwendete Referenz | `ghcr.io/ggml-org/llama.cpp@sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4` |
| Architektur | amd64 |
| Build | `b11058` |
| Revision | `f072b103714dfa1eee531f80b24512faf38e3dd2` |
| Status | ROCm-Geräteerkennung unter `gnai-testing` bestanden |
Der Dienst verwendet ausschließlich den Digest. Ein bewegliches Tag darf in
Testing zur Recherche aufgelöst, aber nicht in ein freigegebenes Profil
übernommen werden.
### 3.2 Swift-Modellprofil
| Feld | Wert |
| --- | --- |
| Repository | `ukisai/Swift-Qwen3.8-27B-GGUF` |
| Repository-Revision | `eb0e3a7dc70643c2ab920f5133750d02c20849ff` |
| Datei | `Swift-Qwen3.8-27B-Q6_K.gguf` |
| veröffentlichte Größe | 22,9 GB |
| SHA-256 | `7f4de8abd5446c08b0f975a1b38e43d02639f4ae9ecdd2ddfd5c5b0f612bda59` |
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/Swift-Qwen3.8-27B-Q6_K.gguf` |
| API-Alias | `swift-qwen3.8-27b-q6k` |
Die Prüfsumme stammt aus der vom Herausgeber veröffentlichten
[`SHA256SUMS`](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/blob/main/SHA256SUMS)-Datei.
Ansible muss die SHA-256-Prüfsumme während des Downloads prüfen. Eine bereits
vorhandene Datei mit abweichender Prüfsumme führt zum Abbruch; sie wird nicht
stillschweigend überschrieben oder verwendet.
### 3.3 Unsloth-Vergleichsprofil
| Feld | Wert |
| --- | --- |
| Repository | `unsloth/Qwen3.8-27B-GGUF` |
| Repository-Revision | `4ca720788d1e01f1bff70c033e0d0028fd02e502` |
| Datei | `Qwen3.8-27B-UD-Q6_K.gguf` |
| veröffentlichte Größe | 22 GB |
| SHA-256 | `c9c206812fbe4ac7b76a729e25928b63f2ae89d37f69da7a71c20aec763cd436` |
| Download | `https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf` |
| API-Alias | `unsloth-qwen3.8-27b-ud-q6k` |
Die SHA-256-Prüfsumme ist in den Xet-Dateimetadaten des Herausgebers
veröffentlicht. Für Download, vorhandene Dateien und Fehlerfälle gelten
dieselben Regeln wie beim Swift-Artefakt. Beide Modelldateien werden
bereitgestellt, bevor die A/B-Serie beginnt.
### 3.4 Optionaler Vision-Projektor
Der erste Text-/Coding-Test lädt keinen Vision-Projektor. Dadurch bleibt rund
ein weiteres GiB für KV-Cache und Laufzeitpuffer verfügbar und die
Grundabnahme erhält weniger Variablen.
Für eine spätere Vision-Stufe ist vorgesehen:
| Feld | Wert |
| --- | --- |
| Datei | `mmproj-Swift-Qwen3.8-27B-F16.gguf` |
| veröffentlichte Größe | 928 MB |
| SHA-256 | `daa1116c9422fa390cc8688495da0e91781f92841dfc3b31a378ff252571745a` |
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/mmproj-Swift-Qwen3.8-27B-F16.gguf` |
Der Projektor wird erst heruntergeladen und mit `--mmproj` eingebunden, wenn
die Vision-Teststufe ausdrücklich aktiviert wird.
### 3.5 Lizenz und Herkunft
Das Qwen-Basismodell und das Unsloth-GGUF-Repository stehen laut ihren
Modellkarten unter Apache-2.0. Die Swift-Änderungen werden laut
Modellkarte unter der Swift Open License 1.0 angeboten. Die Modellkarte nennt
eine kostenlose persönliche, Forschungs-, Bildungs-, Evaluierungs- und
kommerzielle Nutzung bis zu einer dort definierten Umsatzgrenze; oberhalb
davon gelten zusätzliche Lizenzbedingungen.
Vor einer externen oder kommerziellen Production-Nutzung wird der dann
aktuelle Lizenztext gegen den konkreten Betreiber- und Nutzungsrahmen geprüft.
Repository-Revision, Modellkarte, Prüfsummen und Lizenzhinweis werden als
Herkunftsnachweis zum Profil dokumentiert.
## 4. Verzeichnis- und Rechtekonzept
Modelle sind große, unveränderliche und nicht geheime Artefakte. Production
und Testing sollen dieselbe geprüfte Datei lesen, ohne sie in getrennten
Podman-Stores zu duplizieren.
Vorgesehene Hostpfade:
```text
/var/lib/ghostnet-ai/shared/models/
├── swift-qwen3.8-27b/
│ ├── Swift-Qwen3.8-27B-Q6_K.gguf
│ ├── mmproj-Swift-Qwen3.8-27B-F16.gguf # erst in der Vision-Stufe
│ └── SOURCE.yml
└── unsloth-qwen3.8-27b/
├── Qwen3.8-27B-UD-Q6_K.gguf
└── SOURCE.yml
```
| Objekt | Eigentümer | Modus | Begründung |
| --- | --- | --- | --- |
| Modellverzeichnisse | `root:root` | `0755` | durch Deployment verwaltet, für beide Umgebungen lesbar |
| GGUF-Dateien | `root:root` | `0644` | unveränderlicher, nicht geheimer gemeinsamer Bestand |
| `SOURCE.yml` | `root:root` | `0644` | nachvollziehbarer Herkunftsnachweis |
Der Container bindet nur das zum aktiven Profil gehörende Modellverzeichnis
als `/models:ro` ein. Er erhält keinen Schreibzugriff auf den gemeinsamen Bestand.
Downloads erfolgen atomar in ein temporäres Ziel und werden erst nach
erfolgreicher Prüfsumme an den endgültigen Pfad verschoben.
Vor dem Download beider Modelle prüft Ansible mindestens 70 GiB freien Platz
auf dem Zieldateisystem. Das deckt rund 45 GB endgültigen Modellbestand, die
größte temporäre Downloaddatei und einen kleinen Betriebspuffer ab. Bereits
korrekt vorhandene Artefakte werden bei der Berechnung nicht erneut als
Downloadbedarf angesetzt.
## 5. Rootless Quadlet
### 5.1 Ablage und Identität
Ansible rendert für jedes Modell ein eigenes, rootverwaltetes Testing-Quadlet:
```text
/etc/containers/systemd/users/981/
├── gnai-testing-llama-swift.container
└── gnai-testing-llama-unsloth.container
```
Sie werden vom persistenten systemd-User-Manager von `gnai-testing`
ausgeführt. Die Units heißen `gnai-testing-llama-swift.service` und
`gnai-testing-llama-unsloth.service`. Beide verwenden dasselbe gepinnte Image,
aber einen eigenen Containernamen, Modellpfad, API-Alias und Parametersatz.
Podman speichert das identische Image nur einmal im rootless Image-Store.
### 5.2 Containeroptionen
Der `[Container]`-Abschnitt muss mindestens folgende Eigenschaften abbilden:
| Quadlet-Eigenschaft | Sollwert |
| --- | --- |
| `Image=` | gepinnter Digest aus Abschnitt 3.1 |
| `Pull=` | `never`; Images werden vor dem Dienststart kontrolliert importiert |
| `AddDevice=` | `/dev/kfd`, `/dev/dri/renderD128`, `/dev/dri/renderD129` |
| `GroupAdd=` | `keep-groups` |
| `Volume=` | Modellverzeichnis nach `/models:ro` |
| `Network=` | dediziertes Testing-Netz für `llama.cpp` und eine spätere API-Policy-Schicht |
| `NetworkAlias=` | `llama-cpp`; wegen gegenseitigem Ausschluss immer nur einmal aktiv |
| `PublishPort=` | nicht gesetzt |
| `ReadOnly=` | nach erfolgreichem Starttest `true` |
| `SecurityLabelDisable=` | nicht gesetzt, solange SELinux auf dem Host nicht aktiv ist |
Podman dokumentiert `AddDevice=` und `GroupAdd=keep-groups` direkt als
Quadlet-Optionen. Freie `PodmanArgs=` werden für diese Funktionen nicht
benötigt. Die Render-Nodes werden weiterhin aus der Hardwareerkennung der
Ansible-Rolle übernommen und nicht unabhängig als zweite Hardwarequelle
konfiguriert.
### 5.3 systemd-Verhalten und gegenseitiger Ausschluss
Vorgesehene Servicewerte:
```ini
[Service]
Restart=on-failure
RestartSec=10
TimeoutStartSec=900
```
Der erhöhte Start-Timeout berücksichtigt Modellinitialisierung und einen
möglichen kontrollierten Erstimport. Das Image und das Modell sollen dennoch
vor dem Dienststart bereitstehen. Ein normaler administrativer Stop darf nicht
durch eine `always`-Policy sofort rückgängig gemacht werden.
Das Testing-Quadlet erhält keinen `[Install]`-Abschnitt und wird nicht für
`default.target` aktiviert. Es startet ausschließlich für ein bewusst
geöffnetes Testfenster. Die spätere Production-Unit kann nach bestandener
Umschalt- und Neustartabnahme gezielt aktiviert werden.
Die Units erhalten folgende negative Abhängigkeit und eine eindeutige
Reihenfolge:
```ini
# Swift-Unit
[Unit]
Conflicts=gnai-testing-llama-unsloth.service
Before=gnai-testing-llama-unsloth.service
# Unsloth-Unit
[Unit]
Conflicts=gnai-testing-llama-swift.service
```
`Conflicts=` sorgt dafür, dass der Start einer Unit die andere stoppt. Das
einmalig gesetzte `Before=` ergänzt die nötige Ordnungsabhängigkeit; systemd
führt dadurch den Stop vollständig vor dem neuen Start aus. Der reguläre
Profilwechsel bleibt zusätzlich eine explizite, sequenzielle Operation:
1. aktuell aktive Modell-Unit stoppen;
2. auf vollständiges Ende des Containers warten;
3. prüfen, dass kein `llama-server` mehr die GPUs belegt;
4. gewünschte Modell-Unit starten; und
5. Alias, Health, Modellrevision und GPU-Belegung prüfen.
Das Ansible-Playbook und die spätere Betriebs-SOP kapseln diese Reihenfolge.
Ein direkter Start der jeweils anderen Unit löst denselben geordneten Wechsel
aus. Der gleichzeitige Start beider Units in einer Transaktion gilt als Fehler
und ist kein unterstützter Betriebsweg.
### 5.4 Entscheidung gegen den Router-Modus in der ersten Ausbaustufe
Der gepinnte `llama.cpp`-Build unterstützt bereits einen Router-Modus mit
`--models-preset`, `--models-max`, automatischem Laden sowie den Endpunkten
`/models/load` und `/models/unload`. Technisch wäre damit ein einzelner
Router-Container für beide Modelle möglich.
Für die erste Abnahme bleibt jedes Quadlet im einfacheren Single-Model-Modus.
Damit sind Startparameter, Logs, Health, VRAM-Verbrauch und Fehlerzustand einem
Modell eindeutig zugeordnet. Außerdem benötigt der interne Dienst keine
dynamische Modellverwaltungs-API. Nach stabiler Abnahme kann der Router-Modus
separat bewertet werden; er ist keine Voraussetzung für eine externe
API-Policy-Schicht.
## 6. `llama-server`-Ausgangsprofil
Das erste 128k-Zielprofil entspricht konzeptionell folgender Kommandozeile:
```text
--host 0.0.0.0
--port 8080
--model /models/Swift-Qwen3.8-27B-Q6_K.gguf
--alias swift-qwen3.8-27b-q6k
--parallel 1
--ctx-size 131072
--n-predict 32768
--n-gpu-layers all
--split-mode layer
--tensor-split 1,1
--flash-attn on
--cache-type-k q8_0
--cache-type-v q8_0
--jinja
--reasoning on
--reasoning-effort xhigh
--reasoning-budget -1
--temp 1.0
--top-p 0.95
--top-k 20
--min-p 0.0
--presence-penalty 0.0
--repeat-penalty 1.0
--no-webui
--metrics
```
Die Parameter werden als strukturierte Ansible-Variablen verwaltet und von
einem Template in `Exec=` gerendert. Eine freie, manuell gepflegte
Environment-Datei ist nicht die Konfigurationsquelle.
Vor der Implementierung wird `llama-server --help` im exakt gepinnten Image
gegen alle Optionen geprüft. Falls dieser Build `all` für
`--n-gpu-layers` nicht akzeptiert, wird der bereits bewährte numerische Wert
`999` verwendet und die Abweichung dokumentiert.
Das Unsloth-Profil verwendet dieselbe Kommandozeile mit diesen gezielten
Abweichungen:
| Parameter | kontrollierter A/B-Test | Praxisvergleich |
| --- | --- | --- |
| `--model` | `/models/Qwen3.8-27B-UD-Q6_K.gguf` | identisch |
| `--alias` | `unsloth-qwen3.8-27b-ud-q6k` | identisch |
| `--reasoning-effort` | `xhigh` | `medium` |
Alle nicht aufgeführten Parameter bleiben gegenüber dem Swift-Profil
unverändert.
### 6.1 Reasoning
`xhigh` ist der Ausgangswert für Swift. `--reasoning-budget -1` vermeidet einen
kurzen serverseitigen Thinking-Cap. `--n-predict 32768` begrenzt dagegen die
gesamte Ausgabe eines einzelnen Requests und schützt den zunächst manuell
betriebenen Testing-Dienst vor unbegrenzten Läufen.
Die Evaluierung enthält zusätzlich einen kontrollierten 65.536-Token-Lauf.
Vor Production wird entschieden, ob die allgemeine Obergrenze erhöht oder
später durch die API-Policy-Schicht pro Nutzer und Modell gesteuert wird. Ein Default von
2.048 oder 4.096 Token ist für dieses Profil nicht vorgesehen.
Die API muss Reasoning getrennt von der finalen Antwort liefern. Zunächst wird
das automatische `--reasoning-format` des gepinnten Builds geprüft. Ein fester
Wert wird erst gesetzt, wenn die Kombination aus `llama.cpp`, der gewählten
API-Policy-Schicht und den
verwendeten Clients mit einem reproduzierbaren Request abgenommen wurde.
Für Unsloth werden zwei Reasoning-Varianten gerendert. `xhigh` dient dem
kontrollierten A/B-Test mit Swift. `medium` bildet das geplante Praxisprofil
ab. Alle übrigen Laufzeitparameter bleiben innerhalb der jeweiligen
Vergleichsserie gleich.
### 6.2 Sampling
Die Werte entsprechen der Qwen-Modellkarte für Thinking. Sie bleiben Teil des
Modellprofils. Ein späteres Instruct-/Non-Thinking-Profil erhält eigene Werte
und überschreibt diese nicht stillschweigend.
### 6.3 Multi-GPU
`layer` und `1,1` sind die Ausgangskonfiguration für die zwei gleich großen
Karten. Der zweite PCIe-Slot ist langsamer angebunden, weshalb experimentelle
parallele Split-Verfahren nicht vor der stabilen Layer-Baseline aktiviert
werden.
Die Startlogs müssen für beide Karten den belegten VRAM und die tatsächliche
Layer-Verteilung ausweisen. `1,1` ist eine Gewichtung, keine Garantie für exakt
gleiche Speicherbelegung. Ein abweichender Split wird ausschließlich anhand
gemessener freier Reserven und Durchsatzwerte eingeführt.
### 6.4 Vision
Der Haupttest verwendet `--model` mit einer lokalen Datei und übergibt kein
`--mmproj`. Erst die Vision-Stufe bindet den Projektor ein. Dadurch werden
Text-/Tool-Qualität, Modellformat und 128k-Speicherbedarf unabhängig vom
Vision-Encoder geprüft.
### 6.5 MTP
MTP bleibt im Ausgangsprofil aus. Die erste Variante ergänzt:
```text
--spec-type draft-mtp
--spec-draft-n-max 2
```
Erst nach bestandener A/B-Messung folgt optional `n-max=3`. Draft-Kontext,
VRAM-Zuwachs, Akzeptanzrate und tatsächlicher End-to-End-Gewinn werden
protokolliert. MTP wird nicht zusammen mit Vision erstmals aktiviert.
## 7. VRAM-Modell
Die Hardware stellt nominell 2 × 16 GiB VRAM bereit. Die Summe allein genügt
nicht als Fit-Nachweis, weil llama.cpp Gewichte, KV-Cache, Rechenpuffer und
gegebenenfalls den Vision- oder Draft-Kontext nicht zwingend gleichmäßig auf
beide Karten verteilt.
### 7.1 Gewichte
Die veröffentlichte Swift-Q6_K-Dateigröße von 22,9 GB entspricht grob
21,3 GiB. Unsloth UD-Q6_K ist mit veröffentlichten 22 GB ähnlich groß. Die
tatsächliche GPU-Belegung kann durch Metadaten, Ausrichtung, mmap und
Backend-Puffer abweichen und wird aus den Startlogs übernommen.
### 7.2 KV-Cache
Die Swift-Modellkarte berechnet für FP16-K/V 64 KiB pro Token, weil nur 16 der
64 Blöcke Full Attention verwenden:
| Kontext | FP16-K/V laut Modellkarte | Q8-K/V, rechnerische Näherung |
| ---: | ---: | ---: |
| 65.536 | 4 GiB | etwa 2 GiB |
| 131.072 | 8 GiB | etwa 4 GiB |
| 196.608 | 12 GiB | etwa 6 GiB |
| 262.144 | 16 GiB | etwa 8 GiB |
Die Q8-Spalte ist eine Halbierungsnäherung und kein gemessener Wert. Block- und
Backend-Overhead sowie die Verteilung je GPU werden beim Modellstart erfasst.
### 7.3 Grobe Zielrechnung
Für das textbasierte 128k-Profil ergibt sich vor der Messung:
```text
Q6_K-Gewichte etwa 21,3 GiB
Q8-K/V bei 128 KiToken etwa 4,0 GiB
ROCm-/Graph-/Compute-Puffer noch zu messen
──────────────────────────────────────────
Zwischensumme vor Puffern etwa 25,3 GiB
```
Mit Vision kommen rund 0,9 GB Dateigröße sowie zusätzliche Laufzeitpuffer
hinzu. MTP kann ebenfalls weiteren Draft-Kontext und Puffer belegen. Deshalb
werden beide Funktionen getrennt zugeschaltet und Q5_K_M bleibt als
Ressourcen-Fallback vorgesehen.
## 8. Netzwerk und API-Grenze
`llama-server` hört im Container auf TCP 8080. Es gibt kein `PublishPort=` und
keine Host-Portweiterleitung. Eine später beschlossene API-Policy-Schicht wird
in dasselbe umgebungsspezifische rootless Podman-Netz aufgenommen und erreicht
den Dienst über `http://llama-cpp:8080`.
Direkte Funktionsprüfungen erfolgen aus dem Container-Netz oder über einen
kurzlebigen administrativen Prüfcontainer. Externe Nutzer sprechen später nur
die freigegebene API-Policy-Schicht beziehungsweise die dafür vorgesehene
veröffentlichte Anwendung an.
Der integrierte Webserver wird mit `--no-webui` betrieben. Metriken und Health
bleiben ausschließlich im internen Container-Netz. Vor der Health-Quadlet-
Konfiguration wird geprüft, welches HTTP-Werkzeug das gepinnte Image enthält;
die Abnahme darf nicht von einem im Image fehlenden `curl` abhängen.
## 9. Ansible-Rollenentwurf
Die spätere Rolle `llama_cpp` soll folgende Zuständigkeiten besitzen:
1. Variablen und Artefaktmanifest vollständig validieren.
2. Modellverzeichnisse mit festgelegten Rechten anlegen.
3. beide Modelle über revisionsgebundene URLs und SHA-256 atomar
bereitstellen.
4. Gepinntes Containerimage im rootless Store der Zielumgebung sicherstellen.
5. Render-Nodes aus `amd_gpu_access` übernehmen.
6. umgebungsspezifisches Podman-Netz als Quadlet rendern.
7. beide `llama.cpp`-Quadlets rendern und Generator-Dry-Run ausführen.
8. systemd-User-Manager neu laden und den ausgewählten Testing-Dienst bewusst
starten.
9. Startlog, API-Health, Modellalias und GPU-Verteilung prüfen.
10. einen kleinen Inferenztest ausführen und Ergebnis protokollieren.
Die Rollenvariablen enthalten eine Map unveränderlicher Modellprofile und
einen einzelnen Selektor wie `llama_cpp_active_profile`. Die Rolle rendert
beide Quadlets, startet aber nur die ausgewählte Unit. Ein Profilwechsel
verändert keine Unit-Datei, sondern stoppt die aktive Unit vollständig und
startet anschließend die bereits gerenderte Ziel-Unit.
Modell-Download und Dienstaktivierung erhalten getrennte Tags. Dadurch kann
ein großes Artefakt vorbereitet werden, ohne den aktiven Stack umzuschalten.
Check-Modus lädt kein Modell und startet keinen Dienst.
Production und Testing verwenden dieselbe Rollenlogik, aber getrennte
Variablen, Quadlets, Netzwerke, Podman-Stores und systemd-User-Manager. Die
Rolle darf nie beide GPU-Umgebungen gleichzeitig starten.
## 10. Technische Abnahme
### 10.1 Vor dem Start
- Modellrevision, Dateiname, Größe und SHA-256 stimmen mit dem Manifest
überein.
- Das Containerimage stimmt mit Digest und OCI-Labels überein.
- Beide Render-Nodes und `/dev/kfd` sind für `gnai-testing` zugreifbar.
- Production ist vollständig beendet und hält keine GPU-Prozesse.
- Der Quadlet-Generator meldet keine Fehler.
- Die generierte Unit enthält keinen veröffentlichten Host-Port.
### 10.2 Start mit 64k
- Container und Unit werden `active` und bleiben stabil.
- Modellformat und eingebettetes Chat-Template werden erkannt.
- alle Layer werden auf ROCm ausgelagert;
- beide GPUs erscheinen in den Startlogs;
- `/health` und `/v1/models` antworten erfolgreich;
- der API-Alias entspricht dem jeweils ausgewählten Profil;
- ein kurzer Thinking-Request liefert Reasoning und finale Antwort; und
- ein definierter Tool-Call ist syntaktisch gültig.
### 10.3 Start mit 128k
- kein OOM oder unbeabsichtigter CPU-Offload;
- freier VRAM je GPU und tatsächlicher KV-Verbrauch werden protokolliert;
- Prompt-Prefill wird stufenweise mit 8k, 32k, 64k und 128k geprüft;
- mindestens ein langer Request endet ohne Kontext- oder Parserfehler; und
- ein einstündiger gemischter Test erzeugt keinen Neustart, GPU-Reset oder
dauerhaft hängenden Slot.
Diese Abnahme wird zuerst für Swift und danach für Unsloth wiederholt. Erst
wenn beide Profile die technische Abnahme bestanden haben, beginnt die
fachliche A/B-Serie aus GNAI-009.
### 10.4 Wiederanlauf und Idempotenz
- ein zweiter identischer Ansible-Lauf meldet `changed=0`;
- ein kontrollierter Containerneustart erhält Profil und Modell;
- ein geplanter Hostneustart startet nur die freigegebene Umgebung;
- ein absichtlich gestoppter Testing-Dienst wird nicht ungewollt neu gestartet;
- ein Startversuch der zweiten Modell-Unit kann niemals beide Modelle
gleichzeitig im VRAM belassen;
- `podman ps`, systemd-Status und Health stimmen überein; und
- der Rückwechsel auf das vorherige Profil funktioniert ohne erneuten
Modelldownload.
## 11. Noch nicht festgelegte Werte
- genaue Health-Implementierung im gepinnten Containerimage;
- CPU-Thread-, Batch- und UBatch-Tuning für den i5-13600K;
- verbindliche maximale Ausgabe für Production und einzelne Nutzer;
- Aufnahme des Vision-Projektors in das erste Production-Profil;
- gemessener optimaler Split bei unterschiedlicher PCIe-Anbindung;
- Aktivierung und Parameter von MTP;
- Aufbewahrungs- und Löschregeln für abgelöste Modellartefakte.
## 12. Quellen
- [Qwen3.8-27B – Architektur, Reasoning und Sampling](https://huggingface.co/Qwen/Qwen3.8-27B)
- [Swift-Qwen3.8-27B – Training und Benchmarks](https://huggingface.co/ukisai/Swift-Qwen3.8-27b)
- [Swift-Qwen3.8-27B-GGUF – Dateien, Quantisierung und MTP](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
- [Swift-GGUF-Prüfsummen](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/blob/main/SHA256SUMS)
- [Unsloth Qwen3.8-27B-GGUF – Modellkarte](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)
- [Unsloth UD-Q6_K – Datei und SHA-256](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/blob/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf)
- [llama.cpp Server-Parameter und Router-Modus des gepinnten Builds](https://github.com/ggml-org/llama.cpp/blob/f072b103714dfa1eee531f80b24512faf38e3dd2/tools/server/README.md)
- [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [systemd-Unit-Abhängigkeiten](https://manpages.debian.org/trixie/systemd/systemd.unit.5.en.html)
+19
View File
@@ -0,0 +1,19 @@
# KI-Dokumentation
Dieser Bereich enthält die fachliche Zielarchitektur und Deployment-Planung für
den KI-Stack auf dem LLM-Server. Gemeinsame Hostgrundlagen liegen im
[Geräteindex](../README.md), übergreifende Netzwerk- und Sicherheitsgrundlagen
in der [Plattformdokumentation](../../README.md). Der CPU-basierte
Video-Encoding-Workload besitzt einen
[eigenen Dokumentationsbereich](../encoding/GNENC-000-DOC_Index.md).
| Dokument | Inhalt |
| --- | --- |
| [GNAI-001](../../../GNAI-001-PLAN_Project-Roadmap.md) | priorisierter High-Level-Implementierungsplan mit Entscheidungstoren und Abnahmekriterien |
| [GNAI-004](GNAI-004-INFRA_Architecture.md) | Zielarchitektur, Komponenten und Datenwege des KI-Stacks |
| [GNAI-008](GNAI-008-PLAN_Ansible-Deployment.md) | nicht finaler Arbeitsentwurf für Ansible- und Gitea-Deployment |
| [GNAI-009](GNAI-009-PLAN_Llama-Model-Evaluation.md) | nicht finaler A/B-Plan für Swift, Unsloth, Reasoning-, Kontext- und MTP-Abnahme |
| [GNAI-010](GNAI-010-TECH_Llama-Serving.md) | technische Sollkonfiguration für zwei geprüfte Modellprofile auf rootless `llama.cpp` mit ROCm |
Der übergreifende Projektstand steht im
[zentralen Dokumentationsindex](../../../GNAI-000-DOC_Index.md).
@@ -0,0 +1,32 @@
# Video-Encoding – Dokumentationsindex und Projektstand
**Dokumenten-ID:** GNENC-000-DOC
**Stand:** 2026-10-05
**Status:** Nicht finaler Arbeitsstand; Encoding-Workload noch nicht installiert
## Dokumente
| Dokument | Zweck / Zielgruppe |
| --- | --- |
| [GNENC-001-PLAN_Architecture.md](GNENC-001-PLAN_Architecture.md) | vorläufige Architektur, Repository-Grenzen, Ressourcenmodell und offene Entscheidungen; Infrastrukturadministratoren |
## Bestätigter Stand
- Das gemeinsame Infrastructure-Repository bleibt die einzige Quelle für den
Ansible-Sollzustand des physischen Servers.
- Encoding-Dokumentation verwendet innerhalb des LLM-Server-Gerätebereichs
`docs/platform/llm-server/encoding/` und die Dokumentenkennung `GNENC`.
- Encoding ist nach Betreiberfestlegung CPU-basiert und darf parallel zur
jeweils aktiven KI-Umgebung laufen.
- Encoding soll ausschließlich außerhalb der Stoßzeiten, zunächst nachts,
automatisch ausgeführt werden.
- Das gesperrte Servicekonto `gn-encoding` mit UID/GID 982 und eigenem
rootless Podman-Store ist eingerichtet und verifiziert.
- Es sind noch kein Encoding-Image, kein Container, keine Quadlet-Unit und kein
Timer eingerichtet.
## Nächste Festlegungen
Vor einer Encoding-Implementierung müssen insbesondere Encoder und
Container-Image, Ein- und Ausgabeverfahren, genaue Zeitfenster, CPU-/I/O-Grenzen,
Abbruchverhalten am Ende des Zeitfensters und Aufbewahrung festgelegt werden.
@@ -0,0 +1,142 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNENC-001-PLAN
**Titel:** CPU-basiertes Video-Encoding auf dem KI-Server
**Stand:** 2026-10-05
**Status:** Nicht finaler Arbeitsentwurf zur Persistierung der Planungsentscheidung
## 1. Zweck und Abgrenzung
Der physische Server `ghostnet-ki` soll neben den KI-Workloads einen getrennten
Workload zum Encoding von Videodateien ausführen. Der Encoding-Workload gehört
nicht zum LLM-Stack. Seine fachliche Architektur, Bedienung und späteren SOPs
werden deshalb in diesem eigenen Dokumentationsbereich geführt.
Ansible, Podman, Betriebssystem und hostweite Ressourcenregeln bleiben
gemeinsame Plattformbestandteile. Sie werden weiterhin ausschließlich im
Infrastructure-Repository verwaltet, damit nicht mehrere Repositories
unabhängig konkurrierende Sollzustände auf denselben Host anwenden.
## 2. Bestätigte Entscheidungen
| Thema | Entscheidung | Nachweisgrenze |
| --- | --- | --- |
| Repository | Host- und Deployment-Ansible bleibt im bestehenden Infrastructure-Repository | angenommen |
| Dokumentation | Encoding liegt unter `docs/platform/llm-server/encoding/` und verwendet die Kennung `GNENC` | umgesetzt |
| Ausführungsidentität | eigenes gesperrtes Konto `gn-encoding`, UID/GID 982 | eingerichtet und mit rootless Podman geprüft |
| Containertrennung | eigener rootless Podman-Store und eigener rootverwalteter Quadlet-Pfad | eingerichtet; noch keine Workload definiert |
| Rechenressource | Encoding ist nach Betreiberfestlegung rein CPU-basiert und erhält keinen GPU-Zugriff | Planungsentscheidung; reale Last noch nicht gemessen |
| Parallelität | darf parallel zu AI Production oder AI Testing laufen | Planungsentscheidung |
| Zeitsteuerung | automatische Ausführung ausschließlich außerhalb der Stoßzeiten, zunächst nachts | Richtung festgelegt; konkrete Uhrzeiten fehlen |
| Anwendungen | konkrete Encoder, Images, Profile und Queue sind noch nicht gewählt | offen |
## 3. Zielbild
```mermaid
flowchart LR
HOST[ghostnet-ki]
PROD[gnai-production\nrootless Podman]
TEST[gnai-testing\nrootless Podman]
ENC[gn-encoding\nrootless Podman]
GPU[beide GPUs]
CPU[CPU / I-O Budget]
TIMER[nächtliche systemd-Zeitsteuerung]
HOST --> PROD
HOST --> TEST
HOST --> ENC
PROD --> GPU
TEST --> GPU
ENC --> CPU
TIMER --> ENC
```
AI Production und AI Testing bleiben untereinander exklusiv. Der
Encoding-Workload ist von dieser KI-Umschaltung unabhängig und darf parallel
zur jeweils aktiven KI-Umgebung laufen. Hostwartung kann weiterhin einen Stopp
aller Workloads erfordern.
## 4. Ressourcen- und Zeitsteuerung
Die nächtliche Einplanung allein schützt Production nicht vollständig: Ein
Encoding-Lauf kann länger dauern als sein Startfenster, und Inferenz benötigt
trotz GPU-Schwerpunkt CPU für Tokenisierung, Netzwerk und Begleitdienste. Die
spätere Encoding-Unit soll deshalb mindestens verwalten:
- ein eindeutig definiertes Zeitfenster mit Zeitzone;
- Verhalten bei verpasstem Start, Neustart und überlangem Lauf;
- maximale CPU-Nutzung oder CPU-Gewichtung;
- niedrigere Scheduling-Priorität gegenüber interaktiven Diensten;
- I/O-Gewichtung und freien Speicher;
- geordneten Abbruch beziehungsweise Abschluss am Ende des erlaubten Fensters;
- Begrenzung auf genau einen gleichzeitigen Encoding-Job.
Konkrete Grenzwerte werden erst nach einem repräsentativen Encoding-Test unter
gleichzeitiger Inferenzlast festgelegt. `gn-encoding` erhält keine Mitgliedschaft
in GPU-Gruppen und keine `/dev/kfd`- oder `/dev/dri`-Weitergabe.
## 5. Repository-Grenzen
Vorgesehene Ansible-Struktur:
```text
ansible/
├── playbooks/
│ ├── host.yml
│ ├── ai.yml
│ ├── video-encoding.yml
│ └── site.yml
└── roles/
├── host_baseline/
├── podman_runtime/
├── workload_accounts/
├── ai_stack/
├── ai_environment_switch/
└── video_encoding/
```
`host.yml` verwaltet ausschließlich gemeinsame Plattformzustände.
`ai.yml` darf nur KI-Zustände verändern. `video-encoding.yml` verwaltet nur das
Konto `gn-encoding`, seine Pfade, Images, Quadlets, Timer und Health Checks. Ein
späteres `site.yml` kann die unterstützte Gesamtkonfiguration in definierter
Reihenfolge anwenden.
Ein separates Quellcode-Repository wird erst benötigt, wenn eigene
Encoding-Software, eine Oberfläche oder ein Queue-Dienst entwickelt wird. Ein
solches Repository erzeugt ein versioniertes Artefakt oder Image; ausschließlich
das Infrastructure-Repository legt fest, welche Version auf dem Server läuft.
## 6. Aktueller Implementierungsstand
Die gemeinsame Rolle `workload_accounts` hat am 20.09.2026 eingerichtet:
```text
Konto: gn-encoding (UID/GID 982)
Konfiguration: /etc/ghostnet-encoding
Daten: /var/lib/ghostnet-encoding
Eingang: /var/lib/ghostnet-encoding/input
Ausgang: /var/lib/ghostnet-encoding/output
Arbeitsdaten: /var/lib/ghostnet-encoding/work
Quadlets: /etc/containers/systemd/users/982
Podman-Store: /var/lib/ghostnet-encoding/.local/share/containers/storage
```
Passwort und interaktive Shell sind gesperrt. Der systemd-User-Manager läuft
dank Linger unabhängig von einer Anmeldung. Der Podman-Store wurde als
rootless und getrennt von beiden KI-Stores geprüft. Es wurden noch keine Images
oder Container angelegt.
## 7. Offene Entscheidungen
1. Welcher Encoder und welches fest versionierte Container-Image werden genutzt?
2. Welche Codecs, Qualitätsprofile und Hardwarekompatibilitäten sind erforderlich?
3. Woher kommen Eingabedateien, und wie werden fertige Dateien abgeholt?
4. Welches konkrete nächtliche Zeitfenster und welche Zeitzone gelten?
5. Darf ein laufender Job das Zeitfenster überschreiten oder wird er gestoppt?
6. Welche CPU-, Speicher- und I/O-Grenzen schützen die KI-Dienste?
7. Wie werden Warteschlange, Wiederholung, Teilresultate und Fehler behandelt?
8. Welche Aufbewahrungs-, Backup- und Löschregeln gelten für Video- und
Arbeitsdateien?
9. Welche Statusanzeige oder Benachrichtigung benötigen Nutzer?
Diese Werte werden nicht vorab in Playbooks angenommen.
@@ -0,0 +1,159 @@
# Ghostnet.AI-Host – Basisinstallation
**Dokumenten-ID:** GNAI-002-INFRA
**Titel:** Initialisierung und OS-Installation des KI-Hosts `10.0.0.90`
**Status:** Installationsanleitung mit lesendem Teilabgleich; vollständige Abnahme offen
**Referenzen:** -
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung der Änderung |
| --- | --- | --- | --- |
| 2026-08-24 | 1.0 | spektr | Erstellung des Initial-Dokuments (Hardware & OS) |
| 2026-08-24 | 1.1 | spektr | Detaillierung der BIOS-ReBAR-Settings, Netinst-CLI-Schritte, Post-Installation & Abnahmekriterien hinzugefügt. |
| 2026-09-16 | 1.2 | spektr | Benutzer- und Netzwerkaufgaben nach GNAI-003 verlagert; ungesicherte Downloadpfade und Domainvorgabe entfernt. |
| 2026-09-18 | 1.3 | spektr | GPU-Abnahme auf nachweisbare Geräteerkennung begrenzt; Firmwarepaket präzisiert. |
| 2026-09-19 | 1.4 | spektr | Getrennte SSH-Zugänge für `ghost` und `ghost-agent` als erfolgreich getestet übernommen. |
| 2026-09-22 | 1.5 | Codex | In die hostbezogene Struktur `platform/ghostnet-ai/` verschoben und Geltungsbereich präzisiert. |
---
## 1. Zweck und Geltungsbereich
Dieses Dokument beschreibt Hardware, BIOS und Debian-Installation des KI-Servers. Benutzer, statische Netzkonfiguration und SSH-Härtung werden in [GNAI-003](GNAI-003-INFRA_Users.md) eingerichtet. Die Schritte sind eine Anleitung, kein Nachweis ihrer Ausführung auf dem bestehenden Host.
---
## 2. Spezifikationen (Hardware-Konfiguration)
| Komponente | Spezifikation | Bemerkung |
| --- | --- | --- |
| **Gehäuse** | Silverstone SST-RM42-502 | Rackmount / Server-Chassis |
| **Mainboard** | ASRock Z790 PG Lightning | Socket LGA1700 |
| **CPU** | Intel Core i5-13600K | 14 Kerne (6P + 8E) |
| **CPU-Kühler** | Thermalright Peerless Assassin 120 Mini | - |
| **RAM** | Crucial Pro DDR5 RAM 64GB Kit (2x32GB) | Dimm Slot A2 & B2 |
| **PSU** | Corsair RM1200e | 1200W ATX 3.0 |
| **SSD** | 2 TB Samsung 970 EVO Plus | Primäre NVMe (Slot M2_1) |
| **GPU 1** | Sapphire Nitro+ AMD Radeon RX 6900 XT OC 16GB | PCIe Slot 1 (x16) |
| **GPU 2** | Sapphire Nitro+ AMD Radeon RX 6900 XT OC 16GB | PCIe Slot 2 (x8/x4 via PCH) |
### 2.1 BIOS-Konfiguration
Vor der Betriebssystem-Installation sind folgende Schritte im BIOS (Aufrufen via `DEL` oder `F2` beim Booten) durchzuführen:
1. **Firmware-Update (Instant Flash):**
* BIOS-Datei auf einen FAT32-formatierten USB-Stick entpacken.
* Im BIOS navigieren zu: `Tool` -> `Instant Flash`.
* BIOS-Version auf **`19.02`** (oder neuer) aktualisieren.
2. **RAM-Performance:**
* Navigieren zu: `OC Tweaker` -> `DRAM Configuration`.
* **Load XMP Setting:** auf **XMP Profile 1** stellen.
3. **Virtualisierung & PCI-Einstellungen (Wichtig für GPU/ROCm):**
* Navigieren zu: `Advanced` -> `CPU Configuration` -> **Intel Virtualization Technology** -> `Enabled`.
* Navigieren zu: `Advanced` -> `PCI Configuration`:
* **Above 4G Decoding:** `Enabled`
* **Re-Size BAR Support:** `Enabled`; tatsächliche GPU-Zuordnung nach der Installation prüfen. Aus der BIOS-Option allein folgt noch kein nachgewiesener ROCm-Leistungsgewinn.
---
## 3. Installation des Betriebssystems
### 3.1 Vorbereitung des Installationsmediums (am Admin-PC)
1. **Debian ISO herunterladen:** Die gewünschte Debian-Version und die passende `amd64`-Netinst-ISO auf [Debian CD Images](https://cdimage.debian.org/debian-cd/) auswählen. ISO und `SHA512SUMS` aus demselben Release-Verzeichnis herunterladen; Dateinamen nicht aus dieser Anleitung ableiten.
2. **Checksum verifizieren:**
```bash
sha512sum --ignore-missing -c SHA512SUMS
```
Erwartet wird eine `OK`-Zeile für die tatsächlich heruntergeladene ISO. Bei einem Fehler Download und Prüfsummenquelle kontrollieren.
3. **Bootfähigen USB-Stick erstellen:**
* [Ventoy](https://www.ventoy.net) auf dem USB-Stick installieren.
* Die heruntergeladene ISO-Datei direkt per Dateimanager auf die Ventoy-Partition kopieren.
---
### 3.2 Schritt-für-Schritt Installationsanleitung (Debian Netinst)
1. **Booten:**
* USB-Stick einstecken, System starten und per `F11` das Boot-Menü öffnen.
* USB-Stick auswählen -> Im Ventoy-Menü `debian-netinst.iso` wählen -> **Graphical Install** auswählen.
2. **Lokalisierung:**
* **Language:** `German - Deutsch`
* **Land/Territorium:** `Deutschland`
* **Tastaturlayout:** `Deutsch`
3. **Netzwerk & Hostname:**
* **Hostname:** `ghostnet-ki` (auf dem bestehenden KI-Server am 18.09.2026 per `hostnamectl` bestätigt).
* **Domain-Name:** Nur eine tatsächlich konfigurierte lokale Domain eintragen; andernfalls leer lassen. `ghostnet.ki` ist in dieser Dokumentation lediglich ein optionaler SSH-Alias.
* **Netzwerk:** Per DHCP konfigurieren lassen. *(Feste IP wird nach der Installation vergeben).*
4. **Benutzerverwaltung:**
* **Root-Passwort:** Starkes Passwort aus dem Passwort-Manager eintragen.
* **Neuer Benutzer:**
* Vollständiger Name: `Admin`
* Benutzername: vor der Installation festlegen. Der bestehende KI-Server verwendet `ghost` als initiales Administrationskonto; `admin` ist dort nicht vorhanden.
* Passwort: Sicheres Kennwort vergeben.
5. **Partitionierung (Manuell / Geführt):**
* Methode: `Geführt - gesamte Festplatte verwenden`.
* Disk auswählen: `Samsung SSD 970 EVO Plus 2TB`.
* Schema: `Alle Dateien auf eine Partition (für Anfänger empfohlen)`.
* **Wichtig:** Änderungen auf die Festplatte schreiben -> `Ja`.
6. **Paketmanager-Konfiguration:**
* Spiegelserver-Land: `Deutschland`
* `deb.debian.org` wählen.
* HTTP-Proxy: Leer lassen.
7. **Software-Auswahl (Software selection):**
* [_] Debian desktop environment *(ABWÄHLEN)*
* [_] GNOME / KDE / etc. *(ABWÄHLEN)*
* [x] **SSH server** *(AUSWÄHLEN)*
* [x] **standard system utilities** *(AUSWÄHLEN)*
8. **Bootloader (GRUB):**
* GRUB-Bootloader in den primären Laufwerksbereich installieren: `Ja`
* Gerät wählen: `/dev/nvme0n1` (Samsung SSD).
9. **Abschluss:**
* USB-Stick entfernen und auf `Weiter` klicken, um das System neu zu starten.
---
### 3.3 Grundlegende Werkzeuge und Firmware
Für die AMD-GPUs ist in Debian 13 das Paket [`firmware-amd-graphics`](https://packages.debian.org/trixie/firmware-amd-graphics) aus `non-free-firmware` vorgesehen. Das breitere Metapaket `firmware-linux-nonfree` zieht dieses Paket bereits als Abhängigkeit nach sich und wird für diesen Schritt nicht zusätzlich benötigt. Die folgende Anweisung gehört zu einer Neuinstallation oder zum späteren Ansible-Sprint, nicht zu diesem Dokumentationsabgleich:
```bash
# Vorher prüfen, ob die Debian-Paketquellen die Komponente non-free-firmware enthalten.
apt update
apt install -y curl wget git htop tmux pciutils firmware-amd-graphics
```
Für Sudo, Netzwerk und SSH mit [GNAI-003](GNAI-003-INFRA_Users.md) fortfahren. Vor Änderungen auf einem laufenden Host die dort dokumentierte Netzverwaltung prüfen.
---
## 4. Abnahmekriterien
Vor der Übergabe an das KI-Team (Phase 2) muss das System folgende Punkte bestehen:
* **RAM-Check:** `free -h` zeigt mindestens ~62 GB RAM an.
* **GPU-Erkennung:** `lspci -nn | grep -Ei 'vga|display|3d'` listet **zwei** AMD-Grafikgeräte auf. Das genaue Kartenmodell anhand des Hardwareinventars oder der Geräte selbst prüfen; die PCI-Familienkennung allein genügt dafür nicht.
* **Netzwerk:** Der Server ist nach GNAI-003 unter `10.0.0.90` erreichbar. Die getrennten SSH-Zugänge für `ghost` und `ghost-agent` wurden am 19.09.2026 erfolgreich getestet.
* **Storage:** `lsblk -f` zeigt die vorgesehene Partitionierung der 2-TB-SSD; `df -h /` zeigt die nutzbare Dateisystemgröße (in binären Einheiten kleiner als 2 TB).
* **Re-Size BAR:** BIOS-Einstellung und zugewiesene PCIe-BAR-Größen getrennt prüfen; ein bloßer `dmesg | grep -i BAR`-Treffer gilt nicht als Funktions- oder Leistungsnachweis.
---
## 5. Lesender Teilabgleich des bestehenden Hosts am 18.09.2026
Per SSH wurden Debian 13.6, Hostname `ghostnet-ki`, Intel Core i5-13600K, 62 GiB nutzbarer RAM und zwei AMD Navi-21-GPUs mit PCI-ID `1002:73bf` beobachtet. `lsblk` zeigt eine 1,8-TiB-NVMe mit EFI-, ext4-Root- und Swap-Partition; `df -h /` zeigte 1,8 TiB Dateisystemgröße. `lspci` bestätigt die Gerätefamilie, aber weder das genaue Kartenmodell noch ROCm-Funktion und ReBAR-Leistung. BIOS-Version, XMP, Firmwarestand und Restore wurden nicht geprüft.
@@ -0,0 +1,206 @@
# Ghostnet.AI-Host – Benutzer, Netzwerk und SSH
**Dokumenten-ID:** GNAI-003-INFRA
**Titel:** Benutzer-, Netzwerk- und SSH-Sicherheitsarchitektur des KI-Hosts `10.0.0.90`
**Status:** Betriebsanleitung; tatsächliche Netzverwaltung und Konten vor Anwendung prüfen
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung der Änderung |
| --- | --- | --- | --- |
| 2026-08-26 | 1.0 | spektr | Erstellung des Dokuments (Benutzer, Netzwerkkonfiguration, SSH-Härtung) |
| 2026-09-15 | 1.1 | spektr | Kontenbestand, DNS-Verwaltung und SSH-Dienstnamen präzisiert; doppelte Basisschritte bereinigt. |
| 2026-09-16 | 1.2 | spektr | Tatsächliche Konten, Gruppen, Netz- und SSH-Konfiguration sowie Grenzen des Login-Tests dokumentiert. |
| 2026-09-18 | 1.3 | spektr | Workstation-spezifische Schlüsselpfade und Host-Fingerprint in den lokalen Arbeitskontext verlagert. |
| 2026-09-19 | 1.4 | spektr | Persönlichen Login `ghost` und Agentenlogin `ghost-agent` als erfolgreich getestet dokumentiert. |
| 2026-09-20 | 1.5 | spektr / Codex | Deployment- und getrennte rootless Workload-Konten mit festen IDs und Zuständigkeiten ergänzt. |
| 2026-09-20 | 1.6 | spektr / Codex | `render`-Mitgliedschaft und geprüfte AMD-Geräterechte der beiden KI-Konten ergänzt. |
| 2026-09-22 | 1.7 | Codex | In die hostbezogene Struktur `platform/ghostnet-ai/` verschoben und Geltungsbereich präzisiert. |
---
## 1. Zweck und Geltungsbereich
Dieses Dokument beschreibt Benutzer, statische IP und SSH des KI-Servers. Der ursprüngliche lesende Kontenabgleich vom 18.09.2026 umfasste `ghost`, `debugofen` und `ghost-agent`; `admin` existiert auf diesem Host nicht. Am 19. und 20.09.2026 kamen der Ansible-Deployment-Account sowie drei gesperrte Workload-Konten hinzu. Die folgenden Befehle sind für Neuinstallation oder gezielte Änderung gedacht und ersetzen keine Prüfung des vorhandenen Systems. Der WireGuard-LXC besitzt ein separates Konto `admin`.
---
## 2. Voraussetzungen & Berechtigungen
* Bereits installiertes Debian-Grundsystem gemäß [GNAI-002](GNAI-002-INFRA_BaseSystem.md).
* Root-Zugriff oder Benutzer mit `sudo`-Rechten.
* Admin-Workstation mit lokal installierter SSH-Suite (`ssh`, `ssh-keygen`); für den bestehenden Host ist die lokale Konsole oder ein bereits berechtigter Zugang zur Schlüsselaufnahme nötig.
---
## 3. Prozessbeschreibung
### 3.1 Benutzerverwaltung & Sudo-Berechtigungen (Server-Seite)
#### 1. Sudo-Paket installieren & prüfen (falls noch nicht geschehen)
Als `root` am Server anmelden:
```bash
su -
apt update && apt install -y sudo
```
#### 2. Benutzer anlegen & Berechtigungen vergeben
Vor dem Anlegen prüfen, ob das Konto bereits existiert (`getent passwd <username>`). Konten und Home-Verzeichnisse nur nach Prüfung ihrer Daten und Berechtigungen entfernen.
Nur bei einer **Neuinstallation** einen vorher festgelegten Administrationsbenutzer anlegen und zur `sudo`-Gruppe hinzufügen. Auf dem bestehenden KI-Server besitzen `ghost` und `debugofen` bereits sudo-Gruppenmitgliedschaft. Das folgende Beispiel mit `ghost` dort nicht ausführen:
```bash
# Nur auf einem neuen System ausführen.
sudo adduser ghost
sudo usermod -aG sudo ghost
```
Weitere Konten nur nach Rollenentscheidung anlegen. Die sudo-Gruppenmitgliedschaft ist pro Konto ausdrücklich festzulegen; auf dem bestehenden Host keine bereits vorhandenen Konten erneut anlegen.
Die folgenden technischen Konten werden ausschließlich durch das
Ansible-Projekt verwaltet:
| Konto | UID/GID | Zweck | interaktiver Login / sudo |
| --- | --- | --- | --- |
| `ansible-deploy` | 1003 | privilegierte Ansible-Ausführung | SSH-Key, gesperrtes Passwort, `NOPASSWD` gemäß eigener sudoers-Regel |
| `gnai-production` | 980 | rootless AI Production | gesperrtes Passwort, `/usr/sbin/nologin`, kein sudo |
| `gnai-testing` | 981 | rootless AI Testing | gesperrtes Passwort, `/usr/sbin/nologin`, kein sudo |
| `gn-encoding` | 982 | rootless CPU-Encoding | gesperrtes Passwort, `/usr/sbin/nologin`, kein sudo |
Die drei Workload-Konten besitzen nicht überlappende SubUID-/SubGID-Bereiche,
eigene Daten- und Podman-Stores sowie persistente systemd-User-Manager. Ihre
rootverwalteten Quadlet-Verzeichnisse liegen unter
`/etc/containers/systemd/users/<UID>`. Direkte SSH-Schlüssel werden dort nicht
hinterlegt. `gnai-production` und `gnai-testing` gehören zusätzlich zur
Gerätegruppe `render`; `gn-encoding` erhält keinen GPU-Zugriff.
---
### 3.2 Statische Netzwerkkonfiguration (Server-Seite)
#### 1. Netzschnittstelle ermitteln & anpassen
Primäre Netzwerkschnittstelle und zuständigen Netzwerkdienst ermitteln (z. B. `enp8s0`):
```bash
ip addr show
systemctl is-active networking NetworkManager systemd-networkd dhcpcd
```
Nur falls `ifupdown` den Host tatsächlich verwaltet, `/etc/network/interfaces` bearbeiten. Bei NetworkManager, systemd-networkd oder dhcpcd deren Konfiguration verwenden; die Schnittstelle darf nicht von mehreren Diensten zugleich verwaltet werden.
```bash
sudo nano /etc/network/interfaces
```
Netzwerkkonfiguration für statische IP eintragen:
```text
# Loopback Interface
auto lo
iface lo inet loopback
# Primary Network Interface
allow-hotplug enp8s0
iface enp8s0 inet static
address 10.0.0.90/24
gateway 10.0.0.1
dns-nameservers 10.0.0.1 1.1.1.1
```
#### 2. DNS-Resolver prüfen
`/etc/resolv.conf` kann von einem Netzwerk- oder Resolverdienst erzeugt werden. Den aktiven Dienst und die wirksamen Nameserver prüfen (`readlink -f /etc/resolv.conf`, `cat /etc/resolv.conf`); Änderungen in dessen Konfiguration vornehmen. Die aktuelle Solladresse des Heimnetz-DNS ist `10.0.0.1`.
#### 3. Netzwerkdienst neustarten & Funktion prüfen
```bash
sudo systemctl restart networking # nur bei ifupdown und mit Konsolenzugang
ip -4 addr
ping -c 4 google.com
```
Vor einem Remote-Neustart einen Rückweg über Konsole/Proxmox sicherstellen. Ein Reboot behebt keine widersprüchliche Netzkonfiguration.
---
### 3.3 SSH-Schlüssel und getrennte Konten
Für persönliche und automatisierte Zugriffe getrennte Konten und klar benannte Schlüsselpaare verwenden. Auf dem bestehenden KI-Server sind `ghost` und `ghost-agent` getrennt; `ghost-agent` hat keine sudo-Gruppenmitgliedschaft. Schlüsseldateien, lokale SSH-Aliase und der auf dieser Workstation bekannte Hostschlüssel-Fingerprint sind nur im lokalen, nicht versionierten Arbeitskontext vermerkt. Private Schlüssel und Passphrasen gehören weder in diese Dokumentation noch in ein Serverinventar. Ein Kommentar wie `@ghostnet-ki` im Public Key ist nur ein Label; die Zuordnung erfolgt über den Schlüsselinhalt und das jeweilige `authorized_keys`.
Neue öffentliche Schlüssel auf dem bestehenden Host über eine bereits berechtigte Sitzung oder die lokale Konsole in das **richtige Konto** unter `~/.ssh/authorized_keys` eintragen; bestehende Schlüssel dabei erhalten. Verzeichnis auf `700`, Datei auf `600` und Eigentümer auf das Zielkonto setzen. Passwort-SSH ist bereits deaktiviert, daher ist `ssh-copy-id` ohne bestehenden Schlüsselzugang hier kein Wiederherstellungsverfahren.
Vor Erstverbindung den Hostschlüssel-Fingerprint aus einer vertrauenswürdigen Konsole abgleichen. Die lokalen SSH-Aliase müssen auf `10.0.0.90`, das jeweils richtige Konto und Schlüsseldatei zeigen; `IdentitiesOnly yes` und strikte Hostschlüsselprüfung verwenden. Die folgenden Beispiele setzen entsprechend eingerichtete Aliase auf der Admin-Workstation voraus:
```bash
ssh ghostnet-ai-ghost 'id -un; hostnamectl --static'
ssh ghostnet-ai-agent 'id -un; hostnamectl --static'
```
Für `ghost` ist auf der aktuellen Workstation eine Passphrase-Eingabe am Terminal erforderlich, sofern der Schlüssel nicht bereits im SSH-Agenten entsperrt wurde. Beide Profile sind getestet: `ghost-agent` wurde am 19.09.2026 erneut technisch geprüft; der Betreiber bestätigte am selben Tag den erfolgreichen interaktiven Login als `ghost`.
---
### 3.4 SSH-Dienst härten (Server-Seite)
Nur auf einem **neu eingerichteten System** nach erfolgreichem SSH-Key-Test die Passwort-Authentifizierung deaktivieren. Auf dem bestehenden KI-Server ist das folgende Drop-in bereits vorhanden; es wurde am 18.09.2026 lesend geprüft.
#### 1. Drop-in-Konfiguration für SSH erstellen
```bash
sudo nano /etc/ssh/sshd_config.d/99-security.conf
```
Den folgenden Inhalt einfügen:
```text
# Root-Login über SSH komplett verbieten
PermitRootLogin no
# Passwort-Authentifizierung deaktivieren (nur SSH-Keys erlauben)
PasswordAuthentication no
KbdInteractiveAuthentication no
# Leere Passwörter verbieten
PermitEmptyPasswords no
# Nur explizite SSH-Key Methoden erlauben
PubkeyAuthentication yes
```
#### 2. SSH-Konfiguration prüfen und Dienst neustarten
> **Achtung:** Die bestehende SSH-Sitzung **nicht** schließen, bevor der Login in einem zweiten Terminalfenster erfolgreich verifiziert wurde!
```bash
# Syntaktische Prüfung der Konfiguration
sudo sshd -t
# Wenn sshd -t keine Fehler ausgibt, Dienst neustarten
sudo systemctl restart ssh
```
---
## 4. Abnahmekriterien (Checkliste für den Junior-Admin)
* [ ] **Benutzerprüfung:** `ghost` und `debugofen` können ihre vorgesehenen sudo-Rechte nutzen; `ghost-agent` bleibt ohne sudo-Zuweisung.
* [x] **Workload-Konten:** `gnai-production`, `gnai-testing` und `gn-encoding` besitzen getrennte rootless Podman-Stores und aktive persistente User-Manager; geprüft am 20.09.2026.
* [x] **GPU-Geräterechte:** Beide KI-Konten können `/dev/kfd`, `/dev/dri/renderD128` und `/dev/dri/renderD129` lesen und schreiben; das Encoding-Konto gehört nicht zu `render`; geprüft am 20.09.2026.
* [ ] **Netzwerk:** Server ist unter der statischen IP `10.0.0.90` erreichbar. Ausgehender Datenverkehr funktioniert (`ping google.com`).
* [x] **SSH-Aliase:** `ghostnet-ai-ghost` meldet `ghost`, `ghostnet-ai-agent` meldet `ghost-agent`. Beide Zugänge wurden am 19.09.2026 bestätigt. Die Passphrase des persönlichen Schlüssels ist keine Server-Passwortanmeldung.
* [ ] **Root-Sperre:** Direct-Login als Root per SSH wird abgelehnt (`ssh root@10.0.0.90` -> *Permission denied*).
* [ ] **Passwort-Sperre:** Login-Versuch ohne hinterlegten SSH-Key wird direkt abgelehnt (keine Passwort-Aufforderung mehr).
---
## 5. Lesender Ist-Abgleich am 18.09.2026
`ghost` (UID 1000) und `debugofen` (UID 1001) gehören zur Gruppe `sudo`; `ghost-agent` (UID 1002) gehört nicht dazu. Der technische Login als `ghost-agent` und der vom Betreiber interaktiv geprüfte Login als `ghost` funktionieren mit ihren jeweils eigenen Schlüsseln. Die Prüfung belegt die Kontentrennung und den Schlüsselzugang, aber keine noch offenen sudo-, Root-Sperr- oder Negativtests.
`enp8s0` hat `10.0.0.90/24`, die Default-Route führt über `10.0.0.1`. `networking` und `ssh` sind aktiv und aktiviert. `/etc/network/interfaces` enthält die statische Konfiguration; `/etc/resolv.conf` nennt `10.0.0.1` und `1.1.1.1` und trägt einen dhcpcd-Generierungskommentar. Ein aktuell laufender `dhcpcd`-Dienst wurde nicht beobachtet; vor Resolveränderungen die tatsächliche Verwaltung erneut prüfen. Das SSH-Drop-in deaktiviert Root-, Passwort- und Keyboard-Interactive-Anmeldung und aktiviert Public-Key-Anmeldung.