GNAI007: Render- und GPU-Rechte den beiden KI-Konten hinzugefügt.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
Dokumentation für die Infrastruktur eines privaten Homelabs mit öffentlich erreichbaren Webdiensten, einem WireGuard-Zugang und einem dedizierten KI-Server. Dieses Repository hält Architekturentscheidungen, den belegten Betriebsstand und Verfahren für Einrichtung, Betrieb und Sicherheitsabnahme fest.
|
||||
|
||||
> **Projektstand: 20.09.2026.** Die Dokumentationsrevisionen sind zusammengeführt. Mehrere Sicherheitsmaßnahmen und technische Endabnahmen stehen noch aus. Auf dem KI-Server sind Host-Baseline, Podman und getrennte rootless Workload-Konten eingerichtet; LLM- und Encoding-Anwendungen sind noch nicht installiert.
|
||||
> **Projektstand: 20.09.2026.** Die Dokumentationsrevisionen sind zusammengeführt. Mehrere Sicherheitsmaßnahmen und technische Endabnahmen stehen noch aus. Auf dem KI-Server sind Host-Baseline, Podman, getrennte rootless Workload-Konten und der Gerätezugriff der KI-Konten auf beide AMD-GPUs eingerichtet; LLM- und Encoding-Anwendungen sind noch nicht installiert.
|
||||
|
||||
## Überblick
|
||||
|
||||
@@ -26,7 +26,7 @@ Die drei vorgesehenen VPN-Netzwerkrollen sind **Familie** (gesamtes Heimnetz), *
|
||||
|
||||
| Bereich | Belegter Stand | Nächster Nachweis oder Schritt |
|
||||
| --- | --- | --- |
|
||||
| KI-Server | Debian-Basissystem, Deployment-Zugang, Host-Baseline, Podman 5.4.2 und drei getrennte rootless Workload-Konten | GPU-Zugriff für KI prüfen sowie Encoding-Zeitfenster und Ressourcenlimits festlegen |
|
||||
| KI-Server | Debian-Basissystem, Deployment-Zugang, Podman 5.4.2, drei getrennte rootless Workload-Konten und geprüfter Zugriff beider KI-Konten auf `/dev/kfd` sowie zwei AMD-Render-Nodes | ROCm im rootless `llama.cpp`-Container prüfen und danach den Testing-Stack bereitstellen |
|
||||
| VPN | wg-easy v15.4.0 auf CT 105 aktiv; ein externer Laptop-Teiltest liegt vor | Rollenrechte serverseitig umsetzen und mit erlaubten sowie gesperrten Zielen testen |
|
||||
| Reverse Proxy | NPM auf CT 103 dokumentiert; Fail2Ban laut Betreiber fest gepinnt und wieder in Betrieb | Effektive Jails, Sperren und Wiederanlauf prüfen |
|
||||
| Identität | Keycloak/OIDC laut Betreiber produktiv für Vaultwarden | Öffentlich erreichbare Keycloak-Administration sperren und den OIDC-Login erneut prüfen |
|
||||
@@ -41,7 +41,7 @@ Der [Dokumentationsindex](docs/GNAI-000-DOC_Index.md) enthält die datierten Que
|
||||
| [Plattform-Dokumentation](docs/platform/README.md) | Hostbasis, Zugriffsmodell, Reverse Proxy und VPN |
|
||||
| [KI-Dokumentation](docs/ai/README.md) | KI-Zielarchitektur und nicht finale Ansible-Deployment-Planung |
|
||||
| [Video-Encoding](docs/video-encoding/GNENC-000-DOC_Index.md) | getrennter Planungs- und späterer SOP-Bereich für den CPU-Encoding-Workload |
|
||||
| [Ansible-Control-Projekt](ansible/README.md) | lokale Einrichtung, Preflight, Deployment-Account, Host-Baseline, Podman und Workload-Konten |
|
||||
| [Ansible-Control-Projekt](ansible/README.md) | lokale Einrichtung, Preflight, Deployment-Account, Host-Baseline, Podman, Workload-Konten und AMD-GPU-Zugriff |
|
||||
|
||||
## Arbeiten mit dem Repository
|
||||
|
||||
|
||||
+24
-4
@@ -2,8 +2,9 @@
|
||||
|
||||
This directory contains the reproducible control project for `ghostnet-ki`.
|
||||
The read-only `playbooks/preflight.yml` verifies the documented host identity
|
||||
and hardware. The first writing milestone, `playbooks/host.yml`, applies a
|
||||
small shared host baseline through the dedicated deployment account.
|
||||
and hardware. The shared `playbooks/host.yml` applies the host baseline,
|
||||
Podman runtime, workload identities and narrowly scoped AMD device access
|
||||
through the dedicated deployment account.
|
||||
|
||||
## Control-node setup
|
||||
|
||||
@@ -166,5 +167,24 @@ UID/GID and subordinate-ID ranges:
|
||||
Each account has a persistent systemd user manager and a separate,
|
||||
administrator-managed directory below `/etc/containers/systemd/users/<UID>`.
|
||||
Configuration is root-owned and group-readable; workload data belongs to its
|
||||
service account. The role does not grant GPU access and creates no workload
|
||||
containers.
|
||||
service account. The two AI identities have `render` as their explicit
|
||||
supplementary-group baseline; the encoding identity has no GPU group. The role
|
||||
creates no workload containers.
|
||||
|
||||
## AMD GPU access
|
||||
|
||||
The `amd_gpu_access` role verifies exactly two PCI devices `1002:73bf` using
|
||||
the `amdgpu` kernel driver. It maps their sysfs identities to the corresponding
|
||||
DRM render nodes, checks `/dev/kfd`, derives the device groups and confirms
|
||||
read/write access as both AI workload identities.
|
||||
|
||||
On the current host, the verified devices are `/dev/kfd`,
|
||||
`/dev/dri/renderD128` and `/dev/dri/renderD129`; all use group `render`.
|
||||
`gnai-production` and `gnai-testing` can access them, while `gn-encoding`
|
||||
remains outside that group. The final convergence run reported
|
||||
`ok=55 changed=0 unreachable=0 failed=0`.
|
||||
|
||||
This proves host-level device permissions only. No ROCm userspace image has
|
||||
been pulled and no GPU workload has run yet. The next workload check must pass
|
||||
these devices into a rootless container with `GroupAdd=keep-groups` and prove
|
||||
that `llama.cpp` detects both cards as `gfx1030`.
|
||||
|
||||
@@ -12,3 +12,4 @@
|
||||
- role: host_baseline
|
||||
- role: podman_runtime
|
||||
- role: workload_accounts
|
||||
- role: amd_gpu_access
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# AMD GPU access role
|
||||
|
||||
This shared host role verifies the two configured AMD GPUs and their `amdgpu`
|
||||
kernel driver, discovers only the DRM render nodes belonging to their PCI IDs,
|
||||
and checks `/dev/kfd`. It grants the Production and Testing service accounts
|
||||
the minimal supplementary groups required by those device nodes.
|
||||
|
||||
When group membership changes, the role restarts both persistent systemd user
|
||||
managers so later rootless Quadlets can use `GroupAdd=keep-groups`. It verifies
|
||||
that each service account can read and write `/dev/kfd` and both selected
|
||||
render devices.
|
||||
|
||||
The role installs no ROCm userspace packages, pulls no image and starts no
|
||||
container. ROCm userspace will come from the pinned `llama.cpp` container. A
|
||||
later workload check must still prove that the container detects both GPUs as
|
||||
`gfx1030` and performs inference on them.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
amd_gpu_vendor_id: "1002"
|
||||
amd_gpu_device_id: "73bf"
|
||||
amd_gpu_expected_count: 2
|
||||
|
||||
amd_gpu_workload_accounts:
|
||||
- name: gnai-production
|
||||
uid: 980
|
||||
- name: gnai-testing
|
||||
uid: 981
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Restart AI workload user managers
|
||||
ansible.builtin.systemd_service:
|
||||
name: "user@{{ amd_gpu_account.uid }}.service"
|
||||
state: restarted
|
||||
loop: "{{ amd_gpu_workload_accounts }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_account
|
||||
label: "{{ amd_gpu_account.name }}"
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
- name: Verify AMD GPU access declarations
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- amd_gpu_vendor_id is match("^[0-9a-fA-F]{4}$")
|
||||
- amd_gpu_device_id is match("^[0-9a-fA-F]{4}$")
|
||||
- amd_gpu_expected_count | int > 0
|
||||
- >-
|
||||
amd_gpu_workload_accounts | map(attribute="name") | unique | length ==
|
||||
amd_gpu_workload_accounts | length
|
||||
- >-
|
||||
amd_gpu_workload_accounts | map(attribute="uid") | unique | length ==
|
||||
amd_gpu_workload_accounts | length
|
||||
fail_msg: AMD GPU identifiers and workload accounts must be valid and unique.
|
||||
|
||||
- name: Read matching PCI devices and kernel drivers
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/lspci
|
||||
- -Dnnk
|
||||
- -d
|
||||
- "{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }}"
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
register: amd_gpu_lspci
|
||||
|
||||
- name: Verify expected GPUs use the amdgpu kernel driver
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
amd_gpu_lspci.stdout_lines
|
||||
| select("search", "(?i)" + amd_gpu_vendor_id + ":" + amd_gpu_device_id)
|
||||
| list | length == amd_gpu_expected_count | int
|
||||
- >-
|
||||
amd_gpu_lspci.stdout
|
||||
| regex_findall("Kernel driver in use: amdgpu")
|
||||
| length == amd_gpu_expected_count | int
|
||||
fail_msg: >-
|
||||
Expected {{ amd_gpu_expected_count }} PCI devices
|
||||
{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }} using amdgpu.
|
||||
|
||||
- name: Find DRM render nodes in sysfs
|
||||
ansible.builtin.find:
|
||||
paths: /sys/class/drm
|
||||
patterns: "renderD*"
|
||||
file_type: link
|
||||
recurse: false
|
||||
register: amd_gpu_sysfs_render_nodes
|
||||
|
||||
- name: Read render node PCI vendor IDs
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ amd_gpu_render_node.path }}/device/vendor"
|
||||
loop: "{{ amd_gpu_sysfs_render_nodes.files }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_render_node
|
||||
label: "{{ amd_gpu_render_node.path | basename }}"
|
||||
register: amd_gpu_render_vendor_ids
|
||||
|
||||
- name: Read render node PCI device IDs
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ amd_gpu_render_node.path }}/device/device"
|
||||
loop: "{{ amd_gpu_sysfs_render_nodes.files }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_render_node
|
||||
label: "{{ amd_gpu_render_node.path | basename }}"
|
||||
register: amd_gpu_render_device_ids
|
||||
|
||||
- name: Initialize matching AMD render device list
|
||||
ansible.builtin.set_fact:
|
||||
amd_gpu_render_devices: []
|
||||
|
||||
- name: Select render devices belonging to the configured AMD GPUs
|
||||
ansible.builtin.set_fact:
|
||||
amd_gpu_render_devices: >-
|
||||
{{ amd_gpu_render_devices
|
||||
+ ['/dev/dri/'
|
||||
+ (amd_gpu_sysfs_render_nodes.files[amd_gpu_render_index].path
|
||||
| basename)] }}
|
||||
loop: "{{ range(0, amd_gpu_sysfs_render_nodes.files | length) | list }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_render_index
|
||||
label: >-
|
||||
{{ amd_gpu_sysfs_render_nodes.files[amd_gpu_render_index].path | basename }}
|
||||
when:
|
||||
- >-
|
||||
amd_gpu_render_vendor_ids.results[amd_gpu_render_index].content
|
||||
| b64decode | trim | lower == "0x" + amd_gpu_vendor_id | lower
|
||||
- >-
|
||||
amd_gpu_render_device_ids.results[amd_gpu_render_index].content
|
||||
| b64decode | trim | lower == "0x" + amd_gpu_device_id | lower
|
||||
|
||||
- name: Verify one render device was found for each configured GPU
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- amd_gpu_render_devices | length == amd_gpu_expected_count
|
||||
fail_msg: >-
|
||||
Expected {{ amd_gpu_expected_count }} matching AMD render devices but found
|
||||
{{ amd_gpu_render_devices | length }}: {{ amd_gpu_render_devices }}.
|
||||
|
||||
- name: Inspect the ROCm kernel device
|
||||
ansible.builtin.stat:
|
||||
path: /dev/kfd
|
||||
register: amd_gpu_kfd
|
||||
|
||||
- name: Verify the ROCm kernel device exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- amd_gpu_kfd.stat.exists
|
||||
- amd_gpu_kfd.stat.ischr | default(false)
|
||||
- amd_gpu_kfd.stat.gr_name != "root"
|
||||
fail_msg: /dev/kfd must exist as a character device with a dedicated group.
|
||||
|
||||
- name: Inspect matching AMD render devices
|
||||
ansible.builtin.stat:
|
||||
path: "{{ amd_gpu_render_device }}"
|
||||
loop: "{{ amd_gpu_render_devices }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_render_device
|
||||
register: amd_gpu_render_device_stats
|
||||
|
||||
- name: Verify matching AMD render devices are usable character devices
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- amd_gpu_render_stat.stat.exists
|
||||
- amd_gpu_render_stat.stat.ischr | default(false)
|
||||
- amd_gpu_render_stat.stat.gr_name != "root"
|
||||
loop: "{{ amd_gpu_render_device_stats.results }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_render_stat
|
||||
label: "{{ amd_gpu_render_stat.amd_gpu_render_device }}"
|
||||
|
||||
- name: Derive the minimal host groups required by the GPU devices
|
||||
ansible.builtin.set_fact:
|
||||
amd_gpu_required_group_names: >-
|
||||
{{ ([amd_gpu_kfd.stat.gr_name]
|
||||
+ (amd_gpu_render_device_stats.results
|
||||
| map(attribute="stat.gr_name") | list))
|
||||
| unique | sort | list }}
|
||||
|
||||
- name: Grant AI workload accounts access to the GPU device groups
|
||||
ansible.builtin.user:
|
||||
name: "{{ amd_gpu_account.name }}"
|
||||
groups: "{{ amd_gpu_required_group_names | join(',') }}"
|
||||
append: true
|
||||
loop: "{{ amd_gpu_workload_accounts }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_account
|
||||
label: "{{ amd_gpu_account.name }}"
|
||||
notify: Restart AI workload user managers
|
||||
|
||||
- name: Apply changed group memberships to persistent user managers
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Read effective AI workload account groups
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/id
|
||||
- -nG
|
||||
- "{{ amd_gpu_account.name }}"
|
||||
changed_when: false
|
||||
loop: "{{ amd_gpu_workload_accounts }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_account
|
||||
label: "{{ amd_gpu_account.name }}"
|
||||
register: amd_gpu_account_groups
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify AI workload account group memberships
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
amd_gpu_required_group_names
|
||||
| difference(amd_gpu_account_group.stdout.split()) | length == 0
|
||||
fail_msg: >-
|
||||
{{ amd_gpu_account_group.amd_gpu_account.name }} is missing a required GPU
|
||||
device group.
|
||||
loop: "{{ amd_gpu_account_groups.results }}"
|
||||
loop_control:
|
||||
loop_var: amd_gpu_account_group
|
||||
label: "{{ amd_gpu_account_group.amd_gpu_account.name }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify AI workload accounts can read and write required GPU devices
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/runuser
|
||||
- --user
|
||||
- "{{ amd_gpu_access_pair.0.name }}"
|
||||
- --
|
||||
- /usr/bin/python3
|
||||
- -c
|
||||
- >-
|
||||
import os, sys; raise SystemExit(
|
||||
0 if os.access(sys.argv[1], os.R_OK | os.W_OK) else 1)
|
||||
- "{{ amd_gpu_access_pair.1 }}"
|
||||
changed_when: false
|
||||
loop: >-
|
||||
{{ amd_gpu_workload_accounts
|
||||
| product(['/dev/kfd'] + amd_gpu_render_devices) | list }}
|
||||
loop_control:
|
||||
loop_var: amd_gpu_access_pair
|
||||
label: "{{ amd_gpu_access_pair.0.name }} -> {{ amd_gpu_access_pair.1 }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Report verified AMD GPU access
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
pci_id: "{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }}"
|
||||
gpu_count: "{{ amd_gpu_expected_count }}"
|
||||
render_devices: "{{ amd_gpu_render_devices }}"
|
||||
device_groups: "{{ amd_gpu_required_group_names }}"
|
||||
accounts: "{{ amd_gpu_workload_accounts | map(attribute='name') | list }}"
|
||||
@@ -11,6 +11,7 @@ service group. Workload data and the rootless Podman store belong to the
|
||||
service account. The role verifies that all three Podman stores are rootless
|
||||
and use distinct graph roots.
|
||||
|
||||
The role does not grant GPU device access and does not define containers,
|
||||
networks, schedules or resource limits. Those states belong to the AI and
|
||||
video-encoding workload roles.
|
||||
The two AI accounts receive `render` as an explicit supplementary-group
|
||||
baseline; the encoding account receives no GPU group. Hardware discovery and
|
||||
effective device-access checks belong to `amd_gpu_access`. This role does not
|
||||
define containers, networks, schedules or resource limits.
|
||||
|
||||
@@ -7,7 +7,8 @@ workload_accounts:
|
||||
home: /var/lib/ghostnet-ai/production
|
||||
config_directory: /etc/ghostnet-ai/production
|
||||
subid_start: 362144
|
||||
supplementary_groups: []
|
||||
supplementary_groups:
|
||||
- render
|
||||
- name: gnai-testing
|
||||
uid: 981
|
||||
gid: 981
|
||||
@@ -15,7 +16,8 @@ workload_accounts:
|
||||
home: /var/lib/ghostnet-ai/testing
|
||||
config_directory: /etc/ghostnet-ai/testing
|
||||
subid_start: 427680
|
||||
supplementary_groups: []
|
||||
supplementary_groups:
|
||||
- render
|
||||
- name: gn-encoding
|
||||
uid: 982
|
||||
gid: 982
|
||||
|
||||
@@ -61,6 +61,8 @@ Die Rolle `podman_runtime` wurde am 20.09.2026 angewendet. Sie installierte Podm
|
||||
|
||||
Die Rolle `workload_accounts` richtete am 20.09.2026 die gesperrten Konten `gnai-production` (UID/GID 980), `gnai-testing` (981) und `gn-encoding` (982) mit festen, getrennten SubID-Bereichen ein. Konfiguration bleibt rootverwaltet; Daten, rootless Podman-Stores und persistente systemd-User-Manager sind pro Workload getrennt. Der korrigierte Gesamtlauf meldete `ok=36 changed=0 unreachable=0 failed=0` und bestätigte drei unterschiedliche rootless Graph Roots. Images und Container wurden nicht angelegt.
|
||||
|
||||
Die Rolle `amd_gpu_access` bestätigte am 20.09.2026 zwei Sapphire-Navi-21-Geräte `1002:73bf` mit dem Kernel-Treiber `amdgpu`, `/dev/kfd` sowie die zugehörigen Render-Nodes `/dev/dri/renderD128` und `/dev/dri/renderD129`. Alle drei Geräte verwenden die Gruppe `render`. `gnai-production` und `gnai-testing` besitzen nachweislich Lese-/Schreibzugriff; `gn-encoding` erhielt keine GPU-Gruppe. Nach Beseitigung einer konkurrierenden Gruppenverwaltung endete der Gesamtlauf mit `ok=55 changed=0 unreachable=0 failed=0`. ROCm im Container und tatsächliche Inferenz sind damit noch nicht nachgewiesen.
|
||||
|
||||
## 3. Sicherheitsbefunde mit Priorität
|
||||
|
||||
| Befund | dokumentiertes Verfahren / Status |
|
||||
@@ -105,4 +107,13 @@ rootseitiges `runuser` und eindeutiger Eigentümerzuständigkeit endete der
|
||||
Gesamtlauf mit `ok=36 changed=0 unreachable=0 failed=0`. Die drei rootless
|
||||
Podman-Stores sind voneinander getrennt und noch leer.
|
||||
|
||||
Der folgende GPU-Zugriffslauf ordnete beide erkannten AMD-Render-Nodes anhand
|
||||
von PCI-Vendor und Geräte-ID den Navi-21-Karten zu und leitete aus ihnen sowie
|
||||
`/dev/kfd` ausschließlich die Gruppe `render` ab. Der erste Wiederholungslauf
|
||||
deckte auf, dass `workload_accounts` ergänzende Gruppen wieder entfernte. Nach
|
||||
Übernahme von `render` in den expliziten Kontosollzustand blieb
|
||||
`amd_gpu_access` für Hardwareerkennung und effektive Rechteprüfung zuständig.
|
||||
Der Abschlusslauf war mit `changed=0` idempotent; Images und Container blieben
|
||||
weiterhin leer.
|
||||
|
||||
Die WireGuard-Evidenz bleibt der Teiltest vom 12.09.2026: CachyOS-Laptop von WAN, Handshake, Ping auf Gateway/Proxmox und direkter IPv4-Internetpfad bestanden; KI-Server war ausgeschaltet. Neue Rollen, weitere Clientvarianten, Gateway-GUI-Zugriff, WAN-/IPv6-Isolation, Neustart und Restore sind noch nicht abgenommen. GNAI-006 ist erst nach Klärung der offenen Werte, Umsetzung und dokumentierter Abnahme technisch abgeschlossen.
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -378,6 +380,75 @@ 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` wird auf eine geprüfte
|
||||
Version und den zugehörigen Digest 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 LiteLLM 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.
|
||||
|
||||
Der erste Testing-Modellstand verwendet vorbehaltlich Artefakt- und
|
||||
Checksum-Prüfung:
|
||||
|
||||
| Parameter | Ausgangswert |
|
||||
| --- | --- |
|
||||
| Modelldatei | `Qwen3.8-27B-UD-IQ4_XS.gguf` |
|
||||
| API-Alias | `qwen3.8-27b` |
|
||||
| Kontext | 51.200 Token |
|
||||
| parallele Slots | 1 |
|
||||
| maximale Ausgabe | 4.096 Token |
|
||||
| GPU-Layer | alle |
|
||||
| Flash Attention | aktiviert |
|
||||
| K-/V-KV-Cache | jeweils `q8_0` |
|
||||
| Reasoning Effort | `medium` |
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Vor dem dauerhaften Start muss ein begrenzter Test `gfx1030`, beide GPUs,
|
||||
Device-Reihenfolge, verfügbaren VRAM 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 51.200 Token Kontext
|
||||
gemessen. Der 1:1-Wert ist erst nach diesem Test ein abgenommener
|
||||
Betriebsparameter.
|
||||
|
||||
---
|
||||
|
||||
## 8. Vorgesehener Workflow
|
||||
@@ -555,7 +626,8 @@ nicht als Nachweis einer erfolgreichen Einrichtung.
|
||||
|
||||
- Podman- und Quadlet-Grundruntime sowie getrennte rootless Workload-Konten
|
||||
eingerichtet und geprüft;
|
||||
- AMD-GPU-/ROCm- oder alternative Laufzeit nach technischer Prüfung;
|
||||
- rootless Host-Gerätezugriff auf beide RX 6900 XT eingerichtet und geprüft;
|
||||
- ROCm-Funktion im rootless Container prüfen;
|
||||
- `llama.cpp` und lokaler Funktionstest;
|
||||
- Wiederanlauf nach geplantem Reboot prüfen.
|
||||
|
||||
@@ -635,6 +707,8 @@ Herstellerdokumentationen herangezogen:
|
||||
- [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.
|
||||
@@ -753,25 +827,43 @@ die umgebungsspezifischen Pfade wurden daraufhin vollständig an
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 15. Abschlussgrenze dieses Entwurfs
|
||||
|
||||
Mit Version 0.9 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
|
||||
Mit Version 0.11 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 persistiert.
|
||||
Workload-Konten sowie deren begrenzter AMD-Gerätezugriff persistiert.
|
||||
Insbesondere wurden noch nicht:
|
||||
|
||||
- ein Gitea Runner angelegt,
|
||||
- Gitea Actions oder Schutzregeln konfiguriert,
|
||||
- GPU- oder Anwendungsrollen, ein Production-Inventar oder ein Vault erstellt,
|
||||
- eine ROCm-Container- oder Anwendungsrolle, ein Production-Inventar oder ein
|
||||
Vault erstellt,
|
||||
- eine Production- oder Testing-Umgebung eingerichtet oder voneinander
|
||||
abgenommen,
|
||||
- GPU-Komponenten oder Anwendungen auf dem KI-Server installiert,
|
||||
- ROCm-Userspace, GPU-Container oder Anwendungen auf dem KI-Server installiert,
|
||||
- Backups oder Rollbacks praktisch getestet oder
|
||||
- die geplante Deploymentkette technisch abgenommen.
|
||||
|
||||
Die nächsten technischen Schritte sind der begrenzte GPU-Zugriffstest für die
|
||||
beiden KI-Konten und die Festlegung von Zeitfenster sowie Ressourcenlimits im
|
||||
getrennten Encoding-Plan. Darauf bauen die jeweiligen Fachrollen und SOPs auf.
|
||||
Der nächste technische Schritt ist ein begrenzter rootless ROCm-Container-Test
|
||||
für beide KI-Konten, der beide Karten als `gfx1030` erkennen muss. Darauf bauen
|
||||
die `llama.cpp`-Rolle, das initiale Testing-Modellprofil und später LiteLLM auf.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
| 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. |
|
||||
|
||||
---
|
||||
|
||||
@@ -74,7 +75,8 @@ 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.
|
||||
hinterlegt. `gnai-production` und `gnai-testing` gehören zusätzlich zur
|
||||
Gerätegruppe `render`; `gn-encoding` erhält keinen GPU-Zugriff.
|
||||
|
||||
---
|
||||
|
||||
@@ -188,6 +190,7 @@ sudo systemctl restart ssh
|
||||
|
||||
* [ ] **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*).
|
||||
|
||||
Reference in New Issue
Block a user