From d90bb66daae7a57c7710cb6d09ec815181530134 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Mon, 13 Jul 2026 16:54:01 +0200 Subject: [PATCH] feat: ML hardware acceleration testing on pokedex --- .github/workflows/test.yml | 44 +++++++++++++++++++++++++++++++ machine-learning/mise.toml | 3 +++ machine-learning/pyproject.toml | 2 +- machine-learning/test_hardware.py | 35 ++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 machine-learning/test_hardware.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 465f18ae8b..0f9d8c3c57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -636,6 +636,50 @@ jobs: - name: Run ci-unit run: mise run ci-unit + ml-hwaccel-tests: + name: HW Accel Test ML (${{ matrix.name }}) + needs: pre-job + if: ${{ fromJSON(needs.pre-job.outputs.should_run).machine-learning == true }} + strategy: + fail-fast: false + matrix: + include: + - name: nvidia-ampere + runner: pokedex-dgpu-nvidia-ampere + device: cuda + expected-ep: CUDAExecutionProvider + runs-on: ${{ matrix.runner }} + permissions: + contents: read + defaults: + run: + working-directory: ./machine-learning + env: + IMMICH_HWACCEL_EXPECTED_EP: ${{ matrix.expected-ep }} + HF_HUB_DISABLE_XET: '1' + steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 + with: + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + token: ${{ steps.token.outputs.token }} + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0 + with: + github_token: ${{ steps.token.outputs.token }} + + - name: Install + run: mise run install --extra ${{ matrix.device }} + + - name: Run hardware tests + run: mise run test-hardware + github-files-formatting: name: .github Files Formatting needs: pre-job diff --git a/machine-learning/mise.toml b/machine-learning/mise.toml index e5e30c4fc2..aa37c90d88 100644 --- a/machine-learning/mise.toml +++ b/machine-learning/mise.toml @@ -14,6 +14,9 @@ run = "uv run pytest --cov=immich_ml --cov-report term-missing" [tasks.format] run = "uv run ruff format immich_ml" +[tasks.test-hardware] +run = "uv run pytest -m gpu test_hardware.py" + [tasks.check] run = "uv run mypy --strict immich_ml/" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 6b2082930c..4fc4f48dd3 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -92,4 +92,4 @@ select = ["E", "F", "I"] per-file-ignores = { "test_main.py" = ["F403"] } [tool.pytest.ini_options] -markers = ["providers", "ov_device_ids"] +markers = ["providers", "ov_device_ids", "gpu"] diff --git a/machine-learning/test_hardware.py b/machine-learning/test_hardware.py new file mode 100644 index 0000000000..dde9abcc18 --- /dev/null +++ b/machine-learning/test_hardware.py @@ -0,0 +1,35 @@ +import os + +import pytest +from PIL import Image + +from immich_ml.models import from_model_type +from immich_ml.schemas import ModelTask, ModelType + +pytestmark = pytest.mark.gpu + +EXPECTED_EP = os.environ.get("IMMICH_HWACCEL_EXPECTED_EP") + + +@pytest.fixture(autouse=True) +def require_hwaccel_run() -> None: + if not EXPECTED_EP: + pytest.skip("IMMICH_HWACCEL_EXPECTED_EP unset; not a hardware run") + + +MODELS = [ + pytest.param("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH, id="clip-visual"), +] + + +@pytest.mark.parametrize("model_name, model_type, model_task", MODELS) +def test_hwaccel_engaged(model_name: str, model_type: ModelType, model_task: ModelTask) -> None: + model = from_model_type(model_name, model_type, model_task) + model.load() + + # a CPU fallback returns the same output, so the provider list is the only fallback signal + assert EXPECTED_EP in model.session.providers, ( + f"expected {EXPECTED_EP}, session resolved to {model.session.providers}" + ) + + model.predict(Image.new("RGB", (224, 224)))