Rename phy-z-srv-gpu01 to phy-srv-gpu01; fix share analysis scripts
- rename host/group/folder everywhere to match the server's actual hostname and physical label - share-analysis.ps1: sanitize the output filename prefix — '-Paths "D:"' produced 'D:-file-types.csv' and Export-Csv failed with 'path format not supported', so no CSVs were written - share-analysis.ps1: new -FolderDepth so folders can be aggregated at D:\Abteilungen\<Share> level, which matches the share layout - list-shares.ps1: -WithSize walks local paths instead of UNC when run on the server itself (UNC was orders of magnitude slower and looked stuck) and prints progress every 50k files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
## Hardware Components
|
||||
|
||||
### HPE GPU SERVER, 2 CPUs, 128GB RAM, RTX Pro 6000 GPU 96GB RAM, Red. Netzteil
|
||||
|
||||
| POS | BEZEICHNUNG |ANZ. |EINZELPREIS | GESAMTPREIS |
|
||||
| - | - | - | - | - |
|
||||
| 1 | HPE DL380 Gen12 SFF NC CTO Svr | 1 | 1.598,59 € | 1.598,59 € |
|
||||
| 2 | HPE Intel Xeon 6714P 4.0GHz 8-core 165W Processor | 2 | 4.148,67 € | 8.297,34 € |
|
||||
| 3 | HPE 16GB 1Rx8 PC5-6400B-R Smart FIO Kit | 8 | 1.171,82 € | 9.374,52 € |
|
||||
| 4 | HPE DL3XX Gen12 8SFF x1 U.3 TM Kit | 1 | 87,48 € | 87,48 € |
|
||||
| 5 | HPE 960G NVMe RI SFF BC U.3ST V2 MV SSD | 2 | 1.757,04 € | 3.514,08 € |
|
||||
| 6 | HPE DL380 G11 2U x8/x16/x8 Sec Riser Kit | 1 | 67,98 € | 67,98 € |
|
||||
| 7 | NVIDIA RTX PRO 6000 96G PCIe | 1 | 13.976,64 € | 13.976,64 € |
|
||||
| 8 | HPE 96W Smart Stg Li-ion Batt 145mm Kit | 1 | 36,80 € | 36,80 € |
|
||||
| 9 | HPE DL360 Gen11 Stg Cntrl Enable Cbl Kit | 1 | 7,20 € | 7,20 € |
|
||||
| 10 | HPE MR408i-o Gen11 SPDM Storage Cntlr | 1 | 1.075,46 € | 1.075,46 € |
|
||||
| 11 | BCM 57412 10GbE 2p SFP+ OCP3 Adptr | 1 | 152,35 € | 152,35 € |
|
||||
| 12 | HPE BLc 10G SFP+ SR Transceiver | 2 | 16,20 € | 32,41 € |
|
||||
| 13 | HPE 1800W-2200W FS Ti Ht Plg PS Kit | 2 | 283,11 € | 566,21 € |
|
||||
| 14 | HPE C13 - C14 250V 10A 2m FIO Pwr Cord | 2 | 2,58 € | 5,15 € |
|
||||
| 15 | HPE iLO Adv 1-svr Lic 3yr Support | 1 | 315,27 € | 315,27 € |
|
||||
| 16 | HPE Cmp Cloud Mgmt Srv FIO Enablement | 1 | 1,04 € | 1,04 € |
|
||||
| 17 | HPE DL380/DL560 Gen11 2U GPU Pwr Cbl Kit | 1 | 26,96 € | 26,96 € |
|
||||
| 18 | HPE DL3XX/ML350 G12 CPU1/OCPB x8 Cbl Kit | 1 | 20,84 € | 20,84 € |
|
||||
| 19 | HPE DL380 Gen12 8SFF UMB OROC Cbl Kit | 1 | 22,71 € | 22,71 € |
|
||||
| 20 | HPE DL380/DL560 G11 2U High Perf Fan Kit | 1 | 197,57 € | 197,57 € |
|
||||
| 21 | HPE DL380 Gen12 GPU Air Upg Enable Kit | 1 | 158,88 € | 158,88 € |
|
||||
| 22 | HPE DL3XX Gen11 Easy Install Rail 3 Kit | 1 | 41,60 € | 41,60 € |
|
||||
| 23 | HPE Localization FIO Kit | 1 | 0,01 € | 0,01 € |
|
||||
| 24 | HPE DL3XX Gen12 High Perf Heat Sink Kit | 2 | 75,27 € | 150,54 € |
|
||||
| 25 | HPE 23C Max Rec Ambient Temp Config Trk | 1 | 0,01 € | 0,01 € |
|
||||
| 26 | HPE COM Std 5yr Up ProLiant SaaS | 1 | 490,13 € | 490,13 € |
|
||||
| 27 | HPE 5Y Tech Care Essential SVC | 1 | none | none |
|
||||
| 28 | HPE iLO Advanced Non Blade Support | 1 | 25,17 € | 25,17 € |
|
||||
| 29 | HPE DL380 Gen12 Support | 1 | 4.145,87 € | 4.145,87 € |
|
||||
| 30 | HP ENTERPRISE X242 10G SFP+ to SFP+ 3m DAC Cable | 1 | 96,00 € | 96,00 € |
|
||||
@@ -0,0 +1,31 @@
|
||||
# phy-srv-gpu01
|
||||
|
||||
GPU server for AI/ML workloads. Hardware is ordered/assessed; OS setup and configuration are the next step.
|
||||
|
||||
| | |
|
||||
| - | - |
|
||||
| IP | 192.168.66.69 (planned) |
|
||||
| Ansible group | `phy_srv_gpu01` |
|
||||
| Status | planned — not yet configured |
|
||||
|
||||
## Hardware
|
||||
|
||||
HPE ProLiant DL380 Gen12, 2× Intel Xeon 6714P (8-core, 4.0 GHz), 128 GB RAM,
|
||||
NVIDIA RTX PRO 6000 96 GB, 2× 960 GB NVMe SSD, redundant PSU — full BOM in [HW.md](HW.md).
|
||||
|
||||
## Planning notes
|
||||
|
||||
- [Hardware assessment (2026-07-06)](notes/20260706-hardware-assessment.md)
|
||||
- [Software assessment (2026-07-06)](notes/20260706-software-assessment.md)
|
||||
- [Deep dive (2026-07-07)](notes/20260707-deep-dive.md)
|
||||
- [Project plan for the setup (2026-07-10)](notes/20260710-projektplan.md) — start here when the server arrives
|
||||
|
||||
Open pre-work items are tracked in the repo-root [TODO.md](../../TODO.md).
|
||||
|
||||
## Runbooks
|
||||
|
||||
- [NVIDIA driver, CUDA repo & container toolkit (2026-07-14)](manuals/20260714-nvidia-driver-install.md) — automated by the Ansible role `nvidia_gpu`
|
||||
|
||||
## Scripts
|
||||
|
||||
- [share-analysis.ps1](scripts/share-analysis.ps1) — read-only SMB share analysis (projektplan §2.1); run on a Windows machine with read access to the shares
|
||||
@@ -0,0 +1,87 @@
|
||||
# Manual nvidia driver, cuda repo and container toolkit
|
||||
|
||||
Automated by the Ansible role `ansible/roles/nvidia_gpu` — this manual documents the steps.
|
||||
|
||||
**Disable Secure Boot in BIOS first** (unsigned kernel modules won't load otherwise).
|
||||
|
||||
## NVIDIA driver
|
||||
|
||||
Check if GPUs are recognized by the base OS:
|
||||
```bash
|
||||
sudo lspci | grep -i nvidia
|
||||
```
|
||||
|
||||
Which should show some output if it finds nvidia devices.
|
||||
|
||||
Search for available drivers for your GPUs:
|
||||
```bash
|
||||
sudo ubuntu-drivers devices
|
||||
```
|
||||
|
||||
Install the driver pinned. The RTX PRO 6000 (Blackwell) needs **driver >= 580**
|
||||
and **only works with the open kernel modules** — the proprietary ones load but
|
||||
fail at `RmInitAdapter`, leaving `nvidia-smi` with "No devices were found".
|
||||
Use the `-server-open` variant and prefer a pinned install over
|
||||
`ubuntu-drivers autoinstall` so the choice is explicit and reproducible
|
||||
(re-check for a newer branch at install time):
|
||||
```bash
|
||||
sudo apt install -y --no-install-recommends nvidia-driver-595-server-open
|
||||
```
|
||||
|
||||
`--no-install-recommends` keeps `nvidia-settings` and its GTK dependency chain
|
||||
off this headless server.
|
||||
|
||||
> Note: do **not** additionally install `cuda-drivers` from the NVIDIA repo —
|
||||
> that would mix the Ubuntu-archive driver with the NVIDIA-repo driver and the
|
||||
> two can conflict. Pick one source; we use the Ubuntu archive.
|
||||
|
||||
Reboot the system for changes to take effect:
|
||||
```bash
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
Show GPU stats with:
|
||||
```bash
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
## CUDA repository (toolkit optional)
|
||||
|
||||
Add the NVIDIA CUDA apt repository:
|
||||
|
||||
```bash
|
||||
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb
|
||||
sudo dpkg -i cuda-keyring_1.1-1_all.deb
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
The full CUDA toolkit is **not needed** for Docker-based workloads (vLLM etc. —
|
||||
the driver plus container toolkit suffice). Only if compiling on the host:
|
||||
```bash
|
||||
sudo apt install -y cuda-toolkit # meta package, pulls the current release
|
||||
```
|
||||
|
||||
## Container toolkit
|
||||
|
||||
Install the Nvidia Container toolkit:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
|
||||
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
|
||||
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
|
||||
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
|
||||
sudo apt update
|
||||
sudo apt install -y nvidia-container-toolkit
|
||||
```
|
||||
|
||||
Configure Docker to use the NVIDIA runtime (writes `/etc/docker/daemon.json`) and restart it —
|
||||
without this step `docker run --gpus all` fails:
|
||||
```bash
|
||||
sudo nvidia-ctk runtime configure --runtime=docker
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
Test a simple cuda container and nvidia-smi command inside:
|
||||
```bash
|
||||
docker run --rm --gpus all nvidia/cuda:13.0.0-base-ubuntu24.04 nvidia-smi
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# Hardware Assessment — GPU Server für LLM-Workloads
|
||||
|
||||
Date: 2026-07-06
|
||||
Status: Draft for internal review
|
||||
Budget: ~50.000 €
|
||||
|
||||
## TL;DR
|
||||
|
||||
Die Beobachtung vom Bestandsserver (CPU < 10 %, RAM < 150 GB, GPUs zeitweise
|
||||
voll ausgelastet) ist typisch: **LLM-Inference ist fast reine GPU-Last.**
|
||||
Downscaling von CPU und RAM ist richtig. Bei der GPU empfehle ich statt einer
|
||||
halben H100-NVL-Konfiguration die **NVIDIA RTX PRO 6000 Blackwell Server
|
||||
Edition (96 GB)** — mehr VRAM als eine H100 NVL, in Single-GPU-Inference-
|
||||
Benchmarks gleichauf oder schneller, für etwa **ein Drittel des Preises**
|
||||
(~9–10 k€ statt ~31 k€). Gesamtsystem landet grob bei **20–25 k€ inkl.
|
||||
Support** — deutlich unter Budget.
|
||||
|
||||
## 1. Einordnung des Bestandsservers
|
||||
|
||||
Die Referenz-Konfiguration (DL380a Gen11, 2× Xeon 6526Y, 512 GB RAM,
|
||||
2× H100 NVL) ist für den Inference-Use-Case überdimensioniert:
|
||||
|
||||
- **CPU < 10 %**: erwartbar — bei GPU-Inference macht die CPU nur
|
||||
Tokenizing, Scheduling und I/O. 2× 16 Kerne sind unnötig.
|
||||
- **RAM < 100–150 GB**: davon ist erfahrungsgemäß ein großer Teil
|
||||
Page-Cache. Echter Bedarf: Modell-Staging + Services, deutlich unter 128 GB.
|
||||
- **2× H100 NVL**: NVLink-Pärchen lohnt sich nur, wenn ein Modell über beide
|
||||
GPUs gespannt wird (Tensor-Parallel). Für Modelle ≤ 96 GB unnötig.
|
||||
|
||||
## 2. GPU-Empfehlung
|
||||
|
||||
| | RTX PRO 6000 Blackwell SE | H100 NVL | L40S |
|
||||
|---|---|---|---|
|
||||
| VRAM | **96 GB GDDR7** | 94 GB HBM3 | 48 GB GDDR6 |
|
||||
| Speicherbandbreite | 1.792 GB/s | 3.900 GB/s | 864 GB/s |
|
||||
| FP8/FP4 | ✅ / ✅ (Blackwell) | ✅ / ❌ | ❌ / ❌ |
|
||||
| NVLink | ❌ (hier irrelevant, 1 GPU) | ✅ | ❌ |
|
||||
| Straßenpreis (ca.) | **~9–10 k€** | ~31 k€ | ~8 k€ |
|
||||
|
||||
- Benchmarks 2026 zeigen die RTX PRO 6000 bei Single-GPU-LLM-Inference
|
||||
**gleichauf bis vor der H100** bei ~28 % niedrigeren Kosten pro Token
|
||||
([cloudrift.ai](https://www.cloudrift.ai/blog/benchmarking-rtx6000-vs-datacenter-gpus),
|
||||
[openmetal.io](https://openmetal.io/resources/blog/comparing-the-nvidia-rtx-pro-6000-vs-h100-for-ai-inference/)).
|
||||
- Gegenüber der in den Notizen angesetzten 48-GB-Karte (L40S-Klasse):
|
||||
doppelter VRAM und > 2× Bandbreite für ähnliches Geld — 96 GB erlauben
|
||||
70B-Modelle in FP8 bzw. viel KV-Cache für parallele Nutzer
|
||||
([gpuperhour.com](https://gpuperhour.com/compare/l40s-vs-rtx-pro-6000-blackwell)).
|
||||
- H100 NVL nur dann, wenn später auf 2 GPUs mit Tensor-Parallelität erweitert
|
||||
werden soll — dafür gibt es hier kein Anzeichen.
|
||||
|
||||
⚠️ Mit dem Distributor klären, welche HPE-Plattform (DL380 Gen11/Gen12 bzw.
|
||||
DL380a) für die RTX PRO 6000 Blackwell SE qualifiziert ist.
|
||||
|
||||
## 3. Empfohlene Konfiguration (angelehnt an das Referenz-Angebot)
|
||||
|
||||
| Komponente | Referenz-Server | Empfehlung neu | Begründung |
|
||||
|---|---|---|---|
|
||||
| Chassis | DL380a Gen11 | DL380 Gen11/Gen12 mit GPU-Kit (oder DL380a) | 1 GPU braucht kein 4-GPU-Chassis |
|
||||
| CPU | 2× Xeon 6526Y (32C) | **1× Xeon 16C** (z. B. 6526Y o. kleiner) | CPU-Last < 10 % beobachtet |
|
||||
| RAM | 512 GB (8× 64 GB) | **128 GB (8× 16 GB)** | Alle 8 Kanäle belegt (Bandbreite), Upgrade-Pfad auf 256 GB frei |
|
||||
| Boot-SSD | 2× 960 GB NVMe | 2× 960 GB NVMe (RAID 1) | unverändert |
|
||||
| Daten-SSD | — | **+ 2× 3,84 TB NVMe (RAID 1)** | Modelle + Vektor-Index der Datei-Suche |
|
||||
| NIC | 2× 25 GbE + 4× 1 GbE | 1× 25/10 GbE SFP28 + 1 GbE (Mgmt) | Indizierung zieht die SMB-Shares übers Netz — 1 GbE wäre der Flaschenhals |
|
||||
| GPU | 2× H100 NVL 94 GB | **1× RTX PRO 6000 Blackwell SE 96 GB** | s. o. |
|
||||
| PSU | 4× 1800–2200 W | 2× 1800 W (redundant) | 1 GPU à 600 W |
|
||||
| iLO Adv + Support | 5 J. | 5 J. Tech Care Essential | unverändert |
|
||||
|
||||
**Grobe Kostenschätzung:** Server-Basis + CPU + RAM + Storage ≈ 10–12 k€,
|
||||
GPU ≈ 9–10 k€, Support ≈ 4–6 k€ → **~23–28 k€**. Es bleibt Budget für
|
||||
Einrichtung/Software-Dienstleistung (RAG-Pipeline!) und ggf. eine zweite GPU
|
||||
später — das Chassis dafür gleich mit einplanen (Riser/Netzteile).
|
||||
|
||||
## 4. Passt eine GPU für "viele Nutzer"?
|
||||
|
||||
Ja, für den beschriebenen Fall (Nicht-Power-User, Chat + Datei-Suche):
|
||||
|
||||
- Nicht-Power-User erzeugen wenig gleichzeitige Last; realistisch sind
|
||||
5–20 parallele Anfragen selbst bei 100+ angebundenen Usern.
|
||||
- vLLM mit einem 20–32B-Modell in FP8 auf 96 GB bedient das komfortabel;
|
||||
ein 70B-Modell geht ebenfalls, mit weniger Parallelitäts-Headroom.
|
||||
- Wachstumspfad: zweite RTX PRO 6000 nachrüsten (zwei unabhängige
|
||||
vLLM-Instanzen bzw. Modell-Replikation — kein NVLink nötig).
|
||||
|
||||
## Quellen
|
||||
|
||||
- [RTX PRO 6000 vs H100/H200/L40S LLM Inference Benchmarks (cloudrift.ai)](https://www.cloudrift.ai/blog/benchmarking-rtx6000-vs-datacenter-gpus)
|
||||
- [RTX PRO 6000 Pricing July 2026 (Thunder Compute)](https://www.thundercompute.com/blog/nvidia-rtx-pro-6000-pricing)
|
||||
- [L40S vs RTX PRO 6000 (gpuperhour)](https://gpuperhour.com/compare/l40s-vs-rtx-pro-6000-blackwell)
|
||||
- [RTX PRO 6000 vs H100 for AI Inference (OpenMetal)](https://openmetal.io/resources/blog/comparing-the-nvidia-rtx-pro-6000-vs-h100-for-ai-inference/)
|
||||
- [RTX PRO 6000 Benchmarks: 30B AWQ, 70B FP8, Cost per Mtok (Spheron)](https://www.spheron.network/blog/rent-nvidia-rtx-pro-6000/)
|
||||
@@ -0,0 +1,119 @@
|
||||
# Software Assessment — GPU Server für LLM-Workloads
|
||||
|
||||
Date: 2026-07-06
|
||||
Status: Draft for internal review
|
||||
|
||||
## TL;DR
|
||||
|
||||
**LM Studio ist als Server-Dienst für viele Benutzer nicht geeignet.** Es ist ein
|
||||
Desktop-Tool für Einzelnutzer. Empfehlung: **vLLM** als Inference-Server +
|
||||
**Open WebUI** als Benutzeroberfläche (Multi-User, LDAP). Für die Datei-Suche
|
||||
über SMB-Shares wird zusätzlich eine RAG-/Enterprise-Search-Komponente
|
||||
benötigt — das ist der eigentlich anspruchsvolle Teil des Projekts, nicht das
|
||||
LLM-Hosting.
|
||||
|
||||
## 1. Ist LM Studio als Server-Dienst geeignet?
|
||||
|
||||
Kurz: **Nein**, nicht für den Multi-User-Betrieb.
|
||||
|
||||
Was LM Studio inzwischen kann:
|
||||
|
||||
- Headless-Betrieb ohne GUI über den `llmster`-Daemon (offiziell dokumentiert:
|
||||
[lmstudio.ai/docs/app/api/headless](https://lmstudio.ai/docs/app/api/headless))
|
||||
- OpenAI-kompatible lokale API
|
||||
- Seit 2025 auch für kommerzielle Nutzung kostenlos
|
||||
|
||||
Warum es trotzdem ungeeignet ist:
|
||||
|
||||
| Anforderung | LM Studio |
|
||||
|---|---|
|
||||
| Multi-User / Benutzerverwaltung | ❌ Keine — die API ist für einen lokalen Einzelnutzer gedacht, ohne Benutzerkonten |
|
||||
| LDAP/AD-Anbindung | ❌ Nicht vorhanden |
|
||||
| Gleichzeitige Anfragen vieler Nutzer | ❌ llama.cpp-Backend, kein Continuous Batching wie vLLM — Durchsatz bricht bei Parallelität ein |
|
||||
| Modell-Verwaltung im Betrieb | ⚠️ Modell-Laden ist exklusiv; Wechsel unterbricht den Dienst |
|
||||
| RAG / Suche über Netzlaufwerke | ❌ Nur lokale Datei-Anhänge im Chat, keine Index-Pipeline |
|
||||
|
||||
Fazit: LM Studio bleibt sinnvoll als **lokales Tool für Power-User am
|
||||
Arbeitsplatz**, aber der Server braucht einen echten Inference-Stack.
|
||||
|
||||
## 2. Empfohlener Software-Stack
|
||||
|
||||
```
|
||||
[User-Browser] ──► Open WebUI (Auth via LDAP/AD, Chat-UI, RAG)
|
||||
│ OpenAI-kompatible API
|
||||
▼
|
||||
vLLM (Inference-Server, Continuous Batching)
|
||||
▼
|
||||
1× GPU (96 GB VRAM)
|
||||
|
||||
[SMB-Shares] ──► Index-Pipeline (Embedding + Vektor-DB) ──► RAG-Abfragen
|
||||
```
|
||||
|
||||
### Inference: vLLM
|
||||
|
||||
- De-facto-Standard für Multi-User-LLM-Serving: Continuous Batching,
|
||||
PagedAttention, hoher Durchsatz bei vielen gleichzeitigen Nutzern
|
||||
- OpenAI-kompatible API → jedes Frontend (auch LM Studio-Clients der
|
||||
Power-User) kann sich verbinden
|
||||
- Läuft als Docker-Container, gut automatisierbar
|
||||
|
||||
### Frontend: Open WebUI
|
||||
|
||||
- Multi-User mit Rollen/Gruppen, **native LDAP-Anbindung** (deckt die
|
||||
Anforderung "Alle User angebunden (LDAP?)" aus den Notizen ab)
|
||||
- Chat-UI im ChatGPT-Stil — geeignet für Nicht-Power-User
|
||||
- Eingebaute RAG-Funktion ("Knowledge Bases"): Dokumente hochladen und
|
||||
durchsuchen
|
||||
|
||||
### Datei-Suche über SMB-Shares — der kritische Punkt
|
||||
|
||||
"Suchmaschine für eigene Daten" heißt: die Shares müssen **indiziert** werden
|
||||
(Embedding-Modell + Vektor-Datenbank), nicht nur per Chat angehängt. Optionen:
|
||||
|
||||
1. **Open WebUI Knowledge Bases** — seit v0.9.6 mit offiziellem Sync-Tool
|
||||
`oikb`: gemountete Verzeichnisse (→ SMB-Shares) werden inkrementell in
|
||||
Knowledge Bases gespiegelt. Details: `20260707-deep-dive.md` §4.3.
|
||||
2. **Onyx (ehem. Danswer)** — dediziertes Enterprise-Search-Produkt,
|
||||
Konnektoren für Datei-Quellen, eigene Benutzer-/Rechteverwaltung,
|
||||
nutzt das lokale LLM via OpenAI-API. Passender für den Vollausbau.
|
||||
3. Eigene Pipeline (gemountete Shares + Embedding-Job + Qdrant/pgvector) —
|
||||
maximale Kontrolle, mehr Aufwand.
|
||||
|
||||
⚠️ **Offener Punkt, vor Angebot klären:** Müssen Suchergebnisse die
|
||||
NTFS-/Share-Berechtigungen respektieren (User A darf Dokumente von Abteilung B
|
||||
nicht sehen)? **Permission-Trimming ist in keiner der Standard-Lösungen
|
||||
trivial** und bestimmt maßgeblich Aufwand und Produktwahl. Wenn ja, ist Onyx
|
||||
oder eine kommerzielle Lösung fast zwingend; wenn die Shares ohnehin für alle
|
||||
lesbar sind, reicht der einfache Stack.
|
||||
|
||||
### Modelle (bei 96 GB VRAM)
|
||||
|
||||
- Start: ein 20–32B-Instruct-Modell (z. B. Qwen3-32B oder gpt-oss-20b) in
|
||||
FP8 — schnell, viel KV-Cache-Headroom für viele parallele Nutzer, gutes
|
||||
Deutsch
|
||||
- Option: 70B-Klasse in FP8 oder gpt-oss-120b (MXFP4, ~63 GB) passt auf die
|
||||
eine Karte, wenn mehr Qualität gewünscht ist — auf Kosten des Durchsatzes
|
||||
- Zusätzlich ein Embedding-Modell für die Suche (z. B. multilingual-e5 oder
|
||||
bge-m3, klein, läuft nebenher auf derselben GPU)
|
||||
|
||||
### Betrieb
|
||||
|
||||
- Ubuntu Server 24.04 LTS, NVIDIA-Treiber + Container Toolkit
|
||||
- Alles als Docker Compose (vLLM, Open WebUI, Vektor-DB) → reproduzierbar,
|
||||
einfache Updates
|
||||
- Backup nur für Konfiguration + Vektor-DB nötig; Modelle sind wiederbeschaffbar
|
||||
|
||||
## 3. Offene Fragen an den Kunden
|
||||
|
||||
1. Wie viele Benutzer insgesamt / gleichzeitig? (bestimmt Modellgröße)
|
||||
2. Datenvolumen und Dateitypen auf den Shares? (Office, PDF, Scans/OCR?)
|
||||
3. Müssen Berechtigungen der Shares in der Suche durchgesetzt werden? (s. o.)
|
||||
4. Nur Chat + Suche, oder auch API-Zugriff für Anwendungen/Skripte?
|
||||
|
||||
## Quellen
|
||||
|
||||
- [LM Studio Docs — Headless/llmster](https://lmstudio.ai/docs/app/api/headless)
|
||||
- [Top 20 Tools to Run LLMs Locally in 2026 (iunera)](https://www.iunera.com/kraken/enterprise-ai/top-20-tools-to-run-llms-locally-in-2026-ollama-anythingllm-open-webui-lm-studio-vllm-and-every-real-alternative-compared/)
|
||||
- [Open WebUI Overview (glukhov.org)](https://www.glukhov.org/llm-hosting/llm-frontends/open-webui-overview-quickstart-and-alternatives/)
|
||||
- [LM Studio Complete Guide 2026 (codersera)](https://codersera.com/blog/lm-studio-complete-guide-2026/)
|
||||
- [Open WebUI + LM Studio Integration (markaicode)](https://markaicode.com/integrate/open-webui-with-lm-studio/)
|
||||
@@ -0,0 +1,427 @@
|
||||
# Deep Dive — GPU-Auswahl, Inference-Backend & Anbindung der SMB-Wissensbasis
|
||||
|
||||
Date: 2026-07-07
|
||||
Status: Entscheidungsgrundlage für Kundengespräch
|
||||
Basiert auf: `20260706-hardware-assessment.md`, `20260706-software-assessment.md`
|
||||
|
||||
---
|
||||
|
||||
## 0. Executive Summary
|
||||
|
||||
| Entscheidung | Empfehlung | Konfidenz |
|
||||
|---|---|---|
|
||||
| GPU | **RTX PRO 6000 Blackwell Server Edition (96 GB)** statt H100 NVL | Hoch — durch mehrere unabhängige Benchmarks gedeckt |
|
||||
| LM Studio als Server | **Nein** — Desktop-Tool, kein Mehrbenutzer-Server | Hoch |
|
||||
| Inference-Backend | **vLLM** (ggf. Ollama zusätzlich für Power-User-Experimente) | Hoch |
|
||||
| Frontend | **Open WebUI** (LDAP/AD, Gruppen, Knowledge Bases) | Hoch |
|
||||
| SMB-Anbindung | **Phase 1: Open WebUI Knowledge + offizielles Sync-Tool `oikb`**; Phase 2: Onyx nur falls Skalierung/Berechtigungen es erzwingen | Mittel — hängt von offenen Fragen ab (§6) |
|
||||
|
||||
---
|
||||
|
||||
## 1. GPU: RTX PRO 6000 Blackwell SE vs. H100 NVL
|
||||
|
||||
Der Kunde hat die H100 genannt. Das ist verständlich („die KI-GPU"), aber die
|
||||
H100 ist eine **Hopper-Karte von 2022/23** und für verteiltes Training bzw.
|
||||
Multi-GPU-Inference gebaut. Die RTX PRO 6000 Blackwell Server Edition (2025)
|
||||
ist **zwei Architektur-Generationen neuer** und von NVIDIA explizit als
|
||||
Enterprise-Inference-Karte positioniert.
|
||||
|
||||
### 1.1 Daten im Vergleich
|
||||
|
||||
| | RTX PRO 6000 Blackwell SE | H100 NVL |
|
||||
|---|---|---|
|
||||
| Architektur | Blackwell (2025) | Hopper (2022/23) |
|
||||
| VRAM | **96 GB GDDR7, ECC** | 94 GB HBM3, ECC |
|
||||
| Speicherbandbreite | 1,79 TB/s | **3,9 TB/s** |
|
||||
| FP8 / FP4 (NVFP4) | ✅ / **✅** | ✅ / ❌ |
|
||||
| MIG (GPU-Partitionierung) | ✅ bis 4 Instanzen ([NVIDIA](https://www.nvidia.com/en-us/data-center/rtx-pro-6000-blackwell-server-edition/)) | ✅ bis 7 Instanzen |
|
||||
| vGPU-Support | ✅ (vGPU 19.0, [NVIDIA Blog](https://developer.nvidia.com/blog/nvidia-vgpu-19-0-enables-graphics-and-ai-virtualization-on-nvidia-blackwell-gpus/)) | ✅ |
|
||||
| NVLink | ❌ (nur PCIe Gen5) | ✅ (Paarweise) |
|
||||
| Leistungsaufnahme | 600 W (passiv, Server-Kühlung) | ~400 W |
|
||||
| Formfaktor | PCIe FHFL Dual-Slot, OEM-qualifiziert (u. a. [Lenovo](https://lenovopress.lenovo.com/lp2263-thinksystem-nvidia-rtx-pro-6000-blackwell-server-edition-pcie-gen5-gpu), HPE) | PCIe Dual-Slot |
|
||||
| Straßenpreis | **~9–10 k€** | ~31 k€ (lt. Referenz-Angebot) |
|
||||
|
||||
### 1.2 Benchmark-Evidenz (der Kern der Argumentation)
|
||||
|
||||
Mehrere **unabhängige** Quellen, alle mit vLLM als Serving-Stack:
|
||||
|
||||
1. **Single-GPU-Durchsatz:** RTX PRO 6000 schlägt sogar die H100 SXM
|
||||
(3.140 vs. 2.987 tok/s) bei **28 % niedrigeren Kosten pro Token**
|
||||
([cloudrift.ai](https://www.cloudrift.ai/blog/benchmarking-rtx6000-vs-datacenter-gpus)).
|
||||
2. **Unter Parallellast** (unser Szenario!): stärkste Skalierung aller
|
||||
getesteten Karten; auf Qwen-32B vor A100 **und** H100
|
||||
([databasemart vLLM-Benchmark](https://www.databasemart.com/blog/vllm-gpu-benchmark-pro6000)).
|
||||
3. **Bei 100 gleichzeitigen Requests**: bis zu **1,63× höherer Durchsatz als
|
||||
die H100** ([Akamai-Benchmark](https://www.akamai.com/blog/cloud/benchmarking-nvidia-rtx-pro-6000-blackwell-akamai-cloud)).
|
||||
4. Kostenanalyse pro Million Token: [Spheron](https://www.spheron.network/blog/rent-nvidia-rtx-pro-6000/),
|
||||
unabhängige Tests: [HOSTKEY](https://huggingface.co/blog/HOSTKEY/nvidia-rtx-6000-blackwell-server-edition-tests-ben),
|
||||
Vergleich: [OpenMetal](https://openmetal.io/resources/blog/comparing-the-nvidia-rtx-pro-6000-vs-h100-for-ai-inference/).
|
||||
|
||||
Warum schlägt GDDR7 hier HBM3? Die H100 spielt ihre Bandbreite erst bei
|
||||
Multi-GPU-Tensor-Parallelität über NVLink aus. Bei **einer** Karte limitiert
|
||||
meist Compute/KV-Cache-Management — und da ist Blackwell schlicht neuer
|
||||
(mehr FP8/FP4-Durchsatz, größere L2-Caches, besserer Scheduler).
|
||||
|
||||
### 1.3 Wann wäre die H100 trotzdem richtig? (Ehrlichkeit fürs Gespräch)
|
||||
|
||||
- **Multi-GPU-Modelle**: Modelle > 96 GB über 2+ GPUs gespannt → NVLink
|
||||
gewinnt deutlich (8× H100 SXM ≈ 3× Durchsatz von 8× RTX PRO 6000).
|
||||
- **Training/Fine-Tuning im Dauerbetrieb** über mehrere Karten.
|
||||
- **Extreme Kontextlängen + sehr hohe Batchgrößen** (Bandbreiten-limitiert).
|
||||
|
||||
**Keiner dieser Punkte trifft hier zu**: 1 GPU, reine Inference, ≤ 20
|
||||
gleichzeitige Anfragen, Modelle ≤ 96 GB. Selbst der Wachstumspfad (zweite
|
||||
RTX PRO 6000, zwei unabhängige vLLM-Replikate) braucht kein NVLink.
|
||||
|
||||
### 1.4 Argumentationslinie für den Kunden (Kurzform)
|
||||
|
||||
1. „Die H100 ist eine 2022er-Architektur am Ende ihres Zyklus; die RTX PRO
|
||||
6000 ist NVIDIAs aktuelle Enterprise-Inference-Karte (Blackwell, 2025)."
|
||||
2. „Für genau unser Szenario — eine Karte, viele gleichzeitige Chat-Anfragen —
|
||||
ist sie in unabhängigen Benchmarks **gleich schnell bis 1,6× schneller**."
|
||||
3. „Mehr Speicher (96 vs. 94 GB), ECC, MIG, vGPU — alle Enterprise-Merkmale
|
||||
vorhanden, OEM-qualifiziert (HPE/Lenovo/Dell)."
|
||||
4. „**Ein Drittel des Preises.** Die Differenz (~21 k€) finanziert die
|
||||
eigentliche Wertschöpfung — die Such-Pipeline — oder eine zweite GPU."
|
||||
5. Einziger technischer Trade-off: kein NVLink und höhere Leistungsaufnahme
|
||||
(600 W) — beides für diesen Use Case irrelevant bzw. vom Chassis abgedeckt.
|
||||
|
||||
⚠️ Vor Angebot prüfen: HPE-Qualifizierung der Karte für das konkrete Chassis
|
||||
(DL380 Gen12 / DL380a) und vBIOS-Stand für MIG.
|
||||
|
||||
### 1.5 Alternativen — vollständig geprüft (Stand Juli 2026)
|
||||
|
||||
Re-Evaluation gegen das gesamte Feld, nicht nur gegen die H100:
|
||||
|
||||
| Kandidat | VRAM | Preis (ca.) | Bewertung für unseren Fall |
|
||||
|---|---|---|---|
|
||||
| **RTX PRO 6000 Blackwell SE** | 96 GB | **~9–10 k€** | ✅ Empfehlung — s. §1.2, aber Risiken in §1.6 beachten |
|
||||
| H100 NVL | 94 GB | ~26–30 k€ | Ausgereifteste Software-Basis (Hopper/SM90), sonst in allen Benchmarks unterlegen; 3× Preis. **Der risikoärmste, teuerste Weg.** |
|
||||
| H200 NVL | 141 GB | ~30–42 k€ ([IntuitionLabs](https://intuitionlabs.ai/articles/nvidia-ai-gpu-pricing-guide), [JarvisLabs](https://jarvislabs.ai/blog/h200-price)) | 45 GB mehr VRAM als nötig; Preis wie H100+15–20 %. Nur sinnvoll bei Modellen > 96 GB — nicht unser Fall. |
|
||||
| L40S | 48 GB | ~8 k€ | Halber Speicher, weniger als halber Durchsatz (864 GB/s, Ada 2022, kein FP4), kaum billiger. Von der RTX PRO 6000 obsolet gemacht. |
|
||||
| 2× RTX PRO 5000 Blackwell | 2×48 GB | ~9 k€ | Gleicher Gesamt-VRAM, aber Tensor-Parallel über PCIe (Overhead), doppelte Komplexität, gleiche SM120-Risiken. Kein Vorteil. |
|
||||
| RTX 5090 (Consumer) | 32 GB | ~2,5 k€ | Kein ECC, keine OEM-/Server-Qualifizierung, EULA-Grauzone im Rechenzentrum, 32 GB zu klein. Nein. |
|
||||
| AMD MI300X | 192 GB | ~15–18 k€ ([gpucost](https://gpucost.org/gpu/mi300x)) | Auf dem Papier attraktiv (192 GB!), ROCm/vLLM inzwischen First-Class ([AMD](https://rocm.blogs.amd.com/software-tools-optimization/vllm-omni/README.html)). Aber: **OAM-Modul, keine PCIe-Einzelkarte** — existiert praktisch nur in 8-GPU-Plattformen, nicht im ProLiant-2U. Dazu ROCm-Betriebsrisiko ohne AMD-Erfahrung im Team. Scheidet formfaktorbedingt aus. |
|
||||
|
||||
Fazit der Re-Evaluation: Die Empfehlung **hält**. Die einzige echte
|
||||
Alternative ist die H100 NVL als „konservative Versicherung" zum 3-fachen
|
||||
Preis — alle anderen Kandidaten scheitern an Formfaktor, Speicher oder
|
||||
Preis-Leistung.
|
||||
|
||||
### 1.6 Risiken der RTX PRO 6000 SE — ehrlich benannt (wichtig!)
|
||||
|
||||
Die Karte ist neue Architektur (Blackwell/SM120), und 2025 bis Anfang 2026
|
||||
gab es dokumentierte Kinderkrankheiten:
|
||||
|
||||
- **Kernel-Kompatibilität**: SM120 ist nicht abwärtskompatibel zu
|
||||
Hopper-Kerneln; exotische Modellarchitekturen liefen anfangs nicht
|
||||
(z. B. [DeepSeek-Serie](https://github.com/vllm-project/vllm/issues/26211),
|
||||
[NVFP4-Kernel](https://github.com/vllm-project/vllm/issues/23497)).
|
||||
Mainstream-Modelle (Llama, Qwen, Mistral, gpt-oss) sind davon nicht
|
||||
betroffen.
|
||||
- **Reset-Bug bei Virtualisierung** (VM-Passthrough): GPU nach VM-Shutdown
|
||||
in unrecoverable state; ab Treiber 580+ weitgehend behoben. → **Bare-Metal
|
||||
betreiben** (ohnehin empfohlen).
|
||||
- Vereinzelte Berichte über GPU-Resets unter Dauerlast und ein
|
||||
[offenes vLLM-Issue (03/2026)](https://github.com/vllm-project/vllm/issues/36327) —
|
||||
letzteres betrifft `--tensor-parallel-size 4`, also **Multi-GPU, nicht
|
||||
unsere Single-GPU-Konfiguration**.
|
||||
|
||||
**Warum das Risiko tragbar ist (Stand Juli 2026):**
|
||||
|
||||
1. NVIDIA führt die Karte offiziell in den
|
||||
[vLLM-Release-Notes](https://docs.nvidia.com/deeplearning/frameworks/vllm-release-notes/rel-25-09.html)
|
||||
(funktionaler Support, CUDA 13.0) und stellt einen eigenen
|
||||
[vLLM-Container für RTX](https://build.nvidia.com/rtx/vllm) bereit.
|
||||
2. Systemhäuser liefern inzwischen **vLLM-vorvalidierte Server mit genau
|
||||
dieser Karte** aus ([VRLA Tech](https://vrlatech.com/running-vllm-on-your-own-hardware-the-production-guide-for-2026/)).
|
||||
3. HPE-qualifizierte Karte + Support-Vertrag = Hardware-Risiko abgedeckt.
|
||||
|
||||
**Absicherung im Projekt:** aktueller Treiber (≥ 580/581), NVIDIA-vLLM-
|
||||
Container statt Eigenbau, Bare-Metal, Mainstream-Modell, und die Pilotphase
|
||||
(§4.7) als **Burn-in-Test** nutzen — 2 Wochen Dauerlast vor Produktivsetzung.
|
||||
Falls der Kunde maximale Risikoaversion über Preis stellt: H100 NVL als
|
||||
dokumentierte Alternative anbieten und die Differenz transparent machen
|
||||
(~20 k€ „Versicherungsprämie" für ausgereiftere Software-Basis).
|
||||
|
||||
---
|
||||
|
||||
## 2. Warum LM Studio (llmster) kein Server ist — Argumentation
|
||||
|
||||
LM Studio ist bei den Mitarbeitern bekannt und beliebt — das Argument muss
|
||||
also respektvoll sein: **das Tool ist gut, aber es löst eine andere Aufgabe.**
|
||||
|
||||
| Server-Anforderung | LM Studio / llmster | Server-Stack (vLLM + Open WebUI) |
|
||||
|---|---|---|
|
||||
| Benutzerkonten & Login | ❌ keine Benutzerverwaltung | ✅ LDAP/AD, Rollen, Gruppen |
|
||||
| Wer darf was (Modelle, Daten)? | ❌ ein API-Key für alle | ✅ RBAC pro Gruppe/Ressource |
|
||||
| 10–20 gleichzeitige Anfragen | ❌ llama.cpp-Engine, Anfragen stauen sich | ✅ Continuous Batching |
|
||||
| Zentrale Wissensbasis (SMB) | ❌ nur lokale Datei-Anhänge pro Chat | ✅ zentrale, gepflegte Indizes |
|
||||
| Audit/Protokollierung | ❌ | ✅ |
|
||||
| Betrieb (Updates, Monitoring, HA) | ⚠️ Desktop-App-Zyklus | ✅ Container, Standard-Ops |
|
||||
|
||||
Kernsatz fürs Gespräch: *„llmster macht aus LM Studio einen Dienst ohne
|
||||
Bildschirm — aber nicht ohne die Einzelnutzer-Architektur. Es fehlt alles,
|
||||
was aus einem laufenden Modell einen Firmendienst macht: Anmeldung,
|
||||
Berechtigungen, Parallelität, zentrale Datenanbindung."*
|
||||
|
||||
Wichtig: Die Mitarbeiter **verlieren nichts** — Open WebUI fühlt sich wie
|
||||
ChatGPT an (einfacher als LM Studio), und Power-User können LM Studio lokal
|
||||
weiterverwenden oder per API-Key direkt gegen den Server arbeiten.
|
||||
|
||||
---
|
||||
|
||||
## 3. Inference-Backend: vLLM vs. Ollama
|
||||
|
||||
Beide sprechen die OpenAI-API und funktionieren mit Open WebUI. Der
|
||||
Unterschied liegt in Architektur und Zielgruppe.
|
||||
|
||||
### 3.1 Ist vLLM wirklich effizienter? Ja — deutlich, sobald parallel
|
||||
|
||||
Aktuelle Benchmarks (2025/26):
|
||||
|
||||
- Bei **50 gleichzeitigen Nutzern**: vLLM ~920 tok/s gesamt, Ollama
|
||||
plateauiert bei ~155 tok/s; je nach Test **20–29× mehr Durchsatz** und
|
||||
8–19× niedrigere P95-Latenz ([runaihome](https://runaihome.com/blog/vllm-vs-ollama-when-each-wins-2026/),
|
||||
[tech-insider](https://tech-insider.org/vllm-vs-ollama-2026/)).
|
||||
- Peer-reviewte Studie: Ollama erreicht **Engpass bei ~10 gleichzeitigen
|
||||
Nutzern**, 13–30 % Timeouts unter Last; vLLM 100 % Erfolgsquote
|
||||
([MDPI Applied Sciences](https://doi.org/10.3390/app16115435)).
|
||||
- Bei **einem einzelnen Nutzer** ist Ollama minimal schneller beim ersten
|
||||
Token (~45 vs. ~82 ms TTFT) — irrelevant für einen Firmenserver
|
||||
([Red Hat Benchmark](https://developers.redhat.com/articles/2025/08/08/ollama-vs-vllm-deep-dive-performance-benchmarking),
|
||||
[SitePoint](https://www.sitepoint.com/ollama-vs-vllm-performance-benchmark-2026/)).
|
||||
|
||||
### 3.2 Der Modellwechsel-Trade-off, korrekt dargestellt
|
||||
|
||||
| | Ollama | vLLM |
|
||||
|---|---|---|
|
||||
| Modellwechsel | ✅ automatisch, lädt/entlädt on-the-fly | ⚠️ 1 Modell pro Instanz, Wechsel = Neustart (1–2 min) |
|
||||
| Mehrere Modelle parallel | ⚠️ ja, aber teilen sich GPU unkontrolliert | ✅ mehrere Instanzen mit fester VRAM-Zuteilung |
|
||||
| Durchsatz unter Last | ❌ s. o. | ✅ |
|
||||
|
||||
Die Flexibilität von Ollama ist im Firmenbetrieb **weniger wert, als sie
|
||||
klingt**: Nicht-Power-User wollen kein Modell wählen — sie wollen, dass es
|
||||
funktioniert. Ein ungeplanter Modellwechsel entlädt zudem das Modell der
|
||||
anderen 15 aktiven Nutzer (Kaltstart-Latenz für alle).
|
||||
|
||||
**Empfehlung — kein Entweder-oder:**
|
||||
|
||||
```
|
||||
GPU (96 GB, via MIG oder VRAM-Quoten aufteilbar)
|
||||
├── vLLM #1: Haupt-Chatmodell (fest, z. B. Qwen3-32B FP8) ~40 GB
|
||||
├── vLLM #2: Embedding-Modell für die Suche ~4 GB
|
||||
└── optional Ollama: Spielwiese für Power-User Rest
|
||||
```
|
||||
|
||||
Open WebUI kann mehrere Backends gleichzeitig einbinden — die Normalnutzer
|
||||
sehen nur „Firmen-Assistent", Power-User sehen zusätzlich die
|
||||
Ollama-Modelle. Damit ist die Kundenfrage („Flexibilität vs. Effizienz")
|
||||
keine Entscheidung mehr, sondern eine Konfiguration.
|
||||
|
||||
### 3.3 Lastabschätzung (100 Mitarbeiter)
|
||||
|
||||
Erfahrungswerte für interne Chat-Assistenten: ~30–60 % registrieren sich,
|
||||
davon ~10–20 % gleichzeitig aktiv in Spitzen → **5–15 parallele Anfragen**.
|
||||
Das bedient eine RTX PRO 6000 mit einem 32B-FP8-Modell unter vLLM komfortabel
|
||||
(vgl. Benchmarks oben: brauchbare Latenz noch bei 50–100 parallelen Requests
|
||||
mit kleineren Modellen).
|
||||
|
||||
---
|
||||
|
||||
## 4. Anbindung der Wissensbasis (SMB-Shares) — der Deep Dive
|
||||
|
||||
### 4.1 Was technisch passieren muss (unabhängig vom Produkt)
|
||||
|
||||
Ein LLM kann nicht „auf den Share schauen". Die Pipeline ist immer:
|
||||
|
||||
```
|
||||
SMB-Share ──mount──► Server (read-only, Service-Account)
|
||||
│
|
||||
▼
|
||||
Extraktion (PDF, Office, E-Mail…; OCR für Scans)
|
||||
▼
|
||||
Chunking + Embedding (Embedding-Modell auf der GPU)
|
||||
▼
|
||||
Vektor-Datenbank (Index) ← periodischer Sync (cron)
|
||||
▼
|
||||
Retrieval zur Laufzeit: Frage → relevante Chunks → LLM antwortet mit Quellen
|
||||
```
|
||||
|
||||
Die Produktfrage ist nur: **wer betreibt diese Pipeline und wie gut?**
|
||||
|
||||
### 4.2 Option A — LM Studio: scheidet aus
|
||||
|
||||
Kein zentraler Index möglich. Jeder Nutzer müsste Dateien manuell in seinen
|
||||
Chat ziehen (RAG pro Sitzung, wenige Dateien). Für „Suchmaschine über die
|
||||
Firmendaten" schlicht das falsche Werkzeug — unabhängig von der Serverfrage.
|
||||
|
||||
### 4.3 Option B — Open WebUI Knowledge + offizielles Sync-Tool `oikb` ⭐
|
||||
|
||||
Seit **v0.9.6** hat Open WebUI eine offizielle Lösung genau für unseren Fall
|
||||
([Docs](https://docs.openwebui.com/features/knowledge-base-sync/),
|
||||
[GitHub open-webui/oikb](https://github.com/open-webui/oikb)):
|
||||
|
||||
- **`oikb`** (CLI, [PyPI](https://pypi.org/project/oikb/)) synchronisiert ein
|
||||
Verzeichnis — also einen **auf dem Server gemounteten SMB-Share** — in eine
|
||||
Knowledge Base: inkrementell per SHA-256-Diff, nur neue/geänderte Dateien
|
||||
werden neu embedded, Löschungen werden nachgezogen, Ordnerstruktur wird
|
||||
gespiegelt. Ein unveränderter 10.000-Dateien-Bestand re-synct in Sekunden.
|
||||
- Läuft als **Cron/systemd-Timer pro Share** → vollautomatisch, kein
|
||||
Nutzer-Zutun.
|
||||
- **Berechtigungen**: Knowledge Bases sind per Gruppe zugreifbar; Open WebUI
|
||||
synchronisiert **Gruppen direkt aus LDAP/AD**
|
||||
([LDAP-Doku](https://docs.openwebui.com/features/authentication-access/auth/ldap/)).
|
||||
Damit lässt sich die Share-Struktur abbilden:
|
||||
|
||||
```
|
||||
\\fs01\konstruktion ──oikb──► KB "Konstruktion" ──► AD-Gruppe Konstruktion
|
||||
\\fs01\vertrieb ──oikb──► KB "Vertrieb" ──► AD-Gruppe Vertrieb
|
||||
\\fs01\allgemein ──oikb──► KB "Allgemein" ──► alle
|
||||
```
|
||||
|
||||
- **Nutzererlebnis Normies**: Vorkonfigurierte „Modelle" in Open WebUI
|
||||
(z. B. „Konstruktions-Assistent" = Chatmodell + KB Konstruktion fest
|
||||
verdrahtet). Der Nutzer wählt den Assistenten und fragt auf Deutsch —
|
||||
kein RAG-Wissen nötig, Antworten mit Quellenangabe (Datei + Passage).
|
||||
- **Power-User**: können eigene Knowledge Bases anlegen, per `#` gezielt
|
||||
Kollektionen in den Chat holen, oder per API-Key direkt gegen vLLM arbeiten
|
||||
(IDE-Integration, Skripte).
|
||||
|
||||
**Grenzen (ehrlich einplanen):**
|
||||
|
||||
1. **Berechtigungsgranularität = Share/Ordner-Ebene**, nicht NTFS-Datei-ACL.
|
||||
Wer Zugriff auf die KB „Konstruktion" hat, kann alles darin erfragen.
|
||||
2. **Skala**: praktikabel bis in den Bereich von einigen zehntausend
|
||||
Dokumenten pro Kollektion; bei sehr großen Shares selektiv syncen
|
||||
(Unterordner, Dateitypen-Filter). Für den DB-Unterbau bei Wachstum
|
||||
**pgvector statt Default** einplanen ([Scaling-Doku](https://docs.openwebui.com/getting-started/advanced-topics/scaling/)).
|
||||
3. **Extraktionsqualität** hängt von den Dateitypen ab: Office/PDF gut;
|
||||
**gescannte PDFs brauchen OCR** (Tika/Docling als Extraktions-Engine
|
||||
konfigurieren); CAD/proprietäre Formate bleiben außen vor.
|
||||
|
||||
### 4.4 Option C — Onyx (ehem. Danswer) als dedizierte Such-Anwendung
|
||||
|
||||
[Onyx](https://github.com/onyx-dot-app/onyx) ist ein spezialisiertes
|
||||
Enterprise-Search-Produkt mit 50+ Konnektoren, darunter ein **File Connector
|
||||
für lokale/Netzlaufwerke** ([Connector-Doku](https://docs.onyx.app/overview/core_features/connectors)) —
|
||||
SMB-Anbindung also wie bei Option B über einen Mount, aber mit produktisierter
|
||||
Crawling-/Index-Pipeline.
|
||||
|
||||
- **Stärken**: bessere Such-Ergonomie (hybride Suche, Reranking), skaliert
|
||||
auf große Bestände, Konnektoren für spätere Quellen (SharePoint, Wiki,
|
||||
E-Mail…), eigenes Berechtigungsmodell.
|
||||
- **Schwächen für unseren Fall**:
|
||||
- **Document-Level Permission Sync ist Enterprise Edition** (kostenpflichtig)
|
||||
und primär für Cloud-Quellen (SharePoint, Google Drive) gebaut — für
|
||||
SMB-NTFS-ACLs gibt es auch hier **keine fertige Lösung**.
|
||||
- **Zweite Anwendung** mit eigener UI, eigener Nutzerverwaltung, eigenem
|
||||
Betrieb (mehrere Container, Vespa-Index, Worker) — spürbar mehr
|
||||
Betriebsaufwand als Option B.
|
||||
- Zwei UIs für die Nutzer (Chat in Open WebUI, Suche in Onyx) oder man
|
||||
ersetzt Open WebUI ganz durch Onyx' Chat — dann verliert man dessen
|
||||
Flexibilität (Multi-Backend, Power-User-Features).
|
||||
|
||||
### 4.5 Option D — Eigenbau-Pipeline (Qdrant/pgvector + eigener Indexer)
|
||||
|
||||
Maximale Kontrolle (eigenes ACL-Modell, beliebige Formate/OCR, Anbindung an
|
||||
Open WebUI über dessen Pipelines/Tools-API) — aber man betreibt und wartet
|
||||
eine Individualsoftware. Nur gerechtfertigt, wenn **Datei-genaue
|
||||
NTFS-Berechtigungen** harte Anforderung sind; dann als Projekt kalkulieren
|
||||
(mehrere Wochen Entwicklung + laufende Pflege).
|
||||
|
||||
### 4.6 Entscheidungsmatrix
|
||||
|
||||
| Kriterium | B: Open WebUI + oikb | C: Onyx | D: Eigenbau |
|
||||
|---|---|---|---|
|
||||
| Aufwand Einführung | **niedrig** (Tage) | mittel (Wochen) | hoch |
|
||||
| Betriebsaufwand | **niedrig** (1 Stack) | mittel (2 Stacks) | hoch |
|
||||
| Berechtigungen | Share/Ordner-Ebene via AD-Gruppen | ähnlich; Datei-Ebene nur EE + nicht für SMB | frei gestaltbar |
|
||||
| Suchqualität große Bestände | gut bis ~10⁴–10⁵ Docs | **sehr gut, skaliert** | je nach Bau |
|
||||
| Normie-Tauglichkeit | **sehr gut** (ein UI, Assistenten) | gut | je nach Bau |
|
||||
| Power-User | **sehr gut** (API, eigene KBs, Ollama) | mittel | gut |
|
||||
| Kosten | Open Source | OSS; Permissions = EE-Lizenz | Entwicklungszeit |
|
||||
|
||||
### 4.7 Empfehlung: Phasenmodell
|
||||
|
||||
- **Phase 1 (Pilot, ~4–6 Wochen):** vLLM + Open WebUI + LDAP + `oikb` auf
|
||||
1–2 ausgewählte Shares (read-only Service-Account). Misst reale Nutzung,
|
||||
Suchqualität und die tatsächliche Parallellast — mit minimalem Invest.
|
||||
- **Phase 2 (Entscheid nach Pilot):** Bleiben = Ausbau Option B (mehr Shares,
|
||||
pgvector, OCR). Nur bei nachgewiesenem Bedarf (sehr große Bestände, harte
|
||||
Berechtigungsanforderungen) → Onyx bzw. Eigenbau ergänzen. Die
|
||||
Investition aus Phase 1 (Server, vLLM, LDAP, Mounts) bleibt vollständig
|
||||
erhalten — Onyx würde denselben vLLM-Endpunkt nutzen.
|
||||
|
||||
Das Phasenmodell ist auch das Verkaufsargument: kein Big-Bang, Budget bleibt
|
||||
unter Kontrolle, und die GPU-Ersparnis aus §1 finanziert die Pilotphase.
|
||||
|
||||
---
|
||||
|
||||
## 5. Gesamtbild (Zielarchitektur Phase 1)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
Mitarbeiter ────────►│ Open WebUI (LDAP/AD-Login, Gruppen) │
|
||||
(Browser) │ ├─ „Firmen-Assistent" + KB je Abteilung │
|
||||
│ └─ Power-User: API-Keys, eigene KBs │
|
||||
└───────┬────────────────────────┬───────────┘
|
||||
Power-User ─────────────────►│ OpenAI-API │ Retrieval
|
||||
(Skripte, IDE, LM Studio ▼ ▼
|
||||
lokal als Client) vLLM #1 Chat 32B FP8 Vektor-DB (pgvector)
|
||||
vLLM #2 Embeddings ▲
|
||||
┌──────────────────────┐ │ oikb-Sync (cron)
|
||||
│ RTX PRO 6000 96 GB │ SMB-Mounts (ro)
|
||||
└──────────────────────┘ ▲
|
||||
\\fs01\... Shares
|
||||
```
|
||||
|
||||
## 6. Offene Fragen an den Kunden (vor Angebotslegung)
|
||||
|
||||
1. **Berechtigungen** (kritischster Punkt): Genügt Zugriffssteuerung auf
|
||||
Share-/Ordner-Ebene via AD-Gruppen — oder müssen Datei-genaue NTFS-ACLs
|
||||
durchgesetzt werden? (Letzteres = Eigenbau-Anteil, deutlich teurer.)
|
||||
2. **Datenbestand**: Wie viele Shares, wie viel Volumen, welche Dateitypen?
|
||||
Anteil gescannter Dokumente (OCR-Bedarf)? CAD-Daten relevant?
|
||||
3. **Nutzerkreis**: Wie viele der ~100 Mitarbeiter sollen initial angebunden
|
||||
werden? Gibt es Abteilungen mit Priorität?
|
||||
4. **H100-Motivation**: Steht hinter dem H100-Wunsch eine konkrete Anforderung
|
||||
(Training? Zukunftspläne?) oder Markenwahrnehmung? → bestimmt, wie viel
|
||||
der Argumentation aus §1.4 nötig ist.
|
||||
5. **Virtualisierung**: Soll die GPU ggf. in eine VM-Umgebung (vGPU/MIG,
|
||||
Lizenzthema) oder Bare-Metal-Betrieb (empfohlen, einfacher)?
|
||||
6. **Sprache**: Dokumente überwiegend Deutsch? (Modellwahl: Qwen3/Mistral/
|
||||
Llama sind mehrsprachig solide; Embedding-Modell muss multilingual sein,
|
||||
z. B. bge-m3.)
|
||||
|
||||
## 7. Quellen
|
||||
|
||||
**GPU:**
|
||||
[NVIDIA RTX PRO 6000 Blackwell SE](https://www.nvidia.com/en-us/data-center/rtx-pro-6000-blackwell-server-edition/) ·
|
||||
[cloudrift.ai Benchmark](https://www.cloudrift.ai/blog/benchmarking-rtx6000-vs-datacenter-gpus) ·
|
||||
[Akamai Benchmark](https://www.akamai.com/blog/cloud/benchmarking-nvidia-rtx-pro-6000-blackwell-akamai-cloud) ·
|
||||
[databasemart vLLM-Benchmark](https://www.databasemart.com/blog/vllm-gpu-benchmark-pro6000) ·
|
||||
[HOSTKEY Tests](https://huggingface.co/blog/HOSTKEY/nvidia-rtx-6000-blackwell-server-edition-tests-ben) ·
|
||||
[OpenMetal RTX PRO 6000 vs H100](https://openmetal.io/resources/blog/comparing-the-nvidia-rtx-pro-6000-vs-h100-for-ai-inference/) ·
|
||||
[Spheron Cost/Mtok](https://www.spheron.network/blog/rent-nvidia-rtx-pro-6000/) ·
|
||||
[Lenovo Product Guide](https://lenovopress.lenovo.com/lp2263-thinksystem-nvidia-rtx-pro-6000-blackwell-server-edition-pcie-gen5-gpu) ·
|
||||
[NVIDIA vGPU 19.0](https://developer.nvidia.com/blog/nvidia-vgpu-19-0-enables-graphics-and-ai-virtualization-on-nvidia-blackwell-gpus/) ·
|
||||
[MIG auf RTX PRO 6000 (Medium)](https://medium.com/@sangjinn/maximize-your-gpu-efficiency-configuring-4-nvidia-mig-instances-on-the-rtx-pro-6000-blackwell-1c9b3714af61)
|
||||
|
||||
**vLLM vs Ollama:**
|
||||
[Red Hat Benchmark](https://developers.redhat.com/articles/2025/08/08/ollama-vs-vllm-deep-dive-performance-benchmarking) ·
|
||||
[MDPI-Studie](https://doi.org/10.3390/app16115435) ·
|
||||
[runaihome Konkurrenz-Zahlen](https://runaihome.com/blog/vllm-vs-ollama-when-each-wins-2026/) ·
|
||||
[tech-insider](https://tech-insider.org/vllm-vs-ollama-2026/) ·
|
||||
[SitePoint](https://www.sitepoint.com/ollama-vs-vllm-performance-benchmark-2026/)
|
||||
|
||||
**Open WebUI / RAG:**
|
||||
[Knowledge-Doku](https://docs.openwebui.com/features/workspace/knowledge/) ·
|
||||
[Knowledge Base Sync / oikb](https://docs.openwebui.com/features/knowledge-base-sync/) ·
|
||||
[oikb GitHub](https://github.com/open-webui/oikb) ·
|
||||
[LDAP-Doku](https://docs.openwebui.com/features/authentication-access/auth/ldap/) ·
|
||||
[Scaling-Doku](https://docs.openwebui.com/getting-started/advanced-topics/scaling/)
|
||||
|
||||
**Onyx:**
|
||||
[Onyx GitHub](https://github.com/onyx-dot-app/onyx) ·
|
||||
[Connector-Übersicht](https://docs.onyx.app/overview/core_features/connectors)
|
||||
@@ -0,0 +1,251 @@
|
||||
# Projektplan — Setup phy-srv-gpu01 (LLM-Server mit SMB-Wissensbasis)
|
||||
|
||||
Date: 2026-07-10 (Stand-Update: 2026-09-03)
|
||||
Status: **In Umsetzung** — Server geliefert, Basis-Setup + GPU-Treiber fertig
|
||||
Basiert auf: `20260706-hardware-assessment.md`, `20260706-software-assessment.md`, `20260707-deep-dive.md`
|
||||
|
||||
---
|
||||
|
||||
## Stand 2026-09-03 (Kurzfassung — Details in den jeweiligen Abschnitten)
|
||||
|
||||
**Erledigt:** Server geliefert und im Rack; Basis-Setup (Ubuntu 24.04.4, Nutzer,
|
||||
Netz, RAID/Filesystem — s. `SETUP.md` im Repo-Root); GPU-Treiber via Ansible-Rolle
|
||||
`nvidia_gpu` (595er-Branch, **open** Kernel-Module — Blackwell läuft nicht mit den
|
||||
proprietären Modulen, s. `manuals/20260714-nvidia-driver-install.md`); Docker
|
||||
installiert. Share-Analyse gelaufen (§2.1).
|
||||
|
||||
**Als Nächstes:** CIFS-Mounts + Container-Stack (Rollen `cifs_mounts`, `llm_stack`)
|
||||
— entspricht Phasen 3–5 in §3.
|
||||
|
||||
**Kritischer offener Punkt:** Der Bestand ist rund **12× größer** als die in §1
|
||||
gesetzte Schwelle. Ohne Eingrenzung des Korpus (Kundengespräch) ist Phase 5 nicht
|
||||
sinnvoll umsetzbar — s. §2.1 und §2.6.
|
||||
|
||||
## 0. Rahmenbedingungen (fixiert)
|
||||
|
||||
| Punkt | Entscheidung |
|
||||
|---|---|
|
||||
| Hardware | HPE DL380 Gen12, 2× Xeon 6714P, 128 GB RAM, RTX PRO 6000 96 GB, 2× 960 GB NVMe — s. `HW.md` |
|
||||
| Betrieb | **Bare Metal**, keine Virtualisierung des LLM-Stacks |
|
||||
| Nutzer | ~25, gemischt (technisch versiert + Nicht-Techniker) |
|
||||
| Auth | **AD/LDAP** (Details Service-Account/Basis-DN als Pre-Work klären) |
|
||||
| Berechtigungen | **Einfach: alle Nutzer sehen den gesamten indizierten Bestand** (Kunde bestätigt; Shares ohne HR-/Personal-Inhalte halten) |
|
||||
| Sprache | Dokumente & Nutzer überwiegend **Deutsch** → Modell- und Embedding-Wahl daran ausrichten |
|
||||
| Inference | **vLLM** mit fester GPU-Auslastung (kein Ollama als Basis) |
|
||||
| Wissensbasis | SMB-Shares (einige TB, PDF/Office), **Sync ist die Kernanforderung** |
|
||||
| Später | Jira-Anbindung u. a. Workloads; dafür ggf. **MIG** |
|
||||
| Vorgehen | Setup vollständig über **Ansible** (dieses Repo), PoC vorab: nein — reine Planung |
|
||||
|
||||
## 1. Produktentscheidung: Open WebUI statt Onyx
|
||||
|
||||
**Empfehlung: Open WebUI** (bestätigt und verschärft die Empfehlung aus dem
|
||||
Deep Dive §4.7). Ausschlaggebend, Stand 2026-07-10 verifiziert:
|
||||
|
||||
1. **Onyx' File-Connector ist reiner manueller Upload** — kein Verzeichnis-Watching,
|
||||
kein periodischer Re-Sync eines gemounteten Shares
|
||||
([Doku](https://docs.onyx.app/admins/connectors/official/file)). Die
|
||||
Kernanforderung „Shares synchron halten" hieße bei Onyx: Eigenbau gegen
|
||||
deren API. Open WebUI hat dafür das offizielle Sync-Tool
|
||||
[`oikb`](https://github.com/open-webui/oikb) (inkrementell, Löschungen,
|
||||
Cron-fähig — Deep Dive §4.3).
|
||||
2. **Onyx: SSO (OIDC/SAML), User Groups, RBAC, Permission-Sync = Enterprise
|
||||
Edition (kostenpflichtig)**
|
||||
([Doku](https://docs.onyx.app/deployment/miscellaneous/enterprise_edition)).
|
||||
Open WebUI: LDAP/AD-Anbindung und Gruppen sind frei.
|
||||
3. Onyx bringt einen zweiten schweren Stack mit (Vespa-Index, Postgres,
|
||||
Worker) — auf 128 GB RAM **neben** vLLM spürbar; widerspricht „it just works".
|
||||
4. Bedienung: ein UI im ChatGPT-Stil, vorkonfigurierte Assistenten mit fester
|
||||
Knowledge Base → passt für den gemischten Nutzerkreis.
|
||||
|
||||
**Wann die Entscheidung zu revidieren ist** (Trigger, nach der Share-Analyse §2.1):
|
||||
|
||||
- Der *relevante* Bestand liegt deutlich über ~10⁵ Dokumenten und lässt sich
|
||||
nicht sinnvoll filtern → Suchqualität/Skalierung von Open WebUI prüfen,
|
||||
Onyx (mit Eigenbau-Sync) oder Eigenbau-Pipeline (Deep Dive §4.5) neu bewerten.
|
||||
- Datei-genaue NTFS-ACLs werden doch Anforderung → Neubewertung (Deep Dive §4.4/4.5).
|
||||
|
||||
**Status 2026-09-03:** Trigger 1 ist **eingetreten** (1,26 Mio. Office-Dateien,
|
||||
§2.1). Die Produktentscheidung bleibt dennoch bei Open WebUI — aber **nur unter
|
||||
der Bedingung**, dass der Korpus auf einen kuratierten Teilbestand eingegrenzt
|
||||
wird (§2.6). Ein ungefilterter Vollindex über 1,26 Mio. Dokumente ist mit Open
|
||||
WebUI nicht sinnvoll zu betreiben (und liefert auch inhaltlich schlechte
|
||||
Antworten — RAG über einen kompletten, ungeordneten Fileserver retourniert
|
||||
überwiegend Rauschen).
|
||||
|
||||
Jira später: für *Indizierung* von Jira-Inhalten hat Onyx zwar native
|
||||
Konnektoren, Open WebUI deckt das aber über Tools/MCP (Atlassian-MCP) bzw.
|
||||
API-Workloads direkt gegen vLLM ab — kein Entscheidungskriterium für heute.
|
||||
|
||||
## 2. Phase 0 — Pre-Work (jetzt, ohne Server)
|
||||
|
||||
### 2.1 Share-Analyse (wichtigster Punkt — ja, unbedingt vorab)
|
||||
|
||||
Read-only-Skript auf einer beliebigen Maschine mit Share-Zugriff (kein
|
||||
GPU-Server nötig). Zu erheben:
|
||||
|
||||
- Volumen & Dateianzahl **pro Share und Dateityp** (pdf/docx/xlsx/…)
|
||||
- Anteil **gescannter PDFs** (Stichprobe: PDFs ohne Textlayer) → OCR-Bedarf
|
||||
- Duplikate/Altversionen („Kopie von …", Versionsordner) → Filterregeln
|
||||
- Änderungsrate (mtime-Verteilung) → Sync-Frequenz
|
||||
- Anteil nicht extrahierbarer Formate (CAD, Bilder, Archive) → Erwartungsmanagement
|
||||
- Sonderzeichen/Pfadlängen, Encoding (Umlaute!)
|
||||
|
||||
Ergebnis bestimmt: Korpus-Umfang (→ §1-Trigger), OCR-Pipeline ja/nein,
|
||||
Index-Größe (→ Storage-Check §2.3), Erst-Indexierungsdauer.
|
||||
|
||||
**Ergebnis (Lauf 2026-07-14, Skript `scripts/share-analysis.ps1` auf `D:` des
|
||||
Fileservers Z-FILESERVER):**
|
||||
|
||||
| Kennzahl | Wert | Konsequenz |
|
||||
|---|---|---|
|
||||
| Gesamt | **3.322.175 Dateien, 2.224 GB** | Scan lief über `D:` gesamt, nicht je Share → Obergrenze, enthält evtl. nicht freigegebene Daten |
|
||||
| `office` (extrahierbar) | **1.258.642 Dateien, 587 GB** | **12× über der §1-Schwelle** → Eingrenzung zwingend (§2.6) |
|
||||
| `other` (unklassifiziert) | 1.575.752 Dateien, 934 GB | größter Block, **noch unidentifiziert** → `D-file-types.csv` auswerten |
|
||||
| Bilder / CAD / Archive / Medien | 403k / 60k / 12,6k / 6,2k Dateien | nicht extrahierbar → Erwartungsmanagement |
|
||||
| PDFs | **924.076**, davon ~**18 % ohne Textlayer** (Stichprobe 200) | ≈ **166.000 Scan-PDFs → OCR**; eigener Zeit-/GPU-Aufwand, konkurriert mit vLLM |
|
||||
| Änderungsrate | 94 % älter als 1 Jahr; nur ~158k in den letzten 365 Tagen | Sync-Frequenz unkritisch; **Alter ≠ Irrelevanz** (Datenblätter, Normen) → Filter primär ordner-/inhaltsbasiert, Aktualität nur sekundär |
|
||||
| Duplikat-/Altversions-Muster | 33.179 Dateien | Filterregeln |
|
||||
| Pfade > 240 Zeichen | 25.169 | Extraktions-Toolchain gezielt testen |
|
||||
| Nicht-ASCII-Dateinamen | 126.111 | UTF-8 durchgängig (CIFS-Mount-Optionen → Extraktion → Index) |
|
||||
|
||||
**Noch auszuwerten** (CSVs liegen auf dem Fileserver, noch nicht gesichtet):
|
||||
`D-toplevel-folders.csv` (Größe/Anzahl je Top-Level-Ordner — die Grundlage für
|
||||
die Include-/Exclude-Liste) und `D-file-types.csv` (Auflösung der `other`-Kategorie).
|
||||
|
||||
### 2.2 Mit dem Kunden zu klären (Checkliste) — Stand 2026-09-03
|
||||
|
||||
- [x] AD/LDAP: AD-Gruppe **`llm_users`** wird angelegt. *Offen:* Bind-Service-Account
|
||||
und Basis-DN (werden für die LDAP-Konfiguration in Open WebUI gebraucht)
|
||||
- [x] **SMB-Service-Account read-only** wird separat angelegt (kein Nutzer-Account)
|
||||
- [ ] **Welche Shares/Unterordner in den Index?** — offen, wird besprochen;
|
||||
**wichtigster Punkt**, s. §2.1/§2.6
|
||||
- [x] DNS-Name **`chat.phytron.local`** (vorerst); **kein TLS/keine lokalen
|
||||
Zertifikate** zu Beginn → reines HTTP im LAN. *Hinweis:* AD-Passwörter gehen
|
||||
damit im Klartext über das Netz; für später ein internes CA-Zertifikat
|
||||
einplanen (Reverse Proxy ist vorbereitet, nur Zertifikat fehlt)
|
||||
- [x] Internetzugang: **unbeschränkt**, keine Einschränkung nötig
|
||||
- [ ] Protokollierung/Datenschutz: **noch zu besprechen** (Chat-Logs =
|
||||
Verhaltensdaten; Betriebsrat/DSGVO) — vor dem Rollout klären, nicht vor der
|
||||
technischen Umsetzung
|
||||
- [x] Betrieb/Wartung/Updates: **wir** (Softbox)
|
||||
- [x] „Andere Workloads": **Jira vorerst nicht relevant** → MIG bleibt aus, die
|
||||
volle GPU geht an den LLM-Stack
|
||||
|
||||
### 2.3 Storage-Check — ✅ erledigt, unkritisch
|
||||
|
||||
Ist-Stand auf dem Server: **876 GB nutzbar** (`/`, LVM), davon 818 GB frei.
|
||||
Gegenrechnung mit §2.1: selbst ein Vollindex des `office`-Bestands
|
||||
(1,26 Mio. Dokumente → grob 150–250 GB inkl. extrahiertem Text, Embeddings und
|
||||
Index-Overhead) plus Modelle (~100 GB) passt. Strategie bleibt: **kein lokaler
|
||||
Spiegel der Shares**, nur Index. **Keine zusätzlichen NVMe nötig.**
|
||||
|
||||
Der begrenzende Faktor ist damit *nicht* der Plattenplatz, sondern Retrieval-Qualität
|
||||
und Indexgröße (§2.6).
|
||||
|
||||
### 2.4 Ansible vorbereiten (im Repo, testbar ohne GPU)
|
||||
|
||||
- `run.yml`: Play für `phy_srv_gpu01` ergänzen
|
||||
- Rollen-Skelett unter `ansible/roles/` (Details erst bei Umsetzung):
|
||||
- `nvidia_gpu` — Treiber (≥ 580), Container Toolkit, optional MIG, DCGM
|
||||
- `cifs_mounts` — ro-Mounts, Credentials aus `group_vars/secrets.yml`
|
||||
- `llm_stack` — Docker Compose: vLLM (feste `--gpu-memory-utilization`),
|
||||
Embedding-Server, Open WebUI + pgvector, Reverse Proxy (TLS), oikb-Timer
|
||||
- Bestehendes nachnutzen: `geerlingguy.security` (Basis-Härtung),
|
||||
`geerlingguy.docker`
|
||||
- `group_vars/phy_srv_gpu01.yml`: nur Overrides (Modellname, VRAM-Quote,
|
||||
Share-Liste, LDAP-Parameter)
|
||||
- Alles außer GPU-Rolle ist vorab in einer Wegwerf-VM testbar (`just run`-Pfad)
|
||||
|
||||
### 2.5 Modell-Shortlist — Entscheidung 2026-09-03
|
||||
|
||||
**Startmodell: Qwen3-32B FP8.** Begründung: ~35 GB VRAM, damit reichlich Reserve
|
||||
für KV-Cache (~25 parallele Nutzer) und das Embedding-Modell daneben; gutes
|
||||
Deutsch; hoher Durchsatz. Alternative bei Qualitätsbedarf: gpt-oss-120b
|
||||
(MoE, ~60–65 GB) — weniger Parallelität, engerer Fit. Embeddings:
|
||||
bge-m3 / multilingual-e5 (mehrsprachig, zwingend für Deutsch).
|
||||
|
||||
Das ist der **Startwert als Rollen-Variable**, keine endgültige Festlegung: Die
|
||||
finale Wahl fällt gegen das Eval-Set (s. u.) und nach dem vLLM-Smoke-Test.
|
||||
|
||||
⚠️ **Offener technischer Punkt:** Die offiziellen vLLM-Docker-Images unterstützen
|
||||
SM120 (Blackwell Workstation/Server) laut aktueller Quellenlage **nicht
|
||||
zuverlässig out of the box**; auf RTX-PRO-6000-Systemen werden verbreitet
|
||||
Nightly- oder Community-Builds eingesetzt
|
||||
([vLLM-Forum](https://discuss.vllm.ai/t/support-for-rtx-6000-blackwell-96gb-card/1707),
|
||||
[rtx6kpro](https://github.com/local-inference-lab/rtx6kpro/blob/master/inference-engines/vllm.md)).
|
||||
Vor dem Bau der Rolle `llm_stack` daher **ein Container manuell auf dem Server
|
||||
testen** und das funktionierende Image pinnen — dieselbe Fehlerklasse wie beim
|
||||
Treiber (proprietäre vs. open Kernel-Module).
|
||||
|
||||
Kleines deutsches Eval-Set (20–30 Fragen mit erwarteten Antworten aus dem
|
||||
Bestand) mit dem Kunden sammeln — das ist später das Abnahmekriterium.
|
||||
|
||||
### 2.6 Korpus eingrenzen (neu, wichtigster offener Punkt)
|
||||
|
||||
Die Share-Analyse (§2.1) hat 1,26 Mio. potenziell extrahierbare Dokumente
|
||||
ergeben. „Alles indizieren" ist weder technisch sinnvoll noch inhaltlich
|
||||
wünschenswert. Vorschlag für das Kundengespräch:
|
||||
|
||||
1. **Ordner statt Alter als Filter.** Aus `D-toplevel-folders.csv` gemeinsam mit
|
||||
dem Kunden die Ordner markieren, die tatsächlich Wissensbasis sind
|
||||
(Doku, Normen, Datenblätter, Handbücher, Projektdoku) — Rest bleibt außen vor.
|
||||
Reiner Aktualitätsfilter wäre falsch: Datenblätter und Normen sind über Jahre
|
||||
gültig.
|
||||
2. **Ausschlusslisten** für Duplikate/Altversionen (33k Treffer), Archiv- und
|
||||
Backup-Ordner, nicht extrahierbare Formate (CAD, Bilder, Medien).
|
||||
3. **Zielgröße nennen.** Als Orientierung: eine gut funktionierende
|
||||
Wissensbasis liegt eher im Bereich **10.000–100.000 Dokumente**. Das ist
|
||||
kein hartes Limit, aber der Bereich, in dem Retrieval-Qualität und
|
||||
Indexierungsdauer beherrschbar bleiben.
|
||||
4. **Iterativ starten.** Phase 5 zuerst mit *einem* gut abgegrenzten Share/Ordner,
|
||||
Qualität am Eval-Set (§2.5) messen, dann erweitern. Das erspart einen
|
||||
tagelangen Fehlversuch über den Gesamtbestand.
|
||||
|
||||
**Erwartungsmanagement gegenüber dem Kunden:** Die ursprüngliche Anforderung
|
||||
lautete sinngemäß „unsere Dokumente auslesen". Realistisch ist „ein kuratierter
|
||||
Teil unserer Dokumente, dafür mit guten Antworten". Das früh sagen — nicht erst,
|
||||
wenn der Vollindex schlechte Treffer liefert.
|
||||
|
||||
## 3. Phasen ab Server-Lieferung
|
||||
|
||||
| Phase | Inhalt | Ergebnis/Abnahme |
|
||||
|---|---|---|
|
||||
| **1. Basis** (Woche 1) | Rack/Strom (600-W-GPU!), iLO, Firmware, RAID, Ubuntu 24.04 LTS, Eintrag in Ansible-Basis-Setup (Security, Pakete, Nutzer) | `just run phy_srv_gpu01` läuft grün |
|
||||
| **2. GPU-Stack** (Woche 1–2) | Rolle `nvidia_gpu`: Treiber, Container Toolkit, DCGM; **Burn-in unter Dauerlast** (SM120-Risiken, Deep Dive §1.6); MIG erst mal **aus** | `nvidia-smi` ok, 48 h-Lasttest ohne Reset |
|
||||
| **3. Inference** (Woche 2) | vLLM-Container (NVIDIA-Build) mit gewähltem Modell, feste VRAM-Quote, Embedding-Server daneben; Benchmark mit Eval-Set | deutsche Antworten ok, Ziel-Parallelität erreicht |
|
||||
| **4. UI + Auth** (Woche 2–3) | Open WebUI + pgvector, LDAP-Login, Reverse Proxy + TLS, vorkonfigurierter „Phytron-Assistent" | Login mit AD-Konto, Chat läuft |
|
||||
| **5. Wissensbasis** (Woche 3–5) | CIFS-Mounts (ro), Extraktion inkl. OCR-Engine (Tika/Docling), oikb-Sync erst auf **einen Teilbestand**, Qualität prüfen, dann Vollindex (Dauer aus §2.1 abschätzen) | Fragen aus dem Eval-Set werden mit korrekten Quellen beantwortet |
|
||||
| **6. Pilot → Rollout** (Woche 5–7) | 3–5 Pilotnutzer (gemischt), Feedback, dann alle 25; Onboarding-Einseiter (was kann es, was nicht — CAD/Scans!) | Abnahme durch Kunden |
|
||||
| **7. Betrieb** | Monitoring (GPU/DCGM, Dienste), Backup (Konfig + DBs — Modelle nicht), Update-Runbook `manuals/`, Doku im README | Runbooks vorhanden |
|
||||
| **später** | Jira & weitere Workloads; **erst dann MIG-Layout** festlegen (bis 4 Instanzen) statt jetzt raten | — |
|
||||
|
||||
## 4. Risiken
|
||||
|
||||
| Risiko | Umgang |
|
||||
|---|---|
|
||||
| ~~Storage zu klein für Index~~ | ✅ erledigt — 876 GB nutzbar, reicht (§2.3) |
|
||||
| **Korpus 12× über der Planungsschwelle (§2.1)** | **Eingrenzung auf kuratierten Teilbestand (§2.6); ohne Kundenentscheid keine Phase 5** |
|
||||
| ~166.000 Scan-PDFs → OCR-Aufwand & -Qualität | eigener Arbeitsblock, GPU-Konkurrenz zu vLLM einplanen; Erwartungen dämpfen |
|
||||
| ~~SM120/Treiber-Kinderkrankheiten~~ | ✅ erledigt — Treiber 595 **open** läuft (proprietäre Module scheitern an `RmInitAdapter`) |
|
||||
| Kein TLS zu Beginn (§2.2) | AD-Passwörter im Klartext im LAN; internes CA-Zertifikat nachrüsten, Reverse Proxy vorbereiten |
|
||||
| Software-Stand veraltet bis Lieferung | Versionen **erst bei Installation pinnen**; Wiedereinstiegs-Checkliste |
|
||||
| Erst-Indexierung dauert Tage | Teilbestand zuerst; Sync nachts; Dauer vorab abschätzen |
|
||||
| RAM-Konkurrenz Extraktion/OCR vs. vLLM | vLLM hat feste VRAM-Quote; Indexer-Jobs drosseln (nice/cgroups); 128 GB reichen für OWUI-Stack |
|
||||
| Datenschutz/Betriebsrat bei Chat-Logs | Früh klären (§2.2), Logging-Policy dokumentieren |
|
||||
| Kein HA — ein Server | Erwartung managen: Wartungsfenster = Dienst weg; Backup-Restore-Runbook |
|
||||
|
||||
## 5. Wiedereinstiegs-Checkliste (wenn der Server da ist)
|
||||
|
||||
1. [x] Dieses Dokument + Deep Dive lesen; offene Punkte aus §2.2 abhaken
|
||||
2. [x] Ergebnis der Share-Analyse vorliegen? → §2.1
|
||||
3. [ ] Neu sichten & **dann erst pinnen**: vLLM-Version (RTX-PRO-6000-/SM120-Support),
|
||||
Open WebUI + oikb Release Notes, Modell-Shortlist §2.5 — NVIDIA-Treiber ✅ (595-open)
|
||||
4. [x] Storage-Entscheidung §2.3 → unkritisch, nichts nachzubestellen
|
||||
5. [ ] Dann Phasenplan §3 von oben abarbeiten; jede Phase = Ansible-Commit + ggf.
|
||||
Runbook unter `manuals/`
|
||||
|
||||
## 6. Bewusst offen gelassen (kein Jetzt-Entscheid nötig)
|
||||
|
||||
- Exaktes Modell & Quantisierung, vLLM-Flags, MIG-Layout, OCR-Engine-Wahl,
|
||||
Monitoring-Stack-Details, Reverse-Proxy-Produkt — alles bei Umsetzung mit
|
||||
aktuellem Stand entscheiden; Rahmen steht oben.
|
||||
@@ -0,0 +1,127 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Lists all SMB shares of a Windows file server incl. local path, size and
|
||||
permissions — the basis for the include/exclude decision (projektplan §2.6).
|
||||
|
||||
.DESCRIPTION
|
||||
Run ON the file server (or against a remote one via -ComputerName). Reports
|
||||
per share: name, local path, description, share-level permissions, NTFS
|
||||
groups, plus optional size/file count. Admin shares (C$, ADMIN$, IPC$) are
|
||||
skipped unless -IncludeAdminShares is given.
|
||||
|
||||
Purely read-only. Requires local admin for Get-SmbShare (part of Windows
|
||||
Server, no extra module needed).
|
||||
|
||||
.EXAMPLE
|
||||
.\list-shares.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# with size per share — run this ON the file server, it then walks the local
|
||||
# paths instead of the UNC paths (far faster). Still minutes to hours on
|
||||
# millions of files; progress is printed every 50k files.
|
||||
.\list-shares.ps1 -WithSize
|
||||
|
||||
.NOTES
|
||||
Faster alternative when all shares live under one drive (as on Z-FILESERVER,
|
||||
where everything sits under D:): run share-analysis.ps1 once with a matching
|
||||
folder depth instead of measuring every share separately, e.g.
|
||||
|
||||
.\share-analysis.ps1 -Paths "D:" -FolderDepth 2
|
||||
|
||||
That produces size/count per D:\Abteilungen\<Share> in a single pass over
|
||||
the disk, which is what the share list here maps onto.
|
||||
#>
|
||||
param(
|
||||
[string]$ComputerName = $env:COMPUTERNAME,
|
||||
[switch]$IncludeAdminShares,
|
||||
[switch]$WithSize,
|
||||
[string]$OutDir = (Join-Path (Get-Location) "share-list")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
|
||||
|
||||
Write-Host "=== SMB shares on $ComputerName ===" -ForegroundColor Cyan
|
||||
|
||||
$shares = Get-SmbShare -CimSession $ComputerName -ErrorAction Stop
|
||||
if (-not $IncludeAdminShares) {
|
||||
$shares = $shares | Where-Object { -not $_.Name.EndsWith('$') }
|
||||
}
|
||||
|
||||
$result = foreach ($sh in $shares) {
|
||||
Write-Host (" {0,-25} {1}" -f $sh.Name, $sh.Path)
|
||||
|
||||
# share-level permissions
|
||||
$sharePerms = try {
|
||||
(Get-SmbShareAccess -Name $sh.Name -CimSession $ComputerName -ErrorAction Stop |
|
||||
ForEach-Object { "$($_.AccountName)=$($_.AccessRight)" }) -join '; '
|
||||
} catch { 'n/a' }
|
||||
|
||||
# NTFS permissions (top level only) — who actually has access
|
||||
$ntfsPerms = try {
|
||||
$uncPath = "\\$ComputerName\$($sh.Name)"
|
||||
((Get-Acl -LiteralPath $uncPath -ErrorAction Stop).Access |
|
||||
ForEach-Object { "$($_.IdentityReference)=$($_.FileSystemRights)" } |
|
||||
Select-Object -Unique | Select-Object -First 10) -join '; '
|
||||
} catch { 'n/a' }
|
||||
|
||||
$sizeGB = $null; $fileCount = $null
|
||||
if ($WithSize) {
|
||||
# Walk the LOCAL path when we are on the server itself — going through
|
||||
# the UNC path (\\server\share) routes every single file through the SMB
|
||||
# stack and is slower by orders of magnitude on large shares.
|
||||
$scanPath = if ($ComputerName -eq $env:COMPUTERNAME -and $sh.Path) {
|
||||
$sh.Path
|
||||
} else {
|
||||
"\\$ComputerName\$($sh.Name)"
|
||||
}
|
||||
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$n = 0L; $bytes = 0L
|
||||
Get-ChildItem -LiteralPath $scanPath -Recurse -File -Force -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {
|
||||
$n++; $bytes += $_.Length
|
||||
if ($n % 50000 -eq 0) {
|
||||
Write-Host (" ... {0:N0} files, {1:N1} GB ({2:N0}s)" -f $n, ($bytes / 1GB), $sw.Elapsed.TotalSeconds) -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
$sw.Stop()
|
||||
$sizeGB = [Math]::Round($bytes / 1GB, 2)
|
||||
$fileCount = $n
|
||||
Write-Host (" {0:N0} files, {1:N1} GB in {2:N0}s" -f $n, ($bytes / 1GB), $sw.Elapsed.TotalSeconds) -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
Name = $sh.Name
|
||||
Path = $sh.Path
|
||||
Description = $sh.Description
|
||||
SharePermissions = $sharePerms
|
||||
NtfsPermissions = $ntfsPerms
|
||||
SizeGB = $sizeGB
|
||||
FileCount = $fileCount
|
||||
}
|
||||
}
|
||||
|
||||
$csv = Join-Path $OutDir "smb-shares.csv"
|
||||
$result | Export-Csv $csv -NoTypeInformation -Encoding UTF8
|
||||
$result | Format-Table Name, Path, SizeGB, FileCount -AutoSize
|
||||
|
||||
# DFS namespaces (if used) — often the path users actually see
|
||||
Write-Host "`n=== DFS namespaces ===" -ForegroundColor Cyan
|
||||
try {
|
||||
$roots = Get-DfsnRoot -ErrorAction Stop
|
||||
if ($roots) {
|
||||
$dfs = foreach ($r in $roots) {
|
||||
Get-DfsnFolder -Path "$($r.Path)\*" -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$t = Get-DfsnFolderTarget -Path $_.Path -ErrorAction SilentlyContinue
|
||||
[PSCustomObject]@{ DfsPath = $_.Path; Targets = ($t.TargetPath -join '; ') }
|
||||
}
|
||||
}
|
||||
$dfs | Export-Csv (Join-Path $OutDir "dfs-namespaces.csv") -NoTypeInformation -Encoding UTF8
|
||||
$dfs | Format-Table -AutoSize
|
||||
} else { Write-Host " none" }
|
||||
} catch {
|
||||
Write-Host " no DFS role / not available on this host"
|
||||
}
|
||||
|
||||
Write-Host "`nResults in: $OutDir" -ForegroundColor Green
|
||||
@@ -0,0 +1,235 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Read-only analysis of SMB shares as LLM knowledge base (projektplan §2.1).
|
||||
|
||||
.DESCRIPTION
|
||||
Walks one or more share paths and reports, per share:
|
||||
- volume and file count per file type / category (extractable vs. not)
|
||||
- size and count per top-level folder (for the include/exclude decision)
|
||||
- modification-time distribution (sync frequency, data age)
|
||||
- scanned-PDF ratio via sampling (PDFs without a text layer -> OCR need)
|
||||
- duplicate/old-version candidates by name patterns
|
||||
- long paths (> 240 chars) and non-ASCII names (umlauts etc.)
|
||||
Writes CSVs plus a summary.txt into the output directory. Never writes to
|
||||
the shares themselves. PowerShell 5.1 compatible; no external modules.
|
||||
|
||||
.EXAMPLE
|
||||
.\share-analysis.ps1 -Paths '\\fileserver\projekte','\\fileserver\doku'
|
||||
|
||||
.EXAMPLE
|
||||
.\share-analysis.ps1 -Paths 'D:\shares\projekte' -OutDir C:\temp\analysis -PdfSampleSize 300
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$Paths,
|
||||
|
||||
[string]$OutDir = (Join-Path (Get-Location) ("share-analysis-" + (Get-Date -Format "yyyyMMdd-HHmmss"))),
|
||||
|
||||
# PDFs sampled per share for the text-layer check
|
||||
[int]$PdfSampleSize = 200,
|
||||
|
||||
# How many folder levels to aggregate in the *-toplevel-folders.csv.
|
||||
# 1 = D:\Abteilungen, 2 = D:\Abteilungen\AA (matches the share layout here).
|
||||
[int]$FolderDepth = 1
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
|
||||
|
||||
# --- classification ---------------------------------------------------------
|
||||
|
||||
$categories = @{
|
||||
'office' = @('.pdf', '.doc', '.docx', '.xls', '.xlsx', '.xlsm', '.ppt', '.pptx', '.txt', '.md', '.rtf', '.odt', '.ods', '.odp', '.csv', '.vsd', '.vsdx')
|
||||
'email' = @('.msg', '.eml')
|
||||
'image' = @('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tif', '.tiff', '.svg', '.heic', '.webp')
|
||||
'cad' = @('.dwg', '.dxf', '.step', '.stp', '.iges', '.igs', '.sldprt', '.sldasm', '.slddrw', '.ipt', '.iam', '.catpart', '.catproduct', '.3ds', '.stl')
|
||||
'archive' = @('.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.iso')
|
||||
'media' = @('.mp4', '.avi', '.mov', '.wmv', '.mp3', '.wav')
|
||||
}
|
||||
$extToCategory = @{}
|
||||
foreach ($cat in $categories.Keys) {
|
||||
foreach ($ext in $categories[$cat]) { $extToCategory[$ext] = $cat }
|
||||
}
|
||||
|
||||
# name patterns that suggest duplicates / old versions
|
||||
$dupePattern = '(?i)(kopie|copy|backup|_old|_alt\b|\.bak$|~\$|\(\d+\)\s*(\.[^.]+)?$)'
|
||||
|
||||
$summaryLines = New-Object System.Collections.Generic.List[string]
|
||||
$summaryLines.Add("Share analysis $(Get-Date -Format 'yyyy-MM-dd HH:mm') — read-only")
|
||||
$summaryLines.Add("Paths: $($Paths -join ', ')")
|
||||
$summaryLines.Add("")
|
||||
|
||||
# --- helpers -----------------------------------------------------------------
|
||||
|
||||
function Test-PdfTextLayer {
|
||||
# Heuristic: a PDF without any /Font reference in its first 4 MB most
|
||||
# likely has no text layer (scanned). Also detects encrypted PDFs.
|
||||
param([string]$Path)
|
||||
try {
|
||||
$fs = [System.IO.File]::Open($Path, 'Open', 'Read', 'ReadWrite')
|
||||
try {
|
||||
$len = [int][Math]::Min($fs.Length, 4MB)
|
||||
$buf = New-Object byte[] $len
|
||||
[void]$fs.Read($buf, 0, $len)
|
||||
} finally { $fs.Close() }
|
||||
$text = [System.Text.Encoding]::ASCII.GetString($buf)
|
||||
if ($text -match '/Encrypt') { return 'encrypted' }
|
||||
if ($text -match '/Font') { return 'text' }
|
||||
return 'no-text-layer'
|
||||
} catch {
|
||||
return 'unreadable'
|
||||
}
|
||||
}
|
||||
|
||||
# --- per-share pass ----------------------------------------------------------
|
||||
|
||||
foreach ($root in $Paths) {
|
||||
# Label used for the output filenames. Must not contain characters that are
|
||||
# illegal in a filename — e.g. -Paths "D:" would otherwise produce
|
||||
# "D:-file-types.csv" and Export-Csv fails with "path format not supported".
|
||||
$shareName = ($root.TrimEnd('\') -split '[\\/]')[-1]
|
||||
if (-not $shareName) { $shareName = 'root' }
|
||||
foreach ($c in [System.IO.Path]::GetInvalidFileNameChars()) {
|
||||
$shareName = $shareName.Replace($c, '_')
|
||||
}
|
||||
$shareName = $shareName.TrimEnd('_', '.', ' ')
|
||||
if (-not $shareName) { $shareName = 'root' }
|
||||
|
||||
Write-Host "=== Analyzing '$root' ..." -ForegroundColor Cyan
|
||||
|
||||
if (-not (Test-Path -LiteralPath $root)) {
|
||||
Write-Warning "Path not found or no access: $root"
|
||||
$summaryLines.Add("[$shareName] SKIPPED — path not found or no access: $root")
|
||||
continue
|
||||
}
|
||||
|
||||
# aggregates (streaming — file objects are not kept in memory)
|
||||
$extStats = @{} # ext -> @{Count; Bytes}
|
||||
$topStats = @{} # top-level folder -> @{Count; Bytes}
|
||||
$yearStats = @{} # mtime year -> count
|
||||
$totalCount = 0L
|
||||
$totalBytes = 0L
|
||||
$dupeCount = 0L
|
||||
$longPaths = 0L
|
||||
$nonAscii = 0L
|
||||
$now = Get-Date
|
||||
$recency = @{ 'last 30 days' = 0L; 'last 90 days' = 0L; 'last 365 days' = 0L; 'older' = 0L }
|
||||
|
||||
# reservoir sample of PDF paths for the text-layer check
|
||||
$pdfSample = New-Object System.Collections.Generic.List[string]
|
||||
$pdfSeen = 0L
|
||||
$rand = New-Object System.Random
|
||||
$rootLen = $root.TrimEnd('\').Length
|
||||
|
||||
Get-ChildItem -LiteralPath $root -Recurse -File -Force -ErrorAction SilentlyContinue -ErrorVariable +enumErrors |
|
||||
ForEach-Object {
|
||||
$totalCount++
|
||||
$totalBytes += $_.Length
|
||||
|
||||
$ext = $_.Extension.ToLowerInvariant()
|
||||
if (-not $ext) { $ext = '(none)' }
|
||||
if (-not $extStats.ContainsKey($ext)) { $extStats[$ext] = @{ Count = 0L; Bytes = 0L } }
|
||||
$extStats[$ext].Count++
|
||||
$extStats[$ext].Bytes += $_.Length
|
||||
|
||||
# folder relative to the share root, aggregated at -FolderDepth levels
|
||||
$rel = $_.FullName.Substring($rootLen).TrimStart('\')
|
||||
$parts = $rel.Split('\')
|
||||
if ($parts.Count -le 1) {
|
||||
$top = '(root)'
|
||||
} else {
|
||||
$n = [Math]::Min($FolderDepth, $parts.Count - 1)
|
||||
$top = ($parts[0..($n - 1)] -join '\')
|
||||
}
|
||||
if (-not $topStats.ContainsKey($top)) { $topStats[$top] = @{ Count = 0L; Bytes = 0L } }
|
||||
$topStats[$top].Count++
|
||||
$topStats[$top].Bytes += $_.Length
|
||||
|
||||
$year = $_.LastWriteTime.Year
|
||||
if (-not $yearStats.ContainsKey($year)) { $yearStats[$year] = 0L }
|
||||
$yearStats[$year]++
|
||||
|
||||
$age = ($now - $_.LastWriteTime).TotalDays
|
||||
if ($age -le 30) { $recency['last 30 days']++ }
|
||||
elseif ($age -le 90) { $recency['last 90 days']++ }
|
||||
elseif ($age -le 365) { $recency['last 365 days']++ }
|
||||
else { $recency['older']++ }
|
||||
|
||||
if ($_.Name -match $dupePattern) { $dupeCount++ }
|
||||
if ($_.FullName.Length -gt 240) { $longPaths++ }
|
||||
if ($_.Name -match '[^\x00-\x7F]') { $nonAscii++ }
|
||||
|
||||
if ($ext -eq '.pdf') {
|
||||
$pdfSeen++
|
||||
if ($pdfSample.Count -lt $PdfSampleSize) {
|
||||
$pdfSample.Add($_.FullName)
|
||||
} else {
|
||||
$i = $rand.Next(0, [int][Math]::Min($pdfSeen, [int]::MaxValue))
|
||||
if ($i -lt $PdfSampleSize) { $pdfSample[$i] = $_.FullName }
|
||||
}
|
||||
}
|
||||
|
||||
if ($totalCount % 20000 -eq 0) {
|
||||
Write-Host (" {0:N0} files, {1:N1} GB ..." -f $totalCount, ($totalBytes / 1GB))
|
||||
}
|
||||
}
|
||||
|
||||
# PDF text-layer sampling
|
||||
Write-Host " Sampling $($pdfSample.Count) of $pdfSeen PDFs for text layer ..."
|
||||
$pdfResults = @{ 'text' = 0; 'no-text-layer' = 0; 'encrypted' = 0; 'unreadable' = 0 }
|
||||
foreach ($p in $pdfSample) { $pdfResults[(Test-PdfTextLayer $p)]++ }
|
||||
|
||||
# --- write per-share CSVs ---
|
||||
$prefix = Join-Path $OutDir $shareName
|
||||
|
||||
$extStats.GetEnumerator() | ForEach-Object {
|
||||
$cat = if ($extToCategory.ContainsKey($_.Key)) { $extToCategory[$_.Key] } else { 'other' }
|
||||
[PSCustomObject]@{ Extension = $_.Key; Category = $cat; Count = $_.Value.Count; GB = [Math]::Round($_.Value.Bytes / 1GB, 2) }
|
||||
} | Sort-Object GB -Descending | Export-Csv "$prefix-file-types.csv" -NoTypeInformation -Encoding UTF8
|
||||
|
||||
$topStats.GetEnumerator() | ForEach-Object {
|
||||
[PSCustomObject]@{ Folder = $_.Key; Count = $_.Value.Count; GB = [Math]::Round($_.Value.Bytes / 1GB, 2) }
|
||||
} | Sort-Object GB -Descending | Export-Csv "$prefix-toplevel-folders.csv" -NoTypeInformation -Encoding UTF8
|
||||
|
||||
$yearStats.GetEnumerator() | ForEach-Object {
|
||||
[PSCustomObject]@{ Year = $_.Key; Count = $_.Value }
|
||||
} | Sort-Object Year | Export-Csv "$prefix-mtime-years.csv" -NoTypeInformation -Encoding UTF8
|
||||
|
||||
# --- category rollup for the summary ---
|
||||
$catBytes = @{}; $catCount = @{}
|
||||
foreach ($e in $extStats.GetEnumerator()) {
|
||||
$cat = if ($extToCategory.ContainsKey($e.Key)) { $extToCategory[$e.Key] } else { 'other' }
|
||||
if (-not $catBytes.ContainsKey($cat)) { $catBytes[$cat] = 0L; $catCount[$cat] = 0L }
|
||||
$catBytes[$cat] += $e.Value.Bytes
|
||||
$catCount[$cat] += $e.Value.Count
|
||||
}
|
||||
|
||||
$summaryLines.Add("[$shareName] $root")
|
||||
$summaryLines.Add((" Total: {0:N0} files, {1:N1} GB" -f $totalCount, ($totalBytes / 1GB)))
|
||||
foreach ($c in ($catBytes.Keys | Sort-Object { $catBytes[$_] } -Descending)) {
|
||||
$summaryLines.Add((" {0,-8} {1,10:N0} files {2,10:N1} GB" -f $c, $catCount[$c], ($catBytes[$c] / 1GB)))
|
||||
}
|
||||
if ($pdfSample.Count -gt 0) {
|
||||
$scannedPct = [Math]::Round(100 * $pdfResults['no-text-layer'] / $pdfSample.Count, 1)
|
||||
$summaryLines.Add((" PDFs: {0:N0} total; sample of {1}: {2} with text, {3} WITHOUT text layer (~{4}% -> OCR), {5} encrypted, {6} unreadable" -f `
|
||||
$pdfSeen, $pdfSample.Count, $pdfResults['text'], $pdfResults['no-text-layer'], $scannedPct, $pdfResults['encrypted'], $pdfResults['unreadable']))
|
||||
}
|
||||
$summaryLines.Add((" Duplicate/old-version name patterns: {0:N0} files" -f $dupeCount))
|
||||
$summaryLines.Add((" Paths > 240 chars: {0:N0}; non-ASCII names: {1:N0}" -f $longPaths, $nonAscii))
|
||||
$summaryLines.Add(" Modified: " + (($recency.GetEnumerator() | Sort-Object { @('last 30 days','last 90 days','last 365 days','older').IndexOf($_.Key) } |
|
||||
ForEach-Object { "$($_.Key): $("{0:N0}" -f $_.Value)" }) -join ' | '))
|
||||
$summaryLines.Add("")
|
||||
}
|
||||
|
||||
if ($enumErrors) {
|
||||
$summaryLines.Add(("NOTE: {0:N0} paths could not be read (access denied / path too long). Counts are lower bounds." -f $enumErrors.Count))
|
||||
$enumErrors | ForEach-Object { $_.TargetObject } | Select-Object -First 50 |
|
||||
Set-Content (Join-Path $OutDir "enumeration-errors-sample.txt") -Encoding UTF8
|
||||
}
|
||||
|
||||
$summaryPath = Join-Path $OutDir "summary.txt"
|
||||
$summaryLines | Set-Content $summaryPath -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done. Results in: $OutDir" -ForegroundColor Green
|
||||
Get-Content $summaryPath | Write-Host
|
||||
Reference in New Issue
Block a user