Skip to content

Commit ff6b225

Browse files
CI: ccache the SD CUDA leg (#9)
* CI: ccache the SD CUDA leg This leg rebuilt every object on every run. It took 3903 s of the 4121 s job on 2026-08-09 and 4942 s on the run before it, with no speedup between the two, while every other job in the pipeline finished in under 8 minutes. The repo held no cache entry for it at all, only the ROCm ones build.yml writes. Key on the CUDA version and the architecture list, since both decide the objects, and set the CUDA compiler launcher as well as C and CXX: nvcc is nearly the whole build and Jimver installs it outside the default search. Save on always() so a failed or capped job keeps what it compiled. * CI: do not fail the CUDA build on a ccache stats call
1 parent 13b9d92 commit ff6b225

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

.github/workflows/unsloth-sd-prebuilt.yml

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,11 @@ jobs:
349349
continue-on-error: true
350350
if: ${{ needs.resolve.outputs.exists != 'true' || github.event_name == 'workflow_dispatch' }}
351351
runs-on: ubuntu-22.04
352+
# One source of truth for both: the ccache key has to track whatever changes
353+
# the objects, and these two do.
354+
env:
355+
CUDA_VERSION: "12.8.1"
356+
CUDA_ARCHS: "75;80;86;89;90;100;120"
352357
steps:
353358
- name: Checkout mirror (tooling)
354359
uses: actions/checkout@v4
@@ -379,7 +384,7 @@ jobs:
379384
id: cuda-toolkit
380385
uses: Jimver/cuda-toolkit@v0.2.22
381386
with:
382-
cuda: "12.8.1"
387+
cuda: ${{ env.CUDA_VERSION }}
383388
method: "network"
384389
# sub-packages are installed as cuda-<name>-12-8. cuBLAS is not under that
385390
# prefix (it ships as libcublas / libcublas-dev), so it has to go in the
@@ -388,22 +393,59 @@ jobs:
388393
sub-packages: '["nvcc", "cudart", "cudart-dev", "thrust"]'
389394
non-cuda-sub-packages: '["libcublas", "libcublas-dev"]'
390395

396+
# This leg rebuilt every object on every run: 3903 s of the 4121 s job on
397+
# 2026-08-09, and 4942 s on the run before it, with no speedup between the
398+
# two. The repo held no cache entry for it at all, only build.yml's ROCm
399+
# ones.
400+
#
401+
# The key carries the CUDA version and the architecture list because both
402+
# decide the objects. ccache hashes compiler identity into every entry, so
403+
# a cache written by another toolkit can never hit -- a version-less key
404+
# is what held the llama.cpp ROCm legs at a 0% hit rate and made them that
405+
# pipeline's critical path (llama.cpp#88). restore-keys lets a new tag
406+
# start from the previous generation instead of from nothing.
407+
- name: ccache key
408+
id: cckey
409+
run: echo "archs=$(echo "$CUDA_ARCHS" | tr ';' '-')" >> "$GITHUB_OUTPUT"
410+
411+
- name: ccache
412+
uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23
413+
with:
414+
key: sd-cuda-${{ env.CUDA_VERSION }}-${{ steps.cckey.outputs.archs }}-${{ needs.resolve.outputs.tag }}
415+
restore-keys: |
416+
sd-cuda-${{ env.CUDA_VERSION }}-${{ steps.cckey.outputs.archs }}
417+
append-timestamp: false
418+
variant: ccache
419+
max-size: 2G
420+
# Saved by the explicit step below instead, so a failed build still
421+
# keeps what it compiled.
422+
save: false
423+
391424
- name: Build sd-cli + sd-server (CUDA)
392425
working-directory: src
393426
run: |
394427
set -euo pipefail
395428
# Turing through Blackwell. 12.8 is the first toolkit that can emit sm_100 (B200) and
396429
# sm_120 (RTX 50), and anything older than Turing is not a realistic host for a 20 GB
397430
# video denoiser. build.yml's Windows leg uses the same list plus 61 and 70.
431+
#
432+
# The CUDA launcher matters more than the other two here: Jimver installs nvcc outside
433+
# the default search, and nvcc is nearly the whole build, so caching only C/CXX would
434+
# leave the expensive half uncached.
398435
cmake -B build \
399436
-DCMAKE_BUILD_TYPE=Release \
400437
-DSD_BUILD_EXAMPLES=ON \
401438
-DSD_SERVER_BUILD_FRONTEND=OFF \
402439
-DSD_WEBP=OFF -DSD_WEBM=OFF \
403440
-DGGML_NATIVE=OFF \
404441
-DSD_CUDA=ON \
405-
-DCMAKE_CUDA_ARCHITECTURES='75;80;86;89;90;100;120'
442+
-DCMAKE_CUDA_ARCHITECTURES="$CUDA_ARCHS" \
443+
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
444+
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
445+
-DCMAKE_CUDA_COMPILER_LAUNCHER=ccache
406446
cmake --build build --config Release -j "$(nproc)" --target sd-cli sd-server
447+
# Diagnostic only, under set -e: never fail a good build over stats.
448+
ccache --show-stats || true
407449
408450
- name: Bundle the CUDA runtime beside the binaries
409451
run: |
@@ -444,6 +486,28 @@ jobs:
444486
path: dist/sd-${{ needs.resolve.outputs.tag }}-bin-Linux-Ubuntu-22.04-x86_64-cuda12.zip
445487
if-no-files-found: error
446488

489+
- name: Evict stale ccache files
490+
# !cancelled(), unlike the save below: a cancelled job gets one short
491+
# teardown window that is not replenished, and the save is what needs
492+
# it.
493+
if: ${{ !cancelled() }}
494+
continue-on-error: true
495+
run: ccache --evict-older-than 14d
496+
497+
- name: Save ccache
498+
# Save even when the build failed. The objects compiled before the
499+
# failure still count, and this leg is continue-on-error, so a failure
500+
# here is routine. always(), not !cancelled(): a job killed by the cap
501+
# takes the cancellation path, and that is the most expensive case to
502+
# lose (llama.cpp#81).
503+
if: ${{ always() }}
504+
uses: actions/cache/save@v6
505+
with:
506+
path: ${{ github.workspace }}/.ccache
507+
# Trailing dash keeps this distinct from the key the ccache action
508+
# restored from, so the save is never a no-op against itself.
509+
key: ccache-sd-cuda-${{ env.CUDA_VERSION }}-${{ steps.cckey.outputs.archs }}-${{ needs.resolve.outputs.tag }}-
510+
447511
build-windows:
448512
name: win-cpu-x64
449513
needs: resolve

0 commit comments

Comments
 (0)