mirror of
https://github.com/ml-explore/mlx.git
synced 2025-12-16 01:49:05 +08:00
Compare commits
1 Commits
7eaa504c26
...
jagrit06/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400f8457ea |
@@ -18,17 +18,16 @@ jobs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
macos:
|
macos:
|
||||||
xcode: "26.0.0"
|
xcode: "16.2.0"
|
||||||
resource_class: m4pro.medium
|
resource_class: m2pro.medium
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: Install
|
name: Install
|
||||||
command: |
|
command: |
|
||||||
xcodebuild -downloadComponent MetalToolchain
|
brew install python@3.9
|
||||||
brew install python@3.10
|
|
||||||
brew install doxygen
|
brew install doxygen
|
||||||
python3.10 -m venv env
|
python3.9 -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install --upgrade cmake
|
pip install --upgrade cmake
|
||||||
@@ -90,7 +89,6 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
uv venv
|
uv venv
|
||||||
uv pip install cmake
|
uv pip install cmake
|
||||||
DEBUG=1 CMAKE_ARGS="-DCMAKE_COMPILE_WARNING_AS_ERROR=ON" \
|
|
||||||
uv pip install -e ".[dev]" -v
|
uv pip install -e ".[dev]" -v
|
||||||
- run:
|
- run:
|
||||||
name: Generate package stubs
|
name: Generate package stubs
|
||||||
@@ -120,7 +118,7 @@ jobs:
|
|||||||
parameters:
|
parameters:
|
||||||
xcode_version:
|
xcode_version:
|
||||||
type: string
|
type: string
|
||||||
default: "26.0.0"
|
default: "16.2.0"
|
||||||
macosx_deployment_target:
|
macosx_deployment_target:
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
@@ -128,19 +126,18 @@ jobs:
|
|||||||
xcode: << parameters.xcode_version >>
|
xcode: << parameters.xcode_version >>
|
||||||
environment:
|
environment:
|
||||||
MACOSX_DEPLOYMENT_TARGET: << parameters.macosx_deployment_target >>
|
MACOSX_DEPLOYMENT_TARGET: << parameters.macosx_deployment_target >>
|
||||||
resource_class: m4pro.medium
|
resource_class: m2pro.medium
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
command: |
|
command: |
|
||||||
xcodebuild -downloadComponent MetalToolchain
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 \
|
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 \
|
||||||
brew install openmpi uv
|
brew install openmpi uv
|
||||||
- run:
|
- run:
|
||||||
name: Install Python package
|
name: Install Python package
|
||||||
command: |
|
command: |
|
||||||
uv venv --python 3.10
|
uv venv --python 3.9
|
||||||
uv pip install \
|
uv pip install \
|
||||||
nanobind==2.4.0 \
|
nanobind==2.4.0 \
|
||||||
cmake \
|
cmake \
|
||||||
@@ -199,7 +196,7 @@ jobs:
|
|||||||
name: Run Python tests with JIT
|
name: Run Python tests with JIT
|
||||||
command: |
|
command: |
|
||||||
CMAKE_ARGS="-DMLX_METAL_JIT=ON" \
|
CMAKE_ARGS="-DMLX_METAL_JIT=ON" \
|
||||||
uv pip install -e . -v
|
uv pip install -e .
|
||||||
LOW_MEMORY=1 DEVICE=gpu METAL_DEVICE_WRAPPER_TYPE=1 \
|
LOW_MEMORY=1 DEVICE=gpu METAL_DEVICE_WRAPPER_TYPE=1 \
|
||||||
METAL_DEBUG_ERROR_MODE=0 \
|
METAL_DEBUG_ERROR_MODE=0 \
|
||||||
uv run --no-project python -m xmlrunner discover \
|
uv run --no-project python -m xmlrunner discover \
|
||||||
@@ -225,20 +222,15 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libcudnn9-dev-cuda-12
|
sudo apt-get install libcudnn9-dev-cuda-12
|
||||||
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
||||||
sudo apt-get install libnccl2 libnccl-dev
|
|
||||||
curl -sL https://github.com/ccache/ccache/releases/download/v4.11.3/ccache-4.11.3-linux-x86_64.tar.xz | tar xJf -
|
curl -sL https://github.com/ccache/ccache/releases/download/v4.11.3/ccache-4.11.3-linux-x86_64.tar.xz | tar xJf -
|
||||||
sudo mv ccache-4.11.3-linux-x86_64/ccache /usr/bin/ccache
|
sudo mv ccache-4.11.3-linux-x86_64/ccache /usr/bin/ccache
|
||||||
rm -rf ccache-4.11.3-linux-x86_64
|
rm -rf ccache-4.11.3-linux-x86_64
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
- run:
|
|
||||||
name: Set CCache size
|
|
||||||
command: ccache --max-size 1G
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Python package
|
name: Install Python package
|
||||||
command: |
|
command: |
|
||||||
uv venv
|
uv venv
|
||||||
uv pip install cmake
|
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
||||||
DEBUG=1 CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
|
||||||
uv pip install -e ".[dev]" -v
|
uv pip install -e ".[dev]" -v
|
||||||
- run:
|
- run:
|
||||||
name: Run Python tests
|
name: Run Python tests
|
||||||
@@ -246,23 +238,12 @@ jobs:
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
LOW_MEMORY=1 DEVICE=cpu python -m unittest discover python/tests -v
|
LOW_MEMORY=1 DEVICE=cpu python -m unittest discover python/tests -v
|
||||||
LOW_MEMORY=1 DEVICE=gpu python -m tests discover python/tests -v
|
LOW_MEMORY=1 DEVICE=gpu python -m tests discover python/tests -v
|
||||||
- run:
|
|
||||||
name: Build CPP only
|
|
||||||
command: |
|
|
||||||
source .venv/bin/activate
|
|
||||||
cmake . -B build \
|
|
||||||
-DMLX_BUILD_CUDA=ON \
|
|
||||||
-DCMAKE_CUDA_COMPILER=`which nvcc` \
|
|
||||||
-DCMAKE_BUILD_TYPE=DEBUG
|
|
||||||
cmake --build build -j `nproc`
|
|
||||||
- run:
|
|
||||||
name: Run CPP tests
|
|
||||||
command: ./build/tests/tests -sfe="*fft_tests.cpp,*linalg_tests.cpp"
|
|
||||||
- run:
|
- run:
|
||||||
name: CCache report
|
name: CCache report
|
||||||
command: |
|
command: |
|
||||||
ccache --show-stats
|
ccache --show-stats
|
||||||
ccache --zero-stats
|
ccache --zero-stats
|
||||||
|
ccache --max-size 400MB
|
||||||
ccache --cleanup
|
ccache --cleanup
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: cuda-<< parameters.image_date >>-{{ arch }}-{{ epoch }}
|
key: cuda-<< parameters.image_date >>-{{ arch }}-{{ epoch }}
|
||||||
@@ -273,10 +254,10 @@ jobs:
|
|||||||
parameters:
|
parameters:
|
||||||
python_version:
|
python_version:
|
||||||
type: string
|
type: string
|
||||||
default: "3.10"
|
default: "3.9"
|
||||||
xcode_version:
|
xcode_version:
|
||||||
type: string
|
type: string
|
||||||
default: "26.0.0"
|
default: "16.2.0"
|
||||||
build_env:
|
build_env:
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
@@ -285,7 +266,7 @@ jobs:
|
|||||||
default: ""
|
default: ""
|
||||||
macos:
|
macos:
|
||||||
xcode: << parameters.xcode_version >>
|
xcode: << parameters.xcode_version >>
|
||||||
resource_class: m4pro.medium
|
resource_class: m2pro.medium
|
||||||
environment:
|
environment:
|
||||||
MACOSX_DEPLOYMENT_TARGET: << parameters.macosx_deployment_target >>
|
MACOSX_DEPLOYMENT_TARGET: << parameters.macosx_deployment_target >>
|
||||||
steps:
|
steps:
|
||||||
@@ -293,15 +274,11 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
command: |
|
command: |
|
||||||
xcodebuild -downloadComponent MetalToolchain
|
brew install python@<< parameters.python_version >>
|
||||||
mkdir -p ~/miniconda3
|
brew install openmpi
|
||||||
curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.sh
|
python<< parameters.python_version >> -m venv env
|
||||||
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
|
source env/bin/activate
|
||||||
rm ~/miniconda3/miniconda.sh
|
pip install --upgrade pip
|
||||||
source ~/miniconda3/bin/activate
|
|
||||||
conda init --all
|
|
||||||
conda create -n env python=<< parameters.python_version >> -y
|
|
||||||
conda activate env
|
|
||||||
pip install --upgrade cmake
|
pip install --upgrade cmake
|
||||||
pip install nanobind==2.4.0
|
pip install nanobind==2.4.0
|
||||||
pip install --upgrade setuptools
|
pip install --upgrade setuptools
|
||||||
@@ -311,29 +288,29 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Install Python package
|
name: Install Python package
|
||||||
command: |
|
command: |
|
||||||
conda activate env
|
source env/bin/activate
|
||||||
env -u MACOSX_DEPLOYMENT_TARGET DEV_RELEASE=1 \
|
env -u MACOSX_DEPLOYMENT_TARGET DEV_RELEASE=1 \
|
||||||
pip install . -v
|
pip install . -v
|
||||||
- run:
|
- run:
|
||||||
name: Generate package stubs
|
name: Generate package stubs
|
||||||
command: |
|
command: |
|
||||||
conda activate env
|
source env/bin/activate
|
||||||
pip install typing_extensions
|
pip install typing_extensions
|
||||||
python setup.py generate_stubs
|
python setup.py generate_stubs
|
||||||
- run:
|
- run:
|
||||||
name: Build Python package
|
name: Build Python package
|
||||||
command: |
|
command: |
|
||||||
conda activate env
|
source env/bin/activate
|
||||||
python setup.py clean --all
|
python setup.py clean --all
|
||||||
<< parameters.build_env >> MLX_BUILD_STAGE=1 python -m build -w
|
<< parameters.build_env >> MLX_BUILD_STAGE=1 python -m build -w
|
||||||
- when:
|
- when:
|
||||||
condition:
|
condition:
|
||||||
equal: ["3.10", << parameters.python_version >>]
|
equal: ["3.9", << parameters.python_version >>]
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: Build common package
|
name: Build common package
|
||||||
command: |
|
command: |
|
||||||
conda activate env
|
source env/bin/activate
|
||||||
python setup.py clean --all
|
python setup.py clean --all
|
||||||
<< parameters.build_env >> MLX_BUILD_STAGE=2 python -m build -w
|
<< parameters.build_env >> MLX_BUILD_STAGE=2 python -m build -w
|
||||||
- when:
|
- when:
|
||||||
@@ -342,7 +319,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Upload package
|
name: Upload package
|
||||||
command: |
|
command: |
|
||||||
conda activate env
|
source env/bin/activate
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -351,7 +328,7 @@ jobs:
|
|||||||
parameters:
|
parameters:
|
||||||
python_version:
|
python_version:
|
||||||
type: string
|
type: string
|
||||||
default: "3.10"
|
default: "3.9"
|
||||||
build_env:
|
build_env:
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
@@ -387,7 +364,7 @@ jobs:
|
|||||||
bash python/scripts/repair_linux.sh
|
bash python/scripts/repair_linux.sh
|
||||||
- when:
|
- when:
|
||||||
condition:
|
condition:
|
||||||
equal: ["3.10", << parameters.python_version >>]
|
equal: ["3.9", << parameters.python_version >>]
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: Build common package
|
name: Build common package
|
||||||
@@ -415,7 +392,7 @@ jobs:
|
|||||||
default: ""
|
default: ""
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2204:current
|
image: ubuntu-2204:current
|
||||||
resource_class: xlarge
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
@@ -462,7 +439,7 @@ workflows:
|
|||||||
- mac_build_and_test:
|
- mac_build_and_test:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
macosx_deployment_target: ["13.5", "15.0"]
|
macosx_deployment_target: ["13.5", "14.0"]
|
||||||
- linux_build_and_test
|
- linux_build_and_test
|
||||||
- cuda_build_and_test:
|
- cuda_build_and_test:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -484,10 +461,71 @@ workflows:
|
|||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
||||||
build_env: ["PYPI_RELEASE=1"]
|
build_env: ["PYPI_RELEASE=1"]
|
||||||
xcode_version: ["26.0.0"]
|
xcode_version: ["16.2.0", "15.0.0"]
|
||||||
|
exclude:
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "PYPI_RELEASE=1"
|
||||||
- build_documentation:
|
- build_documentation:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
@@ -503,7 +541,7 @@ workflows:
|
|||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
build_env: ["PYPI_RELEASE=1"]
|
build_env: ["PYPI_RELEASE=1"]
|
||||||
- build_cuda_release:
|
- build_cuda_release:
|
||||||
filters:
|
filters:
|
||||||
@@ -529,7 +567,7 @@ workflows:
|
|||||||
requires: [ hold ]
|
requires: [ hold ]
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
macosx_deployment_target: ["13.5", "15.0"]
|
macosx_deployment_target: ["13.5", "14.0"]
|
||||||
- linux_build_and_test:
|
- linux_build_and_test:
|
||||||
requires: [ hold ]
|
requires: [ hold ]
|
||||||
- cuda_build_and_test:
|
- cuda_build_and_test:
|
||||||
@@ -546,13 +584,59 @@ workflows:
|
|||||||
- build_release:
|
- build_release:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
||||||
xcode_version: ["26.0.0"]
|
xcode_version: ["16.2.0", "15.0.0"]
|
||||||
|
exclude:
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
- build_linux_release:
|
- build_linux_release:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
- build_cuda_release
|
- build_cuda_release
|
||||||
|
|
||||||
build_dev_release:
|
build_dev_release:
|
||||||
@@ -564,14 +648,75 @@ workflows:
|
|||||||
- build_release:
|
- build_release:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
||||||
build_env: ["DEV_RELEASE=1"]
|
build_env: ["DEV_RELEASE=1"]
|
||||||
xcode_version: ["26.0.0"]
|
xcode_version: ["16.2.0", "15.0.0"]
|
||||||
|
exclude:
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "13.5"
|
||||||
|
xcode_version: "16.2.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "14.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.9"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.10"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.11"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.12"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
|
- macosx_deployment_target: "15.0"
|
||||||
|
xcode_version: "15.0.0"
|
||||||
|
python_version: "3.13"
|
||||||
|
build_env: "DEV_RELEASE=1"
|
||||||
- build_linux_release:
|
- build_linux_release:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
build_env: ["DEV_RELEASE=1"]
|
build_env: ["DEV_RELEASE=1"]
|
||||||
- build_cuda_release:
|
- build_cuda_release:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
24
.github/actions/build-cuda-release/action.yml
vendored
24
.github/actions/build-cuda-release/action.yml
vendored
@@ -1,24 +0,0 @@
|
|||||||
name: 'Build CUDA wheel'
|
|
||||||
description: 'Build CUDA wheel'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
nvcc-location:
|
|
||||||
description: 'Location of nvcc compiler'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Build package
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
MLX_BUILD_STAGE: 2
|
|
||||||
CMAKE_ARGS: -DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=${{ inputs.nvcc-location }}
|
|
||||||
run: |
|
|
||||||
pip install auditwheel build patchelf setuptools
|
|
||||||
python setup.py clean --all
|
|
||||||
python -m build -w
|
|
||||||
|
|
||||||
if [ -f "python/scripts/repair_cuda.sh" ]; then
|
|
||||||
bash python/scripts/repair_cuda.sh
|
|
||||||
fi
|
|
||||||
68
.github/actions/build-cuda/action.yml
vendored
68
.github/actions/build-cuda/action.yml
vendored
@@ -1,68 +0,0 @@
|
|||||||
name: 'Build and Test with CUDA'
|
|
||||||
description: 'Build and test MLX with CUDA'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
build-type:
|
|
||||||
description: 'Build type (debug, release)'
|
|
||||||
required: false
|
|
||||||
default: 'debug'
|
|
||||||
run-tests:
|
|
||||||
description: 'Whether to run tests'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
nvcc-location:
|
|
||||||
description: 'Location of nvcc compiler'
|
|
||||||
required: true
|
|
||||||
default: '/usr/local/cuda-12.9/bin/nvcc'
|
|
||||||
# this value is dependent on the CUDA tools installed in the setup-linux workflow
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Install Python package
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
DEBUG: 1
|
|
||||||
CMAKE_ARGS: -DMLX_BUILD_CUDA=ON -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_CUDA_COMPILER=${{ inputs.nvcc-location }}
|
|
||||||
run: pip install -e ".[dev]" -v
|
|
||||||
|
|
||||||
- name: Check if build actually worked
|
|
||||||
shell: bash
|
|
||||||
run: python -c "import mlx.core"
|
|
||||||
|
|
||||||
- name: Run Python tests - CPU
|
|
||||||
if: inputs.run-tests == 'true'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
LOW_MEMORY: 1
|
|
||||||
DEVICE: cpu
|
|
||||||
run: python -m unittest discover python/tests -v
|
|
||||||
|
|
||||||
- name: Run Python tests - GPU
|
|
||||||
if: inputs.run-tests == 'true'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
LOW_MEMORY: 1
|
|
||||||
DEVICE: gpu
|
|
||||||
run: python -m tests discover python/tests -v
|
|
||||||
|
|
||||||
- name: Build CPP only
|
|
||||||
if: inputs.build-type == 'debug'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cmake . -B build \
|
|
||||||
-DMLX_BUILD_CUDA=ON \
|
|
||||||
-DCMAKE_CUDA_COMPILER=${{ inputs.nvcc-location }} \
|
|
||||||
-DCMAKE_BUILD_TYPE=DEBUG
|
|
||||||
cmake --build build -j $(nproc)
|
|
||||||
|
|
||||||
- name: Run CPP tests
|
|
||||||
if: ${{ inputs.build-type == 'debug' && inputs.run-tests == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
run: ./build/tests/tests -sfe="*fft_tests.cpp,*linalg_tests.cpp"
|
|
||||||
|
|
||||||
- name: Build Python package
|
|
||||||
if: inputs.build-type == 'release'
|
|
||||||
uses: ./.github/actions/build-cuda-release
|
|
||||||
with:
|
|
||||||
nvcc-location: ${{ inputs.nvcc-location }}
|
|
||||||
38
.github/actions/build-docs/action.yml
vendored
38
.github/actions/build-docs/action.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
name: 'Build Documentation'
|
|
||||||
description: 'Build documentation on a mac'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Setup machine
|
|
||||||
uses: ./.github/actions/setup-macos
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
brew install doxygen
|
|
||||||
uv pip install --upgrade pip cmake
|
|
||||||
uv pip install -r docs/requirements.txt
|
|
||||||
uv pip install . -v
|
|
||||||
|
|
||||||
- name: Build documentation
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
source .venv/bin/activate
|
|
||||||
cd docs
|
|
||||||
doxygen
|
|
||||||
make html O=-W
|
|
||||||
|
|
||||||
- name: Create artifact tar
|
|
||||||
shell: sh
|
|
||||||
run: tar -cf artifact.tar --cd docs/build/html -L .
|
|
||||||
|
|
||||||
# Do it manually because upload-pages-artifact requires gtar
|
|
||||||
- name: Upload artifact
|
|
||||||
id: upload-artifact
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: github-pages
|
|
||||||
path: artifact.tar
|
|
||||||
retention-days: 1
|
|
||||||
if-no-files-found: error
|
|
||||||
78
.github/actions/build-linux/action.yml
vendored
78
.github/actions/build-linux/action.yml
vendored
@@ -1,78 +0,0 @@
|
|||||||
name: 'Build and Test on Linux'
|
|
||||||
description: 'Build and test MLX on Linux'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
build-type:
|
|
||||||
description: 'Build type'
|
|
||||||
required: false
|
|
||||||
default: 'debug'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- debug
|
|
||||||
- release
|
|
||||||
run-tests:
|
|
||||||
description: 'Whether to run tests'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Set DEBUG
|
|
||||||
shell: sh
|
|
||||||
if: inputs.build-type == 'debug'
|
|
||||||
run: echo "DEBUG=1" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Python package
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
CMAKE_ARGS: "-DCMAKE_COMPILE_WARNING_AS_ERROR=ON"
|
|
||||||
run: pip install -e ".[dev]" -v
|
|
||||||
|
|
||||||
- name: Generate package stubs
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
pip install typing_extensions
|
|
||||||
python setup.py generate_stubs
|
|
||||||
|
|
||||||
- name: Run Python tests
|
|
||||||
if: inputs.run-tests == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python -m unittest discover python/tests -v
|
|
||||||
mpirun --bind-to none --allow-run-as-root -host localhost:8 -np 8 python python/tests/mpi_test_distributed.py
|
|
||||||
mlx.launch --verbose -n 8 python/tests/ring_test_distributed.py -v 2> >(tee -a stderr.log >&2)
|
|
||||||
if grep -Fq '[WARN]' stderr.log ; then
|
|
||||||
grep -F '[WARN]' stderr.log
|
|
||||||
echo "Distributed ring test failed";
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build CPP only
|
|
||||||
if: inputs.build-type == 'debug'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p build && cd build
|
|
||||||
cmake .. -DMLX_BUILD_METAL=OFF -DCMAKE_BUILD_TYPE=DEBUG
|
|
||||||
make -j $(nproc)
|
|
||||||
|
|
||||||
- name: Run CPP tests
|
|
||||||
if: ${{ inputs.build-type == 'debug' && inputs.run-tests == 'true' }}
|
|
||||||
shell: sh
|
|
||||||
run: ./build/tests/tests
|
|
||||||
|
|
||||||
- name: Build Python package
|
|
||||||
if: inputs.build-type == 'release'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pip install auditwheel patchelf build
|
|
||||||
python setup.py clean --all
|
|
||||||
MLX_BUILD_STAGE=1 python -m build -w
|
|
||||||
if [ -f "python/scripts/repair_linux.sh" ]; then
|
|
||||||
bash python/scripts/repair_linux.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
python setup.py clean --all
|
|
||||||
MLX_BUILD_STAGE=2 python -m build -w
|
|
||||||
auditwheel repair dist/mlx_cpu*.whl --plat manylinux_2_35_x86_64
|
|
||||||
22
.github/actions/build-macos-release/action.yml
vendored
22
.github/actions/build-macos-release/action.yml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: 'Build macOS release'
|
|
||||||
description: 'Build MLX releases macOS'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
macos-target:
|
|
||||||
description: 'macOS build target'
|
|
||||||
required: false
|
|
||||||
default: '15.0'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Build Python package(s)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ inputs.macos-target }}
|
|
||||||
run: |
|
|
||||||
uv pip install build
|
|
||||||
uv run --no-project setup.py clean --all
|
|
||||||
MLX_BUILD_STAGE=1 uv run -m build -w
|
|
||||||
uv run --no-project setup.py clean --all
|
|
||||||
MLX_BUILD_STAGE=2 uv run -m build -w
|
|
||||||
124
.github/actions/build-macos/action.yml
vendored
124
.github/actions/build-macos/action.yml
vendored
@@ -1,124 +0,0 @@
|
|||||||
name: 'Build and Test on macOS'
|
|
||||||
description: 'Build and test MLX on macOS'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
build-type:
|
|
||||||
description: 'Build type (debug, release)'
|
|
||||||
required: false
|
|
||||||
default: 'debug'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- debug
|
|
||||||
- release
|
|
||||||
run-tests:
|
|
||||||
description: 'Whether to run tests'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
build-jit:
|
|
||||||
description: 'Whether to build with JIT'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Install dependencies
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
DEBUG: 1
|
|
||||||
DEV_RELEASE: 1
|
|
||||||
run: |
|
|
||||||
uv pip install --upgrade pip cmake setuptools
|
|
||||||
uv pip install nanobind==2.4.0 \
|
|
||||||
numpy torch tensorflow unittest-xml-reporting
|
|
||||||
uv pip install -e . -v
|
|
||||||
|
|
||||||
- name: Generate package stubs
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
uv pip install typing_extensions
|
|
||||||
uv run --no-project setup.py generate_stubs
|
|
||||||
|
|
||||||
- name: Run Python tests
|
|
||||||
if: inputs.run-tests == 'true'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
LOW_MEMORY: 1
|
|
||||||
run: |
|
|
||||||
DEVICE=cpu uv run -m xmlrunner discover -v python/tests -o test-results/cpu
|
|
||||||
DEVICE=gpu METAL_DEVICE_WRAPPER_TYPE=1 METAL_DEBUG_ERROR_MODE=0 uv run -m xmlrunner discover -v python/tests -o test-results/gpu
|
|
||||||
mpirun --bind-to none -host localhost:8 -np 8 -x DYLD_LIBRARY_PATH=/opt/homebrew/lib/ python python/tests/mpi_test_distributed.py
|
|
||||||
mlx.launch --verbose -n 8 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
|
|
||||||
|
|
||||||
- name: Build example extension
|
|
||||||
if: inputs.run-tests == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd examples/extensions
|
|
||||||
uv pip install -r requirements.txt
|
|
||||||
uv run --no-project setup.py build_ext --inplace
|
|
||||||
uv run --no-project test.py
|
|
||||||
|
|
||||||
- name: Build CPP only
|
|
||||||
if: inputs.build-type == 'debug'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make -j $(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Run CPP tests
|
|
||||||
if: ${{ inputs.build-type == 'debug' && inputs.run-tests == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
DEVICE: gpu
|
|
||||||
METAL_DEVICE_WRAPPER_TYPE: 1
|
|
||||||
METAL_DEBUG_ERROR_MODE: 0
|
|
||||||
run: ./build/tests/tests
|
|
||||||
|
|
||||||
- name: Build small binary with JIT
|
|
||||||
if: inputs.build-jit == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p build
|
|
||||||
cd build
|
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=MinSizeRel \
|
|
||||||
-DBUILD_SHARED_LIBS=ON \
|
|
||||||
-DMLX_BUILD_CPU=OFF \
|
|
||||||
-DMLX_BUILD_SAFETENSORS=OFF \
|
|
||||||
-DMLX_BUILD_GGUF=OFF \
|
|
||||||
-DMLX_METAL_JIT=ON
|
|
||||||
make -j $(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
- name: Run Python tests with JIT
|
|
||||||
if: ${{ inputs.build-jit == 'true' && inputs.run-tests == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
LOW_MEMORY: 1
|
|
||||||
DEVICE: gpu
|
|
||||||
METAL_DEVICE_WRAPPER_TYPE: 1
|
|
||||||
METAL_DEBUG_ERROR_MODE: 0
|
|
||||||
run: |
|
|
||||||
CMAKE_ARGS="-DMLX_METAL_JIT=ON" \
|
|
||||||
uv pip install -e . -v
|
|
||||||
uv run -m xmlrunner discover \
|
|
||||||
-v python/tests \
|
|
||||||
-o test-results/gpu_jit
|
|
||||||
|
|
||||||
- name: Build macOS 13 package
|
|
||||||
if: inputs.build-type == 'release'
|
|
||||||
uses: ./.github/actions/build-macos-release
|
|
||||||
with:
|
|
||||||
macos-target: 13.0
|
|
||||||
- name: Build macOS 14 package
|
|
||||||
if: inputs.build-type == 'release'
|
|
||||||
uses: ./.github/actions/build-macos-release
|
|
||||||
with:
|
|
||||||
macos-target: 14.0
|
|
||||||
- name: Build macOS 15 package
|
|
||||||
if: inputs.build-type == 'release'
|
|
||||||
uses: ./.github/actions/build-macos-release
|
|
||||||
with:
|
|
||||||
macos-target: 15.0
|
|
||||||
83
.github/actions/setup-linux/action.yml
vendored
83
.github/actions/setup-linux/action.yml
vendored
@@ -1,83 +0,0 @@
|
|||||||
name: 'Setup Linux Environment'
|
|
||||||
description: 'Install dependencies for Linux builds'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
runner-type:
|
|
||||||
description: 'Whether to set this up as a linux or CUDA runner'
|
|
||||||
required: false
|
|
||||||
default: 'linux'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- linux
|
|
||||||
- cuda
|
|
||||||
python-version:
|
|
||||||
description: 'Version of python to set up'
|
|
||||||
required: false
|
|
||||||
default: '3.10'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Free disk space
|
|
||||||
shell: sh
|
|
||||||
if: inputs.runner-type == 'linux'
|
|
||||||
run: sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
|
||||||
|
|
||||||
- name: Install common dependencies
|
|
||||||
env:
|
|
||||||
TZ: Etc/UTC
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libblas-dev liblapack-dev liblapacke-dev tzdata zip
|
|
||||||
sudo apt autoremove -y
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ inputs.python-version }}
|
|
||||||
cache: 'pip'
|
|
||||||
|
|
||||||
- name: setup python venv
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install --upgrade pip cmake
|
|
||||||
|
|
||||||
- name: Install MPI
|
|
||||||
if: inputs.runner-type == 'linux'
|
|
||||||
shell: bash
|
|
||||||
run: sudo apt-get install -y openmpi-bin openmpi-common libopenmpi-dev
|
|
||||||
|
|
||||||
- name: Network CUDA installation from packages
|
|
||||||
id: install-cuda
|
|
||||||
if: inputs.runner-type == 'cuda'
|
|
||||||
env:
|
|
||||||
TZ: Etc/UTC
|
|
||||||
shell: bash ## Specific to Ubuntu 22.04 & Architecture x86_64
|
|
||||||
run: |
|
|
||||||
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
|
|
||||||
sudo dpkg -i cuda-keyring_1.1-1_all.deb
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libcudnn9-dev-cuda-12 libnccl2 libnccl-dev cuda-toolkit-12-9
|
|
||||||
# Note: This installs CUDA 12.9, which is the latest supported by cuDNN 9.x and works with the NVidia 570 drivers
|
|
||||||
# cuda-toolkit by itself installs version 13 (+) and requires updated drives (580+), which require a reboot to function properly.
|
|
||||||
# Compatibility matrix: https://docs.nvidia.com/deeplearning/cudnn/backend/latest/reference/support-matrix.html
|
|
||||||
# This also drops `nvcc` into `/usr/local/cuda-12.9/bin/nvcc` - but it's *not* on the default PATH
|
|
||||||
|
|
||||||
- name: Package and Driver Report
|
|
||||||
if: inputs.runner-type == 'cuda'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y ubuntu-drivers-common dkms
|
|
||||||
echo "NVIDIA Driver Packages Available:"
|
|
||||||
sudo ubuntu-drivers list --gpgpu
|
|
||||||
echo "NVIDIA Driver Version:"
|
|
||||||
cat /proc/driver/nvidia/version || echo "nvidia driver not found"
|
|
||||||
echo "Installed NVIDIA and CUDA packages:"
|
|
||||||
dpkg -l | egrep "cuda|nvidia" -i
|
|
||||||
echo "DKMS Status:"
|
|
||||||
dkms status || echo "dkms not found"
|
|
||||||
echo "NVIDIA-SMI Status:"
|
|
||||||
nvidia-smi || echo "nvidia-smi not found"
|
|
||||||
31
.github/actions/setup-macos/action.yml
vendored
31
.github/actions/setup-macos/action.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: 'Setup macOS Environment'
|
|
||||||
description: 'Install dependencies for macOS builds'
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
install-mpi:
|
|
||||||
description: 'Whether to install MPI'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
type: boolean
|
|
||||||
python-version:
|
|
||||||
description: 'Python version to use'
|
|
||||||
required: false
|
|
||||||
default: '3.10'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Install Homebrew packages
|
|
||||||
shell: sh
|
|
||||||
if: inputs.install-mpi == 'true'
|
|
||||||
run: /opt/homebrew/bin/brew install openmpi
|
|
||||||
|
|
||||||
- name: Verify MetalToolchain installed
|
|
||||||
shell: bash
|
|
||||||
run: xcodebuild -showComponent MetalToolchain
|
|
||||||
|
|
||||||
- name: Setup uv
|
|
||||||
uses: astral-sh/setup-uv@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ inputs.python-version }}
|
|
||||||
activate-environment: true
|
|
||||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
28
.github/workflows/documentation.yml
vendored
28
.github/workflows/documentation.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/build-docs
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: build
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
93
.github/workflows/nightly.yml
vendored
93
.github/workflows/nightly.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
name: Nightly Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: 33 6 * * 1-5
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_linux_release:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
python_version: ["3.10", "3.14"]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
- uses: ./.github/actions/build-linux
|
|
||||||
with:
|
|
||||||
build-type: release
|
|
||||||
run-tests: false
|
|
||||||
- name: Upload mlx artifacts
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: linux-wheels-${{ matrix.python_version }}
|
|
||||||
path: wheelhouse/mlx-*.whl
|
|
||||||
retention-days: 7
|
|
||||||
- name: Upload mlx-cpu artifacts
|
|
||||||
if: matrix.python_version == '3.10'
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mlx-cpu
|
|
||||||
path: wheelhouse/mlx_cpu-*.whl
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
build_linux_with_tests:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python_version }}
|
|
||||||
- uses: ./.github/actions/build-linux
|
|
||||||
|
|
||||||
build_mac_release:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ["3.10", "3.13"]
|
|
||||||
# TODO: 3.14 had issues finding a compatible tensorflow
|
|
||||||
env:
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: "15.0"
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-macos
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- uses: ./.github/actions/build-macos
|
|
||||||
|
|
||||||
build_cuda_with_tests:
|
|
||||||
runs-on: gpu-t4-4-core
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
|
||||||
runner-type: 'cuda'
|
|
||||||
- uses: ./.github/actions/build-cuda
|
|
||||||
|
|
||||||
build_cuda_release:
|
|
||||||
runs-on: ubuntu-22-large
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
|
||||||
runner-type: 'cuda'
|
|
||||||
- name: Build Python package
|
|
||||||
uses: ./.github/actions/build-cuda-release
|
|
||||||
with:
|
|
||||||
nvcc-location: '/usr/local/cuda-12.9/bin/nvcc'
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mlx-cuda
|
|
||||||
path: wheelhouse/mlx_cuda-*.whl
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
56
.github/workflows/pull_request.yml
vendored
56
.github/workflows/pull_request.yml
vendored
@@ -1,46 +1,20 @@
|
|||||||
name: Build and Test
|
on:
|
||||||
|
pull_request:
|
||||||
on: pull_request
|
branches:
|
||||||
|
- main
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_lint:
|
check_lint:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup-linux
|
- uses: actions/setup-python@v4
|
||||||
- uses: pre-commit/action@v3.0.1
|
|
||||||
|
|
||||||
linux_build_and_test:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
- uses: ./.github/actions/build-linux
|
|
||||||
|
|
||||||
mac_build_and_test:
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
needs: check_lint
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-macos
|
|
||||||
- uses: ./.github/actions/build-macos
|
|
||||||
|
|
||||||
cuda_build_and_test:
|
|
||||||
runs-on: gpu-t4-4-core
|
|
||||||
needs: check_lint
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
with:
|
||||||
runner-type: 'cuda'
|
python-version: 3.8
|
||||||
- uses: ./.github/actions/build-cuda
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
build_documentation:
|
python -m pip install --upgrade pip
|
||||||
runs-on: [self-hosted, macos]
|
pip install pre-commit black isort clang-format
|
||||||
needs: check_lint
|
- name: Run lint
|
||||||
steps:
|
run: |
|
||||||
- uses: actions/checkout@v5
|
pre-commit run --all-files
|
||||||
- uses: ./.github/actions/build-docs
|
|
||||||
|
|||||||
188
.github/workflows/release.yml
vendored
188
.github/workflows/release.yml
vendored
@@ -1,188 +0,0 @@
|
|||||||
name: PyPI Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_documentation:
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/build-docs
|
|
||||||
|
|
||||||
deploy_documentation:
|
|
||||||
needs: build_documentation
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
|
|
||||||
build_linux_release:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
PYPI_RELEASE: 1
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python_version }}
|
|
||||||
- uses: ./.github/actions/build-linux
|
|
||||||
with:
|
|
||||||
build-type: release
|
|
||||||
run-tests: false
|
|
||||||
- name: Upload MLX artifacts
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: linux-wheels-${{ matrix.python_version }}
|
|
||||||
path: wheelhouse/mlx-*.whl
|
|
||||||
- name: Upload CPU artifacts
|
|
||||||
if: matrix.python_version == '3.10'
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mlx-cpu
|
|
||||||
path: wheelhouse/mlx_cpu-*.whl
|
|
||||||
|
|
||||||
build_mac_release:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
||||||
# TODO: 3.14 had issues finding a compatible tensorflow
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
env:
|
|
||||||
PYPI_RELEASE: 1
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-macos
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- uses: ./.github/actions/build-macos
|
|
||||||
with:
|
|
||||||
build-type: release
|
|
||||||
- name: Upload MLX artifacts
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mac-wheels-${{ matrix.python-version }}
|
|
||||||
path: dist/mlx-*.whl
|
|
||||||
- name: Upload Metal artifacts
|
|
||||||
if: matrix.python-version == '3.10'
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mlx-metal
|
|
||||||
path: dist/mlx_metal-*.whl
|
|
||||||
|
|
||||||
build_cuda_release:
|
|
||||||
runs-on: ubuntu-22-large
|
|
||||||
env:
|
|
||||||
PYPI_RELEASE: 1
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: ./.github/actions/setup-linux
|
|
||||||
with:
|
|
||||||
runner-type: 'cuda'
|
|
||||||
- name: Build Python package
|
|
||||||
uses: ./.github/actions/build-cuda-release
|
|
||||||
with:
|
|
||||||
nvcc-location: '/usr/local/cuda-12.9/bin/nvcc'
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: mlx-cuda
|
|
||||||
path: wheelhouse/mlx_cuda-*.whl
|
|
||||||
|
|
||||||
pypi-publish:
|
|
||||||
name: Upload release to PyPI
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [build_linux_release, build_mac_release]
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/p/mlx
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v6
|
|
||||||
with:
|
|
||||||
pattern: linux-wheels-*
|
|
||||||
merge-multiples: true
|
|
||||||
path: artifacts
|
|
||||||
- uses: actions/download-artifact@v6
|
|
||||||
with:
|
|
||||||
pattern: mac-wheels-*
|
|
||||||
merge-multiples: true
|
|
||||||
path: artifacts
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R artifacts
|
|
||||||
# - name: Publish package distributions to PyPI
|
|
||||||
# uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
|
|
||||||
pypi-publish-cuda:
|
|
||||||
name: Upload CUDA release to PyPI
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build_cuda_release
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/p/mlx-cuda
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v6
|
|
||||||
with:
|
|
||||||
name: mlx-cuda
|
|
||||||
path: artifacts
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R artifacts
|
|
||||||
# - name: Publish package distributions to PyPI
|
|
||||||
# uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
|
|
||||||
pypi-publish-cpu:
|
|
||||||
name: Upload CPU release to PyPI
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build_linux_release
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/p/mlx-cpu
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v6
|
|
||||||
with:
|
|
||||||
name: mlx-cpu
|
|
||||||
path: artifacts
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R artifacts
|
|
||||||
# - name: Publish package distributions to PyPI
|
|
||||||
# uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
|
|
||||||
pypi-publish-metal:
|
|
||||||
name: Upload Metal release to PyPI
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build_mac_release
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/p/mlx-metal
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v6
|
|
||||||
with:
|
|
||||||
name: mlx-metal
|
|
||||||
path: artifacts
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R artifacts
|
|
||||||
# - name: Publish package distributions to PyPI
|
|
||||||
# uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v6.0.0
|
|
||||||
hooks:
|
|
||||||
- id: check-yaml
|
|
||||||
# - id: end-of-file-fixer
|
|
||||||
# - id: trailing-whitespace
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v19.1.7
|
rev: v19.1.7
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -19,17 +19,12 @@ MLX was developed with contributions from the following individuals:
|
|||||||
- Gleb Pobudzey: Added the `where` primitive, and groups in 1D and 2D convolutions.
|
- Gleb Pobudzey: Added the `where` primitive, and groups in 1D and 2D convolutions.
|
||||||
- Paul Paczuski: Improved stability of BCE loss calculation
|
- Paul Paczuski: Improved stability of BCE loss calculation
|
||||||
- Max-Heinrich Laves: Added `conv_transpose1d`, `conv_transpose2d`, and `conv_transpose3d` ops.
|
- Max-Heinrich Laves: Added `conv_transpose1d`, `conv_transpose2d`, and `conv_transpose3d` ops.
|
||||||
- Gökdeniz Gülmez: Added the `Muon (MomentUm Orthogonalized by Newton-schulz)` optimizer, and the `ReLU²` activation function.
|
- Gökdeniz Gülmez: Added the `Muon (MomentUm Orthogonalized by Newton-schulz)` optimizer.
|
||||||
|
|
||||||
<a href="https://github.com/ml-explore/mlx/graphs/contributors">
|
<a href="https://github.com/ml-explore/mlx/graphs/contributors">
|
||||||
<img class="dark-light" src="https://contrib.rocks/image?repo=ml-explore/mlx&anon=0&columns=20&max=100&r=true" />
|
<img class="dark-light" src="https://contrib.rocks/image?repo=ml-explore/mlx&anon=0&columns=20&max=100&r=true" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
# Organizations
|
|
||||||
|
|
||||||
MLX has received contributions from the following companies:
|
|
||||||
- NVIDIA Corporation & Affiliates
|
|
||||||
|
|
||||||
# Third-Party Software
|
# Third-Party Software
|
||||||
|
|
||||||
MLX leverages several third-party software, listed here together with
|
MLX leverages several third-party software, listed here together with
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_INSTALL_MESSAGE NEVER)
|
set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
# ----------------------------- Configuration -----------------------------
|
# ----------------------------- Configuration -----------------------------
|
||||||
option(MLX_BUILD_TESTS "Build tests for mlx" ON)
|
option(MLX_BUILD_TESTS "Build tests for mlx" ON)
|
||||||
@@ -88,26 +87,22 @@ cmake_policy(SET CMP0135 NEW)
|
|||||||
|
|
||||||
add_library(mlx)
|
add_library(mlx)
|
||||||
|
|
||||||
# Supress warnings: note: parameter passing for argument of type
|
if(MLX_BUILD_METAL)
|
||||||
# ‘std::pair<float, float>’ when C++17 is enabled changed to match C++14 in GCC
|
set(METAL_LIB "-framework Metal")
|
||||||
# 10.1
|
set(FOUNDATION_LIB "-framework Foundation")
|
||||||
target_compile_options(mlx PRIVATE -Wno-psabi)
|
set(QUARTZ_LIB "-framework QuartzCore")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(MLX_BUILD_CUDA)
|
if(MLX_BUILD_CUDA)
|
||||||
enable_language(CUDA)
|
enable_language(CUDA)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MLX_BUILD_METAL)
|
if(MLX_BUILD_METAL AND NOT METAL_LIB)
|
||||||
find_library(METAL_LIB Metal)
|
message(STATUS "Metal not found. Unable to build GPU")
|
||||||
find_library(FOUNDATION_LIB Foundation)
|
set(MLX_BUILD_METAL OFF)
|
||||||
find_library(QUARTZ_LIB QuartzCore)
|
set(MLX_METAL_DEBUG OFF)
|
||||||
if(METAL_LIB)
|
elseif(MLX_BUILD_METAL)
|
||||||
message(STATUS "Metal found ${METAL_LIB}")
|
message(STATUS "Building METAL sources")
|
||||||
else()
|
|
||||||
message(
|
|
||||||
FATAL_ERROR
|
|
||||||
"Metal not found. Set MLX_BUILD_METAL=OFF to build without GPU")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MLX_METAL_DEBUG)
|
if(MLX_METAL_DEBUG)
|
||||||
add_compile_definitions(MLX_METAL_DEBUG)
|
add_compile_definitions(MLX_METAL_DEBUG)
|
||||||
@@ -116,8 +111,7 @@ if(MLX_BUILD_METAL)
|
|||||||
# Throw an error if xcrun not found
|
# Throw an error if xcrun not found
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND zsh "-c" "/usr/bin/xcrun -sdk macosx --show-sdk-version"
|
COMMAND zsh "-c" "/usr/bin/xcrun -sdk macosx --show-sdk-version"
|
||||||
OUTPUT_VARIABLE MACOS_SDK_VERSION
|
OUTPUT_VARIABLE MACOS_SDK_VERSION COMMAND_ERROR_IS_FATAL ANY)
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
|
|
||||||
|
|
||||||
if(${MACOS_SDK_VERSION} LESS 14.0)
|
if(${MACOS_SDK_VERSION} LESS 14.0)
|
||||||
message(
|
message(
|
||||||
@@ -146,12 +140,6 @@ if(MLX_BUILD_METAL)
|
|||||||
target_link_libraries(mlx PUBLIC ${METAL_LIB} ${FOUNDATION_LIB} ${QUARTZ_LIB})
|
target_link_libraries(mlx PUBLIC ${METAL_LIB} ${FOUNDATION_LIB} ${QUARTZ_LIB})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
||||||
# With newer clang/gcc versions following libs are implicitly linked, but when
|
|
||||||
# building on old distributions they need to be explicitly listed.
|
|
||||||
target_link_libraries(mlx PRIVATE dl pthread)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
# GGUF does not build with MSVC.
|
# GGUF does not build with MSVC.
|
||||||
@@ -179,7 +167,7 @@ if(MLX_BUILD_CPU)
|
|||||||
message(STATUS "Accelerate found ${ACCELERATE_LIBRARY}")
|
message(STATUS "Accelerate found ${ACCELERATE_LIBRARY}")
|
||||||
set(MLX_BUILD_ACCELERATE ON)
|
set(MLX_BUILD_ACCELERATE ON)
|
||||||
else()
|
else()
|
||||||
message(STATUS "Accelerate not found, using default backend.")
|
message(STATUS "Accelerate or arm neon not found, using default backend.")
|
||||||
set(MLX_BUILD_ACCELERATE OFF)
|
set(MLX_BUILD_ACCELERATE OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ Hannun, Jagrit Digani, Angelos Katharopoulos, and Ronan Collobert. If you find
|
|||||||
MLX useful in your research and wish to cite it, please use the following
|
MLX useful in your research and wish to cite it, please use the following
|
||||||
BibTex entry:
|
BibTex entry:
|
||||||
|
|
||||||
```text
|
```
|
||||||
@software{mlx2023,
|
@software{mlx2023,
|
||||||
author = {Awni Hannun and Jagrit Digani and Angelos Katharopoulos and Ronan Collobert},
|
author = {Awni Hannun and Jagrit Digani and Angelos Katharopoulos and Ronan Collobert},
|
||||||
title = {{MLX}: Efficient and flexible machine learning on Apple silicon},
|
title = {{MLX}: Efficient and flexible machine learning on Apple silicon},
|
||||||
|
|||||||
@@ -142,7 +142,9 @@ def bench_shape(B, M, N, K, np_dtype, transpose="nn"):
|
|||||||
t_b = (0, 1, 2) if transpose[1] == "n" else (0, 2, 1)
|
t_b = (0, 1, 2) if transpose[1] == "n" else (0, 2, 1)
|
||||||
|
|
||||||
c_mlx = a_mx.transpose(t_a) @ b_mx.transpose(t_b)
|
c_mlx = a_mx.transpose(t_a) @ b_mx.transpose(t_b)
|
||||||
c_npy = a_np.transpose(t_a).astype(np_dtype) @ b_np.transpose(t_b).astype(np_dtype)
|
c_npy = a_np.transpose(t_a).astype(np.float32) @ b_np.transpose(t_b).astype(
|
||||||
|
np.float32
|
||||||
|
)
|
||||||
|
|
||||||
atol = 1e-5 if np_dtype == np.float32 else 1e-4
|
atol = 1e-5 if np_dtype == np.float32 else 1e-4
|
||||||
|
|
||||||
@@ -161,7 +163,7 @@ def get_gflop_count(B, M, N, K):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Run gemm benchmarks")
|
parser = argparse.ArgumentParser(description="Run gemm benchmarks")
|
||||||
|
|
||||||
dtypes = ("float32", "float16", "complex64")
|
dtypes = ("float32", "float16")
|
||||||
transposes = ("nn", "nt", "tn")
|
transposes = ("nn", "nt", "tn")
|
||||||
shapes = (
|
shapes = (
|
||||||
(16, 234, 768, 3072),
|
(16, 234, 768, 3072),
|
||||||
@@ -185,7 +187,7 @@ if __name__ == "__main__":
|
|||||||
diff = gflops_mx / gflops_pt - 1.0
|
diff = gflops_mx / gflops_pt - 1.0
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{B:3d}, {M:4d}, {N:4d}, {K:4d}, {dtype}, {transpose}, {gflops_pt:05.3f}, {gflops_mx:05.3f}, {100.0 * diff:+5.2f}%"
|
f"{B:3d}, {M:4d}, {N:4d}, {K:4d}, {dtype}, {transpose}, {gflops_pt:05.3f}, {gflops_mx:05.3f}, {100. * diff:+5.2f}%"
|
||||||
)
|
)
|
||||||
if gflops_pt >= 2.0 * gflops_mx:
|
if gflops_pt >= 2.0 * gflops_mx:
|
||||||
print("ATTENTION ^^^^^^^")
|
print("ATTENTION ^^^^^^^")
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ def bench_with_out_len(ax, out_vec_len, in_vector_lens, dtype, transpose):
|
|||||||
|
|
||||||
|
|
||||||
for transpose in (False, True):
|
for transpose in (False, True):
|
||||||
for dtype in ("float32", "float16", "complex64"):
|
for dtype in ("float32", "float16"):
|
||||||
fig, axs = plt.subplots(
|
fig, axs = plt.subplots(
|
||||||
len(in_vec_sizes), 2, figsize=(8.5, 11), layout="constrained"
|
len(in_vec_sizes), 2, figsize=(8.5, 11), layout="constrained"
|
||||||
)
|
)
|
||||||
@@ -215,7 +215,7 @@ for transpose in (False, True):
|
|||||||
fig.suptitle(f"{device_name}: {dtype} {op_name}")
|
fig.suptitle(f"{device_name}: {dtype} {op_name}")
|
||||||
fig.savefig(
|
fig.savefig(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
results_dir, f"{device_name.replace(' ', '_')}_{dtype}_{op_name}.pdf"
|
results_dir, f'{device_name.replace(" ", "_")}_{dtype}_{op_name}.pdf'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
# FindNCCL.cmake This module finds the NVIDIA NCCL library and its include
|
|
||||||
# directories.
|
|
||||||
|
|
||||||
set(NCCL_ROOT_DIR
|
|
||||||
$ENV{NCCL_ROOT_DIR}
|
|
||||||
CACHE PATH "Folder contains NVIDIA NCCL")
|
|
||||||
|
|
||||||
find_path(
|
|
||||||
NCCL_INCLUDE_DIRS
|
|
||||||
NAMES nccl.h
|
|
||||||
HINTS ${NCCL_INCLUDE_DIR} ${NCCL_ROOT_DIR} ${NCCL_ROOT_DIR}/include
|
|
||||||
${CUDA_TOOLKIT_ROOT_DIR}/include)
|
|
||||||
|
|
||||||
if($ENV{USE_STATIC_NCCL})
|
|
||||||
message(
|
|
||||||
STATUS "USE_STATIC_NCCL detected. Linking against static NCCL library")
|
|
||||||
set(NCCL_LIBNAME "libnccl_static.a")
|
|
||||||
else()
|
|
||||||
set(NCCL_LIBNAME "nccl")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_library(
|
|
||||||
NCCL_LIBRARIES
|
|
||||||
NAMES ${NCCL_LIBNAME}
|
|
||||||
HINTS ${NCCL_LIB_DIR}
|
|
||||||
${NCCL_ROOT_DIR}
|
|
||||||
${NCCL_ROOT_DIR}/lib
|
|
||||||
${NCCL_ROOT_DIR}/lib/x86_64-linux-gnu
|
|
||||||
${NCCL_ROOT_DIR}/lib64
|
|
||||||
${CUDA_TOOLKIT_ROOT_DIR}/lib
|
|
||||||
${CUDA_TOOLKIT_ROOT_DIR}/lib64)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(NCCL DEFAULT_MSG NCCL_INCLUDE_DIRS
|
|
||||||
NCCL_LIBRARIES)
|
|
||||||
|
|
||||||
if(NCCL_FOUND)
|
|
||||||
set(NCCL_HEADER_FILE "${NCCL_INCLUDE_DIRS}/nccl.h")
|
|
||||||
message(
|
|
||||||
STATUS "Determining NCCL version from the header file: ${NCCL_HEADER_FILE}")
|
|
||||||
file(
|
|
||||||
STRINGS ${NCCL_HEADER_FILE} NCCL_MAJOR_VERSION_DEFINED
|
|
||||||
REGEX "^[ \t]*#define[ \t]+NCCL_MAJOR[ \t]+[0-9]+.*$"
|
|
||||||
LIMIT_COUNT 1)
|
|
||||||
if(NCCL_MAJOR_VERSION_DEFINED)
|
|
||||||
string(REGEX REPLACE "^[ \t]*#define[ \t]+NCCL_MAJOR[ \t]+" ""
|
|
||||||
NCCL_MAJOR_VERSION ${NCCL_MAJOR_VERSION_DEFINED})
|
|
||||||
message(STATUS "NCCL_MAJOR_VERSION: ${NCCL_MAJOR_VERSION}")
|
|
||||||
endif()
|
|
||||||
message(
|
|
||||||
STATUS
|
|
||||||
"Found NCCL (include: ${NCCL_INCLUDE_DIRS}, library: ${NCCL_LIBRARIES})")
|
|
||||||
mark_as_advanced(NCCL_ROOT_DIR NCCL_INCLUDE_DIRS NCCL_LIBRARIES)
|
|
||||||
endif()
|
|
||||||
@@ -127,8 +127,7 @@ relying on a copy from ``ensure_row_contiguous``:
|
|||||||
name="myexp_strided",
|
name="myexp_strided",
|
||||||
input_names=["inp"],
|
input_names=["inp"],
|
||||||
output_names=["out"],
|
output_names=["out"],
|
||||||
source=source,
|
source=source
|
||||||
ensure_row_contiguous=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def exp_elementwise(a: mx.array):
|
def exp_elementwise(a: mx.array):
|
||||||
@@ -139,6 +138,7 @@ relying on a copy from ``ensure_row_contiguous``:
|
|||||||
threadgroup=(256, 1, 1),
|
threadgroup=(256, 1, 1),
|
||||||
output_shapes=[a.shape],
|
output_shapes=[a.shape],
|
||||||
output_dtypes=[a.dtype],
|
output_dtypes=[a.dtype],
|
||||||
|
ensure_row_contiguous=False,
|
||||||
)
|
)
|
||||||
return outputs[0]
|
return outputs[0]
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ are the CPU and GPU.
|
|||||||
python/fft
|
python/fft
|
||||||
python/linalg
|
python/linalg
|
||||||
python/metal
|
python/metal
|
||||||
python/cuda
|
|
||||||
python/memory_management
|
python/memory_management
|
||||||
python/nn
|
python/nn
|
||||||
python/optimizers
|
python/optimizers
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ silicon computer is
|
|||||||
To install from PyPI your system must meet the following requirements:
|
To install from PyPI your system must meet the following requirements:
|
||||||
|
|
||||||
- Using an M series chip (Apple silicon)
|
- Using an M series chip (Apple silicon)
|
||||||
- Using a native Python >= 3.10
|
- Using a native Python >= 3.9
|
||||||
- macOS >= 13.5
|
- macOS >= 13.5
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -39,7 +39,7 @@ requirements:
|
|||||||
- Nvidia driver >= 550.54.14
|
- Nvidia driver >= 550.54.14
|
||||||
- CUDA toolkit >= 12.0
|
- CUDA toolkit >= 12.0
|
||||||
- Linux distribution with glibc >= 2.35
|
- Linux distribution with glibc >= 2.35
|
||||||
- Python >= 3.10
|
- Python >= 3.9
|
||||||
|
|
||||||
|
|
||||||
CPU-only (Linux)
|
CPU-only (Linux)
|
||||||
@@ -55,7 +55,7 @@ To install the CPU-only package from PyPi your system must meet the following
|
|||||||
requirements:
|
requirements:
|
||||||
|
|
||||||
- Linux distribution with glibc >= 2.35
|
- Linux distribution with glibc >= 2.35
|
||||||
- Python >= 3.10
|
- Python >= 3.9
|
||||||
|
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
@@ -271,7 +271,7 @@ and the CUDA toolkit. For example on Ubuntu, run the following:
|
|||||||
dpkg -i cuda-keyring_1.1-1_all.deb
|
dpkg -i cuda-keyring_1.1-1_all.deb
|
||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get -y install cuda-toolkit-12-9
|
apt-get -y install cuda-toolkit-12-9
|
||||||
apt-get install libblas-dev liblapack-dev liblapacke-dev libcudnn9-dev-cuda-12 -y
|
apt-get install libblas-dev liblapack-dev liblapacke-dev -y
|
||||||
|
|
||||||
|
|
||||||
When building either the Python or C++ APIs make sure to pass the cmake flag
|
When building either the Python or C++ APIs make sure to pass the cmake flag
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
CUDA
|
|
||||||
=====
|
|
||||||
|
|
||||||
.. currentmodule:: mlx.core.cuda
|
|
||||||
|
|
||||||
.. autosummary::
|
|
||||||
:toctree: _autosummary
|
|
||||||
|
|
||||||
is_available
|
|
||||||
@@ -13,4 +13,3 @@ Fast
|
|||||||
rope
|
rope
|
||||||
scaled_dot_product_attention
|
scaled_dot_product_attention
|
||||||
metal_kernel
|
metal_kernel
|
||||||
cuda_kernel
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ simple functions.
|
|||||||
mish
|
mish
|
||||||
prelu
|
prelu
|
||||||
relu
|
relu
|
||||||
relu2
|
|
||||||
relu6
|
relu6
|
||||||
selu
|
selu
|
||||||
sigmoid
|
sigmoid
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ Layers
|
|||||||
QuantizedLinear
|
QuantizedLinear
|
||||||
RMSNorm
|
RMSNorm
|
||||||
ReLU
|
ReLU
|
||||||
ReLU2
|
|
||||||
ReLU6
|
ReLU6
|
||||||
RNN
|
RNN
|
||||||
RoPE
|
RoPE
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ Operations
|
|||||||
max
|
max
|
||||||
maximum
|
maximum
|
||||||
mean
|
mean
|
||||||
median
|
|
||||||
meshgrid
|
meshgrid
|
||||||
min
|
min
|
||||||
minimum
|
minimum
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ Now make an array, and benchmark both functions:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
x = mx.random.uniform(shape=(32, 1000, 4096))
|
x = mx.random.uniform(shape=(32, 1000, 4096))
|
||||||
timeit(gelu, x)
|
timeit(nn.gelu, x)
|
||||||
timeit(mx.compile(gelu), x)
|
timeit(mx.compile(nn.gelu), x)
|
||||||
|
|
||||||
On an M1 Max the times are 15.5 and 3.1 milliseconds. The compiled ``gelu`` is
|
On an M1 Max the times are 15.5 and 3.1 milliseconds. The compiled ``gelu`` is
|
||||||
five times faster.
|
five times faster.
|
||||||
@@ -225,7 +225,7 @@ In some cases returning updated state can be pretty inconvenient. Hence,
|
|||||||
def fun(x, y):
|
def fun(x, y):
|
||||||
z = x + y
|
z = x + y
|
||||||
state.append(z)
|
state.append(z)
|
||||||
return mx.exp(z)
|
return mx.exp(z), state
|
||||||
|
|
||||||
fun(mx.array(1.0), mx.array(2.0))
|
fun(mx.array(1.0), mx.array(2.0))
|
||||||
# Prints [array(3, dtype=float32)]
|
# Prints [array(3, dtype=float32)]
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ almost identical to the example above:
|
|||||||
|
|
||||||
def step(model, x, y):
|
def step(model, x, y):
|
||||||
loss, grads = loss_grad_fn(model, x, y)
|
loss, grads = loss_grad_fn(model, x, y)
|
||||||
grads = mx.nn.average_gradients(grads) # <---- This line was added
|
grads = mlx.nn.average_gradients(grads) # <---- This line was added
|
||||||
optimizer.update(model, grads)
|
optimizer.update(model, grads)
|
||||||
return loss
|
return loss
|
||||||
|
|
||||||
|
|||||||
@@ -164,11 +164,11 @@ to export a function which can be used for inputs with variable shapes:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
mx.export_function("fun.mlxfn", mx.abs, mx.array([0.0]), shapeless=True)
|
mx.export_function("fun.mlxfn", mx.abs, mx.array(0.0), shapeless=True)
|
||||||
imported_abs = mx.import_function("fun.mlxfn")
|
imported_abs = mx.import_function("fun.mlxfn")
|
||||||
|
|
||||||
# Ok
|
# Ok
|
||||||
out, = imported_abs(mx.array([-1.0]))
|
out, = imported_abs(mx.array(-1.0))
|
||||||
|
|
||||||
# Also ok
|
# Also ok
|
||||||
out, = imported_abs(mx.array([-1.0, -2.0]))
|
out, = imported_abs(mx.array([-1.0, -2.0]))
|
||||||
|
|||||||
@@ -107,20 +107,8 @@ same array:
|
|||||||
>>> a
|
>>> a
|
||||||
array([1, 2, 0], dtype=int32)
|
array([1, 2, 0], dtype=int32)
|
||||||
|
|
||||||
Note that unlike NumPy, slicing an array creates a copy, not a view. So
|
|
||||||
mutating it does not mutate the original array:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
Note, unlike NumPy, updates to the same location are nondeterministic:
|
||||||
|
|
||||||
>>> a = mx.array([1, 2, 3])
|
|
||||||
>>> b = a[:]
|
|
||||||
>>> b[2] = 0
|
|
||||||
>>> b
|
|
||||||
array([1, 2, 0], dtype=int32)
|
|
||||||
>>> a
|
|
||||||
array([1, 2, 3], dtype=int32)
|
|
||||||
|
|
||||||
Also unlike NumPy, updates to the same location are nondeterministic:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Buffer {
|
|||||||
void* ptr_;
|
void* ptr_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Buffer(void* ptr) : ptr_(ptr) {};
|
Buffer(void* ptr) : ptr_(ptr) {};
|
||||||
|
|
||||||
// Get the raw data pointer from the buffer
|
// Get the raw data pointer from the buffer
|
||||||
void* raw_ptr();
|
void* raw_ptr();
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ array array::unsafe_weak_copy(const array& other) {
|
|||||||
other.strides(),
|
other.strides(),
|
||||||
other.flags(),
|
other.flags(),
|
||||||
[](auto) {});
|
[](auto) {});
|
||||||
cpy.array_desc_->offset = other.array_desc_->offset;
|
cpy.array_desc_->data_ptr = other.array_desc_->data_ptr;
|
||||||
return cpy;
|
return cpy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ bool array::is_tracer() const {
|
|||||||
|
|
||||||
void array::set_data(allocator::Buffer buffer, Deleter d) {
|
void array::set_data(allocator::Buffer buffer, Deleter d) {
|
||||||
array_desc_->data = std::make_shared<Data>(buffer, d);
|
array_desc_->data = std::make_shared<Data>(buffer, d);
|
||||||
array_desc_->offset = 0;
|
array_desc_->data_ptr = buffer.raw_ptr();
|
||||||
array_desc_->data_size = size();
|
array_desc_->data_size = size();
|
||||||
array_desc_->flags.contiguous = true;
|
array_desc_->flags.contiguous = true;
|
||||||
array_desc_->flags.row_contiguous = true;
|
array_desc_->flags.row_contiguous = true;
|
||||||
@@ -156,7 +156,7 @@ void array::set_data(
|
|||||||
Flags flags,
|
Flags flags,
|
||||||
Deleter d) {
|
Deleter d) {
|
||||||
array_desc_->data = std::make_shared<Data>(buffer, d);
|
array_desc_->data = std::make_shared<Data>(buffer, d);
|
||||||
array_desc_->offset = 0;
|
array_desc_->data_ptr = buffer.raw_ptr();
|
||||||
array_desc_->data_size = data_size;
|
array_desc_->data_size = data_size;
|
||||||
array_desc_->strides = std::move(strides);
|
array_desc_->strides = std::move(strides);
|
||||||
array_desc_->flags = flags;
|
array_desc_->flags = flags;
|
||||||
@@ -172,8 +172,9 @@ void array::copy_shared_buffer(
|
|||||||
array_desc_->strides = strides;
|
array_desc_->strides = strides;
|
||||||
array_desc_->flags = flags;
|
array_desc_->flags = flags;
|
||||||
array_desc_->data_size = data_size;
|
array_desc_->data_size = data_size;
|
||||||
array_desc_->offset =
|
auto char_offset = sizeof(char) * itemsize() * offset;
|
||||||
sizeof(char) * itemsize() * offset + other.array_desc_->offset;
|
array_desc_->data_ptr = static_cast<void*>(
|
||||||
|
static_cast<char*>(other.array_desc_->data_ptr) + char_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void array::copy_shared_buffer(const array& other) {
|
void array::copy_shared_buffer(const array& other) {
|
||||||
@@ -240,8 +241,8 @@ array::ArrayDesc::ArrayDesc(
|
|||||||
std::vector<array> inputs)
|
std::vector<array> inputs)
|
||||||
: shape(std::move(shape)),
|
: shape(std::move(shape)),
|
||||||
dtype(dtype),
|
dtype(dtype),
|
||||||
primitive(std::move(primitive)),
|
|
||||||
status(Status::unscheduled),
|
status(Status::unscheduled),
|
||||||
|
primitive(std::move(primitive)),
|
||||||
inputs(std::move(inputs)) {
|
inputs(std::move(inputs)) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|||||||
18
mlx/array.h
18
mlx/array.h
@@ -349,23 +349,15 @@ class array {
|
|||||||
return array_desc_->data;
|
return array_desc_->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a raw pointer to the arrays data. This function may do a copy if
|
// Return a raw pointer to the arrays data
|
||||||
// the underlying buffer is not accessible on the CPU. When accessing the
|
|
||||||
// data for GPU kernels, be sure to use the correct method / function for the
|
|
||||||
// given backend to access the GPU pointer.
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* data() {
|
T* data() {
|
||||||
return reinterpret_cast<T*>(
|
return static_cast<T*>(array_desc_->data_ptr);
|
||||||
(static_cast<char*>(buffer().raw_ptr()) + array_desc_->offset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
const T* data() const {
|
const T* data() const {
|
||||||
return const_cast<array&>(*this).data<T>();
|
return static_cast<T*>(array_desc_->data_ptr);
|
||||||
}
|
|
||||||
|
|
||||||
int64_t offset() const {
|
|
||||||
return array_desc_->offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
@@ -469,8 +461,8 @@ class array {
|
|||||||
// can share the underlying data buffer.
|
// can share the underlying data buffer.
|
||||||
std::shared_ptr<Data> data;
|
std::shared_ptr<Data> data;
|
||||||
|
|
||||||
// Offset from beginning of data pointer
|
// Properly offset data pointer
|
||||||
int64_t offset{0};
|
void* data_ptr{nullptr};
|
||||||
|
|
||||||
// The size in elements of the data buffer the array accesses
|
// The size in elements of the data buffer the array accesses
|
||||||
size_t data_size;
|
size_t data_size;
|
||||||
|
|||||||
@@ -38,20 +38,20 @@ inline void set_binary_op_output_data(
|
|||||||
const array& a,
|
const array& a,
|
||||||
const array& b,
|
const array& b,
|
||||||
array& out,
|
array& out,
|
||||||
BinaryOpType bopt,
|
BinaryOpType bopt) {
|
||||||
std::function<allocator::Buffer(size_t)> mallocfn = allocator::malloc) {
|
|
||||||
bool b_donatable = is_donatable(b, out);
|
bool b_donatable = is_donatable(b, out);
|
||||||
bool a_donatable = is_donatable(a, out);
|
bool a_donatable = is_donatable(a, out);
|
||||||
switch (bopt) {
|
switch (bopt) {
|
||||||
case BinaryOpType::ScalarScalar:
|
case BinaryOpType::ScalarScalar:
|
||||||
out.set_data(mallocfn(out.itemsize()), 1, a.strides(), a.flags());
|
out.set_data(
|
||||||
|
allocator::malloc(out.itemsize()), 1, a.strides(), a.flags());
|
||||||
break;
|
break;
|
||||||
case BinaryOpType::ScalarVector:
|
case BinaryOpType::ScalarVector:
|
||||||
if (b_donatable) {
|
if (b_donatable) {
|
||||||
out.copy_shared_buffer(b);
|
out.copy_shared_buffer(b);
|
||||||
} else {
|
} else {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(b.data_size() * out.itemsize()),
|
allocator::malloc(b.data_size() * out.itemsize()),
|
||||||
b.data_size(),
|
b.data_size(),
|
||||||
b.strides(),
|
b.strides(),
|
||||||
b.flags());
|
b.flags());
|
||||||
@@ -62,7 +62,7 @@ inline void set_binary_op_output_data(
|
|||||||
out.copy_shared_buffer(a);
|
out.copy_shared_buffer(a);
|
||||||
} else {
|
} else {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(a.data_size() * out.itemsize()),
|
allocator::malloc(a.data_size() * out.itemsize()),
|
||||||
a.data_size(),
|
a.data_size(),
|
||||||
a.strides(),
|
a.strides(),
|
||||||
a.flags());
|
a.flags());
|
||||||
@@ -75,7 +75,7 @@ inline void set_binary_op_output_data(
|
|||||||
out.copy_shared_buffer(b);
|
out.copy_shared_buffer(b);
|
||||||
} else {
|
} else {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(a.data_size() * out.itemsize()),
|
allocator::malloc(a.data_size() * out.itemsize()),
|
||||||
a.data_size(),
|
a.data_size(),
|
||||||
a.strides(),
|
a.strides(),
|
||||||
a.flags());
|
a.flags());
|
||||||
@@ -88,7 +88,7 @@ inline void set_binary_op_output_data(
|
|||||||
b_donatable && b.flags().row_contiguous && b.size() == out.size()) {
|
b_donatable && b.flags().row_contiguous && b.size() == out.size()) {
|
||||||
out.copy_shared_buffer(b);
|
out.copy_shared_buffer(b);
|
||||||
} else {
|
} else {
|
||||||
out.set_data(mallocfn(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace mlx::core {
|
|||||||
|
|
||||||
void broadcast(const array& in, array& out) {
|
void broadcast(const array& in, array& out) {
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(0));
|
out.set_data(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Strides strides(out.ndim(), 0);
|
Strides strides(out.ndim(), 0);
|
||||||
|
|||||||
@@ -114,9 +114,7 @@ void compiled_allocate_outputs(
|
|||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::function<bool(size_t)>& is_constant,
|
const std::function<bool(size_t)>& is_constant,
|
||||||
bool contiguous,
|
bool contiguous) {
|
||||||
const std::function<allocator::Buffer(size_t)>&
|
|
||||||
mallocfn /* = allocator::malloc */) {
|
|
||||||
if (contiguous) {
|
if (contiguous) {
|
||||||
int o = 0;
|
int o = 0;
|
||||||
Strides strides;
|
Strides strides;
|
||||||
@@ -142,7 +140,7 @@ void compiled_allocate_outputs(
|
|||||||
}
|
}
|
||||||
for (; o < outputs.size(); ++o) {
|
for (; o < outputs.size(); ++o) {
|
||||||
outputs[o].set_data(
|
outputs[o].set_data(
|
||||||
mallocfn(data_size * outputs[o].itemsize()),
|
allocator::malloc(data_size * outputs[o].itemsize()),
|
||||||
data_size,
|
data_size,
|
||||||
strides,
|
strides,
|
||||||
flags);
|
flags);
|
||||||
@@ -165,7 +163,7 @@ void compiled_allocate_outputs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; o < outputs.size(); ++o) {
|
for (; o < outputs.size(); ++o) {
|
||||||
outputs[o].set_data(mallocfn(outputs[o].nbytes()));
|
outputs[o].set_data(allocator::malloc(outputs[o].nbytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ void compiled_allocate_outputs(
|
|||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::function<bool(size_t)>& is_constant,
|
const std::function<bool(size_t)>& is_constant,
|
||||||
bool contiguous,
|
bool contiguous);
|
||||||
const std::function<allocator::Buffer(size_t)>& mallocfn =
|
|
||||||
allocator::malloc);
|
|
||||||
|
|
||||||
// Collapse contiguous dims ignoring scalars and constants.
|
// Collapse contiguous dims ignoring scalars and constants.
|
||||||
std::tuple<bool, Shape, std::vector<Strides>> compiled_collapse_contiguous_dims(
|
std::tuple<bool, Shape, std::vector<Strides>> compiled_collapse_contiguous_dims(
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ enum class CopyType {
|
|||||||
GeneralGeneral
|
GeneralGeneral
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool set_copy_output_data(
|
inline bool set_copy_output_data(const array& in, array& out, CopyType ctype) {
|
||||||
const array& in,
|
|
||||||
array& out,
|
|
||||||
CopyType ctype,
|
|
||||||
std::function<allocator::Buffer(size_t)> mallocfn = allocator::malloc) {
|
|
||||||
if (ctype == CopyType::Vector) {
|
if (ctype == CopyType::Vector) {
|
||||||
// If the input is donateable, we are doing a vector copy and the types
|
// If the input is donateable, we are doing a vector copy and the types
|
||||||
// have the same size, then the input buffer can hold the output.
|
// have the same size, then the input buffer can hold the output.
|
||||||
@@ -35,14 +31,14 @@ inline bool set_copy_output_data(
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(in.data_size() * out.itemsize()),
|
allocator::malloc(in.data_size() * out.itemsize()),
|
||||||
in.data_size(),
|
in.data_size(),
|
||||||
in.strides(),
|
in.strides(),
|
||||||
in.flags());
|
in.flags());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out.set_data(mallocfn(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ inline std::tuple<Shape, Strides, Strides> collapse_batches(
|
|||||||
const array& a,
|
const array& a,
|
||||||
const array& b) {
|
const array& b) {
|
||||||
if (a.ndim() == 2) {
|
if (a.ndim() == 2) {
|
||||||
return {Shape{1}, Strides{0}, Strides{0}};
|
return {{1}, {0}, {0}};
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape A_bshape{a.shape().begin(), a.shape().end() - 2};
|
Shape A_bshape{a.shape().begin(), a.shape().end() - 2};
|
||||||
@@ -38,7 +38,7 @@ inline std::tuple<Shape, Strides, Strides> collapse_batches(
|
|||||||
inline std::tuple<Shape, Strides, Strides, Strides>
|
inline std::tuple<Shape, Strides, Strides, Strides>
|
||||||
collapse_batches(const array& a, const array& b, const array& c) {
|
collapse_batches(const array& a, const array& b, const array& c) {
|
||||||
if (a.ndim() == 2) {
|
if (a.ndim() == 2) {
|
||||||
return {Shape{1}, Strides{0}, Strides{0}, Strides{0}};
|
return {{1}, {0}, {0}, {0}};
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape A_bshape{a.shape().begin(), a.shape().end() - 2};
|
Shape A_bshape{a.shape().begin(), a.shape().end() - 2};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ void slice(
|
|||||||
const Shape& start_indices,
|
const Shape& start_indices,
|
||||||
const Shape& strides) {
|
const Shape& strides) {
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(0));
|
out.set_data(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ namespace mlx::core {
|
|||||||
enum class TernaryOpType {
|
enum class TernaryOpType {
|
||||||
ScalarScalarScalar,
|
ScalarScalarScalar,
|
||||||
VectorVectorVector,
|
VectorVectorVector,
|
||||||
VectorVectorScalar,
|
|
||||||
VectorScalarVector,
|
|
||||||
General,
|
General,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,14 +25,6 @@ get_ternary_op_type(const array& a, const array& b, const array& c) {
|
|||||||
(a.flags().col_contiguous && b.flags().col_contiguous &&
|
(a.flags().col_contiguous && b.flags().col_contiguous &&
|
||||||
c.flags().col_contiguous)) {
|
c.flags().col_contiguous)) {
|
||||||
topt = TernaryOpType::VectorVectorVector;
|
topt = TernaryOpType::VectorVectorVector;
|
||||||
} else if (
|
|
||||||
b.data_size() == 1 && a.flags().row_contiguous &&
|
|
||||||
c.flags().row_contiguous) {
|
|
||||||
topt = TernaryOpType::VectorScalarVector;
|
|
||||||
} else if (
|
|
||||||
c.data_size() == 1 && a.flags().row_contiguous &&
|
|
||||||
b.flags().row_contiguous) {
|
|
||||||
topt = TernaryOpType::VectorVectorScalar;
|
|
||||||
} else {
|
} else {
|
||||||
topt = TernaryOpType::General;
|
topt = TernaryOpType::General;
|
||||||
}
|
}
|
||||||
@@ -46,8 +36,7 @@ inline void set_ternary_op_output_data(
|
|||||||
const array& b,
|
const array& b,
|
||||||
const array& c,
|
const array& c,
|
||||||
array& out,
|
array& out,
|
||||||
TernaryOpType topt,
|
TernaryOpType topt) {
|
||||||
std::function<allocator::Buffer(size_t)> mallocfn = allocator::malloc) {
|
|
||||||
auto maybe_donate = [&out](const array& x) {
|
auto maybe_donate = [&out](const array& x) {
|
||||||
if (is_donatable(x, out)) {
|
if (is_donatable(x, out)) {
|
||||||
out.copy_shared_buffer(x);
|
out.copy_shared_buffer(x);
|
||||||
@@ -58,25 +47,24 @@ inline void set_ternary_op_output_data(
|
|||||||
|
|
||||||
switch (topt) {
|
switch (topt) {
|
||||||
case TernaryOpType::ScalarScalarScalar:
|
case TernaryOpType::ScalarScalarScalar:
|
||||||
out.set_data(mallocfn(out.itemsize()), 1, b.strides(), b.flags());
|
out.set_data(
|
||||||
|
allocator::malloc(out.itemsize()), 1, b.strides(), b.flags());
|
||||||
break;
|
break;
|
||||||
case TernaryOpType::VectorVectorVector:
|
case TernaryOpType::VectorVectorVector:
|
||||||
if (!(maybe_donate(a) || maybe_donate(b) || maybe_donate(c))) {
|
if (!(maybe_donate(a) || maybe_donate(b) || maybe_donate(c))) {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(out.itemsize() * b.data_size()),
|
allocator::malloc(out.itemsize() * b.data_size()),
|
||||||
b.data_size(),
|
b.data_size(),
|
||||||
b.strides(),
|
b.strides(),
|
||||||
b.flags());
|
b.flags());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TernaryOpType::VectorVectorScalar:
|
|
||||||
case TernaryOpType::VectorScalarVector:
|
|
||||||
case TernaryOpType::General:
|
case TernaryOpType::General:
|
||||||
// Try to donate an input which is row_contiguous
|
// Try to donate an input which is row_contiguous
|
||||||
if (!((a.flags().row_contiguous && maybe_donate(a)) ||
|
if (!((a.flags().row_contiguous && maybe_donate(a)) ||
|
||||||
(b.flags().row_contiguous && maybe_donate(b)) ||
|
(b.flags().row_contiguous && maybe_donate(b)) ||
|
||||||
(c.flags().row_contiguous && maybe_donate(c)))) {
|
(c.flags().row_contiguous && maybe_donate(c)))) {
|
||||||
out.set_data(mallocfn(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,19 @@
|
|||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
inline void set_unary_output_data(
|
inline void set_unary_output_data(const array& in, array& out) {
|
||||||
const array& in,
|
|
||||||
array& out,
|
|
||||||
std::function<allocator::Buffer(size_t)> mallocfn = allocator::malloc) {
|
|
||||||
if (in.flags().contiguous) {
|
if (in.flags().contiguous) {
|
||||||
if (is_donatable(in, out)) {
|
if (is_donatable(in, out)) {
|
||||||
out.copy_shared_buffer(in);
|
out.copy_shared_buffer(in);
|
||||||
} else {
|
} else {
|
||||||
out.set_data(
|
out.set_data(
|
||||||
mallocfn(in.data_size() * out.itemsize()),
|
allocator::malloc(in.data_size() * out.itemsize()),
|
||||||
in.data_size(),
|
in.data_size(),
|
||||||
in.strides(),
|
in.strides(),
|
||||||
in.flags());
|
in.flags());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out.set_data(mallocfn(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,4 +228,31 @@ std::pair<Dims, Dims> get_grid_and_block_common(int dim0, int dim1, int dim2) {
|
|||||||
std::make_tuple(gx, gy, gz), std::make_tuple(bx, by, bz));
|
std::make_tuple(gx, gy, gz), std::make_tuple(bx, by, bz));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
array swapaxes_in_eval(const array& x, int axis1, int axis2) {
|
||||||
|
int ndim = x.ndim();
|
||||||
|
if (axis1 < 0) {
|
||||||
|
axis1 += ndim;
|
||||||
|
}
|
||||||
|
if (axis2 < 0) {
|
||||||
|
axis2 += ndim;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shape = x.shape();
|
||||||
|
std::swap(shape[axis1], shape[axis2]);
|
||||||
|
auto strides = x.strides();
|
||||||
|
std::swap(strides[axis1], strides[axis2]);
|
||||||
|
|
||||||
|
auto [data_size, row_contiguous, col_contiguous] =
|
||||||
|
check_contiguity(shape, strides);
|
||||||
|
bool contiguous = data_size == x.data_size();
|
||||||
|
|
||||||
|
array out(std::move(shape), x.dtype(), nullptr, {});
|
||||||
|
out.copy_shared_buffer(
|
||||||
|
x,
|
||||||
|
std::move(strides),
|
||||||
|
{contiguous, row_contiguous, col_contiguous},
|
||||||
|
x.data_size());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ void shared_buffer_reshape(
|
|||||||
const Strides& out_strides,
|
const Strides& out_strides,
|
||||||
array& out);
|
array& out);
|
||||||
|
|
||||||
|
// Like the swapaxes op but safe to call in eval_gpu.
|
||||||
|
array swapaxes_in_eval(const array& x, int axis1, int axis2);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline SmallVector<T> remove_index(SmallVector<T> vec, size_t index) {
|
inline SmallVector<T> remove_index(SmallVector<T> vec, size_t index) {
|
||||||
vec.erase(std::next(vec.begin(), index));
|
vec.erase(std::next(vec.begin(), index));
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "mlx/backend/cpu/jit_compiler.h"
|
#include "mlx/backend/cpu/jit_compiler.h"
|
||||||
#include "mlx/device.h"
|
#include "mlx/device.h"
|
||||||
#include "mlx/graph_utils.h"
|
#include "mlx/graph_utils.h"
|
||||||
#include "mlx/version.h"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
@@ -95,11 +94,7 @@ void* compile(
|
|||||||
kernel_file_name = kernel_name;
|
kernel_file_name = kernel_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output_dir =
|
auto output_dir = std::filesystem::temp_directory_path();
|
||||||
std::filesystem::temp_directory_path() / "mlx" / version() / "cpu";
|
|
||||||
if (!std::filesystem::exists(output_dir)) {
|
|
||||||
std::filesystem::create_directories(output_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string shared_lib_name = "lib" + kernel_file_name + ".so";
|
std::string shared_lib_name = "lib" + kernel_file_name + ".so";
|
||||||
auto shared_lib_path = (output_dir / shared_lib_name).string();
|
auto shared_lib_path = (output_dir / shared_lib_name).string();
|
||||||
@@ -162,12 +157,10 @@ inline void build_kernel(
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Start the kernel
|
// Start the kernel
|
||||||
os << "void " << kernel_name
|
os << "void " << kernel_name << "(void** args) {" << std::endl;
|
||||||
<< "(int* shape, int64_t** strides, void** args) {" << std::endl;
|
|
||||||
|
|
||||||
// Add the input arguments
|
// Add the input arguments
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
int strides_index = 1;
|
|
||||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
// Skip constants from the input list
|
// Skip constants from the input list
|
||||||
if (is_constant(i)) {
|
if (is_constant(i)) {
|
||||||
@@ -182,8 +175,8 @@ inline void build_kernel(
|
|||||||
<< "];" << std::endl;
|
<< "];" << std::endl;
|
||||||
// Scalars and contiguous need no strides
|
// Scalars and contiguous need no strides
|
||||||
if (!is_scalar(x) && !contiguous) {
|
if (!is_scalar(x) && !contiguous) {
|
||||||
os << " const int64_t* " << xname << "_strides = strides["
|
os << " const size_t* " << xname << "_strides = (size_t*)args[" << cnt++
|
||||||
<< strides_index++ << "];" << std::endl;
|
<< "];" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +186,10 @@ inline void build_kernel(
|
|||||||
os << " " << tstr << "* " << namer.get_name(x) << " = (" << tstr
|
os << " " << tstr << "* " << namer.get_name(x) << " = (" << tstr
|
||||||
<< "*)args[" << cnt++ << "];" << std::endl;
|
<< "*)args[" << cnt++ << "];" << std::endl;
|
||||||
}
|
}
|
||||||
// Add output size
|
// Add output strides and shape to extract the indices.
|
||||||
if (contiguous) {
|
if (!contiguous) {
|
||||||
|
os << " const int* shape = (int*)args[" << cnt++ << "];" << std::endl;
|
||||||
|
} else {
|
||||||
os << " const size_t size = (size_t)args[" << cnt++ << "];" << std::endl;
|
os << " const size_t size = (size_t)args[" << cnt++ << "];" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +288,17 @@ void Compiled::eval_cpu(
|
|||||||
auto [contiguous, shape, strides] =
|
auto [contiguous, shape, strides] =
|
||||||
compiled_collapse_contiguous_dims(inputs, outputs[0], is_constant_);
|
compiled_collapse_contiguous_dims(inputs, outputs[0], is_constant_);
|
||||||
|
|
||||||
|
// Force allocating shape/strides on heap so we can take their data() first
|
||||||
|
// and then std::move them.
|
||||||
|
// TODO: Refactor code to avoid heap allocation.
|
||||||
|
shape.grow();
|
||||||
|
for (auto& s : strides) {
|
||||||
|
s.grow();
|
||||||
|
}
|
||||||
|
|
||||||
// Collect function input arguments.
|
// Collect function input arguments.
|
||||||
std::vector<void*> args;
|
std::vector<void*> args;
|
||||||
|
int strides_index = 1;
|
||||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
if (is_constant_(i)) {
|
if (is_constant_(i)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -302,6 +306,9 @@ void Compiled::eval_cpu(
|
|||||||
const auto& x = inputs[i];
|
const auto& x = inputs[i];
|
||||||
encoder.set_input_array(x);
|
encoder.set_input_array(x);
|
||||||
args.push_back((void*)x.data<void>());
|
args.push_back((void*)x.data<void>());
|
||||||
|
if (!contiguous && !is_scalar(x)) {
|
||||||
|
args.push_back(strides[strides_index++].data());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the kernel name from the lib
|
// Get the kernel name from the lib
|
||||||
@@ -336,20 +343,16 @@ void Compiled::eval_cpu(
|
|||||||
args.push_back(x.data<void>());
|
args.push_back(x.data<void>());
|
||||||
encoder.set_output_array(x);
|
encoder.set_output_array(x);
|
||||||
}
|
}
|
||||||
if (contiguous) {
|
if (!contiguous) {
|
||||||
|
args.push_back((void*)shape.data());
|
||||||
|
} else {
|
||||||
args.push_back((void*)outputs[0].data_size());
|
args.push_back((void*)outputs[0].data_size());
|
||||||
}
|
}
|
||||||
auto fun = reinterpret_cast<void (*)(int*, int64_t**, void**)>(fn_ptr);
|
auto fun = (void (*)(void**))fn_ptr;
|
||||||
encoder.dispatch([fun,
|
encoder.dispatch([fun,
|
||||||
args = std::move(args),
|
args = std::move(args),
|
||||||
strides = std::move(strides),
|
strides = std::move(strides),
|
||||||
shape = std::move(shape)]() mutable {
|
shape = std::move(shape)]() mutable { fun(args.data()); });
|
||||||
SmallVector<int64_t*> strides_ptrs;
|
|
||||||
for (auto& s : strides) {
|
|
||||||
strides_ptrs.push_back(s.data());
|
|
||||||
}
|
|
||||||
fun(shape.data(), strides_ptrs.data(), args.data());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -996,6 +996,131 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
encoder.add_temporaries(std::move(temps));
|
encoder.add_temporaries(std::move(temps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void explicit_gemm_conv_2D_cpu(
|
||||||
|
const array& in,
|
||||||
|
const array& wt,
|
||||||
|
array out,
|
||||||
|
const std::vector<int>& padding_lo,
|
||||||
|
const std::vector<int>& padding_hi,
|
||||||
|
const std::vector<int>& wt_strides,
|
||||||
|
const std::vector<int>& wt_dilation,
|
||||||
|
Stream stream) {
|
||||||
|
const int N = in.shape(0); // Batch size, should be the same as out.shape(0)
|
||||||
|
const int iH = in.shape(1); // Input spatial dim
|
||||||
|
const int iW = in.shape(2); // Input spatial dim
|
||||||
|
const int oH = out.shape(1); // Output spatial dim
|
||||||
|
const int oW = out.shape(2); // Output spatial dim
|
||||||
|
const int O = wt.shape(0); // Out channels
|
||||||
|
const int C = wt.shape(3); // In channels
|
||||||
|
const int wH = wt.shape(1); // Weight spatial dim
|
||||||
|
const int wW = wt.shape(2); // Weight spatial dim
|
||||||
|
|
||||||
|
auto conv_dtype = out.dtype();
|
||||||
|
auto& encoder = cpu::get_command_encoder(stream);
|
||||||
|
|
||||||
|
// Pad input
|
||||||
|
Shape padded_shape = {
|
||||||
|
N,
|
||||||
|
iH + padding_lo[0] + padding_hi[0],
|
||||||
|
iW + padding_lo[1] + padding_hi[1],
|
||||||
|
C};
|
||||||
|
array in_padded(padded_shape, conv_dtype, nullptr, {});
|
||||||
|
|
||||||
|
// Fill with zeros
|
||||||
|
std::vector<array> temps;
|
||||||
|
temps.push_back(array(0, conv_dtype));
|
||||||
|
copy_cpu(temps.back(), in_padded, CopyType::Scalar, stream);
|
||||||
|
|
||||||
|
// Pick input slice from padded
|
||||||
|
size_t data_offset = padding_lo[0] * in_padded.strides()[1] +
|
||||||
|
padding_lo[1] * in_padded.strides()[2];
|
||||||
|
array in_padded_slice(in.shape(), in_padded.dtype(), nullptr, {});
|
||||||
|
in_padded_slice.copy_shared_buffer(
|
||||||
|
in_padded,
|
||||||
|
in_padded.strides(),
|
||||||
|
in_padded.flags(),
|
||||||
|
in_padded_slice.size(),
|
||||||
|
data_offset);
|
||||||
|
temps.push_back(in_padded_slice);
|
||||||
|
|
||||||
|
// Copy input values into the slice
|
||||||
|
copy_cpu_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
||||||
|
|
||||||
|
// Make strided view
|
||||||
|
Shape strided_shape = {N, oH, oW, wH, wW, C};
|
||||||
|
|
||||||
|
Strides strided_strides = {
|
||||||
|
in_padded.strides()[0],
|
||||||
|
in_padded.strides()[1] * wt_strides[0],
|
||||||
|
in_padded.strides()[2] * wt_strides[1],
|
||||||
|
in_padded.strides()[1],
|
||||||
|
in_padded.strides()[2],
|
||||||
|
in_padded.strides()[3]};
|
||||||
|
auto flags = in_padded.flags();
|
||||||
|
|
||||||
|
array in_strided_view(strided_shape, in_padded.dtype(), nullptr, {});
|
||||||
|
in_strided_view.copy_shared_buffer(
|
||||||
|
in_padded, strided_strides, flags, in_strided_view.size(), 0);
|
||||||
|
|
||||||
|
// Materialize strided view
|
||||||
|
Shape strided_reshape = {N * oH * oW, wH * wW * C};
|
||||||
|
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
||||||
|
copy_cpu(in_strided_view, in_strided, CopyType::General, stream);
|
||||||
|
temps.push_back(in_strided);
|
||||||
|
|
||||||
|
// Check wt dtype and prepare
|
||||||
|
auto gemm_wt = wt;
|
||||||
|
auto gemm_out = out;
|
||||||
|
|
||||||
|
if (wt.dtype() != float32 || !wt.flags().row_contiguous) {
|
||||||
|
auto ctype =
|
||||||
|
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
|
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
||||||
|
copy_cpu(wt, gemm_wt, ctype, stream);
|
||||||
|
temps.push_back(gemm_wt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.dtype() != float32) {
|
||||||
|
gemm_out = array(out.shape(), float32, nullptr, {});
|
||||||
|
gemm_out.set_data(allocator::malloc(gemm_out.nbytes()));
|
||||||
|
temps.push_back(gemm_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.set_input_array(in_strided);
|
||||||
|
encoder.set_input_array(gemm_wt);
|
||||||
|
encoder.set_output_array(gemm_out);
|
||||||
|
|
||||||
|
encoder.dispatch([in_strided_ptr = in_strided.data<float>(),
|
||||||
|
gemm_wt_ptr = gemm_wt.data<float>(),
|
||||||
|
gemm_out_ptr = gemm_out.data<float>(),
|
||||||
|
strided_reshape = std::move(strided_reshape),
|
||||||
|
O]() {
|
||||||
|
// Perform gemm
|
||||||
|
cblas_sgemm(
|
||||||
|
CblasRowMajor,
|
||||||
|
CblasNoTrans, // no trans A
|
||||||
|
CblasTrans, // transB
|
||||||
|
strided_reshape[0], // M
|
||||||
|
O, // N
|
||||||
|
strided_reshape[1], // K
|
||||||
|
1.0f, // alpha
|
||||||
|
in_strided_ptr,
|
||||||
|
strided_reshape[1], // lda
|
||||||
|
gemm_wt_ptr,
|
||||||
|
strided_reshape[1], // ldb
|
||||||
|
0.0f, // beta
|
||||||
|
gemm_out_ptr,
|
||||||
|
O // ldc
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy results if needed
|
||||||
|
if (out.dtype() != float32) {
|
||||||
|
copy_cpu_inplace(gemm_out, out, CopyType::Vector, stream);
|
||||||
|
}
|
||||||
|
encoder.add_temporaries(std::move(temps));
|
||||||
|
}
|
||||||
|
|
||||||
void explicit_gemm_conv_ND_cpu(
|
void explicit_gemm_conv_ND_cpu(
|
||||||
const array& in,
|
const array& in,
|
||||||
const array& wt,
|
const array& wt,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ void eig_impl(
|
|||||||
int info;
|
int info;
|
||||||
{
|
{
|
||||||
T work;
|
T work;
|
||||||
|
int iwork;
|
||||||
geev<T>(
|
geev<T>(
|
||||||
&jobl,
|
&jobl,
|
||||||
&jobr,
|
&jobr,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright © 2023-2024 Apple Inc.
|
// Copyright © 2023-2024 Apple Inc.
|
||||||
|
|
||||||
#include <Accelerate/Accelerate.h>
|
#include <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
#include "mlx/array.h"
|
#include "mlx/array.h"
|
||||||
@@ -48,15 +49,9 @@ void matmul_bnns(
|
|||||||
size_t K = a_shape[ndim - 1];
|
size_t K = a_shape[ndim - 1];
|
||||||
|
|
||||||
BNNSDataType bnns_dtype = to_bnns_dtype<T>();
|
BNNSDataType bnns_dtype = to_bnns_dtype<T>();
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
if (beta != 1.0 && beta != 0.0) {
|
|
||||||
// scale the output
|
|
||||||
for (auto i = 0; i < batch_size * M * N; ++i) {
|
|
||||||
out[i] *= beta;
|
|
||||||
}
|
|
||||||
beta = 1.0;
|
|
||||||
}
|
|
||||||
const BNNSLayerParametersBroadcastMatMul gemm_params{
|
const BNNSLayerParametersBroadcastMatMul gemm_params{
|
||||||
/* float alpha = */ alpha,
|
/* float alpha = */ alpha,
|
||||||
/* float beta = */ beta,
|
/* float beta = */ beta,
|
||||||
|
|||||||
@@ -88,47 +88,4 @@ void matmul<double>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
|
||||||
void matmul<complex64_t>(
|
|
||||||
const complex64_t* a,
|
|
||||||
const complex64_t* b,
|
|
||||||
complex64_t* out,
|
|
||||||
bool a_transposed,
|
|
||||||
bool b_transposed,
|
|
||||||
size_t lda,
|
|
||||||
size_t ldb,
|
|
||||||
size_t ldc,
|
|
||||||
float alpha,
|
|
||||||
float beta,
|
|
||||||
size_t batch_size,
|
|
||||||
const Shape& a_shape,
|
|
||||||
const Strides& a_strides,
|
|
||||||
const Shape& b_shape,
|
|
||||||
const Strides& b_strides) {
|
|
||||||
auto ndim = a_shape.size();
|
|
||||||
size_t M = a_shape[ndim - 2];
|
|
||||||
size_t N = b_shape[ndim - 1];
|
|
||||||
size_t K = a_shape[ndim - 1];
|
|
||||||
auto calpha = static_cast<complex64_t>(alpha);
|
|
||||||
auto cbeta = static_cast<complex64_t>(beta);
|
|
||||||
|
|
||||||
for (int i = 0; i < batch_size; ++i) {
|
|
||||||
cblas_cgemm(
|
|
||||||
CblasRowMajor,
|
|
||||||
a_transposed ? CblasTrans : CblasNoTrans, // transA
|
|
||||||
b_transposed ? CblasTrans : CblasNoTrans, // transB
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
K,
|
|
||||||
&calpha,
|
|
||||||
a + elem_to_loc(M * K * i, a_shape, a_strides),
|
|
||||||
lda,
|
|
||||||
b + elem_to_loc(K * N * i, b_shape, b_strides),
|
|
||||||
ldb,
|
|
||||||
&cbeta,
|
|
||||||
out + M * N * i,
|
|
||||||
ldc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ INSTANTIATE_LAPACK_REAL(orgqr)
|
|||||||
INSTANTIATE_LAPACK_REAL(syevd)
|
INSTANTIATE_LAPACK_REAL(syevd)
|
||||||
INSTANTIATE_LAPACK_REAL(geev)
|
INSTANTIATE_LAPACK_REAL(geev)
|
||||||
INSTANTIATE_LAPACK_REAL(potrf)
|
INSTANTIATE_LAPACK_REAL(potrf)
|
||||||
INSTANTIATE_LAPACK_REAL(gesdd)
|
INSTANTIATE_LAPACK_REAL(gesvdx)
|
||||||
INSTANTIATE_LAPACK_REAL(getrf)
|
INSTANTIATE_LAPACK_REAL(getrf)
|
||||||
INSTANTIATE_LAPACK_REAL(getri)
|
INSTANTIATE_LAPACK_REAL(getri)
|
||||||
INSTANTIATE_LAPACK_REAL(trtri)
|
INSTANTIATE_LAPACK_REAL(trtri)
|
||||||
|
|||||||
@@ -215,18 +215,18 @@ void BlockMaskedMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
|
|
||||||
encoder.set_input_array(a);
|
encoder.set_input_array(a);
|
||||||
encoder.set_input_array(b);
|
encoder.set_input_array(b);
|
||||||
const void* a_mask_ptr = nullptr;
|
const void* a_mask_ptr;
|
||||||
const void* b_mask_ptr = nullptr;
|
const void* b_mask_ptr;
|
||||||
const void* out_mask_ptr = nullptr;
|
const void* out_mask_ptr;
|
||||||
Shape a_mask_shape;
|
Shape a_mask_shape;
|
||||||
Shape b_mask_shape;
|
Shape b_mask_shape;
|
||||||
Shape out_mask_shape;
|
Shape out_mask_shape;
|
||||||
Strides a_mask_strides;
|
Strides a_mask_strides;
|
||||||
Strides b_mask_strides;
|
Strides b_mask_strides;
|
||||||
Strides out_mask_strides;
|
Strides out_mask_strides;
|
||||||
bool a_mask_bool = false;
|
bool a_mask_bool;
|
||||||
bool b_mask_bool = false;
|
bool b_mask_bool;
|
||||||
bool out_mask_bool = false;
|
bool out_mask_bool;
|
||||||
if (has_op_mask) {
|
if (has_op_mask) {
|
||||||
auto& a_mask = inputs[inputs.size() - 2];
|
auto& a_mask = inputs[inputs.size() - 2];
|
||||||
auto& b_mask = inputs[inputs.size() - 1];
|
auto& b_mask = inputs[inputs.size() - 1];
|
||||||
@@ -423,6 +423,7 @@ void GatherMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto& rhs_indices = inputs[3];
|
auto& rhs_indices = inputs[3];
|
||||||
|
|
||||||
auto batch_shape = get_batch_dims(out.shape());
|
auto batch_shape = get_batch_dims(out.shape());
|
||||||
|
int batch_ndim = batch_shape.size();
|
||||||
|
|
||||||
auto batch_shape_A = get_batch_dims(a.shape());
|
auto batch_shape_A = get_batch_dims(a.shape());
|
||||||
auto batch_strides_A = get_batch_dims(a.strides());
|
auto batch_strides_A = get_batch_dims(a.strides());
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ void matmul_general(
|
|||||||
auto [b_transposed, ldb, b] = check_transpose(b_pre);
|
auto [b_transposed, ldb, b] = check_transpose(b_pre);
|
||||||
size_t M = a.shape(-2);
|
size_t M = a.shape(-2);
|
||||||
size_t N = b.shape(-1);
|
size_t N = b.shape(-1);
|
||||||
|
size_t K = a.shape(-1);
|
||||||
if (M == 0 || N == 0) {
|
if (M == 0 || N == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,9 +108,6 @@ void matmul_general(
|
|||||||
} else if (out.dtype() == float64) {
|
} else if (out.dtype() == float64) {
|
||||||
matmul_dispatch<double>(
|
matmul_dispatch<double>(
|
||||||
a, b, out, a_transposed, b_transposed, lda, ldb, alpha, beta, stream);
|
a, b, out, a_transposed, b_transposed, lda, ldb, alpha, beta, stream);
|
||||||
} else if (out.dtype() == complex64) {
|
|
||||||
matmul_dispatch<complex64_t>(
|
|
||||||
a, b, out, a_transposed, b_transposed, lda, ldb, alpha, beta, stream);
|
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("[Matmul::eval_cpu] Invalid type.");
|
throw std::runtime_error("[Matmul::eval_cpu] Invalid type.");
|
||||||
}
|
}
|
||||||
@@ -130,6 +128,10 @@ void Matmul::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AddMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void AddMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
if (out.dtype() != float32) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"[AddMM::eval_cpu] Currently only supports float32.");
|
||||||
|
}
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ void Reshape::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
|
|
||||||
void DynamicSlice::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void DynamicSlice::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(0));
|
out.set_data(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
@@ -361,7 +361,7 @@ void DynamicSliceUpdate::eval_cpu(
|
|||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out) {
|
array& out) {
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(0));
|
out.set_data(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +396,7 @@ void DynamicSliceUpdate::eval_cpu(
|
|||||||
void SliceUpdate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void SliceUpdate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
assert(inputs.size() == 2);
|
assert(inputs.size() == 2);
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
out.set_data(allocator::malloc(0));
|
out.set_data(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// Copyright © 2023 Apple Inc.
|
// Copyright © 2023 Apple Inc.
|
||||||
|
|
||||||
#include "mlx/backend/common/unary.h"
|
#include <cassert>
|
||||||
|
|
||||||
#include "mlx/backend/cpu/copy.h"
|
#include "mlx/backend/cpu/copy.h"
|
||||||
#include "mlx/backend/cpu/encoder.h"
|
#include "mlx/backend/cpu/encoder.h"
|
||||||
#include "mlx/backend/cpu/simd/simd.h"
|
#include "mlx/backend/cpu/simd/simd.h"
|
||||||
#include "mlx/backend/cpu/unary.h"
|
|
||||||
#include "mlx/backend/cpu/unary_ops.h"
|
|
||||||
#include "mlx/fast_primitives.h"
|
#include "mlx/fast_primitives.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
#include "mlx/utils.h"
|
#include "mlx/utils.h"
|
||||||
@@ -14,35 +13,6 @@ namespace mlx::core {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const static float MXFP4_LUT[16] = {
|
|
||||||
+0.0f,
|
|
||||||
+0.5f,
|
|
||||||
+1.0f,
|
|
||||||
+1.5f,
|
|
||||||
+2.0f,
|
|
||||||
+3.0f,
|
|
||||||
+4.0f,
|
|
||||||
+6.0f,
|
|
||||||
-0.0f,
|
|
||||||
-0.5f,
|
|
||||||
-1.0f,
|
|
||||||
-1.5f,
|
|
||||||
-2.0f,
|
|
||||||
-3.0f,
|
|
||||||
-4.0f,
|
|
||||||
-6.0f};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
static inline T dequantize_scale(uint8_t s) {
|
|
||||||
using FOrI = union {
|
|
||||||
bfloat16_t f;
|
|
||||||
uint16_t i;
|
|
||||||
};
|
|
||||||
FOrI out;
|
|
||||||
out.i = (s == 0 ? 0x40 : (static_cast<uint16_t>(s) << 7));
|
|
||||||
return static_cast<T>(out.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr short get_pack_factor(int bits, int wsize = 8) {
|
inline constexpr short get_pack_factor(int bits, int wsize = 8) {
|
||||||
return (bits == 3 || bits == 5) ? 8 : (bits == 6 ? 4 : wsize / bits);
|
return (bits == 3 || bits == 5) ? 8 : (bits == 6 ? 4 : wsize / bits);
|
||||||
}
|
}
|
||||||
@@ -437,229 +407,6 @@ void _qmm_dispatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_qmm(
|
|
||||||
T* result,
|
|
||||||
const T* x,
|
|
||||||
const uint32_t* w,
|
|
||||||
const uint8_t* scales,
|
|
||||||
int M,
|
|
||||||
int N,
|
|
||||||
int K) {
|
|
||||||
constexpr int group_size = 32;
|
|
||||||
constexpr int pack_factor = get_pack_factor(4, 8);
|
|
||||||
constexpr int packs_in_group = group_size / pack_factor;
|
|
||||||
|
|
||||||
for (int m = 0; m < M; m++) {
|
|
||||||
const uint8_t* w_local = (const uint8_t*)w;
|
|
||||||
const uint8_t* scales_local = scales;
|
|
||||||
|
|
||||||
std::fill(result, result + N, 0);
|
|
||||||
|
|
||||||
for (int k = 0; k < K; k++) {
|
|
||||||
T* result_local = result;
|
|
||||||
T xi = *x++;
|
|
||||||
|
|
||||||
for (int n = 0; n < N; n += group_size) {
|
|
||||||
T scale = dequantize_scale<T>(*scales_local++);
|
|
||||||
for (int ng = 0; ng < packs_in_group; ng++) {
|
|
||||||
uint8_t wi = *w_local++;
|
|
||||||
#pragma clang loop unroll(full)
|
|
||||||
for (int p = 0; p < pack_factor; p++) {
|
|
||||||
(*result_local++) +=
|
|
||||||
xi * scale * static_cast<T>(MXFP4_LUT[wi & 0xf]);
|
|
||||||
wi >>= 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result += N;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_qmm_t(
|
|
||||||
T* result,
|
|
||||||
const T* x,
|
|
||||||
const uint32_t* w,
|
|
||||||
const uint8_t* scales,
|
|
||||||
int M,
|
|
||||||
int N,
|
|
||||||
int K) {
|
|
||||||
constexpr int group_size = 32;
|
|
||||||
constexpr int pack_factor = get_pack_factor(4, 8);
|
|
||||||
constexpr int packs_in_group = group_size / pack_factor;
|
|
||||||
|
|
||||||
for (int m = 0; m < M; m++) {
|
|
||||||
const uint8_t* w_local = (const uint8_t*)w;
|
|
||||||
const uint8_t* scales_local = scales;
|
|
||||||
|
|
||||||
for (int n = 0; n < N; n++) {
|
|
||||||
const T* x_local = x;
|
|
||||||
T sum = 0;
|
|
||||||
for (int k = 0; k < K; k += group_size) {
|
|
||||||
T scale = dequantize_scale<T>(*scales_local++);
|
|
||||||
|
|
||||||
T gsum = 0;
|
|
||||||
for (int kw = 0; kw < packs_in_group; kw++) {
|
|
||||||
uint8_t wi = *w_local++;
|
|
||||||
#pragma clang loop unroll(full)
|
|
||||||
for (int p = 0; p < pack_factor; p++) {
|
|
||||||
gsum += (*x_local++) * static_cast<T>(MXFP4_LUT[wi & 0xf]);
|
|
||||||
wi >>= 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sum += scale * gsum;
|
|
||||||
}
|
|
||||||
*result = sum;
|
|
||||||
result++;
|
|
||||||
}
|
|
||||||
|
|
||||||
x += K;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int S>
|
|
||||||
simd::Simd<float, S> mxfp4_extract_bits_simd(const uint32_t* w) {
|
|
||||||
if constexpr (S == 8) {
|
|
||||||
constexpr std::array<uint32_t, 8> shifts_ = {{0, 4, 8, 12, 16, 20, 24, 28}};
|
|
||||||
auto shifts(*(simd::Simd<uint32_t, S>*)&shifts_);
|
|
||||||
auto wi = simd::Simd<uint32_t, S>(*w);
|
|
||||||
wi = wi >> shifts;
|
|
||||||
wi = wi & 0xf;
|
|
||||||
simd::Simd<float, S> w_out;
|
|
||||||
for (int i = 0; i < S; ++i) {
|
|
||||||
w_out[i] = MXFP4_LUT[wi[i]];
|
|
||||||
}
|
|
||||||
return w_out;
|
|
||||||
} else {
|
|
||||||
// Appease compiler.. but should never get here
|
|
||||||
throw std::runtime_error("Unsupported combination for simd qmm.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_qmm_t_simd(
|
|
||||||
T* result,
|
|
||||||
const T* x,
|
|
||||||
const uint32_t* w,
|
|
||||||
const uint8_t* scales,
|
|
||||||
int M,
|
|
||||||
int N,
|
|
||||||
int K) {
|
|
||||||
constexpr int group_size = 32;
|
|
||||||
constexpr int pack_factor = 32 / 4;
|
|
||||||
constexpr int packs_in_group = group_size / pack_factor;
|
|
||||||
constexpr int S = simd::max_size<T>;
|
|
||||||
static_assert(
|
|
||||||
S % pack_factor == 0, "SIMD size must be divisible by pack factor");
|
|
||||||
constexpr int packs_per_simd = S / pack_factor;
|
|
||||||
|
|
||||||
for (int m = 0; m < M; m++) {
|
|
||||||
const uint32_t* w_local = w;
|
|
||||||
const uint8_t* scales_local = scales;
|
|
||||||
|
|
||||||
for (int n = 0; n < N; n++) {
|
|
||||||
simd::Simd<float, S> acc(0);
|
|
||||||
auto x_local = x;
|
|
||||||
for (int k = 0; k < K; k += group_size) {
|
|
||||||
T scale = dequantize_scale<T>(*scales_local++);
|
|
||||||
|
|
||||||
simd::Simd<float, S> g_acc(0);
|
|
||||||
for (int kw = 0; kw < packs_in_group; kw += packs_per_simd) {
|
|
||||||
// Extract bits
|
|
||||||
auto wf = mxfp4_extract_bits_simd<S>(w_local);
|
|
||||||
w_local += packs_per_simd;
|
|
||||||
simd::Simd<float, S> x_simd = simd::load<T, S>(x_local);
|
|
||||||
g_acc = g_acc + x_simd * wf;
|
|
||||||
x_local += S;
|
|
||||||
}
|
|
||||||
acc = acc + scale * g_acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
*result = T(simd::sum(acc));
|
|
||||||
result++;
|
|
||||||
}
|
|
||||||
x += K;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_qmm_dispatch_transpose(
|
|
||||||
T* result,
|
|
||||||
const T* x,
|
|
||||||
const uint32_t* w,
|
|
||||||
const uint8_t* scales,
|
|
||||||
int M,
|
|
||||||
int N,
|
|
||||||
int K,
|
|
||||||
bool transposed_w) {
|
|
||||||
if (transposed_w) {
|
|
||||||
// the simd size must be a multiple of the number of elements per word
|
|
||||||
if constexpr (simd::max_size<T> % 8 == 0) {
|
|
||||||
mxfp4_qmm_t_simd<T>(result, x, w, scales, M, N, K);
|
|
||||||
} else {
|
|
||||||
mxfp4_qmm_t<T>(result, x, w, scales, M, N, K);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mxfp4_qmm<T>(result, x, w, scales, M, N, K);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_qmm_dispatch_typed(
|
|
||||||
array& out,
|
|
||||||
const array& x,
|
|
||||||
const array& w,
|
|
||||||
const array& scales,
|
|
||||||
bool transposed_w) {
|
|
||||||
int K = x.shape(-1);
|
|
||||||
int M = x.ndim() > 1 ? x.shape(-2) : 1;
|
|
||||||
int N = out.shape(-1);
|
|
||||||
int w_els = w.ndim() > 2 ? w.shape(-1) * w.shape(-2) : 0;
|
|
||||||
int g_els = w.ndim() > 2 ? scales.shape(-1) * scales.shape(-2) : 0;
|
|
||||||
int batch_size = x.size() / (K * M);
|
|
||||||
|
|
||||||
auto out_ptr = out.data<T>();
|
|
||||||
auto x_ptr = x.data<T>();
|
|
||||||
auto w_ptr = w.data<uint32_t>();
|
|
||||||
auto scales_ptr = scales.data<uint8_t>();
|
|
||||||
for (int i = 0; i < batch_size; i++) {
|
|
||||||
mxfp4_qmm_dispatch_transpose<T>(
|
|
||||||
out_ptr + i * M * N,
|
|
||||||
x_ptr + elem_to_loc(i * M * K, x.shape(), x.strides()),
|
|
||||||
w_ptr + elem_to_loc(i * w_els, w.shape(), w.strides()),
|
|
||||||
scales_ptr + elem_to_loc(i * g_els, scales.shape(), scales.strides()),
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
K,
|
|
||||||
transposed_w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mxfp4_qmm_dispatch(
|
|
||||||
array& out,
|
|
||||||
const array& x,
|
|
||||||
const array& w,
|
|
||||||
const array& scales,
|
|
||||||
bool transposed_w) {
|
|
||||||
switch (x.dtype()) {
|
|
||||||
case bfloat16:
|
|
||||||
mxfp4_qmm_dispatch_typed<bfloat16_t>(out, x, w, scales, transposed_w);
|
|
||||||
break;
|
|
||||||
case float16:
|
|
||||||
mxfp4_qmm_dispatch_typed<float16_t>(out, x, w, scales, transposed_w);
|
|
||||||
break;
|
|
||||||
case float32:
|
|
||||||
mxfp4_qmm_dispatch_typed<float>(out, x, w, scales, transposed_w);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::invalid_argument(
|
|
||||||
"[quantized_matmul] only floating types are supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void _bs_qmm_dispatch_typed(
|
void _bs_qmm_dispatch_typed(
|
||||||
array& out,
|
array& out,
|
||||||
@@ -766,106 +513,41 @@ void _bs_qmm_dispatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void mxfp4_bs_qmm_dispatch_typed(
|
|
||||||
array& out,
|
|
||||||
const array& x,
|
|
||||||
const array& w,
|
|
||||||
const array& scales,
|
|
||||||
const array& lhs_indices,
|
|
||||||
const array& rhs_indices,
|
|
||||||
bool transposed_w) {
|
|
||||||
int K = x.shape(-1);
|
|
||||||
int M = x.shape(-2);
|
|
||||||
int N = out.shape(-1);
|
|
||||||
|
|
||||||
int w_els = w.shape(-1) * w.shape(-2);
|
|
||||||
int g_els = scales.shape(-1) * scales.shape(-2);
|
|
||||||
|
|
||||||
auto out_ptr = out.data<T>();
|
|
||||||
auto x_ptr = x.data<T>();
|
|
||||||
auto w_ptr = w.data<uint32_t>();
|
|
||||||
auto scales_ptr = scales.data<uint8_t>();
|
|
||||||
auto lhs_indices_ptr = lhs_indices.data<uint32_t>();
|
|
||||||
auto rhs_indices_ptr = rhs_indices.data<uint32_t>();
|
|
||||||
|
|
||||||
for (int i = 0; i < lhs_indices.size(); i++) {
|
|
||||||
int x_idx = lhs_indices_ptr[elem_to_loc(
|
|
||||||
i, lhs_indices.shape(), lhs_indices.strides())];
|
|
||||||
int w_idx = rhs_indices_ptr[elem_to_loc(
|
|
||||||
i, rhs_indices.shape(), rhs_indices.strides())];
|
|
||||||
mxfp4_qmm_dispatch_transpose<T>(
|
|
||||||
out_ptr + i * M * N,
|
|
||||||
x_ptr + elem_to_loc(x_idx * M * K, x.shape(), x.strides()),
|
|
||||||
w_ptr + elem_to_loc(w_idx * w_els, w.shape(), w.strides()),
|
|
||||||
scales_ptr +
|
|
||||||
elem_to_loc(w_idx * g_els, scales.shape(), scales.strides()),
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
K,
|
|
||||||
transposed_w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mxfp4_bs_qmm_dispatch(
|
|
||||||
array& out,
|
|
||||||
const array& x,
|
|
||||||
const array& w,
|
|
||||||
const array& scales,
|
|
||||||
const array& lhs_indices,
|
|
||||||
const array& rhs_indices,
|
|
||||||
bool transposed_w) {
|
|
||||||
switch (x.dtype()) {
|
|
||||||
case float32:
|
|
||||||
mxfp4_bs_qmm_dispatch_typed<float>(
|
|
||||||
out, x, w, scales, lhs_indices, rhs_indices, transposed_w);
|
|
||||||
break;
|
|
||||||
case float16:
|
|
||||||
mxfp4_bs_qmm_dispatch_typed<float16_t>(
|
|
||||||
out, x, w, scales, lhs_indices, rhs_indices, transposed_w);
|
|
||||||
break;
|
|
||||||
case bfloat16:
|
|
||||||
mxfp4_bs_qmm_dispatch_typed<bfloat16_t>(
|
|
||||||
out, x, w, scales, lhs_indices, rhs_indices, transposed_w);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::invalid_argument(
|
|
||||||
"[quantized_matmul] only floating types are supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void QuantizedMatmul::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void QuantizedMatmul::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
assert(inputs.size() == 4);
|
||||||
|
|
||||||
auto& x_pre = inputs[0];
|
auto& x_pre = inputs[0];
|
||||||
auto& w_pre = inputs[1];
|
auto& w_pre = inputs[1];
|
||||||
auto& scales_pre = inputs[2];
|
auto& scales_pre = inputs[2];
|
||||||
|
auto& biases_pre = inputs[3];
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
std::vector<array> temps;
|
||||||
auto ensure_row_contiguous = [s = stream(), &encoder](const array& arr) {
|
auto ensure_row_contiguous = [s = stream(), &temps](const array& arr) {
|
||||||
if (arr.flags().row_contiguous) {
|
if (arr.flags().row_contiguous) {
|
||||||
return arr;
|
return arr;
|
||||||
} else {
|
} else {
|
||||||
auto arr_cpy = array(arr.shape(), arr.dtype(), nullptr, {});
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy_cpu(arr, arr_cpy, CopyType::General, s);
|
copy_cpu(arr, temps.back(), CopyType::General, s);
|
||||||
encoder.add_temporary(arr_cpy);
|
return temps.back();
|
||||||
return arr_cpy;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto x = ensure_row_contiguous(x_pre);
|
auto x = ensure_row_contiguous(x_pre);
|
||||||
auto w = ensure_row_contiguous(w_pre);
|
auto w = ensure_row_contiguous(w_pre);
|
||||||
auto scales = ensure_row_contiguous(scales_pre);
|
auto scales = ensure_row_contiguous(scales_pre);
|
||||||
|
auto biases = ensure_row_contiguous(biases_pre);
|
||||||
|
|
||||||
out.set_data(allocator::malloc(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
|
||||||
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
|
encoder.add_temporaries(std::move(temps));
|
||||||
encoder.set_input_array(x);
|
encoder.set_input_array(x);
|
||||||
encoder.set_input_array(w);
|
encoder.set_input_array(w);
|
||||||
encoder.set_input_array(scales);
|
encoder.set_input_array(scales);
|
||||||
encoder.set_output_array(out);
|
|
||||||
if (mode_ == QuantizationMode::Affine) {
|
|
||||||
auto biases = ensure_row_contiguous(inputs[3]);
|
|
||||||
encoder.set_input_array(biases);
|
encoder.set_input_array(biases);
|
||||||
|
encoder.set_output_array(out);
|
||||||
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
||||||
x = array::unsafe_weak_copy(x),
|
x = array::unsafe_weak_copy(x),
|
||||||
w = array::unsafe_weak_copy(w),
|
w = array::unsafe_weak_copy(w),
|
||||||
@@ -876,54 +558,48 @@ void QuantizedMatmul::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
transpose_ = transpose_]() mutable {
|
transpose_ = transpose_]() mutable {
|
||||||
_qmm_dispatch(out, x, w, scales, biases, group_size_, bits_, transpose_);
|
_qmm_dispatch(out, x, w, scales, biases, group_size_, bits_, transpose_);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
|
||||||
x = array::unsafe_weak_copy(x),
|
|
||||||
w = array::unsafe_weak_copy(w),
|
|
||||||
scales = array::unsafe_weak_copy(scales),
|
|
||||||
transpose_ = transpose_]() mutable {
|
|
||||||
mxfp4_qmm_dispatch(out, x, w, scales, transpose_);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatherQMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void GatherQMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
assert(inputs.size() == 6);
|
||||||
|
|
||||||
auto& x_pre = inputs[0];
|
auto& x_pre = inputs[0];
|
||||||
auto& w_pre = inputs[1];
|
auto& w_pre = inputs[1];
|
||||||
auto& scales_pre = inputs[2];
|
auto& scales_pre = inputs[2];
|
||||||
auto& lhs_indices = inputs[inputs.size() - 2];
|
auto& biases_pre = inputs[3];
|
||||||
auto& rhs_indices = inputs[inputs.size() - 1];
|
auto& lhs_indices = inputs[4];
|
||||||
|
auto& rhs_indices = inputs[5];
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
std::vector<array> temps;
|
||||||
auto ensure_row_contiguous_last_dims = [s = stream(),
|
auto ensure_row_contiguous_last_dims = [s = stream(),
|
||||||
&encoder](const array& arr) {
|
&temps](const array& arr) {
|
||||||
auto stride_0 = arr.strides()[arr.ndim() - 2];
|
auto stride_0 = arr.strides()[arr.ndim() - 2];
|
||||||
auto stride_1 = arr.strides()[arr.ndim() - 1];
|
auto stride_1 = arr.strides()[arr.ndim() - 1];
|
||||||
if (stride_0 == arr.shape(-1) && stride_1 == 1) {
|
if (stride_0 == arr.shape(-1) && stride_1 == 1) {
|
||||||
return arr;
|
return arr;
|
||||||
} else {
|
} else {
|
||||||
auto arr_cpy = array(arr.shape(), arr.dtype(), nullptr, {});
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy_cpu(arr, arr_cpy, CopyType::General, s);
|
copy_cpu(arr, temps.back(), CopyType::General, s);
|
||||||
encoder.add_temporary(arr_cpy);
|
return temps.back();
|
||||||
return arr_cpy;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto x = ensure_row_contiguous_last_dims(x_pre);
|
auto x = ensure_row_contiguous_last_dims(x_pre);
|
||||||
auto w = ensure_row_contiguous_last_dims(w_pre);
|
auto w = ensure_row_contiguous_last_dims(w_pre);
|
||||||
auto scales = ensure_row_contiguous_last_dims(scales_pre);
|
auto scales = ensure_row_contiguous_last_dims(scales_pre);
|
||||||
|
auto biases = ensure_row_contiguous_last_dims(biases_pre);
|
||||||
|
|
||||||
out.set_data(allocator::malloc(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
|
||||||
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
|
encoder.add_temporaries(std::move(temps));
|
||||||
encoder.set_input_array(x);
|
encoder.set_input_array(x);
|
||||||
encoder.set_input_array(w);
|
encoder.set_input_array(w);
|
||||||
encoder.set_input_array(scales);
|
encoder.set_input_array(scales);
|
||||||
|
encoder.set_input_array(biases);
|
||||||
encoder.set_input_array(lhs_indices);
|
encoder.set_input_array(lhs_indices);
|
||||||
encoder.set_input_array(rhs_indices);
|
encoder.set_input_array(rhs_indices);
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
if (mode_ == QuantizationMode::Affine) {
|
|
||||||
auto biases = ensure_row_contiguous_last_dims(inputs[3]);
|
|
||||||
encoder.set_input_array(biases);
|
|
||||||
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
||||||
x = array::unsafe_weak_copy(x),
|
x = array::unsafe_weak_copy(x),
|
||||||
w = array::unsafe_weak_copy(w),
|
w = array::unsafe_weak_copy(w),
|
||||||
@@ -946,18 +622,6 @@ void GatherQMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
bits_,
|
bits_,
|
||||||
transpose_);
|
transpose_);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
encoder.dispatch([out = array::unsafe_weak_copy(out),
|
|
||||||
x = array::unsafe_weak_copy(x),
|
|
||||||
w = array::unsafe_weak_copy(w),
|
|
||||||
scales = array::unsafe_weak_copy(scales),
|
|
||||||
lhs_indices = array::unsafe_weak_copy(lhs_indices),
|
|
||||||
rhs_indices = array::unsafe_weak_copy(rhs_indices),
|
|
||||||
transpose_ = transpose_]() mutable {
|
|
||||||
mxfp4_bs_qmm_dispatch(
|
|
||||||
out, x, w, scales, lhs_indices, rhs_indices, transpose_);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
@@ -1041,7 +705,7 @@ void dispatch_quantize(
|
|||||||
w_ptr, out_ptr, scales_ptr, biases_ptr, bits, group_size, w.size());
|
w_ptr, out_ptr, scales_ptr, biases_ptr, bits, group_size, w.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void fast::Quantize::eval_cpu(
|
void fast::AffineQuantize::eval_cpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs) {
|
std::vector<array>& outputs) {
|
||||||
auto ensure_row_contiguous = [s = stream()](const array& arr) {
|
auto ensure_row_contiguous = [s = stream()](const array& arr) {
|
||||||
@@ -1100,47 +764,7 @@ void fast::Quantize::eval_cpu(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"[fast::Quantize::eval_cpu] Only supports floating point inputs");
|
"[fast::AffineQuantize::eval_cpu] Only supports floating point inputs");
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void fast::ConvertFP8::eval_cpu(
|
|
||||||
const std::vector<array>& inputs,
|
|
||||||
std::vector<array>& outputs) {
|
|
||||||
auto& in = inputs[0];
|
|
||||||
auto& out = outputs[0];
|
|
||||||
set_unary_output_data(in, out);
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
|
||||||
encoder.set_input_array(in);
|
|
||||||
encoder.set_output_array(out);
|
|
||||||
encoder.dispatch([in = array::unsafe_weak_copy(in),
|
|
||||||
out = array::unsafe_weak_copy(out),
|
|
||||||
to_fp8 = to_fp8_]() mutable {
|
|
||||||
if (to_fp8) {
|
|
||||||
switch (in.dtype()) {
|
|
||||||
case float16:
|
|
||||||
unary_op<float16_t, uint8_t>(in, out, detail::ToFP8());
|
|
||||||
break;
|
|
||||||
case bfloat16:
|
|
||||||
unary_op<bfloat16_t, uint8_t>(in, out, detail::ToFP8());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
unary_op<float, uint8_t>(in, out, detail::ToFP8());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (out.dtype()) {
|
|
||||||
case float16:
|
|
||||||
unary_op<uint8_t, float16_t>(in, out, detail::FromFP8());
|
|
||||||
break;
|
|
||||||
case bfloat16:
|
|
||||||
unary_op<uint8_t, bfloat16_t>(in, out, detail::FromFP8());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
unary_op<uint8_t, float>(in, out, detail::FromFP8());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <arm_neon.h>
|
|
||||||
#include <simd/math.h>
|
#include <simd/math.h>
|
||||||
#include <simd/vector.h>
|
#include <simd/vector.h>
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@
|
|||||||
|
|
||||||
#include "mlx/backend/cpu/simd/base_simd.h"
|
#include "mlx/backend/cpu/simd/base_simd.h"
|
||||||
|
|
||||||
// There seems to be a bug in simd/base_simd.h
|
// There seems to be a bug in sims/base.h
|
||||||
// __XROS_2_0 is not defined, the expression evaluates
|
// __XROS_2_0 is not defined, the expression evaluates
|
||||||
// to true instead of false setting the SIMD library
|
// to true instead of false setting the SIMD library
|
||||||
// higher than it should be even on macOS < 15
|
// higher than it should be even on macOS < 15
|
||||||
@@ -201,15 +200,6 @@ SIMD_DEFAULT_COMPARISONS(<=)
|
|||||||
SIMD_DEFAULT_COMPARISONS(==)
|
SIMD_DEFAULT_COMPARISONS(==)
|
||||||
SIMD_DEFAULT_COMPARISONS(!=)
|
SIMD_DEFAULT_COMPARISONS(!=)
|
||||||
|
|
||||||
template <typename T, int N>
|
|
||||||
Simd<T, N> clz(Simd<T, N> x) {
|
|
||||||
auto a = *(uint32x4_t*)(&x);
|
|
||||||
auto b = *((uint32x4_t*)(&x) + 1);
|
|
||||||
a = vclzq_u32(a);
|
|
||||||
b = vclzq_u32(b);
|
|
||||||
return asd::make_uint8(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, int N>
|
template <typename T, int N>
|
||||||
Simd<T, N> atan2(Simd<T, N> a, Simd<T, N> b) {
|
Simd<T, N> atan2(Simd<T, N> a, Simd<T, N> b) {
|
||||||
return asd::atan2(a.value, b.value);
|
return asd::atan2(a.value, b.value);
|
||||||
@@ -244,7 +234,6 @@ Simd<T, N> remainder(Simd<T, N> a, Simd<T, N> b) {
|
|||||||
|
|
||||||
template <typename MaskT, typename T1, typename T2, int N>
|
template <typename MaskT, typename T1, typename T2, int N>
|
||||||
Simd<T1, N> select(Simd<MaskT, N> mask, Simd<T1, N> x, Simd<T2, N> y) {
|
Simd<T1, N> select(Simd<MaskT, N> mask, Simd<T1, N> x, Simd<T2, N> y) {
|
||||||
static_assert(std::is_same_v<MaskT, bool>);
|
|
||||||
if constexpr (sizeof(T1) == 1) {
|
if constexpr (sizeof(T1) == 1) {
|
||||||
return asd::bitselect(y.value, x.value, asd::convert<char>(mask.value));
|
return asd::bitselect(y.value, x.value, asd::convert<char>(mask.value));
|
||||||
} else if constexpr (sizeof(T1) == 2) {
|
} else if constexpr (sizeof(T1) == 2) {
|
||||||
@@ -262,13 +251,9 @@ Simd<T, N> pow(Simd<T, N> base, Simd<T, N> exp) {
|
|||||||
return asd::pow(base.value, exp.value);
|
return asd::pow(base.value, exp.value);
|
||||||
} else {
|
} else {
|
||||||
Simd<T, N> res = 1;
|
Simd<T, N> res = 1;
|
||||||
// Raising an integer to a negative power is undefined
|
while (any(exp)) {
|
||||||
if (any(exp < 0)) {
|
res = select(exp & 1, res * base, res);
|
||||||
return 0;
|
base = select(exp, base * base, base);
|
||||||
}
|
|
||||||
while (any(exp > 0)) {
|
|
||||||
res = select((exp & 1) != 0, res * base, res);
|
|
||||||
base = select(exp > 0, base * base, base);
|
|
||||||
exp = exp >> 1;
|
exp = exp >> 1;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -171,11 +171,6 @@ DEFAULT_BINARY(&)
|
|||||||
DEFAULT_BINARY(&&)
|
DEFAULT_BINARY(&&)
|
||||||
DEFAULT_BINARY(||)
|
DEFAULT_BINARY(||)
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
Simd<T, 1> clz(Simd<T, 1> x_) {
|
|
||||||
return __builtin_clz(x_.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Simd<T, 1> remainder(Simd<T, 1> a_, Simd<T, 1> b_) {
|
Simd<T, 1> remainder(Simd<T, 1> a_, Simd<T, 1> b_) {
|
||||||
T a = a_.value;
|
T a = a_.value;
|
||||||
|
|||||||
@@ -15,18 +15,6 @@ namespace mlx::core {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// NaN-aware comparator that places NaNs at the end
|
|
||||||
template <typename T>
|
|
||||||
bool nan_aware_less(T a, T b) {
|
|
||||||
if constexpr (std::is_floating_point_v<T> || std::is_same_v<T, complex64_t>) {
|
|
||||||
if (std::isnan(a))
|
|
||||||
return false;
|
|
||||||
if (std::isnan(b))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return a < b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct StridedIterator {
|
struct StridedIterator {
|
||||||
using iterator_category = std::random_access_iterator_tag;
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
@@ -39,7 +27,7 @@ struct StridedIterator {
|
|||||||
StridedIterator() = default;
|
StridedIterator() = default;
|
||||||
|
|
||||||
explicit StridedIterator(T* ptr, int64_t stride, difference_type offset = 0)
|
explicit StridedIterator(T* ptr, int64_t stride, difference_type offset = 0)
|
||||||
: stride_(stride), ptr_(ptr + offset * stride) {}
|
: ptr_(ptr + offset * stride), stride_(stride) {}
|
||||||
|
|
||||||
explicit StridedIterator(array& arr, int axis, difference_type offset = 0)
|
explicit StridedIterator(array& arr, int axis, difference_type offset = 0)
|
||||||
: StridedIterator(arr.data<T>(), arr.strides()[axis], offset) {}
|
: StridedIterator(arr.data<T>(), arr.strides()[axis], offset) {}
|
||||||
@@ -142,7 +130,7 @@ void sort(array& out, int axis) {
|
|||||||
StridedIterator st(data_ptr, axis_stride, 0);
|
StridedIterator st(data_ptr, axis_stride, 0);
|
||||||
StridedIterator ed(data_ptr, axis_stride, axis_size);
|
StridedIterator ed(data_ptr, axis_stride, axis_size);
|
||||||
|
|
||||||
std::stable_sort(st, ed, nan_aware_less<T>);
|
std::stable_sort(st, ed);
|
||||||
src_it.step();
|
src_it.step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,15 +184,6 @@ void argsort(const array& in, array& out, int axis) {
|
|||||||
std::stable_sort(st, ed, [data_ptr, in_stride](IdxT a, IdxT b) {
|
std::stable_sort(st, ed, [data_ptr, in_stride](IdxT a, IdxT b) {
|
||||||
auto v1 = data_ptr[a * in_stride];
|
auto v1 = data_ptr[a * in_stride];
|
||||||
auto v2 = data_ptr[b * in_stride];
|
auto v2 = data_ptr[b * in_stride];
|
||||||
|
|
||||||
// Handle NaNs (place them at the end)
|
|
||||||
if (std::is_floating_point<T>::value) {
|
|
||||||
if (std::isnan(v1))
|
|
||||||
return false;
|
|
||||||
if (std::isnan(v2))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return v1 < v2 || (v1 == v2 && a < b);
|
return v1 < v2 || (v1 == v2 && a < b);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -240,7 +219,7 @@ void partition(array& out, int axis, int kth) {
|
|||||||
StridedIterator md(data_ptr, axis_stride, kth);
|
StridedIterator md(data_ptr, axis_stride, kth);
|
||||||
StridedIterator ed(data_ptr, axis_stride, axis_size);
|
StridedIterator ed(data_ptr, axis_stride, axis_size);
|
||||||
|
|
||||||
std::nth_element(st, md, ed, nan_aware_less<T>);
|
std::nth_element(st, md, ed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,15 +276,6 @@ void argpartition(const array& in, array& out, int axis, int kth) {
|
|||||||
std::nth_element(st, md, ed, [data_ptr, in_stride](IdxT a, IdxT b) {
|
std::nth_element(st, md, ed, [data_ptr, in_stride](IdxT a, IdxT b) {
|
||||||
auto v1 = data_ptr[a * in_stride];
|
auto v1 = data_ptr[a * in_stride];
|
||||||
auto v2 = data_ptr[b * in_stride];
|
auto v2 = data_ptr[b * in_stride];
|
||||||
|
|
||||||
// Handle NaNs (place them at the end)
|
|
||||||
if (std::is_floating_point<T>::value) {
|
|
||||||
if (std::isnan(v1))
|
|
||||||
return false;
|
|
||||||
if (std::isnan(v2))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return v1 < v2 || (v1 == v2 && a < b);
|
return v1 < v2 || (v1 == v2 && a < b);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,26 +81,40 @@ void svd_impl(
|
|||||||
// Vᵀ of shape N x N. (M x M in lapack).
|
// Vᵀ of shape N x N. (M x M in lapack).
|
||||||
const int ldvt = M;
|
const int ldvt = M;
|
||||||
|
|
||||||
auto jobz = (u_ptr) ? "A" : "N";
|
auto job_u = (u_ptr) ? "V" : "N";
|
||||||
|
auto job_vt = (u_ptr) ? "V" : "N";
|
||||||
|
static constexpr auto range = "A";
|
||||||
|
|
||||||
|
// Will contain the number of singular values after the call has returned.
|
||||||
|
int ns = 0;
|
||||||
T workspace_dimension = 0;
|
T workspace_dimension = 0;
|
||||||
|
|
||||||
// Will contain the indices of eigenvectors that failed to converge (not
|
// Will contain the indices of eigenvectors that failed to converge (not
|
||||||
// used here but required by lapack).
|
// used here but required by lapack).
|
||||||
auto iwork = array::Data{allocator::malloc(sizeof(int) * 8 * K)};
|
auto iwork = array::Data{allocator::malloc(sizeof(int) * 12 * K)};
|
||||||
|
|
||||||
static const int lwork_query = -1;
|
static const int lwork_query = -1;
|
||||||
|
|
||||||
|
static const int ignored_int = 0;
|
||||||
|
static const T ignored_float = 0;
|
||||||
|
|
||||||
int info;
|
int info;
|
||||||
|
|
||||||
// Compute workspace size.
|
// Compute workspace size.
|
||||||
gesdd<T>(
|
gesvdx<T>(
|
||||||
/* jobz = */ jobz,
|
/* jobu = */ job_u,
|
||||||
|
/* jobvt = */ job_vt,
|
||||||
|
/* range = */ range,
|
||||||
// M and N are swapped since lapack expects column-major.
|
// M and N are swapped since lapack expects column-major.
|
||||||
/* m = */ &N,
|
/* m = */ &N,
|
||||||
/* n = */ &M,
|
/* n = */ &M,
|
||||||
/* a = */ nullptr,
|
/* a = */ nullptr,
|
||||||
/* lda = */ &lda,
|
/* lda = */ &lda,
|
||||||
|
/* vl = */ &ignored_float,
|
||||||
|
/* vu = */ &ignored_float,
|
||||||
|
/* il = */ &ignored_int,
|
||||||
|
/* iu = */ &ignored_int,
|
||||||
|
/* ns = */ &ns,
|
||||||
/* s = */ nullptr,
|
/* s = */ nullptr,
|
||||||
/* u = */ nullptr,
|
/* u = */ nullptr,
|
||||||
/* ldu = */ &ldu,
|
/* ldu = */ &ldu,
|
||||||
@@ -122,13 +136,20 @@ void svd_impl(
|
|||||||
|
|
||||||
// Loop over matrices.
|
// Loop over matrices.
|
||||||
for (int i = 0; i < num_matrices; i++) {
|
for (int i = 0; i < num_matrices; i++) {
|
||||||
gesdd<T>(
|
gesvdx<T>(
|
||||||
/* jobz = */ jobz,
|
/* jobu = */ job_u,
|
||||||
|
/* jobvt = */ job_vt,
|
||||||
|
/* range = */ range,
|
||||||
// M and N are swapped since lapack expects column-major.
|
// M and N are swapped since lapack expects column-major.
|
||||||
/* m = */ &N,
|
/* m = */ &N,
|
||||||
/* n = */ &M,
|
/* n = */ &M,
|
||||||
/* a = */ in_ptr + M * N * i,
|
/* a = */ in_ptr + M * N * i,
|
||||||
/* lda = */ &lda,
|
/* lda = */ &lda,
|
||||||
|
/* vl = */ &ignored_float,
|
||||||
|
/* vu = */ &ignored_float,
|
||||||
|
/* il = */ &ignored_int,
|
||||||
|
/* iu = */ &ignored_int,
|
||||||
|
/* ns = */ &ns,
|
||||||
/* s = */ s_ptr + K * i,
|
/* s = */ s_ptr + K * i,
|
||||||
// According to the identity above, lapack will write Vᵀᵀ as U.
|
// According to the identity above, lapack will write Vᵀᵀ as U.
|
||||||
/* u = */ vt_ptr ? vt_ptr + N * N * i : nullptr,
|
/* u = */ vt_ptr ? vt_ptr + N * N * i : nullptr,
|
||||||
@@ -146,6 +167,13 @@ void svd_impl(
|
|||||||
ss << "svd_impl: sgesvdx_ failed with code " << info;
|
ss << "svd_impl: sgesvdx_ failed with code " << info;
|
||||||
throw std::runtime_error(ss.str());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ns != K) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "svd_impl: expected " << K << " singular values, but " << ns
|
||||||
|
<< " were computed.";
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
encoder.add_temporary(in);
|
encoder.add_temporary(in);
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ void unary_op(const array& a, array& out, Op) {
|
|||||||
auto ndim = a.ndim();
|
auto ndim = a.ndim();
|
||||||
if (a.flags().contiguous) {
|
if (a.flags().contiguous) {
|
||||||
auto size = a.data_size();
|
auto size = a.data_size();
|
||||||
constexpr int N = std::min(simd::max_size<T>, simd::max_size<U>);
|
constexpr int N = simd::max_size<T>;
|
||||||
while (size >= N) {
|
while (size >= N) {
|
||||||
simd::store(dst, simd::Simd<U, N>(Op{}(simd::load<T, N>(src))));
|
simd::store(dst, Op{}(simd::load<T, N>(src)));
|
||||||
size -= N;
|
size -= N;
|
||||||
src += N;
|
src += N;
|
||||||
dst += N;
|
dst += N;
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ struct Real {
|
|||||||
struct Sigmoid {
|
struct Sigmoid {
|
||||||
template <int N, typename T>
|
template <int N, typename T>
|
||||||
Simd<T, N> operator()(Simd<T, N> x) {
|
Simd<T, N> operator()(Simd<T, N> x) {
|
||||||
auto y = 1.0f / (1.0f + simd::exp(simd::abs(x)));
|
return 1.0f / (1.0f + simd::exp(-x));
|
||||||
return simd::select(x < Simd<T, N>{0}, y, Simd<T, N>{1} - y);
|
|
||||||
}
|
}
|
||||||
SINGLE()
|
SINGLE()
|
||||||
};
|
};
|
||||||
@@ -108,73 +107,4 @@ struct Square {
|
|||||||
SINGLE()
|
SINGLE()
|
||||||
};
|
};
|
||||||
|
|
||||||
template <int N>
|
|
||||||
Simd<float, N> fp32_from_bits(Simd<uint32_t, N> x) {
|
|
||||||
return *(Simd<float, N>*)(&x);
|
|
||||||
}
|
|
||||||
template <int N>
|
|
||||||
Simd<uint32_t, N> fp32_to_bits(Simd<float, N> x) {
|
|
||||||
return *(Simd<uint32_t, N>*)(&x);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ToFP8 {
|
|
||||||
template <typename T, int N>
|
|
||||||
Simd<uint8_t, N> operator()(Simd<T, N> f) {
|
|
||||||
uint32_t fp8_max = 543 << 21;
|
|
||||||
auto denorm_mask = Simd<uint32_t, N>(141 << 23);
|
|
||||||
Simd<uint32_t, N> f_bits;
|
|
||||||
Simd<float, N> f32 = f;
|
|
||||||
f_bits = fp32_to_bits(f32);
|
|
||||||
Simd<uint8_t, N> result = 0u;
|
|
||||||
auto sign = f_bits & 0x80000000;
|
|
||||||
f_bits = f_bits ^ sign;
|
|
||||||
|
|
||||||
auto f_bits_low =
|
|
||||||
fp32_to_bits(fp32_from_bits(f_bits) + fp32_from_bits(denorm_mask));
|
|
||||||
auto result_low = Simd<uint8_t, N>(f_bits_low - denorm_mask);
|
|
||||||
|
|
||||||
auto mant_odd = Simd<uint8_t, N>((f_bits >> 20) & 1);
|
|
||||||
auto f_bits_high = f_bits + (((uint32_t)(7 - 127) << 23) + 0x7FFFF);
|
|
||||||
f_bits_high = f_bits_high + Simd<uint32_t, N>(mant_odd);
|
|
||||||
|
|
||||||
auto result_high = Simd<uint8_t, N>(f_bits_high >> 20);
|
|
||||||
result = select(f_bits < (121 << 23), result_low, result_high);
|
|
||||||
|
|
||||||
auto result_sat = Simd<uint8_t, N>(0x7E);
|
|
||||||
result = select(f_bits >= fp8_max, result_sat, result);
|
|
||||||
return result | Simd<uint8_t, N>(sign >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
uint8_t operator()(T x) {
|
|
||||||
return (*this)(Simd<T, 1>(x)).value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FromFP8 {
|
|
||||||
template <int N>
|
|
||||||
Simd<float, N> operator()(Simd<uint8_t, N> x) {
|
|
||||||
auto w = Simd<uint32_t, N>(x) << 24;
|
|
||||||
auto sign = w & 0x80000000;
|
|
||||||
auto nonsign = w & 0x7FFFFFFF;
|
|
||||||
|
|
||||||
auto renorm_shift = clz(nonsign);
|
|
||||||
renorm_shift = simd::select(
|
|
||||||
renorm_shift > Simd<uint32_t, N>{4},
|
|
||||||
renorm_shift - Simd<uint32_t, N>{4},
|
|
||||||
Simd<uint32_t, N>{0});
|
|
||||||
|
|
||||||
Simd<int32_t, N> inf_nan_mask =
|
|
||||||
(Simd<int32_t, N>(nonsign + 0x01000000) >> 8) & 0x7F800000;
|
|
||||||
auto zero_mask = Simd<int32_t, N>(nonsign - 1) >> 31;
|
|
||||||
auto result = sign |
|
|
||||||
((((nonsign << renorm_shift >> 4) + ((0x78 - renorm_shift) << 23)) |
|
|
||||||
inf_nan_mask) &
|
|
||||||
~zero_mask);
|
|
||||||
return fp32_from_bits(result);
|
|
||||||
}
|
|
||||||
float operator()(uint8_t x) {
|
|
||||||
return (*this)(Simd<uint8_t, 1>(x)).value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace mlx::core::detail
|
} // namespace mlx::core::detail
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ target_sources(
|
|||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/allocator.cpp
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/allocator.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/arange.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/arange.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/arg_reduce.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/arg_reduce.cu
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/binary.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/binary_two.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/binary_two.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/compiled.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/compiled.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/copy.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/copy.cu
|
||||||
@@ -16,23 +17,18 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/copy/copy_general_dynamic.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/copy/copy_general_dynamic.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/copy/copy_general_input.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/copy/copy_general_input.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/conv.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/conv.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/conv/gemm_conv.cu
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/conv/gemm_grouped_conv.cu
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/cuda.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/cuda.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/cudnn_utils.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/custom_kernel.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/device.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/device.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/distributed.cu
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/eval.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/eval.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/event.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/event.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fence.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/fence.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/gemms/gemv.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/gemms/gemv.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/gemms/cublas_gemm.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/gemms/cublas_gemm.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/gemms/steel_gemm.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/jit_module.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/jit_module.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/indexing.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/indexing.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/kernel_utils.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/kernel_utils.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/matmul.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/matmul.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/load.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/layer_norm.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/layer_norm.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/logsumexp.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/logsumexp.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/primitives.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/primitives.cpp
|
||||||
@@ -50,21 +46,12 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/softmax.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/softmax.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/sort.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/sort.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ternary.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/ternary.cu
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/unary.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/quantized/affine_quantize.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/quantized/affine_quantize.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/quantized/fp_quantize.cu
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/quantized/quantized.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/quantized/quantized.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/quantized/convert_fp8.cu
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/worker.cpp)
|
${CMAKE_CURRENT_SOURCE_DIR}/worker.cpp)
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/binary)
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/unary)
|
|
||||||
|
|
||||||
# fp4 is not available on < 12.8
|
|
||||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12.8.0)
|
|
||||||
target_include_directories(mlx PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/quantized/)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9.0)
|
if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9.0)
|
||||||
target_sources(
|
target_sources(
|
||||||
mlx PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/gemms/cublas_gemm_batched_12_9.cu)
|
mlx PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/gemms/cublas_gemm_batched_12_9.cu)
|
||||||
@@ -162,7 +149,7 @@ target_link_libraries(mlx PRIVATE CUDA::nvrtc CUDA::cuda_driver)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
cudnn
|
cudnn
|
||||||
GIT_REPOSITORY https://github.com/NVIDIA/cudnn-frontend.git
|
GIT_REPOSITORY https://github.com/NVIDIA/cudnn-frontend.git
|
||||||
GIT_TAG v1.14.0
|
GIT_TAG v1.12.1
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
EXCLUDE_FROM_ALL)
|
EXCLUDE_FROM_ALL)
|
||||||
set(CUDNN_FRONTEND_SKIP_JSON_LIB ON)
|
set(CUDNN_FRONTEND_SKIP_JSON_LIB ON)
|
||||||
@@ -178,6 +165,7 @@ target_link_libraries(mlx PRIVATE CUDNN::cudnn_all)
|
|||||||
# Suppress nvcc warnings on MLX headers.
|
# Suppress nvcc warnings on MLX headers.
|
||||||
target_compile_options(mlx PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-Xcudafe
|
target_compile_options(mlx PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-Xcudafe
|
||||||
--diag_suppress=997>)
|
--diag_suppress=997>)
|
||||||
|
|
||||||
# Install CCCL headers for JIT.
|
# Install CCCL headers for JIT.
|
||||||
install(DIRECTORY ${cccl_SOURCE_DIR}/include/cuda
|
install(DIRECTORY ${cccl_SOURCE_DIR}/include/cuda
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cccl)
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cccl)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
#include "mlx/backend/cuda/allocator.h"
|
#include "mlx/backend/cuda/allocator.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
|
||||||
#include "mlx/backend/cuda/utils.h"
|
#include "mlx/backend/cuda/utils.h"
|
||||||
#include "mlx/utils.h"
|
#include "mlx/utils.h"
|
||||||
|
|
||||||
@@ -31,20 +30,8 @@ SmallSizePool::SmallSizePool() {
|
|||||||
next_free_ = buffer_;
|
next_free_ = buffer_;
|
||||||
|
|
||||||
CHECK_CUDA_ERROR(cudaMallocManaged(&data_, small_pool_size));
|
CHECK_CUDA_ERROR(cudaMallocManaged(&data_, small_pool_size));
|
||||||
|
|
||||||
int device_count = 0;
|
|
||||||
CHECK_CUDA_ERROR(cudaGetDeviceCount(&device_count));
|
|
||||||
for (int i = 0; i < device_count; ++i) {
|
|
||||||
#if CUDART_VERSION >= 13000
|
|
||||||
cudaMemLocation loc;
|
|
||||||
loc.type = cudaMemLocationTypeDevice;
|
|
||||||
loc.id = i;
|
|
||||||
#else
|
|
||||||
int loc = i;
|
|
||||||
#endif // CUDART_VERSION >= 13000
|
|
||||||
CHECK_CUDA_ERROR(
|
CHECK_CUDA_ERROR(
|
||||||
cudaMemAdvise(data_, small_pool_size, cudaMemAdviseSetAccessedBy, loc));
|
cudaMemAdvise(data_, small_pool_size, cudaMemAdviseSetReadMostly, 0));
|
||||||
}
|
|
||||||
|
|
||||||
auto curr = next_free_;
|
auto curr = next_free_;
|
||||||
for (size_t i = 1; i < num_blocks; ++i) {
|
for (size_t i = 1; i < num_blocks; ++i) {
|
||||||
@@ -68,7 +55,6 @@ CudaBuffer* SmallSizePool::malloc() {
|
|||||||
next_free_ = next_free_->next;
|
next_free_ = next_free_->next;
|
||||||
b->buf.data = static_cast<char*>(data_) + i * small_block_size;
|
b->buf.data = static_cast<char*>(data_) + i * small_block_size;
|
||||||
b->buf.size = small_block_size;
|
b->buf.size = small_block_size;
|
||||||
b->buf.device = -1;
|
|
||||||
return &b->buf;
|
return &b->buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,22 +76,14 @@ CudaAllocator::CudaAllocator()
|
|||||||
page_size,
|
page_size,
|
||||||
[](CudaBuffer* buf) { return buf->size; },
|
[](CudaBuffer* buf) { return buf->size; },
|
||||||
[this](CudaBuffer* buf) { cuda_free(buf); }) {
|
[this](CudaBuffer* buf) { cuda_free(buf); }) {
|
||||||
|
// TODO: Set memory limit for multi-device.
|
||||||
size_t free, total;
|
size_t free, total;
|
||||||
CHECK_CUDA_ERROR(cudaMemGetInfo(&free, &total));
|
CHECK_CUDA_ERROR(cudaMemGetInfo(&free, &total));
|
||||||
memory_limit_ = total * 0.95;
|
memory_limit_ = total * 0.8;
|
||||||
max_pool_size_ = memory_limit_;
|
max_pool_size_ = memory_limit_;
|
||||||
|
|
||||||
int device_count = 0;
|
|
||||||
CHECK_CUDA_ERROR(cudaGetDeviceCount(&device_count));
|
|
||||||
for (int i = 0; i < device_count; ++i) {
|
|
||||||
CHECK_CUDA_ERROR(cudaSetDevice(i));
|
|
||||||
cudaStream_t s;
|
|
||||||
CHECK_CUDA_ERROR(cudaStreamCreateWithFlags(&s, cudaStreamNonBlocking));
|
|
||||||
free_streams_.push_back(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer CudaAllocator::malloc_impl(size_t size, cudaStream_t stream) {
|
Buffer CudaAllocator::malloc(size_t size) {
|
||||||
// Find available buffer from cache.
|
// Find available buffer from cache.
|
||||||
auto orig_size = size;
|
auto orig_size = size;
|
||||||
std::unique_lock lock(mutex_);
|
std::unique_lock lock(mutex_);
|
||||||
@@ -132,17 +110,8 @@ Buffer CudaAllocator::malloc_impl(size_t size, cudaStream_t stream) {
|
|||||||
}
|
}
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
int device = -1;
|
buf = new CudaBuffer{nullptr, size};
|
||||||
if (stream != nullptr) {
|
cudaError_t err = cudaMallocManaged(&buf->data, size);
|
||||||
cudaStreamGetDevice(stream, &device);
|
|
||||||
}
|
|
||||||
buf = new CudaBuffer{nullptr, size, device};
|
|
||||||
cudaError_t err;
|
|
||||||
if (device == -1) {
|
|
||||||
err = cudaMallocManaged(&buf->data, size);
|
|
||||||
} else {
|
|
||||||
err = cudaMallocAsync(&buf->data, size, stream);
|
|
||||||
}
|
|
||||||
if (err != cudaSuccess && err != cudaErrorMemoryAllocation) {
|
if (err != cudaSuccess && err != cudaErrorMemoryAllocation) {
|
||||||
throw std::runtime_error(fmt::format(
|
throw std::runtime_error(fmt::format(
|
||||||
"cudaMallocManaged failed: {}.", cudaGetErrorString(err)));
|
"cudaMallocManaged failed: {}.", cudaGetErrorString(err)));
|
||||||
@@ -160,14 +129,6 @@ Buffer CudaAllocator::malloc_impl(size_t size, cudaStream_t stream) {
|
|||||||
return Buffer{buf};
|
return Buffer{buf};
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer CudaAllocator::malloc_async(size_t size, cudaStream_t stream) {
|
|
||||||
return malloc_impl(size, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer CudaAllocator::malloc(size_t size) {
|
|
||||||
return malloc_impl(size, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CudaAllocator::free(Buffer buffer) {
|
void CudaAllocator::free(Buffer buffer) {
|
||||||
auto* buf = static_cast<CudaBuffer*>(buffer.ptr());
|
auto* buf = static_cast<CudaBuffer*>(buffer.ptr());
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
@@ -195,12 +156,8 @@ size_t CudaAllocator::size(Buffer buffer) const {
|
|||||||
void CudaAllocator::cuda_free(CudaBuffer* buf) {
|
void CudaAllocator::cuda_free(CudaBuffer* buf) {
|
||||||
if (scalar_pool_.in_pool(buf)) {
|
if (scalar_pool_.in_pool(buf)) {
|
||||||
scalar_pool_.free(buf);
|
scalar_pool_.free(buf);
|
||||||
} else {
|
|
||||||
if (buf->device >= 0) {
|
|
||||||
cudaFreeAsync(buf->data, free_streams_[buf->device]);
|
|
||||||
} else {
|
} else {
|
||||||
cudaFree(buf->data);
|
cudaFree(buf->data);
|
||||||
}
|
|
||||||
delete buf;
|
delete buf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,16 +208,6 @@ CudaAllocator& allocator() {
|
|||||||
return *allocator_;
|
return *allocator_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer malloc_async(size_t size, cudaStream_t stream) {
|
|
||||||
auto buffer = allocator().malloc_async(size, stream);
|
|
||||||
if (size && !buffer.ptr()) {
|
|
||||||
std::ostringstream msg;
|
|
||||||
msg << "[malloc_async] Unable to allocate " << size << " bytes.";
|
|
||||||
throw std::runtime_error(msg.str());
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace cu
|
} // namespace cu
|
||||||
|
|
||||||
namespace allocator {
|
namespace allocator {
|
||||||
@@ -273,19 +220,7 @@ void* Buffer::raw_ptr() {
|
|||||||
if (!ptr_) {
|
if (!ptr_) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto& cbuf = *static_cast<cu::CudaBuffer*>(ptr_);
|
return static_cast<cu::CudaBuffer*>(ptr_)->data;
|
||||||
if (cbuf.device != -1) {
|
|
||||||
// TODO maybe make this async on a i/o stream to avoid synchronizing the
|
|
||||||
// device on malloc/and free
|
|
||||||
void* new_data;
|
|
||||||
CHECK_CUDA_ERROR(cudaMallocManaged(&new_data, cbuf.size));
|
|
||||||
cbuf.device = -1;
|
|
||||||
CHECK_CUDA_ERROR(
|
|
||||||
cudaMemcpy(new_data, cbuf.data, cbuf.size, cudaMemcpyDefault));
|
|
||||||
CHECK_CUDA_ERROR(cudaFree(cbuf.data));
|
|
||||||
cbuf.data = new_data;
|
|
||||||
}
|
|
||||||
return cbuf.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace allocator
|
} // namespace allocator
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
|
|
||||||
#include "mlx/allocator.h"
|
#include "mlx/allocator.h"
|
||||||
#include "mlx/backend/common/buffer_cache.h"
|
#include "mlx/backend/common/buffer_cache.h"
|
||||||
#include "mlx/backend/cuda/cuda_utils.h"
|
|
||||||
|
|
||||||
#include <cuda_runtime.h>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -19,7 +17,6 @@ using allocator::Buffer;
|
|||||||
struct CudaBuffer {
|
struct CudaBuffer {
|
||||||
void* data;
|
void* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
int device; // -1 for managed
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SmallSizePool {
|
class SmallSizePool {
|
||||||
@@ -48,7 +45,6 @@ class SmallSizePool {
|
|||||||
class CudaAllocator : public allocator::Allocator {
|
class CudaAllocator : public allocator::Allocator {
|
||||||
public:
|
public:
|
||||||
Buffer malloc(size_t size) override;
|
Buffer malloc(size_t size) override;
|
||||||
Buffer malloc_async(size_t size, cudaStream_t stream);
|
|
||||||
void free(Buffer buffer) override;
|
void free(Buffer buffer) override;
|
||||||
size_t size(Buffer buffer) const override;
|
size_t size(Buffer buffer) const override;
|
||||||
|
|
||||||
@@ -62,7 +58,6 @@ class CudaAllocator : public allocator::Allocator {
|
|||||||
void clear_cache();
|
void clear_cache();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Buffer malloc_impl(size_t size, cudaStream_t stream);
|
|
||||||
void cuda_free(CudaBuffer* buf);
|
void cuda_free(CudaBuffer* buf);
|
||||||
|
|
||||||
CudaAllocator();
|
CudaAllocator();
|
||||||
@@ -74,12 +69,9 @@ class CudaAllocator : public allocator::Allocator {
|
|||||||
BufferCache<CudaBuffer> buffer_cache_;
|
BufferCache<CudaBuffer> buffer_cache_;
|
||||||
size_t active_memory_{0};
|
size_t active_memory_{0};
|
||||||
size_t peak_memory_{0};
|
size_t peak_memory_{0};
|
||||||
std::vector<cudaStream_t> free_streams_;
|
|
||||||
SmallSizePool scalar_pool_;
|
SmallSizePool scalar_pool_;
|
||||||
};
|
};
|
||||||
|
|
||||||
CudaAllocator& allocator();
|
CudaAllocator& allocator();
|
||||||
|
|
||||||
Buffer malloc_async(size_t size, cudaStream_t stream);
|
|
||||||
|
|
||||||
} // namespace mlx::core::cu
|
} // namespace mlx::core::cu
|
||||||
|
|||||||
@@ -6,33 +6,23 @@
|
|||||||
#include "mlx/dtype_utils.h"
|
#include "mlx/dtype_utils.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
|
||||||
#include <nvtx3/nvtx3.hpp>
|
#include <nvtx3/nvtx3.hpp>
|
||||||
|
#include <thrust/device_ptr.h>
|
||||||
|
#include <thrust/transform.h>
|
||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
namespace cu {
|
namespace cu {
|
||||||
|
|
||||||
namespace cg = cooperative_groups;
|
template <typename T>
|
||||||
|
struct Arange {
|
||||||
|
const T start;
|
||||||
|
const T step;
|
||||||
|
|
||||||
template <typename T, typename IdxT, int N_WRITES>
|
__device__ T operator()(uint32_t i) const {
|
||||||
__global__ void arange(T* out, IdxT size, T start, T step) {
|
return start + i * step;
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
|
||||||
|
|
||||||
if ((index + 1) * N_WRITES > size) {
|
|
||||||
for (IdxT i = index * N_WRITES; i < size; ++i) {
|
|
||||||
out[i] = start + i * step;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AlignedVector<T, N_WRITES> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_WRITES; ++i) {
|
|
||||||
out_vec[i] = start + (index * N_WRITES + i) * step;
|
|
||||||
}
|
|
||||||
|
|
||||||
store_vector<N_WRITES>(out, index, out_vec);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace cu
|
} // namespace cu
|
||||||
|
|
||||||
@@ -41,27 +31,24 @@ void Arange::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
|
||||||
auto& encoder = cu::get_command_encoder(stream());
|
auto& encoder = cu::get_command_encoder(stream());
|
||||||
out.set_data(cu::malloc_async(out.nbytes(), encoder.stream()));
|
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
|
|
||||||
|
auto capture = encoder.capture_context();
|
||||||
dispatch_int_float_types(out.dtype(), "Arange", [&](auto type_tag) {
|
dispatch_int_float_types(out.dtype(), "Arange", [&](auto type_tag) {
|
||||||
using CTYPE = MLX_GET_TYPE(type_tag);
|
using CTYPE = MLX_GET_TYPE(type_tag);
|
||||||
using OutType = cuda_type_t<CTYPE>;
|
using OutType = cuda_type_t<CTYPE>;
|
||||||
constexpr int N_WRITES = 16 / sizeof(OutType);
|
CTYPE step =
|
||||||
dispatch_bool(out.data_size() > INT32_MAX, [&](auto large) {
|
static_cast<CTYPE>(start_ + step_) - static_cast<CTYPE>(start_);
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
thrust::transform(
|
||||||
auto [num_blocks, block_dims] = get_launch_args(out, large(), N_WRITES);
|
cu::thrust_policy(encoder.stream()),
|
||||||
encoder.add_kernel_node(
|
thrust::counting_iterator<uint32_t>(0),
|
||||||
cu::arange<OutType, IdxT, N_WRITES>,
|
thrust::counting_iterator<uint32_t>(out.data_size()),
|
||||||
num_blocks,
|
thrust::device_pointer_cast(out.data<OutType>()),
|
||||||
block_dims,
|
cu::Arange<OutType>{
|
||||||
0,
|
static_cast<OutType>(start_), static_cast<OutType>(step)});
|
||||||
gpu_ptr<OutType>(out),
|
|
||||||
out.data_size(),
|
|
||||||
static_cast<CTYPE>(start_),
|
|
||||||
static_cast<CTYPE>(start_ + step_) - static_cast<CTYPE>(start_));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,10 +140,8 @@ void ArgReduce::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
nvtx3::scoped_range r("ArgReduce::eval_gpu");
|
nvtx3::scoped_range r("ArgReduce::eval_gpu");
|
||||||
assert(inputs.size() == 1);
|
assert(inputs.size() == 1);
|
||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
auto& s = stream();
|
auto& s = stream();
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
|
||||||
out.set_data(cu::malloc_async(out.nbytes(), encoder.stream()));
|
|
||||||
|
|
||||||
// Prepare the shapes, strides and axis arguments.
|
// Prepare the shapes, strides and axis arguments.
|
||||||
Shape shape = remove_index(in.shape(), axis_);
|
Shape shape = remove_index(in.shape(), axis_);
|
||||||
@@ -156,6 +154,7 @@ void ArgReduce::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
int32_t ndim = shape.size();
|
int32_t ndim = shape.size();
|
||||||
|
|
||||||
// ArgReduce.
|
// ArgReduce.
|
||||||
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
encoder.set_input_array(in);
|
encoder.set_input_array(in);
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
dispatch_real_types(in.dtype(), "ArgReduce", [&](auto type_tag) {
|
dispatch_real_types(in.dtype(), "ArgReduce", [&](auto type_tag) {
|
||||||
@@ -173,8 +172,8 @@ void ArgReduce::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
num_blocks,
|
num_blocks,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
0,
|
0,
|
||||||
gpu_ptr<T>(in),
|
in.data<T>(),
|
||||||
gpu_ptr<uint32_t>(out),
|
out.data<uint32_t>(),
|
||||||
out.size(),
|
out.size(),
|
||||||
const_param(shape),
|
const_param(shape),
|
||||||
const_param(in_strides),
|
const_param(in_strides),
|
||||||
|
|||||||
@@ -99,89 +99,39 @@ __global__ void binary_vv(const In* a, const In* b, Out* out, IdxT size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <typename Op, typename In, typename Out, typename IdxT, int NDIM>
|
||||||
typename Op,
|
|
||||||
typename In,
|
|
||||||
typename Out,
|
|
||||||
typename IdxT,
|
|
||||||
int NDIM,
|
|
||||||
int N_READS>
|
|
||||||
__global__ void binary_g_nd(
|
__global__ void binary_g_nd(
|
||||||
const In* a,
|
const In* a,
|
||||||
const In* b,
|
const In* b,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> a_strides,
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> a_strides,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> b_strides) {
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> b_strides) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[NDIM - 1];
|
|
||||||
auto a_stride_x = a_strides[NDIM - 1];
|
|
||||||
auto b_stride_x = b_strides[NDIM - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [a_idx, b_idx] = elem_to_loc_nd<NDIM>(
|
auto [a_idx, b_idx] = elem_to_loc_nd<NDIM>(
|
||||||
index_rest * shape_x, shape.data(), a_strides.data(), b_strides.data());
|
index, shape.data(), a_strides.data(), b_strides.data());
|
||||||
auto a_vec =
|
out[index] = Op{}(a[a_idx], b[b_idx]);
|
||||||
load_vector<N_READS>(a + a_idx, index_x, shape_x, a_stride_x, In(0));
|
|
||||||
auto b_vec =
|
|
||||||
load_vector<N_READS>(b + b_idx, index_x, shape_x, b_stride_x, In(0));
|
|
||||||
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = Op{}(a_vec[i], b_vec[i]);
|
|
||||||
}
|
}
|
||||||
store_vector(out + shape_x * index_rest, index_x, out_vec, shape_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
template <typename Op, typename In, typename Out, typename IdxT>
|
||||||
__global__ void binary_g(
|
__global__ void binary_g(
|
||||||
const In* a,
|
const In* a,
|
||||||
const In* b,
|
const In* b,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ Shape shape,
|
const __grid_constant__ Shape shape,
|
||||||
const __grid_constant__ Strides a_strides,
|
const __grid_constant__ Strides a_strides,
|
||||||
const __grid_constant__ Strides b_strides,
|
const __grid_constant__ Strides b_strides,
|
||||||
int ndim) {
|
int ndim) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[ndim - 1];
|
|
||||||
auto a_stride_x = a_strides[ndim - 1];
|
|
||||||
auto b_stride_x = b_strides[ndim - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [a_idx, b_idx] = elem_to_loc(
|
auto [a_idx, b_idx] = elem_to_loc(
|
||||||
index_rest * shape_x,
|
index, shape.data(), a_strides.data(), b_strides.data(), ndim);
|
||||||
shape.data(),
|
out[index] = Op{}(a[a_idx], b[b_idx]);
|
||||||
a_strides.data(),
|
|
||||||
b_strides.data(),
|
|
||||||
ndim);
|
|
||||||
auto a_vec =
|
|
||||||
load_vector<N_READS>(a + a_idx, index_x, shape_x, a_stride_x, In(0));
|
|
||||||
auto b_vec =
|
|
||||||
load_vector<N_READS>(b + b_idx, index_x, shape_x, b_stride_x, In(0));
|
|
||||||
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = Op{}(a_vec[i], b_vec[i]);
|
|
||||||
}
|
}
|
||||||
store_vector(out + shape_x * index_rest, index_x, out_vec, shape_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out>
|
template <typename Op, typename In, typename Out>
|
||||||
@@ -259,61 +209,39 @@ void binary_op_gpu_inplace(
|
|||||||
auto& a_strides = strides[0];
|
auto& a_strides = strides[0];
|
||||||
auto& b_strides = strides[1];
|
auto& b_strides = strides[1];
|
||||||
int ndim = shape.size();
|
int ndim = shape.size();
|
||||||
int work_per_thread = 1;
|
|
||||||
auto dim0 = ndim > 0 ? shape.back() : 1;
|
|
||||||
auto rest = out.size() / dim0;
|
|
||||||
if (dim0 >= 4) {
|
|
||||||
work_per_thread = 4;
|
|
||||||
}
|
|
||||||
dim0 = (dim0 + work_per_thread - 1) / work_per_thread;
|
|
||||||
auto block_dims = get_block_dims(dim0, rest, 1);
|
|
||||||
uint32_t num_blocks_x = cuda::ceil_div(dim0, block_dims.x);
|
|
||||||
uint32_t num_blocks_y = cuda::ceil_div(rest, block_dims.y);
|
|
||||||
if (ndim <= 3) {
|
if (ndim <= 3) {
|
||||||
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
||||||
auto kernel = cu::binary_g_nd<
|
auto [num_blocks, block_dims] =
|
||||||
Op,
|
get_launch_args(out, large());
|
||||||
InType,
|
|
||||||
OutType,
|
|
||||||
IdxT,
|
|
||||||
dims_constant(),
|
|
||||||
1>;
|
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel = cu::binary_g_nd<
|
|
||||||
Op,
|
|
||||||
InType,
|
|
||||||
OutType,
|
|
||||||
IdxT,
|
|
||||||
dims_constant(),
|
|
||||||
4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::binary_g_nd<
|
||||||
{num_blocks_x, num_blocks_y},
|
Op,
|
||||||
|
InType,
|
||||||
|
OutType,
|
||||||
|
IdxT,
|
||||||
|
dims_constant()>,
|
||||||
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out),
|
out.data<OutType>(),
|
||||||
rest,
|
out.size(),
|
||||||
const_param<dims_constant()>(shape),
|
const_param<dims_constant()>(shape),
|
||||||
const_param<dims_constant()>(a_strides),
|
const_param<dims_constant()>(a_strides),
|
||||||
const_param<dims_constant()>(b_strides));
|
const_param<dims_constant()>(b_strides));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
auto kernel = cu::binary_g<Op, InType, OutType, IdxT, 1>;
|
auto [num_blocks, block_dims] = get_launch_args(out, large());
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel = cu::binary_g<Op, InType, OutType, IdxT, 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::binary_g<Op, InType, OutType, IdxT>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out),
|
out.data<OutType>(),
|
||||||
rest,
|
out.size(),
|
||||||
const_param(shape),
|
const_param(shape),
|
||||||
const_param(a_strides),
|
const_param(a_strides),
|
||||||
const_param(b_strides),
|
const_param(b_strides),
|
||||||
@@ -339,9 +267,9 @@ void binary_op_gpu_inplace(
|
|||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out),
|
out.data<OutType>(),
|
||||||
out.data_size());
|
out.data_size());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -365,11 +293,7 @@ void binary_op_gpu(
|
|||||||
auto& a = inputs[0];
|
auto& a = inputs[0];
|
||||||
auto& b = inputs[1];
|
auto& b = inputs[1];
|
||||||
auto bopt = get_binary_op_type(a, b);
|
auto bopt = get_binary_op_type(a, b);
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
set_binary_op_output_data(a, b, out, bopt);
|
||||||
|
|
||||||
set_binary_op_output_data(a, b, out, bopt, [&](auto n) {
|
|
||||||
return cu::malloc_async(n, encoder.stream());
|
|
||||||
});
|
|
||||||
binary_op_gpu_inplace<Op>(inputs, out, op, s);
|
binary_op_gpu_inplace<Op>(inputs, out, op, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,4 +304,54 @@ void binary_op_gpu(
|
|||||||
binary_op_gpu<cu::func>(inputs, out, name(), s); \
|
binary_op_gpu<cu::func>(inputs, out, name(), s); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BINARY_GPU(Add)
|
||||||
|
BINARY_GPU(ArcTan2)
|
||||||
|
BINARY_GPU(Divide)
|
||||||
|
BINARY_GPU(Remainder)
|
||||||
|
BINARY_GPU(Greater)
|
||||||
|
BINARY_GPU(GreaterEqual)
|
||||||
|
BINARY_GPU(Less)
|
||||||
|
BINARY_GPU(LessEqual)
|
||||||
|
BINARY_GPU(LogicalAnd)
|
||||||
|
BINARY_GPU(LogicalOr)
|
||||||
|
BINARY_GPU(LogAddExp)
|
||||||
|
BINARY_GPU(Maximum)
|
||||||
|
BINARY_GPU(Minimum)
|
||||||
|
BINARY_GPU(Multiply)
|
||||||
|
BINARY_GPU(NotEqual)
|
||||||
|
BINARY_GPU(Power)
|
||||||
|
BINARY_GPU(Subtract)
|
||||||
|
|
||||||
|
void Equal::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
nvtx3::scoped_range r("Equal::eval_gpu");
|
||||||
|
auto& s = out.primitive().stream();
|
||||||
|
if (equal_nan_) {
|
||||||
|
binary_op_gpu<cu::NaNEqual>(inputs, out, name(), s);
|
||||||
|
} else {
|
||||||
|
binary_op_gpu<cu::Equal>(inputs, out, name(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
nvtx3::scoped_range r("BitwiseBinary::eval_gpu");
|
||||||
|
auto& s = out.primitive().stream();
|
||||||
|
switch (op_) {
|
||||||
|
case BitwiseBinary::And:
|
||||||
|
binary_op_gpu<cu::BitwiseAnd>(inputs, out, name(), s);
|
||||||
|
break;
|
||||||
|
case BitwiseBinary::Or:
|
||||||
|
binary_op_gpu<cu::BitwiseOr>(inputs, out, name(), s);
|
||||||
|
break;
|
||||||
|
case BitwiseBinary::Xor:
|
||||||
|
binary_op_gpu<cu::BitwiseXor>(inputs, out, name(), s);
|
||||||
|
break;
|
||||||
|
case BitwiseBinary::LeftShift:
|
||||||
|
binary_op_gpu<cu::LeftShift>(inputs, out, name(), s);
|
||||||
|
break;
|
||||||
|
case BitwiseBinary::RightShift:
|
||||||
|
binary_op_gpu<cu::RightShift>(inputs, out, name(), s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
target_sources(
|
|
||||||
mlx
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/add.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/arctan2.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bitwise_binary.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/divide.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/equal.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/greater.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/greater_equal.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/less.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/less_equal.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/logical_and.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/logical_or.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/log_add_exp.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/minimum.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/maximum.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/multiply.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/power.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/remainder.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/not_equal.cu
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/subtract.cu)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Add)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(ArcTan2)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|
||||||
nvtx3::scoped_range r("BitwiseBinary::eval_gpu");
|
|
||||||
auto& s = out.primitive().stream();
|
|
||||||
switch (op_) {
|
|
||||||
case BitwiseBinary::And:
|
|
||||||
binary_op_gpu<cu::BitwiseAnd>(inputs, out, name(), s);
|
|
||||||
break;
|
|
||||||
case BitwiseBinary::Or:
|
|
||||||
binary_op_gpu<cu::BitwiseOr>(inputs, out, name(), s);
|
|
||||||
break;
|
|
||||||
case BitwiseBinary::Xor:
|
|
||||||
binary_op_gpu<cu::BitwiseXor>(inputs, out, name(), s);
|
|
||||||
break;
|
|
||||||
case BitwiseBinary::LeftShift:
|
|
||||||
binary_op_gpu<cu::LeftShift>(inputs, out, name(), s);
|
|
||||||
break;
|
|
||||||
case BitwiseBinary::RightShift:
|
|
||||||
binary_op_gpu<cu::RightShift>(inputs, out, name(), s);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Divide)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
void Equal::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|
||||||
nvtx3::scoped_range r("Equal::eval_gpu");
|
|
||||||
auto& s = out.primitive().stream();
|
|
||||||
if (equal_nan_) {
|
|
||||||
binary_op_gpu<cu::NaNEqual>(inputs, out, name(), s);
|
|
||||||
} else {
|
|
||||||
binary_op_gpu<cu::Equal>(inputs, out, name(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Greater)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(GreaterEqual)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Less)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(LessEqual)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(LogAddExp)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(LogicalAnd)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(LogicalOr)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Maximum)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Minimum)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Multiply)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(NotEqual)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Power)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Remainder)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/binary/binary.cuh"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
BINARY_GPU(Subtract)
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -127,99 +127,45 @@ binary_two_vv(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <typename Op, typename In, typename Out, typename IdxT, int NDIM>
|
||||||
typename Op,
|
|
||||||
typename In,
|
|
||||||
typename Out,
|
|
||||||
typename IdxT,
|
|
||||||
int NDIM,
|
|
||||||
int N_READS>
|
|
||||||
__global__ void binary_two_g_nd(
|
__global__ void binary_two_g_nd(
|
||||||
const In* a,
|
const In* a,
|
||||||
const In* b,
|
const In* b,
|
||||||
Out* out_a,
|
Out* out_a,
|
||||||
Out* out_b,
|
Out* out_b,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> a_strides,
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> a_strides,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> b_strides) {
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> b_strides) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[NDIM - 1];
|
|
||||||
auto a_stride_x = a_strides[NDIM - 1];
|
|
||||||
auto b_stride_x = b_strides[NDIM - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [a_idx, b_idx] = elem_to_loc_nd<NDIM>(
|
auto [a_idx, b_idx] = elem_to_loc_nd<NDIM>(
|
||||||
index_rest * shape_x, shape.data(), a_strides.data(), b_strides.data());
|
index, shape.data(), a_strides.data(), b_strides.data());
|
||||||
auto a_vec =
|
auto out = Op{}(a[a_idx], b[b_idx]);
|
||||||
load_vector<N_READS>(a + a_idx, index_x, shape_x, a_stride_x, In(0));
|
out_a[index] = out[0];
|
||||||
auto b_vec =
|
out_b[index] = out[1];
|
||||||
load_vector<N_READS>(b + b_idx, index_x, shape_x, b_stride_x, In(0));
|
|
||||||
|
|
||||||
AlignedVector<Out, N_READS> out_vec_a;
|
|
||||||
AlignedVector<Out, N_READS> out_vec_b;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
auto out = Op{}(a_vec[i], b_vec[i]);
|
|
||||||
out_vec_a[i] = out[0];
|
|
||||||
out_vec_b[i] = out[1];
|
|
||||||
}
|
}
|
||||||
store_vector(out_a + shape_x * index_rest, index_x, out_vec_a, shape_x);
|
|
||||||
store_vector(out_b + shape_x * index_rest, index_x, out_vec_b, shape_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
template <typename Op, typename In, typename Out, typename IdxT>
|
||||||
__global__ void binary_two_g(
|
__global__ void binary_two_g(
|
||||||
const In* a,
|
const In* a,
|
||||||
const In* b,
|
const In* b,
|
||||||
Out* out_a,
|
Out* out_a,
|
||||||
Out* out_b,
|
Out* out_b,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ Shape shape,
|
const __grid_constant__ Shape shape,
|
||||||
const __grid_constant__ Strides a_strides,
|
const __grid_constant__ Strides a_strides,
|
||||||
const __grid_constant__ Strides b_strides,
|
const __grid_constant__ Strides b_strides,
|
||||||
int ndim) {
|
int ndim) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[ndim - 1];
|
|
||||||
auto a_stride_x = a_strides[ndim - 1];
|
|
||||||
auto b_stride_x = b_strides[ndim - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [a_idx, b_idx] = elem_to_loc(
|
auto [a_idx, b_idx] = elem_to_loc(
|
||||||
index_rest * shape_x,
|
index, shape.data(), a_strides.data(), b_strides.data(), ndim);
|
||||||
shape.data(),
|
auto out = Op{}(a[a_idx], b[b_idx]);
|
||||||
a_strides.data(),
|
out_a[index] = out[0];
|
||||||
b_strides.data(),
|
out_b[index] = out[1];
|
||||||
ndim);
|
|
||||||
auto a_vec =
|
|
||||||
load_vector<N_READS>(a + a_idx, index_x, shape_x, a_stride_x, In(0));
|
|
||||||
auto b_vec =
|
|
||||||
load_vector<N_READS>(b + b_idx, index_x, shape_x, b_stride_x, In(0));
|
|
||||||
|
|
||||||
AlignedVector<Out, N_READS> out_vec_a;
|
|
||||||
AlignedVector<Out, N_READS> out_vec_b;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
auto out = Op{}(a_vec[i], b_vec[i]);
|
|
||||||
out_vec_a[i] = out[0];
|
|
||||||
out_vec_b[i] = out[1];
|
|
||||||
}
|
}
|
||||||
store_vector(out_a + shape_x * index_rest, index_x, out_vec_a, shape_x);
|
|
||||||
store_vector(out_b + shape_x * index_rest, index_x, out_vec_b, shape_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out>
|
template <typename Op, typename In, typename Out>
|
||||||
@@ -245,18 +191,14 @@ void binary_two_op_gpu_inplace(
|
|||||||
auto& out_a = outputs[0];
|
auto& out_a = outputs[0];
|
||||||
auto& out_b = outputs[1];
|
auto& out_b = outputs[1];
|
||||||
auto bopt = get_binary_op_type(a, b);
|
auto bopt = get_binary_op_type(a, b);
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
set_binary_op_output_data(a, b, out_a, bopt);
|
||||||
set_binary_op_output_data(a, b, out_a, bopt, [&](auto n) {
|
set_binary_op_output_data(a, b, out_b, bopt);
|
||||||
return cu::malloc_async(n, encoder.stream());
|
|
||||||
});
|
|
||||||
set_binary_op_output_data(a, b, out_b, bopt, [&](auto n) {
|
|
||||||
return cu::malloc_async(n, encoder.stream());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (out_a.size() == 0) {
|
if (out_a.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
encoder.set_input_array(a);
|
encoder.set_input_array(a);
|
||||||
encoder.set_input_array(b);
|
encoder.set_input_array(b);
|
||||||
encoder.set_output_array(out_a);
|
encoder.set_output_array(out_a);
|
||||||
@@ -283,64 +225,42 @@ void binary_two_op_gpu_inplace(
|
|||||||
auto& a_strides = strides[0];
|
auto& a_strides = strides[0];
|
||||||
auto& b_strides = strides[1];
|
auto& b_strides = strides[1];
|
||||||
int ndim = shape.size();
|
int ndim = shape.size();
|
||||||
int work_per_thread = 1;
|
|
||||||
auto dim0 = ndim > 0 ? shape.back() : 1;
|
|
||||||
auto rest = out_a.size() / dim0;
|
|
||||||
if (dim0 >= 4) {
|
|
||||||
work_per_thread = 4;
|
|
||||||
}
|
|
||||||
dim0 = (dim0 + work_per_thread - 1) / work_per_thread;
|
|
||||||
auto block_dims = get_block_dims(dim0, rest, 1);
|
|
||||||
uint32_t num_blocks_x = cuda::ceil_div(dim0, block_dims.x);
|
|
||||||
uint32_t num_blocks_y = cuda::ceil_div(rest, block_dims.y);
|
|
||||||
|
|
||||||
if (ndim <= 3) {
|
if (ndim <= 3) {
|
||||||
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
||||||
auto kernel = cu::binary_two_g_nd<
|
auto [num_blocks, block_dims] =
|
||||||
Op,
|
get_launch_args(out_a, large());
|
||||||
InType,
|
|
||||||
OutType,
|
|
||||||
IdxT,
|
|
||||||
dims_constant(),
|
|
||||||
1>;
|
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel = cu::binary_two_g_nd<
|
|
||||||
Op,
|
|
||||||
InType,
|
|
||||||
OutType,
|
|
||||||
IdxT,
|
|
||||||
dims_constant(),
|
|
||||||
4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::binary_two_g_nd<
|
||||||
{num_blocks_x, num_blocks_y},
|
Op,
|
||||||
|
InType,
|
||||||
|
OutType,
|
||||||
|
IdxT,
|
||||||
|
dims_constant()>,
|
||||||
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out_a),
|
out_a.data<OutType>(),
|
||||||
gpu_ptr<OutType>(out_b),
|
out_b.data<OutType>(),
|
||||||
rest,
|
out_a.size(),
|
||||||
const_param<dims_constant()>(shape),
|
const_param<dims_constant()>(shape),
|
||||||
const_param<dims_constant()>(a_strides),
|
const_param<dims_constant()>(a_strides),
|
||||||
const_param<dims_constant()>(b_strides));
|
const_param<dims_constant()>(b_strides));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
auto kernel = cu::binary_two_g<Op, InType, OutType, IdxT, 1>;
|
auto [num_blocks, block_dims] =
|
||||||
if (work_per_thread == 4) {
|
get_launch_args(out_a, large());
|
||||||
kernel = cu::binary_two_g<Op, InType, OutType, IdxT, 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::binary_two_g<Op, InType, OutType, IdxT>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out_a),
|
out_a.data<OutType>(),
|
||||||
gpu_ptr<OutType>(out_b),
|
out_b.data<OutType>(),
|
||||||
rest,
|
out_a.size(),
|
||||||
const_param(shape),
|
const_param(shape),
|
||||||
const_param(a_strides),
|
const_param(a_strides),
|
||||||
const_param(b_strides),
|
const_param(b_strides),
|
||||||
@@ -370,10 +290,10 @@ void binary_two_op_gpu_inplace(
|
|||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(a),
|
a.data<InType>(),
|
||||||
gpu_ptr<InType>(b),
|
b.data<InType>(),
|
||||||
gpu_ptr<OutType>(out_a),
|
out_a.data<OutType>(),
|
||||||
gpu_ptr<OutType>(out_b),
|
out_b.data<OutType>(),
|
||||||
out_a.data_size());
|
out_a.data_size());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,8 +267,7 @@ void Compiled::eval_gpu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_tuple(
|
return std::make_pair(std::move(builder.os), std::move(kernel_names));
|
||||||
false, std::move(builder.os), std::move(kernel_names));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collapse contiguous dims to route to a faster kernel if possible. Also
|
// Collapse contiguous dims to route to a faster kernel if possible. Also
|
||||||
@@ -293,13 +292,8 @@ void Compiled::eval_gpu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
|
||||||
|
|
||||||
// Put outputs.
|
// Put outputs.
|
||||||
compiled_allocate_outputs(
|
compiled_allocate_outputs(inputs, outputs, is_constant_, contiguous);
|
||||||
inputs, outputs, is_constant_, contiguous, [&](auto n) {
|
|
||||||
return cu::malloc_async(n, encoder.stream());
|
|
||||||
});
|
|
||||||
for (auto& x : outputs) {
|
for (auto& x : outputs) {
|
||||||
args.append(x);
|
args.append(x);
|
||||||
}
|
}
|
||||||
@@ -329,6 +323,7 @@ void Compiled::eval_gpu(
|
|||||||
kernel_name += fmt::format(
|
kernel_name += fmt::format(
|
||||||
"_strided<{}, {}, {}>", shape.size(), index_type, work_per_thread);
|
"_strided<{}, {}, {}>", shape.size(), index_type, work_per_thread);
|
||||||
}
|
}
|
||||||
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
for (const auto& in : inputs) {
|
for (const auto& in : inputs) {
|
||||||
encoder.set_input_array(in);
|
encoder.set_input_array(in);
|
||||||
}
|
}
|
||||||
@@ -336,9 +331,9 @@ void Compiled::eval_gpu(
|
|||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [kernel, max_block_dims] = mod.get_kernel_and_dims(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] =
|
auto [num_blocks, block_dims] =
|
||||||
get_launch_args(outputs[0], large, work_per_thread, max_block_dims);
|
get_launch_args(outputs[0], large, work_per_thread);
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
#include "mlx/backend/cuda/conv/conv.h"
|
|
||||||
#include "mlx/backend/cuda/cudnn_utils.h"
|
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/device/config.h"
|
||||||
#include "mlx/backend/cuda/lru_cache.h"
|
#include "mlx/backend/cuda/lru_cache.h"
|
||||||
#include "mlx/backend/gpu/copy.h"
|
#include "mlx/backend/gpu/copy.h"
|
||||||
|
#include "mlx/dtype_utils.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
|
|
||||||
|
// cudnn_frontend.h redefines this macro.
|
||||||
|
#undef CHECK_CUDA_ERROR
|
||||||
|
|
||||||
|
#include <cudnn_frontend.h>
|
||||||
|
#include <cudnn_frontend_find_plan.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
#include <nvtx3/nvtx3.hpp>
|
#include <nvtx3/nvtx3.hpp>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -15,6 +21,9 @@ namespace mlx::core {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// Not all engines support it so can not use this API now.
|
||||||
|
#define MLX_USE_CUDNN_NATIVE_CUDA_GRAPH_API 0
|
||||||
|
|
||||||
// Alias for better readability.
|
// Alias for better readability.
|
||||||
#define CONV_FORWARD CUDNN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR
|
#define CONV_FORWARD CUDNN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR
|
||||||
#define CONV_BACKWARD_INPUT \
|
#define CONV_BACKWARD_INPUT \
|
||||||
@@ -22,9 +31,6 @@ namespace {
|
|||||||
#define CONV_BACKWARD_WEIGHT \
|
#define CONV_BACKWARD_WEIGHT \
|
||||||
CUDNN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR
|
CUDNN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR
|
||||||
|
|
||||||
// Custom placeholder representing fallback kernel.
|
|
||||||
#define CONV_FALLBACK static_cast<cudnnBackendDescriptorType_t>(-1)
|
|
||||||
|
|
||||||
struct ConvCacheKey {
|
struct ConvCacheKey {
|
||||||
int device_id;
|
int device_id;
|
||||||
cudnnDataType_t cudnn_dtype;
|
cudnnDataType_t cudnn_dtype;
|
||||||
@@ -44,13 +50,203 @@ struct ConvCacheKey {
|
|||||||
auto& conv_cache() {
|
auto& conv_cache() {
|
||||||
static LRUBytesKeyCache<
|
static LRUBytesKeyCache<
|
||||||
ConvCacheKey,
|
ConvCacheKey,
|
||||||
std::pair<
|
std::pair<cudnnBackendDescriptorType_t, cudnn_frontend::ExecutionPlan>>
|
||||||
cudnnBackendDescriptorType_t,
|
cache(/* capacity */ 128);
|
||||||
std::optional<cudnn_frontend::ExecutionPlan>>>
|
|
||||||
cache("MLX_CUDA_CONV_CACHE_SIZE", /* default_capacity */ 128);
|
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Vec>
|
||||||
|
inline SmallVector<T> convert_vector(const Vec& vec) {
|
||||||
|
return SmallVector<T>(vec.begin(), vec.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, template <typename U> class Vec>
|
||||||
|
inline std::array<T, MAX_NDIM> fixed_vector(const Vec<T>& vec) {
|
||||||
|
if (vec.size() > MAX_NDIM) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
fmt::format("ndim can not be larger than {}.", MAX_NDIM));
|
||||||
|
}
|
||||||
|
std::array<T, MAX_NDIM> result = {};
|
||||||
|
std::copy_n(vec.begin(), vec.size(), result.begin());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nhwc_to_nchw(const array& x) {
|
||||||
|
auto shape = convert_vector<int64_t>(x.shape());
|
||||||
|
shape.insert(shape.begin() + 1, shape.back());
|
||||||
|
shape.erase(shape.end() - 1);
|
||||||
|
auto strides = convert_vector<int64_t>(x.strides());
|
||||||
|
strides.insert(strides.begin() + 1, strides.back());
|
||||||
|
strides.erase(strides.end() - 1);
|
||||||
|
return std::make_tuple(std::move(shape), std::move(strides));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline cudnnDataType_t dtype_to_cudnn_type(Dtype dtype) {
|
||||||
|
switch (dtype) {
|
||||||
|
case int8:
|
||||||
|
return CUDNN_DATA_INT8;
|
||||||
|
case int32:
|
||||||
|
return CUDNN_DATA_INT32;
|
||||||
|
case uint8:
|
||||||
|
return CUDNN_DATA_UINT8;
|
||||||
|
case float16:
|
||||||
|
return CUDNN_DATA_HALF;
|
||||||
|
case bfloat16:
|
||||||
|
return CUDNN_DATA_BFLOAT16;
|
||||||
|
case float32:
|
||||||
|
return CUDNN_DATA_FLOAT;
|
||||||
|
case float64:
|
||||||
|
return CUDNN_DATA_DOUBLE;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error(fmt::format(
|
||||||
|
"Unsupported dtype in Convolution: {}.", dtype_to_string(dtype)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8_t get_alignment(const array& x) {
|
||||||
|
uint8_t alignment = 1;
|
||||||
|
uintptr_t address = reinterpret_cast<uintptr_t>(x.data<void>());
|
||||||
|
for (; alignment < 32; alignment *= 2) {
|
||||||
|
if (address % (alignment * 2)) {
|
||||||
|
return alignment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline cudnn_frontend::Tensor build_tensor(int64_t id, const array& x) {
|
||||||
|
auto [shape, strides] = nhwc_to_nchw(x);
|
||||||
|
return cudnn_frontend::TensorBuilder()
|
||||||
|
.setDim(shape.size(), shape.data())
|
||||||
|
.setStrides(strides.size(), strides.data())
|
||||||
|
.setId(id)
|
||||||
|
.setAlignment(get_alignment(x))
|
||||||
|
.setDataType(dtype_to_cudnn_type(x.dtype()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
cudnn_frontend::EngineConfigList get_engine_configs(
|
||||||
|
cudnnBackendDescriptorType_t backend_type,
|
||||||
|
Dtype dtype,
|
||||||
|
cudnn_frontend::OperationGraph& op_graph,
|
||||||
|
bool use_fallback = false) {
|
||||||
|
cudnn_frontend::GeneratorSource source;
|
||||||
|
if (use_fallback) {
|
||||||
|
source = [&backend_type](cudnn_frontend::OperationGraph& op_graph) {
|
||||||
|
auto fallback = cudnn_frontend::EngineFallbackListBuilder()
|
||||||
|
.setOperationGraph(op_graph)
|
||||||
|
.setOperation(backend_type)
|
||||||
|
.build();
|
||||||
|
return fallback.getFallbackList();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
source = [](cudnn_frontend::OperationGraph& op_graph) {
|
||||||
|
auto heuristics = cudnn_frontend::EngineHeuristicsBuilder()
|
||||||
|
.setOperationGraph(op_graph)
|
||||||
|
.setHeurMode(CUDNN_HEUR_MODE_A)
|
||||||
|
.build();
|
||||||
|
return heuristics.getEngineConfig(heuristics.getEngineConfigCount());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cudnn_frontend::EngineConfigGenerator generator(1, &source);
|
||||||
|
auto configs = generator.generate_engine_config(op_graph);
|
||||||
|
|
||||||
|
cudnn_frontend::EngineConfigList filtered_configs;
|
||||||
|
cudnn_frontend::filter(configs, filtered_configs, [dtype](auto c) {
|
||||||
|
if (cudnn_frontend::hasNumericalNote<
|
||||||
|
CUDNN_NUMERICAL_NOTE_DOWN_CONVERT_INPUTS>(c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cudnn_frontend::hasNumericalNote<CUDNN_NUMERICAL_NOTE_TENSOR_CORE>(c) &&
|
||||||
|
dtype == float32 && !env::enable_tf32()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return filtered_configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool execute_plan(
|
||||||
|
cu::CommandEncoder& encoder,
|
||||||
|
cudnn_frontend::ExecutionPlan& plan,
|
||||||
|
array& x,
|
||||||
|
array& w,
|
||||||
|
array& y) {
|
||||||
|
int workspace_size = plan.getWorkspaceSize();
|
||||||
|
array workspace(allocator::malloc(workspace_size), {workspace_size}, uint8);
|
||||||
|
|
||||||
|
int64_t uids[3] = {'x', 'w', 'y'};
|
||||||
|
void* data_ptrs[3] = {
|
||||||
|
x.data<void>(),
|
||||||
|
w.data<void>(),
|
||||||
|
y.data<void>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
auto variantPack = cudnn_frontend::VariantPackBuilder()
|
||||||
|
.setWorkspacePointer(workspace.data<void>())
|
||||||
|
.setDataPointers(3, data_ptrs)
|
||||||
|
.setUids(3, uids)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
auto handle = encoder.device().cudnn_handle();
|
||||||
|
cudnnSetStream(handle, encoder.stream());
|
||||||
|
|
||||||
|
#if CUDNN_VERSION >= 90500 && MLX_USE_CUDNN_NATIVE_CUDA_GRAPH_API
|
||||||
|
cudaGraph_t graph;
|
||||||
|
cudaGraphCreate(&graph, 0);
|
||||||
|
std::unique_ptr<cudaGraph_t, void (*)(cudaGraph_t*)> graph_freer(
|
||||||
|
&graph, [](cudaGraph_t* p) { cudaGraphDestroy(*p); });
|
||||||
|
if (cudnnBackendPopulateCudaGraph(
|
||||||
|
handle, plan.get_raw_desc(), variantPack.get_raw_desc(), graph) !=
|
||||||
|
CUDNN_STATUS_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
encoder.add_graph_node(graph);
|
||||||
|
#else
|
||||||
|
auto capture = encoder.capture_context();
|
||||||
|
if (cudnnBackendExecute(
|
||||||
|
handle, plan.get_raw_desc(), variantPack.get_raw_desc()) !=
|
||||||
|
CUDNN_STATUS_SUCCESS) {
|
||||||
|
// Discard the captured graph when failed.
|
||||||
|
capture.discard = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
encoder.add_temporary(workspace);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_engines(
|
||||||
|
cu::CommandEncoder& encoder,
|
||||||
|
const ConvCacheKey& cache_key,
|
||||||
|
cudnnBackendDescriptorType_t backend_type,
|
||||||
|
cudnn_frontend::EngineConfigList& configs,
|
||||||
|
const std::string& op_graph_tag,
|
||||||
|
array& x,
|
||||||
|
array& w,
|
||||||
|
array& y) {
|
||||||
|
for (auto& config : configs) {
|
||||||
|
try {
|
||||||
|
auto plan = cudnn_frontend::ExecutionPlanBuilder()
|
||||||
|
.setHandle(encoder.device().cudnn_handle())
|
||||||
|
.setEngineConfig(config, op_graph_tag)
|
||||||
|
.build();
|
||||||
|
if (execute_plan(encoder, plan, x, w, y)) {
|
||||||
|
conv_cache().emplace(
|
||||||
|
cache_key, std::make_pair(backend_type, std::move(plan)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (cudnn_frontend::cudnnException& error) {
|
||||||
|
if (error.getCudnnStatus() != CUDNN_STATUS_NOT_SUPPORTED) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto get_conv_op_settings(
|
auto get_conv_op_settings(
|
||||||
cudnnBackendDescriptorType_t backend_type,
|
cudnnBackendDescriptorType_t backend_type,
|
||||||
array& x,
|
array& x,
|
||||||
@@ -95,7 +291,7 @@ auto get_conv_op_settings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<cudnn_frontend::OperationGraph> build_conv_op_graph(
|
std::optional<cudnn_frontend::OperationGraph> build_op_graph(
|
||||||
cu::CommandEncoder& encoder,
|
cu::CommandEncoder& encoder,
|
||||||
cudnnBackendDescriptorType_t backend_type,
|
cudnnBackendDescriptorType_t backend_type,
|
||||||
Dtype dtype,
|
Dtype dtype,
|
||||||
@@ -121,9 +317,9 @@ std::optional<cudnn_frontend::OperationGraph> build_conv_op_graph(
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
auto op = cudnn_frontend::OperationBuilder(backend_type)
|
auto op = cudnn_frontend::OperationBuilder(backend_type)
|
||||||
.setxDesc(build_cudnn_tensor_nchw('x', x))
|
.setxDesc(build_tensor('x', x))
|
||||||
.setwDesc(build_cudnn_tensor_nchw('w', w))
|
.setwDesc(build_tensor('w', w))
|
||||||
.setyDesc(build_cudnn_tensor_nchw('y', y))
|
.setyDesc(build_tensor('y', y))
|
||||||
.setcDesc(conv_desc)
|
.setcDesc(conv_desc)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -140,42 +336,6 @@ std::optional<cudnn_frontend::OperationGraph> build_conv_op_graph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transpose from (C_out, H, W, C_in / groups) to (C_in, H, W, C_out / groups).
|
|
||||||
array group_transpose(
|
|
||||||
const array& x,
|
|
||||||
int groups,
|
|
||||||
int group_dim,
|
|
||||||
int axis1,
|
|
||||||
int axis2,
|
|
||||||
Stream s) {
|
|
||||||
if (groups == 1) {
|
|
||||||
return swapaxes_in_eval(x, axis1, axis2);
|
|
||||||
}
|
|
||||||
int ndim = x.ndim();
|
|
||||||
if (group_dim < 0) {
|
|
||||||
group_dim += ndim;
|
|
||||||
}
|
|
||||||
if (axis1 < 0) {
|
|
||||||
axis1 += ndim;
|
|
||||||
}
|
|
||||||
if (axis2 < 0) {
|
|
||||||
axis2 += ndim;
|
|
||||||
}
|
|
||||||
if (group_dim <= axis1) {
|
|
||||||
axis1 += 1;
|
|
||||||
}
|
|
||||||
if (group_dim <= axis2) {
|
|
||||||
axis2 += 1;
|
|
||||||
}
|
|
||||||
auto shape = x.shape();
|
|
||||||
shape.insert(shape.begin() + group_dim, groups);
|
|
||||||
shape[group_dim + 1] = shape[group_dim + 1] / groups;
|
|
||||||
array x_trans = reshape_in_eval(x, std::move(shape), s);
|
|
||||||
x_trans = swapaxes_in_eval(x_trans, axis1, axis2);
|
|
||||||
x_trans = flatten_in_eval(x_trans, group_dim, group_dim + 1, s);
|
|
||||||
return x_trans;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do necessary transposes and copies to prepare the inputs and outputs for
|
// Do necessary transposes and copies to prepare the inputs and outputs for
|
||||||
// building the cuDNN conv op. It is safe to be called multiple times in one
|
// building the cuDNN conv op. It is safe to be called multiple times in one
|
||||||
// eval_gpu, with cost of possible redundant copies.
|
// eval_gpu, with cost of possible redundant copies.
|
||||||
@@ -185,14 +345,13 @@ std::tuple<array, array, array> prepare_args(
|
|||||||
array in,
|
array in,
|
||||||
array wt,
|
array wt,
|
||||||
array out,
|
array out,
|
||||||
int groups,
|
|
||||||
Stream s) {
|
Stream s) {
|
||||||
// Transpose the args depending on the backend type.
|
// Transpose the args depending on the backend type.
|
||||||
// TODO: Handle groups.
|
// TODO: Handle groups.
|
||||||
if (backend_type == CONV_BACKWARD_INPUT) {
|
if (backend_type == CONV_BACKWARD_INPUT) {
|
||||||
wt = group_transpose(wt, groups, 0, 0, -1, s);
|
wt = swapaxes_in_eval(wt, 0, -1);
|
||||||
} else if (backend_type == CONV_BACKWARD_WEIGHT) {
|
} else if (backend_type == CONV_BACKWARD_WEIGHT) {
|
||||||
in = group_transpose(in, groups, -1, 0, -1, s);
|
in = swapaxes_in_eval(in, 0, -1);
|
||||||
wt = swapaxes_in_eval(wt, 0, -1);
|
wt = swapaxes_in_eval(wt, 0, -1);
|
||||||
// Create a contiguous array that shares the data with |out|, but with dim
|
// Create a contiguous array that shares the data with |out|, but with dim
|
||||||
// C_in and C_out swapped.
|
// C_in and C_out swapped.
|
||||||
@@ -270,26 +429,27 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out_) {
|
|||||||
if (out_.size() == 0) {
|
if (out_.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& s = stream();
|
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
|
||||||
|
|
||||||
assert(inputs.size() == 2);
|
assert(inputs.size() == 2);
|
||||||
array in = inputs[0];
|
array in = inputs[0];
|
||||||
array wt = inputs[1];
|
array wt = inputs[1];
|
||||||
array out = out_;
|
array out = out_;
|
||||||
out.set_data(cu::malloc_async(out.nbytes(), encoder.stream()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
Dtype dtype = out.dtype();
|
Dtype dtype = out.dtype();
|
||||||
|
|
||||||
|
auto& s = stream();
|
||||||
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
|
|
||||||
// Search cache.
|
// Search cache.
|
||||||
ConvCacheKey cache_key{
|
ConvCacheKey cache_key{
|
||||||
encoder.device().cuda_device(),
|
encoder.device().cuda_device(),
|
||||||
dtype_to_cudnn_type(dtype),
|
dtype_to_cudnn_type(dtype),
|
||||||
vector_key(in.shape()),
|
fixed_vector(in.shape()),
|
||||||
vector_key(wt.shape()),
|
fixed_vector(wt.shape()),
|
||||||
vector_key(kernel_strides_),
|
fixed_vector(kernel_strides_),
|
||||||
vector_key(padding_lo_),
|
fixed_vector(padding_lo_),
|
||||||
vector_key(padding_hi_),
|
fixed_vector(padding_hi_),
|
||||||
vector_key(kernel_dilation_),
|
fixed_vector(kernel_dilation_),
|
||||||
groups_,
|
groups_,
|
||||||
flip_,
|
flip_,
|
||||||
get_alignment(in),
|
get_alignment(in),
|
||||||
@@ -297,30 +457,12 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out_) {
|
|||||||
get_alignment(out)};
|
get_alignment(out)};
|
||||||
if (auto it = conv_cache().find(cache_key); it != conv_cache().end()) {
|
if (auto it = conv_cache().find(cache_key); it != conv_cache().end()) {
|
||||||
auto& [backend_type, plan] = it->second;
|
auto& [backend_type, plan] = it->second;
|
||||||
if (plan) {
|
std::tie(in, wt, out) = prepare_args(encoder, backend_type, in, wt, out, s);
|
||||||
// Run cached plan.
|
|
||||||
std::tie(in, wt, out) =
|
|
||||||
prepare_args(encoder, backend_type, in, wt, out, groups_, s);
|
|
||||||
register_args(encoder, backend_type, in, wt, out, out_);
|
register_args(encoder, backend_type, in, wt, out, out_);
|
||||||
auto [x, w, y] = dispatch_args(backend_type, in, wt, out);
|
auto [x, w, y] = dispatch_args(backend_type, in, wt, out);
|
||||||
if (!encode_cudnn_plan(encoder, *plan, {'x', 'w', 'y'}, x, w, y)) {
|
if (!execute_plan(encoder, plan, x, w, y)) {
|
||||||
throw std::runtime_error("[conv] Cached plan failed to execute.");
|
throw std::runtime_error("[conv] Cached plan failed to execute.");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Run fallback kernel.
|
|
||||||
gemm_conv(
|
|
||||||
encoder,
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
kernel_strides_,
|
|
||||||
padding_lo_,
|
|
||||||
kernel_dilation_,
|
|
||||||
input_dilation_,
|
|
||||||
groups_,
|
|
||||||
flip_,
|
|
||||||
s);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +490,7 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out_) {
|
|||||||
std::optional<cudnn_frontend::OperationGraph> op_graph;
|
std::optional<cudnn_frontend::OperationGraph> op_graph;
|
||||||
for (auto try_backend : try_backends) {
|
for (auto try_backend : try_backends) {
|
||||||
auto [in_copy, wt_copy, out_copy] =
|
auto [in_copy, wt_copy, out_copy] =
|
||||||
prepare_args(encoder, try_backend, in, wt, out, groups_, s);
|
prepare_args(encoder, try_backend, in, wt, out, s);
|
||||||
auto [x, w, y] = dispatch_args(try_backend, in_copy, wt_copy, out_copy);
|
auto [x, w, y] = dispatch_args(try_backend, in_copy, wt_copy, out_copy);
|
||||||
auto [stride, padding_lo, padding_hi, dilation] = get_conv_op_settings(
|
auto [stride, padding_lo, padding_hi, dilation] = get_conv_op_settings(
|
||||||
try_backend,
|
try_backend,
|
||||||
@@ -360,7 +502,7 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out_) {
|
|||||||
padding_hi_,
|
padding_hi_,
|
||||||
kernel_dilation_,
|
kernel_dilation_,
|
||||||
input_dilation_);
|
input_dilation_);
|
||||||
op_graph = build_conv_op_graph(
|
op_graph = build_op_graph(
|
||||||
encoder,
|
encoder,
|
||||||
try_backend,
|
try_backend,
|
||||||
dtype,
|
dtype,
|
||||||
@@ -379,38 +521,26 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out_) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!op_graph) {
|
||||||
|
throw std::runtime_error("[conv] Can not build op graph.");
|
||||||
|
}
|
||||||
|
|
||||||
if (op_graph) {
|
// Get ready to execute the graph.
|
||||||
// Find a plan for the graph and execute it.
|
|
||||||
auto plan = find_cudnn_plan_from_op_graph(
|
|
||||||
encoder.device().cudnn_handle(), backend_type, dtype, *op_graph);
|
|
||||||
if (plan) {
|
|
||||||
// Setup inputs and outputs.
|
|
||||||
register_args(encoder, backend_type, in, wt, out, out_);
|
register_args(encoder, backend_type, in, wt, out, out_);
|
||||||
|
|
||||||
|
// Try to run plans based on heuristics.
|
||||||
|
auto configs = get_engine_configs(backend_type, dtype, *op_graph);
|
||||||
|
auto tag = op_graph->getTag();
|
||||||
auto [x, w, y] = dispatch_args(backend_type, in, wt, out);
|
auto [x, w, y] = dispatch_args(backend_type, in, wt, out);
|
||||||
if (encode_cudnn_plan(encoder, *plan, {'x', 'w', 'y'}, x, w, y)) {
|
if (try_engines(encoder, cache_key, backend_type, configs, tag, x, w, y)) {
|
||||||
conv_cache().emplace(
|
|
||||||
cache_key, std::make_pair(backend_type, std::move(*plan)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Then try fallback plans.
|
||||||
|
configs = get_engine_configs(backend_type, dtype, *op_graph);
|
||||||
|
if (try_engines(encoder, cache_key, backend_type, configs, tag, x, w, y)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
throw std::runtime_error("[conv] Unable to find a working engine.");
|
||||||
|
|
||||||
// Use fallback kernel for settings not supported by cuDNN.
|
|
||||||
gemm_conv(
|
|
||||||
encoder,
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
kernel_strides_,
|
|
||||||
padding_lo_,
|
|
||||||
kernel_dilation_,
|
|
||||||
input_dilation_,
|
|
||||||
groups_,
|
|
||||||
flip_,
|
|
||||||
s);
|
|
||||||
conv_cache().emplace(cache_key, std::make_pair(CONV_FALLBACK, std::nullopt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/device.h"
|
|
||||||
#include "mlx/backend/gpu/copy.h"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
|
|
||||||
template <int NDIM>
|
|
||||||
struct ConvParams {
|
|
||||||
int N; // Batch size
|
|
||||||
int C; // In channels
|
|
||||||
int O; // Out channels
|
|
||||||
int strides[NDIM];
|
|
||||||
int padding[NDIM];
|
|
||||||
int kernel_dilation[NDIM];
|
|
||||||
int input_dilation[NDIM];
|
|
||||||
int groups;
|
|
||||||
bool flip;
|
|
||||||
int in_spatial_dims[NDIM];
|
|
||||||
int wt_spatial_dims[NDIM];
|
|
||||||
int out_spatial_dims[NDIM];
|
|
||||||
int64_t in_strides[NDIM + 2];
|
|
||||||
|
|
||||||
ConvParams(
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
const array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
int groups,
|
|
||||||
bool flip)
|
|
||||||
: N(in.shape(0)),
|
|
||||||
C(in.shape(-1)),
|
|
||||||
O(wt.shape(0)),
|
|
||||||
groups(groups),
|
|
||||||
flip(flip) {
|
|
||||||
std::copy_n(strides.begin(), NDIM, this->strides);
|
|
||||||
std::copy_n(padding.begin(), NDIM, this->padding);
|
|
||||||
std::copy_n(kernel_dilation.begin(), NDIM, this->kernel_dilation);
|
|
||||||
std::copy_n(input_dilation.begin(), NDIM, this->input_dilation);
|
|
||||||
std::copy_n(in.shape().begin() + 1, NDIM, this->in_spatial_dims);
|
|
||||||
std::copy_n(wt.shape().begin() + 1, NDIM, this->wt_spatial_dims);
|
|
||||||
std::copy_n(out.shape().begin() + 1, NDIM, this->out_spatial_dims);
|
|
||||||
std::copy_n(in.strides().begin(), NDIM + 2, this->in_strides);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void gemm_grouped_conv(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
int groups,
|
|
||||||
bool flip,
|
|
||||||
Stream s);
|
|
||||||
|
|
||||||
void gemm_conv(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
bool flip,
|
|
||||||
Stream s);
|
|
||||||
|
|
||||||
inline void gemm_conv(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
array in,
|
|
||||||
array wt,
|
|
||||||
array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
int groups,
|
|
||||||
bool flip,
|
|
||||||
Stream s) {
|
|
||||||
if (!in.flags().row_contiguous) {
|
|
||||||
in = contiguous_copy_gpu(in, s);
|
|
||||||
encoder.add_temporary(in);
|
|
||||||
}
|
|
||||||
if (!wt.flags().row_contiguous) {
|
|
||||||
wt = contiguous_copy_gpu(wt, s);
|
|
||||||
encoder.add_temporary(wt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groups == 1) {
|
|
||||||
gemm_conv(
|
|
||||||
encoder,
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
strides,
|
|
||||||
padding,
|
|
||||||
kernel_dilation,
|
|
||||||
input_dilation,
|
|
||||||
flip,
|
|
||||||
s);
|
|
||||||
} else {
|
|
||||||
gemm_grouped_conv(
|
|
||||||
encoder,
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
strides,
|
|
||||||
padding,
|
|
||||||
kernel_dilation,
|
|
||||||
input_dilation,
|
|
||||||
groups,
|
|
||||||
flip,
|
|
||||||
s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/conv/conv.h"
|
|
||||||
#include "mlx/backend/cuda/gemms/cublas_gemm.h"
|
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
|
||||||
#include "mlx/dtype_utils.h"
|
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
|
|
||||||
namespace cu {
|
|
||||||
|
|
||||||
namespace cg = cooperative_groups;
|
|
||||||
|
|
||||||
template <typename T, int NDIM>
|
|
||||||
__global__ void naive_unfold_nd(
|
|
||||||
const T* in,
|
|
||||||
T* out,
|
|
||||||
int filter_size,
|
|
||||||
int out_pixels,
|
|
||||||
const __grid_constant__ ConvParams<NDIM> params) {
|
|
||||||
auto block = cg::this_thread_block();
|
|
||||||
auto tid = block.group_index();
|
|
||||||
auto lid = block.thread_index();
|
|
||||||
|
|
||||||
int index_batch = tid.z / out_pixels; // [0, N)
|
|
||||||
int index_out_spatial = tid.z % out_pixels; // [0, H_out * W_out)
|
|
||||||
int index_wt_spatial =
|
|
||||||
tid.x * block.dim_threads().x + lid.x; // [0, H_wt * W_wt)
|
|
||||||
|
|
||||||
if (index_wt_spatial >= filter_size / params.C) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
in += tid.y; // [0, C)
|
|
||||||
out += tid.z * filter_size + index_wt_spatial * params.C + tid.y;
|
|
||||||
|
|
||||||
bool valid = index_batch < params.N;
|
|
||||||
|
|
||||||
// Get the coordinates in input.
|
|
||||||
int index_in[NDIM] = {};
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = NDIM - 1; i >= 0; --i) {
|
|
||||||
int index_out = index_out_spatial % params.out_spatial_dims[i];
|
|
||||||
int index_wt = index_wt_spatial % params.wt_spatial_dims[i];
|
|
||||||
|
|
||||||
if (params.flip) {
|
|
||||||
index_wt = params.wt_spatial_dims[i] - index_wt - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = index_out * params.strides[i] - params.padding[i] +
|
|
||||||
index_wt * params.kernel_dilation[i];
|
|
||||||
int index_max =
|
|
||||||
1 + params.input_dilation[i] * (params.in_spatial_dims[i] - 1);
|
|
||||||
|
|
||||||
valid &= (index >= 0) && (index < index_max) &&
|
|
||||||
(index % params.input_dilation[i] == 0);
|
|
||||||
|
|
||||||
index_in[i] = index / params.input_dilation[i];
|
|
||||||
|
|
||||||
index_out_spatial /= params.out_spatial_dims[i];
|
|
||||||
index_wt_spatial /= params.wt_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
int in_offset = index_batch * params.in_strides[0];
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
in_offset += index_in[i] * params.in_strides[i + 1];
|
|
||||||
}
|
|
||||||
*out = in[in_offset];
|
|
||||||
} else {
|
|
||||||
*out = T{0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace cu
|
|
||||||
|
|
||||||
template <int NDIM>
|
|
||||||
array unfold_inputs_nd(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
int mat_M,
|
|
||||||
int mat_K,
|
|
||||||
int mat_N,
|
|
||||||
ConvParams<NDIM>& params) {
|
|
||||||
array unfolded({mat_M, mat_K}, in.dtype(), nullptr, {});
|
|
||||||
unfolded.set_data(cu::malloc_async(unfolded.nbytes(), encoder.stream()));
|
|
||||||
encoder.add_temporary(unfolded);
|
|
||||||
|
|
||||||
int filter_size = params.C;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
filter_size *= params.wt_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int out_pixels = 1;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
out_pixels *= params.out_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int wt_spatial_size = mat_K / params.C;
|
|
||||||
dim3 block_dims;
|
|
||||||
block_dims.x = std::min(std::max(wt_spatial_size, 32), 1024);
|
|
||||||
dim3 num_blocks;
|
|
||||||
num_blocks.x = cuda::ceil_div(wt_spatial_size, block_dims.x);
|
|
||||||
num_blocks.y = params.C;
|
|
||||||
num_blocks.z = mat_M;
|
|
||||||
|
|
||||||
encoder.set_input_array(in);
|
|
||||||
encoder.set_output_array(unfolded);
|
|
||||||
dispatch_float_types(in.dtype(), "unfold", [&](auto type_tag) {
|
|
||||||
using DataType = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
|
||||||
encoder.add_kernel_node(
|
|
||||||
cu::naive_unfold_nd<DataType, NDIM>,
|
|
||||||
num_blocks,
|
|
||||||
block_dims,
|
|
||||||
0,
|
|
||||||
gpu_ptr<DataType>(in),
|
|
||||||
gpu_ptr<DataType>(unfolded),
|
|
||||||
filter_size,
|
|
||||||
out_pixels,
|
|
||||||
params);
|
|
||||||
});
|
|
||||||
|
|
||||||
return unfolded;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int NDIM>
|
|
||||||
void gemm_conv_nd(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
ConvParams<NDIM>& params,
|
|
||||||
Stream s) {
|
|
||||||
// Get gemm shapes.
|
|
||||||
int mat_M = out.size() / params.O; // N * H_out * W_out
|
|
||||||
int mat_K = wt.size() / params.O; // C * H_wt * W_wt
|
|
||||||
int mat_N = params.O; // O
|
|
||||||
|
|
||||||
// Unfold input to (N * H_out * W_out, C * H_wt * W_wt) for gemm.
|
|
||||||
array in_unfolded =
|
|
||||||
unfold_inputs_nd<NDIM>(encoder, in, mat_M, mat_K, mat_N, params);
|
|
||||||
|
|
||||||
// Reshape weight to (C * H_wt * W_wt, O) for gemm.
|
|
||||||
array wt_reshaped({mat_K, mat_N}, wt.dtype(), nullptr, {});
|
|
||||||
wt_reshaped.copy_shared_buffer(
|
|
||||||
wt,
|
|
||||||
{1, mat_K},
|
|
||||||
{false, false, /* col_contiguous */ true},
|
|
||||||
wt.data_size());
|
|
||||||
|
|
||||||
// Single batch.
|
|
||||||
Shape batch_shape{1};
|
|
||||||
Strides a_batch_strides{0};
|
|
||||||
Strides b_batch_strides{0};
|
|
||||||
|
|
||||||
// Run matmul.
|
|
||||||
CublasGemm gemm(
|
|
||||||
encoder.device(),
|
|
||||||
in.dtype(),
|
|
||||||
false, // a_transposed
|
|
||||||
mat_M, // a_rows
|
|
||||||
mat_K, // a_cols
|
|
||||||
mat_K, // lda
|
|
||||||
true, // b_transposed
|
|
||||||
mat_K, // b_rows
|
|
||||||
mat_N, // b_cols
|
|
||||||
mat_K, // ldb
|
|
||||||
batch_shape.back(),
|
|
||||||
a_batch_strides.back(),
|
|
||||||
b_batch_strides.back());
|
|
||||||
gemm.run(
|
|
||||||
encoder,
|
|
||||||
out,
|
|
||||||
in_unfolded,
|
|
||||||
wt_reshaped,
|
|
||||||
batch_shape,
|
|
||||||
a_batch_strides,
|
|
||||||
b_batch_strides);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gemm_conv(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
bool flip,
|
|
||||||
Stream s) {
|
|
||||||
int conv_ndim = in.ndim() - 2;
|
|
||||||
if (conv_ndim < 1 || conv_ndim > 3) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
fmt::format("[conv] Unsupported gemm_conv for {}D conv.", conv_ndim));
|
|
||||||
}
|
|
||||||
dispatch_1_2_3(conv_ndim, [&](auto ndim_constant) {
|
|
||||||
ConvParams<ndim_constant()> params(
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
strides,
|
|
||||||
padding,
|
|
||||||
kernel_dilation,
|
|
||||||
input_dilation,
|
|
||||||
1, // groups
|
|
||||||
flip);
|
|
||||||
gemm_conv_nd<ndim_constant()>(encoder, in, wt, out, params, s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
|
|
||||||
#include "mlx/backend/cuda/conv/conv.h"
|
|
||||||
#include "mlx/backend/cuda/gemms/cublas_gemm.h"
|
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
|
||||||
#include "mlx/dtype_utils.h"
|
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
|
||||||
|
|
||||||
namespace mlx::core {
|
|
||||||
|
|
||||||
namespace cu {
|
|
||||||
|
|
||||||
namespace cg = cooperative_groups;
|
|
||||||
|
|
||||||
template <typename T, int NDIM>
|
|
||||||
__global__ void naive_grouped_unfold_transpose_nd(
|
|
||||||
const T* in,
|
|
||||||
T* out,
|
|
||||||
int filter_size,
|
|
||||||
int out_pixels,
|
|
||||||
const __grid_constant__ ConvParams<NDIM> params) {
|
|
||||||
auto block = cg::this_thread_block();
|
|
||||||
auto tid = block.group_index();
|
|
||||||
auto lid = block.thread_index();
|
|
||||||
|
|
||||||
int index_batch = tid.z / out_pixels; // [0, N)
|
|
||||||
int index_out_spatial = tid.z % out_pixels; // [0, H_out * W_out)
|
|
||||||
int index_wt_spatial =
|
|
||||||
tid.x * block.dim_threads().x + lid.x; // [0, H_wt * W_wt)
|
|
||||||
|
|
||||||
if (index_wt_spatial >= filter_size / params.C) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
in += tid.y; // [0, C)
|
|
||||||
out += tid.z * filter_size + tid.y * (filter_size / params.C);
|
|
||||||
|
|
||||||
bool valid = index_batch < params.N;
|
|
||||||
|
|
||||||
// Get the coordinates in input.
|
|
||||||
int index_in[NDIM] = {};
|
|
||||||
int wt_stride = 1;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = NDIM - 1; i >= 0; --i) {
|
|
||||||
int index_out = index_out_spatial % params.out_spatial_dims[i];
|
|
||||||
int index_wt = index_wt_spatial % params.wt_spatial_dims[i];
|
|
||||||
out += index_wt * wt_stride;
|
|
||||||
|
|
||||||
if (params.flip) {
|
|
||||||
index_wt = params.wt_spatial_dims[i] - index_wt - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = index_out * params.strides[i] - params.padding[i] +
|
|
||||||
index_wt * params.kernel_dilation[i];
|
|
||||||
int index_max =
|
|
||||||
1 + params.input_dilation[i] * (params.in_spatial_dims[i] - 1);
|
|
||||||
|
|
||||||
valid &= (index >= 0) && (index < index_max) &&
|
|
||||||
(index % params.input_dilation[i] == 0);
|
|
||||||
|
|
||||||
index_in[i] = index / params.input_dilation[i];
|
|
||||||
|
|
||||||
index_out_spatial /= params.out_spatial_dims[i];
|
|
||||||
index_wt_spatial /= params.wt_spatial_dims[i];
|
|
||||||
wt_stride *= params.wt_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
int in_offset = index_batch * params.in_strides[0];
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
in_offset += index_in[i] * params.in_strides[i + 1];
|
|
||||||
}
|
|
||||||
*out = in[in_offset];
|
|
||||||
} else {
|
|
||||||
*out = T{0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace cu
|
|
||||||
|
|
||||||
template <int NDIM>
|
|
||||||
array grouped_unfold_transpose_inputs_nd(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
int mat_M,
|
|
||||||
int mat_K,
|
|
||||||
int mat_N,
|
|
||||||
ConvParams<NDIM>& params) {
|
|
||||||
array unfolded({mat_M, mat_K * params.groups}, in.dtype(), nullptr, {});
|
|
||||||
unfolded.set_data(cu::malloc_async(unfolded.nbytes(), encoder.stream()));
|
|
||||||
encoder.add_temporary(unfolded);
|
|
||||||
|
|
||||||
int filter_size = params.C;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
filter_size *= params.wt_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int out_pixels = 1;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < NDIM; ++i) {
|
|
||||||
out_pixels *= params.out_spatial_dims[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int wt_spatial_size = (mat_K * params.groups) / params.C;
|
|
||||||
dim3 block_dims;
|
|
||||||
block_dims.x = std::min(std::max(wt_spatial_size, 32), 1024);
|
|
||||||
dim3 num_blocks;
|
|
||||||
num_blocks.x = cuda::ceil_div(wt_spatial_size, block_dims.x);
|
|
||||||
num_blocks.y = params.C;
|
|
||||||
num_blocks.z = mat_M;
|
|
||||||
|
|
||||||
encoder.set_input_array(in);
|
|
||||||
encoder.set_output_array(unfolded);
|
|
||||||
dispatch_float_types(in.dtype(), "unfold", [&](auto type_tag) {
|
|
||||||
using DataType = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
|
||||||
encoder.add_kernel_node(
|
|
||||||
cu::naive_grouped_unfold_transpose_nd<DataType, NDIM>,
|
|
||||||
num_blocks,
|
|
||||||
block_dims,
|
|
||||||
0,
|
|
||||||
gpu_ptr<DataType>(in),
|
|
||||||
gpu_ptr<DataType>(unfolded),
|
|
||||||
filter_size,
|
|
||||||
out_pixels,
|
|
||||||
params);
|
|
||||||
});
|
|
||||||
|
|
||||||
return unfolded;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int NDIM>
|
|
||||||
void gemm_grouped_conv_nd(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
ConvParams<NDIM>& params,
|
|
||||||
Stream s) {
|
|
||||||
// Get gemm shapes.
|
|
||||||
int C_per_group = params.C / params.groups;
|
|
||||||
int O_per_group = params.O / params.groups;
|
|
||||||
int mat_M = out.size() / params.O; // N * H_out * W_out
|
|
||||||
int mat_K = wt.size() / params.O; // C_per_group * H_wt * W_wt
|
|
||||||
int mat_N = O_per_group; // O_per_group
|
|
||||||
|
|
||||||
// Unfold input to (N * H_out * W_out, C * H_wt * W_wt) for gemm.
|
|
||||||
array in_unfolded = grouped_unfold_transpose_inputs_nd<NDIM>(
|
|
||||||
encoder, in, mat_M, mat_K, mat_N, params);
|
|
||||||
|
|
||||||
// Reshape weight to (O, C_per_group, H_wt * W_wt) for gemm.
|
|
||||||
int wt_spatial_size = (wt.size() / wt.shape(0)) / wt.shape(-1);
|
|
||||||
array wt_view(
|
|
||||||
{params.O, C_per_group, wt_spatial_size}, wt.dtype(), nullptr, {});
|
|
||||||
wt_view.copy_shared_buffer(
|
|
||||||
wt, {wt.strides(0), 1, C_per_group}, wt.flags(), wt.size());
|
|
||||||
array wt_reshaped = contiguous_copy_gpu(wt_view, s);
|
|
||||||
|
|
||||||
// Batch with size of groups.
|
|
||||||
Shape batch_shape{params.groups};
|
|
||||||
Strides a_batch_strides{mat_K};
|
|
||||||
Strides b_batch_strides{mat_N * mat_K};
|
|
||||||
|
|
||||||
// Run matmul.
|
|
||||||
CublasGemm gemm(
|
|
||||||
encoder.device(),
|
|
||||||
in.dtype(),
|
|
||||||
false, // a_transposed
|
|
||||||
mat_M, // a_rows
|
|
||||||
mat_K, // a_cols
|
|
||||||
mat_K * params.groups, // lda
|
|
||||||
true, // b_transposed
|
|
||||||
mat_K, // b_rows
|
|
||||||
mat_N, // b_cols
|
|
||||||
mat_K, // ldb
|
|
||||||
batch_shape.back(),
|
|
||||||
a_batch_strides.back(),
|
|
||||||
b_batch_strides.back());
|
|
||||||
gemm.set_out(
|
|
||||||
out.dtype(),
|
|
||||||
false, // out_transposed
|
|
||||||
mat_M, // out_rows
|
|
||||||
mat_N, // out_cols
|
|
||||||
mat_N * params.groups, // out_ld
|
|
||||||
params.groups, // batch_count
|
|
||||||
mat_N); // batch_stride
|
|
||||||
gemm.run(
|
|
||||||
encoder,
|
|
||||||
out,
|
|
||||||
in_unfolded,
|
|
||||||
wt_reshaped,
|
|
||||||
batch_shape,
|
|
||||||
a_batch_strides,
|
|
||||||
b_batch_strides);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gemm_grouped_conv(
|
|
||||||
cu::CommandEncoder& encoder,
|
|
||||||
const array& in,
|
|
||||||
const array& wt,
|
|
||||||
array& out,
|
|
||||||
const std::vector<int>& strides,
|
|
||||||
const std::vector<int>& padding,
|
|
||||||
const std::vector<int>& kernel_dilation,
|
|
||||||
const std::vector<int>& input_dilation,
|
|
||||||
int groups,
|
|
||||||
bool flip,
|
|
||||||
Stream s) {
|
|
||||||
int conv_ndim = in.ndim() - 2;
|
|
||||||
if (conv_ndim < 1 || conv_ndim > 3) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
fmt::format("[conv] Unsupported gemm_conv for {}D conv.", conv_ndim));
|
|
||||||
}
|
|
||||||
dispatch_1_2_3(conv_ndim, [&](auto ndim_constant) {
|
|
||||||
ConvParams<ndim_constant()> params(
|
|
||||||
in,
|
|
||||||
wt,
|
|
||||||
out,
|
|
||||||
strides,
|
|
||||||
padding,
|
|
||||||
kernel_dilation,
|
|
||||||
input_dilation,
|
|
||||||
groups,
|
|
||||||
flip);
|
|
||||||
gemm_grouped_conv_nd<ndim_constant()>(encoder, in, wt, out, params, s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core
|
|
||||||
@@ -5,22 +5,6 @@
|
|||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
void copy_gpu(const array& in, array& out, CopyType ctype, const Stream& s) {
|
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
|
||||||
bool donated = set_copy_output_data(in, out, ctype, [&](auto n) {
|
|
||||||
return cu::malloc_async(n, encoder.stream());
|
|
||||||
});
|
|
||||||
if (donated && in.dtype() == out.dtype()) {
|
|
||||||
// If the output has the same type as the input then there is nothing to
|
|
||||||
// copy, just use the buffer.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ctype == CopyType::GeneralGeneral) {
|
|
||||||
ctype = CopyType::General;
|
|
||||||
}
|
|
||||||
copy_gpu_inplace(in, out, ctype, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void copy_gpu_inplace(
|
void copy_gpu_inplace(
|
||||||
const array& in,
|
const array& in,
|
||||||
array& out,
|
array& out,
|
||||||
@@ -31,8 +15,8 @@ void copy_gpu_inplace(
|
|||||||
int64_t offset_out,
|
int64_t offset_out,
|
||||||
CopyType ctype,
|
CopyType ctype,
|
||||||
const Stream& s,
|
const Stream& s,
|
||||||
std::optional<array> dynamic_offset_in,
|
const std::optional<array>& dynamic_offset_in,
|
||||||
std::optional<array> dynamic_offset_out) {
|
const std::optional<array>& dynamic_offset_out) {
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -60,16 +44,6 @@ void copy_gpu_inplace(
|
|||||||
strides_vec[0]);
|
strides_vec[0]);
|
||||||
} else {
|
} else {
|
||||||
if (dynamic_offset_in || dynamic_offset_out) {
|
if (dynamic_offset_in || dynamic_offset_out) {
|
||||||
if (!dynamic_offset_in) {
|
|
||||||
dynamic_offset_in = array(0, int64);
|
|
||||||
encoder.add_temporary(*dynamic_offset_in);
|
|
||||||
}
|
|
||||||
if (!dynamic_offset_out) {
|
|
||||||
dynamic_offset_out = array(0, int64);
|
|
||||||
encoder.add_temporary(*dynamic_offset_out);
|
|
||||||
}
|
|
||||||
encoder.set_input_array(*dynamic_offset_in);
|
|
||||||
encoder.set_input_array(*dynamic_offset_out);
|
|
||||||
copy_general_dynamic(
|
copy_general_dynamic(
|
||||||
encoder,
|
encoder,
|
||||||
ctype,
|
ctype,
|
||||||
@@ -80,8 +54,8 @@ void copy_gpu_inplace(
|
|||||||
shape_collapsed,
|
shape_collapsed,
|
||||||
strides_vec[0],
|
strides_vec[0],
|
||||||
strides_vec[1],
|
strides_vec[1],
|
||||||
*dynamic_offset_in,
|
dynamic_offset_in ? *dynamic_offset_in : array(0, int64),
|
||||||
*dynamic_offset_out);
|
dynamic_offset_out ? *dynamic_offset_out : array(0, int64));
|
||||||
} else {
|
} else {
|
||||||
copy_general(
|
copy_general(
|
||||||
encoder,
|
encoder,
|
||||||
@@ -103,31 +77,11 @@ void fill_gpu(const array& in, array& out, const Stream& s) {
|
|||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
out.set_data(cu::malloc_async(out.nbytes(), encoder.stream()));
|
|
||||||
encoder.set_input_array(in);
|
encoder.set_input_array(in);
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
copy_contiguous(encoder, CopyType::Scalar, in, out, 0, 0);
|
copy_contiguous(encoder, CopyType::Scalar, in, out, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reshape_gpu(const array& in, array& out, Stream s) {
|
|
||||||
auto [copy_necessary, out_strides] = prepare_reshape(in, out);
|
|
||||||
if (copy_necessary) {
|
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
|
||||||
out.set_data(cu::malloc_async(out.nbytes(), encoder.stream()));
|
|
||||||
copy_gpu_inplace(
|
|
||||||
in,
|
|
||||||
out,
|
|
||||||
in.shape(),
|
|
||||||
in.strides(),
|
|
||||||
make_contiguous_strides(in.shape()),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
CopyType::General,
|
|
||||||
s);
|
|
||||||
} else {
|
|
||||||
shared_buffer_reshape(in, out_strides, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ void copy_contiguous(
|
|||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
gpu_ptr<InType>(in) + in_offset,
|
in.data<InType>() + in_offset,
|
||||||
gpu_ptr<OutType>(out) + out_offset,
|
out.data<OutType>() + out_offset,
|
||||||
out.data_size());
|
out.data_size());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,80 +10,37 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT, int NDIM, int N_READS>
|
template <typename In, typename Out, typename IdxT, int NDIM>
|
||||||
__global__ void copy_gg_nd(
|
__global__ void copy_gg_nd(
|
||||||
const In* in,
|
const In* in,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides_in,
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides_in,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides_out) {
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides_out) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[NDIM - 1];
|
|
||||||
auto in_stride_x = strides_in[NDIM - 1];
|
|
||||||
auto out_stride_x = strides_out[NDIM - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [idx_in, idx_out] = elem_to_loc_nd<NDIM>(
|
auto [idx_in, idx_out] = elem_to_loc_nd<NDIM>(
|
||||||
index_rest * shape_x,
|
index, shape.data(), strides_in.data(), strides_out.data());
|
||||||
shape.data(),
|
out[idx_out] = CastOp<In, Out>{}(in[idx_in]);
|
||||||
strides_in.data(),
|
|
||||||
strides_out.data());
|
|
||||||
|
|
||||||
auto in_vec =
|
|
||||||
load_vector<N_READS>(in + idx_in, index_x, shape_x, in_stride_x, In(0));
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = CastOp<In, Out>{}(in_vec[i]);
|
|
||||||
}
|
}
|
||||||
store_vector(out + idx_out, index_x, out_vec, shape_x, out_stride_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT, int N_READS>
|
template <typename In, typename Out, typename IdxT>
|
||||||
__global__ void copy_gg(
|
__global__ void copy_gg(
|
||||||
const In* in,
|
const In* in,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ Shape shape,
|
const __grid_constant__ Shape shape,
|
||||||
const __grid_constant__ Strides strides_in,
|
const __grid_constant__ Strides strides_in,
|
||||||
const __grid_constant__ Strides strides_out,
|
const __grid_constant__ Strides strides_out,
|
||||||
int ndim) {
|
int ndim) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shape_x = shape[ndim - 1];
|
|
||||||
auto in_stride_x = strides_in[ndim - 1];
|
|
||||||
auto out_stride_x = strides_out[ndim - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto [idx_in, idx_out] = elem_to_loc(
|
auto [idx_in, idx_out] = elem_to_loc(
|
||||||
index_rest * shape_x,
|
index, shape.data(), strides_in.data(), strides_out.data(), ndim);
|
||||||
shape.data(),
|
out[idx_out] = CastOp<In, Out>{}(in[idx_in]);
|
||||||
strides_in.data(),
|
|
||||||
strides_out.data(),
|
|
||||||
ndim);
|
|
||||||
|
|
||||||
auto in_vec =
|
|
||||||
load_vector<N_READS>(in + idx_in, index_x, shape_x, in_stride_x, In(0));
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = CastOp<In, Out>{}(in_vec[i]);
|
|
||||||
}
|
}
|
||||||
store_vector(out + idx_out, index_x, out_vec, shape_x, out_stride_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cu
|
} // namespace cu
|
||||||
@@ -106,58 +63,39 @@ void copy_general(
|
|||||||
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
||||||
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
||||||
const InType* in_ptr = gpu_ptr<InType>(in) + offset_in;
|
const InType* in_ptr = in.data<InType>() + offset_in;
|
||||||
OutType* out_ptr = gpu_ptr<OutType>(out) + offset_out;
|
OutType* out_ptr = out.data<OutType>() + offset_out;
|
||||||
int ndim = shape.size();
|
int ndim = shape.size();
|
||||||
size_t data_size = 1;
|
size_t data_size = 1;
|
||||||
for (auto& s : shape)
|
for (auto& s : shape)
|
||||||
data_size *= s;
|
data_size *= s;
|
||||||
|
|
||||||
int work_per_thread = 1;
|
|
||||||
auto dim0 = ndim > 0 ? shape.back() : 1;
|
|
||||||
auto rest = data_size / dim0;
|
|
||||||
if (dim0 >= 4) {
|
|
||||||
work_per_thread = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
dim0 = (dim0 + work_per_thread - 1) / work_per_thread;
|
|
||||||
auto block_dims = get_block_dims(dim0, rest, 1);
|
|
||||||
uint32_t num_blocks_x = cuda::ceil_div(dim0, block_dims.x);
|
|
||||||
uint32_t num_blocks_y = cuda::ceil_div(rest, block_dims.y);
|
|
||||||
|
|
||||||
if (ndim <= 3) {
|
if (ndim <= 3) {
|
||||||
dispatch_1_2_3(ndim, [&](auto ndim_constant) {
|
dispatch_1_2_3(ndim, [&](auto ndim_constant) {
|
||||||
auto kernel =
|
auto [num_blocks, block_dims] =
|
||||||
cu::copy_gg_nd<InType, OutType, IdxT, ndim_constant(), 1>;
|
get_launch_args(data_size, shape, out.strides(), large());
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel =
|
|
||||||
cu::copy_gg_nd<InType, OutType, IdxT, ndim_constant(), 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::copy_gg_nd<InType, OutType, IdxT, ndim_constant()>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
rest,
|
data_size,
|
||||||
const_param<ndim_constant()>(shape),
|
const_param<ndim_constant()>(shape),
|
||||||
const_param<ndim_constant()>(strides_in),
|
const_param<ndim_constant()>(strides_in),
|
||||||
const_param<ndim_constant()>(strides_out));
|
const_param<ndim_constant()>(strides_out));
|
||||||
});
|
});
|
||||||
} else { // ndim >= 4
|
} else { // ndim >= 4
|
||||||
auto kernel = cu::copy_gg<InType, OutType, IdxT, 1>;
|
auto [num_blocks, block_dims] =
|
||||||
if (work_per_thread == 4) {
|
get_launch_args(data_size, shape, out.strides(), large());
|
||||||
kernel = cu::copy_gg<InType, OutType, IdxT, 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::copy_gg<InType, OutType, IdxT>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
rest,
|
data_size,
|
||||||
const_param(shape),
|
const_param(shape),
|
||||||
const_param(strides_in),
|
const_param(strides_in),
|
||||||
const_param(strides_out),
|
const_param(strides_out),
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ void copy_general_dynamic(
|
|||||||
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
||||||
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
||||||
const InType* in_ptr = gpu_ptr<InType>(in) + offset_in;
|
const InType* in_ptr = in.data<InType>() + offset_in;
|
||||||
OutType* out_ptr = gpu_ptr<OutType>(out) + offset_out;
|
OutType* out_ptr = out.data<OutType>() + offset_out;
|
||||||
int ndim = shape.size();
|
int ndim = shape.size();
|
||||||
if (ndim <= 3) {
|
if (ndim <= 3) {
|
||||||
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
||||||
@@ -90,8 +90,8 @@ void copy_general_dynamic(
|
|||||||
const_param<dims_constant()>(shape),
|
const_param<dims_constant()>(shape),
|
||||||
const_param<dims_constant()>(strides_in),
|
const_param<dims_constant()>(strides_in),
|
||||||
const_param<dims_constant()>(strides_out),
|
const_param<dims_constant()>(strides_out),
|
||||||
gpu_ptr<int64_t>(dynamic_offset_in),
|
dynamic_offset_in.data<int64_t>(),
|
||||||
gpu_ptr<int64_t>(dynamic_offset_out));
|
dynamic_offset_out.data<int64_t>());
|
||||||
});
|
});
|
||||||
} else { // ndim >= 4
|
} else { // ndim >= 4
|
||||||
auto [num_blocks, block_dims] = get_launch_args(out, large());
|
auto [num_blocks, block_dims] = get_launch_args(out, large());
|
||||||
@@ -107,8 +107,8 @@ void copy_general_dynamic(
|
|||||||
const_param(strides_in),
|
const_param(strides_in),
|
||||||
const_param(strides_out),
|
const_param(strides_out),
|
||||||
ndim,
|
ndim,
|
||||||
gpu_ptr<int64_t>(dynamic_offset_in),
|
dynamic_offset_in.data<int64_t>(),
|
||||||
gpu_ptr<int64_t>(dynamic_offset_out));
|
dynamic_offset_out.data<int64_t>());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,67 +10,33 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT, int NDIM, int N_READS>
|
template <typename In, typename Out, typename IdxT, int NDIM>
|
||||||
__global__ void copy_g_nd(
|
__global__ void copy_g_nd(
|
||||||
const In* in,
|
const In* in,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
const __grid_constant__ cuda::std::array<int32_t, NDIM> shape,
|
||||||
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides) {
|
const __grid_constant__ cuda::std::array<int64_t, NDIM> strides_in) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
IdxT idx_in = elem_to_loc_nd<NDIM>(index, shape.data(), strides_in.data());
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
out[index] = CastOp<In, Out>{}(in[idx_in]);
|
||||||
if (index_rest >= size_rest) {
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto shape_x = shape[NDIM - 1];
|
template <typename In, typename Out, typename IdxT>
|
||||||
auto stride_x = strides[NDIM - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto idx =
|
|
||||||
elem_to_loc_nd<NDIM>(index_rest * shape_x, shape.data(), strides.data());
|
|
||||||
auto in_vec =
|
|
||||||
load_vector<N_READS>(in + idx, index_x, shape_x, stride_x, In(0));
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = CastOp<In, Out>{}(in_vec[i]);
|
|
||||||
}
|
|
||||||
store_vector(out + shape_x * index_rest, index_x, out_vec, shape_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT, int N_READS>
|
|
||||||
__global__ void copy_g(
|
__global__ void copy_g(
|
||||||
const In* in,
|
const In* in,
|
||||||
Out* out,
|
Out* out,
|
||||||
IdxT size_rest,
|
IdxT size,
|
||||||
const __grid_constant__ Shape shape,
|
const __grid_constant__ Shape shape,
|
||||||
const __grid_constant__ Strides strides,
|
const __grid_constant__ Strides strides_in,
|
||||||
int ndim) {
|
int ndim) {
|
||||||
auto block = cg::this_thread_block();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
auto grid = cg::this_grid();
|
if (index < size) {
|
||||||
IdxT index_rest =
|
IdxT idx_in = elem_to_loc(index, shape.data(), strides_in.data(), ndim);
|
||||||
grid.block_index().y * block.dim_threads().y + block.thread_index().y;
|
out[index] = CastOp<In, Out>{}(in[idx_in]);
|
||||||
if (index_rest >= size_rest) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto shape_x = shape[ndim - 1];
|
|
||||||
auto stride_x = strides[ndim - 1];
|
|
||||||
IdxT index_x =
|
|
||||||
grid.block_index().x * block.dim_threads().x + block.thread_index().x;
|
|
||||||
auto idx =
|
|
||||||
elem_to_loc(index_rest * shape_x, shape.data(), strides.data(), ndim);
|
|
||||||
auto in_vec =
|
|
||||||
load_vector<N_READS>(in + idx, index_x, shape_x, stride_x, In(0));
|
|
||||||
AlignedVector<Out, N_READS> out_vec;
|
|
||||||
#pragma unroll
|
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
|
||||||
out_vec[i] = CastOp<In, Out>{}(in_vec[i]);
|
|
||||||
}
|
|
||||||
store_vector(out + shape_x * index_rest, index_x, out_vec, shape_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cu
|
} // namespace cu
|
||||||
@@ -92,52 +58,33 @@ void copy_general_input(
|
|||||||
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
using InType = cuda_type_t<MLX_GET_TYPE(in_type_tag)>;
|
||||||
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
using OutType = cuda_type_t<MLX_GET_TYPE(out_type_tag)>;
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
||||||
const InType* in_ptr = gpu_ptr<InType>(in) + offset_in;
|
const InType* in_ptr = in.data<InType>() + offset_in;
|
||||||
OutType* out_ptr = gpu_ptr<OutType>(out) + offset_out;
|
OutType* out_ptr = out.data<OutType>() + offset_out;
|
||||||
int ndim = shape.size();
|
int ndim = shape.size();
|
||||||
int work_per_thread = 1;
|
|
||||||
auto dim0 = ndim > 0 ? shape.back() : 1;
|
|
||||||
auto rest = out.size() / dim0;
|
|
||||||
if (dim0 >= 4) {
|
|
||||||
work_per_thread = 4;
|
|
||||||
}
|
|
||||||
dim0 = (dim0 + work_per_thread - 1) / work_per_thread;
|
|
||||||
auto block_dims = get_block_dims(dim0, rest, 1);
|
|
||||||
uint32_t num_blocks_x = cuda::ceil_div(dim0, block_dims.x);
|
|
||||||
uint32_t num_blocks_y = cuda::ceil_div(rest, block_dims.y);
|
|
||||||
|
|
||||||
if (ndim <= 3) {
|
if (ndim <= 3) {
|
||||||
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
dispatch_1_2_3(ndim, [&](auto dims_constant) {
|
||||||
auto kernel =
|
auto [num_blocks, block_dims] = get_launch_args(out, large());
|
||||||
cu::copy_g_nd<InType, OutType, IdxT, dims_constant(), 1>;
|
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel =
|
|
||||||
cu::copy_g_nd<InType, OutType, IdxT, dims_constant(), 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::copy_g_nd<InType, OutType, IdxT, dims_constant()>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
rest,
|
out.size(),
|
||||||
const_param<dims_constant()>(shape),
|
const_param<dims_constant()>(shape),
|
||||||
const_param<dims_constant()>(strides_in));
|
const_param<dims_constant()>(strides_in));
|
||||||
});
|
});
|
||||||
} else { // ndim >= 4
|
} else { // ndim >= 4
|
||||||
auto kernel = cu::copy_g<InType, OutType, IdxT, 1>;
|
auto [num_blocks, block_dims] = get_launch_args(out, large());
|
||||||
if (work_per_thread == 4) {
|
|
||||||
kernel = cu::copy_g<InType, OutType, IdxT, 4>;
|
|
||||||
}
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
cu::copy_g<InType, OutType, IdxT>,
|
||||||
{num_blocks_x, num_blocks_y},
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
0,
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
rest,
|
out.size(),
|
||||||
const_param(shape),
|
const_param(shape),
|
||||||
const_param(strides_in),
|
const_param(strides_in),
|
||||||
ndim);
|
ndim);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user