# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license

# Ultralytics YOLO Continuous Integration (CI) GitHub Actions tests

name: CI

permissions:
  contents: read

env:
  PYTHONFAULTHANDLER: 1

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: "0 8 * * *" # runs at 08:00 UTC every day
  workflow_dispatch:
    inputs:
      benchmarks:
        description: "Run Benchmarks"
        default: true
        type: boolean
      tests:
        description: "Run Tests"
        default: true
        type: boolean
      gpu:
        description: "Run GPU"
        default: true
        type: boolean
      amd-gpu:
        description: "Run AMD GPU"
        default: true
        type: boolean
      raspberrypi:
        description: "Run Raspberry Pi"
        default: true
        type: boolean
      nvidia-jetson:
        description: "Run NVIDIA Jetson"
        default: true
        type: boolean
      conda:
        description: "Run Conda"
        default: true
        type: boolean

jobs:
  # Builds the shared CI asset cache once; every job that needs: Assets restores it
  Assets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main # outside the composite so it adds no post step to every consumer
        with:
          python-version: "3.13"
      - uses: ./.github/actions/ci-assets
        with:
          build: true

  Benchmarks:
    needs: Assets
    if: github.event_name != 'workflow_dispatch' || github.event.inputs.benchmarks == 'true'
    permissions:
      contents: read
      id-token: write
    runs-on: ${{ matrix.os }}
    env:
      # Keep benchmark jobs hermetic so missing export deps fail during setup instead of mutating mid-run.
      YOLO_AUTOINSTALL: "false"
    strategy:
      fail-fast: false
      matrix:
        # Temporarily disable windows-latest due to https://github.com/ultralytics/ultralytics/actions/runs/13020330819/job/36319338854?pr=18921
        os: [ubuntu-latest, macos-26, ubuntu-24.04-arm]
        python: ["3.13"]
        model: [yolo26n]
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          python-version: ${{ matrix.python }}
      - name: Install requirements
        uses: ultralytics/actions/retry@main
        with:
          retries: 3
          retry_delay_seconds: 20
          timeout_minutes: 20
          run: |
            # Pin PNNX until 20260704 NCNN inference segfault is fixed: https://github.com/pnnx/pnnx/issues/293
            uv pip install -e ".[export,export-executorch]" \
              ncnn pnnx==20260526 "coverage[toml]" \
              --torch-backend cpu
            # Core AI benchmark rows: export on macOS and x86_64 Linux, inference on macOS (a no-op on Linux arm64)
            uv pip install -e ".[export-coreai]" --torch-backend cpu
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - uses: ./.github/actions/ci-assets
      - name: Benchmark DetectionModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}.pt' imgsz=160 verbose=0.216
      - name: Benchmark ClassificationModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-cls.pt' imgsz=160 verbose=0.249
      - name: Benchmark YOLOE SegmentationModel
        shell: bash
        # Prompt-free classes don't map to COCO, so verbose=True checks exports without a metric floor
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model=yoloe-26n-seg-pf.pt imgsz=160 verbose=True
      - name: Benchmark SegmentationModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-seg.pt' imgsz=160 verbose=0.21
      - name: Benchmark PoseModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-pose.pt' imgsz=160 verbose=0.179
      - name: Benchmark OBBModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-obb.pt' imgsz=160 verbose=0.371
      - name: Benchmark SemanticSegmentationModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-sem.pt' imgsz=160 verbose=0.189
      - name: Benchmark DepthModel
        shell: bash
        run: coverage run -a --source=ultralytics -m ultralytics.cfg.__init__ benchmark model='path with spaces/${{ matrix.model }}-depth.pt' imgsz=160 verbose=0.5
      - name: Merge Coverage Reports
        run: |
          coverage xml -o coverage-benchmarks.xml
      - name: Upload Coverage Reports to CodeCov
        if: github.repository == 'ultralytics/ultralytics'
        continue-on-error: true
        uses: codecov/codecov-action@v7
        with:
          flags: Benchmarks
          fail_ci_if_error: false
          use_oidc: true
      - name: Prune uv Cache
        run: uv cache prune --ci
      - name: Benchmark Summary
        run: |
          cat benchmarks.log
          echo '```' >> $GITHUB_STEP_SUMMARY
          cat benchmarks.log >> $GITHUB_STEP_SUMMARY
          echo '```' >> $GITHUB_STEP_SUMMARY

  Tests:
    needs: Assets
    if: github.event_name == 'pull_request' || github.event_name == 'push'
    permissions:
      contents: read
      id-token: write
    timeout-minutes: 60
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-26, windows-latest, ubuntu-24.04-arm]
        python: ["3.13"]
        torch: [latest]
        include:
          - os: ubuntu-latest
            python: "3.8" # torch 1.8.0 requires python >=3.6, <=3.9
            torch: "1.8.0"
            torchvision: "0.9.0"
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          python-version: ${{ matrix.python }}
      - name: Install requirements
        shell: bash # for Windows compatibility
        run: |
          slow=""
          torch=""
          if [ "${{ matrix.torch }}" != "latest" ]; then
              torch="torch==${{ matrix.torch }} torchvision==${{ matrix.torchvision }}"
          fi
          uv pip install -e ".[export-base,export-openvino,solutions]" \
            $torch aiohttp pytest-cov pytest-xdist "git+https://github.com/ultralytics/CLIP.git" \
            --torch-backend cpu
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - uses: ./.github/actions/ci-assets
      - name: Pytest tests
        shell: bash # for Windows compatibility
        # GitHub-hosted ARM runners have disconnected under xdist auto-parallelism: ubuntu-24.04-arm runs
        # single-process, and the 3-vCPU/7GB macos-26 runners cap at 2 workers to avoid memory starvation.
        run: |
          # --max-worker-restart=0 fails fast on a worker crash: loadfile re-queues the crashed test onto replacement
          # workers (pytest-dev/pytest-xdist#1371), which crash-loop or hang instead of adding coverage
          xdist="-n auto --dist=loadfile --max-worker-restart=0"
          [[ "${{ matrix.os }}" == "macos-26" ]] && xdist="-n 2 --dist=loadfile --max-worker-restart=0"
          [[ "${{ matrix.os }}" == "ubuntu-24.04-arm" ]] && xdist=""
          pytest $xdist --cov=ultralytics/ --cov-report=xml tests/ --export-env base
        env:
          YOLO_AUTOINSTALL: "false"
      - name: Upload Coverage Reports to CodeCov
        if: github.repository == 'ultralytics/ultralytics' # && matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
        continue-on-error: true
        uses: codecov/codecov-action@v7
        with:
          flags: Tests
          fail_ci_if_error: false
          use_oidc: true
      - name: Prune uv Cache
        run: uv cache prune --ci

  # Shared runner CI keeps current stacks on the main path; legacy torch shards are isolated only where needed.
  SlowTests:
    needs: Assets
    if: (github.event_name == 'workflow_dispatch' && github.event.inputs.tests == 'true') || github.event_name == 'schedule'
    timeout-minutes: 360
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-26, windows-latest, ubuntu-24.04-arm]
        python: ["3.13"]
        torch: [latest]
        include:
          # Legacy PyTorch shards keep NumPy <2 only where the older torch builds actually need it.
          - os: ubuntu-latest
            python: "3.8" # torch 1.8.0 requires python >=3.6, <=3.9
            torch: "1.8.0"
            torchvision: "0.9.0"
          - os: ubuntu-latest
            python: "3.9"
            torch: "1.9.0"
            torchvision: "0.10.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.9"
            torch: "1.10.0"
            torchvision: "0.11.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.10"
            torch: "1.11.0"
            torchvision: "0.12.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.10"
            torch: "1.12.0"
            torchvision: "0.13.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.10"
            torch: "1.13.0"
            torchvision: "0.14.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.11"
            torch: "2.0.0"
            torchvision: "0.15.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.11"
            torch: "2.1.0"
            torchvision: "0.16.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.2.0"
            torchvision: "0.17.0"
            numpy_extra: export-legacy-torch
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.3.0"
            torchvision: "0.18.0"
            numpy_extra: export-legacy-torch # torchvision 0.18.0 adjust_hue casts negative hue via np.uint8(), rejected by NumPy 2 (fixed in torchvision 0.19.0)
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.4.0"
            torchvision: "0.19.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.5.0"
            torchvision: "0.20.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.6.0"
            torchvision: "0.21.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.7.0"
            torchvision: "0.22.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.8.0"
            torchvision: "0.23.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.9.0"
            torchvision: "0.24.0"
          - os: ubuntu-latest
            python: "3.12"
            torch: "2.10.0"
            torchvision: "0.25.0"
          - os: ubuntu-latest
            python: "3.13"
            torch: "2.11.0"
            torchvision: "0.26.0"
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          python-version: ${{ matrix.python }}
      - name: Install requirements
        shell: bash # for Windows compatibility
        run: |
          legacy_extra=""
          if [ -n "${{ matrix.numpy_extra }}" ]; then
            legacy_extra=",${{ matrix.numpy_extra }}"
          fi
          torch=""
          if [ "${{ matrix.torch }}" != "latest" ]; then
            torch="torch==${{ matrix.torch }} torchvision==${{ matrix.torchvision }}"
          fi
          uv pip install -e ".[export-base,export-openvino,solutions${legacy_extra}]" $torch \
            aiohttp faster-coco-eval mlflow pytest-cov pytest-xdist "git+https://github.com/ultralytics/CLIP.git" \
            --torch-backend cpu
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - uses: ./.github/actions/ci-assets
      - name: Pytest tests
        uses: ultralytics/actions/retry@main
        with:
          shell: bash # for Windows compatibility
          # -n 2 limits parallelism for resource-heavy base export tests on shared runners. The ARM runner is
          # single-process to avoid xdist worker overhead and runner disconnects during long slow-test runs.
          run: |
            pytest_args=(-vv -s --slow --durations=50 --durations-min=1 tests/ --export-env base)
            if [[ "${{ matrix.os }}" == "ubuntu-24.04-arm" ]]; then
              PYTHONFAULTHANDLER=1 PYTHONUNBUFFERED=1 pytest "${pytest_args[@]}"
            else
              PYTHONFAULTHANDLER=1 PYTHONUNBUFFERED=1 pytest -n 2 --dist=loadfile --max-worker-restart=0 "${pytest_args[@]}"
            fi
          retries: 1 # Retry once after initial attempt (2 total runs)
          timeout_minutes: 60 # ~2x the slowest observed attempt (~31 min on ubuntu-24.04-arm)
          retry_delay_seconds: 60
        env:
          YOLO_AUTOINSTALL: "false"
      - name: Prune uv Cache
        run: uv cache prune --ci

  # Export recipes that need their own environments are validated here so the shared CI jobs stay lean.
  IsolatedExports:
    needs: Assets
    if: github.event_name == 'pull_request' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tests == 'true') || github.event_name == 'schedule'
    timeout-minutes: 360
    runs-on: ubuntu-latest
    steps:
      - name: Cleanup disk space
        uses: ultralytics/actions/cleanup-disk@main
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          python-version: "3.13"
      - name: Install export system dependencies
        uses: ultralytics/actions/retry@main
        with:
          retries: 3
          retry_delay_seconds: 60
          timeout_minutes: 10
          run: sudo apt-get update && sudo apt-get install -y openjdk-21-jre flatbuffers-compiler
      - name: Cache uv wheels
        uses: actions/cache@v6
        with:
          path: ~/.cache/uv
          key: isolated-exports-uv-${{ hashFiles('ultralytics/engine/exporter.py', 'pyproject.toml') }}
          restore-keys: |
            isolated-exports-uv-
      - name: Install base package
        run: uv pip install -e ".[export-base]" pytest "git+https://github.com/ultralytics/CLIP.git" --torch-backend cpu
      - uses: ./.github/actions/ci-assets
      - name: Build and run export tests in isolated environments
        env:
          ULTRALYTICS_ISOLATED_VENVS: ${{ runner.temp }}/venvs
          YOLO_AUTOINSTALL: "false"
        run: |
          slow_arg=""
          if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            slow_arg="--slow"
          fi
          for env in $(python .github/scripts/create-export-env.py --list); do
            python .github/scripts/create-export-env.py --env "$env"
            "${ULTRALYTICS_ISOLATED_VENVS}/${env}/bin/python" -m pytest -vv -s $slow_arg tests/test_exports.py --export-env "$env"
            rm -rf "${ULTRALYTICS_ISOLATED_VENVS:?}/${env}"
          done
      - name: Prune uv Cache
        run: uv cache prune --ci

  GPU:
    if: github.repository == 'ultralytics/ultralytics' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && (github.event_name != 'workflow_dispatch' || github.event.inputs.gpu == 'true')
    permissions:
      contents: read
      id-token: write
    timeout-minutes: 60
    runs-on: gpu-latest
    env:
      YOLO_AUTOINSTALL: "false"
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
      - name: Install requirements
        run: |
          sed -i'' -e 's/"opencv-python/"opencv-python-headless/' pyproject.toml  # headless OpenCV
          # Use the CUDA 13 ONNX Runtime wheel; ModelOpt still pins its CUDA 12 predecessor.
          uv pip install -e ".[export-base]" pytest-cov onnxruntime-gpu tensorrt-cu13 "nvidia-modelopt[onnx]>=0.44" lap cloudpickle --torch-backend cu132 --excludes <(echo onnxruntime) --overrides <(echo "onnxruntime-gpu>=1.30.0")
        env:
          PIP_BREAK_SYSTEM_PACKAGES: 1
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - name: Pytest tests
        run: |
          slow=""
          if [[ "${{ github.event_name }}" =~ ^(schedule|workflow_dispatch)$ ]]; then
            slow="--slow"
          fi
          pytest $slow --cov=ultralytics/ --cov-report xml tests/test_cuda.py -sv
        env:
          PIP_BREAK_SYSTEM_PACKAGES: 1
      - name: Upload Coverage Reports to CodeCov
        continue-on-error: true
        uses: codecov/codecov-action@v7
        with:
          flags: GPU
          fail_ci_if_error: false
          use_oidc: true

  AMD_GPU:
    if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.amd-gpu == 'true')
    permissions:
      contents: read
      id-token: write
    timeout-minutes: 120
    runs-on: amd-gpu-latest
    concurrency: # a newer run-all on the same PR supersedes its queued/running hardware jobs
      group: amd-gpu-${{ github.event_name }}-${{ github.ref_name }}
      cancel-in-progress: ${{ github.event_name == 'workflow_dispatch' }}
    env:
      YOLO_AUTOINSTALL: "false"
    steps:
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          python-version: "3.12"
      - name: Install requirements
        run: |
          uv pip install "torch[device-all]==2.13.0+rocm10.0.0" "torchvision[device-all]==0.28.0+rocm10.0.0" --index-url https://stable.repo.amd.com/rocm/whl-next/ --index-strategy unsafe-best-match
          uv pip install -e . pytest-cov "onnx>=1.12.0,<2.0.0" "onnxslim>=0.1.82"
          uv pip install "onnxruntime-ep-migraphx==1.0.0+rocm10.0.0" "migraphx-libs==2.17.0+rocm10.0.0" --extra-index-url https://stable.repo.amd.com/rocm/onnxruntime/whl-next/ --extra-index-url https://stable.repo.amd.com/rocm/migraphx/whl-next/ --index-strategy unsafe-best-match
        env:
          PIP_BREAK_SYSTEM_PACKAGES: 1
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - name: Pytest ROCm tests
        run: |
          pytest --slow --cov=ultralytics/ --cov-report xml tests/test_rocm.py -sv
        env:
          PIP_BREAK_SYSTEM_PACKAGES: 1
          ORT_MIGRAPHX_CACHE_DIR: ${{ runner.temp }}/migraphx_cache # emptied by the runner after every job
      - name: Upload Coverage Reports to CodeCov
        continue-on-error: true
        uses: codecov/codecov-action@v7
        with:
          flags: AMD_GPU
          fail_ci_if_error: false
          use_oidc: true

  # Raspberry Pi stays separate because it is a long-tail self-hosted runner with its own setup and cleanup flow.
  RaspberryPi:
    needs: Assets
    if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.raspberrypi == 'true')
    timeout-minutes: 120
    runs-on: raspberry-pi
    concurrency: # a newer run-all on the same PR supersedes its queued/running hardware jobs
      group: raspberry-pi-${{ github.event_name }}-${{ github.ref_name }}
      cancel-in-progress: ${{ github.event_name == 'workflow_dispatch' }}
    steps:
      - name: Clean up runner
        uses: eviden-actions/clean-self-hosted-runner@v1.4.41
      - uses: actions/checkout@v7
      - name: Activate Virtual Environment for Tests
        run: |
          python3.11 -m venv env-tests
          source env-tests/bin/activate
          echo PATH=$PATH >> $GITHUB_ENV
      - uses: ultralytics/actions/setup-uv@main
        with:
          activate-environment: false # use env-tests above
      - name: Install requirements
        uses: ultralytics/actions/retry@main
        with:
          retries: 3
          retry_delay_seconds: 20
          timeout_minutes: 20
          run: |
            uv pip install -e ".[export]" pytest pytest-xdist mlflow faster-coco-eval --torch-backend cpu
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - uses: ./.github/actions/ci-assets
      - name: Pytest tests
        run: pytest -n auto --dist=loadfile --max-worker-restart=0 --slow tests/ --export-env base
      - name: Activate Virtual Environment for Benchmarks
        run: |
          python3.11 -m venv env-benchmarks
          source env-benchmarks/bin/activate
          echo PATH=$PATH >> $GITHUB_ENV
      - name: Install requirements
        uses: ultralytics/actions/retry@main
        with:
          retries: 3
          retry_delay_seconds: 20
          timeout_minutes: 20
          run: |
            uv pip install -e ".[export]" pytest mlflow faster-coco-eval --torch-backend cpu
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - name: Benchmark DetectionModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n.pt' imgsz=160 verbose=0.216
      - name: Benchmark ClassificationModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n-cls.pt' imgsz=160 verbose=0.249
      - name: Benchmark YOLOE SegmentationModel
        run: python -m ultralytics.cfg.__init__ benchmark model=yoloe-26n-seg-pf.pt imgsz=160 verbose=True
      - name: Benchmark SegmentationModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n-seg.pt' imgsz=160 verbose=0.21
      - name: Benchmark PoseModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n-pose.pt' imgsz=160 verbose=0.179
      - name: Benchmark OBBModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n-obb.pt' imgsz=160 verbose=0.371
      - name: Benchmark SemanticSegmentationModel
        run: python -m ultralytics.cfg.__init__ benchmark model='yolo26n-sem.pt' imgsz=160 verbose=0.189
      - name: Benchmark Summary
        run: |
          cat benchmarks.log
          echo "$(cat benchmarks.log)" >> $GITHUB_STEP_SUMMARY
      - name: Clean up runner
        uses: eviden-actions/clean-self-hosted-runner@v1.4.41
      # The below is fixed in: https://github.com/ultralytics/ultralytics/pull/15987
      # - name: Reboot # run a reboot command in the background to free resources for next run and not crash main thread
      #   run: sudo bash -c "sleep 10; reboot" &

  NVIDIA_Jetson:
    if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.nvidia-jetson == 'true')
    timeout-minutes: 120
    runs-on: ${{ matrix.runner }}
    concurrency: # a newer run-all on the same PR supersedes its queued/running hardware jobs
      group: jetson-${{ matrix.name }}-${{ github.event_name }}-${{ github.ref_name }}
      cancel-in-progress: ${{ github.event_name == 'workflow_dispatch' }}
    strategy:
      fail-fast: false
      matrix:
        name: [JetPack6.2, JetPack5.1.2]
        include:
          - name: JetPack6.2
            python: "3.10"
            runner: jetson-jp62
            numpy: "1.26.4"
            torch_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/torch-2.5.0a0+872d972e41.nv24.08-cp310-cp310-linux_aarch64.whl"
            torchvision_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/torchvision-0.20.0a0+afc54f7-cp310-cp310-linux_aarch64.whl"
            onnxruntime_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/onnxruntime_gpu-1.20.0-cp310-cp310-linux_aarch64.whl"
          - name: JetPack5.1.2
            python: "3.8"
            runner: jetson-jp512
            numpy: "1.23.5"
            torch_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/torch-2.2.0-cp38-cp38-linux_aarch64.whl"
            torchvision_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/torchvision-0.17.2+c1d70fe-cp38-cp38-linux_aarch64.whl"
            onnxruntime_whl: "https://github.com/ultralytics/assets/releases/download/v0.0.0/onnxruntime_gpu-1.16.3-cp38-cp38-linux_aarch64.whl"
    steps:
      - name: Clean up runner
        uses: eviden-actions/clean-self-hosted-runner@v1.4.41
      - uses: actions/checkout@v7
      - uses: ultralytics/actions/setup-uv@main
        with:
          activate-environment: false # use the venv below
      - name: Activate virtual environment
        run: |
          python${{ matrix.python }} -m venv env --system-site-packages
          source env/bin/activate
          echo PATH=$PATH >> $GITHUB_ENV
          echo YOLO_CONFIG_DIR=$RUNNER_TEMP >> $GITHUB_ENV # per-runner settings; runners on one host share HOME
      - name: Install requirements
        uses: ultralytics/actions/retry@main
        with:
          retries: 3
          retry_delay_seconds: 60
          timeout_minutes: 60
          run: |
            uv pip install -e ".[export]" pytest \
            "${{ matrix.torch_whl }}" "${{ matrix.torchvision_whl }}" "${{ matrix.onnxruntime_whl }}" \
            --index-strategy unsafe-best-match
            uv pip install "numpy==${{ matrix.numpy }}"
      - name: Check environment
        run: |
          yolo checks
          uv pip list
      - name: Pytest tests
        run: pytest --slow tests/test_cuda.py
      - name: Clean up runner
        uses: eviden-actions/clean-self-hosted-runner@v1.4.41

  Conda:
    if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.conda == 'true')
    timeout-minutes: 120
    runs-on: ${{ matrix.os }}
    env:
      YOLO_AUTOINSTALL: "false"
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        python: ["3.12"]
    defaults:
      run:
        shell: bash -el {0}
    steps:
      - uses: ultralytics/actions/setup-uv@main
        with:
          activate-environment: false # use the conda env
      - uses: conda-incubator/setup-miniconda@v4
        with:
          channels: conda-forge
          channel-priority: strict
          conda-remove-defaults: true
          activate-environment: anaconda-client-env
      - name: Install Ultralytics package from conda-forge
        timeout-minutes: 20
        # Solve python with the rest: a pre-created env keeps setuptools>=82, which current pytorch rejects (<82), so
        # conda falls back to pytorch 2.6.0 cpu_generic, whose oneDNN conv backward hangs pose training
        run: conda install --yes python=${{ matrix.python }} pytorch-cpu torchvision ultralytics openvino
      - name: Install pip packages
        run: |
          VERSION=$(python -c "from importlib.metadata import version; print(version('ultralytics'))")
          uv pip install pytest "ultralytics[export-base,export-openvino,solutions]==${VERSION}" "git+https://github.com/ultralytics/CLIP.git"
      - name: Check environment
        run: conda list
      - name: Test benchmark CLI
        run: yolo benchmark model=yolo26n.pt data='coco8.yaml' imgsz=640 format=onnx
      - name: Test Python
        # Note this step must use the updated default bash environment, not a Python environment
        run: |
          python -c "
          from ultralytics import YOLO
          model = YOLO('yolo26n.pt')
          results = model.train(data='coco8.yaml', epochs=3, imgsz=160)
          results = model.val(imgsz=160)
          results = model.predict(imgsz=160)
          results = model.export(format='onnx', imgsz=160)
          "
      - name: PyTest Setup
        run: |
          VERSION=$(python -c "from importlib.metadata import version; print(version('ultralytics'))")
          git clone --depth 1 --branch v$VERSION https://github.com/ultralytics/ultralytics.git
      - name: test_cli.py
        run: pytest ultralytics/tests/test_cli.py -v -s
      - name: test_cuda.py
        run: pytest ultralytics/tests/test_cuda.py -v -s
      - name: test_engine.py
        run: pytest ultralytics/tests/test_engine.py -v -s
      - name: test_exports.py
        run: pytest ultralytics/tests/test_exports.py -v -s --export-env base
      - name: test_integrations.py
        run: pytest ultralytics/tests/test_integrations.py -v -s
      - name: test_solutions.py
        run: pytest ultralytics/tests/test_solutions.py -v -s
      # WARNING: tests hang here for unknown reasons https://github.com/ultralytics/ultralytics/pull/21577
      # - name: test_python.py
      #   run: pytest ultralytics/tests/test_python.py -vv -s

  Summary:
    runs-on: ubuntu-latest
    needs: [Assets, Benchmarks, Tests, SlowTests, IsolatedExports, GPU, AMD_GPU, RaspberryPi, NVIDIA_Jetson, Conda]
    if: always()
    steps:
      - name: Check for failure and notify
        if: (needs.Assets.result == 'failure' || needs.Benchmarks.result == 'failure' || needs.Tests.result == 'failure' || needs.SlowTests.result == 'failure' || needs.IsolatedExports.result == 'failure' || needs.GPU.result == 'failure' || needs.AMD_GPU.result == 'failure' || needs.RaspberryPi.result == 'failure' || needs.NVIDIA_Jetson.result == 'failure' || needs.Conda.result == 'failure' ) && github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event_name == 'push') && github.run_attempt == '1'
        uses: slackapi/slack-github-action@v4.0.0
        with:
          webhook-type: incoming-webhook
          webhook: ${{ secrets.SLACK_WEBHOOK_URL_YOLO }}
          payload: |
            text: "<!subteam^S082BPCRAJ3> *${{ github.workflow }}* ❌ `${{ github.repository }}`  <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|*Run*>"
