Trial GPU-resident Qwen3.5-9B with verified 4B rollback
This commit is contained in:
1 parent
0929cdb555
commit
dec534cc88
10 files changed
+307
-93
No files matched your search
@@ -14,13 +14,14 @@ Reproduzierbares Benutzer-Deployment für Hermes Agent auf Arch Linux mit:
|
||||
- optionalem CUDA-Build von llama-server, llama-cli und llama-bench
|
||||
- Qwen-/RTX-3080-Profilen mit prüfbarem Wechsel und Rückweg
|
||||
|
||||
Stand: 2026-10-23. Die Anleitung für das **aktuelle Setup auf einem neuen Rechner**
|
||||
Stand: 2026-10-27. Die Anleitung für das **aktuelle Setup auf einem neuen Rechner**
|
||||
steht in [NEW_WORKSTATION.md](docs/NEW_WORKSTATION.md): feste Modellrevisionen und
|
||||
Prüfsummen, Vorbereitung der Profile und Hermes-Limits, Aktivierung und Rückweg.
|
||||
Für deine **bestehende Workstation** zuerst
|
||||
[`docs/WORKSTATION.md`](docs/WORKSTATION.md) lesen. Der Schnellstart unten ist für
|
||||
eine neue Maschine. Das Routineprofil nutzt Qwen3.5-4B; der kurze lokale Texttest
|
||||
vom 23.10.2026 erreichte rund 130–136 Tokens/s. Das 27B-Modell bleibt optional.
|
||||
eine neue Maschine. Das Routineprofil erprobt Qwen3.5-9B IQ4_XS mit 64K Kontext. Das bisherige
|
||||
4B-Modell mit 64K bleibt als `fallback` verfügbar. Details und Rückweg:
|
||||
[QWEN9B_TRIAL.md](docs/QWEN9B_TRIAL.md). Das 27B-Modell bleibt optional.
|
||||
Frühere VRAM-Messungen stehen in [RTX3080.md](docs/RTX3080.md). Der Installer richtet keinen
|
||||
Gitea-Server ein und pusht keine Änderungen.
|
||||
|
||||
@@ -134,13 +135,14 @@ Start des llama-Dienstes geladen, nicht bei der Konfigurationsprüfung.
|
||||
Die aktuellen Schutzmaßnahmen gegen endlose Modellantworten und die explizite
|
||||
Umschaltung zwischen Routine- und großem Modell stehen in
|
||||
[INFERENCE_LIMITS.md](docs/INFERENCE_LIMITS.md).
|
||||
Hermes wird mit `model.context_length: 65536` auf das tatsächliche 64K-Fenster
|
||||
beider Hauptmodellprofile eingestellt; die CPU-Kompression beginnt spätestens
|
||||
bei 24.000 Tokens.
|
||||
Hermes verwendet zum aktiven Modell passend 64K Kontext für `routine` (9B),
|
||||
`fallback` (4B) und `large` (27B). Der Modellschalter passt auch die
|
||||
Kompressionsschwelle an: 24.000 Tokens.
|
||||
|
||||
```bash
|
||||
hermes-stack-model status
|
||||
hermes-stack-model routine
|
||||
hermes-stack-model fallback # bisheriges 4B-/64K-Setup
|
||||
hermes-stack-model large
|
||||
```
|
||||
|
||||
|
||||
+25
-8
@@ -2,19 +2,19 @@
|
||||
"llama_cpp_commit": "d81235049384534c167caea52b85a694f6103d14",
|
||||
"models": {
|
||||
"routine": {
|
||||
"repo": "unsloth/Qwen3.5-4B-GGUF",
|
||||
"revision": "e87f176479d0855a907a41277aca2f8ee7a09523",
|
||||
"directory": "qwen35-4b/e87f176479d0855a907a41277aca2f8ee7a09523",
|
||||
"repo": "unsloth/Qwen3.5-9B-GGUF",
|
||||
"revision": "3885219b6810b007914f3a7950a8d1b469d598a5",
|
||||
"directory": "qwen35-9b/3885219b6810b007914f3a7950a8d1b469d598a5",
|
||||
"files": [
|
||||
{
|
||||
"name": "Qwen3.5-4B-Q4_K_M.gguf",
|
||||
"sha256": "00fe7986ff5f6b463e62455821146049db6f9313603938a70800d1fb69ef11a4",
|
||||
"size": 2740937888
|
||||
"name": "Qwen3.5-9B-IQ4_XS.gguf",
|
||||
"size": 5168653536,
|
||||
"sha256": "7e918aeca06c52bcb528ea6b04b4ec957e75ee8c0a73138854c0dfcf371ea429"
|
||||
},
|
||||
{
|
||||
"name": "mmproj-F16.gguf",
|
||||
"sha256": "cd88edcf8d031894960bb0c9c5b9b7e1fea6ebee02b9f7ce925a00d12891f864",
|
||||
"size": 672423616
|
||||
"sha256": "f70dc3509053962b0d0d3ee8a7eacebf5d60aa560cad78254ae8698516ae029f",
|
||||
"size": 918166080
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -46,6 +46,23 @@
|
||||
"size": 1282439264
|
||||
}
|
||||
]
|
||||
},
|
||||
"fallback": {
|
||||
"repo": "unsloth/Qwen3.5-4B-GGUF",
|
||||
"revision": "e87f176479d0855a907a41277aca2f8ee7a09523",
|
||||
"directory": "qwen35-4b/e87f176479d0855a907a41277aca2f8ee7a09523",
|
||||
"files": [
|
||||
{
|
||||
"name": "Qwen3.5-4B-Q4_K_M.gguf",
|
||||
"sha256": "00fe7986ff5f6b463e62455821146049db6f9313603938a70800d1fb69ef11a4",
|
||||
"size": 2740937888
|
||||
},
|
||||
{
|
||||
"name": "mmproj-F16.gguf",
|
||||
"sha256": "cd88edcf8d031894960bb0c9c5b9b7e1fea6ebee02b9f7ce925a00d12891f864",
|
||||
"size": 672423616
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-29
@@ -1,12 +1,12 @@
|
||||
# Lokale Inferenz begrenzen
|
||||
|
||||
Die Workstation verwendet `hermes-local` als stabilen API-Modellnamen. Das
|
||||
Routineprofil lädt Qwen3.5-4B Q4_K_M (Unsloth-Revision
|
||||
`e87f176479d0855a907a41277aca2f8ee7a09523`), das große Profil weiterhin das
|
||||
Routineprofil lädt Qwen3.5-9B IQ4_XS (Unsloth-Revision
|
||||
`3885219b6810b007914f3a7950a8d1b469d598a5`), das große Profil weiterhin das
|
||||
vorhandene Qwen3.8-27B. Die tatsächliche Auswahl steht in
|
||||
`~/.config/hermes-stack-maintenance/llama.env`.
|
||||
|
||||
`hermes-stack-model routine` bzw. `hermes-stack-model large` schaltet explizit um
|
||||
`hermes-stack-model routine`, `fallback` bzw. `large` schaltet explizit um
|
||||
und beendet dabei laufende Gateway-Anfragen. Beide Dateien bleiben erhalten.
|
||||
Es wird nur ein Hauptmodell gleichzeitig geladen. Die lokalen profilspezifischen
|
||||
Konfigurationen `llama-routine.env` und `llama-large.env` enthalten absolute
|
||||
@@ -22,21 +22,16 @@ ebenfalls das aktiv ausgewählte Modell; die tatsächliche Modelldatei zeigt
|
||||
|
||||
## Kontextgröße und Kompression
|
||||
|
||||
Beide Hauptmodellprofile starten mit `--ctx-size 65536`. Hermes muss dieselbe
|
||||
Grenze kennen: `model.context_length: 65536` in `~/.hermes/config.yaml`.
|
||||
Der lokale Alias `hermes-local` wurde bei der Live-Prüfung vom 23.10.2026 nicht
|
||||
zuverlässig erkannt; Hermes meldete einen Fallback auf 256.000 Tokens.
|
||||
Die explizite Grenze verhindert eine zu große Annahme bei Kontextverwaltung und
|
||||
Platzplanung. Sie vergrößert weder das Modellfenster noch die GPU-Belegung.
|
||||
Das Routineprofil verwendet `--ctx-size 65536`, das bisherige 4B-Profil
|
||||
(`fallback`) und das große Profil jeweils 65536. Der Modellschalter setzt
|
||||
`model.context_length` und `compression.threshold_tokens` zusammen mit dem
|
||||
Modell: 65536/24000 für alle drei Profile. Er sichert
|
||||
beide Konfigurationen und stellt bei einem Wechsel-Fehler beide wieder her.
|
||||
Legacy-Profile ohne `LLAMA_CONTEXT_TOKENS` werden als 64K behandelt; eigene
|
||||
Profile müssen diese Metadaten passend zu `--ctx-size` setzen.
|
||||
|
||||
`scripts/prepare-workstation.py --configure-hermes` setzt die Grenze bei der
|
||||
Einrichtung für Routine- und großes Profil. Bei einem späteren eigenen Profil
|
||||
mit anderer `--ctx-size` muss `model.context_length` entsprechend mitgeändert
|
||||
werden.
|
||||
|
||||
Die bestehende CPU-Kompression bleibt bei `threshold: 0.5` und
|
||||
`threshold_tokens: 24000`: Die absolute Obergrenze zieht die Kompression vor
|
||||
die rechnerische 50-Prozent-Schwelle von 32.768 Tokens. `target_ratio: 0.2`,
|
||||
Die CPU-Kompression bleibt bei `threshold: 0.5` und der absoluten Schwelle
|
||||
von 24.000 Tokens. `target_ratio: 0.2`,
|
||||
Schutz der ersten drei und letzten zehn Nachrichten sowie die bestehenden
|
||||
Zeitbudgets bleiben erhalten. Die Schwelle ist ein Auslöser, keine harte
|
||||
Obergrenze für einzelne Nachrichten oder Werkzeugergebnisse.
|
||||
@@ -44,18 +39,13 @@ Obergrenze für einzelne Nachrichten oder Werkzeugergebnisse.
|
||||
Für eine bestehende Installation genügt nach Abschluss laufender Aufträge:
|
||||
|
||||
```bash
|
||||
cp -p ~/.hermes/config.yaml ~/.hermes/config.yaml.before-context-64k
|
||||
hermes config set model.context_length 65536
|
||||
hermes config get model.context_length
|
||||
systemctl --user restart hermes-gateway.service
|
||||
hermes-stack-model routine # 9B, 64K
|
||||
hermes-stack-model fallback # 4B, 64K inklusive Kompressionsschwelle
|
||||
```
|
||||
|
||||
Den Sicherungsnamen bei wiederholter Ausführung ändern, damit die frühere
|
||||
Sicherung erhalten bleibt. Der Hauptmodell- und CPU-Kompressionsdienst brauchen
|
||||
für diese Hermes-Einstellung keinen Neustart. Neue Sitzungen verwenden die neue
|
||||
Grenze; bereits im Agentzustand gehaltene Kontextwerte sind kein verlässlicher
|
||||
Nachweis. Zur Abnahme eine neue Sitzung öffnen und die Modellantwort sowie das
|
||||
Gateway-Protokoll auf erneute Kontext-Fallback-Warnungen prüfen.
|
||||
Der Modellschalter startet Hauptmodell und Gateway kontrolliert neu. Bereits
|
||||
laufende Anfragen werden beendet; CPU-Kompression läuft unverändert weiter.
|
||||
Die aktuellen Messungen und der Rückweg stehen in [QWEN9B_TRIAL.md](QWEN9B_TRIAL.md).
|
||||
|
||||
## Drei Schutzebenen
|
||||
|
||||
@@ -99,5 +89,5 @@ sowie Gateway neu starten. Cronjob-Modellbindungen sind über `hermes cron edit
|
||||
JOB_ID --model MODELL --provider custom` wiederherstellbar.
|
||||
|
||||
Quellen: [llama.cpp Serverparameter](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md),
|
||||
[Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B),
|
||||
[verwendete GGUF-Dateien](https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/tree/e87f176479d0855a907a41277aca2f8ee7a09523).
|
||||
[Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B),
|
||||
[verwendete GGUF-Dateien](https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/tree/3885219b6810b007914f3a7950a8d1b469d598a5).
|
||||
+18
-14
@@ -1,8 +1,9 @@
|
||||
# Aktuelles Workstation-Setup neu installieren
|
||||
|
||||
Stand: 23.10.2026. Diese Anleitung stellt den aktuellen lokalen Modellbetrieb
|
||||
wieder her: Qwen3.5-4B für den Alltag, Qwen3.8-27B als explizite Alternative,
|
||||
Qwen3-1.7B für Kompression auf der CPU, 64K Kontext sowie Antwort- und Zeitlimits.
|
||||
Stand: 27.10.2026. Diese Anleitung stellt den aktuellen lokalen Modellbetrieb
|
||||
wieder her: Qwen3.5-9B mit 64K für den Alltag als Erprobung, Qwen3.5-4B mit 64K als
|
||||
Rückfallprofil, Qwen3.8-27B als explizite Alternative und Qwen3-1.7B für
|
||||
Kompression auf der CPU sowie Antwort- und Zeitlimits.
|
||||
Die Modelle werden nacheinander betrieben; der Kompressionsserver läuft separat.
|
||||
|
||||
## 1. Voraussetzungen und Deployment
|
||||
@@ -75,7 +76,7 @@ Während dieser Konfigurationsänderung muss der Gateway gestoppt sein:
|
||||
```bash
|
||||
systemctl --user stop hermes-gateway.service
|
||||
python3 scripts/prepare-workstation.py \
|
||||
--download --models routine,large,compression \
|
||||
--download --models routine,large,compression,fallback \
|
||||
--select routine --configure-hermes --unpin-local-cron
|
||||
```
|
||||
|
||||
@@ -83,7 +84,7 @@ Bei noch nicht installierter Gateway-Unit ist deren Stop-Fehler unkritisch.
|
||||
Die Vorbereitung startet selbst keine Dienste. Sie prüft vor Änderungen, dass
|
||||
kein Gateway läuft, und teilt das Maintenance-Lock mit Updater und Watchdog.
|
||||
|
||||
Alle fünf Modelldateien zusammen benötigen rund **22,6 GiB**. Wer zunächst nur
|
||||
Alle sieben Modelldateien zusammen benötigen rund **28,3 GiB**. Wer zunächst nur
|
||||
das Alltagsmodell möchte, verwendet `--models routine`. Für Routine plus CPU-
|
||||
Kompression `--models routine,compression`. Ohne `--download` müssen die Dateien
|
||||
bereits im erwarteten Modellverzeichnis liegen; das ermöglicht eine Offline-
|
||||
@@ -96,7 +97,7 @@ Prüfung umbenannt. Beschädigte vorhandene Dateien werden gemeldet und behalten
|
||||
Fehlgeschlagene Downloads können erneut gestartet werden; bei einem falschen
|
||||
Partial-Download muss die betroffene `.part` vor erneutem Download entfernt werden.
|
||||
|
||||
Der Helfer erzeugt fehlende Dateien `llama-routine.env`, `llama-large.env` und
|
||||
Der Helfer erzeugt fehlende Dateien `llama-routine.env`, `llama-fallback.env`, `llama-large.env` und
|
||||
`compression.env` für den jeweiligen Benutzernamen. Bereits vorhandene Profile
|
||||
und deren Tuning bleiben erhalten. `--select routine` übernimmt das vorbereitete
|
||||
Routineprofil ausdrücklich als aktive `llama.env` und sichert deren alten Stand.
|
||||
@@ -106,9 +107,9 @@ verzeichnis verschieben. Das gilt auch für `LLAMA_SERVER_BIN`.
|
||||
|
||||
`--configure-hermes` aktualisiert mit Backup die lokalen Provider-, Vision- und
|
||||
optional Kompressionseinstellungen, das 30-Minuten-Auftragsbudget und die
|
||||
600-Sekunden-/2048-Token-Limits. Dabei setzt er `model.context_length: 65536`
|
||||
passend zu beiden Hauptmodellprofilen. Bei CPU-Kompression bleibt die
|
||||
Kompressionsschwelle auf 24.000 Tokens begrenzt. Fremde Provider, Terminal, Plugins und andere
|
||||
600-Sekunden-/2048-Token-Limits. Dabei setzt er `model.context_length`
|
||||
passend zur Auswahl: 65536 für alle drei Profile. Die
|
||||
Kompressionsschwelle beträgt 24.000 Tokens. Fremde Provider, Terminal, Plugins und andere
|
||||
Einstellungen bleiben bestehen. `--unpin-local-cron` löst nur bekannte lokale
|
||||
Modellbindungen über die Hermes-CLI; eigene Cloud-Provider bleiben gebunden.
|
||||
Die Option legt keine Cronjobs an. Vorhandene Cronjobs kommen aus dem privaten
|
||||
@@ -146,8 +147,9 @@ systemctl --user status hermes-stack-llama-watchdog.timer
|
||||
```
|
||||
|
||||
Danach eine deutsche Textantwort, ein Bild und einen Werkzeugaufruf in Hermes
|
||||
prüfen. Der bisherige Live-Test erreichte bei kurzen Textanfragen etwa
|
||||
130–136 Tokens/s; das ist kein Benchmark für lange Kontexte oder Bildaufgaben.
|
||||
prüfen. Messungen zur aktuellen 9B-Erprobung stehen in
|
||||
[QWEN9B_TRIAL.md](QWEN9B_TRIAL.md). Der historische 4B-Test erreichte bei
|
||||
kurzen Textanfragen etwa 130–136 Tokens/s; das ist kein Benchmark für lange Kontexte oder Bildaufgaben.
|
||||
Eine erzwungene lange Antwort endete bei genau 2048 Tokens, und ein reiner
|
||||
Tool-Call wurde korrekt als JSON erzeugt. Ein zehnminütiger echter Hänger wurde
|
||||
nicht absichtlich erzeugt; dessen Watchdog-Recovery ist automatisiert simuliert.
|
||||
@@ -156,15 +158,17 @@ nicht absichtlich erzeugt; dessen Watchdog-Recovery ist automatisiert simuliert.
|
||||
|
||||
```bash
|
||||
hermes-stack-model # aktives Modell anzeigen
|
||||
hermes-stack-model routine # schnelles Alltagsmodell
|
||||
hermes-stack-model routine # 9B/64K-Erprobung
|
||||
hermes-stack-model fallback # bisheriges 4B/64K-Profil
|
||||
hermes-stack-model large # größeres Modell für bewusste Einzelnutzung
|
||||
```
|
||||
|
||||
Ein Wechsel stoppt laufende Gateway-Anfragen, prüft die Kandidatenkonfiguration,
|
||||
sichert `llama.env`, wartet nach dem Modellstart auf `/health` und startet den
|
||||
sichert `llama.env` und die Hermes-Konfiguration, setzt Kontext und
|
||||
Kompressionsschwelle passend, wartet nach dem Modellstart auf `/health` und startet den
|
||||
Gateway wieder. Bei Fehlern wird die vorherige Konfiguration zurückkopiert und
|
||||
die Wiederaufnahme der Dienste versucht; Journal und Dienststatus kontrollieren.
|
||||
Beide Modelle verwenden den stabilen API-Alias `hermes-local` und dieselben Limits.
|
||||
Alle Hauptmodelle verwenden den stabilen API-Alias `hermes-local` und dieselben Limits.
|
||||
|
||||
Automatischer Wechsel zwischen den Modellen findet nicht statt. Das große Modell
|
||||
bleibt auf der 10-GB-Karte langsam. Für Betrieb ohne Anmeldung optional
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Qwen3.5-9B auf der RTX 3080 erproben
|
||||
|
||||
Stand: 27.10.2026. Ziel ist bessere Alltagsqualität bei vollständigem GPU-Offload
|
||||
des Sprachmodells und weiter nutzbarem Hyprland-/Browser-Desktop. Die Freigabe
|
||||
ist eine Erprobung, keine Langzeit-Stabilitätszusage oder Qualitätsbewertung.
|
||||
|
||||
## Profile und Schutzgrenzen
|
||||
|
||||
- `routine`: Qwen3.5-9B IQ4_XS, 65536 Kontexttokens, Q4_0-K/V-Cache,
|
||||
vollständiger GPU-Offload (`--n-gpu-layers 99 --fit off`), Batch 512,
|
||||
Microbatch 128. Der Bildprojektor läuft mit `--no-mmproj-offload` auf der CPU.
|
||||
- `fallback`: das bisherige Qwen3.5-4B Q4_K_M mit 65536 Kontexttokens und
|
||||
Q8_0-K/V-Cache. Auf Archwork wurde die aktive Ausgangskonfiguration unverändert
|
||||
als `llama-fallback.env` übernommen. Eine Neuinstallation erzeugt sie aus dem Manifest.
|
||||
- `large`: bisheriges Qwen3.8-27B-Profil für bewusste Einzelnutzung.
|
||||
|
||||
Die Gewichte und der Bildprojektor sind in `config/models.lock.json` mit fester
|
||||
Hugging-Face-Revision, Dateigröße und SHA-256 fixiert. Der Projektor bleibt erhalten;
|
||||
die Bildverarbeitung benötigt CPU-Zeit. IQ4_XS und der Q4_0-Kontextcache sparen
|
||||
Speicher gegenüber Q4_K_M/Q8_0 und können die Antwortqualität beeinflussen.
|
||||
|
||||
Hermes und Server bleiben bei 65536 Kontexttokens; die CPU-Kompression beginnt
|
||||
spätestens bei 24000 Tokens. Antwortlimit 2048 Tokens, Request-Watchdog und
|
||||
bestehende Zeitbudgets bleiben bestehen. Die jetzige Hermes-Version verlangt
|
||||
mindestens 64000 Kontexttokens: Eine 16K-Konfiguration wird beim Agentstart
|
||||
abgelehnt, auch wenn der Modellserver selbst antwortet. Das wurde lokal geprüft.
|
||||
Der Modellschalter lehnt bei vorhandener Hermes-Konfiguration Metadaten unter
|
||||
dieser Grenze ab und stellt bei Fehlern die vorherige Modell- und Hermes-Konfiguration
|
||||
wieder her.
|
||||
|
||||
## Messungen und verworfene Konfigurationen
|
||||
|
||||
Alle Speicherangaben sind MiB laut `nvidia-smi`; die freie Menge hängt von den
|
||||
gleichzeitig laufenden Desktop-Anwendungen ab und ist nicht reserviert.
|
||||
|
||||
| Konfiguration | Beobachtung |
|
||||
|---|---|
|
||||
| 4B Q4_K_M, 64K, GPU-Projektor | Vergleichstest: mindestens 2288 MiB frei; kurze Textgenerierung ca. 136 Tokens/s |
|
||||
| 9B Q4_K_M, 16K, GPU-Projektor, Standardpuffer | Nur ca. 900 MiB frei nach Start; verworfen |
|
||||
| 9B Q4_K_M, 16K, CPU-Projektor, kleinere Puffer | Mindestens 1760 MiB frei; ca. 94 Tokens/s, Screenshot ca. 39 s; direktes API funktioniert, Hermes lehnt 16K ab |
|
||||
| 9B Q4_K_M, 64K, CPU-Projektor, Q4_0-Cache | Nur ca. 1439 MiB frei nach Start; zugunsten kleinerer Gewichtsdatei verworfen |
|
||||
| 9B IQ4_XS, 64K, CPU-Projektor, Q4_0-Cache, Batch 512/Microbatch 128 | Nach Start 1988 MiB frei; im Text-/Bild-/Werkzeug-/58315-Token-Test mindestens **1627 MiB frei**; ca. 73–101 Tokens/s |
|
||||
|
||||
Die ersten API-Tests prüfen deutsche Textantwort, Screenshot-Erkennung,
|
||||
strukturierten Werkzeugaufruf (`add`, Argumente 17/25) und knapp 11000 Tokens
|
||||
Eingabekontext. Der Werkzeugaufruf wird nur als JSON geprüft und nicht ausgeführt.
|
||||
Die kurzen Beispiele belegen Funktionsfähigkeit, keinen Qualitätsgewinn gegenüber
|
||||
4B. Die ursprüngliche Modellbewertung beruhte auf Hersteller-Benchmarks und muss
|
||||
sich in Johannes' tatsächlichen Aufgaben bewähren.
|
||||
|
||||
Das endgültige IQ4_XS-Profil beantwortete die 58315-Token-Anfrage ohne
|
||||
Abschneiden in ca. 22,4 Sekunden. Der strukturierte Werkzeugaufruf enthielt
|
||||
korrekt `{"a":17,"b":25}`. Ein echter lokaler Hermes-Lauf mit 65536 Kontext,
|
||||
20243 Zeichen Systemprompt und 19 Signal-Werkzeugbeschreibungen antwortete
|
||||
in ca. 5,2 Sekunden mit „Modelltest erfolgreich“. Werkzeugausführung war für
|
||||
diesen Lauf deaktiviert; es wurden keine Nachrichten an Kontakte versendet.
|
||||
|
||||
**Bildgrenze:** Der Screenshot brauchte mit dem CPU-Projektor ca. 38,9 Sekunden.
|
||||
CPU-Modell und die Angabe 6.0G/10G wurden erkannt, im freien Text wurde aber die
|
||||
Leistungsangabe 106 W fälschlich als „106 GB“ wiedergegeben. Die Bildverarbeitung
|
||||
funktioniert technisch; fehlerfreie OCR oder ein Qualitätsgewinn gegenüber 4B
|
||||
sind damit nicht belegt. Eine gezielte JSON-Nachfrage nach belegtem und gesamtem
|
||||
VRAM lieferte anschließend korrekt `6.0`/`10.0` in ca. 38,9 Sekunden.
|
||||
Die freien 1627 MiB sind ein gemessenes Minimum während
|
||||
dieses kurzen Tests, keine dauerhafte Zusicherung bei zusätzlichen Anwendungen.
|
||||
|
||||
Repository-Prüfungen: 55 Python-Tests und Bash-Syntaxprüfungen bestanden;
|
||||
Shellcheck ist auf der Workstation nicht installiert. Hauptmodell, Gateway und
|
||||
CPU-Kompression waren abschließend `active/running`, jeweils ohne automatische
|
||||
Neustarts. Im zugänglichen Kerneljournal seit Versuchsbeginn keine Treffer für
|
||||
NVIDIA-/Xid-/Speicherfehler. Kein absichtlich erzeugter
|
||||
UI-Absturz, keine Neustart- oder Langzeitabnahme.
|
||||
|
||||
## Bestehende 4B-Installation übernehmen
|
||||
|
||||
Die Vorbereitungshilfe überschreibt vorhandene Profil-Dateien absichtlich nicht.
|
||||
Vor einer manuellen Migration deshalb die aktive `llama.env`, das bisherige
|
||||
`llama-routine.env` und die Hermes-YAML separat sichern, die bisherige aktive
|
||||
4B-Konfiguration als `llama-fallback.env` erhalten und das alte Routineprofil
|
||||
in die Sicherung verschieben. Erst dann `prepare-workstation.py --download
|
||||
--models routine` ausführen (gegebenenfalls `--server-bin /usr/bin/llama-server`).
|
||||
Das erzeugt die neue Routinekonfiguration, ohne bereits umzuschalten.
|
||||
Nach Installation des aktuellen Umschalters erfolgt die Aktivierung ausdrücklich
|
||||
mit `hermes-stack-model routine`. Kein Modellwechsel durch `install.sh` allein.
|
||||
|
||||
## Umschalten und zurückrollen
|
||||
|
||||
```bash
|
||||
hermes-stack-model status
|
||||
hermes-stack-model fallback # ursprüngliches 4B-/64K-Setup wieder aktivieren
|
||||
hermes-stack-model routine # 9B-Erprobung wieder aktivieren
|
||||
```
|
||||
|
||||
Ein Wechsel beendet laufende Gateway-Anfragen. Der Umschalter sichert beide
|
||||
Konfigurationen, übernimmt das Profil samt Kontext-/Kompressionsgrenze, wartet
|
||||
auf den Modell-Healthcheck und startet den Gateway. Bei Startfehlern versucht er
|
||||
die Rückkehr zum vorherigen Profil; danach Dienststatus prüfen.
|
||||
|
||||
Der Rückweg auf Archwork wurde während der Erprobung tatsächlich ausgeführt:
|
||||
`llama.env` war danach bytegleich zum Ausgangsstand, die gesamte Hermes-YAML
|
||||
semantisch identisch; ein lokaler Hermes-Lauf mit 19 Signal-Werkzeugbeschreibungen
|
||||
antwortete mit „Modelltest erfolgreich.“. Es wurde keine Nachricht an einen
|
||||
Kontakt gesendet.
|
||||
|
||||
Die vollständige lokale Ausgangssicherung liegt auf Archwork unter
|
||||
`~/.local/state/hermes-stack-maintenance/qwen9b-20261027/` (private Hermes-YAML,
|
||||
aktives Profil, altes Routineprofil, vorheriger Umschalter und Messberichte).
|
||||
Diese privaten Dateien werden nicht eingecheckt. Der normale Rückweg braucht
|
||||
keine Wiederherstellung der gesamten Sicherung, sondern nur den Fallback-Befehl.
|
||||
|
||||
Bei erneutem UI-Stottern, Grafikfehlern oder zu geringer Reserve zuerst
|
||||
`hermes-stack-model fallback` verwenden. Bei einem dauerhaft unbedienbaren
|
||||
Desktop lässt sich derselbe Befehl aus einer Textkonsole ausführen. Ein
|
||||
automatischer Wechsel wegen neuer UI-Probleme ist nicht eingerichtet.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Qwen auf RTX 3080 10 GB / Hyprland
|
||||
|
||||
Aktuelle 9B-Erprobung mit 4B-Rückfallprofil: [QWEN9B_TRIAL.md](QWEN9B_TRIAL.md).
|
||||
Die folgenden Fit-Profile und älteren Messungen betreffen den 27B-Betrieb.
|
||||
|
||||
Zielhardware: RTX 3080 mit 10 GiB, i9-13900KF, 64 GB RAM. Der Desktop und
|
||||
llama.cpp nutzen dieselbe GPU. Das 27B-Modell in Q4_K_M passt nicht vollständig
|
||||
in 10 GiB: CPU/RAM-Offload bleibt erforderlich. Mehr VRAM für Gewichte kann
|
||||
|
||||
+7
-5
@@ -1,10 +1,12 @@
|
||||
# Bestehende Workstation behutsam übernehmen
|
||||
|
||||
Aktueller Stand seit 23.10.2026: Qwen3.5-4B als Routineprofil und Qwen3.8-27B
|
||||
als Alternative mit gemeinsamem Alias `hermes-local`, Token-/Zeitlimits und
|
||||
Slot-Watchdog. Einrichtung und Migration: [NEW_WORKSTATION.md](NEW_WORKSTATION.md),
|
||||
[INFERENCE_LIMITS.md](INFERENCE_LIMITS.md). Die nachfolgende Profilumstellung
|
||||
beschreibt den früheren 27B-Betrieb und ersetzt diese aktuelle Auswahl nicht.
|
||||
Aktueller Stand seit 27.10.2026: Erprobung von Qwen3.5-9B IQ4_XS mit 16K
|
||||
Kontext als Routineprofil. Das bisherige Qwen3.5-4B-/64K-Profil bleibt mit
|
||||
`hermes-stack-model fallback` erreichbar. Messungen und Rückweg:
|
||||
[QWEN9B_TRIAL.md](QWEN9B_TRIAL.md). Einrichtung und Migration:
|
||||
[NEW_WORKSTATION.md](NEW_WORKSTATION.md), [INFERENCE_LIMITS.md](INFERENCE_LIMITS.md).
|
||||
Die nachfolgenden historischen Profilumstellungen beschreiben den früheren
|
||||
27B-Betrieb und ersetzen diese aktuelle Auswahl nicht.
|
||||
|
||||
Nicht blind den Schnellstart für eine neue Maschine ausführen. Vorhandene
|
||||
Hermes-/Signal-Konfiguration bleibt privat und wird nicht durch das Repo ersetzt.
|
||||
|
||||
@@ -4,7 +4,7 @@ set -Eeuo pipefail
|
||||
umask 077
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||
usage() {
|
||||
printf 'Aufruf: %s [status|routine|large] (Modellwechsel beendet laufende Anfragen)\n' "$0"
|
||||
printf 'Aufruf: %s [status|routine|fallback|large] (Modellwechsel beendet laufende Anfragen)\n' "$0"
|
||||
}
|
||||
case "${1:-}" in
|
||||
''|status)
|
||||
@@ -13,7 +13,7 @@ case "${1:-}" in
|
||||
printf 'Aktives Modell: %s\nKonfiguration: %s/llama.env\n' "$LLAMA_MODEL" "$config_dir"
|
||||
exit 0 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
routine|large) selected="$1" ;;
|
||||
routine|fallback|large) selected="$1" ;;
|
||||
*) usage >&2; exit 2 ;;
|
||||
esac
|
||||
[[ $# -eq 1 ]] || exit 2
|
||||
@@ -24,12 +24,20 @@ candidate="$config_dir/llama-$selected.env"
|
||||
HERMES_LLAMA_CONFIG="$candidate" "$runner" --check
|
||||
backup="$(mktemp "$config_dir/llama.env.before-model-switch.XXXXXX")"
|
||||
cp -p -- "$config_dir/llama.env" "$backup"
|
||||
systemctl --user stop hermes-gateway.service
|
||||
hermes_config="${HERMES_HOME:-$HOME/.hermes}/config.yaml"
|
||||
hermes_backup=""
|
||||
if [[ -f "$hermes_config" ]]; then
|
||||
hermes_backup="$(mktemp "$config_dir/hermes.yaml.before-model-switch.XXXXXX")"
|
||||
cp -p -- "$hermes_config" "$hermes_backup"
|
||||
fi
|
||||
rollback() {
|
||||
local rc=$?
|
||||
trap - EXIT
|
||||
if ((rc != 0)); then
|
||||
install -m 0600 "$backup" "$config_dir/llama.env" || true
|
||||
if [[ -n "$hermes_backup" ]]; then
|
||||
install -m 0600 "$hermes_backup" "$hermes_config" || true
|
||||
fi
|
||||
systemctl --user restart llama-qwen.service || true
|
||||
systemctl --user start hermes-gateway.service || true
|
||||
printf 'Modellwechsel fehlgeschlagen; vorherige Konfiguration zurückkopiert. Dienststatus prüfen.\n' >&2
|
||||
@@ -37,11 +45,38 @@ rollback() {
|
||||
exit "$rc"
|
||||
}
|
||||
trap rollback EXIT
|
||||
systemctl --user stop hermes-gateway.service
|
||||
install -m 0600 "$candidate" "$config_dir/llama.env"
|
||||
# Keep Hermes' actual context and compression trigger paired with the model.
|
||||
# Legacy profiles without metadata use the original 64K window.
|
||||
LLAMA_CONTEXT_TOKENS=65536
|
||||
# shellcheck disable=SC1090
|
||||
source "$candidate"
|
||||
if [[ -n "$hermes_backup" ]]; then
|
||||
python3 - "$hermes_config" "$LLAMA_CONTEXT_TOKENS" <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import yaml
|
||||
path = Path(sys.argv[1])
|
||||
context = int(sys.argv[2])
|
||||
if context < 64000:
|
||||
raise ValueError("Hermes requires at least 64000 context tokens")
|
||||
config = yaml.safe_load(path.read_text())
|
||||
config.setdefault("model", {})["context_length"] = context
|
||||
config.setdefault("compression", {})["threshold_tokens"] = min(24000, context * 3 // 8)
|
||||
with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, delete=False) as out:
|
||||
yaml.safe_dump(config, out, allow_unicode=True, sort_keys=False, width=1000)
|
||||
temporary = Path(out.name)
|
||||
os.chmod(temporary, 0o600)
|
||||
temporary.replace(path)
|
||||
PY
|
||||
fi
|
||||
ready=0
|
||||
if systemctl --user restart llama-qwen.service; then
|
||||
for ((i=0; i<90; i++)); do
|
||||
if curl -fsS --max-time 2 http://127.0.0.1:8080/health | jq -e '.status == "ok"' >/dev/null 2>&1; then
|
||||
if curl -fsS --max-time 2 http://127.0.0.1:8080/health 2>/dev/null | jq -e '.status == "ok"' >/dev/null 2>&1; then
|
||||
ready=1; break
|
||||
fi
|
||||
sleep 2
|
||||
|
||||
@@ -14,7 +14,7 @@ import tempfile
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ALIASES = ["hermes-local", "qwen35-4b-local", "qwen38-27b-local", "qwen36-27b-local"]
|
||||
ALIASES = ["hermes-local", "qwen35-9b-local", "qwen35-4b-local", "qwen38-27b-local", "qwen36-27b-local"]
|
||||
LOCAL_URL = "http://127.0.0.1:8080/v1"
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ def write_config(path, text, replace=False):
|
||||
|
||||
|
||||
def profile_config(name, paths, server_bin=""):
|
||||
args = ["--ctx-size", "65536", "--parallel", "1", "--jinja"]
|
||||
context = 65536
|
||||
args = ["--ctx-size", str(context), "--parallel", "1", "--jinja"]
|
||||
if name == "compression":
|
||||
args += ["--rope-scaling", "yarn", "--rope-scale", "2", "--yarn-orig-ctx", "32768",
|
||||
"--reasoning-budget", "0", "--no-reasoning-preserve", "--n-gpu-layers", "0",
|
||||
@@ -77,27 +78,32 @@ def profile_config(name, paths, server_bin=""):
|
||||
else:
|
||||
args += ["--reasoning", "auto", "--reasoning-budget", "128", "--no-reasoning-preserve",
|
||||
"--threads", "6", "--threads-batch", "16"]
|
||||
if name == "routine":
|
||||
# Fits on the verified 10-GiB workstation. Explicit layers disable fit.
|
||||
if name in ("routine", "fallback"):
|
||||
# Explicit full offload: fail instead of silently moving layers to CPU.
|
||||
args += ["--n-gpu-layers", "99", "--fit", "off"]
|
||||
else:
|
||||
args += ["--n-gpu-layers", "auto", "--fit", "on", "--fit-target", "4096"]
|
||||
if name == "routine":
|
||||
args += ["--no-mmproj-offload", "--batch-size", "512", "--ubatch-size", "128"]
|
||||
args += ["--sleep-idle-seconds", "300", "--image-min-tokens", "1024",
|
||||
"--cors-origins", "localhost"]
|
||||
args += ["--flash-attn", "auto", "--cache-type-k", "q8_0", "--cache-type-v", "q8_0", "--no-ui"]
|
||||
cache = "q4_0" if name == "routine" else "q8_0"
|
||||
args += ["--flash-attn", "auto", "--cache-type-k", cache, "--cache-type-v", cache, "--no-ui"]
|
||||
settings = {"LLAMA_SERVER_BIN": server_bin, "LLAMA_MODEL": str(paths[0]),
|
||||
"LLAMA_MMPROJ": str(paths[1]) if len(paths) > 1 else "", "LLAMA_HF_REPO": "",
|
||||
"LLAMA_ALIAS": "qwen3-1.7b-compression" if name == "compression" else ",".join(ALIASES),
|
||||
"LLAMA_HOST": "127.0.0.1", "LLAMA_PORT": "8081" if name == "compression" else "8080",
|
||||
"LLAMA_PROFILE": "", "LLAMA_MAX_OUTPUT_TOKENS": "2048"}
|
||||
"LLAMA_PROFILE": "", "LLAMA_CONTEXT_TOKENS": str(context), "LLAMA_MAX_OUTPUT_TOKENS": "2048"}
|
||||
return ("# Generated from config/models.lock.json; no service started.\n"
|
||||
+ "".join(f"{key}={shlex.quote(value)}\n" for key, value in settings.items())
|
||||
+ "LLAMA_ARGS=(\n " + shlex.join(args) + "\n)\n")
|
||||
|
||||
|
||||
def configure_hermes(config, compression=False):
|
||||
def configure_hermes(config, compression=False, context=65536):
|
||||
if context < 64000:
|
||||
raise ValueError("Hermes requires at least 64000 context tokens")
|
||||
config.setdefault("model", {}).update(default="hermes-local", provider="custom",
|
||||
base_url=LOCAL_URL, context_length=65536)
|
||||
base_url=LOCAL_URL, context_length=context)
|
||||
config.setdefault("agent", {}).update(run_budget_seconds=1800, budget_warning_ratio=0.8)
|
||||
config.setdefault("providers", {}).setdefault("custom", {}).update(
|
||||
request_timeout_seconds=600, stale_timeout_seconds=180)
|
||||
@@ -119,6 +125,7 @@ def configure_hermes(config, compression=False):
|
||||
config.setdefault("compression", {}).update(enabled=True, threshold=0.5,
|
||||
threshold_tokens=24000, target_ratio=0.2, protect_last_n=10, protect_first_n=3,
|
||||
context_timeout_seconds=120, context_total_ceiling_seconds=600)
|
||||
config.setdefault("compression", {})["threshold_tokens"] = min(24000, context * 3 // 8)
|
||||
return config
|
||||
|
||||
|
||||
@@ -144,22 +151,22 @@ def unpin_local_jobs(path):
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--download", action="store_true", help="Fehlende, fest versionierte GGUF-Dateien herunterladen")
|
||||
parser.add_argument("--models", default="routine,large,compression", help="Kommagetrennt: routine,large,compression")
|
||||
parser.add_argument("--models", default="routine,large,compression,fallback", help="Kommagetrennt: routine,large,compression,fallback")
|
||||
parser.add_argument("--server-bin", default="", help="Leer: verwalteter CUDA-Build oder llama-server im PATH")
|
||||
parser.add_argument("--model-dir", type=Path, default=Path.home() / ".local/share/hermes-stack/models")
|
||||
parser.add_argument("--config-dir", type=Path, default=Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / "hermes-stack-maintenance")
|
||||
parser.add_argument("--hermes-config", type=Path, default=Path(os.environ.get("HERMES_HOME", str(Path.home() / ".hermes"))) / "config.yaml")
|
||||
parser.add_argument("--select", choices=["routine", "large"], help="Aktive llama.env vorbereiten; kein Dienststart")
|
||||
parser.add_argument("--select", choices=["routine", "large", "fallback"], help="Aktive llama.env vorbereiten; kein Dienststart")
|
||||
parser.add_argument("--configure-hermes", action="store_true", help="Gezielte Hermes-/Maintenance-Einstellungen mit Backup übernehmen")
|
||||
parser.add_argument("--unpin-local-cron", action="store_true", help="Lokale Cron-Modellbindungen über Hermes-CLI lösen")
|
||||
options = parser.parse_args()
|
||||
names = list(dict.fromkeys(options.models.split(",")))
|
||||
manifest = json.loads((ROOT / "config/models.lock.json").read_text())["models"]
|
||||
if not names or any(name not in manifest for name in names):
|
||||
parser.error("--models erlaubt nur routine,large,compression")
|
||||
parser.error("--models erlaubt nur routine,large,compression,fallback")
|
||||
if options.select and options.select not in names:
|
||||
parser.error("--select muss in --models enthalten sein")
|
||||
if options.configure_hermes and "routine" not in names and "large" not in names:
|
||||
if options.configure_hermes and not any(n in names for n in ("routine", "large", "fallback")):
|
||||
parser.error("--configure-hermes benötigt ein Hauptmodell")
|
||||
if options.select or options.configure_hermes or options.unpin_local_cron:
|
||||
state = subprocess.check_output(["systemctl", "--user", "show", "hermes-gateway.service",
|
||||
@@ -185,7 +192,7 @@ def main():
|
||||
if options.configure_hermes:
|
||||
text = options.hermes_config.read_text() if options.hermes_config.exists() else ""
|
||||
config = yaml.safe_load(text) or {}
|
||||
configure_hermes(config, "compression" in names)
|
||||
configure_hermes(config, "compression" in names, 65536)
|
||||
write_config(options.hermes_config, yaml.safe_dump(config, allow_unicode=True, sort_keys=False, width=1000), replace=True)
|
||||
maintenance = options.config_dir / "config.env"
|
||||
text = maintenance.read_text() if maintenance.exists() else (ROOT / "config/config.env.example").read_text()
|
||||
|
||||
+54
-14
@@ -58,22 +58,25 @@ class PreparationTests(unittest.TestCase):
|
||||
self.assertEqual(updated["custom_providers"][1]["extra_body"]["max_tokens"], 2048)
|
||||
self.assertEqual(updated["auxiliary"]["compression"]["base_url"], "http://127.0.0.1:8081/v1")
|
||||
|
||||
def test_context_matches_both_main_profiles_and_replaces_stale_window(self):
|
||||
for previous in (None, 256000):
|
||||
with self.subTest(previous=previous):
|
||||
config = {"model": {"context_length": previous},
|
||||
"compression": {"threshold_tokens": 24000}}
|
||||
prepare.configure_hermes(config)
|
||||
window = config["model"]["context_length"]
|
||||
self.assertEqual(window, 65536)
|
||||
for name in ("routine", "large"):
|
||||
text = prepare.profile_config(name, [self.root / "weights.gguf"])
|
||||
self.assertIn(f"--ctx-size {window}", text)
|
||||
self.assertEqual(config["compression"]["threshold_tokens"], 24000)
|
||||
def test_context_and_compression_follow_selected_model(self):
|
||||
for name, window, trigger in (("routine", 65536, 24000), ("fallback", 65536, 24000), ("large", 65536, 24000)):
|
||||
with self.subTest(name=name):
|
||||
config = {"model": {"context_length": 256000}, "compression": {"threshold_tokens": 24000}}
|
||||
prepare.configure_hermes(config, context=window)
|
||||
self.assertEqual(config["model"]["context_length"], window)
|
||||
self.assertEqual(config["compression"]["threshold_tokens"], trigger)
|
||||
text = prepare.profile_config(name, [self.root / "weights.gguf"])
|
||||
self.assertIn(f"--ctx-size {window}", text)
|
||||
before = json.dumps(config, sort_keys=True)
|
||||
prepare.configure_hermes(config)
|
||||
prepare.configure_hermes(config, context=window)
|
||||
self.assertEqual(json.dumps(config, sort_keys=True), before)
|
||||
|
||||
def test_configuration_refuses_below_hermes_context_floor(self):
|
||||
config = {"model": {"context_length": 65536}}
|
||||
with self.assertRaisesRegex(ValueError, "at least 64000"):
|
||||
prepare.configure_hermes(config, context=16384)
|
||||
self.assertEqual(config, {"model": {"context_length": 65536}})
|
||||
|
||||
def test_existing_profiles_preserved_and_explicit_selection_backed_up(self):
|
||||
path = self.root / "llama.env"
|
||||
path.write_text("custom tuning\n")
|
||||
@@ -141,7 +144,44 @@ class ModelSwitchTests(unittest.TestCase):
|
||||
p.write_text("#!/usr/bin/env bash\n" + body + "\n")
|
||||
p.chmod(0o755)
|
||||
self.env = dict(os.environ, PATH=str(self.bin) + ":" + os.environ["PATH"],
|
||||
XDG_CONFIG_HOME=str(self.config.parent), XDG_RUNTIME_DIR=str(self.root), TEST_CALLS=str(self.root / "calls"))
|
||||
HERMES_HOME=str(self.root / "hermes"), XDG_CONFIG_HOME=str(self.config.parent), XDG_RUNTIME_DIR=str(self.root), TEST_CALLS=str(self.root / "calls"))
|
||||
|
||||
def test_switch_and_failure_keep_hermes_window_paired_with_model(self):
|
||||
import yaml
|
||||
directory = self.root / "hermes"
|
||||
directory.mkdir()
|
||||
path = directory / "config.yaml"
|
||||
original = "model:\n context_length: 65536\ncompression:\n threshold_tokens: 24000\nterminal:\n backend: docker\n"
|
||||
(self.config / "llama-routine.env").write_text("LLAMA_MODEL=new.gguf\nLLAMA_CONTEXT_TOKENS=64000\n")
|
||||
for fail in (False, True):
|
||||
path.write_text(original)
|
||||
(self.config / "llama.env").write_text("LLAMA_MODEL=old.gguf\n")
|
||||
result = subprocess.run(["bash", str(self.bin / "hermes-stack-model"), "routine"],
|
||||
env=dict(self.env, TEST_FAIL=str(int(fail))), capture_output=True, text=True)
|
||||
if fail:
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(path.read_text(), original)
|
||||
self.assertEqual((self.config / "llama.env").read_text(), "LLAMA_MODEL=old.gguf\n")
|
||||
else:
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
config = yaml.safe_load(path.read_text())
|
||||
self.assertEqual(config["model"]["context_length"], 64000)
|
||||
self.assertEqual(config["compression"]["threshold_tokens"], 24000)
|
||||
self.assertEqual(config["terminal"]["backend"], "docker")
|
||||
|
||||
def test_hermes_rejects_too_small_context_and_restores_pair(self):
|
||||
directory = self.root / "hermes"
|
||||
directory.mkdir()
|
||||
path = directory / "config.yaml"
|
||||
original = "model:\n context_length: 65536\n"
|
||||
path.write_text(original)
|
||||
(self.config / "llama-routine.env").write_text("LLAMA_MODEL=new.gguf\nLLAMA_CONTEXT_TOKENS=16384\n")
|
||||
result = subprocess.run(["bash", str(self.bin / "hermes-stack-model"), "routine"],
|
||||
env=self.env, capture_output=True, text=True)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("at least 64000", result.stderr)
|
||||
self.assertEqual(path.read_text(), original)
|
||||
self.assertEqual((self.config / "llama.env").read_text(), "LLAMA_MODEL=old.gguf\n")
|
||||
|
||||
def test_successful_switch_through_path_entrypoint(self):
|
||||
(self.root / "entrypoint").symlink_to(self.bin / "hermes-stack-model")
|
||||
|
||||
Reference in new issue
Block a user