From d65f8b19d85ff729557fdefe9c6aaf3aa1e397d4 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Fri, 8 May 2026 07:28:07 +0000 Subject: [PATCH] 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). --- .../workflows/studio-windows-api-smoke.yml | 24 +++++++ .../studio-windows-inference-smoke.yml | 72 +++++++++++++++++++ .github/workflows/studio-windows-ui-smoke.yml | 25 +++++++ .../workflows/studio-windows-update-smoke.yml | 24 +++++++ 4 files changed, 145 insertions(+) diff --git a/.github/workflows/studio-windows-api-smoke.yml b/.github/workflows/studio-windows-api-smoke.yml index 0208b0f5d8..b166112c45 100644 --- a/.github/workflows/studio-windows-api-smoke.yml +++ b/.github/workflows/studio-windows-api-smoke.yml @@ -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: diff --git a/.github/workflows/studio-windows-inference-smoke.yml b/.github/workflows/studio-windows-inference-smoke.yml index 32a86d239d..454c21c750 100644 --- a/.github/workflows/studio-windows-inference-smoke.yml +++ b/.github/workflows/studio-windows-inference-smoke.yml @@ -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: diff --git a/.github/workflows/studio-windows-ui-smoke.yml b/.github/workflows/studio-windows-ui-smoke.yml index 2ceb99fa0c..03255de3b3 100644 --- a/.github/workflows/studio-windows-ui-smoke.yml +++ b/.github/workflows/studio-windows-ui-smoke.yml @@ -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 diff --git a/.github/workflows/studio-windows-update-smoke.yml b/.github/workflows/studio-windows-update-smoke.yml index 86957f2b36..e2bf771dd5 100644 --- a/.github/workflows/studio-windows-update-smoke.yml +++ b/.github/workflows/studio-windows-update-smoke.yml @@ -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: