@@ -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