Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f86c1054a | ||
|
|
634688fc93 | ||
|
|
9f0a78522f | ||
|
|
8d1f5da05a | ||
|
|
a7257cf031 | ||
|
|
326fdbdf38 | ||
|
|
91e0858aa6 | ||
|
|
71df0e81de | ||
|
|
9bb0727807 | ||
|
|
7c306a4423 | ||
|
|
c22857b912 | ||
|
|
c53172f5ff | ||
|
|
9598142997 |
@@ -2,7 +2,7 @@ name: Publish Docker Image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main", "dev"]
|
branches: ["dev"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**.md"
|
- "**.md"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
@@ -15,6 +15,16 @@ on:
|
|||||||
- "uv-cpu.lock"
|
- "uv-cpu.lock"
|
||||||
- "uv-rocm.lock"
|
- "uv-rocm.lock"
|
||||||
- "uv-intel.lock"
|
- "uv-intel.lock"
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: "Release tag, e.g. v0.4.1 — triggers :latest + versioned image tags"
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: "Version string without v prefix, e.g. 0.4.1"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: docker-${{ github.ref }}
|
group: docker-${{ github.ref }}
|
||||||
@@ -50,6 +60,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag || github.ref }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: matrix.platform == 'linux/arm64'
|
if: matrix.platform == 'linux/arm64'
|
||||||
@@ -65,6 +77,15 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Compute build version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ inputs.version }}" ]; then
|
||||||
|
echo "value=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build and push by digest
|
- name: Build and push by digest
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
@@ -72,6 +93,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: VERSION=${{ steps.version.outputs.value }}
|
||||||
cache-from: type=gha,scope=${{ matrix.platform }}
|
cache-from: type=gha,scope=${{ matrix.platform }}
|
||||||
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
|
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -120,22 +142,26 @@ jobs:
|
|||||||
- name: Determine image tags
|
- name: Determine image tags
|
||||||
id: tags
|
id: tags
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.ref_name }}" = "dev" ]; then
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||||
echo "tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev" >> "$GITHUB_OUTPUT"
|
INPUT_TAG="${{ inputs.tag }}"
|
||||||
|
if [ -n "$INPUT_TAG" ]; then
|
||||||
|
echo "tag_args=-t ${IMAGE}:latest -t ${IMAGE}:${INPUT_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT"
|
echo "tag_args=-t ${IMAGE}:dev" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:dev" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create and push multi-arch manifest
|
- name: Create and push multi-arch manifest
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
-t ${{ steps.tags.outputs.tags }} \
|
${{ steps.tags.outputs.tag_args }} \
|
||||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||||
|
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ steps.tags.outputs.tags }}
|
docker buildx imagetools inspect ${{ steps.tags.outputs.inspect_tag }}
|
||||||
|
|
||||||
- name: Ensure package is public
|
- name: Ensure package is public
|
||||||
run: |
|
run: |
|
||||||
@@ -162,6 +188,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag || github.ref }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v4
|
uses: docker/setup-qemu-action@v4
|
||||||
@@ -176,13 +204,30 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Determine CPU image tag
|
- name: Determine CPU image tags
|
||||||
id: cpu-tag
|
id: cpu-tags
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.ref_name }}" = "dev" ]; then
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-cpu" >> "$GITHUB_OUTPUT"
|
INPUT_TAG="${{ inputs.tag }}"
|
||||||
|
if [ -n "$INPUT_TAG" ]; then
|
||||||
|
{
|
||||||
|
echo "tags<<EOF"
|
||||||
|
printf '%s\n' "${IMAGE}:cpu" "${IMAGE}:${INPUT_TAG}-cpu"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:cpu" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cpu" >> "$GITHUB_OUTPUT"
|
echo "tags=${IMAGE}:dev-cpu" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:dev-cpu" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Compute build version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ inputs.version }}" ]; then
|
||||||
|
echo "value=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push CPU image
|
- name: Build and push CPU image
|
||||||
@@ -191,16 +236,18 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: VARIANT=cpu
|
build-args: |
|
||||||
|
VARIANT=cpu
|
||||||
|
VERSION=${{ steps.version.outputs.value }}
|
||||||
cache-from: type=gha,scope=cpu
|
cache-from: type=gha,scope=cpu
|
||||||
cache-to: type=gha,mode=max,scope=cpu
|
cache-to: type=gha,mode=max,scope=cpu
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.cpu-tag.outputs.tag }}
|
tags: ${{ steps.cpu-tags.outputs.tags }}
|
||||||
|
|
||||||
- name: Inspect CPU image
|
- name: Inspect CPU image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ steps.cpu-tag.outputs.tag }}
|
docker buildx imagetools inspect ${{ steps.cpu-tags.outputs.inspect_tag }}
|
||||||
|
|
||||||
- name: Ensure package is public
|
- name: Ensure package is public
|
||||||
run: |
|
run: |
|
||||||
@@ -227,6 +274,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag || github.ref }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
@@ -238,13 +287,30 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Determine ROCm image tag
|
- name: Determine ROCm image tags
|
||||||
id: rocm-tag
|
id: rocm-tags
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.ref_name }}" = "dev" ]; then
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-rocm" >> "$GITHUB_OUTPUT"
|
INPUT_TAG="${{ inputs.tag }}"
|
||||||
|
if [ -n "$INPUT_TAG" ]; then
|
||||||
|
{
|
||||||
|
echo "tags<<EOF"
|
||||||
|
printf '%s\n' "${IMAGE}:rocm" "${IMAGE}:${INPUT_TAG}-rocm"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:rocm" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:rocm" >> "$GITHUB_OUTPUT"
|
echo "tags=${IMAGE}:dev-rocm" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:dev-rocm" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Compute build version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ inputs.version }}" ]; then
|
||||||
|
echo "value=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push ROCm image
|
- name: Build and push ROCm image
|
||||||
@@ -253,16 +319,18 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
build-args: VARIANT=rocm
|
build-args: |
|
||||||
|
VARIANT=rocm
|
||||||
|
VERSION=${{ steps.version.outputs.value }}
|
||||||
cache-from: type=gha,scope=linux/amd64-rocm
|
cache-from: type=gha,scope=linux/amd64-rocm
|
||||||
cache-to: type=gha,mode=max,scope=linux/amd64-rocm
|
cache-to: type=gha,mode=max,scope=linux/amd64-rocm
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.rocm-tag.outputs.tag }}
|
tags: ${{ steps.rocm-tags.outputs.tags }}
|
||||||
|
|
||||||
- name: Inspect ROCm image
|
- name: Inspect ROCm image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ steps.rocm-tag.outputs.tag }}
|
docker buildx imagetools inspect ${{ steps.rocm-tags.outputs.inspect_tag }}
|
||||||
|
|
||||||
- name: Ensure package is public
|
- name: Ensure package is public
|
||||||
run: |
|
run: |
|
||||||
@@ -289,6 +357,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag || github.ref }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
@@ -300,13 +370,30 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Determine Intel image tag
|
- name: Determine Intel image tags
|
||||||
id: intel-tag
|
id: intel-tags
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.ref_name }}" = "dev" ]; then
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-intel" >> "$GITHUB_OUTPUT"
|
INPUT_TAG="${{ inputs.tag }}"
|
||||||
|
if [ -n "$INPUT_TAG" ]; then
|
||||||
|
{
|
||||||
|
echo "tags<<EOF"
|
||||||
|
printf '%s\n' "${IMAGE}:intel" "${IMAGE}:${INPUT_TAG}-intel"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:intel" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "tag=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:intel" >> "$GITHUB_OUTPUT"
|
echo "tags=${IMAGE}:dev-intel" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "inspect_tag=${IMAGE}:dev-intel" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Compute build version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ inputs.version }}" ]; then
|
||||||
|
echo "value=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push Intel image
|
- name: Build and push Intel image
|
||||||
@@ -315,16 +402,18 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
build-args: VARIANT=intel
|
build-args: |
|
||||||
|
VARIANT=intel
|
||||||
|
VERSION=${{ steps.version.outputs.value }}
|
||||||
cache-from: type=gha,scope=linux/amd64-intel
|
cache-from: type=gha,scope=linux/amd64-intel
|
||||||
cache-to: type=gha,mode=max,scope=linux/amd64-intel
|
cache-to: type=gha,mode=max,scope=linux/amd64-intel
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.intel-tag.outputs.tag }}
|
tags: ${{ steps.intel-tags.outputs.tags }}
|
||||||
|
|
||||||
- name: Inspect Intel image
|
- name: Inspect Intel image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ steps.intel-tag.outputs.tag }}
|
docker buildx imagetools inspect ${{ steps.intel-tags.outputs.inspect_tag }}
|
||||||
|
|
||||||
- name: Ensure package is public
|
- name: Ensure package is public
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -127,188 +127,14 @@ jobs:
|
|||||||
prerelease: false,
|
prerelease: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
build-gpu:
|
build-images:
|
||||||
name: Build GPU image
|
name: Build and push Docker images
|
||||||
needs: release
|
needs: release
|
||||||
runs-on: ubuntu-latest
|
uses: ./.github/workflows/docker-publish.yml
|
||||||
|
with:
|
||||||
|
tag: ${{ needs.release.outputs.tag }}
|
||||||
|
version: ${{ needs.release.outputs.version }}
|
||||||
|
secrets: inherit
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
contents: read
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf "/usr/local/share/boost"
|
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
echo "Disk space freed."
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push GPU image (latest)
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
build-args: VERSION=${{ needs.release.outputs.version }}
|
|
||||||
cache-from: type=gha,scope=release-gpu
|
|
||||||
cache-to: type=gha,mode=max,scope=release-gpu
|
|
||||||
tags: |
|
|
||||||
ghcr.io/sudolulo/winnow:latest
|
|
||||||
ghcr.io/sudolulo/winnow:${{ needs.release.outputs.tag }}
|
|
||||||
|
|
||||||
build-cpu:
|
|
||||||
name: Build CPU image
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf "/usr/local/share/boost"
|
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
echo "Disk space freed."
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push CPU image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
VARIANT=cpu
|
|
||||||
VERSION=${{ needs.release.outputs.version }}
|
|
||||||
cache-from: type=gha,scope=release-cpu
|
|
||||||
cache-to: type=gha,mode=max,scope=release-cpu
|
|
||||||
tags: |
|
|
||||||
ghcr.io/sudolulo/winnow:cpu
|
|
||||||
ghcr.io/sudolulo/winnow:${{ needs.release.outputs.tag }}-cpu
|
|
||||||
|
|
||||||
build-rocm:
|
|
||||||
name: Build ROCm image
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf "/usr/local/share/boost"
|
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
echo "Disk space freed."
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push ROCm image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
VARIANT=rocm
|
|
||||||
VERSION=${{ needs.release.outputs.version }}
|
|
||||||
cache-from: type=gha,scope=release-rocm
|
|
||||||
cache-to: type=gha,mode=max,scope=release-rocm
|
|
||||||
tags: |
|
|
||||||
ghcr.io/sudolulo/winnow:rocm
|
|
||||||
ghcr.io/sudolulo/winnow:${{ needs.release.outputs.tag }}-rocm
|
|
||||||
|
|
||||||
build-intel:
|
|
||||||
name: Build Intel image
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf "/usr/local/share/boost"
|
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
echo "Disk space freed."
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Intel image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
VARIANT=intel
|
|
||||||
VERSION=${{ needs.release.outputs.version }}
|
|
||||||
cache-from: type=gha,scope=release-intel
|
|
||||||
cache-to: type=gha,mode=max,scope=release-intel
|
|
||||||
tags: |
|
|
||||||
ghcr.io/sudolulo/winnow:intel
|
|
||||||
ghcr.io/sudolulo/winnow:${{ needs.release.outputs.tag }}-intel
|
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.4.2] - 2026-06-13
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **GPU image now uses CUDA 12.8.1** (was 13.3): CUDA 13.3 requires driver ≥ 575; driver 570 (the current stable release) was incorrectly rejected with "CUDA driver version is insufficient" at startup. The `:latest` image now works with any NVIDIA driver ≥ 570.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`scripts/benchmark.py`**: measures InsightFace and SigLIP inference latency and throughput across GPU and CPU modes. Run inside the container with `python /app/scripts/benchmark.py`. RTX 2070 SUPER results: InsightFace 12.8 ms / 78 img/s (8× CPU), SigLIP batch 32 at 5.4 ms/img / 187 img/s (33× CPU).
|
||||||
|
|
||||||
## [0.4.1] - 2026-06-13
|
## [0.4.1] - 2026-06-13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# ── Base images ───────────────────────────────────────────────────────────────
|
# ── Base images ───────────────────────────────────────────────────────────────
|
||||||
# amd64 + gpu: NVIDIA CUDA 13.3 + cuDNN (GPU acceleration via NVIDIA Container Toolkit)
|
# amd64 + gpu: NVIDIA CUDA 12.8 + cuDNN (GPU acceleration via NVIDIA Container Toolkit)
|
||||||
# amd64 + rocm: Ubuntu 22.04 (AMD GPU via ROCm — pass /dev/kfd and /dev/dri)
|
# amd64 + rocm: Ubuntu 22.04 (AMD GPU via ROCm — pass /dev/kfd and /dev/dri)
|
||||||
# amd64 + intel: Ubuntu 22.04 (Intel Arc / iGPU via OpenVINO — pass /dev/dri)
|
# amd64 + intel: Ubuntu 22.04 (Intel Arc / iGPU via OpenVINO — pass /dev/dri)
|
||||||
# amd64 + cpu: Ubuntu 22.04 (CPU-only, ~2 GB smaller image)
|
# amd64 + cpu: Ubuntu 22.04 (CPU-only, ~2 GB smaller image)
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
ARG VARIANT=gpu
|
ARG VARIANT=gpu
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM nvidia/cuda:13.3.0-cudnn-runtime-ubuntu22.04 AS base-amd64-gpu
|
FROM --platform=$BUILDPLATFORM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04 AS base-amd64-gpu
|
||||||
FROM ubuntu:22.04 AS base-amd64-rocm
|
FROM ubuntu:22.04 AS base-amd64-rocm
|
||||||
FROM ubuntu:22.04 AS base-amd64-intel
|
FROM ubuntu:22.04 AS base-amd64-intel
|
||||||
FROM ubuntu:22.04 AS base-amd64-cpu
|
FROM ubuntu:22.04 AS base-amd64-cpu
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition and object classification."
|
description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition and object classification."
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
winnow inference benchmark: GPU vs CPU throughput.
|
||||||
|
|
||||||
|
Measures InsightFace (face mode) and SigLIP (object mode) latency and
|
||||||
|
throughput. Run with FORCE_CPU=true for CPU-only baseline.
|
||||||
|
|
||||||
|
Usage inside container:
|
||||||
|
# GPU mode:
|
||||||
|
docker exec winnow python /app/scripts/benchmark.py
|
||||||
|
|
||||||
|
# CPU mode:
|
||||||
|
docker exec -e FORCE_CPU=true winnow python /app/scripts/benchmark.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
|
||||||
|
def _mode_label() -> str:
|
||||||
|
if os.getenv("FORCE_CPU", "").lower() in ("true", "1", "yes"):
|
||||||
|
return "CPU (FORCE_CPU=true)"
|
||||||
|
return "GPU (auto)"
|
||||||
|
|
||||||
|
|
||||||
|
def make_face_image(size: int = 640) -> Image.Image:
|
||||||
|
"""Synthetic face-like image: skin-tone rectangle with landmark blobs."""
|
||||||
|
img = Image.new("RGB", (size, size), (200, 170, 140))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
# Head oval
|
||||||
|
cx, cy = size // 2, size // 2
|
||||||
|
hw, hh = int(size * 0.3), int(size * 0.38)
|
||||||
|
draw.ellipse([cx - hw, cy - hh, cx + hw, cy + hh], fill=(220, 185, 155))
|
||||||
|
# Eyes
|
||||||
|
for ex in [cx - int(size * 0.1), cx + int(size * 0.1)]:
|
||||||
|
ey = cy - int(size * 0.05)
|
||||||
|
r = max(4, size // 40)
|
||||||
|
draw.ellipse([ex - r, ey - r, ex + r, ey + r], fill=(40, 30, 20))
|
||||||
|
# Nose
|
||||||
|
draw.ellipse([cx - 5, cy + 5, cx + 5, cy + 15], fill=(180, 140, 110))
|
||||||
|
# Mouth
|
||||||
|
draw.arc([cx - 20, cy + 25, cx + 20, cy + 45], start=0, end=180, fill=(160, 80, 80), width=3)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def make_random_image(width: int = 224, height: int = 224) -> Image.Image:
|
||||||
|
rng = np.random.default_rng(42)
|
||||||
|
return Image.fromarray(rng.integers(0, 256, (height, width, 3), dtype=np.uint8), "RGB")
|
||||||
|
|
||||||
|
|
||||||
|
def _stats(times_s: list[float]) -> dict:
|
||||||
|
arr = np.array(times_s) * 1000 # ms
|
||||||
|
return {
|
||||||
|
"median_ms": float(np.median(arr)),
|
||||||
|
"mean_ms": float(np.mean(arr)),
|
||||||
|
"min_ms": float(np.min(arr)),
|
||||||
|
"p95_ms": float(np.percentile(arr, 95)),
|
||||||
|
"ips": 1000.0 / float(np.median(arr)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def bench_insightface(n_warmup: int = 5, n_runs: int = 30) -> None:
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
import winnow.embeddings as emb_mod
|
||||||
|
from winnow.embeddings import get_insightface_app
|
||||||
|
|
||||||
|
# Reset singleton so we get a fresh load
|
||||||
|
emb_mod._insightface_app = None
|
||||||
|
emb_mod._insightface_loaded = False
|
||||||
|
|
||||||
|
print(" Loading model...")
|
||||||
|
t_load = time.perf_counter()
|
||||||
|
app = get_insightface_app()
|
||||||
|
load_s = time.perf_counter() - t_load
|
||||||
|
|
||||||
|
if app is None:
|
||||||
|
print(" SKIP: InsightFace failed to load")
|
||||||
|
return
|
||||||
|
|
||||||
|
img_pil = make_face_image(640)
|
||||||
|
img_bgr = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
|
# Warmup
|
||||||
|
for _ in range(n_warmup):
|
||||||
|
app.get(img_bgr)
|
||||||
|
|
||||||
|
# Timed — single image 640×640
|
||||||
|
times: list[float] = []
|
||||||
|
for _ in range(n_runs):
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
app.get(img_bgr)
|
||||||
|
times.append(time.perf_counter() - t0)
|
||||||
|
|
||||||
|
s = _stats(times)
|
||||||
|
print(f" Model load time : {load_s:.2f} s")
|
||||||
|
print(" Input size : 640×640")
|
||||||
|
print(f" Runs : {n_runs} (after {n_warmup} warmup)")
|
||||||
|
print(f" Median latency : {s['median_ms']:.1f} ms")
|
||||||
|
print(f" Mean / p95 : {s['mean_ms']:.1f} ms / {s['p95_ms']:.1f} ms")
|
||||||
|
print(f" Min latency : {s['min_ms']:.1f} ms")
|
||||||
|
print(f" Throughput : {s['ips']:.1f} images/s")
|
||||||
|
|
||||||
|
# Also test at 320×320
|
||||||
|
img_sm = make_face_image(320)
|
||||||
|
img_sm_bgr = cv2.cvtColor(np.asarray(img_sm), cv2.COLOR_RGB2BGR)
|
||||||
|
for _ in range(n_warmup):
|
||||||
|
app.get(img_sm_bgr)
|
||||||
|
times_sm: list[float] = []
|
||||||
|
for _ in range(n_runs):
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
app.get(img_sm_bgr)
|
||||||
|
times_sm.append(time.perf_counter() - t0)
|
||||||
|
s2 = _stats(times_sm)
|
||||||
|
print(f" 320×320 median : {s2['median_ms']:.1f} ms ({s2['ips']:.1f} img/s)")
|
||||||
|
|
||||||
|
|
||||||
|
def bench_siglip(
|
||||||
|
n_warmup: int = 3,
|
||||||
|
n_runs: int = 20,
|
||||||
|
batch_sizes: tuple = (1, 4, 8, 16, 32),
|
||||||
|
) -> None:
|
||||||
|
import torch
|
||||||
|
|
||||||
|
import winnow.embeddings as emb_mod
|
||||||
|
emb_mod._siglip_model = None
|
||||||
|
emb_mod._siglip_processor = None
|
||||||
|
emb_mod._siglip_loaded = False
|
||||||
|
|
||||||
|
print(" Loading model...")
|
||||||
|
t_load = time.perf_counter()
|
||||||
|
model, processor = emb_mod.get_siglip_model()
|
||||||
|
load_s = time.perf_counter() - t_load
|
||||||
|
|
||||||
|
if model is None:
|
||||||
|
print(" SKIP: SigLIP failed to load")
|
||||||
|
return
|
||||||
|
|
||||||
|
device = next(model.parameters()).device
|
||||||
|
print(f" Model load time : {load_s:.2f} s (device: {device})")
|
||||||
|
|
||||||
|
print(f" {'Batch':>5} {'ms/batch':>10} {'ms/img':>8} {'img/s':>8} {'p95/img':>9}")
|
||||||
|
for bs in batch_sizes:
|
||||||
|
imgs = [make_random_image(224, 224) for _ in range(bs)]
|
||||||
|
inputs = processor(images=imgs, return_tensors="pt")
|
||||||
|
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||||
|
|
||||||
|
# Warmup
|
||||||
|
for _ in range(n_warmup):
|
||||||
|
with torch.no_grad():
|
||||||
|
model(**inputs)
|
||||||
|
if str(device) != "cpu":
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
|
times: list[float] = []
|
||||||
|
for _ in range(n_runs):
|
||||||
|
if str(device) != "cpu":
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
with torch.no_grad():
|
||||||
|
model(**inputs)
|
||||||
|
if str(device) != "cpu":
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
times.append(time.perf_counter() - t0)
|
||||||
|
|
||||||
|
s = _stats(times)
|
||||||
|
print(
|
||||||
|
f" {bs:>5} {s['median_ms']:>10.1f} {s['median_ms']/bs:>8.2f}"
|
||||||
|
f" {bs * 1000 / s['median_ms']:>8.1f} {s['p95_ms']/bs:>9.2f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
print("=" * 56)
|
||||||
|
print(" winnow inference benchmark")
|
||||||
|
print(f" Mode: {_mode_label()}")
|
||||||
|
print("=" * 56)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("── InsightFace Buffalo_L (face detection + ArcFace) ──")
|
||||||
|
bench_insightface()
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("── SigLIP google/siglip-base-patch16-224 (objects) ───")
|
||||||
|
bench_siglip()
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Add winnow to path when run directly inside container
|
||||||
|
sys.path.insert(0, "/app")
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user