char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierci: Clean up UI builds from releases (#27706)4d19b2876

char/aliceai-t5-moe
25.3 KiB568 linesraw
1# This workflow uses actions that are not certified by GitHub.
2# They are provided by a third-party and are governed by
3# separate terms of service, privacy policy, and support
4# documentation.
5
6# GitHub recommends pinning actions to a commit SHA.
7# To get a newer version, you will need to update the SHA.
8# You can also reference a tag or branch, but the action may change without warning.
9
10name: Publish Docker image
11
12on:
13  workflow_dispatch: # allows manual triggering
14    inputs:
15      skip_s390x:
16        description: "Skip the s390x build target (useful for fast test runs that do not need the IBM Z runner)"
17        type: boolean
18        default: false
19  schedule:
20    # Rebuild daily rather than on every push because it is expensive
21    - cron: '12 4 * * *'
22
23concurrency:
24  group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
25  cancel-in-progress: true
26
27# Fine-grant permission
28# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
29permissions:
30  packages: write
31
32jobs:
33  create_tag:
34    name: Create and push git tag
35    runs-on: ubuntu-slim
36    permissions:
37      contents: write
38    outputs:
39      source_tag: ${{ steps.srctag.outputs.name }}
40
41    steps:
42      - name: Clone
43        id: checkout
44        uses: actions/checkout@v6
45        with:
46          fetch-depth: 0
47          ssh-key: ${{ secrets.DEPLOY_KEY_RELEASE }}
48
49      - name: Determine source tag name
50        id: srctag
51        uses: ./.github/actions/get-tag-name
52        env:
53          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
54
55      - name: Create and push git tag
56        env:
57          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58        run: |
59          git tag ${{ steps.srctag.outputs.name }} || exit 0
60          git push origin ${{ steps.srctag.outputs.name }} || exit 0
61
62  build_ui:
63    name: Build UI
64    needs: create_tag
65    uses: ./.github/workflows/ui-build.yml
66    with:
67      ui_version: ${{ needs.create_tag.outputs.source_tag }}
68
69  prepare_matrices:
70    name: Prepare Docker matrices
71    runs-on: ubuntu-24.04
72    outputs:
73      build_matrix: ${{ steps.matrices.outputs.build_matrix }}
74      merge_matrix: ${{ steps.matrices.outputs.merge_matrix }}
75
76    steps:
77      - name: Generate build and merge matrices
78        id: matrices
79        shell: bash
80        env:
81          SKIP_S390X: ${{ inputs.skip_s390x || 'false' }}
82        run: |
83          set -euo pipefail
84
85          # Keep all build targets in one place and derive merge targets from it.
86          cat > build-matrix.json <<'JSON'
87          [
88            { "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" },
89            { "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-arm" },
90            { "tag": "cpu", "dockerfile": ".devops/s390x.Dockerfile", "platforms": "linux/s390x", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-s390x", "prebuilt_ui": true },
91            { "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
92            { "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04-arm" },
93            { "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.3.0", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
94            { "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.3.0", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04-arm" },
95            { "tag": "musa", "dockerfile": ".devops/musa.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
96            { "tag": "intel", "dockerfile": ".devops/intel.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
97            { "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" },
98            { "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-arm" },
99            { "tag": "rocm", "dockerfile": ".devops/rocm.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
100            { "tag": "openvino", "dockerfile": ".devops/openvino.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" }
101          ]
102          JSON
103
104          if [ "${SKIP_S390X}" = "true" ]; then
105            jq 'map(select(.platforms != "linux/s390x"))' build-matrix.json > build-matrix.json.tmp
106            mv build-matrix.json.tmp build-matrix.json
107          fi
108
109          BUILD_MATRIX="$(jq -c . build-matrix.json)"
110          MERGE_MATRIX="$(jq -c '
111            reduce .[] as $entry ({}; .[$entry.tag] |= (
112              . // {
113                tag: $entry.tag,
114                arches: [],
115                full: false,
116                light: false,
117                server: false
118              }
119              | .full = (.full or ($entry.full // false))
120              | .light = (.light or ($entry.light // false))
121              | .server = (.server or ($entry.server // false))
122              | .arches += [($entry.platforms | sub("^linux/"; ""))]
123            ))
124            # Backward compatibility: s390x tags are aliases of cpu for the linux/s390x platform.
125            | if (has("cpu") and (((.cpu.arches // []) | index("s390x")) != null)) then
126                . + {
127                  s390x: {
128                    tag: "s390x",
129                    arches: ["s390x"],
130                    full: .cpu.full,
131                    light: .cpu.light,
132                    server: .cpu.server
133                  }
134                }
135              else
136                .
137              end
138            | [.[] | .arches = (.arches | unique | sort | join(" "))]
139          ' build-matrix.json)"
140
141          echo "build_matrix=$BUILD_MATRIX" >> "$GITHUB_OUTPUT"
142          echo "merge_matrix=$MERGE_MATRIX" >> "$GITHUB_OUTPUT"
143
144  push_to_registry:
145    name: Push Docker image to Docker Registry
146    needs: [prepare_matrices, create_tag, build_ui]
147
148    runs-on: ${{ matrix.config.runs_on }}
149    strategy:
150      fail-fast: false
151      matrix:
152        config: ${{ fromJSON(needs.prepare_matrices.outputs.build_matrix) }}
153    steps:
154      - name: Check out the repo
155        id: checkout
156        uses: actions/checkout@v6
157        with:
158          fetch-depth: 0
159          ref: ${{ needs.create_tag.outputs.source_tag }}
160
161      - name: Download prebuilt UI
162        if: ${{ matrix.config.prebuilt_ui == true }}
163        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
164        with:
165          name: llama-ui.zip
166          path: tools/ui/dist
167
168      - name: Set up QEMU
169        if: ${{ contains(matrix.config.platforms, 'linux/amd64') }}
170        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
171        with:
172          image: tonistiigi/binfmt:qemu-v10.2.1
173
174      - name: Set up Docker Buildx
175        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
176
177      - name: Log in to Docker Registry
178        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
179        with:
180          registry: ghcr.io
181          username: ${{ github.repository_owner }}
182          password: ${{ secrets.GITHUB_TOKEN }}
183
184      - name: Determine image metadata
185        id: meta
186        shell: bash
187        run: |
188          set -euo pipefail
189
190          REPO_OWNER="${GITHUB_REPOSITORY_OWNER@L}"  # to lower case
191          REPO_NAME="${{ github.event.repository.name }}"
192          IMAGE_REPO="ghcr.io/${REPO_OWNER}/${REPO_NAME}"
193          PREFIX="${IMAGE_REPO}:"
194          PLATFORM="${{ matrix.config.platforms }}"
195          ARCH_SUFFIX="${PLATFORM#linux/}"
196
197          # list all tags possible
198          tags="${{ matrix.config.tag }}"
199          for tag in $tags; do
200              if [[ "$tag" == "cpu" ]]; then
201                  TYPE=""
202              else
203                  TYPE="-$tag"
204              fi
205              CACHETAG="${PREFIX}buildcache${TYPE}-${ARCH_SUFFIX}"
206          done
207
208          SAFE_TAGS="$(echo "$tags" | tr ' ' '_')"
209
210          echo "image_repo=$IMAGE_REPO" >> $GITHUB_OUTPUT
211          echo "arch_suffix=$ARCH_SUFFIX" >> $GITHUB_OUTPUT
212          echo "cache_output_tag=$CACHETAG" >> $GITHUB_OUTPUT
213          echo "digest_artifact_suffix=${SAFE_TAGS}-${ARCH_SUFFIX}" >> $GITHUB_OUTPUT
214          echo "cache_output_tag=$CACHETAG"  # print out for debugging
215        env:
216          GITHUB_REPOSITORY_OWNER: '${{ github.repository_owner }}'
217
218      - name: Get build date
219        id: build_date
220        run: echo "date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
221
222      - name: Free Disk Space (Ubuntu)
223        if: ${{ matrix.config.free_disk_space == true }}
224        uses: ggml-org/free-disk-space@v1.3.1
225        with:
226          # this might remove tools that are actually needed,
227          # if set to "true" but frees about 6 GB
228          tool-cache: false
229
230          # all of these default to true, but feel free to set to
231          # "false" if necessary for your workflow
232          android: true
233          dotnet: true
234          haskell: true
235          large-packages: true
236          docker-images: true
237          swap-storage: true
238
239      - name: Build and push Full Docker image by digest
240        id: build_full
241        if: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.config.full == true }}
242        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
243        with:
244          context: .
245          platforms: ${{ matrix.config.platforms }}
246          outputs: type=image,name=${{ steps.meta.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
247          file: ${{ matrix.config.dockerfile }}
248          target: full
249          provenance: false
250          build-args: |
251            BUILD_DATE=${{ steps.build_date.outputs.date }}
252            APP_VERSION=${{ needs.create_tag.outputs.source_tag }}
253            APP_REVISION=${{ steps.checkout.outputs.commit }}
254            IMAGE_URL=${{ github.server_url }}/${{ github.repository }}
255            IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}
256            ${{ matrix.config.ubuntu_version && format('UBUNTU_VERSION={0}', matrix.config.ubuntu_version) || '' }}
257            ${{ matrix.config.cuda_version && format('CUDA_VERSION={0}', matrix.config.cuda_version) || '' }}
258          annotations: |
259            manifest:org.opencontainers.image.created=${{ steps.build_date.outputs.date }}
260            manifest:org.opencontainers.image.version=${{ needs.create_tag.outputs.source_tag }}
261            manifest:org.opencontainers.image.revision=${{ steps.checkout.outputs.commit }}
262            manifest:org.opencontainers.image.title=llama.cpp
263            manifest:org.opencontainers.image.description=LLM inference in C/C++
264            manifest:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
265            manifest:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
266          # using github experimental cache
267          #cache-from: type=gha
268          #cache-to: type=gha,mode=max
269          # return to this if the experimental github cache is having issues
270          #cache-to: type=local,dest=/tmp/.buildx-cache
271          #cache-from: type=local,src=/tmp/.buildx-cache
272          # using registry cache (no storage limit)
273          cache-from: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }}
274          cache-to: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }},mode=max
275
276      - name: Build and push Light Docker image by digest
277        id: build_light
278        if: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.config.light == true }}
279        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
280        with:
281          context: .
282          platforms: ${{ matrix.config.platforms }}
283          outputs: type=image,name=${{ steps.meta.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
284          file: ${{ matrix.config.dockerfile }}
285          target: light
286          provenance: false
287          build-args: |
288            BUILD_DATE=${{ steps.build_date.outputs.date }}
289            APP_VERSION=${{ needs.create_tag.outputs.source_tag }}
290            APP_REVISION=${{ steps.checkout.outputs.commit }}
291            IMAGE_URL=${{ github.server_url }}/${{ github.repository }}
292            IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}
293            ${{ matrix.config.ubuntu_version && format('UBUNTU_VERSION={0}', matrix.config.ubuntu_version) || '' }}
294            ${{ matrix.config.cuda_version && format('CUDA_VERSION={0}', matrix.config.cuda_version) || '' }}
295          annotations: |
296            manifest:org.opencontainers.image.created=${{ steps.build_date.outputs.date }}
297            manifest:org.opencontainers.image.version=${{ needs.create_tag.outputs.source_tag }}
298            manifest:org.opencontainers.image.revision=${{ steps.checkout.outputs.commit }}
299            manifest:org.opencontainers.image.title=llama.cpp
300            manifest:org.opencontainers.image.description=LLM inference in C/C++
301            manifest:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
302            manifest:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
303          # using github experimental cache
304          #cache-from: type=gha
305          #cache-to: type=gha,mode=max
306          # return to this if the experimental github cache is having issues
307          #cache-to: type=local,dest=/tmp/.buildx-cache
308          #cache-from: type=local,src=/tmp/.buildx-cache
309          # using registry cache (no storage limit)
310          cache-from: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }}
311          cache-to: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }},mode=max
312
313      - name: Build and push Server Docker image by digest
314        id: build_server
315        if: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.config.server == true }}
316        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
317        with:
318          context: .
319          platforms: ${{ matrix.config.platforms }}
320          outputs: type=image,name=${{ steps.meta.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
321          file: ${{ matrix.config.dockerfile }}
322          target: server
323          provenance: false
324          build-args: |
325            BUILD_DATE=${{ steps.build_date.outputs.date }}
326            APP_VERSION=${{ needs.create_tag.outputs.source_tag }}
327            APP_REVISION=${{ steps.checkout.outputs.commit }}
328            IMAGE_URL=${{ github.server_url }}/${{ github.repository }}
329            IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}
330            ${{ matrix.config.ubuntu_version && format('UBUNTU_VERSION={0}', matrix.config.ubuntu_version) || '' }}
331            ${{ matrix.config.cuda_version && format('CUDA_VERSION={0}', matrix.config.cuda_version) || '' }}
332          annotations: |
333            manifest:org.opencontainers.image.created=${{ steps.build_date.outputs.date }}
334            manifest:org.opencontainers.image.version=${{ needs.create_tag.outputs.source_tag }}
335            manifest:org.opencontainers.image.revision=${{ steps.checkout.outputs.commit }}
336            manifest:org.opencontainers.image.title=llama.cpp
337            manifest:org.opencontainers.image.description=LLM inference in C/C++
338            manifest:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
339            manifest:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
340          # using github experimental cache
341          #cache-from: type=gha
342          #cache-to: type=gha,mode=max
343          # return to this if the experimental github cache is having issues
344          #cache-to: type=local,dest=/tmp/.buildx-cache
345          #cache-from: type=local,src=/tmp/.buildx-cache
346          # using registry cache (no storage limit)
347          cache-from: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }}
348          cache-to: type=registry,ref=${{ steps.meta.outputs.cache_output_tag }},mode=max
349
350      - name: Export digest metadata
351        shell: bash
352        run: |
353            set -euo pipefail
354
355            TAGS="${{ matrix.config.tag }}"
356            ARCH_SUFFIX="${{ steps.meta.outputs.arch_suffix }}"
357            DIGEST_FILE="/tmp/digests/${{ steps.meta.outputs.digest_artifact_suffix }}.tsv"
358            mkdir -p /tmp/digests
359
360            add_digest_rows() {
361                local image_type="$1"
362                local digest="$2"
363
364                if [[ -z "$digest" ]]; then
365                  echo "Missing digest for image_type=${image_type}" >&2
366                  exit 1
367                fi
368
369                for tag in $TAGS; do
370                    printf '%s\t%s\t%s\t%s\n' "$tag" "$ARCH_SUFFIX" "$image_type" "$digest" >> "$DIGEST_FILE"
371                done
372            }
373
374            if [[ "${{ matrix.config.full }}" == "true" ]]; then
375                add_digest_rows "full" "${{ steps.build_full.outputs.digest }}"
376            fi
377
378            if [[ "${{ matrix.config.light }}" == "true" ]]; then
379                add_digest_rows "light" "${{ steps.build_light.outputs.digest }}"
380            fi
381
382            if [[ "${{ matrix.config.server }}" == "true" ]]; then
383                add_digest_rows "server" "${{ steps.build_server.outputs.digest }}"
384            fi
385
386      - name: Upload digest metadata
387        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
388        with:
389          name: digests-${{ steps.meta.outputs.digest_artifact_suffix }}
390          path: /tmp/digests/${{ steps.meta.outputs.digest_artifact_suffix }}.tsv
391          if-no-files-found: error
392
393  merge_arch_tags:
394    name: Create shared tags from digests
395    needs: [prepare_matrices, push_to_registry, create_tag]
396    runs-on: ubuntu-24.04
397    permissions:
398      contents: read
399      packages: write
400      id-token: write
401      attestations: write
402    strategy:
403      fail-fast: false
404      matrix:
405        config: ${{ fromJSON(needs.prepare_matrices.outputs.merge_matrix) }}
406
407    steps:
408      - name: Check out the repo
409        id: checkout
410        uses: actions/checkout@v6
411        with:
412          fetch-depth: 0
413
414      - name: Get build date
415        id: build_date
416        run: echo "date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
417
418      - name: Download digest metadata
419        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
420        with:
421          pattern: digests-*
422          path: /tmp/digests
423          merge-multiple: true
424
425      - name: Set up Docker Buildx
426        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
427
428      - name: Log in to Docker Registry
429        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
430        with:
431          registry: ghcr.io
432          username: ${{ github.repository_owner }}
433          password: ${{ secrets.GITHUB_TOKEN }}
434
435      - name: Create tags from digests
436        id: create_tags
437        shell: bash
438        run: |
439          set -euo pipefail
440
441          REPO_OWNER="${GITHUB_REPOSITORY_OWNER@L}"  # to lower case
442          REPO_NAME="${{ github.event.repository.name }}"
443          IMAGE_REPO="ghcr.io/${REPO_OWNER}/${REPO_NAME}"
444          PREFIX="${IMAGE_REPO}:"
445          SRC_TAG="${{ needs.create_tag.outputs.source_tag }}"
446          BUILD_DATE="${{ steps.build_date.outputs.date }}"
447          COMMIT_SHA="${{ steps.checkout.outputs.commit }}"
448          echo "image_repo=${IMAGE_REPO}" >> "$GITHUB_OUTPUT"
449          TAGS="${{ matrix.config.tag }}"
450          ARCHES="${{ matrix.config.arches }}"
451          DIGEST_GLOB="/tmp/digests/*.tsv"
452
453          if ! ls ${DIGEST_GLOB} >/dev/null 2>&1; then
454              echo "No digest metadata found in /tmp/digests" >&2
455              exit 1
456          fi
457
458          if [[ -z "$SRC_TAG" ]]; then
459              echo "Missing source tag from create_tag" >&2
460              exit 1
461          fi
462
463          find_digest() {
464              local tag_name="$1"
465              local arch="$2"
466              local image_type="$3"
467              local digest
468
469              digest="$(awk -F '\t' -v t="$tag_name" -v a="$arch" -v i="$image_type" '$1 == t && $2 == a && $3 == i { print $4; exit }' ${DIGEST_GLOB})"
470
471              # Backward compatibility: s390x tags are aliases of cpu for the linux/s390x platform.
472              if [[ -z "$digest" && "$tag_name" == "s390x" && "$arch" == "s390x" ]]; then
473                digest="$(awk -F '\t' -v t="cpu" -v a="$arch" -v i="$image_type" '$1 == t && $2 == a && $3 == i { print $4; exit }' ${DIGEST_GLOB})"
474              fi
475
476              if [[ -z "$digest" ]]; then
477                echo "Missing digest for tag=${tag_name} arch=${arch} image_type=${image_type}" >&2
478                exit 1
479              fi
480
481              echo "$digest"
482          }
483
484          create_manifest_tags() {
485              local image_type="$1"
486              local tag_name="$2"
487              local suffix="$3"
488
489              local merged_tag="${PREFIX}${image_type}${suffix}"
490              local merged_versioned_tag="${merged_tag}-${SRC_TAG}"
491
492              local refs=()
493
494              for arch in $ARCHES; do
495                  local digest
496                  digest="$(find_digest "$tag_name" "$arch" "$image_type")"
497                  refs+=("${IMAGE_REPO}@${digest}")
498              done
499
500              local annotations=(
501                  --annotation "index:org.opencontainers.image.created=${BUILD_DATE}"
502                  --annotation "index:org.opencontainers.image.version=${SRC_TAG}"
503                  --annotation "index:org.opencontainers.image.revision=${COMMIT_SHA}"
504                  --annotation "index:org.opencontainers.image.title=llama.cpp"
505                  --annotation "index:org.opencontainers.image.description=LLM inference in C/C++"
506                  --annotation "index:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}"
507                  --annotation "index:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}"
508              )
509
510              echo "Creating ${merged_tag} from ${refs[*]}"
511              docker buildx imagetools create "${annotations[@]}" --tag "${merged_tag}" "${refs[@]}"
512
513              echo "Creating ${merged_versioned_tag} from ${refs[*]}"
514              docker buildx imagetools create "${annotations[@]}" --tag "${merged_versioned_tag}" "${refs[@]}"
515
516              if [[ "$tag_name" == "${TAGS%% *}" ]]; then
517                  local digest
518                  digest="$(docker buildx imagetools inspect "${merged_versioned_tag}" --format '{{.Manifest.Digest}}')"
519                  if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then
520                      echo "Invalid digest for ${merged_versioned_tag}: ${digest}" >&2
521                      exit 1
522                  fi
523                  echo "${image_type}_digest=${digest}" >> "$GITHUB_OUTPUT"
524              fi
525          }
526
527          for tag in $TAGS; do
528              if [[ "$tag" == "cpu" ]]; then
529                  TYPE=""
530              else
531                  TYPE="-$tag"
532              fi
533
534              if [[ "${{ matrix.config.full }}" == "true" ]]; then
535                  create_manifest_tags "full" "$tag" "$TYPE"
536              fi
537
538              if [[ "${{ matrix.config.light }}" == "true" ]]; then
539                  create_manifest_tags "light" "$tag" "$TYPE"
540              fi
541
542              if [[ "${{ matrix.config.server }}" == "true" ]]; then
543                  create_manifest_tags "server" "$tag" "$TYPE"
544              fi
545          done
546        env:
547          GITHUB_REPOSITORY_OWNER: '${{ github.repository_owner }}'
548
549      - name: Attest full image
550        if: ${{ matrix.config.full }}
551        uses: actions/attest@v4
552        with:
553          subject-name: ${{ steps.create_tags.outputs.image_repo }}
554          subject-digest: ${{ steps.create_tags.outputs.full_digest }}
555
556      - name: Attest light image
557        if: ${{ matrix.config.light }}
558        uses: actions/attest@v4
559        with:
560          subject-name: ${{ steps.create_tags.outputs.image_repo }}
561          subject-digest: ${{ steps.create_tags.outputs.light_digest }}
562
563      - name: Attest server image
564        if: ${{ matrix.config.server }}
565        uses: actions/attest@v4
566        with:
567          subject-name: ${{ steps.create_tags.outputs.image_repo }}
568          subject-digest: ${{ steps.create_tags.outputs.server_digest }}