ci(windows): cache Studio venv + llama.cpp prebuilt + frontend dist

Windows Studio install (install.ps1 --local --no-torch) is the
second-biggest cost on PR 5312 after the Recents-step fix:
  Windows Studio UI CI:     414s install (of 24m47s wallclock)
  Windows Studio Update:    414s install (of 9m28s)
  Windows Studio API:       379s install (of 7m48s)
  Windows Studio GGUF (x3): 353s..429s install

Of that 6-7 min, ~3.5 min is uv pip install of the studio venv,
~45s is npm ci + vite build of studio/frontend/dist, ~30s is the
llama.cpp prebuilt fetch+extract; ~90s is winget bringing system
tools in (Python, uv, Node, git, cmake, VS, bun) which sits at
the runner-image layer and isn't cacheable from a workflow.

Add three actions/cache@v4 entries before the install step in
each Windows workflow:

  - ~/.unsloth/studio/unsloth_studio  (the studio venv)
    keyed on hashFiles(pyproject.toml, studio/backend/requirements/**,
    install.ps1, studio/setup.ps1, studio/install_python_stack.py)

  - ~/.unsloth/llama.cpp              (the prebuilt llama.cpp tree)
    keyed on hashFiles(studio/install_llama_prebuilt.py)

  - studio/frontend/dist              (the vite build output)
    keyed on hashFiles(studio/frontend/package-lock.json,
    studio/frontend/src/**, studio/frontend/index.html,
    studio/frontend/vite.config.*, studio/frontend/tsconfig*.json,
    studio/frontend/components.json)

Security:
  * Cache keys are content-addressable hashes of every input file
    that meaningfully changes the produced artefact. A malicious
    PR that modifies any of those triggers a fresh build; the
    cache cannot mask a real dependency change.
  * GitHub Actions cache is branch-partitioned -- a PR cache
    cannot poison main's cache. Only a successful build on main
    can populate the main-branch cache.
  * No restore-keys: prefix-matched fallback would resurrect a
    venv whose lockfile no longer matches; uv pip install would
    then silently keep the old packages. We want all-or-nothing
    on lockfile hash.
  * The cache version salt (-v1-) lets us invalidate every entry
    immediately if a future advisory or build-system change
    requires it.

setup.ps1 already takes the "reusing existing virtual environment"
fast-path when ~/.unsloth/studio/unsloth_studio exists, and the
"prebuilt up to date and validated" fast-path when llama.cpp is
already laid down -- no setup.ps1 changes needed.

Estimated saving: ~5 min per Windows job, ~30 min compute per PR
when caches hit. First run on each lockfile change still pays the
full install cost (the cache-miss path is unchanged).
This commit is contained in:
Daniel Han 2026-05-08 07:28:07 +00:00
parent d179701ab8
commit d65f8b19d8
4 changed files with 145 additions and 0 deletions

View file

@ -78,6 +78,30 @@ jobs:
HF_HUB_ENABLE_HF_TRANSFER=1 \
hf download "$GGUF_REPO" "$GGUF_FILE"
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
shell: pwsh
env:

View file

@ -88,6 +88,30 @@ jobs:
HF_HUB_ENABLE_HF_TRANSFER=1 \
hf download "$GGUF_REPO" "$GGUF_FILE"
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
shell: pwsh
env:
@ -353,6 +377,30 @@ jobs:
HF_HUB_ENABLE_HF_TRANSFER=1 \
hf download "$GGUF_REPO" "$GGUF_FILE"
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
shell: pwsh
env:
@ -709,6 +757,30 @@ jobs:
HF_HUB_ENABLE_HF_TRANSFER=1 \
hf download "$GGUF_REPO" "$MMPROJ_FILE"
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
shell: pwsh
env:

View file

@ -87,6 +87,31 @@ jobs:
HF_HUB_ENABLE_HF_TRANSFER=1 \
hf download "$GGUF_REPO" "$GGUF_FILE"
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash on every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change. Cache scope is branch-partitioned by
# GitHub Actions, so a malicious PR can't poison main's cache.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
# install.ps1 is the supported Windows installer. install.sh
# has no Windows branch (apt-get / brew calls). The PS1

View file

@ -73,6 +73,30 @@ jobs:
# then fatal-errors with "Cache folder path is retrieved
# for pip but doesn't exist on disk".
# Caches around install.ps1 -- restore the heavy artefacts the
# installer would otherwise rebuild from scratch every run.
# Keys hash every input file that would meaningfully change
# the produced artefact, so a stale cache cannot mask a real
# dependency change.
- name: Cache Studio venv (~/.unsloth/studio/unsloth_studio)
id: cache-studio-venv
uses: actions/cache@v4
with:
path: ~/.unsloth/studio/unsloth_studio
key: ${{ runner.os }}-studio-venv-v1-${{ hashFiles('pyproject.toml', 'studio/backend/requirements/**', 'install.ps1', 'studio/setup.ps1', 'studio/install_python_stack.py') }}
- name: Cache prebuilt llama.cpp (~/.unsloth/llama.cpp)
id: cache-llama-prebuilt
uses: actions/cache@v4
with:
path: ~/.unsloth/llama.cpp
key: ${{ runner.os }}-llama-cpp-prebuilt-v1-${{ hashFiles('studio/install_llama_prebuilt.py') }}
- name: Cache frontend dist (studio/frontend/dist)
id: cache-frontend-dist
uses: actions/cache@v4
with:
path: studio/frontend/dist
key: ${{ runner.os }}-studio-frontend-dist-v1-${{ hashFiles('studio/frontend/package-lock.json', 'studio/frontend/src/**', 'studio/frontend/index.html', 'studio/frontend/vite.config.*', 'studio/frontend/tsconfig*.json', 'studio/frontend/components.json') }}
- name: Install Studio (--local, --no-torch)
shell: pwsh
env: