# ------------------------------------------------------------------------------ # FILE: roles/deploy-vllm/tasks/models.yml # PHASE 2: Model downloads via huggingface-cli into {{ vllm_hf_hub_cache }}. # # Idempotency: HuggingFace's on-disk cache layout is # {cache}/models--{org}--{repo}/snapshots/{revision}/... # We stat for an existing snapshots dir before downloading — if present with # at least one entry, skip (huggingface-cli download is itself resumable/ # idempotent, but this avoids even the "check remote manifest" round trip on # every run and gives a clean "already staged" line in output). # # Pitfall (t_3dddf37d, homelab-llm-inference skill): a config/template landing # is NOT the same as the model being staged. Always verify via `ls`/`du` on # the actual host, never trust a prior task's claim alone. # ------------------------------------------------------------------------------ - name: Ensure model cache directory exists ansible.builtin.file: path: "{{ vllm_hf_hub_cache }}" state: directory owner: "{{ vllm_venv_owner }}" group: "{{ vllm_venv_owner }}" mode: "0755" become: true - name: Report models to be staged this run ansible.builtin.debug: msg: "{{ vllm_enabled_models | map(attribute='id') | list }}" - name: Check for existing snapshot dir per enabled model ansible.builtin.stat: path: "{{ vllm_hf_hub_cache }}/models--{{ item.hf_repo | regex_replace('/', '--') }}/snapshots" loop: "{{ vllm_enabled_models }}" loop_control: label: "{{ item.id }}" register: vllm_model_snapshot_stat - name: Download model repo(s) not yet staged ansible.builtin.command: cmd: >- {{ vllm_venv_path }}/bin/hf download {{ item.item.hf_repo }} --cache-dir {{ vllm_hf_hub_cache }} become: true become_user: "{{ vllm_venv_owner }}" environment: HF_HUB_ENABLE_HF_TRANSFER: "0" loop: "{{ vllm_model_snapshot_stat.results }}" loop_control: label: "{{ item.item.id }}" when: not (item.stat.exists | default(false)) or (item.stat.isdir | default(false) and item.stat.size == 0) register: vllm_model_download # Full-size model pulls (9-18GB for 32B AWQ) can take a long time on # homelab bandwidth — allow up to 1 hour per model. async: 3600 poll: 30 - name: Re-stat snapshot dirs to confirm download landed ansible.builtin.stat: path: "{{ vllm_hf_hub_cache }}/models--{{ item.hf_repo | regex_replace('/', '--') }}/snapshots" loop: "{{ vllm_enabled_models }}" loop_control: label: "{{ item.id }}" register: vllm_model_snapshot_verify - name: Fail if any enabled model failed to stage ansible.builtin.fail: msg: "Model {{ item.item.id }} ({{ item.item.hf_repo }}) is not present at {{ vllm_hf_hub_cache }} after download step." loop: "{{ vllm_model_snapshot_verify.results }}" loop_control: label: "{{ item.item.id }}" when: not (item.stat.exists | default(false)) - name: Compute on-disk size of each staged model (sanity check, not a strict checksum) ansible.builtin.command: cmd: "du -sh {{ vllm_hf_hub_cache }}/models--{{ item.hf_repo | regex_replace('/', '--') }}" loop: "{{ vllm_enabled_models }}" loop_control: label: "{{ item.id }}" register: vllm_model_size changed_when: false - name: Report staged model sizes ansible.builtin.debug: msg: "{{ item.stdout }}" loop: "{{ vllm_model_size.results }}" loop_control: label: "{{ item.item.id }}"