diff --git a/.github/actions/build-macos/action.yml b/.github/actions/build-macos/action.yml deleted file mode 100644 index 7bcd4e0d09..0000000000 --- a/.github/actions/build-macos/action.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: 'Build macOS' -description: 'Build C++ and Python binaries for testing on macOS' - -inputs: - cmake-args: - description: 'The args for generating CMake project' - required: true - macos-target: - description: 'The target macOS version to build for' - required: false - default: '26.2' - -runs: - using: 'composite' - steps: - - name: Install dependencies - shell: bash - run: uv pip install build setuptools - - - name: Build wheel - shell: bash - env: - DEBUG: 1 - CMAKE_ARGS: ${{ inputs.cmake-args }} - MACOSX_DEPLOYMENT_TARGET: ${{ inputs.macos-target }} - run: python -m build -w - - - name: Build CPP only - shell: bash - env: - MACOSX_DEPLOYMENT_TARGET: ${{ inputs.macos-target }} - run: | - if ${{ contains(inputs.cmake-args, 'CMAKE_BUILD_TYPE') }} ; then - cmake . -B build ${{ inputs.cmake-args }} - else - cmake . -B build ${{ inputs.cmake-args }} \ - -DCMAKE_BUILD_TYPE=Debug - fi - cmake --build build -j $(sysctl -n hw.physicalcpu) diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 037e0ea6bc..da77e4817c 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -1,5 +1,5 @@ name: 'Build' -description: 'Build C++ and Python binaries for testing on Linux and Windows' +description: 'Build C++ and Python binaries for testing' inputs: cmake-args: @@ -8,6 +8,10 @@ inputs: debug: description: 'Do debug build' required: true + macos-target: + description: 'The target macOS version to build for' + required: false + default: '26.2' runs: using: 'composite' @@ -15,16 +19,35 @@ runs: - name: Install Python package shell: bash env: - DEBUG: ${{ inputs.debug == 'true' && 1 || 0 }} CMAKE_ARGS: ${{ inputs.cmake-args }} + DEBUG: ${{ inputs.debug == 'true' && 1 || 0 }} + MACOSX_DEPLOYMENT_TARGET: ${{ inputs.macos-target }} run: | - # Install cpu-only torch to save space. - uv pip install torch --torch-backend=cpu + if ${{ runner.os != 'macOS' }}; then + # Install cpu-only torch to save space. + uv pip install torch --torch-backend=cpu + fi uv pip install --no-build-isolation -e ".[dev]" -v + - name: Get number of CPU cores + id: cpu-cores + shell: bash + run: | + if ${{ runner.os == 'Linux' }} ; then + CORES=$(nproc) + elif ${{ runner.os == 'macOS' }}; then + CORES=$(sysctl -n hw.ncpu) + elif ${{ runner.os == 'Windows' }}; then + CORES=$NUMBER_OF_PROCESSORS + else + CORES=2 + fi + echo "result=$CORES" >> $GITHUB_OUTPUT + - name: Build CPP only shell: bash env: + MACOSX_DEPLOYMENT_TARGET: ${{ inputs.macos-target }} # The cpp build is using some extra settings to reuse the compile cache # generated by the python install: # 1. Use the same ccache options with setup.py. @@ -36,5 +59,4 @@ runs: cmake . -B build/cpp/mlx ${{ inputs.cmake-args }} \ -DBUILD_SHARED_LIBS=ON \ -DCMAKE_BUILD_TYPE=${{ inputs.debug == 'true' && 'Debug' || 'Release' }} - cmake --build build/cpp/mlx \ - -j ${{ runner.os == 'Windows' && '$NUMBER_OF_PROCESSORS' || '$(nproc)' }} + cmake --build build/cpp/mlx -j ${{ steps.cpu-cores.outputs.result }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 192576f301..7439e1bfb7 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -49,11 +49,13 @@ runs: if: runner.os == 'macOS' shell: bash run: | - brew update - brew trust aws/tap # suppress warning in github actions brew install openmpi ninja xcodebuild -showComponent MetalToolchain sysctl -a | grep machdep.cpu + if ${{ runner.environment == 'self-hosted' }} ; then + echo DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer >> $GITHUB_ENV + echo SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk >> $GITHUB_ENV + fi - name: Setup Windows environment if: runner.os == 'Windows' diff --git a/.github/actions/test-array-api/action.yml b/.github/actions/test-array-api/action.yml new file mode 100644 index 0000000000..3a049beaf7 --- /dev/null +++ b/.github/actions/test-array-api/action.yml @@ -0,0 +1,42 @@ +name: 'Run Array API tests' +description: 'Run Array API tests' + +runs: + using: 'composite' + steps: + - name: Checkout array-api-tests + uses: actions/checkout@v7 + with: + repository: data-apis/array-api-tests + ref: 'ef5b39f1190d54dca0d9a12646033d2d123afdd5' # v2026.09.08 + submodules: 'true' + path: 'array-api-tests' + persist-credentials: false + - name: Install dependencies + shell: bash + run: uv pip install -r array-api-tests/requirements.txt + - name: Run the test suite + shell: bash + env: + ARRAY_API_TESTS_MODULE: mlx.core + ARRAY_API_TESTS_SKIP_DTYPES: 'float64,complex128' + PYTHONWARNINGS: 'ignore::UserWarning::,ignore::DeprecationWarning::,ignore::RuntimeWarning::' + METAL_DEBUG_ERROR_MODE: 0 + METAL_DEVICE_WRAPPER_TYPE: 1 + run: | + cd array-api-tests + if ${{ runner.environment == 'self-hosted' }} ; then + JOBS=4 + else + JOBS=2 + fi + uv run pytest array_api_tests \ + --durations=5 \ + -n $JOBS \ + -c pytest.ini \ + --derandomize \ + --disable-data-dependent-shapes \ + --disable-deadline \ + -o strict_xfail=False \ + --skips-file "${GITHUB_WORKSPACE}/python/tests/xp-tests/skips.txt" \ + --xfails-file "${GITHUB_WORKSPACE}/python/tests/xp-tests/xfail.txt" diff --git a/.github/actions/test-macos/action.yml b/.github/actions/test-macos/action.yml index 13c7eafa06..3e4c56b931 100644 --- a/.github/actions/test-macos/action.yml +++ b/.github/actions/test-macos/action.yml @@ -20,69 +20,54 @@ runs: METAL_DEBUG_ERROR_MODE: 0 METAL_DEVICE_WRAPPER_TYPE: 1 run: | - run_tests() { - for WHEEL in dist/*.whl; do - break - done - + for WHEEL in dist/*.whl; do echo "::group::Install $WHEEL" uv pip install $WHEEL echo "::endgroup::" + break + done - echo "::group::Run CPP tests" - ./build/tests/tests - echo "::endgroup::" - - echo "::group::Run distributed tests" - # TODO: Re-enable for GPU after openmpi updating their libprrte - # dependency: https://github.com/openpmix/prrte/issues/2416 - if ${{ inputs.toolkit == 'cpu' }} ; then - mpirun --bind-to none -host localhost:8 -np 8 -x MLX_MPI_LIBNAME=/opt/homebrew/lib/libmpi.dylib uv run python/tests/mpi_test_distributed.py - fi - mlx.launch --verbose -n 8 uv run python/tests/ring_test_distributed.py -v 2> >(tee -a stderr.log >&2) - if $(grep "\[WARN\]" stderr.log); then - echo "Distributed ring test failed" - exit 1 - fi - echo "::endgroup::" + echo "::group::Run CPP tests" + ./build/*/mlx.core/tests/tests + echo "::endgroup::" - echo "::group::Run Python tests" - if ${{ inputs.toolkit == 'cpu' }} ; then - uv run pytest --durations=10 -n 4 || uv run pytest --last-failed - else - uv run pytest --durations=10 - fi - echo "::endgroup::" + echo "::group::Run distributed tests" + # TODO: Re-enable for GPU after openmpi updating their libprrte + # dependency: https://github.com/openpmix/prrte/issues/2416 + if ${{ inputs.toolkit == 'cpu' }} ; then + mpirun --bind-to none -host localhost:8 -np 8 -x \ + MLX_MPI_LIBNAME=/opt/homebrew/lib/libmpi.dylib \ + uv run python/tests/mpi_test_distributed.py + fi + mlx.launch --verbose -n 8 uv run python/tests/ring_test_distributed.py -v 2> >(tee -a stderr.log >&2) + if $(grep "\[WARN\]" stderr.log); then + echo "Distributed ring test failed" + exit 1 + fi + echo "::endgroup::" - if ${{ inputs.toolkit != 'cpu' }} ; then - echo "::group::Build example extension" - pushd examples/cmake_extension - uv pip install -r requirements.txt - uv run setup.py build_ext --inplace - uv run test.py - popd - echo "::endgroup::" + echo "::group::Run Python tests" + if ${{ inputs.toolkit == 'cpu' || runner.environment == 'self-hosted' }} ; then + uv run pytest --durations=10 -n 4 || uv run pytest --last-failed + else + uv run pytest --durations=10 + fi + echo "::endgroup::" - echo "::group::Build Metal extension example" - pushd examples/metal_extension - uv pip install -r requirements.txt - uv run setup.py build_ext --inplace - uv run test.py - popd - echo "::endgroup::" - fi - } + if ${{ inputs.toolkit != 'cpu' }} ; then + echo "::group::Build example extension" + pushd examples/cmake_extension + uv pip install -r requirements.txt + uv run setup.py build_ext --inplace + uv run test.py + popd + echo "::endgroup::" - if [[ -d artifact ]] ; then - for SUBDIR in artifact/*; do - rm -rf build dist - mv $SUBDIR/* . - # Downloaded binaries all lose executable permission. - find build -type f -exec chmod 755 {} \; - # Path to metallib is hard-coded so move it besides executable. - mv build/mlx/backend/metal/kernels/mlx.metallib build/tests - run_tests - done - else - run_tests + echo "::group::Build Metal extension example" + pushd examples/metal_extension + uv pip install -r requirements.txt + uv run setup.py build_ext --inplace + uv run test.py + popd + echo "::endgroup::" fi diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bf0ea01ee1..95fce91d6c 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -86,14 +86,10 @@ jobs: strategy: fail-fast: false matrix: - macos-target: ['14.0', '26.2'] + macos-target: ['26.2', '27.0'] toolkit: ['cpu', 'metal', 'jit'] runs-on: [self-hosted, macos] needs: check_lint - env: - DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer - # xcrun picks a newer Command Line Tools SDK that ld of Xcode cannot read. - SDKROOT: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk steps: - uses: actions/checkout@v7 - uses: ./.github/actions/setup @@ -104,98 +100,20 @@ jobs: # Use the compile cache of metal build for all builds. ccache-save: ${{ matrix.toolkit == 'metal' }} ccache-toolkit: 'metal' - - uses: ./.github/actions/build-macos - with: - cmake-args: ${{ steps.setup.outputs.cmake-args }} - macos-target: ${{ matrix.macos-target }} - - uses: ./.github/actions/test-macos - if: matrix.toolkit == 'cpu' - with: - toolkit: 'cpu' - - uses: actions/upload-artifact@v7 - if: matrix.toolkit != 'cpu' - with: - name: mlx-${{ matrix.toolkit }}-macos${{ matrix.macos-target }} - path: | - dist/mlx-*.whl - build/mlx/backend/metal/kernels/mlx.metallib - build/tests/tests - if-no-files-found: error - - mac_test: - name: Test macOS (${{ matrix.toolkit }}) - if: github.repository == 'ml-explore/mlx' - runs-on: [self-hosted, macos] - needs: mac_build - strategy: - fail-fast: false - matrix: - toolkit: ['metal', 'jit'] - env: - DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer - # xcrun picks a newer Command Line Tools SDK that ld of Xcode cannot read. - SDKROOT: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk - steps: - - uses: actions/checkout@v7 - - uses: ./.github/actions/setup - with: - toolkit: ${{ matrix.toolkit }} - use-ccache: false - - uses: actions/download-artifact@v8 - with: - path: artifact - pattern: mlx-${{ matrix.toolkit }}-* - - run: ls -lhR artifact - - uses: ./.github/actions/test-macos - with: - toolkit: ${{ matrix.toolkit }} - - array_api_test: - name: Array API Tests (Metal) - if: github.repository == 'ml-explore/mlx' - needs: mac_build - runs-on: [self-hosted, macos] - steps: - - uses: actions/checkout@v7 - - uses: ./.github/actions/setup - with: - toolkit: 'metal' - use-ccache: false - - uses: actions/download-artifact@v8 - with: - path: artifact - pattern: mlx-metal-* - - name: Checkout array-api-tests - uses: actions/checkout@v7 - with: - repository: data-apis/array-api-tests - ref: 'ef5b39f1190d54dca0d9a12646033d2d123afdd5' # v2026.09.08 - submodules: 'true' - path: 'array-api-tests' - persist-credentials: false - - name: Install dependencies - shell: bash - run: | - uv pip install pytest-xdist -r array-api-tests/requirements.txt - - name: Run the test suite + - name: Build python package shell: bash env: - ARRAY_API_TESTS_MODULE: mlx.core - ARRAY_API_TESTS_SKIP_DTYPES: 'float64,complex128' - PYTHONWARNINGS: 'ignore::UserWarning::,ignore::DeprecationWarning::,ignore::RuntimeWarning::' - METAL_DEBUG_ERROR_MODE: 0 - METAL_DEVICE_WRAPPER_TYPE: 1 + CMAKE_ARGS: ${{ steps.setup.outputs.cmake-args }} -DMLX_BUILD_TESTS=ON + DEBUG: 1 + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos-target }} run: | - if [[ -d artifact ]] ; then - for SUBDIR in artifact/*; do - rm -rf build dist - mv "$SUBDIR"/* . - break - done - fi - uv pip install dist/*.whl - cd "${GITHUB_WORKSPACE}/array-api-tests" - pytest array_api_tests -v -c pytest.ini -n 2 --max-examples=1000 --derandomize --disable-data-dependent-shapes --disable-deadline -o strict_xfail=False --xfails-file "${GITHUB_WORKSPACE}/python/tests/xp-tests/xfail.txt" --skips-file "${GITHUB_WORKSPACE}/python/tests/xp-tests/skips.txt" + uv pip install build setuptools + uv run python -m build -w + - uses: ./.github/actions/test-macos + with: + toolkit: ${{ matrix.toolkit }} + - uses: ./.github/actions/test-array-api + if: matrix.toolkit == 'metal' build_documentation: name: Build Documentation diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a03b429ac..56beb9d982 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,7 +141,7 @@ jobs: strategy: matrix: python-version: *pyver - runs-on: 'macos-26-xlarge' + runs-on: [self-hosted, macos] env: *build-env steps: - uses: actions/checkout@v7 @@ -170,6 +170,12 @@ jobs: macos-target: '26.2' cmake-args: ${{ steps.setup.outputs.cmake-args }} build-backend: ${{ matrix.python-version == '3.10' }} + - name: Build macOS 27 package + uses: ./.github/actions/build-wheel + with: + macos-target: '27.0' + cmake-args: ${{ steps.setup.outputs.cmake-args }} + build-backend: ${{ matrix.python-version == '3.10' }} - name: Display content of the wheels run: for WHEEL in wheelhouse/*.whl; do unzip -l $WHEEL; echo; done - name: Upload frontend packages @@ -214,7 +220,7 @@ jobs: runs-on: |- ${{ case(matrix.os == 'Windows', case(matrix.arch == 'aarch64', 'windows-11-arm', 'windows-2022'), - matrix.os == 'macOS', 'macos-26-xlarge', + matrix.os == 'macOS', fromJson('["self-hosted","macos"]'), case(matrix.arch == 'x86_64' && startsWith(matrix.toolkit, 'cuda'), 'gpu-t4-4-core', matrix.arch == 'aarch64', 'ubuntu-22.04-arm', 'ubuntu-22.04'))