Files
homelab/ansible/roles/llm-inference/tasks/serve.yml
Hermes Agent service account aff792a061 feat(llm-inference): move astro-orbiter monitoring to GitOps (values.yaml + dashboards.yaml)
- Prometheus scrape configs for node/gpu/llama-server exporters on
  astro-orbiter now declared in cluster/applications/monitoring/values.yaml
  (additionalScrapeConfigs), applied via ArgoCD sync instead of an
  imperative kubectl secret patch from the Ansible role.
- Grafana dashboard for astro-orbiter LLM inference added as a ConfigMap
  in cluster/applications/monitoring/dashboards.yaml (grafana_dashboard=1
  sidecar label), replacing the role's ad-hoc kubectl apply of a rendered
  Jinja template.
- ansible/roles/llm-inference/tasks/monitoring.yml: removed the kubectl
  get/patch/apply tasks and orphaned grafana-llm-dashboard.json.j2
  template; role now only stands up node_exporter + nvidia_gpu_exporter
  and verifies they're reachable — cluster-facing config lives in Git.
- host_vars/vars.yml + inventory.yml: finalize astro-orbiter as the
  llama.cpp/RTX 3090 host (jarvis user, ssh key), drop stale Ollama/AMD
  vars and ollama_server inventory group superseded by the ATX rebuild.
2026-08-05 09:43:54 -05:00

152 lines
4.9 KiB
YAML

---
# ------------------------------------------------------------------------------
# FILE: roles/llm-inference/tasks/serve.yml
# DESCRIPTION: Phase 5 — llama-server (llama.cpp) serving Gemma 2 27B-it GGUF.
#
# WHY llama.cpp instead of vLLM:
# vLLM with bitsandbytes int4 quantizes on-the-fly — loads full bf16 weights
# (~54GB RAM peak) before compressing, killing the 40GB OptiPlex on warmup.
# llama.cpp loads the pre-quantized GGUF directly (~15.5GB peak RAM for Q4_K_M).
# No torch.compile, no warmup spike, OpenAI-compatible API on the same port.
#
# GGUF source: bartowski/gemma-2-27b-it-GGUF (Q4_K_M, 15.5GB)
# Model downloaded to: {{ llm_gguf_path }}
# ------------------------------------------------------------------------------
- name: Install llama.cpp build dependencies
ansible.builtin.apt:
name:
- cmake
- build-essential
- libcurl4-openssl-dev
state: present
update_cache: false
# NOTE: nvidia-driver-595-open provides the runtime driver only (nvidia-smi,
# libcuda.so) — it does NOT ship nvcc/CUDA headers needed to build GGML_CUDA=ON.
# Ubuntu 24.04's nvidia-cuda-toolkit (12.0.x) is sufficient to build llama.cpp
# against; it does not need to match the 595 driver's CUDA 13.2 runtime version.
- name: Install NVIDIA CUDA toolkit (nvcc) for building llama.cpp with CUDA support
ansible.builtin.apt:
name: nvidia-cuda-toolkit
state: present
update_cache: false
become: true
- name: Clone llama.cpp repository
ansible.builtin.git:
repo: https://github.com/ggml-org/llama.cpp.git
dest: /opt/llama.cpp
depth: 1
update: false
become: true
- name: Check for incomplete/stale llama.cpp CMake configuration
ansible.builtin.stat:
path: /opt/llama.cpp/build/Makefile
register: llama_cmake_generated
- name: Remove stale llama.cpp build dir if CMake configure never completed
ansible.builtin.file:
path: /opt/llama.cpp/build
state: absent
become: true
when:
- not llama_cmake_generated.stat.exists
- not (ansible_check_mode | default(false))
- name: Build llama.cpp with CUDA support
ansible.builtin.command:
cmd: cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release
chdir: /opt/llama.cpp
creates: /opt/llama.cpp/build/CMakeCache.txt
become: true
- name: Compile llama.cpp (parallel build)
ansible.builtin.command:
cmd: cmake --build build --config Release --parallel {{ ansible_processor_vcpus }}
chdir: /opt/llama.cpp
creates: /opt/llama.cpp/build/bin/llama-server
become: true
timeout: 600
- name: Create GGUF model directory
ansible.builtin.file:
path: "{{ llm_gguf_dir }}"
state: directory
owner: "{{ llm_venv_owner }}"
group: "{{ llm_venv_owner }}"
mode: "0755"
- name: Check whether GGUF already exists (avoid re-downloading 16.6GB on every run)
ansible.builtin.stat:
path: "{{ llm_gguf_path }}"
register: llm_gguf_stat
- name: Download Gemma 2 27B Q4_K_M GGUF from HuggingFace
ansible.builtin.get_url:
url: "https://huggingface.co/bartowski/gemma-2-27b-it-GGUF/resolve/main/gemma-2-27b-it-Q4_K_M.gguf"
dest: "{{ llm_gguf_path }}"
headers:
Authorization: "Bearer {{ vault_hf_token }}"
owner: "{{ llm_venv_owner }}"
group: "{{ llm_venv_owner }}"
mode: "0644"
timeout: 7200
force: false
become: true
no_log: true
# Idempotency: skip entirely once the file exists and is reasonably sized
# (the finished GGUF is ~16.6GB; guard against a truncated partial download
# being mistaken for complete by only trusting files > 15GB).
when: not llm_gguf_stat.stat.exists or (llm_gguf_stat.stat.size | int) < 15000000000
- name: Disable and stop vllm-serve if present
ansible.builtin.systemd:
name: vllm-serve
state: stopped
enabled: false
failed_when: false
notify: reload systemd
- name: Deploy llama-server systemd service unit
ansible.builtin.template:
src: llama-server.service.j2
dest: /etc/systemd/system/llama-server.service
owner: root
group: root
mode: "0644"
notify:
- reload systemd
- restart llama-server
- name: Flush handlers to reload systemd before enabling service
ansible.builtin.meta: flush_handlers
- name: Enable and start llama-server
ansible.builtin.systemd:
name: llama-server
state: started
enabled: true
daemon_reload: true
- name: Wait for llama-server API to become available (model load ~30s)
ansible.builtin.uri:
url: "http://localhost:{{ llm_serve_port }}/health"
status_code: 200
register: llama_health
retries: 18
delay: 10
until: llama_health.status == 200
- name: Smoke-test — list available models
ansible.builtin.uri:
url: "http://localhost:{{ llm_serve_port }}/v1/models"
status_code: 200
return_content: true
register: llama_models
- name: Print available models
ansible.builtin.debug:
msg: "llama-server serving: {{ llama_models.json.data | map(attribute='id') | list }}"