atlas-iac/testing/tests/test_hermes_chat_local_compute.py

296 lines
13 KiB
Python

"""Hermes chat local compute contracts."""
from __future__ import annotations
from test_hermes_chat_support import (
HERMES,
Path,
ROOT,
SimpleNamespace,
_documents,
importlib,
json,
tomllib,
)
def test_local_flux_runtime_and_gpu_handoff_are_flux_managed():
"""FLUX and Wolf share titan-24 while text stays on titan-20."""
deployment = _documents(HERMES / "local-image-deployment.yaml")[0]
assert deployment["metadata"]["name"] == "hermes-local-image"
pod = deployment["spec"]["template"]["spec"]
assert pod["serviceAccountName"] == "hermes-gpu-runtime"
local = next(item for item in pod["containers"] if item["name"] == "local-image")
assert len(pod["containers"]) == 1
assert local["resources"]["requests"]["nvidia.com/gpu.shared"] == 1
assert local["ports"] == [{"name": "local-image", "containerPort": 9004}]
assert any(mount["mountPath"] == "/models" for mount in local["volumeMounts"])
model_env = {item["name"]: item["value"] for item in local["env"]}
assert model_env["HERMES_LOCAL_IMAGE_LISTEN_PORT"] == "9004"
assert model_env["HERMES_LOCAL_IMAGE_REVISION"] == (
"e7b7dc27f91deacad38e78976d1f2b499d76a294"
)
assert model_env["HERMES_LOCAL_IMAGE_GPU_ACTIVITY_NODE"] == "titan-24"
assert model_env["HERMES_LOCAL_IMAGE_GPU_ACTIVE_SM_PERCENT"] == "80"
assert model_env["HERMES_LOCAL_IMAGE_GPU_MAX_EXTERNAL_MEMORY_BYTES"] == (
"3221225472"
)
assert model_env["HERMES_LOCAL_IMAGE_OFFLOAD_MODE"] == "sequential"
assert "nvidia-process-exporter-local.monitoring.svc.cluster.local" in model_env[
"HERMES_LOCAL_IMAGE_GPU_ACTIVITY_URL"
]
models_volume = next(item for item in pod["volumes"] if item["name"] == "models")
assert models_volume["persistentVolumeClaim"]["claimName"] == (
"hermes-image-models"
)
services = _documents(HERMES / "service.yaml")
image_service = next(
item for item in services if item["metadata"]["name"] == "hermes-local-image"
)
assert image_service["spec"]["selector"] == {"app": "hermes-local-image"}
handoff_services = _documents(HERMES / "model-gate-deployment.yaml")
handoff = next(
item
for item in handoff_services
if item["kind"] == "Service"
and item["metadata"]["name"] == "hermes-gpu-handoff"
)
assert handoff["spec"]["ports"][0]["targetPort"] == "handoff"
ariadne = _documents(
Path(__file__).parents[2]
/ "services/maintenance/apps/ariadne-deployment.yaml"
)[0]
env = {
item["name"]: item["value"]
for item in ariadne["spec"]["template"]["spec"]["containers"][0]["env"]
if "value" in item
}
assert env["GAME_MODE_OLLAMA_URL"] == (
"http://hermes-gpu-handoff.hermes.svc.cluster.local:11434"
)
assert env["GAME_MODE_OLLAMA_MODEL"] == "flux-2-klein-4b-local"
for config_name in ("configmap.yaml", "agent-configmap.yaml", "chat-configmap.yaml"):
config = _documents(HERMES / config_name)[0]["data"]["config.yaml"]
assert "gpt-oss:20b" not in config
assert "atlas-switchyard" in config
switchyard = _documents(HERMES / "switchyard-configmap.yaml")[0]["data"][
"routes.toml"
]
assert 'id = "qwen2.5:14b-instruct-q4_0"' in switchyard
assert "qwen2.5:3b-instruct-q4_0" not in switchyard
assert "route/local/qwen2.5-14b/medium" in switchyard
assert "Anthropic and Claude name the same provider" in switchyard
assert "OpenAI and Codex name the same provider" in switchyard
assert "Choose across every configured Codex and Claude family" in switchyard
assert "Claude Fable" in switchyard
assert switchyard.count('Treat "think hard"') == 4
assert switchyard.count("Never choose below the") >= 5
switchyard_config = tomllib.loads(switchyard)
routes = switchyard_config["routes"]
configured_targets = switchyard_config["targets"]
for route_name in ("auto_fast", "auto_balanced", "auto_deep", "auto_maximum"):
leading_targets = set(routes[route_name]["targets"][:2])
assert leading_targets == {"codex_sol_xhigh", "claude_opus_xhigh"}
assert "max_output_tokens" not in routes[route_name]
for route_name in ("auto_fast", "auto_balanced", "auto_deep", "auto_maximum"):
targets = routes[route_name]["targets"]
selector_targets = routes[route_name]["response_schema"]
assert not any(target.startswith("local_") for target in targets)
assert any("fable" in target for target in targets)
assert "local_qwen" not in selector_targets
assert "not eligible for foreground" in routes[route_name]["prompt"]
for route_name in ("auto_deep", "auto_maximum"):
targets = routes[route_name]["targets"]
selector_targets = routes[route_name]["response_schema"]
assert not any(target.endswith("_low") for target in targets)
assert "_low" not in selector_targets
maximum_targets = routes["auto_maximum"]["targets"]
maximum_selector_targets = routes["auto_maximum"]["response_schema"]
assert not any(target.endswith("_medium") for target in maximum_targets)
assert "_medium" not in maximum_selector_targets
assert "absolute high effort floor" in routes["auto_maximum"]["prompt"]
assert "quality mark was missed" in routes["auto_balanced"]["prompt"]
assert "raises the next boundary to xhigh" in routes["auto_maximum"]["prompt"]
assert "Repeated quality misses require xhigh" in routes[
"worker_auto_maximum"
]["prompt"]
assert any(
target.startswith("local_")
for target in routes["manual_local_qwen"]["targets"]
)
for route_name in (
"manual_codex_luna",
"manual_codex_terra",
"manual_codex_sol",
"manual_claude_haiku",
"manual_claude_fable",
"manual_claude_sonnet",
"manual_claude_opus",
):
assert not any(
target.startswith("local_") for target in routes[route_name]["targets"]
)
for provider, families in {
"codex": ("luna", "terra", "sol"),
"claude": ("haiku", "fable", "sonnet", "opus"),
}.items():
for family in families:
for effort in ("low", "medium", "high", "xhigh"):
route = routes[f"manual_{provider}_{family}_{effort}"]
assert route["id"] == f"atlas/manual/{provider}/{family}/{effort}"
assert route["targets"][0] == f"{provider}_{family}_{effort}"
worker_target = f"worker_{provider}_{family}_{effort}"
assert worker_target in routes["worker_auto_maximum"]["targets"]
assert configured_targets[worker_target]["id"] == (
f"worker/{provider}/{family}/{effort}"
)
for route_name in ("auto_fast", "auto_balanced"):
prompt = routes[route_name]["prompt"]
assert "image tool—not the conversational model" in prompt
assert "Do not select a" in prompt
assert "local Qwen or Claude target" in prompt
assert "max_output_tokens" not in routes["worker_auto_maximum"]
model_gate = _documents(HERMES / "model-gate-configmap.yaml")[0]["data"][
"model_gate.py"
]
assert "qwen2.5:14b-instruct-q4_0" in model_gate
def test_titan20_serializes_classifier_and_local_chat_model_residency():
"""Classifier and local chat share one serialized resident Qwen weight."""
deployment = _documents(
Path(__file__).parents[2] / "services/ai-llm/deployment.yaml"
)[0]
pod = deployment["spec"]["template"]["spec"]
required = pod["affinity"]["nodeAffinity"][
"requiredDuringSchedulingIgnoredDuringExecution"
]["nodeSelectorTerms"][0]["matchExpressions"][0]
assert required["values"] == ["titan-20"]
container = pod["containers"][0]
env = {item["name"]: item["value"] for item in container["env"]}
assert env["OLLAMA_MAX_LOADED_MODELS"] == "1"
assert env["OLLAMA_NUM_PARALLEL"] == "1"
assert env["OLLAMA_KEEP_ALIVE"] == "-1"
assert env["OLLAMA_CONTEXT_LENGTH"] == "8192"
warm_command = " ".join(container["command"])
assert "--keepalive=-1" not in warm_command
models = next(item for item in pod["volumes"] if item["name"] == "models")
assert models["persistentVolumeClaim"]["claimName"] == (
"ollama-models-titan20"
)
def test_local_image_gpu_guard_distinguishes_background_and_saturated_gpu(monkeypatch):
"""Lease-idle desktop spikes may coexist, but saturation still blocks FLUX."""
source = ROOT / "dockerfiles" / "hermes-local-image-server.py"
spec = importlib.util.spec_from_file_location("hermes_local_image_server", source)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
monkeypatch.setattr(module, "GPU_ACTIVITY_NODE", "titan-24")
monkeypatch.setattr(module, "GPU_ACTIVE_SM_PERCENT", 80.0)
monkeypatch.setattr(module, "GPU_MAX_EXTERNAL_MEMORY_BYTES", 3 << 30)
idle = module._parse_gpu_activity(
'\n'.join(
[
'nvidia_process_gpu_sm_util_percent{node="titan-24",namespace="host",process="Xorg"} 0',
'nvidia_process_gpu_memory_used_bytes{node="titan-24",namespace="host",process="Xorg"} 1900000000',
'nvidia_process_gpu_sm_util_percent{node="titan-24",namespace="game-stream",process="wolf"} 3',
'nvidia_process_gpu_memory_used_bytes{node="titan-24",namespace="hermes",process="python"} 9000000000',
]
)
)
assert idle["interactive_active"] is False
assert idle["external_gpu_memory_bytes"] == 1900000000
assert idle["external_gpu_sm_percent"] == 3
background_spike = module._parse_gpu_activity(
'nvidia_process_gpu_sm_util_percent{node="titan-24",namespace="host",process="sway"} 41\n'
)
assert background_spike["interactive_active"] is False
active = module._parse_gpu_activity(
'nvidia_process_gpu_sm_util_percent{node="titan-24",namespace="host",process="steam"} 91\n'
)
assert active["interactive_active"] is True
assert "91%" in active["gpu_guard_reason"]
def test_local_flux_renderer_uses_a_disposable_cuda_worker(monkeypatch):
"""A completed render must not leave its CUDA context in the API process."""
source = ROOT / "dockerfiles" / "hermes-local-image-server.py"
spec = importlib.util.spec_from_file_location("hermes_local_image_worker", source)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
calls = []
def run(command, **kwargs):
calls.append((command, kwargs))
return SimpleNamespace(
returncode=0,
stdout=b'{"success":true,"route":"local","image_b64":"cG5n"}',
stderr=b"",
)
monkeypatch.setattr(module.subprocess, "run", run)
result = module._render({"prompt": "black cat", "aspect_ratio": "square"})
assert result["route"] == "local"
command, options = calls[0]
assert command[-1] == "--render-worker"
assert json.loads(options["input"]) == {
"prompt": "black cat",
"aspect_ratio": "square",
}
assert options["timeout"] == module.RENDER_TIMEOUT_SECONDS
assert options["check"] is False
def test_local_flux_uses_low_vram_offload_without_reducing_resolution():
"""The shared 3080 lane must trade time, not image size, for headroom."""
source = (ROOT / "dockerfiles" / "hermes-local-image-server.py").read_text()
assert 'OFFLOAD_MODE = os.environ.get(' in source
assert '"HERMES_LOCAL_IMAGE_OFFLOAD_MODE", "sequential"' in source
assert "pipe.enable_sequential_cpu_offload()" in source
assert '"square": (1024, 1024)' in source
def test_chat_auth_and_relay_are_pod_lifetime_only():
statefulset = _documents(HERMES / "chat-statefulset.yaml")[0]
pod = statefulset["spec"]["template"]["spec"]
containers = statefulset["spec"]["template"]["spec"]["containers"]
for name in ("hermes", "webui"):
container = next(item for item in containers if item["name"] == name)
env = {item["name"]: item["value"] for item in container["env"]}
assert env["HERMES_AUTH_FILE"] == "/runtime-access/hermes-auth.json"
mount = next(
item for item in container["volumeMounts"] if item["name"] == "runtime-access"
)
assert mount["mountPath"] == "/runtime-access"
assert "subPath" not in mount
hermes_env = {
item["name"]: item["value"]
for item in next(item for item in containers if item["name"] == "hermes")["env"]
}
runtime = next(item for item in pod["volumes"] if item["name"] == "runtime-access")
assert runtime["emptyDir"] == {"medium": "Memory", "sizeLimit": "2Mi"}
assert not any(item["name"] == "provider-auth" for item in pod["volumes"])
init_command = next(
item for item in pod["initContainers"] if item["name"] == "init-config"
)["command"][2]
for key in (
"ANTHROPIC_API_KEY",
"API_SERVER_KEY",
"CLAUDE_CODE_OAUTH_TOKEN",
"GITEA_TOKEN",
"HERMES_IMAGE_BROKER_KEY",
"OPENAI_API_KEY",
):
assert key in init_command
assert "printf 'API_SERVER_KEY=%s" not in init_command
assert hermes_env["AGENT_BROWSER_EXECUTABLE_PATH"].endswith("/chrome-linux/headless_shell")
assert "--no-sandbox" in hermes_env["AGENT_BROWSER_ARGS"]