diff --git a/ansible/playbooks/day3_deploy_qwen38_ctx131k.yml b/ansible/playbooks/day3_deploy_qwen38_ctx131k.yml new file mode 100644 index 0000000..9bf40c5 --- /dev/null +++ b/ansible/playbooks/day3_deploy_qwen38_ctx131k.yml @@ -0,0 +1,22 @@ +--- +# Playbook: day3_deploy_qwen38_ctx131k.yml +# Purpose: Deploy Qwen3.8-27B-Q4_K_M ctx-size 65536 -> 131072 to astro-orbiter +# via llama-swap config re-render + restart. +# +# The git change to defaults/main.yml (line 235: ctx_size: 131072) is already staged. +# This playbook renders /etc/llama-swap/config.yaml from the updated defaults +# and restarts llama-swap to load the new ctx-size. +# +# Run: +# cd /home/hermes/git/homelab/ansible +# ansible-playbook -i inventory.yml playbooks/day3_deploy_qwen38_ctx131k.yml +# +- name: Deploy Qwen3.8 ctx-size 131072 to astro-orbiter + hosts: astro-orbiter + become: true + vars: + llm_swapmode_enabled: true + + roles: + - role: llm-inference-multimodel + tags: [swapmode_config, swapmode_systemd, swapmode_verify] diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml index 93bab39..d650579 100644 --- a/ansible/roles/common/defaults/main.yml +++ b/ansible/roles/common/defaults/main.yml @@ -22,6 +22,7 @@ common_packages: - net-tools - dnsutils - lvm2 + - cloud-guest-utils # ------------------------------------------------------------------------------ # LVM root volume expansion diff --git a/ansible/roles/deploy-vllm/templates/vllm.service.j2 b/ansible/roles/deploy-vllm/templates/vllm.service.j2 index 8fdc36e..fef298d 100644 --- a/ansible/roles/deploy-vllm/templates/vllm.service.j2 +++ b/ansible/roles/deploy-vllm/templates/vllm.service.j2 @@ -44,6 +44,15 @@ ExecStart={{ vllm_venv_path }}/bin/python -m vllm.entrypoints.openai.api_server {% if item.enforce_eager is defined and item.enforce_eager %} --enforce-eager \ {% endif %} +{% if item.enable_auto_tool_choice is defined and item.enable_auto_tool_choice %} + --enable-auto-tool-choice \ +{% endif %} +{% if item.tool_call_parser is defined %} + --tool-call-parser {{ item.tool_call_parser }} \ +{% endif %} +{% if item.reasoning_parser is defined %} + --reasoning-parser {{ item.reasoning_parser }} \ +{% endif %} {% if item.quantization is defined and item.quantization != 'none' %} --quantization {{ item.quantization }} \ {% endif %} diff --git a/ansible/roles/expand_root_lv/README.md b/ansible/roles/expand_root_lv/README.md index 66c2bd6..ace34b1 100644 --- a/ansible/roles/expand_root_lv/README.md +++ b/ansible/roles/expand_root_lv/README.md @@ -1,7 +1,8 @@ # expand-root-lv role -Idempotent role that extends the root LVM logical volume to fill its -volume group and grows the underlying filesystem (ext4 or xfs). +Idempotent role that grows the root partition (via `growpart`), extends the +root LVM logical volume to fill its volume group, and grows the underlying +filesystem (ext4 or xfs). ## Where this runs in the lifecycle @@ -24,6 +25,22 @@ size — a longstanding installer default that surprises every operator who hasn't been bitten by it before. ~90% of mk-labs VMs need this fix-up before they're fully useful. +After a Proxmox disk grow (increasing the VM disk size), the partition +table, physical volume, logical volume, and filesystem all need to be +extended in sequence. This role automates the full chain. + +## Workflow + +1. **growpart** — resizes the underlying partition to claim the newly + provisioned disk space. Idempotent: no-op when the partition already + fills the disk. +2. **pvresize** — tells the kernel/LVM about the new partition size so + the VG sees the additional free PEs. +3. **lvextend** — extends the LV to claim all free PE in the VG + (`+100%FREE`). No-op when there's nothing to grow. +4. **fs grow** — `resize2fs` (ext4) or `xfs_growfs` (xfs), dispatched by + detected filesystem type. + ## Idempotency - If `vg_free_count == 0`, the `lvextend` step is skipped and the @@ -49,22 +66,30 @@ expand_root_lv_skip: true The day0 playbook checks this flag and skips the role cleanly. +To skip only the partition growstep while keeping LV/FS expansion +(e.g. when the partition already covers the whole disk but the LV was +provisioned small by the template), set: + +```yaml +expand_root_lv_pv_partition: undefined +``` + ## Defaults | Variable | Default | Purpose | |-------------------------------|---------------|-----------------------------------------------------| | `expand_root_lv_vg_name` | `ubuntu-vg` | LVM volume group name (Ubuntu installer default). | | `expand_root_lv_lv_name` | `ubuntu-lv` | LVM logical volume name (Ubuntu installer default). | +| `expand_root_lv_pv_partition` | `/dev/sda3` | Partition backing the PV; grown via growpart. | | `expand_root_lv_mountpoint` | `/` | Mountpoint of the filesystem to grow. | -Override the VG/LV names in `host_vars/.yml` for hosts that use a -different LVM layout. +Override the VG/LV/PV names in `host_vars/.yml` for hosts that use a +different layout. ## Limitations -- Does not extend the underlying partition. If the operator grows the - Proxmox disk and the partition itself needs to grow before lvextend - can claim the new space, run `growpart /dev/sda 3` (or equivalent) - first. A future enhancement could automate this via `cloud-utils`' - `growpart` package, but it's out of scope for the initial template - fix-up case where the partition already covers the whole disk. +- The `growpart` step requires the `cloud-guest-utils` package. The role + installs it automatically on Debian/Ubuntu hosts when + `expand_root_lv_pv_partition` is defined. +- Only supports ext4 and xfs filesystems. Other filesystem types (btrfs, + etc.) are left as a future enhancement. diff --git a/ansible/roles/expand_root_lv/defaults/main.yml b/ansible/roles/expand_root_lv/defaults/main.yml index 222a91d..2f106db 100644 --- a/ansible/roles/expand_root_lv/defaults/main.yml +++ b/ansible/roles/expand_root_lv/defaults/main.yml @@ -20,6 +20,12 @@ expand_root_lv_vg_name: ubuntu-vg expand_root_lv_lv_name: ubuntu-lv +# The partition that backs the physical volume. After a Proxmox disk grow, +# growpart must resize this partition before pvresize/lvextend can claim +# the new space. This is the full device path (e.g. /dev/sda3). +# If undefined, the growpart/pvresize steps are skipped. +expand_root_lv_pv_partition: /dev/sda3 + # Mount point we expect to be backed by the target LV. Used purely for # the resize2fs / xfs_growfs decision — the role inspects this path's # filesystem type and dispatches to the correct grow command. diff --git a/ansible/roles/expand_root_lv/tasks/main.yml b/ansible/roles/expand_root_lv/tasks/main.yml index 65d5858..505e13b 100644 --- a/ansible/roles/expand_root_lv/tasks/main.yml +++ b/ansible/roles/expand_root_lv/tasks/main.yml @@ -2,44 +2,108 @@ # ============================================================================ # expand-root-lv / main # ---------------------------------------------------------------------------- -# 1. Confirm the target VG exists (skip role cleanly on non-LVM hosts). -# 2. Read free physical-extent count for the VG. -# 3. Extend the LV to +100%FREE only when free_pe > 0. -# 4. Grow the filesystem on the mountpoint (ext4 -> resize2fs, xfs -> xfs_growfs). +# 0. Ensure growpart is available (cloud-guest-utils provides the growpart binary) +# 1. Grow the partition (growpart) if a PV partition device is defined +# 2. Resize the physical volume (pvresize) to pick up the new partition size +# 3. Confirm the target VG exists (skip role cleanly on non-LVM hosts). +# 4. Read free physical-extent count for the VG. +# 5. Extend the LV to +100%FREE only when free_pe > 0. +# 6. Grow the filesystem on the mountpoint (ext4 -> resize2fs, xfs -> xfs_growfs). # Each step is idempotent and skips when there's nothing to do. # ============================================================================ +# --------------------------------------------------------------------------- +# Step 0: Ensure growpart is available +# --------------------------------------------------------------------------- +- name: Ensure cloud-guest-utils (growpart) is installed + ansible.builtin.package: + name: cloud-guest-utils + state: present + when: expand_root_lv_pv_partition is defined + tags: [growpart, always] + +# --------------------------------------------------------------------------- +# Step 1: Grow the partition that backs the PV +# --------------------------------------------------------------------------- +# growpart expects: growpart +# e.g. growpart /dev/sda 3 — NOT growpart /dev/sda3 +# We split expand_root_lv_pv_partition (e.g. /dev/sda3) into device and part_no. + +- name: Derive device and partition number from PV partition path + ansible.builtin.set_fact: + expand_root_lv_pv_device: "{{ expand_root_lv_pv_partition | regex_replace('p?(\\d+)$', '') }}" + expand_root_lv_pv_part_no: "{{ expand_root_lv_pv_partition | regex_replace('.*p?(\\d+)$', '\\1') }}" + when: expand_root_lv_pv_partition is defined + tags: [growpart, always] + +- name: Grow partition to fill disk (growpart) + ansible.builtin.command: + cmd: "growpart {{ expand_root_lv_pv_device }} {{ expand_root_lv_pv_part_no }}" + register: growpart_result + when: expand_root_lv_pv_partition is defined + changed_when: growpart_result.rc == 0 and "NO CHANGE" not in growpart_result.stdout + tags: [growpart, always] + +# --------------------------------------------------------------------------- +# Step 2: Resize the physical volume to claim the new partition space +# --------------------------------------------------------------------------- +- name: Resize physical volume (pvresize) + ansible.builtin.command: + cmd: "pvresize {{ expand_root_lv_pv_partition }}" + register: pvresize_result + when: expand_root_lv_pv_partition is defined + changed_when: pvresize_result.rc == 0 + tags: [pvresize, always] + +# --------------------------------------------------------------------------- +# Step 3: Confirm the target VG exists (skip role cleanly on non-LVM hosts). +# --------------------------------------------------------------------------- - name: Gather LVM facts ansible.builtin.command: cmd: "vgs --noheadings --nosuffix --units b -o vg_name,vg_free_count {{ expand_root_lv_vg_name }}" register: vg_info changed_when: false failed_when: false + tags: [lvm, always] - name: Skip role when target VG is absent ansible.builtin.meta: end_play when: vg_info.rc != 0 + tags: [lvm, always] +# --------------------------------------------------------------------------- +# Step 4: Parse free PE count for the VG +# --------------------------------------------------------------------------- - name: Parse free PE count ansible.builtin.set_fact: expand_root_lv_free_pe: "{{ (vg_info.stdout.split() | last | int) if vg_info.stdout | length > 0 else 0 }}" + tags: [lvm, always] +# --------------------------------------------------------------------------- +# Step 5: Extend the LV to +100%FREE only when free_pe > 0 +# --------------------------------------------------------------------------- - name: Extend LV to fill VG (only if free PE > 0) ansible.builtin.command: cmd: "lvextend -l +100%FREE /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}" register: lvextend_result when: expand_root_lv_free_pe | int > 0 changed_when: lvextend_result.rc == 0 + tags: [lvm, always] +# --------------------------------------------------------------------------- +# Step 6: Detect filesystem type at mountpoint and grow +# --------------------------------------------------------------------------- - name: Detect filesystem type at mountpoint ansible.builtin.command: cmd: "findmnt {{ expand_root_lv_mountpoint }} -no FSTYPE" register: fstype_result changed_when: false + tags: [filesystem, always] - name: Set filesystem type fact ansible.builtin.set_fact: expand_root_lv_fstype: "{{ fstype_result.stdout | trim }}" + tags: [filesystem, always] - name: Grow ext4 filesystem ansible.builtin.command: @@ -47,8 +111,12 @@ register: resize_result when: - expand_root_lv_fstype == "ext4" - - lvextend_result.changed | default(false) + - (growpart_result is defined and growpart_result.changed) or + (pvresize_result is defined and pvresize_result.changed) or + (lvextend_result is defined and lvextend_result.changed) or + (growpart_result is not defined) changed_when: resize_result.rc == 0 + tags: [filesystem, always] - name: Grow xfs filesystem ansible.builtin.command: @@ -56,15 +124,24 @@ register: xfs_result when: - expand_root_lv_fstype == "xfs" - - lvextend_result.changed | default(false) + - (growpart_result is defined and growpart_result.changed) or + (pvresize_result is defined and pvresize_result.changed) or + (lvextend_result is defined and lvextend_result.changed) or + (growpart_result is not defined) changed_when: xfs_result.rc == 0 + tags: [filesystem, always] +# --------------------------------------------------------------------------- +# Step 7: Report current root size +# --------------------------------------------------------------------------- - name: Report current root size ansible.builtin.command: cmd: "df -h {{ expand_root_lv_mountpoint }}" register: df_result changed_when: false + tags: [always] - name: Show post-resize disk usage ansible.builtin.debug: msg: "{{ df_result.stdout_lines }}" + tags: [always] diff --git a/ansible/roles/llm-inference-multimodel/defaults/main.yml b/ansible/roles/llm-inference-multimodel/defaults/main.yml index 8244069..5324c37 100644 --- a/ansible/roles/llm-inference-multimodel/defaults/main.yml +++ b/ansible/roles/llm-inference-multimodel/defaults/main.yml @@ -232,7 +232,7 @@ llm_swapmode_models: gguf_path: "{{ llm_models_dir }}/Qwen3.8-27B-Q4_K_M.gguf" port: 8105 n_gpu_layers: -1 # -1 = auto-detect / all layers to GPU - ctx_size: 65536 + ctx_size: 131072 # t_ca3ff615: 65536 -> 131072 (VRAM pre-validated ~20.3GB < 24GB, t_441470b9) batch_size: 4096 ubatch_size: 4096 parallel: 1