Phase 2 (systemd tag) notified per-service restart handlers and then called meta: flush_handlers itself, so any run where either unit's template content changed (including first apply) restarted BOTH live services immediately in Phase 2 -- before Phase 3 firewall scoping or Phase 4 smoke tests ran. This contradicted the phase's documented purpose (units land on disk only, nothing starts/restarts until Phase 4). Fix: Phase 2 only reloads the systemd daemon and registers each template task's changed result. Phase 4 (verify.yml) now decides start vs restart per-service based on that recorded change, so restarts remain independent per instance and never fire before Phase 4.
llm-inference-multimodel
Deploys two independent llama-server systemd services on astro-orbiter's
RTX 3090 (24GB), alongside — not replacing — the existing llm-inference role:
| Instance | Port | Model | Quant | ctx | parallel | ~VRAM |
|---|---|---|---|---|---|---|
llama-server-aux |
8000 | Phi-4-14B-Instruct | Q4_K_M | 8192 | 2 | ~10.0GB |
llama-server-toolcall |
8001 | Mistral-Small-24B-Instruct-2501 | Q3_K_M | 4096 | 1 | ~13.2GB |
Combined estimate: ~23.2GB / 24GB (~0.8GB headroom). See
/home/hermes/astro-orbiter-multi-model-plan.md for the full approved design
(VRAM math, model selection rationale, rollback plan, validation harness).
Relationship to roles/llm-inference
This role does not replace llm-inference. It assumes that role's
prerequisites are already satisfied on the host:
- NVIDIA driver installed
/opt/llama.cppcloned and built with CUDA (/opt/llama.cpp/build/bin/llama-serverexists)jarvisservice user +/home/jarvispresent
The pre-existing single-model Gemma llama-server (however it is currently run) is never modified, restarted, or deleted by this role. It is the rollback target.
Phases
Run the whole role, or scope with --tags:
ansible-playbook -i inventory.yml playbooks/day1_deploy_llm_inference_multimodel.yml
# or, once merged into a single play:
ansible-playbook -i inventory.yml <playbook>.yml --tags discover,models,systemd,firewall,verify
-
discover (
tasks/discover.yml) — READ-ONLY. Confirms viaservice_facts+pgrepwhether the existing Gemma llama-server actually runs as a systemd unit today, or some ad hoc way (nohup/screen/tmux). Does not assume a unit exists — this was an open unknown in the plan and is resolved here as a fact-gathering step, not an assumption. Also records baseline VRAM and current port 8000/8001 listeners for comparison later.If this reports no unit found, stop and read the debug message — it means plan §6's rollback story ("systemctl start the old unit to revert") isn't actually available yet, and that should be fixed (codify the existing process as a systemd unit) before proceeding to Phase 2.
-
models (
tasks/models.yml) — Idempotent GGUF download to/opt/models/with a stat + minimum-size guard (mirrors the pattern inroles/llm-inference/tasks/serve.ymland thellm-inference-homelabskill), so reruns don't re-pull 8.5GB/11.7GB files or mistake a truncated partial download for complete. -
systemd (
tasks/systemd.yml) — Templates and deploys both unit files to/etc/systemd/system/. Deliberately does not start or enable either service — units land on disk as a separately reviewable checkpoint. Two fully independent units (not one unit with two ExecStarts) so either instance can be restarted/stopped without affecting the other. -
firewall (
tasks/firewall.yml) — Scopes ports 8000 and 8001 viaufwtollm_allowed_source_cidr(default the Hermes LAN subnet), rather than leaving them open. Both unit templates also bind tollm_bind_address(default10.1.71.130, the host's private LAN IP) — not0.0.0.0— which is a deliberate change from the pre-existing Gemma pattern flagged as insecure in the plan. -
verify (
tasks/verify.yml) — The only phase that actually starts + enables both services. Waits for/healthon both ports, smoke-tests/v1/modelsand a trivial/v1/chat/completionscall on each, checksnvidia-smiVRAM usage against the plan's design estimate, and grepsdmesgfor OOM-kill events.This smoke test is not the tool-calling validation harness. See below.
Key variables
Defined in defaults/main.yml (all overridable via host_vars/group_vars
or -e):
llm_service_user(jarvis),llm_binary_path,llm_models_dir,llm_bind_address,llm_allowed_source_cidr- Aux:
llm_aux_port,llm_aux_model_path,llm_aux_model_url,llm_aux_ctx_size,llm_aux_parallel,llm_aux_gpu_layers - Tool-calling:
llm_toolcall_port,llm_toolcall_model_path,llm_toolcall_model_url,llm_toolcall_ctx_size,llm_toolcall_parallel,llm_toolcall_gpu_layers
vars/main.yml holds constants not meant to be overridden per-host (HF token
reference, expected-VRAM figures used only for the verify.yml report).
⚠️ Tool-calling validation is required before use
Port 8001 (Mistral-Small-24B) must pass the manual validation procedure described in plan §7 before any Claude Code / tool-calling-capable Hermes profile is pointed at it:
- A curl-based
tool_callsemission probe (does it call tools correctly on known trigger prompts?) - A hallucination stress test (does it fabricate
tool_callson prompts that shouldn't trigger any?) - A shadow-mode period (run parallel to the existing tool-calling path, compare outputs, before a hard cutover)
This is intentionally not automated into this role — it is a
correctness/safety judgment call, not a repeatable infra check. See
docs/validation-log.md in this role directory for the procedure reference
and a place to log results once Ryan runs it.
Known gap: Semaphore is broken (as of 2026-08-05)
The normal execution/audit path (Semaphore) is currently non-functional.
This role was authored to be run via direct ansible-playbook as an accepted
interim stopgap, executed personally by Ryan. This is a known gap, not the
intended long-term operational path — once Semaphore is repaired, retarget
execution of this role (and future changes to it) through Semaphore so runs
are audited/logged there again. Flag this in any future work that touches
this role.
Rollback
The existing Gemma llama-server and its GGUF are untouched by every phase of this role. To roll back:
systemctl stop llama-server-aux llama-server-toolcallsystemctl disable llama-server-aux llama-server-toolcall(optional, if reverting permanently)- Confirm the original Gemma service (name determined by
discover.yml, commonlyllama-server.service) is (still) running:systemctl status llama-server - If it was never running because Phase 0 discovered it wasn't a managed unit, whatever ad hoc process/command was used before this role's changes is also unaffected — nothing in this role stopped it.
No files belonging to the existing Gemma deployment (GGUF, unit file, or otherwise) are ever written to or deleted by this role.