mirror of
https://github.com/ml-explore/mlx.git
synced 2025-12-16 01:49:05 +08:00
Compare commits
46 Commits
9794ec6b8e
...
qmm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8269c9d02d | ||
|
|
903b40627c | ||
|
|
700f7dcf01 | ||
|
|
6c60bd1cbf | ||
|
|
a64cc02a0c | ||
|
|
346ae5fdb5 | ||
|
|
93d70419e7 | ||
|
|
63f663d9c6 | ||
|
|
84b4d96efa | ||
|
|
aec67f2fa6 | ||
|
|
deee214a95 | ||
|
|
45adec102c | ||
|
|
31fc530c76 | ||
|
|
fbb3f65a1a | ||
|
|
6b1b8ea91b | ||
|
|
b2273733ea | ||
|
|
f409b229a4 | ||
|
|
30571e2326 | ||
|
|
d7734edd9f | ||
|
|
2ba69bc8fa | ||
|
|
cb349a291c | ||
|
|
f0a0b077a0 | ||
|
|
49114f28ab | ||
|
|
e7d2ebadd2 | ||
|
|
e569803d7c | ||
|
|
d34f887abc | ||
|
|
5201df5030 | ||
|
|
2d3c26c565 | ||
|
|
6325f60d52 | ||
|
|
42cc9cfbc7 | ||
|
|
8347575ba1 | ||
|
|
b6eec20260 | ||
|
|
0eb035b4b1 | ||
|
|
afb9817599 | ||
|
|
8fb3e7a26c | ||
|
|
8c7bc30ce4 | ||
|
|
85873cb162 | ||
|
|
e14ee12491 | ||
|
|
8b9a3f3cea | ||
|
|
fb4e8b896b | ||
|
|
2ca533b279 | ||
|
|
4a9b29a875 | ||
|
|
a4fcc893cd | ||
|
|
9d10239af7 | ||
|
|
19facd4b20 | ||
|
|
f5299f72cd |
@@ -7,18 +7,6 @@ parameters:
|
|||||||
nightly_build:
|
nightly_build:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
weekly_build:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
test_release:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
linux_release:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
cuda_release:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_documentation:
|
build_documentation:
|
||||||
@@ -41,7 +29,7 @@ jobs:
|
|||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install --upgrade cmake
|
pip install --upgrade cmake
|
||||||
pip install -r docs/requirements.txt
|
pip install -r docs/requirements.txt
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`sysctl -n hw.ncpu` pip install . -v
|
pip install . -v
|
||||||
- when:
|
- when:
|
||||||
condition:
|
condition:
|
||||||
not: << parameters.upload-docs >>
|
not: << parameters.upload-docs >>
|
||||||
@@ -73,9 +61,9 @@ jobs:
|
|||||||
git push -f origin gh-pages
|
git push -f origin gh-pages
|
||||||
|
|
||||||
linux_build_and_test:
|
linux_build_and_test:
|
||||||
docker:
|
machine:
|
||||||
- image: cimg/python:3.9
|
image: ubuntu-2204:current
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
@@ -87,21 +75,17 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
command: |
|
command: |
|
||||||
pip install --upgrade cmake
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
pip install nanobind==2.4.0
|
export NEEDRESTART_MODE=a
|
||||||
pip install numpy
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
sudo apt-get upgrade -y
|
||||||
|
pip install --upgrade cmake
|
||||||
|
sudo apt-get install -y libblas-dev liblapack-dev liblapacke-dev
|
||||||
sudo apt-get install openmpi-bin openmpi-common libopenmpi-dev
|
sudo apt-get install openmpi-bin openmpi-common libopenmpi-dev
|
||||||
- run:
|
- run:
|
||||||
name: Install Python package
|
name: Install Python package
|
||||||
command: |
|
command: |
|
||||||
CMAKE_ARGS="-DMLX_BUILD_METAL=OFF" \
|
pip install -e ".[dev]"
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
|
||||||
python3 setup.py build_ext --inplace
|
|
||||||
CMAKE_ARGS="-DMLX_BUILD_METAL=OFF" \
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
|
||||||
python3 setup.py develop
|
|
||||||
- run:
|
- run:
|
||||||
name: Generate package stubs
|
name: Generate package stubs
|
||||||
command: |
|
command: |
|
||||||
@@ -111,13 +95,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Run Python tests
|
name: Run Python tests
|
||||||
command: |
|
command: |
|
||||||
python3 -m unittest discover python/tests -v
|
python -m unittest discover python/tests -v
|
||||||
mpirun --bind-to none -host localhost:8 -np 8 python python/tests/mpi_test_distributed.py
|
mpirun --bind-to none -host localhost:8 -np 8 python python/tests/mpi_test_distributed.py
|
||||||
mlx.launch --verbose -n 8 python/tests/ring_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
|
||||||
- run:
|
- run:
|
||||||
name: Build CPP only
|
name: Build CPP only
|
||||||
command: |
|
command: |
|
||||||
mkdir -p build && cd build
|
mkdir -p build && cd build
|
||||||
cmake .. -DMLX_BUILD_METAL=OFF -DCMAKE_BUILD_TYPE=DEBUG
|
cmake .. -DMLX_BUILD_METAL=OFF -DCMAKE_BUILD_TYPE=DEBUG
|
||||||
make -j `nproc`
|
make -j `nproc`
|
||||||
- run:
|
- run:
|
||||||
@@ -157,8 +142,7 @@ jobs:
|
|||||||
name: Install Python package
|
name: Install Python package
|
||||||
command: |
|
command: |
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
DEBUG=1 CMAKE_BUILD_PARALLEL_LEVEL=`sysctl -n hw.ncpu` \
|
DEBUG=1 CMAKE_ARGS="-DCMAKE_COMPILE_WARNING_AS_ERROR=ON" \
|
||||||
CMAKE_ARGS="-DCMAKE_COMPILE_WARNING_AS_ERROR=ON" \
|
|
||||||
pip install -e . -v
|
pip install -e . -v
|
||||||
- run:
|
- run:
|
||||||
name: Generate package stubs
|
name: Generate package stubs
|
||||||
@@ -173,7 +157,8 @@ jobs:
|
|||||||
LOW_MEMORY=1 DEVICE=cpu python -m xmlrunner discover -v python/tests -o test-results/cpu
|
LOW_MEMORY=1 DEVICE=cpu python -m xmlrunner discover -v python/tests -o test-results/cpu
|
||||||
LOW_MEMORY=1 DEVICE=gpu METAL_DEVICE_WRAPPER_TYPE=1 METAL_DEBUG_ERROR_MODE=0 python -m xmlrunner discover -v python/tests -o test-results/gpu
|
LOW_MEMORY=1 DEVICE=gpu METAL_DEVICE_WRAPPER_TYPE=1 METAL_DEBUG_ERROR_MODE=0 python -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
|
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
|
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
|
||||||
- run:
|
- run:
|
||||||
name: Build example extension
|
name: Build example extension
|
||||||
command: |
|
command: |
|
||||||
@@ -208,8 +193,7 @@ jobs:
|
|||||||
name: Run Python tests with JIT
|
name: Run Python tests with JIT
|
||||||
command: |
|
command: |
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`sysctl -n hw.ncpu` \
|
CMAKE_ARGS="-DMLX_METAL_JIT=ON" \
|
||||||
CMAKE_ARGS="-DMLX_METAL_JIT=ON" \
|
|
||||||
pip install -e . -v
|
pip install -e . -v
|
||||||
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 \
|
||||||
@@ -217,7 +201,7 @@ jobs:
|
|||||||
|
|
||||||
cuda_build_and_test:
|
cuda_build_and_test:
|
||||||
machine:
|
machine:
|
||||||
image: linux-cuda-12:default
|
image: linux-cuda-12:2023.11.1
|
||||||
resource_class: gpu.nvidia.small.gen2
|
resource_class: gpu.nvidia.small.gen2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -226,10 +210,9 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
||||||
python -m venv env
|
python3 -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
||||||
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
|
||||||
pip install -e ".[dev]"
|
pip install -e ".[dev]"
|
||||||
- run:
|
- run:
|
||||||
name: Run Python tests
|
name: Run Python tests
|
||||||
@@ -278,7 +261,6 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
env -u MACOSX_DEPLOYMENT_TARGET DEV_RELEASE=1 \
|
env -u MACOSX_DEPLOYMENT_TARGET DEV_RELEASE=1 \
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`sysctl -n hw.ncpu` \
|
|
||||||
pip install . -v
|
pip install . -v
|
||||||
- run:
|
- run:
|
||||||
name: Generate package stubs
|
name: Generate package stubs
|
||||||
@@ -290,9 +272,18 @@ jobs:
|
|||||||
name: Build Python package
|
name: Build Python package
|
||||||
command: |
|
command: |
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
<< parameters.build_env >> \
|
python setup.py clean --all
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`sysctl -n hw.ncpu` \
|
<< parameters.build_env >> MLX_BUILD_STAGE=1 python -m build -w
|
||||||
python -m build -w
|
- when:
|
||||||
|
condition:
|
||||||
|
equal: ["3.9", << parameters.python_version >>]
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Build common package
|
||||||
|
command: |
|
||||||
|
source env/bin/activate
|
||||||
|
python setup.py clean --all
|
||||||
|
<< parameters.build_env >> MLX_BUILD_STAGE=2 python -m build -w
|
||||||
- when:
|
- when:
|
||||||
condition: << parameters.build_env >>
|
condition: << parameters.build_env >>
|
||||||
steps:
|
steps:
|
||||||
@@ -309,65 +300,73 @@ jobs:
|
|||||||
python_version:
|
python_version:
|
||||||
type: string
|
type: string
|
||||||
default: "3.9"
|
default: "3.9"
|
||||||
extra_env:
|
build_env:
|
||||||
type: string
|
type: string
|
||||||
default: "DEV_RELEASE=1"
|
default: ""
|
||||||
docker:
|
machine:
|
||||||
- image: ubuntu:20.04
|
image: ubuntu-2204:current
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: Build wheel
|
name: Build wheel
|
||||||
command: |
|
command: |
|
||||||
PYTHON=python<< parameters.python_version >>
|
PYTHON=python<< parameters.python_version >>
|
||||||
apt-get update
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get upgrade -y
|
export NEEDRESTART_MODE=a
|
||||||
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
sudo apt-get update
|
||||||
apt-get install -y apt-utils
|
sudo apt-get upgrade -y
|
||||||
apt-get install -y software-properties-common
|
TZ=Etc/UTC sudo apt-get -y install tzdata
|
||||||
add-apt-repository -y ppa:deadsnakes/ppa
|
sudo apt-get install -y apt-utils
|
||||||
apt-get install -y $PYTHON $PYTHON-dev $PYTHON-full
|
sudo apt-get install -y software-properties-common
|
||||||
apt-get install -y libblas-dev liblapack-dev liblapacke-dev
|
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
||||||
apt-get install -y build-essential git
|
sudo apt-get install -y $PYTHON $PYTHON-dev $PYTHON-full
|
||||||
|
sudo apt-get install -y libblas-dev liblapack-dev liblapacke-dev
|
||||||
|
sudo apt-get install -y build-essential git
|
||||||
$PYTHON -m venv env
|
$PYTHON -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
|
||||||
pip install nanobind==2.4.0
|
|
||||||
pip install --upgrade setuptools
|
|
||||||
pip install numpy
|
|
||||||
pip install auditwheel
|
pip install auditwheel
|
||||||
pip install patchelf
|
pip install patchelf
|
||||||
pip install build
|
pip install build
|
||||||
pip install twine
|
pip install twine
|
||||||
<< parameters.extra_env >> \
|
<< parameters.build_env >> pip install ".[dev]" -v
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
|
||||||
pip install . -v
|
|
||||||
pip install typing_extensions
|
pip install typing_extensions
|
||||||
python setup.py generate_stubs
|
python setup.py generate_stubs
|
||||||
<< parameters.extra_env >> \
|
python setup.py clean --all
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
MLX_BUILD_STAGE=1 << parameters.build_env >> python -m build -w
|
||||||
python -m build --wheel
|
bash python/scripts/repair_linux.sh
|
||||||
auditwheel show dist/*
|
- when:
|
||||||
auditwheel repair dist/* --plat manylinux_2_31_x86_64
|
condition:
|
||||||
- run:
|
equal: ["3.9", << parameters.python_version >>]
|
||||||
name: Upload package
|
steps:
|
||||||
command: |
|
- run:
|
||||||
source env/bin/activate
|
name: Build common package
|
||||||
twine upload wheelhouse/*
|
command: |
|
||||||
|
source env/bin/activate
|
||||||
|
python setup.py clean --all
|
||||||
|
<< parameters.build_env >> MLX_BUILD_STAGE=2 \
|
||||||
|
python -m build -w
|
||||||
|
auditwheel repair dist/mlx_cpu*.whl --plat manylinux_2_35_x86_64
|
||||||
|
- when:
|
||||||
|
condition: << parameters.build_env >>
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Upload packages
|
||||||
|
command: |
|
||||||
|
source env/bin/activate
|
||||||
|
twine upload wheelhouse/*.whl
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: wheelhouse/
|
path: wheelhouse/
|
||||||
|
|
||||||
build_cuda_release:
|
build_cuda_release:
|
||||||
parameters:
|
parameters:
|
||||||
python_version:
|
build_env:
|
||||||
type: string
|
type: string
|
||||||
default: "3.9"
|
default: ""
|
||||||
extra_env:
|
|
||||||
type: string
|
|
||||||
default: "DEV_RELEASE=1"
|
|
||||||
machine:
|
machine:
|
||||||
image: linux-cuda-12:default
|
image: linux-cuda-12:2024.11.1
|
||||||
resource_class: gpu.nvidia.small.gen2
|
resource_class: gpu.nvidia.small.gen2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -376,27 +375,25 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
sudo apt-get install libblas-dev liblapack-dev liblapacke-dev
|
||||||
|
sudo apt-get install zip
|
||||||
python -m venv env
|
python -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
pip install auditwheel
|
pip install auditwheel
|
||||||
pip install patchelf
|
pip install patchelf
|
||||||
pip install build
|
pip install build
|
||||||
pip install twine
|
pip install twine
|
||||||
<< parameters.extra_env >> \
|
<< parameters.build_env >> MLX_BUILD_STAGE=2 \
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
|
||||||
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
||||||
pip install ".[dev]" -v
|
python -m build -w
|
||||||
python setup.py generate_stubs
|
|
||||||
<< parameters.extra_env >> \
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=`nproc` \
|
|
||||||
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON -DCMAKE_CUDA_COMPILER=`which nvcc`" \
|
|
||||||
python -m build --wheel
|
|
||||||
bash python/scripts/repair_cuda.sh
|
bash python/scripts/repair_cuda.sh
|
||||||
- run:
|
- when:
|
||||||
name: Upload package
|
condition: << parameters.build_env >>
|
||||||
command: |
|
steps:
|
||||||
source env/bin/activate
|
- run:
|
||||||
twine upload wheelhouse/*.whl
|
name: Upload package
|
||||||
|
command: |
|
||||||
|
source env/bin/activate
|
||||||
|
twine upload wheelhouse/*.whl
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: wheelhouse/
|
path: wheelhouse/
|
||||||
|
|
||||||
@@ -408,8 +405,6 @@ workflows:
|
|||||||
pattern: "^(?!pull/)[-\\w]+$"
|
pattern: "^(?!pull/)[-\\w]+$"
|
||||||
value: << pipeline.git.branch >>
|
value: << pipeline.git.branch >>
|
||||||
- not: << pipeline.parameters.nightly_build >>
|
- not: << pipeline.parameters.nightly_build >>
|
||||||
- not: << pipeline.parameters.weekly_build >>
|
|
||||||
- not: << pipeline.parameters.test_release >>
|
|
||||||
jobs:
|
jobs:
|
||||||
- mac_build_and_test:
|
- mac_build_and_test:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -423,8 +418,6 @@ workflows:
|
|||||||
when:
|
when:
|
||||||
and:
|
and:
|
||||||
- not: << pipeline.parameters.nightly_build >>
|
- not: << pipeline.parameters.nightly_build >>
|
||||||
- not: << pipeline.parameters.weekly_build >>
|
|
||||||
- not: << pipeline.parameters.test_release >>
|
|
||||||
jobs:
|
jobs:
|
||||||
- build_release:
|
- build_release:
|
||||||
filters:
|
filters:
|
||||||
@@ -506,6 +499,25 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
upload-docs: true
|
upload-docs: true
|
||||||
|
- build_linux_release:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
matrix:
|
||||||
|
parameters:
|
||||||
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
build_env: ["PYPI_RELEASE=1"]
|
||||||
|
- build_cuda_release:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
matrix:
|
||||||
|
parameters:
|
||||||
|
build_env: ["PYPI_RELEASE=1"]
|
||||||
|
|
||||||
prb:
|
prb:
|
||||||
when:
|
when:
|
||||||
@@ -584,99 +596,8 @@ workflows:
|
|||||||
- macosx_deployment_target: "15.0"
|
- macosx_deployment_target: "15.0"
|
||||||
xcode_version: "15.0.0"
|
xcode_version: "15.0.0"
|
||||||
python_version: "3.13"
|
python_version: "3.13"
|
||||||
weekly_build:
|
|
||||||
when:
|
|
||||||
and:
|
|
||||||
- equal: [ main, << pipeline.git.branch >> ]
|
|
||||||
- << pipeline.parameters.weekly_build >>
|
|
||||||
jobs:
|
|
||||||
- build_release:
|
|
||||||
matrix:
|
|
||||||
parameters:
|
|
||||||
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
||||||
macosx_deployment_target: ["13.5", "14.0", "15.0"]
|
|
||||||
build_env: ["DEV_RELEASE=1"]
|
|
||||||
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"
|
|
||||||
linux_test_release:
|
|
||||||
when:
|
|
||||||
and:
|
|
||||||
- equal: [ main, << pipeline.git.branch >> ]
|
|
||||||
- << pipeline.parameters.linux_release >>
|
|
||||||
jobs:
|
|
||||||
- build_linux_release:
|
- build_linux_release:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
extra_env: ["PYPI_RELEASE=1"]
|
- build_cuda_release
|
||||||
cuda_test_release:
|
|
||||||
when:
|
|
||||||
and:
|
|
||||||
- equal: [ main, << pipeline.git.branch >> ]
|
|
||||||
- << pipeline.parameters.cuda_release >>
|
|
||||||
jobs:
|
|
||||||
- build_cuda_release:
|
|
||||||
matrix:
|
|
||||||
parameters:
|
|
||||||
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
||||||
extra_env: ["PYPI_RELEASE=1"]
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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.
|
||||||
|
|
||||||
<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" />
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ project(
|
|||||||
|
|
||||||
# ----------------------------- Setup -----------------------------
|
# ----------------------------- Setup -----------------------------
|
||||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
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)
|
||||||
@@ -64,10 +64,8 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
|||||||
message(WARNING "Building for x86_64 arch is not officially supported.")
|
message(WARNING "Building for x86_64 arch is not officially supported.")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
else()
|
else()
|
||||||
set(MLX_BUILD_METAL OFF)
|
set(MLX_BUILD_METAL OFF)
|
||||||
message(WARNING "MLX is prioritised for Apple silicon systems using macOS.")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ----------------------------- Lib -----------------------------
|
# ----------------------------- Lib -----------------------------
|
||||||
|
|||||||
@@ -193,8 +193,8 @@ void time_reductions() {
|
|||||||
auto argmin_along_1 = [&a]() { return mx::argmin(a, 1, false); };
|
auto argmin_along_1 = [&a]() { return mx::argmin(a, 1, false); };
|
||||||
TIME(argmin_along_1);
|
TIME(argmin_along_1);
|
||||||
|
|
||||||
auto indices = mlx::core::array({1});
|
auto indices = mx::array({1});
|
||||||
auto updates = mlx::core::reshape(mlx::core::array({NAN}), {1, 1, 1});
|
auto updates = mx::reshape(mx::array({NAN}), {1, 1, 1});
|
||||||
std::vector<int> axes{0};
|
std::vector<int> axes{0};
|
||||||
auto b = scatter(a, {indices}, updates, axes);
|
auto b = scatter(a, {indices}, updates, axes);
|
||||||
mx::eval(b);
|
mx::eval(b);
|
||||||
@@ -203,6 +203,11 @@ void time_reductions() {
|
|||||||
TIME(max_along_0);
|
TIME(max_along_0);
|
||||||
auto max_along_1 = [&b]() { return mx::max(b, 1, false); };
|
auto max_along_1 = [&b]() { return mx::max(b, 1, false); };
|
||||||
TIME(max_along_1);
|
TIME(max_along_1);
|
||||||
|
|
||||||
|
auto min_along_0 = [&b]() { return mx::min(b, 0, false); };
|
||||||
|
TIME(min_along_0);
|
||||||
|
auto min_along_1 = [&b]() { return mx::min(b, 1, false); };
|
||||||
|
TIME(min_along_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void time_gather_scatter() {
|
void time_gather_scatter() {
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ def time_max():
|
|||||||
time_fn(mx.max, a, 0)
|
time_fn(mx.max, a, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def time_min():
|
||||||
|
a = mx.random.uniform(shape=(32, 1024, 1024))
|
||||||
|
a[1, 1] = mx.nan
|
||||||
|
mx.eval(a)
|
||||||
|
time_fn(mx.min, a, 0)
|
||||||
|
|
||||||
|
|
||||||
def time_negative():
|
def time_negative():
|
||||||
a = mx.random.uniform(shape=(10000, 1000))
|
a = mx.random.uniform(shape=(10000, 1000))
|
||||||
mx.eval(a)
|
mx.eval(a)
|
||||||
@@ -115,6 +122,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
time_add()
|
time_add()
|
||||||
time_matmul()
|
time_matmul()
|
||||||
|
time_min()
|
||||||
time_max()
|
time_max()
|
||||||
time_maximum()
|
time_maximum()
|
||||||
time_exp()
|
time_exp()
|
||||||
|
|||||||
@@ -138,13 +138,13 @@ more concrete:
|
|||||||
* representing the vectorized computation and the axis which
|
* representing the vectorized computation and the axis which
|
||||||
* corresponds to the output vectorized dimension.
|
* corresponds to the output vectorized dimension.
|
||||||
*/
|
*/
|
||||||
virtual std::pair<std::vector<array>, std::vector<int>> vmap(
|
std::pair<std::vector<array>, std::vector<int>> vmap(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
const std::vector<int>& axes) override;
|
const std::vector<int>& axes) override;
|
||||||
|
|
||||||
/** Print the primitive. */
|
/** The name of primitive. */
|
||||||
void print(std::ostream& os) override {
|
const char* name() const override {
|
||||||
os << "Axpby";
|
return "Axpby";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Equivalence check **/
|
/** Equivalence check **/
|
||||||
|
|||||||
@@ -23,13 +23,6 @@ To install from PyPI you must meet the following requirements:
|
|||||||
MLX is only available on devices running macOS >= 13.5
|
MLX is only available on devices running macOS >= 13.5
|
||||||
It is highly recommended to use macOS 14 (Sonoma)
|
It is highly recommended to use macOS 14 (Sonoma)
|
||||||
|
|
||||||
|
|
||||||
MLX is also available on conda-forge. To install MLX with conda do:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
conda install conda-forge::mlx
|
|
||||||
|
|
||||||
CUDA
|
CUDA
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
@@ -38,8 +31,16 @@ and SM 7.0 (Volta) and up. To install MLX with CUDA support, run:
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
pip install mlx-cuda
|
pip install "mlx[cuda]"
|
||||||
|
|
||||||
|
CPU-only (Linux)
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
For a CPU-only version of MLX that runs on Linux use:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
pip install "mlx[cpu]"
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
@@ -88,20 +89,20 @@ Then simply build and install MLX using pip:
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=8 pip install .
|
pip install .
|
||||||
|
|
||||||
For developing, install the package with development dependencies, and use an
|
For developing, install the package with development dependencies, and use an
|
||||||
editable install:
|
editable install:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=8 pip install -e ".[dev]"
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
Once the development dependencies are installed, you can build faster with:
|
Once the development dependencies are installed, you can build faster with:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=8 python setup.py build_ext --inplace
|
python setup.py build_ext --inplace
|
||||||
|
|
||||||
Run the tests with:
|
Run the tests with:
|
||||||
|
|
||||||
@@ -262,7 +263,7 @@ When building either the Python or C++ APIs make sure to pass the cmake flag
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
CMAKE_BUILD_PARALLEL_LEVEL=8 CMAKE_ARGS="-DMLX_BUILD_CUDA=ON" pip install -e ".[dev]"
|
CMAKE_ARGS="-DMLX_BUILD_CUDA=ON" pip install -e ".[dev]"
|
||||||
|
|
||||||
To build the C++ package run:
|
To build the C++ package run:
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ Common Optimizers
|
|||||||
Adamax
|
Adamax
|
||||||
Lion
|
Lion
|
||||||
MultiOptimizer
|
MultiOptimizer
|
||||||
|
Muon
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ class Axpby : public mx::Primitive {
|
|||||||
const std::vector<mx::array>& inputs,
|
const std::vector<mx::array>& inputs,
|
||||||
const std::vector<int>& axes) override;
|
const std::vector<int>& axes) override;
|
||||||
|
|
||||||
/** Print the primitive. */
|
/** The name of primitive. */
|
||||||
void print(std::ostream& os) override {
|
const char* name() const override {
|
||||||
os << "Axpby";
|
return "Axpby";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Equivalence check **/
|
/** Equivalence check **/
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
// Copyright © 2023-2024 Apple Inc.
|
// Copyright © 2023-2024 Apple Inc.
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
#include "mlx/backend/common/utils.h"
|
#include "mlx/backend/common/utils.h"
|
||||||
#include "mlx/primitives.h"
|
|
||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
std::string get_primitive_string(Primitive* primitive) {
|
std::filesystem::path current_binary_dir() {
|
||||||
std::ostringstream op_t;
|
static std::filesystem::path binary_dir = []() {
|
||||||
primitive->print(op_t);
|
Dl_info info;
|
||||||
return op_t.str();
|
if (!dladdr(reinterpret_cast<void*>(¤t_binary_dir), &info)) {
|
||||||
|
throw std::runtime_error("Unable to get current binary dir.");
|
||||||
|
}
|
||||||
|
return std::filesystem::path(info.dli_fname).parent_path();
|
||||||
|
}();
|
||||||
|
return binary_dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<Shape, std::vector<Strides>> collapse_contiguous_dims(
|
std::tuple<Shape, std::vector<Strides>> collapse_contiguous_dims(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@
|
|||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
std::string get_primitive_string(Primitive* primitive);
|
// Return the directory that contains current shared library.
|
||||||
|
std::filesystem::path current_binary_dir();
|
||||||
|
|
||||||
inline int64_t
|
inline int64_t
|
||||||
elem_to_loc(int elem, const Shape& shape, const Strides& strides) {
|
elem_to_loc(int elem, const Shape& shape, const Strides& strides) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ void cholesky_impl(const array& a, array& factor, bool upper, Stream stream) {
|
|||||||
|
|
||||||
// The decomposition is computed in place, so just copy the input to the
|
// The decomposition is computed in place, so just copy the input to the
|
||||||
// output.
|
// output.
|
||||||
copy(
|
copy_cpu(
|
||||||
a,
|
a,
|
||||||
factor,
|
factor,
|
||||||
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ inline void build_kernel(
|
|||||||
os << "static_cast<" << get_type_string(x.dtype()) << ">(tmp_"
|
os << "static_cast<" << get_type_string(x.dtype()) << ">(tmp_"
|
||||||
<< namer.get_name(x.inputs()[0]) << ");" << std::endl;
|
<< namer.get_name(x.inputs()[0]) << ");" << std::endl;
|
||||||
} else {
|
} else {
|
||||||
x.primitive().print(os);
|
os << x.primitive().name();
|
||||||
os << "()(";
|
os << "()(";
|
||||||
for (int i = 0; i < x.inputs().size() - 1; i++) {
|
for (int i = 0; i < x.inputs().size() - 1; i++) {
|
||||||
os << "tmp_" << namer.get_name(x.inputs()[i]) << ", ";
|
os << "tmp_" << namer.get_name(x.inputs()[i]) << ", ";
|
||||||
|
|||||||
@@ -883,7 +883,7 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
// Fill with zeros
|
// Fill with zeros
|
||||||
std::vector<array> temps;
|
std::vector<array> temps;
|
||||||
temps.push_back(array(0, conv_dtype));
|
temps.push_back(array(0, conv_dtype));
|
||||||
copy(temps.back(), in_padded, CopyType::Scalar, stream);
|
copy_cpu(temps.back(), in_padded, CopyType::Scalar, stream);
|
||||||
|
|
||||||
// Pick input slice from padded
|
// Pick input slice from padded
|
||||||
size_t data_offset = padding_lo[0] * in_padded.strides()[1];
|
size_t data_offset = padding_lo[0] * in_padded.strides()[1];
|
||||||
@@ -895,7 +895,7 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
in_padded_slice.size(),
|
in_padded_slice.size(),
|
||||||
data_offset);
|
data_offset);
|
||||||
// Copy input values into the slice
|
// Copy input values into the slice
|
||||||
copy_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
copy_cpu_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
||||||
temps.push_back(in_padded_slice);
|
temps.push_back(in_padded_slice);
|
||||||
|
|
||||||
// Make strided view
|
// Make strided view
|
||||||
@@ -920,7 +920,7 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
// Materialize strided view
|
// Materialize strided view
|
||||||
Shape strided_reshape = {N * oH, wH * C};
|
Shape strided_reshape = {N * oH, wH * C};
|
||||||
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
||||||
copy(in_strided_view, in_strided, CopyType::General, stream);
|
copy_cpu(in_strided_view, in_strided, CopyType::General, stream);
|
||||||
temps.push_back(in_strided);
|
temps.push_back(in_strided);
|
||||||
|
|
||||||
// Check wt dtype and prepare
|
// Check wt dtype and prepare
|
||||||
@@ -938,13 +938,13 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
wt.size(),
|
wt.size(),
|
||||||
0);
|
0);
|
||||||
gemm_wt = array(wt_transpose.shape(), float32, nullptr, {});
|
gemm_wt = array(wt_transpose.shape(), float32, nullptr, {});
|
||||||
copy(wt_transpose, gemm_wt, CopyType::General, stream);
|
copy_cpu(wt_transpose, gemm_wt, CopyType::General, stream);
|
||||||
temps.push_back(gemm_wt);
|
temps.push_back(gemm_wt);
|
||||||
} else if (wt.dtype() != float32 || !wt.flags().row_contiguous) {
|
} else if (wt.dtype() != float32 || !wt.flags().row_contiguous) {
|
||||||
auto ctype =
|
auto ctype =
|
||||||
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
||||||
copy(wt, gemm_wt, ctype, stream);
|
copy_cpu(wt, gemm_wt, ctype, stream);
|
||||||
temps.push_back(gemm_wt);
|
temps.push_back(gemm_wt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,7 +991,7 @@ void explicit_gemm_conv_1D_cpu(
|
|||||||
|
|
||||||
// Copy results if needed
|
// Copy results if needed
|
||||||
if (out.dtype() != float32) {
|
if (out.dtype() != float32) {
|
||||||
copy_inplace(gemm_out, out, CopyType::Vector, stream);
|
copy_cpu_inplace(gemm_out, out, CopyType::Vector, stream);
|
||||||
}
|
}
|
||||||
encoder.add_temporaries(std::move(temps));
|
encoder.add_temporaries(std::move(temps));
|
||||||
}
|
}
|
||||||
@@ -1029,7 +1029,7 @@ void explicit_gemm_conv_2D_cpu(
|
|||||||
// Fill with zeros
|
// Fill with zeros
|
||||||
std::vector<array> temps;
|
std::vector<array> temps;
|
||||||
temps.push_back(array(0, conv_dtype));
|
temps.push_back(array(0, conv_dtype));
|
||||||
copy(temps.back(), in_padded, CopyType::Scalar, stream);
|
copy_cpu(temps.back(), in_padded, CopyType::Scalar, stream);
|
||||||
|
|
||||||
// Pick input slice from padded
|
// Pick input slice from padded
|
||||||
size_t data_offset = padding_lo[0] * in_padded.strides()[1] +
|
size_t data_offset = padding_lo[0] * in_padded.strides()[1] +
|
||||||
@@ -1044,7 +1044,7 @@ void explicit_gemm_conv_2D_cpu(
|
|||||||
temps.push_back(in_padded_slice);
|
temps.push_back(in_padded_slice);
|
||||||
|
|
||||||
// Copy input values into the slice
|
// Copy input values into the slice
|
||||||
copy_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
copy_cpu_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
||||||
|
|
||||||
// Make strided view
|
// Make strided view
|
||||||
Shape strided_shape = {N, oH, oW, wH, wW, C};
|
Shape strided_shape = {N, oH, oW, wH, wW, C};
|
||||||
@@ -1065,7 +1065,7 @@ void explicit_gemm_conv_2D_cpu(
|
|||||||
// Materialize strided view
|
// Materialize strided view
|
||||||
Shape strided_reshape = {N * oH * oW, wH * wW * C};
|
Shape strided_reshape = {N * oH * oW, wH * wW * C};
|
||||||
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
||||||
copy(in_strided_view, in_strided, CopyType::General, stream);
|
copy_cpu(in_strided_view, in_strided, CopyType::General, stream);
|
||||||
temps.push_back(in_strided);
|
temps.push_back(in_strided);
|
||||||
|
|
||||||
// Check wt dtype and prepare
|
// Check wt dtype and prepare
|
||||||
@@ -1076,7 +1076,7 @@ void explicit_gemm_conv_2D_cpu(
|
|||||||
auto ctype =
|
auto ctype =
|
||||||
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
||||||
copy(wt, gemm_wt, ctype, stream);
|
copy_cpu(wt, gemm_wt, ctype, stream);
|
||||||
temps.push_back(gemm_wt);
|
temps.push_back(gemm_wt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,7 +1116,7 @@ void explicit_gemm_conv_2D_cpu(
|
|||||||
|
|
||||||
// Copy results if needed
|
// Copy results if needed
|
||||||
if (out.dtype() != float32) {
|
if (out.dtype() != float32) {
|
||||||
copy_inplace(gemm_out, out, CopyType::Vector, stream);
|
copy_cpu_inplace(gemm_out, out, CopyType::Vector, stream);
|
||||||
}
|
}
|
||||||
encoder.add_temporaries(std::move(temps));
|
encoder.add_temporaries(std::move(temps));
|
||||||
}
|
}
|
||||||
@@ -1156,7 +1156,7 @@ void explicit_gemm_conv_ND_cpu(
|
|||||||
|
|
||||||
// Fill with zeros
|
// Fill with zeros
|
||||||
std::vector<array> temps = {array(0, conv_dtype)};
|
std::vector<array> temps = {array(0, conv_dtype)};
|
||||||
copy(temps.back(), in_padded, CopyType::Scalar, stream);
|
copy_cpu(temps.back(), in_padded, CopyType::Scalar, stream);
|
||||||
|
|
||||||
// Pick input slice from padded
|
// Pick input slice from padded
|
||||||
size_t data_offset = 0;
|
size_t data_offset = 0;
|
||||||
@@ -1173,7 +1173,7 @@ void explicit_gemm_conv_ND_cpu(
|
|||||||
data_offset);
|
data_offset);
|
||||||
|
|
||||||
// Copy input values into the slice
|
// Copy input values into the slice
|
||||||
copy_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
copy_cpu_inplace(in, in_padded_slice, CopyType::GeneralGeneral, stream);
|
||||||
temps.push_back(in_padded_slice);
|
temps.push_back(in_padded_slice);
|
||||||
|
|
||||||
// Make strided view
|
// Make strided view
|
||||||
@@ -1212,7 +1212,7 @@ void explicit_gemm_conv_ND_cpu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
array in_strided(strided_reshape, in_strided_view.dtype(), nullptr, {});
|
||||||
copy(in_strided_view, in_strided, CopyType::General, stream);
|
copy_cpu(in_strided_view, in_strided, CopyType::General, stream);
|
||||||
temps.push_back(in_strided);
|
temps.push_back(in_strided);
|
||||||
|
|
||||||
// Check wt dtype and prepare
|
// Check wt dtype and prepare
|
||||||
@@ -1223,13 +1223,13 @@ void explicit_gemm_conv_ND_cpu(
|
|||||||
auto ctype =
|
auto ctype =
|
||||||
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
wt.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
gemm_wt = array(wt.shape(), float32, nullptr, {});
|
||||||
copy(wt, gemm_wt, ctype, stream);
|
copy_cpu(wt, gemm_wt, ctype, stream);
|
||||||
temps.push_back(gemm_wt);
|
temps.push_back(gemm_wt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flip) {
|
if (flip) {
|
||||||
auto gemm_wt_ = array(gemm_wt.shape(), float32, nullptr, {});
|
auto gemm_wt_ = array(gemm_wt.shape(), float32, nullptr, {});
|
||||||
copy(gemm_wt, gemm_wt_, CopyType::Vector, stream);
|
copy_cpu(gemm_wt, gemm_wt_, CopyType::Vector, stream);
|
||||||
temps.push_back(gemm_wt_);
|
temps.push_back(gemm_wt_);
|
||||||
|
|
||||||
// Calculate the total size of the spatial dimensions
|
// Calculate the total size of the spatial dimensions
|
||||||
@@ -1284,7 +1284,7 @@ void explicit_gemm_conv_ND_cpu(
|
|||||||
|
|
||||||
// Copy results if needed
|
// Copy results if needed
|
||||||
if (out.dtype() != float32) {
|
if (out.dtype() != float32) {
|
||||||
copy_inplace(gemm_out, out, CopyType::Vector, stream);
|
copy_cpu_inplace(gemm_out, out, CopyType::Vector, stream);
|
||||||
}
|
}
|
||||||
encoder.add_temporaries(std::move(temps));
|
encoder.add_temporaries(std::move(temps));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,7 +295,11 @@ inline void copy_inplace_dispatch(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void copy_inplace(const array& src, array& dst, CopyType ctype, Stream stream) {
|
void copy_cpu_inplace(
|
||||||
|
const array& src,
|
||||||
|
array& dst,
|
||||||
|
CopyType ctype,
|
||||||
|
Stream stream) {
|
||||||
auto& encoder = cpu::get_command_encoder(stream);
|
auto& encoder = cpu::get_command_encoder(stream);
|
||||||
encoder.set_input_array(src);
|
encoder.set_input_array(src);
|
||||||
encoder.set_output_array(dst);
|
encoder.set_output_array(dst);
|
||||||
@@ -305,7 +309,7 @@ void copy_inplace(const array& src, array& dst, CopyType ctype, Stream stream) {
|
|||||||
ctype]() mutable { copy_inplace_dispatch(src, dst, ctype); });
|
ctype]() mutable { copy_inplace_dispatch(src, dst, ctype); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void copy(const array& src, array& dst, CopyType ctype, Stream stream) {
|
void copy_cpu(const array& src, array& dst, CopyType ctype, Stream stream) {
|
||||||
bool donated = set_copy_output_data(src, dst, ctype);
|
bool donated = set_copy_output_data(src, dst, ctype);
|
||||||
if (donated && src.dtype() == dst.dtype()) {
|
if (donated && src.dtype() == dst.dtype()) {
|
||||||
// If the output has the same type as the input then there is nothing to
|
// If the output has the same type as the input then there is nothing to
|
||||||
@@ -315,10 +319,10 @@ void copy(const array& src, array& dst, CopyType ctype, Stream stream) {
|
|||||||
if (ctype == CopyType::GeneralGeneral) {
|
if (ctype == CopyType::GeneralGeneral) {
|
||||||
ctype = CopyType::General;
|
ctype = CopyType::General;
|
||||||
}
|
}
|
||||||
copy_inplace(src, dst, ctype, stream);
|
copy_cpu_inplace(src, dst, ctype, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void copy_inplace(
|
void copy_cpu_inplace(
|
||||||
const array& src,
|
const array& src,
|
||||||
array& dst,
|
array& dst,
|
||||||
const Shape& data_shape,
|
const Shape& data_shape,
|
||||||
|
|||||||
@@ -10,10 +10,14 @@
|
|||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
void copy(const array& src, array& dst, CopyType ctype, Stream stream);
|
void copy_cpu(const array& src, array& dst, CopyType ctype, Stream stream);
|
||||||
void copy_inplace(const array& src, array& dst, CopyType ctype, Stream stream);
|
void copy_cpu_inplace(
|
||||||
|
const array& src,
|
||||||
|
array& dst,
|
||||||
|
CopyType ctype,
|
||||||
|
Stream stream);
|
||||||
|
|
||||||
void copy_inplace(
|
void copy_cpu_inplace(
|
||||||
const array& src,
|
const array& src,
|
||||||
array& dst,
|
array& dst,
|
||||||
const Shape& data_shape,
|
const Shape& data_shape,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ std::pair<array, bool> ensure_row_contiguous(const array& arr, Stream stream) {
|
|||||||
return {arr, false};
|
return {arr, false};
|
||||||
} else {
|
} else {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
copy(arr, arr_copy, CopyType::General, stream);
|
copy_cpu(arr, arr_copy, CopyType::General, stream);
|
||||||
return {arr_copy, true};
|
return {arr_copy, true};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -35,7 +35,7 @@ void AllReduce::eval_cpu(
|
|||||||
return in;
|
return in;
|
||||||
} else {
|
} else {
|
||||||
array arr_copy(in.shape(), in.dtype(), nullptr, {});
|
array arr_copy(in.shape(), in.dtype(), nullptr, {});
|
||||||
copy(in, arr_copy, CopyType::General, s);
|
copy_cpu(in, arr_copy, CopyType::General, s);
|
||||||
out.copy_shared_buffer(arr_copy);
|
out.copy_shared_buffer(arr_copy);
|
||||||
return arr_copy;
|
return arr_copy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ void Eig::eval_cpu(
|
|||||||
: array(a.shape(), complex64, nullptr, {});
|
: array(a.shape(), complex64, nullptr, {});
|
||||||
|
|
||||||
auto a_copy = array(a.shape(), a.dtype(), nullptr, {});
|
auto a_copy = array(a.shape(), a.dtype(), nullptr, {});
|
||||||
copy(
|
copy_cpu(
|
||||||
a,
|
a,
|
||||||
a_copy,
|
a_copy,
|
||||||
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ void Eigh::eval_cpu(
|
|||||||
|
|
||||||
values.set_data(allocator::malloc(values.nbytes()));
|
values.set_data(allocator::malloc(values.nbytes()));
|
||||||
|
|
||||||
copy(
|
copy_cpu(
|
||||||
a,
|
a,
|
||||||
vectors,
|
vectors,
|
||||||
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void Hadamard::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
if (in.flags().row_contiguous && in.is_donatable()) {
|
if (in.flags().row_contiguous && in.is_donatable()) {
|
||||||
out.copy_shared_buffer(in);
|
out.copy_shared_buffer(in);
|
||||||
} else {
|
} else {
|
||||||
copy(
|
copy_cpu(
|
||||||
in,
|
in,
|
||||||
out,
|
out,
|
||||||
in.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
in.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ void Scatter::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
// Copy src into out (copy allocates memory for out)
|
// Copy src into out (copy allocates memory for out)
|
||||||
auto ctype =
|
auto ctype =
|
||||||
src.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
src.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
copy(src, out, ctype, stream());
|
copy_cpu(src, out, ctype, stream());
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
std::vector<array> inds;
|
std::vector<array> inds;
|
||||||
@@ -686,7 +686,7 @@ void ScatterAxis::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
// Copy src into out (copy allocates memory for out)
|
// Copy src into out (copy allocates memory for out)
|
||||||
auto ctype =
|
auto ctype =
|
||||||
src.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
src.flags().row_contiguous ? CopyType::Vector : CopyType::General;
|
||||||
copy(src, out, ctype, stream());
|
copy_cpu(src, out, ctype, stream());
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
encoder.set_input_array(idx);
|
encoder.set_input_array(idx);
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ void inverse_impl(
|
|||||||
// (A⁻¹)ᵀ = (Aᵀ)⁻¹
|
// (A⁻¹)ᵀ = (Aᵀ)⁻¹
|
||||||
|
|
||||||
// The inverse is computed in place, so just copy the input to the output.
|
// The inverse is computed in place, so just copy the input to the output.
|
||||||
copy(
|
copy_cpu(
|
||||||
a,
|
a,
|
||||||
inv,
|
inv,
|
||||||
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void LogSumExp::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
||||||
copy(x, x_copy, CopyType::General, s);
|
copy_cpu(x, x_copy, CopyType::General, s);
|
||||||
encoder.add_temporary(x_copy);
|
encoder.add_temporary(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void luf_impl(
|
|||||||
strides[ndim - 1] = M;
|
strides[ndim - 1] = M;
|
||||||
strides[ndim - 2] = 1;
|
strides[ndim - 2] = 1;
|
||||||
lu.set_data(allocator::malloc(lu.nbytes()), lu.nbytes(), strides, flags);
|
lu.set_data(allocator::malloc(lu.nbytes()), lu.nbytes(), strides, flags);
|
||||||
copy_inplace(
|
copy_cpu_inplace(
|
||||||
a,
|
a,
|
||||||
lu,
|
lu,
|
||||||
a.shape(),
|
a.shape(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "mlx/backend/common/utils.h"
|
#include "mlx/backend/common/utils.h"
|
||||||
#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/gemm.h"
|
||||||
#include "mlx/backend/cpu/lapack.h"
|
#include "mlx/backend/cpu/lapack.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
|
|
||||||
@@ -52,6 +53,58 @@ inline void mask_matrix(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void segmented_mm(
|
||||||
|
const T* a,
|
||||||
|
const T* b,
|
||||||
|
const uint32_t* segments,
|
||||||
|
T* out,
|
||||||
|
bool a_transposed,
|
||||||
|
bool b_transposed,
|
||||||
|
size_t lda,
|
||||||
|
size_t ldb,
|
||||||
|
const Shape& a_shape,
|
||||||
|
const Strides& a_strides,
|
||||||
|
const Shape& b_shape,
|
||||||
|
const Strides& b_strides,
|
||||||
|
size_t num_segments,
|
||||||
|
const Shape& segments_shape,
|
||||||
|
const Strides& segments_strides) {
|
||||||
|
int ndim = a_shape.size();
|
||||||
|
Shape a_copy = a_shape;
|
||||||
|
Shape b_copy = b_shape;
|
||||||
|
int32_t M = a_copy[ndim - 2];
|
||||||
|
int32_t N = b_copy[ndim - 1];
|
||||||
|
for (int i = 0; i < num_segments; i++) {
|
||||||
|
uint32_t k_start =
|
||||||
|
segments[elem_to_loc(2 * i, segments_shape, segments_strides)];
|
||||||
|
uint32_t k_end =
|
||||||
|
segments[elem_to_loc(2 * i + 1, segments_shape, segments_strides)];
|
||||||
|
if (k_end <= k_start) {
|
||||||
|
std::fill_n(out + i * M * N, M * N, T(0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
a_copy[ndim - 1] = k_end - k_start;
|
||||||
|
b_copy[ndim - 2] = k_end - k_start;
|
||||||
|
matmul<T>(
|
||||||
|
a + k_start * a_strides[ndim - 1],
|
||||||
|
b + k_start * b_strides[ndim - 2],
|
||||||
|
out + i * M * N,
|
||||||
|
a_transposed,
|
||||||
|
b_transposed,
|
||||||
|
lda,
|
||||||
|
ldb,
|
||||||
|
N,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
1,
|
||||||
|
a_copy,
|
||||||
|
a_strides,
|
||||||
|
b_copy,
|
||||||
|
b_strides);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void BlockMaskedMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void BlockMaskedMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -71,20 +124,20 @@ void BlockMaskedMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
if (!expand_all && stx == arr.shape(-1) && sty == 1) {
|
if (!expand_all && stx == arr.shape(-1) && sty == 1) {
|
||||||
if (do_copy) {
|
if (do_copy) {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
copy(arr, arr_copy, CopyType::Vector, s);
|
copy_cpu(arr, arr_copy, CopyType::Vector, s);
|
||||||
return std::make_tuple(false, stx, arr_copy, true);
|
return std::make_tuple(false, stx, arr_copy, true);
|
||||||
}
|
}
|
||||||
return std::make_tuple(false, stx, arr, false);
|
return std::make_tuple(false, stx, arr, false);
|
||||||
} else if (!expand_all && stx == 1 && sty == arr.shape(-2)) {
|
} else if (!expand_all && stx == 1 && sty == arr.shape(-2)) {
|
||||||
if (do_copy) {
|
if (do_copy) {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
copy(arr, arr_copy, CopyType::Vector, s);
|
copy_cpu(arr, arr_copy, CopyType::Vector, s);
|
||||||
return std::make_tuple(true, sty, arr_copy, true);
|
return std::make_tuple(true, sty, arr_copy, true);
|
||||||
}
|
}
|
||||||
return std::make_tuple(true, sty, arr, false);
|
return std::make_tuple(true, sty, arr, false);
|
||||||
} else {
|
} else {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
copy(arr, arr_copy, CopyType::General, s);
|
copy_cpu(arr, arr_copy, CopyType::General, s);
|
||||||
int64_t stx = arr.shape(-1);
|
int64_t stx = arr.shape(-1);
|
||||||
return std::make_tuple(false, stx, arr_copy, true);
|
return std::make_tuple(false, stx, arr_copy, true);
|
||||||
}
|
}
|
||||||
@@ -333,7 +386,7 @@ void GatherMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
return std::make_tuple(true, sty, arr);
|
return std::make_tuple(true, sty, arr);
|
||||||
} else {
|
} else {
|
||||||
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy(arr, temps.back(), CopyType::General, s);
|
copy_cpu(arr, temps.back(), CopyType::General, s);
|
||||||
int64_t stx = arr.shape(-1);
|
int64_t stx = arr.shape(-1);
|
||||||
return std::make_tuple(false, stx, temps.back());
|
return std::make_tuple(false, stx, temps.back());
|
||||||
}
|
}
|
||||||
@@ -437,4 +490,121 @@ void GatherMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
encoder.add_temporaries(std::move(temps));
|
encoder.add_temporaries(std::move(temps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SegmentedMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
|
||||||
|
auto& s = stream();
|
||||||
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
|
auto check_transpose = [&s, &encoder](const array& x) {
|
||||||
|
auto stx = x.strides()[x.ndim() - 2];
|
||||||
|
auto sty = x.strides()[x.ndim() - 1];
|
||||||
|
if (stx == x.shape(-1) && sty == 1) {
|
||||||
|
return std::make_tuple(false, stx, x);
|
||||||
|
} else if (stx == 1 && sty == x.shape(-2)) {
|
||||||
|
return std::make_tuple(true, sty, x);
|
||||||
|
} else {
|
||||||
|
array xc(x.shape(), x.dtype(), nullptr, {});
|
||||||
|
copy_cpu(x, xc, CopyType::General, s);
|
||||||
|
encoder.add_temporary(xc);
|
||||||
|
int64_t stx = x.shape(-1);
|
||||||
|
return std::make_tuple(false, stx, xc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto [a_transposed, lda, a] = check_transpose(inputs[0]);
|
||||||
|
auto [b_transposed, ldb, b] = check_transpose(inputs[1]);
|
||||||
|
auto& segments = inputs[2];
|
||||||
|
|
||||||
|
encoder.set_input_array(a);
|
||||||
|
encoder.set_input_array(b);
|
||||||
|
encoder.set_input_array(segments);
|
||||||
|
encoder.set_output_array(out);
|
||||||
|
encoder.dispatch([a = array::unsafe_weak_copy(a),
|
||||||
|
b = array::unsafe_weak_copy(b),
|
||||||
|
segments = array::unsafe_weak_copy(segments),
|
||||||
|
out_ptr = out.data<void>(),
|
||||||
|
a_transposed = a_transposed,
|
||||||
|
b_transposed = b_transposed,
|
||||||
|
lda = lda,
|
||||||
|
ldb = ldb]() {
|
||||||
|
switch (a.dtype()) {
|
||||||
|
case float64:
|
||||||
|
segmented_mm<double>(
|
||||||
|
a.data<double>(),
|
||||||
|
b.data<double>(),
|
||||||
|
segments.data<uint32_t>(),
|
||||||
|
static_cast<double*>(out_ptr),
|
||||||
|
a_transposed,
|
||||||
|
b_transposed,
|
||||||
|
lda,
|
||||||
|
ldb,
|
||||||
|
a.shape(),
|
||||||
|
a.strides(),
|
||||||
|
b.shape(),
|
||||||
|
b.strides(),
|
||||||
|
segments.size() / 2,
|
||||||
|
segments.shape(),
|
||||||
|
segments.strides());
|
||||||
|
break;
|
||||||
|
case float32:
|
||||||
|
segmented_mm<float>(
|
||||||
|
a.data<float>(),
|
||||||
|
b.data<float>(),
|
||||||
|
segments.data<uint32_t>(),
|
||||||
|
static_cast<float*>(out_ptr),
|
||||||
|
a_transposed,
|
||||||
|
b_transposed,
|
||||||
|
lda,
|
||||||
|
ldb,
|
||||||
|
a.shape(),
|
||||||
|
a.strides(),
|
||||||
|
b.shape(),
|
||||||
|
b.strides(),
|
||||||
|
segments.size() / 2,
|
||||||
|
segments.shape(),
|
||||||
|
segments.strides());
|
||||||
|
break;
|
||||||
|
case float16:
|
||||||
|
segmented_mm<float16_t>(
|
||||||
|
a.data<float16_t>(),
|
||||||
|
b.data<float16_t>(),
|
||||||
|
segments.data<uint32_t>(),
|
||||||
|
static_cast<float16_t*>(out_ptr),
|
||||||
|
a_transposed,
|
||||||
|
b_transposed,
|
||||||
|
lda,
|
||||||
|
ldb,
|
||||||
|
a.shape(),
|
||||||
|
a.strides(),
|
||||||
|
b.shape(),
|
||||||
|
b.strides(),
|
||||||
|
segments.size() / 2,
|
||||||
|
segments.shape(),
|
||||||
|
segments.strides());
|
||||||
|
break;
|
||||||
|
case bfloat16:
|
||||||
|
segmented_mm<bfloat16_t>(
|
||||||
|
a.data<bfloat16_t>(),
|
||||||
|
b.data<bfloat16_t>(),
|
||||||
|
segments.data<uint32_t>(),
|
||||||
|
static_cast<bfloat16_t*>(out_ptr),
|
||||||
|
a_transposed,
|
||||||
|
b_transposed,
|
||||||
|
lda,
|
||||||
|
ldb,
|
||||||
|
a.shape(),
|
||||||
|
a.strides(),
|
||||||
|
b.shape(),
|
||||||
|
b.strides(),
|
||||||
|
segments.size() / 2,
|
||||||
|
segments.shape(),
|
||||||
|
segments.strides());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Segmented mm supports only real float types.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ void matmul_general(
|
|||||||
return std::make_tuple(true, sty, arr);
|
return std::make_tuple(true, sty, arr);
|
||||||
} else {
|
} else {
|
||||||
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy(arr, temps.back(), CopyType::General, stream);
|
copy_cpu(arr, temps.back(), CopyType::General, stream);
|
||||||
stx = arr.shape(-1);
|
stx = arr.shape(-1);
|
||||||
return std::make_tuple(false, stx, temps.back());
|
return std::make_tuple(false, stx, temps.back());
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ void AddMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
CopyType ctype = c.data_size() == 1
|
CopyType ctype = c.data_size() == 1
|
||||||
? CopyType::Scalar
|
? CopyType::Scalar
|
||||||
: (c.flags().row_contiguous ? CopyType::Vector : CopyType::General);
|
: (c.flags().row_contiguous ? CopyType::Vector : CopyType::General);
|
||||||
copy(c, out, ctype, stream());
|
copy_cpu(c, out, ctype, stream());
|
||||||
if (inputs[0].shape(-1) == 0) {
|
if (inputs[0].shape(-1) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ void reshape(const array& in, array& out) {
|
|||||||
auto [copy_necessary, out_strides] = prepare_reshape(in, out);
|
auto [copy_necessary, out_strides] = prepare_reshape(in, out);
|
||||||
if (copy_necessary) {
|
if (copy_necessary) {
|
||||||
out.set_data(allocator::malloc(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
copy_inplace(in, out, CopyType::General, out.primitive().stream());
|
copy_cpu_inplace(in, out, CopyType::General, out.primitive().stream());
|
||||||
} else {
|
} else {
|
||||||
shared_buffer_reshape(in, out_strides, out);
|
shared_buffer_reshape(in, out_strides, out);
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ void AsType::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
assert(inputs.size() == 1);
|
assert(inputs.size() == 1);
|
||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
CopyType ctype = in.flags().contiguous ? CopyType::Vector : CopyType::General;
|
CopyType ctype = in.flags().contiguous ? CopyType::Vector : CopyType::General;
|
||||||
copy(in, out, ctype, stream());
|
copy_cpu(in, out, ctype, stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Concatenate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void Concatenate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -198,7 +198,7 @@ void Concatenate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
size_t data_offset = strides[axis_] * sizes[i];
|
size_t data_offset = strides[axis_] * sizes[i];
|
||||||
out_slice.copy_shared_buffer(
|
out_slice.copy_shared_buffer(
|
||||||
out, strides, flags, out_slice.size(), data_offset);
|
out, strides, flags, out_slice.size(), data_offset);
|
||||||
copy_inplace(inputs[i], out_slice, CopyType::GeneralGeneral, stream());
|
copy_cpu_inplace(inputs[i], out_slice, CopyType::GeneralGeneral, stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ void Contiguous::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
(allow_col_major_ && in.flags().col_contiguous))) {
|
(allow_col_major_ && in.flags().col_contiguous))) {
|
||||||
out.copy_shared_buffer(in);
|
out.copy_shared_buffer(in);
|
||||||
} else {
|
} else {
|
||||||
copy(in, out, CopyType::General, stream());
|
copy_cpu(in, out, CopyType::General, stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ void Full::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
} else {
|
} else {
|
||||||
ctype = CopyType::General;
|
ctype = CopyType::General;
|
||||||
}
|
}
|
||||||
copy(in, out, ctype, stream());
|
copy_cpu(in, out, ctype, stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Pad::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void Pad::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -251,7 +251,7 @@ void Pad::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
assert(val.dtype() == in.dtype() && in.dtype() == out.dtype());
|
assert(val.dtype() == in.dtype() && in.dtype() == out.dtype());
|
||||||
|
|
||||||
// Fill output with val
|
// Fill output with val
|
||||||
copy(val, out, CopyType::Scalar, stream());
|
copy_cpu(val, out, CopyType::Scalar, stream());
|
||||||
|
|
||||||
// Find offset for start of input values
|
// Find offset for start of input values
|
||||||
size_t data_offset = 0;
|
size_t data_offset = 0;
|
||||||
@@ -266,7 +266,7 @@ void Pad::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
out, out.strides(), out.flags(), out_slice.size(), data_offset);
|
out, out.strides(), out.flags(), out_slice.size(), data_offset);
|
||||||
|
|
||||||
// Copy input values into the slice
|
// Copy input values into the slice
|
||||||
copy_inplace(in, out_slice, CopyType::GeneralGeneral, stream());
|
copy_cpu_inplace(in, out_slice, CopyType::GeneralGeneral, stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RandomBits::eval_cpu(const std::vector<array>& inputs, array& out) {
|
void RandomBits::eval_cpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -340,7 +340,7 @@ void DynamicSlice::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
out.set_data(allocator::malloc(out.nbytes()));
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
auto [in_offset, donated] =
|
auto [in_offset, donated] =
|
||||||
compute_dynamic_offset(inputs[1], in.strides(), axes_, stream());
|
compute_dynamic_offset(inputs[1], in.strides(), axes_, stream());
|
||||||
copy_inplace(
|
copy_cpu_inplace(
|
||||||
/* const array& src = */ in,
|
/* const array& src = */ in,
|
||||||
/* array& dst = */ out,
|
/* array& dst = */ out,
|
||||||
/* const Shape& data_shape = */ out.shape(),
|
/* const Shape& data_shape = */ out.shape(),
|
||||||
@@ -372,11 +372,11 @@ void DynamicSliceUpdate::eval_cpu(
|
|||||||
auto ctype = in.flags().contiguous && in.size() == in.data_size()
|
auto ctype = in.flags().contiguous && in.size() == in.data_size()
|
||||||
? CopyType::Vector
|
? CopyType::Vector
|
||||||
: CopyType::General;
|
: CopyType::General;
|
||||||
copy(in, out, in.data_size() == 1 ? CopyType::Scalar : ctype, stream());
|
copy_cpu(in, out, in.data_size() == 1 ? CopyType::Scalar : ctype, stream());
|
||||||
|
|
||||||
auto [out_offset, donated] =
|
auto [out_offset, donated] =
|
||||||
compute_dynamic_offset(inputs[2], out.strides(), axes_, stream());
|
compute_dynamic_offset(inputs[2], out.strides(), axes_, stream());
|
||||||
copy_inplace(
|
copy_cpu_inplace(
|
||||||
/* const array& src = */ upd,
|
/* const array& src = */ upd,
|
||||||
/* array& dst = */ out,
|
/* array& dst = */ out,
|
||||||
/* const std::vector<int>& data_shape = */ upd.shape(),
|
/* const std::vector<int>& data_shape = */ upd.shape(),
|
||||||
@@ -412,14 +412,14 @@ void SliceUpdate::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto ctype = in.flags().contiguous && in.size() == in.data_size()
|
auto ctype = in.flags().contiguous && in.size() == in.data_size()
|
||||||
? CopyType::Vector
|
? CopyType::Vector
|
||||||
: CopyType::General;
|
: CopyType::General;
|
||||||
copy(in, out, in.data_size() == 1 ? CopyType::Scalar : ctype, stream());
|
copy_cpu(in, out, in.data_size() == 1 ? CopyType::Scalar : ctype, stream());
|
||||||
|
|
||||||
// Calculate out strides, initial offset and if copy needs to be made
|
// Calculate out strides, initial offset and if copy needs to be made
|
||||||
auto [data_offset, out_strides] =
|
auto [data_offset, out_strides] =
|
||||||
prepare_slice(out, start_indices_, strides_);
|
prepare_slice(out, start_indices_, strides_);
|
||||||
|
|
||||||
// Do copy
|
// Do copy
|
||||||
copy_inplace(
|
copy_cpu_inplace(
|
||||||
/* const array& src = */ upd,
|
/* const array& src = */ upd,
|
||||||
/* array& dst = */ out,
|
/* array& dst = */ out,
|
||||||
/* const std::vector<int>& data_shape = */ upd.shape(),
|
/* const std::vector<int>& data_shape = */ upd.shape(),
|
||||||
@@ -456,9 +456,9 @@ void View::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
if (in.dtype() == bool_) {
|
if (in.dtype() == bool_) {
|
||||||
auto in_tmp = array(in.shape(), uint8, nullptr, {});
|
auto in_tmp = array(in.shape(), uint8, nullptr, {});
|
||||||
in_tmp.copy_shared_buffer(in);
|
in_tmp.copy_shared_buffer(in);
|
||||||
copy_inplace(in_tmp, tmp, CopyType::General, stream());
|
copy_cpu_inplace(in_tmp, tmp, CopyType::General, stream());
|
||||||
} else {
|
} else {
|
||||||
copy_inplace(in, tmp, CopyType::General, stream());
|
copy_cpu_inplace(in, tmp, CopyType::General, stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flags = out.flags();
|
auto flags = out.flags();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ void qrf_impl(const array& a, array& q, array& r, Stream stream) {
|
|||||||
strides[in.ndim() - 2] = 1;
|
strides[in.ndim() - 2] = 1;
|
||||||
strides[in.ndim() - 1] = M;
|
strides[in.ndim() - 1] = M;
|
||||||
in.set_data(allocator::malloc(in.nbytes()), in.nbytes(), strides, flags);
|
in.set_data(allocator::malloc(in.nbytes()), in.nbytes(), strides, flags);
|
||||||
copy_inplace(a, in, CopyType::GeneralGeneral, stream);
|
copy_cpu_inplace(a, in, CopyType::GeneralGeneral, stream);
|
||||||
auto& encoder = cpu::get_command_encoder(stream);
|
auto& encoder = cpu::get_command_encoder(stream);
|
||||||
q.set_data(allocator::malloc(q.nbytes()));
|
q.set_data(allocator::malloc(q.nbytes()));
|
||||||
r.set_data(allocator::malloc(r.nbytes()));
|
r.set_data(allocator::malloc(r.nbytes()));
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ void QuantizedMatmul::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
return arr;
|
return arr;
|
||||||
} else {
|
} else {
|
||||||
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy(arr, temps.back(), CopyType::General, s);
|
copy_cpu(arr, temps.back(), CopyType::General, s);
|
||||||
return temps.back();
|
return temps.back();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -579,7 +579,7 @@ void GatherQMM::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
return arr;
|
return arr;
|
||||||
} else {
|
} else {
|
||||||
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
temps.push_back(array(arr.shape(), arr.dtype(), nullptr, {}));
|
||||||
copy(arr, temps.back(), CopyType::General, s);
|
copy_cpu(arr, temps.back(), CopyType::General, s);
|
||||||
return temps.back();
|
return temps.back();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -713,7 +713,7 @@ void fast::AffineQuantize::eval_cpu(
|
|||||||
return std::make_pair(arr, false);
|
return std::make_pair(arr, false);
|
||||||
} else {
|
} else {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
copy(arr, arr_copy, CopyType::General, s);
|
copy_cpu(arr, arr_copy, CopyType::General, s);
|
||||||
return std::make_pair(arr_copy, true);
|
return std::make_pair(arr_copy, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -325,7 +325,15 @@ struct MaxReduce {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <int N, typename T>
|
template <int N, typename T>
|
||||||
T operator()(simd::Simd<T, N> x) {
|
std::enable_if_t<std::is_integral_v<T>, T> operator()(simd::Simd<T, N> x) {
|
||||||
|
return simd::max(x);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int N, typename T>
|
||||||
|
std::enable_if_t<!std::is_integral_v<T>, T> operator()(simd::Simd<T, N> x) {
|
||||||
|
if (simd::any(x != x)) {
|
||||||
|
return static_cast<T>(NAN);
|
||||||
|
}
|
||||||
return simd::max(x);
|
return simd::max(x);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -342,7 +350,15 @@ struct MinReduce {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <int N, typename T>
|
template <int N, typename T>
|
||||||
T operator()(simd::Simd<T, N> x) {
|
std::enable_if_t<std::is_integral_v<T>, T> operator()(simd::Simd<T, N> x) {
|
||||||
|
return simd::min(x);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int N, typename T>
|
||||||
|
std::enable_if_t<!std::is_integral_v<T>, T> operator()(simd::Simd<T, N> x) {
|
||||||
|
if (simd::any(x != x)) {
|
||||||
|
return static_cast<T>(NAN);
|
||||||
|
}
|
||||||
return simd::min(x);
|
return simd::min(x);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -527,10 +543,10 @@ void Reduce::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
reduce_dispatch_min_max<uint64_t>(in, out, reduce_type_, axes_);
|
reduce_dispatch_min_max<uint64_t>(in, out, reduce_type_, axes_);
|
||||||
break;
|
break;
|
||||||
case int8:
|
case int8:
|
||||||
reduce_dispatch_min_max<uint8_t>(in, out, reduce_type_, axes_);
|
reduce_dispatch_min_max<int8_t>(in, out, reduce_type_, axes_);
|
||||||
break;
|
break;
|
||||||
case int16:
|
case int16:
|
||||||
reduce_dispatch_min_max<uint16_t>(in, out, reduce_type_, axes_);
|
reduce_dispatch_min_max<int16_t>(in, out, reduce_type_, axes_);
|
||||||
break;
|
break;
|
||||||
case int32:
|
case int32:
|
||||||
reduce_dispatch_min_max<int32_t>(in, out, reduce_type_, axes_);
|
reduce_dispatch_min_max<int32_t>(in, out, reduce_type_, axes_);
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ void Scan::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto in = inputs[0];
|
auto in = inputs[0];
|
||||||
if (!in.flags().row_contiguous) {
|
if (!in.flags().row_contiguous) {
|
||||||
array arr_copy(in.shape(), in.dtype(), nullptr, {});
|
array arr_copy(in.shape(), in.dtype(), nullptr, {});
|
||||||
copy(in, arr_copy, CopyType::General, stream());
|
copy_cpu(in, arr_copy, CopyType::General, stream());
|
||||||
in = arr_copy;
|
in = arr_copy;
|
||||||
encoder.add_temporary(arr_copy);
|
encoder.add_temporary(arr_copy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ void Softmax::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
array x_copy(x.shape(), x.dtype(), nullptr, {});
|
array x_copy(x.shape(), x.dtype(), nullptr, {});
|
||||||
copy(x, x_copy, CopyType::General, s);
|
copy_cpu(x, x_copy, CopyType::General, s);
|
||||||
out.copy_shared_buffer(x_copy);
|
out.copy_shared_buffer(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,8 +334,10 @@ void Sort::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
|
|
||||||
// Copy input to output
|
// Copy input to output
|
||||||
CopyType ctype = in.flags().contiguous ? CopyType::Vector : CopyType::General;
|
CopyType ctype = (in.flags().contiguous && in.strides()[axis_] != 0)
|
||||||
copy(in, out, ctype, stream());
|
? CopyType::Vector
|
||||||
|
: CopyType::General;
|
||||||
|
copy_cpu(in, out, ctype, stream());
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
@@ -426,8 +428,10 @@ void Partition::eval_cpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
|
|
||||||
// Copy input to output
|
// Copy input to output
|
||||||
CopyType ctype = in.flags().contiguous ? CopyType::Vector : CopyType::General;
|
CopyType ctype = (in.flags().contiguous && in.strides()[axis_] != 0)
|
||||||
copy(in, out, ctype, stream());
|
? CopyType::Vector
|
||||||
|
: CopyType::General;
|
||||||
|
copy_cpu(in, out, ctype, stream());
|
||||||
|
|
||||||
auto& encoder = cpu::get_command_encoder(stream());
|
auto& encoder = cpu::get_command_encoder(stream());
|
||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void svd_impl(
|
|||||||
|
|
||||||
// lapack clobbers the input, so we have to make a copy.
|
// lapack clobbers the input, so we have to make a copy.
|
||||||
array in(a.shape(), a.dtype(), nullptr, {});
|
array in(a.shape(), a.dtype(), nullptr, {});
|
||||||
copy(
|
copy_cpu(
|
||||||
a,
|
a,
|
||||||
in,
|
in,
|
||||||
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
a.flags().row_contiguous ? CopyType::Vector : CopyType::General,
|
||||||
|
|||||||
@@ -35,12 +35,16 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/reduce/row_reduce.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/reduce/row_reduce.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/rms_norm.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/rms_norm.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/rope.cu
|
${CMAKE_CURRENT_SOURCE_DIR}/rope.cu
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/scan.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/slicing.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/slicing.cpp
|
||||||
${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}/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/qmm.cu
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/quantized/quantized.cu
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/worker.cpp)
|
${CMAKE_CURRENT_SOURCE_DIR}/worker.cpp)
|
||||||
|
|
||||||
target_compile_definitions(mlx PRIVATE MLX_USE_CUDA)
|
target_compile_definitions(mlx PRIVATE MLX_USE_CUDA)
|
||||||
@@ -67,6 +71,11 @@ target_include_directories(mlx PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/gen")
|
|||||||
target_compile_options(mlx
|
target_compile_options(mlx
|
||||||
PRIVATE "$<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda>")
|
PRIVATE "$<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda>")
|
||||||
|
|
||||||
|
# Enable calling host constexpr functions from device. This is needed because
|
||||||
|
# the constexpr version of isnan is host only.
|
||||||
|
target_compile_options(
|
||||||
|
mlx PRIVATE "$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>")
|
||||||
|
|
||||||
# CUDA 12.8 emits warning #20280-D for copy kernels which is a false positive.
|
# CUDA 12.8 emits warning #20280-D for copy kernels which is a false positive.
|
||||||
# Explicitly pass this flag to suppress the warning, it is safe to set it to
|
# Explicitly pass this flag to suppress the warning, it is safe to set it to
|
||||||
# true but the warning wouldn't be suppressed.
|
# true but the warning wouldn't be suppressed.
|
||||||
@@ -83,7 +92,7 @@ target_compile_options(
|
|||||||
# Compute capability 7 is required for synchronization between CPU/GPU with
|
# Compute capability 7 is required for synchronization between CPU/GPU with
|
||||||
# managed memory. TODO: Add more architectures for potential performance gain.
|
# managed memory. TODO: Add more architectures for potential performance gain.
|
||||||
set(MLX_CUDA_ARCHITECTURES
|
set(MLX_CUDA_ARCHITECTURES
|
||||||
"70;80"
|
"80"
|
||||||
CACHE STRING "CUDA architectures")
|
CACHE STRING "CUDA architectures")
|
||||||
message(STATUS "CUDA architectures: ${MLX_CUDA_ARCHITECTURES}")
|
message(STATUS "CUDA architectures: ${MLX_CUDA_ARCHITECTURES}")
|
||||||
set_target_properties(mlx PROPERTIES CUDA_ARCHITECTURES
|
set_target_properties(mlx PROPERTIES CUDA_ARCHITECTURES
|
||||||
@@ -119,3 +128,16 @@ target_link_libraries(mlx PRIVATE CUDA::nvrtc CUDA::cuda_driver)
|
|||||||
# 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(DIRECTORY ${cccl_SOURCE_DIR}/include/cuda
|
||||||
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cccl)
|
||||||
|
|
||||||
|
# Make Thunderkittens available
|
||||||
|
FetchContent_Declare(
|
||||||
|
kittens
|
||||||
|
GIT_REPOSITORY https://github.com/HazyResearch/ThunderKittens.git
|
||||||
|
GIT_TAG aaab847f430ed313ed466e64b25b9177babd1db8
|
||||||
|
GIT_SHALLOW TRUE)
|
||||||
|
FetchContent_MakeAvailable(kittens)
|
||||||
|
target_include_directories(mlx BEFORE PRIVATE "${kittens_SOURCE_DIR}/include")
|
||||||
|
|||||||
@@ -17,6 +17,52 @@ namespace cu {
|
|||||||
|
|
||||||
constexpr int page_size = 16384;
|
constexpr int page_size = 16384;
|
||||||
|
|
||||||
|
// Any allocations smaller than this will try to use the small pool
|
||||||
|
constexpr int small_block_size = 8;
|
||||||
|
|
||||||
|
// The small pool size in bytes. This should be a multiple of the host page
|
||||||
|
// size and small_block_size.
|
||||||
|
constexpr int small_pool_size = 4 * page_size;
|
||||||
|
|
||||||
|
SmallSizePool::SmallSizePool() {
|
||||||
|
CHECK_CUDA_ERROR(cudaMallocManaged(&buffer_, small_pool_size));
|
||||||
|
end_ = reinterpret_cast<void*>(
|
||||||
|
reinterpret_cast<char*>(buffer_) + small_pool_size);
|
||||||
|
next_free_ = reinterpret_cast<Block*>(buffer_);
|
||||||
|
|
||||||
|
auto num_blocks = small_pool_size / small_block_size;
|
||||||
|
auto curr = next_free_;
|
||||||
|
for (size_t i = 0; i < num_blocks - 1; ++i) {
|
||||||
|
curr->next = reinterpret_cast<Block*>(
|
||||||
|
reinterpret_cast<char*>(buffer_) + (i + 1) * small_block_size);
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
curr->next = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallSizePool::~SmallSizePool() {
|
||||||
|
CHECK_CUDA_ERROR(cudaFree(buffer_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SmallSizePool::malloc() {
|
||||||
|
if (next_free_ == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Block* b = next_free_;
|
||||||
|
next_free_ = next_free_->next;
|
||||||
|
return static_cast<void*>(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SmallSizePool::free(void* p) {
|
||||||
|
auto b = static_cast<Block*>(p);
|
||||||
|
b->next = next_free_;
|
||||||
|
next_free_ = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SmallSizePool::in_pool(void* p) {
|
||||||
|
return (p >= buffer_) && (p < end_);
|
||||||
|
}
|
||||||
|
|
||||||
CudaAllocator::CudaAllocator()
|
CudaAllocator::CudaAllocator()
|
||||||
: buffer_cache_(
|
: buffer_cache_(
|
||||||
page_size,
|
page_size,
|
||||||
@@ -36,7 +82,9 @@ 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_);
|
||||||
if (size < page_size) {
|
if (size <= small_block_size) {
|
||||||
|
size = 8;
|
||||||
|
} else if (size < page_size) {
|
||||||
size = next_power_of_2(size);
|
size = next_power_of_2(size);
|
||||||
} else {
|
} else {
|
||||||
size = page_size * ((size + page_size - 1) / page_size);
|
size = page_size * ((size + page_size - 1) / page_size);
|
||||||
@@ -53,11 +101,19 @@ Buffer CudaAllocator::malloc(size_t size) {
|
|||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
buf = new CudaBuffer{nullptr, size};
|
buf = new CudaBuffer{nullptr, size};
|
||||||
cudaError_t err = cudaMallocManaged(&buf->data, size);
|
|
||||||
if (err != cudaSuccess && err != cudaErrorMemoryAllocation) {
|
// Try the scalar pool first
|
||||||
throw std::runtime_error(fmt::format(
|
if (size <= small_block_size) {
|
||||||
"cudaMallocManaged failed: {}.", cudaGetErrorString(err)));
|
buf->data = scalar_pool_.malloc();
|
||||||
}
|
}
|
||||||
|
if (!buf->data) {
|
||||||
|
cudaError_t err = cudaMallocManaged(&buf->data, size);
|
||||||
|
if (err != cudaSuccess && err != cudaErrorMemoryAllocation) {
|
||||||
|
throw std::runtime_error(fmt::format(
|
||||||
|
"cudaMallocManaged failed: {}.", cudaGetErrorString(err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
}
|
}
|
||||||
active_memory_ += size;
|
active_memory_ += size;
|
||||||
@@ -116,7 +172,11 @@ void CudaAllocator::cuda_free(void* buf) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cudaFree(buf);
|
if (scalar_pool_.in_pool(buf)) {
|
||||||
|
scalar_pool_.free(buf);
|
||||||
|
} else {
|
||||||
|
cudaFree(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t CudaAllocator::get_active_memory() const {
|
size_t CudaAllocator::get_active_memory() const {
|
||||||
|
|||||||
@@ -22,6 +22,28 @@ struct CudaBuffer {
|
|||||||
size_t size;
|
size_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SmallSizePool {
|
||||||
|
private:
|
||||||
|
struct Block {
|
||||||
|
Block* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
void* buffer_{nullptr};
|
||||||
|
Block* next_free_{nullptr};
|
||||||
|
void* end_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
SmallSizePool();
|
||||||
|
~SmallSizePool();
|
||||||
|
|
||||||
|
SmallSizePool(const SmallSizePool&) = delete;
|
||||||
|
SmallSizePool& operator=(const SmallSizePool&) = delete;
|
||||||
|
|
||||||
|
void* malloc();
|
||||||
|
void free(void* p);
|
||||||
|
bool in_pool(void* p);
|
||||||
|
};
|
||||||
|
|
||||||
class CudaAllocator : public allocator::Allocator {
|
class CudaAllocator : public allocator::Allocator {
|
||||||
public:
|
public:
|
||||||
Buffer malloc(size_t size) override;
|
Buffer malloc(size_t size) override;
|
||||||
@@ -60,6 +82,7 @@ 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};
|
||||||
|
SmallSizePool scalar_pool_;
|
||||||
};
|
};
|
||||||
|
|
||||||
CudaAllocator& allocator();
|
CudaAllocator& allocator();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
// Copyright © 2025 Apple Inc.
|
||||||
#include "mlx/backend/common/utils.h"
|
#include "mlx/backend/common/utils.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/device/fp16_math.cuh"
|
||||||
#include "mlx/backend/cuda/iterators/strided_iterator.cuh"
|
#include "mlx/backend/cuda/iterators/strided_iterator.cuh"
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
#include "mlx/dtype_utils.h"
|
#include "mlx/dtype_utils.h"
|
||||||
@@ -165,6 +166,7 @@ void ArgReduce::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
in.data<T>(),
|
in.data<T>(),
|
||||||
out.data<uint32_t>(),
|
out.data<uint32_t>(),
|
||||||
out.size(),
|
out.size(),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "mlx/backend/common/binary.h"
|
#include "mlx/backend/common/binary.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
#include "mlx/backend/cuda/device/binary_ops.cuh"
|
#include "mlx/backend/cuda/device/binary_ops.cuh"
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
#include "mlx/dtype_utils.h"
|
#include "mlx/dtype_utils.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
@@ -17,35 +16,86 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void binary_ss(const In* a, const In* b, Out* out, IdxT size) {
|
__global__ void binary_ss(const In* a, const In* b, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(a[0], b[0]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (int i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(a[0], b[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(a[0], b[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void binary_sv(const In* a, const In* b, Out* out, IdxT size) {
|
__global__ void binary_sv(const In* a, const In* b, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(a[0], b[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(a[0], b[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto b_vec = load_vector<N_READS>(b, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(a[0], b_vec.val[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void binary_vs(const In* a, const In* b, Out* out, IdxT size) {
|
__global__ void binary_vs(const In* a, const In* b, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(a[index], b[0]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(a[i], b[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto a_vec = load_vector<N_READS>(a, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(a_vec.val[i], b[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void binary_vv(const In* a, const In* b, Out* out, IdxT size) {
|
__global__ void binary_vv(const In* a, const In* b, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(a[index], b[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(a[i], b[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto a_vec = load_vector<N_READS>(a, index);
|
||||||
|
auto b_vec = load_vector<N_READS>(b, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(a_vec.val[i], b_vec.val[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +176,7 @@ template <typename Op>
|
|||||||
void binary_op_gpu_inplace(
|
void binary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
std::string_view op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
assert(inputs.size() > 1);
|
assert(inputs.size() > 1);
|
||||||
const auto& a = inputs[0];
|
const auto& a = inputs[0];
|
||||||
@@ -169,6 +219,7 @@ void binary_op_gpu_inplace(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out.data<OutType>(),
|
out.data<OutType>(),
|
||||||
@@ -185,6 +236,7 @@ void binary_op_gpu_inplace(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out.data<OutType>(),
|
out.data<OutType>(),
|
||||||
@@ -196,22 +248,30 @@ void binary_op_gpu_inplace(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dispatch_bool(out.data_size() > INT32_MAX, [&](auto large) {
|
dispatch_bool(out.data_size() > UINT32_MAX, [&](auto large) {
|
||||||
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
||||||
auto kernel = cu::binary_ss<Op, InType, OutType, IdxT>;
|
// TODO: Choose optimized value based on type size.
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
auto kernel = cu::binary_ss<Op, InType, OutType, IdxT, N_READS>;
|
||||||
if (bopt == BinaryOpType::ScalarVector) {
|
if (bopt == BinaryOpType::ScalarVector) {
|
||||||
kernel = cu::binary_sv<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_sv<Op, InType, OutType, IdxT, N_READS>;
|
||||||
} else if (bopt == BinaryOpType::VectorScalar) {
|
} else if (bopt == BinaryOpType::VectorScalar) {
|
||||||
kernel = cu::binary_vs<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_vs<Op, InType, OutType, IdxT, N_READS>;
|
||||||
} else if (bopt == BinaryOpType::VectorVector) {
|
} else if (bopt == BinaryOpType::VectorVector) {
|
||||||
kernel = cu::binary_vv<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_vv<Op, InType, OutType, IdxT, N_READS>;
|
||||||
}
|
}
|
||||||
auto [num_blocks, block_dims] = get_launch_args(
|
auto [num_blocks, block_dims] = get_launch_args(
|
||||||
kernel, out.data_size(), out.shape(), out.strides(), large());
|
kernel,
|
||||||
|
out.data_size(),
|
||||||
|
out.shape(),
|
||||||
|
out.strides(),
|
||||||
|
large(),
|
||||||
|
N_READS);
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out.data<OutType>(),
|
out.data<OutType>(),
|
||||||
@@ -233,7 +293,7 @@ template <typename Op>
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
std::string_view op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
auto& a = inputs[0];
|
auto& a = inputs[0];
|
||||||
auto& b = inputs[1];
|
auto& b = inputs[1];
|
||||||
@@ -242,11 +302,11 @@ void binary_op_gpu(
|
|||||||
binary_op_gpu_inplace<Op>(inputs, out, op, s);
|
binary_op_gpu_inplace<Op>(inputs, out, op, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BINARY_GPU(func) \
|
#define BINARY_GPU(func) \
|
||||||
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
||||||
nvtx3::scoped_range r(#func "::eval_gpu"); \
|
nvtx3::scoped_range r(#func "::eval_gpu"); \
|
||||||
auto& s = out.primitive().stream(); \
|
auto& s = out.primitive().stream(); \
|
||||||
binary_op_gpu<cu::func>(inputs, out, get_primitive_string(this), s); \
|
binary_op_gpu<cu::func>(inputs, out, name(), s); \
|
||||||
}
|
}
|
||||||
|
|
||||||
BINARY_GPU(Add)
|
BINARY_GPU(Add)
|
||||||
@@ -270,33 +330,31 @@ BINARY_GPU(Subtract)
|
|||||||
void Equal::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void Equal::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
nvtx3::scoped_range r("Equal::eval_gpu");
|
nvtx3::scoped_range r("Equal::eval_gpu");
|
||||||
auto& s = out.primitive().stream();
|
auto& s = out.primitive().stream();
|
||||||
auto op = get_primitive_string(this);
|
|
||||||
if (equal_nan_) {
|
if (equal_nan_) {
|
||||||
binary_op_gpu<cu::NaNEqual>(inputs, out, op, s);
|
binary_op_gpu<cu::NaNEqual>(inputs, out, name(), s);
|
||||||
} else {
|
} else {
|
||||||
binary_op_gpu<cu::Equal>(inputs, out, op, s);
|
binary_op_gpu<cu::Equal>(inputs, out, name(), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
nvtx3::scoped_range r("BitwiseBinary::eval_gpu");
|
nvtx3::scoped_range r("BitwiseBinary::eval_gpu");
|
||||||
auto& s = out.primitive().stream();
|
auto& s = out.primitive().stream();
|
||||||
auto op = get_primitive_string(this);
|
|
||||||
switch (op_) {
|
switch (op_) {
|
||||||
case BitwiseBinary::And:
|
case BitwiseBinary::And:
|
||||||
binary_op_gpu<cu::BitwiseAnd>(inputs, out, op, s);
|
binary_op_gpu<cu::BitwiseAnd>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::Or:
|
case BitwiseBinary::Or:
|
||||||
binary_op_gpu<cu::BitwiseOr>(inputs, out, op, s);
|
binary_op_gpu<cu::BitwiseOr>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::Xor:
|
case BitwiseBinary::Xor:
|
||||||
binary_op_gpu<cu::BitwiseXor>(inputs, out, op, s);
|
binary_op_gpu<cu::BitwiseXor>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::LeftShift:
|
case BitwiseBinary::LeftShift:
|
||||||
binary_op_gpu<cu::LeftShift>(inputs, out, op, s);
|
binary_op_gpu<cu::LeftShift>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::RightShift:
|
case BitwiseBinary::RightShift:
|
||||||
binary_op_gpu<cu::RightShift>(inputs, out, op, s);
|
binary_op_gpu<cu::RightShift>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "mlx/backend/common/binary.h"
|
#include "mlx/backend/common/binary.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
#include "mlx/backend/cuda/device/binary_ops.cuh"
|
#include "mlx/backend/cuda/device/binary_ops.cuh"
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
#include "mlx/dtype_utils.h"
|
#include "mlx/dtype_utils.h"
|
||||||
#include "mlx/primitives.h"
|
#include "mlx/primitives.h"
|
||||||
@@ -17,52 +16,119 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void
|
__global__ void
|
||||||
binary_ss(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
binary_two_ss(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
auto out = Op{}(a[0], b[0]);
|
if ((index + 1) * N_READS > size) {
|
||||||
out_a[0] = out[0];
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
out_b[0] = out[1];
|
auto out = Op{}(a[0], b[0]);
|
||||||
|
out_a[i] = out[0];
|
||||||
|
out_b[i] = out[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AlignedVector<Out, N_READS> out_a_vec;
|
||||||
|
AlignedVector<Out, N_READS> out_b_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
auto out = Op{}(a[0], b[0]);
|
||||||
|
out_a_vec.val[i] = out[0];
|
||||||
|
out_b_vec.val[i] = out[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out_a, index, out_a_vec);
|
||||||
|
store_vector<N_READS>(out_b, index, out_b_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void
|
__global__ void
|
||||||
binary_sv(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
binary_two_sv(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
auto out = Op{}(a[0], b[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
out_a[index] = out[0];
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
out_b[index] = out[1];
|
auto out = Op{}(a[0], b[i]);
|
||||||
|
out_a[i] = out[0];
|
||||||
|
out_b[i] = out[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto b_vec = load_vector<N_READS>(b, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_a_vec;
|
||||||
|
AlignedVector<Out, N_READS> out_b_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
auto out = Op{}(a[0], b_vec.val[i]);
|
||||||
|
out_a_vec.val[i] = out[0];
|
||||||
|
out_b_vec.val[i] = out[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out_a, index, out_a_vec);
|
||||||
|
store_vector<N_READS>(out_b, index, out_b_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void
|
__global__ void
|
||||||
binary_vs(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
binary_two_vs(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
auto out = Op{}(a[index], b[0]);
|
if ((index + 1) * N_READS > size) {
|
||||||
out_a[index] = out[0];
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
out_b[index] = out[1];
|
auto out = Op{}(a[i], b[0]);
|
||||||
|
out_a[i] = out[0];
|
||||||
|
out_b[i] = out[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto a_vec = load_vector<N_READS>(a, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_a_vec;
|
||||||
|
AlignedVector<Out, N_READS> out_b_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
auto out = Op{}(a_vec.val[i], b[0]);
|
||||||
|
out_a_vec.val[i] = out[0];
|
||||||
|
out_b_vec.val[i] = out[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out_a, index, out_a_vec);
|
||||||
|
store_vector<N_READS>(out_b, index, out_b_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void
|
__global__ void
|
||||||
binary_vv(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
binary_two_vv(const In* a, const In* b, Out* out_a, Out* out_b, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
auto out = Op{}(a[index], b[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
out_a[index] = out[0];
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
out_b[index] = out[1];
|
auto out = Op{}(a[i], b[i]);
|
||||||
|
out_a[i] = out[0];
|
||||||
|
out_b[i] = out[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto a_vec = load_vector<N_READS>(a, index);
|
||||||
|
auto b_vec = load_vector<N_READS>(b, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_a_vec;
|
||||||
|
AlignedVector<Out, N_READS> out_b_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
auto out = Op{}(a_vec.val[i], b_vec.val[i]);
|
||||||
|
out_a_vec.val[i] = out[0];
|
||||||
|
out_b_vec.val[i] = out[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out_a, index, out_a_vec);
|
||||||
|
store_vector<N_READS>(out_b, index, out_b_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT, int NDIM>
|
template <typename Op, typename In, typename Out, typename IdxT, int NDIM>
|
||||||
__global__ void binary_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,
|
||||||
@@ -82,7 +148,7 @@ __global__ void binary_g_nd(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT>
|
||||||
__global__ void binary_g(
|
__global__ void binary_two_g(
|
||||||
const In* a,
|
const In* a,
|
||||||
const In* b,
|
const In* b,
|
||||||
Out* out_a,
|
Out* out_a,
|
||||||
@@ -103,7 +169,7 @@ __global__ void binary_g(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out>
|
template <typename Op, typename In, typename Out>
|
||||||
constexpr bool supports_binary_op() {
|
constexpr bool supports_binary_two_op() {
|
||||||
if (std::is_same_v<Op, DivMod>) {
|
if (std::is_same_v<Op, DivMod>) {
|
||||||
return std::is_same_v<In, Out> &&
|
return std::is_same_v<In, Out> &&
|
||||||
(std::is_integral_v<Out> || is_floating_v<Out>);
|
(std::is_integral_v<Out> || is_floating_v<Out>);
|
||||||
@@ -114,10 +180,10 @@ constexpr bool supports_binary_op() {
|
|||||||
} // namespace cu
|
} // namespace cu
|
||||||
|
|
||||||
template <typename Op>
|
template <typename Op>
|
||||||
void binary_op_gpu_inplace(
|
void binary_two_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
std::string_view op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
assert(inputs.size() > 1);
|
assert(inputs.size() > 1);
|
||||||
const auto& a = inputs[0];
|
const auto& a = inputs[0];
|
||||||
@@ -141,7 +207,7 @@ void binary_op_gpu_inplace(
|
|||||||
dispatch_all_types(out_a.dtype(), [&](auto out_type_tag) {
|
dispatch_all_types(out_a.dtype(), [&](auto out_type_tag) {
|
||||||
using CTYPE_IN = MLX_GET_TYPE(in_type_tag);
|
using CTYPE_IN = MLX_GET_TYPE(in_type_tag);
|
||||||
using CTYPE_OUT = MLX_GET_TYPE(out_type_tag);
|
using CTYPE_OUT = MLX_GET_TYPE(out_type_tag);
|
||||||
if constexpr (cu::supports_binary_op<Op, CTYPE_IN, CTYPE_OUT>()) {
|
if constexpr (cu::supports_binary_two_op<Op, CTYPE_IN, CTYPE_OUT>()) {
|
||||||
using InType = cuda_type_t<CTYPE_IN>;
|
using InType = cuda_type_t<CTYPE_IN>;
|
||||||
using OutType = cuda_type_t<CTYPE_OUT>;
|
using OutType = cuda_type_t<CTYPE_OUT>;
|
||||||
|
|
||||||
@@ -161,14 +227,19 @@ void binary_op_gpu_inplace(
|
|||||||
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) {
|
||||||
auto kernel = cu::
|
auto kernel = cu::binary_two_g_nd<
|
||||||
binary_g_nd<Op, InType, OutType, IdxT, dims_constant()>;
|
Op,
|
||||||
|
InType,
|
||||||
|
OutType,
|
||||||
|
IdxT,
|
||||||
|
dims_constant()>;
|
||||||
auto [num_blocks, block_dims] =
|
auto [num_blocks, block_dims] =
|
||||||
get_launch_args(kernel, out_a, large());
|
get_launch_args(kernel, out_a, large());
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out_a.data<OutType>(),
|
out_a.data<OutType>(),
|
||||||
@@ -179,13 +250,14 @@ void binary_op_gpu_inplace(
|
|||||||
const_param<dims_constant()>(b_strides));
|
const_param<dims_constant()>(b_strides));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
auto kernel = cu::binary_g<Op, InType, OutType, IdxT>;
|
auto kernel = cu::binary_two_g<Op, InType, OutType, IdxT>;
|
||||||
auto [num_blocks, block_dims] =
|
auto [num_blocks, block_dims] =
|
||||||
get_launch_args(kernel, out_a, large());
|
get_launch_args(kernel, out_a, large());
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out_a.data<OutType>(),
|
out_a.data<OutType>(),
|
||||||
@@ -198,26 +270,30 @@ void binary_op_gpu_inplace(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dispatch_bool(out_a.data_size() > INT32_MAX, [&](auto large) {
|
dispatch_bool(out_a.data_size() > UINT32_MAX, [&](auto large) {
|
||||||
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
||||||
auto kernel = cu::binary_ss<Op, InType, OutType, IdxT>;
|
// TODO: Choose optimized value based on type size.
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
auto kernel = cu::binary_two_ss<Op, InType, OutType, IdxT, N_READS>;
|
||||||
if (bopt == BinaryOpType::ScalarVector) {
|
if (bopt == BinaryOpType::ScalarVector) {
|
||||||
kernel = cu::binary_sv<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_two_sv<Op, InType, OutType, IdxT, N_READS>;
|
||||||
} else if (bopt == BinaryOpType::VectorScalar) {
|
} else if (bopt == BinaryOpType::VectorScalar) {
|
||||||
kernel = cu::binary_vs<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_two_vs<Op, InType, OutType, IdxT, N_READS>;
|
||||||
} else if (bopt == BinaryOpType::VectorVector) {
|
} else if (bopt == BinaryOpType::VectorVector) {
|
||||||
kernel = cu::binary_vv<Op, InType, OutType, IdxT>;
|
kernel = cu::binary_two_vv<Op, InType, OutType, IdxT, N_READS>;
|
||||||
}
|
}
|
||||||
auto [num_blocks, block_dims] = get_launch_args(
|
auto [num_blocks, block_dims] = get_launch_args(
|
||||||
kernel,
|
kernel,
|
||||||
out_a.data_size(),
|
out_a.data_size(),
|
||||||
out_a.shape(),
|
out_a.shape(),
|
||||||
out_a.strides(),
|
out_a.strides(),
|
||||||
large());
|
large(),
|
||||||
|
N_READS);
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<InType>(),
|
a.data<InType>(),
|
||||||
b.data<InType>(),
|
b.data<InType>(),
|
||||||
out_a.data<OutType>(),
|
out_a.data<OutType>(),
|
||||||
@@ -237,17 +313,17 @@ void binary_op_gpu_inplace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Op>
|
template <typename Op>
|
||||||
void binary_op_gpu(
|
void binary_two_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
std::string_view op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
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);
|
||||||
set_binary_op_output_data(a, b, outputs[0], bopt);
|
set_binary_op_output_data(a, b, outputs[0], bopt);
|
||||||
set_binary_op_output_data(a, b, outputs[1], bopt);
|
set_binary_op_output_data(a, b, outputs[1], bopt);
|
||||||
binary_op_gpu_inplace<Op>(inputs, outputs, op, s);
|
binary_two_op_gpu_inplace<Op>(inputs, outputs, op, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivMod::eval_gpu(
|
void DivMod::eval_gpu(
|
||||||
@@ -255,7 +331,7 @@ void DivMod::eval_gpu(
|
|||||||
std::vector<array>& outputs) {
|
std::vector<array>& outputs) {
|
||||||
nvtx3::scoped_range r("DivMod::eval_gpu");
|
nvtx3::scoped_range r("DivMod::eval_gpu");
|
||||||
auto& s = outputs[0].primitive().stream();
|
auto& s = outputs[0].primitive().stream();
|
||||||
binary_op_gpu<cu::DivMod>(inputs, outputs, get_primitive_string(this), s);
|
binary_two_op_gpu<cu::DivMod>(inputs, outputs, name(), s);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ struct FusedKernelBuilder {
|
|||||||
|
|
||||||
// Build function signature.
|
// Build function signature.
|
||||||
if (contiguous) {
|
if (contiguous) {
|
||||||
os += "template <typename IdxT = uint32_t>\n";
|
os += "template <typename IdxT = uint32_t, int work_per_thread = 1>\n";
|
||||||
} else {
|
} else {
|
||||||
os += "template <int NDIM, typename IdxT = uint32_t>\n";
|
os +=
|
||||||
|
"template <int NDIM, typename IdxT = uint32_t, int work_per_thread = 1>\n";
|
||||||
}
|
}
|
||||||
os += fmt::format("__global__ void {}(\n", kernel_name + name);
|
os += fmt::format("__global__ void {}(\n", kernel_name + name);
|
||||||
for (size_t i = 0; i < params.size(); ++i) {
|
for (size_t i = 0; i < params.size(); ++i) {
|
||||||
@@ -67,12 +68,46 @@ struct FusedKernelBuilder {
|
|||||||
}
|
}
|
||||||
os += ") {\n";
|
os += ") {\n";
|
||||||
|
|
||||||
// Index.
|
// Index. For non contiguous kernels we create a separate index
|
||||||
|
// variable per variable otherwise everyone uses `index`.
|
||||||
os +=
|
os +=
|
||||||
" IdxT index = cg::this_grid().thread_rank();\n"
|
" IdxT index = cg::this_grid().thread_rank() * work_per_thread;\n"
|
||||||
" if (index >= size) {\n"
|
" if (index >= size) {\n"
|
||||||
" return;\n"
|
" return;\n"
|
||||||
" }\n";
|
" }\n";
|
||||||
|
if (!contiguous) {
|
||||||
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
|
const auto& x = inputs[i];
|
||||||
|
const std::string& xname = namer.get_name(x);
|
||||||
|
if (is_scalar(x) || is_constant(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
os += " IdxT " + xname + "_idx = 0;\n";
|
||||||
|
}
|
||||||
|
os += " {\n";
|
||||||
|
os += " IdxT loc = index;\n";
|
||||||
|
os +=
|
||||||
|
" #pragma unroll\n"
|
||||||
|
" for (int i = NDIM - 1; i >= 0; i--) {\n";
|
||||||
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
|
const auto& x = inputs[i];
|
||||||
|
const std::string& xname = namer.get_name(x);
|
||||||
|
if (is_scalar(x) || is_constant(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
os += " " + xname + "_idx += (loc \% shape[i]) * IdxT(" + xname +
|
||||||
|
"_strides[i]);\n";
|
||||||
|
}
|
||||||
|
os +=
|
||||||
|
" loc /= shape[i];\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work loop
|
||||||
|
os +=
|
||||||
|
"\n"
|
||||||
|
" for (int i = 0; i < work_per_thread && index < size; i++) {\n";
|
||||||
|
|
||||||
// Read inputs.
|
// Read inputs.
|
||||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
@@ -89,12 +124,9 @@ struct FusedKernelBuilder {
|
|||||||
} else if (contiguous) {
|
} else if (contiguous) {
|
||||||
value = fmt::format("{}[index]", xname);
|
value = fmt::format("{}[index]", xname);
|
||||||
} else {
|
} else {
|
||||||
std::string index = fmt::format(
|
value = fmt::format("{}[{}_idx]", xname, xname);
|
||||||
"elem_to_loc_nd<NDIM>(index, shape.data(), {}_strides.data())",
|
|
||||||
xname);
|
|
||||||
value = fmt::format("{}[{}]", xname, index);
|
|
||||||
}
|
}
|
||||||
os += fmt::format(" {} tmp_{} = {};\n", type, xname, value);
|
os += fmt::format(" {} tmp_{} = {};\n", type, xname, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write tape.
|
// Write tape.
|
||||||
@@ -106,23 +138,37 @@ struct FusedKernelBuilder {
|
|||||||
value = fmt::format(
|
value = fmt::format(
|
||||||
"static_cast<{}>(tmp_{})", type, namer.get_name(x.inputs()[0]));
|
"static_cast<{}>(tmp_{})", type, namer.get_name(x.inputs()[0]));
|
||||||
} else {
|
} else {
|
||||||
std::ostringstream ss;
|
value = x.primitive().name();
|
||||||
x.primitive().print(ss);
|
|
||||||
value = ss.str();
|
|
||||||
value += "{}(";
|
value += "{}(";
|
||||||
for (size_t i = 0; i < x.inputs().size() - 1; ++i) {
|
for (size_t i = 0; i < x.inputs().size() - 1; ++i) {
|
||||||
value += fmt::format("tmp_{}, ", namer.get_name(x.inputs()[i]));
|
value += fmt::format("tmp_{}, ", namer.get_name(x.inputs()[i]));
|
||||||
}
|
}
|
||||||
value += fmt::format("tmp_{})", namer.get_name(x.inputs().back()));
|
value += fmt::format("tmp_{})", namer.get_name(x.inputs().back()));
|
||||||
}
|
}
|
||||||
os += fmt::format(" {} tmp_{} = {};\n", type, xname, value);
|
os += fmt::format(" {} tmp_{} = {};\n", type, xname, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write output.
|
// Write output.
|
||||||
for (const auto& x : outputs) {
|
for (const auto& x : outputs) {
|
||||||
os += fmt::format(" {0}[index] = tmp_{0};\n", namer.get_name(x));
|
os += fmt::format(" {0}[index] = tmp_{0};\n", namer.get_name(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End of work loop
|
||||||
|
os +=
|
||||||
|
"\n"
|
||||||
|
" index++;\n";
|
||||||
|
if (!contiguous) {
|
||||||
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
|
const auto& x = inputs[i];
|
||||||
|
const std::string& xname = namer.get_name(x);
|
||||||
|
if (is_scalar(x) || is_constant(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
os += " " + xname + "_idx += " + xname + "_strides[NDIM - 1];\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os += " }\n";
|
||||||
|
|
||||||
os += "}\n";
|
os += "}\n";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -158,15 +204,28 @@ void Compiled::eval_gpu(
|
|||||||
builder.build("_strided", false);
|
builder.build("_strided", false);
|
||||||
builder.os += "\n} // namespace mlx::core::cu\n";
|
builder.os += "\n} // namespace mlx::core::cu\n";
|
||||||
// Build kernel names.
|
// Build kernel names.
|
||||||
std::vector<std::string> kernel_names = {
|
std::vector<std::string> kernel_names;
|
||||||
fmt::format("mlx::core::cu::{}_contiguous<uint32_t>", lib_name()),
|
for (auto work_per_thread : std::array<int, 2>{1, 4}) {
|
||||||
fmt::format("mlx::core::cu::{}_contiguous<int64_t>", lib_name()),
|
|
||||||
};
|
|
||||||
for (int i = 1; i <= MAX_NDIM; ++i) {
|
|
||||||
kernel_names.push_back(fmt::format(
|
kernel_names.push_back(fmt::format(
|
||||||
"mlx::core::cu::{}_strided<{}, uint32_t>", lib_name(), i));
|
"mlx::core::cu::{}_contiguous<uint32_t, {}>",
|
||||||
kernel_names.push_back(
|
lib_name(),
|
||||||
fmt::format("mlx::core::cu::{}_strided<{}, int64_t>", lib_name(), i));
|
work_per_thread));
|
||||||
|
kernel_names.push_back(fmt::format(
|
||||||
|
"mlx::core::cu::{}_contiguous<int64_t, {}>",
|
||||||
|
lib_name(),
|
||||||
|
work_per_thread));
|
||||||
|
for (int i = 1; i <= MAX_NDIM; ++i) {
|
||||||
|
kernel_names.push_back(fmt::format(
|
||||||
|
"mlx::core::cu::{}_strided<{}, uint32_t, {}>",
|
||||||
|
lib_name(),
|
||||||
|
i,
|
||||||
|
work_per_thread));
|
||||||
|
kernel_names.push_back(fmt::format(
|
||||||
|
"mlx::core::cu::{}_strided<{}, int64_t, {}>",
|
||||||
|
lib_name(),
|
||||||
|
i,
|
||||||
|
work_per_thread));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return std::make_pair(std::move(builder.os), std::move(kernel_names));
|
return std::make_pair(std::move(builder.os), std::move(kernel_names));
|
||||||
});
|
});
|
||||||
@@ -209,13 +268,21 @@ void Compiled::eval_gpu(
|
|||||||
args.append<uint32_t>(outputs[0].data_size());
|
args.append<uint32_t>(outputs[0].data_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choose work per thread
|
||||||
|
int work_per_thread = 4;
|
||||||
|
if (!contiguous && shape.back() % work_per_thread != 0) {
|
||||||
|
work_per_thread = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Launch kernel.
|
// Launch kernel.
|
||||||
const char* index_type = large ? "int64_t" : "uint32_t";
|
const char* index_type = large ? "int64_t" : "uint32_t";
|
||||||
std::string kernel_name = fmt::format("mlx::core::cu::{}", lib_name());
|
std::string kernel_name = fmt::format("mlx::core::cu::{}", lib_name());
|
||||||
if (contiguous) {
|
if (contiguous) {
|
||||||
kernel_name += fmt::format("_contiguous<{}>", index_type);
|
kernel_name +=
|
||||||
|
fmt::format("_contiguous<{}, {}>", index_type, work_per_thread);
|
||||||
} else {
|
} else {
|
||||||
kernel_name += fmt::format("_strided<{}, {}>", shape.size(), index_type);
|
kernel_name += fmt::format(
|
||||||
|
"_strided<{}, {}, {}>", shape.size(), index_type, work_per_thread);
|
||||||
}
|
}
|
||||||
auto& encoder = cu::get_command_encoder(s);
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
for (const auto& in : inputs) {
|
for (const auto& in : inputs) {
|
||||||
@@ -226,8 +293,9 @@ void Compiled::eval_gpu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto kernel = mod.get_kernel(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, outputs[0], large);
|
auto [num_blocks, block_dims] =
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, args.args());
|
get_launch_args(kernel, outputs[0], large, work_per_thread);
|
||||||
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -10,19 +10,43 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT>
|
template <typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void copy_s(const In* in, Out* out, IdxT size) {
|
__global__ void copy_s(const In* in, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = CastOp<In, Out>{}(in[0]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = cast_to<Out>(in[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = cast_to<Out>(in[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename In, typename Out, typename IdxT>
|
template <typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void copy_v(const In* in, Out* out, IdxT size) {
|
__global__ void copy_v(const In* in, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = CastOp<In, Out>{}(in[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = cast_to<Out>(in[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto in_vec = load_vector<N_READS>(in, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = cast_to<Out>(in_vec.val[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,16 +65,24 @@ void copy_contiguous(
|
|||||||
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, uint32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
||||||
auto kernel = cu::copy_s<InType, OutType, IdxT>;
|
// TODO: Choose optimized value based on type size.
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
auto kernel = cu::copy_s<InType, OutType, IdxT, N_READS>;
|
||||||
if (ctype == CopyType::Vector) {
|
if (ctype == CopyType::Vector) {
|
||||||
kernel = cu::copy_v<InType, OutType, IdxT>;
|
kernel = cu::copy_v<InType, OutType, IdxT, N_READS>;
|
||||||
}
|
}
|
||||||
auto [num_blocks, block_dims] = get_launch_args(
|
auto [num_blocks, block_dims] = get_launch_args(
|
||||||
kernel, out.data_size(), out.shape(), out.strides(), large());
|
kernel,
|
||||||
|
out.data_size(),
|
||||||
|
out.shape(),
|
||||||
|
out.strides(),
|
||||||
|
large(),
|
||||||
|
N_READS);
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in.data<InType>() + in_offset,
|
in.data<InType>() + in_offset,
|
||||||
out.data<OutType>() + out_offset,
|
out.data<OutType>() + out_offset,
|
||||||
out.data_size());
|
out.data_size());
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ void copy_general(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
data_size,
|
data_size,
|
||||||
@@ -94,6 +95,7 @@ void copy_general(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
data_size,
|
data_size,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ void copy_general_dynamic(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
out.size(),
|
out.size(),
|
||||||
@@ -99,6 +100,7 @@ void copy_general_dynamic(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
out.size(),
|
out.size(),
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ void copy_general_input(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
out.size(),
|
out.size(),
|
||||||
@@ -85,6 +86,7 @@ void copy_general_input(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in_ptr,
|
in_ptr,
|
||||||
out_ptr,
|
out_ptr,
|
||||||
out.size(),
|
out.size(),
|
||||||
|
|||||||
@@ -57,8 +57,15 @@ void Device::make_current() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandEncoder& Device::get_command_encoder(Stream s) {
|
||||||
|
auto it = encoders_.find(s.index);
|
||||||
|
if (it == encoders_.end()) {
|
||||||
|
it = encoders_.try_emplace(s.index, *this).first;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
CommandEncoder::CaptureContext::CaptureContext(CommandEncoder& enc) : enc(enc) {
|
CommandEncoder::CaptureContext::CaptureContext(CommandEncoder& enc) : enc(enc) {
|
||||||
CHECK_CUDA_ERROR(cudaGraphCreate(&graph, 0));
|
|
||||||
CHECK_CUDA_ERROR(
|
CHECK_CUDA_ERROR(
|
||||||
cudaStreamBeginCapture(enc.stream(), cudaStreamCaptureModeGlobal));
|
cudaStreamBeginCapture(enc.stream(), cudaStreamCaptureModeGlobal));
|
||||||
}
|
}
|
||||||
@@ -168,15 +175,7 @@ void CommandEncoder::insert_graph_dependencies(std::vector<GraphNode> nodes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandEncoder& Device::get_command_encoder(Stream s) {
|
CommandEncoder::CommandEncoder(Device& d) : device_(d), stream_(d) {
|
||||||
auto it = encoders_.find(s.index);
|
|
||||||
if (it == encoders_.end()) {
|
|
||||||
it = encoders_.try_emplace(s.index, *this).first;
|
|
||||||
}
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandEncoder::CommandEncoder(Device& d) : stream_(d) {
|
|
||||||
CHECK_CUDA_ERROR(cudaGraphCreate(&graph_, 0));
|
CHECK_CUDA_ERROR(cudaGraphCreate(&graph_, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,12 +215,14 @@ void CommandEncoder::add_kernel_node(
|
|||||||
void* func,
|
void* func,
|
||||||
dim3 grid_dim,
|
dim3 grid_dim,
|
||||||
dim3 block_dim,
|
dim3 block_dim,
|
||||||
|
uint32_t smem_bytes,
|
||||||
void** params) {
|
void** params) {
|
||||||
cudaKernelNodeParams kernel_params = {0};
|
cudaKernelNodeParams kernel_params = {0};
|
||||||
kernel_params.func = func;
|
kernel_params.func = func;
|
||||||
kernel_params.gridDim = grid_dim;
|
kernel_params.gridDim = grid_dim;
|
||||||
kernel_params.blockDim = block_dim;
|
kernel_params.blockDim = block_dim;
|
||||||
kernel_params.kernelParams = params;
|
kernel_params.kernelParams = params;
|
||||||
|
kernel_params.sharedMemBytes = smem_bytes;
|
||||||
cudaGraphNode_t node;
|
cudaGraphNode_t node;
|
||||||
CHECK_CUDA_ERROR(
|
CHECK_CUDA_ERROR(
|
||||||
cudaGraphAddKernelNode(&node, graph_, NULL, 0, &kernel_params));
|
cudaGraphAddKernelNode(&node, graph_, NULL, 0, &kernel_params));
|
||||||
@@ -232,6 +233,7 @@ void CommandEncoder::add_kernel_node(
|
|||||||
CUfunction func,
|
CUfunction func,
|
||||||
dim3 grid_dim,
|
dim3 grid_dim,
|
||||||
dim3 block_dim,
|
dim3 block_dim,
|
||||||
|
uint32_t smem_bytes,
|
||||||
void** params) {
|
void** params) {
|
||||||
CUDA_KERNEL_NODE_PARAMS kernel_params = {0};
|
CUDA_KERNEL_NODE_PARAMS kernel_params = {0};
|
||||||
kernel_params.func = func;
|
kernel_params.func = func;
|
||||||
@@ -242,6 +244,7 @@ void CommandEncoder::add_kernel_node(
|
|||||||
kernel_params.blockDimY = block_dim.y;
|
kernel_params.blockDimY = block_dim.y;
|
||||||
kernel_params.blockDimZ = block_dim.z;
|
kernel_params.blockDimZ = block_dim.z;
|
||||||
kernel_params.kernelParams = params;
|
kernel_params.kernelParams = params;
|
||||||
|
kernel_params.sharedMemBytes = smem_bytes;
|
||||||
CUgraphNode node;
|
CUgraphNode node;
|
||||||
CHECK_CUDA_ERROR(
|
CHECK_CUDA_ERROR(
|
||||||
cuGraphAddKernelNode(&node, graph_, NULL, 0, &kernel_params));
|
cuGraphAddKernelNode(&node, graph_, NULL, 0, &kernel_params));
|
||||||
@@ -264,22 +267,30 @@ void CommandEncoder::commit() {
|
|||||||
graph_key_ += std::to_string(graph_node_count_);
|
graph_key_ += std::to_string(graph_node_count_);
|
||||||
graph_key_ += ".";
|
graph_key_ += ".";
|
||||||
graph_key_ += std::to_string(empty_node_count_);
|
graph_key_ += std::to_string(empty_node_count_);
|
||||||
auto [it, _] = graph_cache_.emplace(graph_key_, nullptr);
|
|
||||||
auto& graph_exec = it->second;
|
|
||||||
|
|
||||||
if (graph_exec != NULL) {
|
cudaGraphExec_t& graph_exec = graph_cache_[graph_key_];
|
||||||
cudaGraphExecUpdateResultInfo update_result;
|
|
||||||
cudaGraphExecUpdate(graph_exec, graph_, &update_result);
|
if (graph_exec != nullptr) {
|
||||||
if (update_result.result != cudaGraphExecUpdateSuccess) {
|
cudaGraphExecUpdateResult update_result;
|
||||||
cudaGetLastError();
|
#if CUDART_VERSION >= 12000
|
||||||
|
cudaGraphExecUpdateResultInfo info;
|
||||||
|
cudaGraphExecUpdate(graph_exec, graph_, &info);
|
||||||
|
update_result = info.result;
|
||||||
|
#else
|
||||||
|
cudaGraphNode_t error_node;
|
||||||
|
cudaGraphExecUpdate(graph_exec, graph_, &error_node, &update_result);
|
||||||
|
#endif // CUDART_VERSION >= 12000
|
||||||
|
if (update_result != cudaGraphExecUpdateSuccess) {
|
||||||
|
cudaGetLastError(); // reset error
|
||||||
CHECK_CUDA_ERROR(cudaGraphExecDestroy(graph_exec));
|
CHECK_CUDA_ERROR(cudaGraphExecDestroy(graph_exec));
|
||||||
graph_exec = NULL;
|
graph_exec = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (graph_exec == NULL) {
|
if (graph_exec == nullptr) {
|
||||||
CHECK_CUDA_ERROR(
|
CHECK_CUDA_ERROR(
|
||||||
cudaGraphInstantiate(&graph_exec, graph_, NULL, NULL, 0));
|
cudaGraphInstantiate(&graph_exec, graph_, NULL, NULL, 0));
|
||||||
}
|
}
|
||||||
|
device_.make_current();
|
||||||
CHECK_CUDA_ERROR(cudaGraphLaunch(graph_exec, stream_));
|
CHECK_CUDA_ERROR(cudaGraphLaunch(graph_exec, stream_));
|
||||||
|
|
||||||
// TODO smarter cache policy
|
// TODO smarter cache policy
|
||||||
|
|||||||
@@ -45,25 +45,34 @@ class CommandEncoder {
|
|||||||
void set_output_array(const array& arr);
|
void set_output_array(const array& arr);
|
||||||
|
|
||||||
template <typename F, typename... Params>
|
template <typename F, typename... Params>
|
||||||
void
|
void add_kernel_node(
|
||||||
add_kernel_node(F* func, dim3 grid_dim, dim3 block_dim, Params&&... params) {
|
F* func,
|
||||||
|
dim3 grid_dim,
|
||||||
|
dim3 block_dim,
|
||||||
|
uint32_t smem_bytes,
|
||||||
|
Params&&... params) {
|
||||||
constexpr size_t num = sizeof...(Params);
|
constexpr size_t num = sizeof...(Params);
|
||||||
void* ptrs[num];
|
void* ptrs[num];
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
([&](auto&& p) { ptrs[i++] = static_cast<void*>(&p); }(
|
([&](auto&& p) { ptrs[i++] = static_cast<void*>(&p); }(
|
||||||
std::forward<Params>(params)),
|
std::forward<Params>(params)),
|
||||||
...);
|
...);
|
||||||
add_kernel_node((void*)func, grid_dim, block_dim, ptrs);
|
add_kernel_node((void*)func, grid_dim, block_dim, smem_bytes, ptrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_kernel_node(
|
void add_kernel_node(
|
||||||
CUfunction func,
|
CUfunction func,
|
||||||
dim3 grid_dim,
|
dim3 grid_dim,
|
||||||
dim3 block_dim,
|
dim3 block_dim,
|
||||||
|
uint32_t smem_bytes,
|
||||||
void** params);
|
void** params);
|
||||||
|
|
||||||
void
|
void add_kernel_node(
|
||||||
add_kernel_node(void* func, dim3 grid_dim, dim3 block_dim, void** params);
|
void* func,
|
||||||
|
dim3 grid_dim,
|
||||||
|
dim3 block_dim,
|
||||||
|
uint32_t smem_bytes,
|
||||||
|
void** params);
|
||||||
|
|
||||||
void add_temporary(const array& arr) {
|
void add_temporary(const array& arr) {
|
||||||
temporaries_.push_back(arr.data_shared_ptr());
|
temporaries_.push_back(arr.data_shared_ptr());
|
||||||
@@ -93,6 +102,7 @@ class CommandEncoder {
|
|||||||
void insert_graph_dependencies(GraphNode node);
|
void insert_graph_dependencies(GraphNode node);
|
||||||
void insert_graph_dependencies(std::vector<GraphNode> nodes);
|
void insert_graph_dependencies(std::vector<GraphNode> nodes);
|
||||||
|
|
||||||
|
Device& device_;
|
||||||
CudaStream stream_;
|
CudaStream stream_;
|
||||||
cudaGraph_t graph_;
|
cudaGraph_t graph_;
|
||||||
Worker worker_;
|
Worker worker_;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
#include "mlx/backend/cuda/device/complex.cuh"
|
||||||
#include "mlx/backend/cuda/device/fp16_math.cuh"
|
#include "mlx/backend/cuda/device/fp16_math.cuh"
|
||||||
|
|
||||||
#include <cuda/atomic>
|
#include <cuda/atomic>
|
||||||
@@ -48,7 +48,7 @@ inline __device__ void atomic_add(__half* out, __half val) {
|
|||||||
atomicAdd(out, val);
|
atomicAdd(out, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline __device__ void atomic_add(cuComplex* out, cuComplex val) {
|
inline __device__ void atomic_add(complex64_t* out, complex64_t val) {
|
||||||
#if __CUDA_ARCH__ < 900
|
#if __CUDA_ARCH__ < 900
|
||||||
atomic_add_general(out, val);
|
atomic_add_general(out, val);
|
||||||
#else
|
#else
|
||||||
@@ -58,12 +58,7 @@ inline __device__ void atomic_add(cuComplex* out, cuComplex val) {
|
|||||||
|
|
||||||
inline __device__ void atomic_add(__nv_bfloat16* out, __nv_bfloat16 val) {
|
inline __device__ void atomic_add(__nv_bfloat16* out, __nv_bfloat16 val) {
|
||||||
#if __CUDA_ARCH__ < 800
|
#if __CUDA_ARCH__ < 800
|
||||||
#if CCCL_VERSION >= 2008000
|
|
||||||
atomic_add_general(out, val);
|
atomic_add_general(out, val);
|
||||||
#else
|
|
||||||
bool cccl_version_too_old_for_bfloat16_atomic_add = false;
|
|
||||||
assert(cccl_version_too_old_for_bfloat16_atomic_add);
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
atomicAdd(out, val);
|
atomicAdd(out, val);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
#include "mlx/backend/cuda/device/unary_ops.cuh"
|
||||||
#include "mlx/backend/cuda/device/fp16_math.cuh"
|
|
||||||
#include "mlx/backend/cuda/device/utils.cuh"
|
|
||||||
|
|
||||||
#include <cuComplex.h>
|
|
||||||
#include <cuda/std/array>
|
#include <cuda/std/array>
|
||||||
|
|
||||||
namespace mlx::core::cu {
|
namespace mlx::core::cu {
|
||||||
@@ -47,7 +44,7 @@ struct Remainder {
|
|||||||
} else {
|
} else {
|
||||||
return x % y;
|
return x % y;
|
||||||
}
|
}
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
} else if constexpr (is_complex_v<T>) {
|
||||||
return x % y;
|
return x % y;
|
||||||
} else {
|
} else {
|
||||||
T r = fmod(x, y);
|
T r = fmod(x, y);
|
||||||
@@ -69,14 +66,12 @@ struct Equal {
|
|||||||
struct NaNEqual {
|
struct NaNEqual {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ bool operator()(T x, T y) {
|
__device__ bool operator()(T x, T y) {
|
||||||
if constexpr (std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return x == y ||
|
return x == y ||
|
||||||
(isnan(cuCrealf(x)) && isnan(cuCrealf(y)) && isnan(cuCimagf(x)) &&
|
(isnan(x.real()) && isnan(y.real()) && isnan(x.imag()) &&
|
||||||
isnan(cuCimagf(y))) ||
|
isnan(y.imag())) ||
|
||||||
(cuCrealf(x) == cuCrealf(y) && isnan(cuCimagf(x)) &&
|
(x.real() == y.real() && isnan(x.imag()) && isnan(y.imag())) ||
|
||||||
isnan(cuCimagf(y))) ||
|
(isnan(x.real()) && isnan(y.real()) && x.imag() == y.imag());
|
||||||
(isnan(cuCrealf(x)) && isnan(cuCrealf(y)) &&
|
|
||||||
cuCimagf(x) == cuCimagf(y));
|
|
||||||
} else {
|
} else {
|
||||||
return x == y || (isnan(x) && isnan(y));
|
return x == y || (isnan(x) && isnan(y));
|
||||||
}
|
}
|
||||||
@@ -114,36 +109,38 @@ struct LessEqual {
|
|||||||
struct LogAddExp {
|
struct LogAddExp {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x, T y) {
|
__device__ T operator()(T x, T y) {
|
||||||
if (isnan(x) || isnan(y)) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return cuda::std::numeric_limits<T>::quiet_NaN();
|
if (isnan(x.real()) || isnan(x.imag()) || isnan(y.real()) ||
|
||||||
|
isnan(y.imag())) {
|
||||||
|
return {
|
||||||
|
cuda::std::numeric_limits<float>::quiet_NaN(),
|
||||||
|
cuda::std::numeric_limits<float>::quiet_NaN()};
|
||||||
|
}
|
||||||
|
auto max = x.real() > y.real() ? x : y;
|
||||||
|
auto min = x.real() < y.real() ? x : y;
|
||||||
|
auto min_real = min.real();
|
||||||
|
auto max_real = max.real();
|
||||||
|
if (!isfinite(min_real) && (min_real == max_real)) {
|
||||||
|
if (min_real < 0) {
|
||||||
|
return min;
|
||||||
|
} else {
|
||||||
|
return Log{}(Exp{}(min) + Exp{}(max));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Log1p{}(Exp{}(min - max)) + max;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isnan(x) || isnan(y)) {
|
||||||
|
return cuda::std::numeric_limits<T>::quiet_NaN();
|
||||||
|
}
|
||||||
|
T maxval = max(x, y);
|
||||||
|
T minval = min(x, y);
|
||||||
|
return (minval == -cuda::std::numeric_limits<T>::infinity() ||
|
||||||
|
maxval == cuda::std::numeric_limits<T>::infinity())
|
||||||
|
? maxval
|
||||||
|
: T(float(maxval) + log1p(expf(minval - maxval)));
|
||||||
}
|
}
|
||||||
T maxval = max(x, y);
|
|
||||||
T minval = min(x, y);
|
|
||||||
return (minval == -cuda::std::numeric_limits<T>::infinity() ||
|
|
||||||
maxval == cuda::std::numeric_limits<T>::infinity())
|
|
||||||
? maxval
|
|
||||||
: T(float(maxval) + log1p(expf(minval - maxval)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
__device__ cuComplex operator()(cuComplex x, cuComplex y) {
|
|
||||||
if (isnan(cuCrealf(x)) || isnan(cuCimagf(x)) || isnan(cuCrealf(y)) ||
|
|
||||||
isnan(cuCimagf(y))) {
|
|
||||||
return {
|
|
||||||
cuda::std::numeric_limits<float>::quiet_NaN(),
|
|
||||||
cuda::std::numeric_limits<float>::quiet_NaN()};
|
|
||||||
}
|
|
||||||
float inf = cuda::std::numeric_limits<float>::infinity();
|
|
||||||
auto maxval = x > y ? x : y;
|
|
||||||
auto minval = x < y ? x : y;
|
|
||||||
if (cuCrealf(minval) == -inf || cuCrealf(maxval) == inf)
|
|
||||||
return maxval;
|
|
||||||
float m = exp(cuCrealf(minval) - cuCrealf(maxval));
|
|
||||||
cuComplex dexp{
|
|
||||||
m * cos(cuCimagf(minval) - cuCimagf(maxval)),
|
|
||||||
m * sin(cuCimagf(minval) - cuCimagf(maxval)),
|
|
||||||
};
|
|
||||||
return maxval + log1p(dexp);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Maximum {
|
struct Maximum {
|
||||||
@@ -151,8 +148,8 @@ struct Maximum {
|
|||||||
__device__ T operator()(T x, T y) {
|
__device__ T operator()(T x, T y) {
|
||||||
if constexpr (cuda::std::is_integral_v<T>) {
|
if constexpr (cuda::std::is_integral_v<T>) {
|
||||||
return max(x, y);
|
return max(x, y);
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
} else if constexpr (is_complex_v<T>) {
|
||||||
if (isnan(cuCrealf(x)) || isnan(cuCimagf(x))) {
|
if (isnan(x.real()) || isnan(x.imag())) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
return x > y ? x : y;
|
return x > y ? x : y;
|
||||||
@@ -170,8 +167,8 @@ struct Minimum {
|
|||||||
__device__ T operator()(T x, T y) {
|
__device__ T operator()(T x, T y) {
|
||||||
if constexpr (cuda::std::is_integral_v<T>) {
|
if constexpr (cuda::std::is_integral_v<T>) {
|
||||||
return min(x, y);
|
return min(x, y);
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
} else if constexpr (is_complex_v<T>) {
|
||||||
if (isnan(cuCrealf(x)) || isnan(cuCimagf(x))) {
|
if (isnan(x.real()) || isnan(x.imag())) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
return x < y ? x : y;
|
return x < y ? x : y;
|
||||||
@@ -194,8 +191,8 @@ struct Multiply {
|
|||||||
struct NotEqual {
|
struct NotEqual {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ bool operator()(T x, T y) {
|
__device__ bool operator()(T x, T y) {
|
||||||
if constexpr (std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return cuCrealf(x) != cuCrealf(y) || cuCimagf(x) != cuCimagf(y);
|
return x.real() != y.real() || x.imag() != y.imag();
|
||||||
} else {
|
} else {
|
||||||
return x != y;
|
return x != y;
|
||||||
}
|
}
|
||||||
@@ -215,19 +212,8 @@ struct Power {
|
|||||||
base *= base;
|
base *= base;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
} else if constexpr (is_complex_v<T>) {
|
||||||
if (base.y == 0 && base.x == 0) {
|
return pow(base, exp);
|
||||||
if (isnan(exp.x) || isnan(exp.y)) {
|
|
||||||
auto nan = cuda::std::numeric_limits<float>::quiet_NaN();
|
|
||||||
return make_cuFloatComplex(nan, nan);
|
|
||||||
}
|
|
||||||
return make_cuFloatComplex(0.0, 0.0);
|
|
||||||
}
|
|
||||||
auto x_theta = atan2f(base.y, base.x);
|
|
||||||
auto x_ln_r = 0.5 * logf(base.x * base.x + base.y * base.y);
|
|
||||||
auto mag = expf(exp.x * x_ln_r - exp.y * x_theta);
|
|
||||||
auto phase = exp.y * x_ln_r + exp.x * x_theta;
|
|
||||||
return make_cuFloatComplex(mag * cosf(phase), mag * sinf(phase));
|
|
||||||
} else {
|
} else {
|
||||||
return powf(base, exp);
|
return powf(base, exp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cuComplex.h>
|
#include "mlx/backend/cuda/device/complex.cuh"
|
||||||
|
|
||||||
|
#include <cuda_bf16.h>
|
||||||
|
#include <cuda_fp16.h>
|
||||||
#include <thrust/iterator/transform_iterator.h>
|
#include <thrust/iterator/transform_iterator.h>
|
||||||
|
|
||||||
namespace mlx::core::cu {
|
namespace mlx::core::cu {
|
||||||
@@ -17,34 +20,48 @@ struct CastOp {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Converting a complex number to real number discards the imaginary part.
|
// Castings between complex and boolean.
|
||||||
template <typename DstT>
|
template <typename T>
|
||||||
struct CastOp<
|
struct CastOp<complex_t<T>, bool> {
|
||||||
cuComplex,
|
static constexpr bool is_castable = true;
|
||||||
DstT,
|
|
||||||
cuda::std::enable_if_t<!cuda::std::is_same_v<cuComplex, DstT>>> {
|
|
||||||
static constexpr bool is_castable = cuda::std::is_convertible_v<float, DstT>;
|
|
||||||
|
|
||||||
__device__ DstT operator()(cuComplex x) {
|
__device__ bool operator()(complex_t<T> x) {
|
||||||
static_assert(!cuda::std::is_same_v<cuComplex, DstT>);
|
return x.real() != 0 && x.imag() != 0;
|
||||||
return static_cast<DstT>(cuCrealf(x));
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct CastOp<bool, complex_t<T>> {
|
||||||
|
static constexpr bool is_castable = true;
|
||||||
|
|
||||||
|
__device__ complex_t<T> operator()(bool x) {
|
||||||
|
return x ? complex_t<T>{1, 1} : complex_t<T>{0, 0};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converting a complex number to real number discards the imaginary part.
|
||||||
|
template <typename T, typename DstT>
|
||||||
|
struct CastOp<complex_t<T>, DstT, cuda::std::enable_if_t<!is_complex_v<DstT>>> {
|
||||||
|
static constexpr bool is_castable = cuda::std::is_convertible_v<T, DstT>;
|
||||||
|
|
||||||
|
__device__ DstT operator()(complex_t<T> x) {
|
||||||
|
static_assert(!is_complex_v<DstT>);
|
||||||
|
return static_cast<DstT>(x.real());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow converting a real number to complex number.
|
// Allow converting a real number to complex number.
|
||||||
template <typename SrcT>
|
template <typename SrcT, typename T>
|
||||||
struct CastOp<
|
struct CastOp<SrcT, complex_t<T>, cuda::std::enable_if_t<!is_complex_v<SrcT>>> {
|
||||||
SrcT,
|
static constexpr bool is_castable = cuda::std::is_convertible_v<SrcT, T>;
|
||||||
cuComplex,
|
|
||||||
cuda::std::enable_if_t<!cuda::std::is_same_v<SrcT, cuComplex>>> {
|
|
||||||
static constexpr bool is_castable = cuda::std::is_convertible_v<SrcT, float>;
|
|
||||||
|
|
||||||
__device__ cuComplex operator()(SrcT x) {
|
__device__ complex_t<T> operator()(SrcT x) {
|
||||||
static_assert(!cuda::std::is_same_v<SrcT, cuComplex>);
|
static_assert(!is_complex_v<SrcT>);
|
||||||
return cuComplex{static_cast<float>(x), 0};
|
return complex_t<T>{static_cast<T>(x), 0};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Do nothing when no casting is needed.
|
||||||
template <typename SrcT, typename DstT>
|
template <typename SrcT, typename DstT>
|
||||||
struct CastOp<
|
struct CastOp<
|
||||||
SrcT,
|
SrcT,
|
||||||
@@ -57,9 +74,51 @@ struct CastOp<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In CUDA 11 the half types do not define conversions between some types,
|
||||||
|
// provide fallbacks here.
|
||||||
|
#if CUDART_VERSION < 12000
|
||||||
|
template <typename SrcT, typename DstT>
|
||||||
|
struct CastOp<
|
||||||
|
SrcT,
|
||||||
|
DstT,
|
||||||
|
cuda::std::enable_if_t<
|
||||||
|
!cuda::std::is_convertible_v<SrcT, DstT> && !is_complex_v<SrcT> &&
|
||||||
|
(cuda::std::is_same_v<DstT, __half> ||
|
||||||
|
cuda::std::is_same_v<DstT, __nv_bfloat16>)>> {
|
||||||
|
static constexpr bool is_castable = true;
|
||||||
|
|
||||||
|
__device__ DstT operator()(SrcT x) {
|
||||||
|
return DstT(static_cast<float>(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename SrcT, typename DstT>
|
||||||
|
struct CastOp<
|
||||||
|
SrcT,
|
||||||
|
DstT,
|
||||||
|
cuda::std::enable_if_t<
|
||||||
|
!cuda::std::is_convertible_v<SrcT, DstT> && !is_complex_v<SrcT> &&
|
||||||
|
!cuda::std::is_same_v<DstT, __half> &&
|
||||||
|
!cuda::std::is_same_v<DstT, __nv_bfloat16> &&
|
||||||
|
(cuda::std::is_same_v<SrcT, __half> ||
|
||||||
|
cuda::std::is_same_v<SrcT, __nv_bfloat16>)>> {
|
||||||
|
static constexpr bool is_castable = true;
|
||||||
|
|
||||||
|
__device__ DstT operator()(SrcT x) {
|
||||||
|
return DstT(static_cast<float>(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // CUDART_VERSION < 12000
|
||||||
|
|
||||||
|
// Helper to deduce the SrcT.
|
||||||
|
template <typename DstT, typename SrcT>
|
||||||
|
inline __host__ __device__ auto cast_to(SrcT x) {
|
||||||
|
return CastOp<SrcT, DstT>{}(x);
|
||||||
|
}
|
||||||
|
|
||||||
// Return an iterator that cast the value to DstT using CastOp.
|
// Return an iterator that cast the value to DstT using CastOp.
|
||||||
template <typename DstT, typename Iterator>
|
template <typename DstT, typename Iterator>
|
||||||
__host__ __device__ auto make_cast_iterator(Iterator it) {
|
inline __host__ __device__ auto make_cast_iterator(Iterator it) {
|
||||||
using SrcT = typename cuda::std::iterator_traits<Iterator>::value_type;
|
using SrcT = typename cuda::std::iterator_traits<Iterator>::value_type;
|
||||||
if constexpr (std::is_same_v<SrcT, DstT>) {
|
if constexpr (std::is_same_v<SrcT, DstT>) {
|
||||||
return it;
|
return it;
|
||||||
|
|||||||
60
mlx/backend/cuda/device/complex.cuh
Normal file
60
mlx/backend/cuda/device/complex.cuh
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Make multiplication and division faster.
|
||||||
|
#define LIBCUDACXX_ENABLE_SIMPLIFIED_COMPLEX_OPERATIONS
|
||||||
|
|
||||||
|
#include <cuda/std/complex>
|
||||||
|
#include <cuda/std/type_traits>
|
||||||
|
|
||||||
|
namespace mlx::core::cu {
|
||||||
|
|
||||||
|
// TODO: Consider using a faster implementation as cuda::std::complex has to
|
||||||
|
// conform to C++ standard.
|
||||||
|
template <typename T>
|
||||||
|
using complex_t = cuda::std::complex<T>;
|
||||||
|
|
||||||
|
using complex64_t = complex_t<float>;
|
||||||
|
using complex128_t = complex_t<double>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_complex : cuda::std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_complex<cuda::std::complex<T>> : cuda::std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline constexpr bool is_complex_v = is_complex<T>::value;
|
||||||
|
|
||||||
|
// cuda::std::complex is missing some operators.
|
||||||
|
template <typename T>
|
||||||
|
inline __host__ __device__ complex_t<T> operator%(
|
||||||
|
complex_t<T> a,
|
||||||
|
complex_t<T> b) {
|
||||||
|
T r = a.real() - floor(a.real() / b.real()) * b.real();
|
||||||
|
T i = a.imag() - floor(a.imag() / b.imag()) * b.imag();
|
||||||
|
return complex_t<T>{r, i};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline __host__ __device__ bool operator>(complex_t<T> a, complex_t<T> b) {
|
||||||
|
return (a.real() > b.real()) || (a.real() == b.real() && a.imag() > b.imag());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline __host__ __device__ bool operator<(complex_t<T> a, complex_t<T> b) {
|
||||||
|
return operator>(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline __host__ __device__ bool operator<=(complex_t<T> a, complex_t<T> b) {
|
||||||
|
return !(a > b);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline __host__ __device__ bool operator>=(complex_t<T> a, complex_t<T> b) {
|
||||||
|
return !(a < b);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core::cu
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
// Copyright © 2025 Apple Inc.
|
|
||||||
// Copyright © 2017-2024 The Simons Foundation, Inc.
|
|
||||||
//
|
|
||||||
// FINUFFT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
// "License"); you may not use this file except in compliance with the
|
|
||||||
// License. You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Forked from
|
|
||||||
// https://github.com/flatironinstitute/finufft/blob/main/include/cufinufft/contrib/helper_math.h
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cuComplex.h>
|
|
||||||
|
|
||||||
// This header provides some helper functions for cuComplex types.
|
|
||||||
// It mainly wraps existing CUDA implementations to provide operator overloads
|
|
||||||
// e.g. cuAdd, cuSub, cuMul, cuDiv, cuCreal, cuCimag, cuCabs, cuCarg, cuConj are
|
|
||||||
// all provided by CUDA
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator+(const cuDoubleComplex& a, const cuDoubleComplex& b) {
|
|
||||||
return cuCadd(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator-(const cuDoubleComplex& a, const cuDoubleComplex& b) {
|
|
||||||
return cuCsub(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator*(const cuDoubleComplex& a, const cuDoubleComplex& b) {
|
|
||||||
return cuCmul(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator/(const cuDoubleComplex& a, const cuDoubleComplex& b) {
|
|
||||||
return cuCdiv(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator%(const cuDoubleComplex& a, const cuDoubleComplex& b) {
|
|
||||||
double r = cuCreal(a) - (floorf(cuCreal(a) / cuCreal(b)) * cuCreal(b));
|
|
||||||
double i = cuCimag(a) - (floorf(cuCimag(a) / cuCimag(b)) * cuCimag(b));
|
|
||||||
return make_cuDoubleComplex(r, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator==(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
return cuCreal(a) == cuCreal(b) && cuCimag(a) == cuCimag(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator!=(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator>(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
double mag_a = sqrt(cuCreal(a) * cuCreal(a) + cuCimag(a) * cuCimag(a));
|
|
||||||
double mag_b = sqrt(cuCreal(b) * cuCreal(b) + cuCimag(b) * cuCimag(b));
|
|
||||||
return mag_a > mag_b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator>=(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
return a > b || a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator<(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
return b > a;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator<=(
|
|
||||||
const cuDoubleComplex& a,
|
|
||||||
const cuDoubleComplex& b) {
|
|
||||||
return b > a || a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator+(const cuDoubleComplex& a, double b) {
|
|
||||||
return make_cuDoubleComplex(cuCreal(a) + b, cuCimag(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator+(double a, const cuDoubleComplex& b) {
|
|
||||||
return make_cuDoubleComplex(a + cuCreal(b), cuCimag(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator-(const cuDoubleComplex& a, double b) {
|
|
||||||
return make_cuDoubleComplex(cuCreal(a) - b, cuCimag(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator-(double a, const cuDoubleComplex& b) {
|
|
||||||
return make_cuDoubleComplex(a - cuCreal(b), -cuCimag(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator*(const cuDoubleComplex& a, double b) {
|
|
||||||
return make_cuDoubleComplex(cuCreal(a) * b, cuCimag(a) * b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator*(double a, const cuDoubleComplex& b) {
|
|
||||||
return make_cuDoubleComplex(a * cuCreal(b), a * cuCimag(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator/(const cuDoubleComplex& a, double b) {
|
|
||||||
return make_cuDoubleComplex(cuCreal(a) / b, cuCimag(a) / b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuDoubleComplex
|
|
||||||
operator/(double a, const cuDoubleComplex& b) {
|
|
||||||
double denom = cuCreal(b) * cuCreal(b) + cuCimag(b) * cuCimag(b);
|
|
||||||
return make_cuDoubleComplex(
|
|
||||||
(a * cuCreal(b)) / denom, (-a * cuCimag(b)) / denom);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator+(const cuFloatComplex& a, const cuFloatComplex& b) {
|
|
||||||
return cuCaddf(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator-(const cuFloatComplex& a, const cuFloatComplex& b) {
|
|
||||||
return cuCsubf(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator*(const cuFloatComplex& a, const cuFloatComplex& b) {
|
|
||||||
return cuCmulf(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator/(const cuFloatComplex& a, const cuFloatComplex& b) {
|
|
||||||
return cuCdivf(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator%(const cuFloatComplex& a, const cuFloatComplex& b) {
|
|
||||||
float r = cuCrealf(a) - (floorf(cuCrealf(a) / cuCrealf(b)) * cuCrealf(b));
|
|
||||||
float i = cuCimagf(a) - (floorf(cuCimagf(a) / cuCimagf(b)) * cuCimagf(b));
|
|
||||||
return make_cuFloatComplex(r, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator==(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
return cuCrealf(a) == cuCrealf(b) && cuCimagf(a) == cuCimagf(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator!=(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator>(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
float mag_a = sqrt(cuCrealf(a) * cuCrealf(a) + cuCimagf(a) * cuCimagf(a));
|
|
||||||
float mag_b = sqrt(cuCrealf(b) * cuCrealf(b) + cuCimagf(b) * cuCimagf(b));
|
|
||||||
return mag_a > mag_b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator>=(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
return a > b || a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator<(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
return b > a;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ bool operator<=(
|
|
||||||
const cuFloatComplex& a,
|
|
||||||
const cuFloatComplex& b) {
|
|
||||||
return b > a || a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator+(const cuFloatComplex& a, float b) {
|
|
||||||
return make_cuFloatComplex(cuCrealf(a) + b, cuCimagf(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator+(float a, const cuFloatComplex& b) {
|
|
||||||
return make_cuFloatComplex(a + cuCrealf(b), cuCimagf(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator-(const cuFloatComplex& a, float b) {
|
|
||||||
return make_cuFloatComplex(cuCrealf(a) - b, cuCimagf(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator-(float a, const cuFloatComplex& b) {
|
|
||||||
return make_cuFloatComplex(a - cuCrealf(b), -cuCimagf(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator*(const cuFloatComplex& a, float b) {
|
|
||||||
return make_cuFloatComplex(cuCrealf(a) * b, cuCimagf(a) * b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator*(float a, const cuFloatComplex& b) {
|
|
||||||
return make_cuFloatComplex(a * cuCrealf(b), a * cuCimagf(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator/(const cuFloatComplex& a, float b) {
|
|
||||||
return make_cuFloatComplex(cuCrealf(a) / b, cuCimagf(a) / b);
|
|
||||||
}
|
|
||||||
|
|
||||||
__forceinline__ __host__ __device__ cuFloatComplex
|
|
||||||
operator/(float a, const cuFloatComplex& b) {
|
|
||||||
float denom = cuCrealf(b) * cuCrealf(b) + cuCimagf(b) * cuCimagf(b);
|
|
||||||
return make_cuFloatComplex(
|
|
||||||
(a * cuCrealf(b)) / denom, (-a * cuCimagf(b)) / denom);
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,6 @@ struct Abs {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_unsigned_v<T>) {
|
if constexpr (cuda::std::is_unsigned_v<T>) {
|
||||||
return x;
|
return x;
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
|
||||||
return {sqrt(cuCrealf(x) * cuCrealf(x) + cuCimagf(x) * cuCimagf(x)), 0};
|
|
||||||
} else {
|
} else {
|
||||||
return abs(x);
|
return abs(x);
|
||||||
}
|
}
|
||||||
@@ -27,8 +25,6 @@ struct ArcCos {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
return acos(x);
|
return acos(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ cuComplex operator()(cuComplex x);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ArcCosh {
|
struct ArcCosh {
|
||||||
@@ -43,8 +39,6 @@ struct ArcSin {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
return asin(x);
|
return asin(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ cuComplex operator()(cuComplex x);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ArcSinh {
|
struct ArcSinh {
|
||||||
@@ -59,8 +53,6 @@ struct ArcTan {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
return atan(x);
|
return atan(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ cuComplex operator()(cuComplex x);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ArcTanh {
|
struct ArcTanh {
|
||||||
@@ -82,6 +74,8 @@ struct Ceil {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_integral_v<T>) {
|
if constexpr (cuda::std::is_integral_v<T>) {
|
||||||
return x;
|
return x;
|
||||||
|
} else if constexpr (is_complex_v<T>) {
|
||||||
|
return T{ceil(x.real()), ceil(x.imag())};
|
||||||
} else {
|
} else {
|
||||||
return ceil(x);
|
return ceil(x);
|
||||||
}
|
}
|
||||||
@@ -89,34 +83,23 @@ struct Ceil {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Conjugate {
|
struct Conjugate {
|
||||||
__device__ cuComplex operator()(cuComplex x) {
|
template <typename T>
|
||||||
return {cuCrealf(x), -cuCimagf(x)};
|
__device__ complex_t<T> operator()(complex_t<T> x) {
|
||||||
|
return conj(x);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cos {
|
struct Cos {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return cos(x);
|
||||||
return {
|
|
||||||
cos(cuCrealf(x)) * cosh(cuCimagf(x)),
|
|
||||||
-sin(cuCrealf(x)) * sinh(cuCimagf(x))};
|
|
||||||
} else {
|
|
||||||
return cos(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cosh {
|
struct Cosh {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return cosh(x);
|
||||||
return {
|
|
||||||
cosh(cuCrealf(x)) * cos(cuCimagf(x)),
|
|
||||||
sinh(cuCrealf(x)) * sin(cuCimagf(x))};
|
|
||||||
} else {
|
|
||||||
return cosh(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,12 +132,7 @@ struct ErfInv {
|
|||||||
struct Exp {
|
struct Exp {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return exp(x);
|
||||||
auto m = exp(cuCrealf(x));
|
|
||||||
return {m * cos(cuCimagf(x)), m * sinh(cuCimagf(x))};
|
|
||||||
} else {
|
|
||||||
return exp(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,6 +154,8 @@ struct Floor {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_integral_v<T>) {
|
if constexpr (cuda::std::is_integral_v<T>) {
|
||||||
return x;
|
return x;
|
||||||
|
} else if constexpr (is_complex_v<T>) {
|
||||||
|
return T{floor(x.real()), floor(x.imag())};
|
||||||
} else {
|
} else {
|
||||||
return floor(x);
|
return floor(x);
|
||||||
}
|
}
|
||||||
@@ -183,30 +163,25 @@ struct Floor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Imag {
|
struct Imag {
|
||||||
__device__ float operator()(cuComplex x) {
|
template <typename T>
|
||||||
return cuCimagf(x);
|
__device__ auto operator()(complex_t<T> x) {
|
||||||
|
return x.imag();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Log {
|
struct Log {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return log(x);
|
||||||
auto r = log(cuCrealf(Abs{}(x)));
|
|
||||||
auto i = atan2f(cuCimagf(x), cuCrealf(x));
|
|
||||||
return {r, i};
|
|
||||||
} else {
|
|
||||||
return log(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Log2 {
|
struct Log2 {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
auto y = Log{}(x);
|
auto y = Log{}(x);
|
||||||
return {cuCrealf(y) / CUDART_LN2_F, cuCimagf(y) / CUDART_LN2_F};
|
return {y.real() / CUDART_LN2_F, y.imag() / CUDART_LN2_F};
|
||||||
} else {
|
} else {
|
||||||
return log2(x);
|
return log2(x);
|
||||||
}
|
}
|
||||||
@@ -216,20 +191,31 @@ struct Log2 {
|
|||||||
struct Log10 {
|
struct Log10 {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return log10(x);
|
||||||
auto y = Log{}(x);
|
|
||||||
return {cuCrealf(y) / CUDART_LNT_F, cuCimagf(y) / CUDART_LNT_F};
|
|
||||||
return y;
|
|
||||||
} else {
|
|
||||||
return log10(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Log1p {
|
struct Log1p {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T z) {
|
||||||
return log1p(x);
|
if constexpr (is_complex_v<T>) {
|
||||||
|
float x = z.real();
|
||||||
|
float y = z.imag();
|
||||||
|
float zabs = Abs{}(z).real();
|
||||||
|
float theta = atan2f(y, x + 1);
|
||||||
|
if (zabs < 0.5f) {
|
||||||
|
float r = x * (2 + x) + y * y;
|
||||||
|
if (r == 0) { // handle underflow
|
||||||
|
return {x, theta};
|
||||||
|
}
|
||||||
|
return {0.5f * log1pf(r), theta};
|
||||||
|
} else {
|
||||||
|
float z0 = hypotf(x + 1, y);
|
||||||
|
return {logf(z0), theta};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return log1p(z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -242,8 +228,8 @@ struct LogicalNot {
|
|||||||
struct Negative {
|
struct Negative {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return 0 - x;
|
return T{0, 0} - x;
|
||||||
} else {
|
} else {
|
||||||
return -x;
|
return -x;
|
||||||
}
|
}
|
||||||
@@ -251,16 +237,17 @@ struct Negative {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Real {
|
struct Real {
|
||||||
__device__ float operator()(cuComplex x) {
|
template <typename T>
|
||||||
return cuCrealf(x);
|
__device__ auto operator()(complex_t<T> x) {
|
||||||
|
return x.real();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Round {
|
struct Round {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return {rint(cuCrealf(x)), rint(cuCimagf(x))};
|
return {rint(x.real()), rint(x.imag())};
|
||||||
} else {
|
} else {
|
||||||
return rint(x);
|
return rint(x);
|
||||||
}
|
}
|
||||||
@@ -280,8 +267,8 @@ struct Sign {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_unsigned_v<T>) {
|
if constexpr (cuda::std::is_unsigned_v<T>) {
|
||||||
return x != 0;
|
return x != 0;
|
||||||
} else if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
} else if constexpr (is_complex_v<T>) {
|
||||||
if (cuCrealf(x) == 0 && cuCimagf(x) == 0) {
|
if (x.real() == 0 && x.imag() == 0) {
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
return x / Abs()(x);
|
return x / Abs()(x);
|
||||||
@@ -297,26 +284,14 @@ struct Sign {
|
|||||||
struct Sin {
|
struct Sin {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return sin(x);
|
||||||
return {
|
|
||||||
sin(cuCrealf(x)) * cosh(cuCimagf(x)),
|
|
||||||
cos(cuCrealf(x)) * sinh(cuCimagf(x))};
|
|
||||||
} else {
|
|
||||||
return sin(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Sinh {
|
struct Sinh {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return sinh(x);
|
||||||
return {
|
|
||||||
sinh(cuCrealf(x)) * cos(cuCimagf(x)),
|
|
||||||
cosh(cuCrealf(x)) * sin(cuCimagf(x))};
|
|
||||||
} else {
|
|
||||||
return sinh(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,77 +307,31 @@ struct Sqrt {
|
|||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
return sqrt(x);
|
return sqrt(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ cuComplex operator()(cuComplex x) {
|
|
||||||
auto xr = cuCrealf(x);
|
|
||||||
auto xi = cuCimagf(x);
|
|
||||||
if (xr == 0.0f && xi == 0.0f) {
|
|
||||||
return {0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
auto r = cuCrealf(Abs{}(x));
|
|
||||||
auto a = sqrt((r + xr) / 2.0f);
|
|
||||||
auto b_abs = sqrt((r - xr) / 2.0f);
|
|
||||||
auto b = copysign(b_abs, xi);
|
|
||||||
return {a, b};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Rsqrt {
|
struct Rsqrt {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
return rsqrt(x);
|
if constexpr (is_complex_v<T>) {
|
||||||
}
|
return 1.0f / Sqrt{}(x);
|
||||||
__device__ cuComplex operator()(cuComplex x) {
|
} else {
|
||||||
return 1.0f / Sqrt{}(x);
|
return rsqrt(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Tan {
|
struct Tan {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return tan(x);
|
||||||
float tan_a = tan(cuCrealf(x));
|
|
||||||
float tanh_b = tanh(cuCimagf(x));
|
|
||||||
float t1 = tan_a * tanh_b;
|
|
||||||
float denom = 1. + t1 * t1;
|
|
||||||
return {(tan_a - tanh_b * t1) / denom, (tanh_b + tan_a * t1) / denom};
|
|
||||||
} else {
|
|
||||||
return tan(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Tanh {
|
struct Tanh {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ T operator()(T x) {
|
__device__ T operator()(T x) {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
return tanh(x);
|
||||||
float tanh_a = tanh(cuCrealf(x));
|
|
||||||
float tan_b = tan(cuCimagf(x));
|
|
||||||
float t1 = tanh_a * tan_b;
|
|
||||||
float denom = 1. + t1 * t1;
|
|
||||||
return {(tanh_a + tan_b * t1) / denom, (tan_b - tanh_a * t1) / denom};
|
|
||||||
} else {
|
|
||||||
return tanh(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
__device__ cuComplex ArcCos::operator()(cuComplex x) {
|
|
||||||
auto i = cuComplex{0.0, 1.0};
|
|
||||||
auto y = Log{}(x + i * Sqrt{}(1.0 - x * x));
|
|
||||||
return {cuCimagf(y), -cuCrealf(y)};
|
|
||||||
};
|
|
||||||
|
|
||||||
__device__ cuComplex ArcSin::operator()(cuComplex x) {
|
|
||||||
auto i = cuComplex{0.0f, 1.0f};
|
|
||||||
auto y = Log{}(i * x + Sqrt{}(1.0f - x * x));
|
|
||||||
return {cuCimagf(y), -cuCrealf(y)};
|
|
||||||
};
|
|
||||||
|
|
||||||
__device__ cuComplex ArcTan::operator()(cuComplex x) {
|
|
||||||
auto i = cuComplex{0.0f, 1.0f};
|
|
||||||
auto ix = i * x;
|
|
||||||
return (1.0f / cuComplex{0.0f, 2.0f}) * Log{}((1.0f + ix) / (1.0f - ix));
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace mlx::core::cu
|
} // namespace mlx::core::cu
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device/complex.cuh"
|
||||||
#include "mlx/backend/cuda/device/config.h"
|
#include "mlx/backend/cuda/device/config.h"
|
||||||
|
|
||||||
#include <cuComplex.h>
|
|
||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include <cuda_fp16.h>
|
#include <cuda_fp16.h>
|
||||||
#include <cuda/std/array>
|
#include <cuda/std/array>
|
||||||
@@ -28,6 +28,27 @@ namespace mlx::core::cu {
|
|||||||
using Shape = cuda::std::array<int32_t, MAX_NDIM>;
|
using Shape = cuda::std::array<int32_t, MAX_NDIM>;
|
||||||
using Strides = cuda::std::array<int64_t, MAX_NDIM>;
|
using Strides = cuda::std::array<int64_t, MAX_NDIM>;
|
||||||
|
|
||||||
|
// Vectorized load/store.
|
||||||
|
template <typename T, int N>
|
||||||
|
struct alignas(sizeof(T) * N) AlignedVector {
|
||||||
|
T val[N];
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int N, typename T>
|
||||||
|
inline __device__ AlignedVector<T, N> load_vector(
|
||||||
|
const T* ptr,
|
||||||
|
uint32_t offset) {
|
||||||
|
auto* from = reinterpret_cast<const AlignedVector<T, N>*>(ptr);
|
||||||
|
return from[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N, typename T>
|
||||||
|
inline __device__ void
|
||||||
|
store_vector(T* ptr, uint32_t offset, const AlignedVector<T, N>& vec) {
|
||||||
|
auto* to = reinterpret_cast<AlignedVector<T, N>*>(ptr);
|
||||||
|
to[offset] = vec;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Type limits utils
|
// Type limits utils
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -78,20 +99,20 @@ struct Limits<
|
|||||||
return cuda::std::numeric_limits<T>::infinity();
|
return cuda::std::numeric_limits<T>::infinity();
|
||||||
}
|
}
|
||||||
static constexpr __host__ __device__ T min() {
|
static constexpr __host__ __device__ T min() {
|
||||||
#if defined(__CUDA_ARCH__) || CUDART_VERSION >= 12000
|
#if CUDART_VERSION < 12000 && __CUDA_ARCH__ < 800
|
||||||
return -cuda::std::numeric_limits<T>::infinity();
|
|
||||||
#else
|
|
||||||
return -cuda::std::numeric_limits<float>::infinity();
|
return -cuda::std::numeric_limits<float>::infinity();
|
||||||
|
#else
|
||||||
|
return -cuda::std::numeric_limits<T>::infinity();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
static constexpr __host__ __device__ T finite_max() {
|
static constexpr __host__ __device__ T finite_max() {
|
||||||
return cuda::std::numeric_limits<T>::max();
|
return cuda::std::numeric_limits<T>::max();
|
||||||
}
|
}
|
||||||
static constexpr __host__ __device__ T finite_min() {
|
static constexpr __host__ __device__ T finite_min() {
|
||||||
#if defined(__CUDA_ARCH__) || CUDART_VERSION >= 12000
|
#if CUDART_VERSION < 12000 && __CUDA_ARCH__ < 800
|
||||||
return cuda::std::numeric_limits<T>::lowest();
|
|
||||||
#else
|
|
||||||
return cuda::std::numeric_limits<float>::lowest();
|
return cuda::std::numeric_limits<float>::lowest();
|
||||||
|
#else
|
||||||
|
return cuda::std::numeric_limits<T>::lowest();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -106,13 +127,13 @@ struct Limits<bool> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <typename T>
|
||||||
struct Limits<cuComplex> {
|
struct Limits<complex_t<T>> {
|
||||||
static constexpr __host__ __device__ cuComplex max() {
|
static constexpr __host__ __device__ complex_t<T> max() {
|
||||||
return {Limits<float>::max(), Limits<float>::max()};
|
return {Limits<T>::max(), Limits<T>::max()};
|
||||||
}
|
}
|
||||||
static constexpr __host__ __device__ cuComplex min() {
|
static constexpr __host__ __device__ complex_t<T> min() {
|
||||||
return {Limits<float>::min(), Limits<float>::min()};
|
return {Limits<T>::min(), Limits<T>::min()};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -338,21 +359,4 @@ struct LoopedElemToLoc<1, false, OffsetT> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline __device__ cuComplex log1p(cuComplex in) {
|
|
||||||
float x = cuCrealf(in);
|
|
||||||
float y = cuCimagf(in);
|
|
||||||
float zabs = sqrt(x * x + y * y);
|
|
||||||
float theta = atan2f(y, x + 1);
|
|
||||||
if (zabs < 0.5f) {
|
|
||||||
float r = x * (2 + x) + y * y;
|
|
||||||
if (r == 0) { // handle underflow
|
|
||||||
return {x, theta};
|
|
||||||
}
|
|
||||||
return {0.5f * log1pf(r), theta};
|
|
||||||
} else {
|
|
||||||
auto z0 = sqrt((x + 1) * (x + 1) + y * y);
|
|
||||||
return {log(z0), theta};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mlx::core::cu
|
} // namespace mlx::core::cu
|
||||||
|
|||||||
@@ -90,8 +90,6 @@ bool CudaEvent::completed() const {
|
|||||||
// SharedEvent implementations
|
// SharedEvent implementations
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
__host__ __device__ void event_wait(SharedEvent::Atomic* ac, uint64_t value) {
|
__host__ __device__ void event_wait(SharedEvent::Atomic* ac, uint64_t value) {
|
||||||
uint64_t current;
|
uint64_t current;
|
||||||
while ((current = ac->load()) < value) {
|
while ((current = ac->load()) < value) {
|
||||||
@@ -112,8 +110,6 @@ __global__ void event_signal_kernel(SharedEvent::Atomic* ac, uint64_t value) {
|
|||||||
event_signal(ac, value);
|
event_signal(ac, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
SharedEvent::SharedEvent() {
|
SharedEvent::SharedEvent() {
|
||||||
// Allocate cuda::atomic on managed memory.
|
// Allocate cuda::atomic on managed memory.
|
||||||
Atomic* ac;
|
Atomic* ac;
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ void Gather::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
|
|
||||||
auto kernel = mod.get_kernel(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, out, large);
|
auto [num_blocks, block_dims] = get_launch_args(kernel, out, large);
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, args.args());
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scatter::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void Scatter::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -230,7 +230,7 @@ void Scatter::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
auto kernel = mod.get_kernel(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, upd, large);
|
auto [num_blocks, block_dims] = get_launch_args(kernel, upd, large);
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, args.args());
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatherAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void GatherAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -318,7 +318,7 @@ void GatherAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
auto kernel = mod.get_kernel(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, idx, large);
|
auto [num_blocks, block_dims] = get_launch_args(kernel, idx, large);
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, args.args());
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScatterAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void ScatterAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
@@ -422,7 +422,7 @@ void ScatterAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
encoder.set_output_array(out);
|
encoder.set_output_array(out);
|
||||||
auto kernel = mod.get_kernel(kernel_name);
|
auto kernel = mod.get_kernel(kernel_name);
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, idx, large);
|
auto [num_blocks, block_dims] = get_launch_args(kernel, idx, large);
|
||||||
encoder.add_kernel_node(kernel, num_blocks, block_dims, args.args());
|
encoder.add_kernel_node(kernel, num_blocks, block_dims, 0, args.args());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "mlx/backend/cuda/jit_module.h"
|
#include "mlx/backend/cuda/jit_module.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/version.h"
|
||||||
|
|
||||||
#include "cuda_jit_sources.h"
|
#include "cuda_jit_sources.h"
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <nvrtc.h>
|
#include <nvrtc.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace mlx::core::cu {
|
namespace mlx::core::cu {
|
||||||
|
|
||||||
@@ -49,14 +51,41 @@ const std::string& cuda_home() {
|
|||||||
return home;
|
return home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the location of CCCL headers shipped with the distribution.
|
||||||
|
const std::string& cccl_dir() {
|
||||||
|
static std::string dir = []() {
|
||||||
|
std::filesystem::path path;
|
||||||
|
#if defined(MLX_CCCL_DIR)
|
||||||
|
// First search the install dir if defined.
|
||||||
|
path = MLX_CCCL_DIR;
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Then search dynamically from the dir of libmlx.so file.
|
||||||
|
path = current_binary_dir().parent_path() / "include" / "cccl";
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
// Finally check the environment variable.
|
||||||
|
path = std::getenv("MLX_CCCL_DIR");
|
||||||
|
if (!path.empty() && std::filesystem::exists(path)) {
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the cache directory for storing compiled results.
|
// Get the cache directory for storing compiled results.
|
||||||
const std::filesystem::path& ptx_cache_dir() {
|
const std::filesystem::path& ptx_cache_dir() {
|
||||||
static std::filesystem::path cache = []() -> std::filesystem::path {
|
static std::filesystem::path cache = []() -> std::filesystem::path {
|
||||||
std::filesystem::path cache;
|
std::filesystem::path cache;
|
||||||
if (auto c = std::getenv("MLX_PTX_CACHE"); c) {
|
if (auto c = std::getenv("MLX_PTX_CACHE_DIR"); c) {
|
||||||
cache = c;
|
cache = c;
|
||||||
} else {
|
} else {
|
||||||
cache = std::filesystem::temp_directory_path() / "mlx" / "ptx";
|
cache =
|
||||||
|
std::filesystem::temp_directory_path() / "mlx" / version() / "ptx";
|
||||||
}
|
}
|
||||||
if (!std::filesystem::exists(cache)) {
|
if (!std::filesystem::exists(cache)) {
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
@@ -108,7 +137,8 @@ void write_cached_ptx(
|
|||||||
const std::filesystem::path& cache_dir,
|
const std::filesystem::path& cache_dir,
|
||||||
const std::string& module_name,
|
const std::string& module_name,
|
||||||
const std::vector<char>& ptx,
|
const std::vector<char>& ptx,
|
||||||
const std::vector<std::pair<std::string, std::string>>& ptx_kernels) {
|
const std::vector<std::pair<std::string, std::string>>& ptx_kernels,
|
||||||
|
const std::string& source_code) {
|
||||||
if (cache_dir.empty()) {
|
if (cache_dir.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,6 +151,9 @@ void write_cached_ptx(
|
|||||||
for (const auto& [name, mangled] : ptx_kernels) {
|
for (const auto& [name, mangled] : ptx_kernels) {
|
||||||
txt_file << name << "\t" << mangled << std::endl;
|
txt_file << name << "\t" << mangled << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ofstream source_file(cache_dir / (module_name + ".cu"));
|
||||||
|
source_file << source_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return if |device|'s version is not newer than |major|.|minor| version.
|
// Return if |device|'s version is not newer than |major|.|minor| version.
|
||||||
@@ -160,7 +193,7 @@ constexpr const char* g_include_names[] = {
|
|||||||
INCLUDE_PREFIX "binary_ops.cuh",
|
INCLUDE_PREFIX "binary_ops.cuh",
|
||||||
INCLUDE_PREFIX "cast_op.cuh",
|
INCLUDE_PREFIX "cast_op.cuh",
|
||||||
INCLUDE_PREFIX "config.h",
|
INCLUDE_PREFIX "config.h",
|
||||||
INCLUDE_PREFIX "cucomplex_math.cuh",
|
INCLUDE_PREFIX "complex.cuh",
|
||||||
INCLUDE_PREFIX "fp16_math.cuh",
|
INCLUDE_PREFIX "fp16_math.cuh",
|
||||||
INCLUDE_PREFIX "indexing.cuh",
|
INCLUDE_PREFIX "indexing.cuh",
|
||||||
INCLUDE_PREFIX "scatter_ops.cuh",
|
INCLUDE_PREFIX "scatter_ops.cuh",
|
||||||
@@ -176,7 +209,7 @@ constexpr const char* g_headers[] = {
|
|||||||
jit_source_binary_ops,
|
jit_source_binary_ops,
|
||||||
jit_source_cast_op,
|
jit_source_cast_op,
|
||||||
jit_source_config,
|
jit_source_config,
|
||||||
jit_source_cucomplex_math,
|
jit_source_complex,
|
||||||
jit_source_fp16_math,
|
jit_source_fp16_math,
|
||||||
jit_source_indexing,
|
jit_source_indexing,
|
||||||
jit_source_scatter_ops,
|
jit_source_scatter_ops,
|
||||||
@@ -213,16 +246,24 @@ JitModule::JitModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile program.
|
// Compile program.
|
||||||
|
std::vector<const char*> args;
|
||||||
bool use_sass = compiler_supports_device_sass(device);
|
bool use_sass = compiler_supports_device_sass(device);
|
||||||
std::string compute = fmt::format(
|
std::string compute = fmt::format(
|
||||||
"--gpu-architecture={}_{}{}",
|
"--gpu-architecture={}_{}{}",
|
||||||
use_sass ? "sm" : "compute",
|
use_sass ? "sm" : "compute",
|
||||||
device.compute_capability_major(),
|
device.compute_capability_major(),
|
||||||
device.compute_capability_minor());
|
device.compute_capability_minor());
|
||||||
std::string include = fmt::format("--include-path={}/include", cuda_home());
|
args.push_back(compute.c_str());
|
||||||
const char* args[] = {compute.c_str(), include.c_str()};
|
std::string cccl_include = cccl_dir();
|
||||||
|
if (!cccl_include.empty()) {
|
||||||
|
cccl_include = fmt::format("--include-path={}", cccl_include);
|
||||||
|
args.push_back(cccl_include.c_str());
|
||||||
|
}
|
||||||
|
std::string cuda_include =
|
||||||
|
fmt::format("--include-path={}/include", cuda_home());
|
||||||
|
args.push_back(cuda_include.c_str());
|
||||||
nvrtcResult compile_result =
|
nvrtcResult compile_result =
|
||||||
nvrtcCompileProgram(prog, std::size(args), args);
|
nvrtcCompileProgram(prog, args.size(), args.data());
|
||||||
if (compile_result != NVRTC_SUCCESS) {
|
if (compile_result != NVRTC_SUCCESS) {
|
||||||
size_t log_size;
|
size_t log_size;
|
||||||
CHECK_NVRTC_ERROR(nvrtcGetProgramLogSize(prog, &log_size));
|
CHECK_NVRTC_ERROR(nvrtcGetProgramLogSize(prog, &log_size));
|
||||||
@@ -252,7 +293,8 @@ JitModule::JitModule(
|
|||||||
} else {
|
} else {
|
||||||
CHECK_NVRTC_ERROR(nvrtcGetPTX(prog, ptx.data()));
|
CHECK_NVRTC_ERROR(nvrtcGetPTX(prog, ptx.data()));
|
||||||
}
|
}
|
||||||
write_cached_ptx(ptx_cache_dir(), module_name, ptx, ptx_kernels);
|
write_cached_ptx(
|
||||||
|
ptx_cache_dir(), module_name, ptx, ptx_kernels, source_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load module.
|
// Load module.
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "mlx/array.h"
|
#include "mlx/array.h"
|
||||||
#include "mlx/backend/cuda/device/utils.cuh"
|
#include "mlx/backend/cuda/device/utils.cuh"
|
||||||
|
|
||||||
#include <cuComplex.h>
|
|
||||||
#include <cuda.h>
|
#include <cuda.h>
|
||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include <cuda_fp16.h>
|
#include <cuda_fp16.h>
|
||||||
@@ -79,7 +78,7 @@ struct CTypeToCudaType<bfloat16_t> {
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct CTypeToCudaType<complex64_t> {
|
struct CTypeToCudaType<complex64_t> {
|
||||||
using type = cuComplex;
|
using type = cu::complex64_t;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -91,10 +90,14 @@ inline constexpr bool is_floating_v =
|
|||||||
cuda::std::is_same_v<T, float> || cuda::std::is_same_v<T, double> ||
|
cuda::std::is_same_v<T, float> || cuda::std::is_same_v<T, double> ||
|
||||||
cuda::std::is_same_v<T, float16_t> || cuda::std::is_same_v<T, bfloat16_t>;
|
cuda::std::is_same_v<T, float16_t> || cuda::std::is_same_v<T, bfloat16_t>;
|
||||||
|
|
||||||
|
// Type traits for detecting complex numbers.
|
||||||
|
template <typename T>
|
||||||
|
inline constexpr bool is_complex_v = cuda::std::is_same_v<T, complex64_t> ||
|
||||||
|
cuda::std::is_same_v<T, complex128_t>;
|
||||||
|
|
||||||
// Type traits for detecting complex or real floating point numbers.
|
// Type traits for detecting complex or real floating point numbers.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline constexpr bool is_inexact_v =
|
inline constexpr bool is_inexact_v = is_floating_v<T> || is_complex_v<T>;
|
||||||
is_floating_v<T> || cuda::std::is_same_v<T, complex64_t>;
|
|
||||||
|
|
||||||
// Utility to copy data from vector to array in host.
|
// Utility to copy data from vector to array in host.
|
||||||
template <int NDIM = MAX_NDIM, typename T = int32_t>
|
template <int NDIM = MAX_NDIM, typename T = int32_t>
|
||||||
|
|||||||
@@ -237,8 +237,7 @@ void LayerNorm::eval_gpu(
|
|||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
out.copy_shared_buffer(x_copy);
|
out.copy_shared_buffer(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
@@ -267,6 +266,7 @@ void LayerNorm::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
x.data<DataType>(),
|
x.data<DataType>(),
|
||||||
w.data<DataType>(),
|
w.data<DataType>(),
|
||||||
b.data<DataType>(),
|
b.data<DataType>(),
|
||||||
@@ -295,9 +295,7 @@ void LayerNormVJP::eval_gpu(
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
copied = true;
|
copied = true;
|
||||||
array x_copy(x.shape(), x.dtype(), nullptr, {});
|
return contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
return x_copy;
|
|
||||||
};
|
};
|
||||||
bool donate_x = inputs[0].is_donatable();
|
bool donate_x = inputs[0].is_donatable();
|
||||||
bool donate_g = inputs[3].is_donatable();
|
bool donate_g = inputs[3].is_donatable();
|
||||||
@@ -381,6 +379,7 @@ void LayerNormVJP::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
x.data<DataType>(),
|
x.data<DataType>(),
|
||||||
w.data<DataType>(),
|
w.data<DataType>(),
|
||||||
g.data<DataType>(),
|
g.data<DataType>(),
|
||||||
|
|||||||
@@ -108,8 +108,7 @@ void LogSumExp::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
if (x.flags().contiguous && x.strides()[x.ndim() - 1] == 1) {
|
if (x.flags().contiguous && x.strides()[x.ndim() - 1] == 1) {
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
encoder.add_temporary(x_copy);
|
encoder.add_temporary(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
@@ -152,6 +151,7 @@ void LogSumExp::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
in.data<DataType>(),
|
in.data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
axis_size);
|
axis_size);
|
||||||
|
|||||||
@@ -27,6 +27,35 @@ void check_cublas_error(const char* name, cublasStatus_t err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CublasPreference {
|
||||||
|
CublasPreference(Device& device) {
|
||||||
|
// The recommended cublas workspace size is 4 MiB for pre-Hopper and 32 MiB
|
||||||
|
// for Hopper+:
|
||||||
|
// https://docs.nvidia.com/cuda/cublas/#cublassetworkspace
|
||||||
|
uint64_t MiB = 1024 * 1024;
|
||||||
|
uint64_t workspace_size =
|
||||||
|
device.compute_capability_major() >= 9 ? 32 * MiB : 4 * MiB;
|
||||||
|
|
||||||
|
CHECK_CUBLAS_ERROR(cublasLtMatmulPreferenceCreate(&pref_));
|
||||||
|
CHECK_CUBLAS_ERROR(cublasLtMatmulPreferenceSetAttribute(
|
||||||
|
pref_,
|
||||||
|
CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
|
||||||
|
&workspace_size,
|
||||||
|
sizeof(uint64_t)));
|
||||||
|
}
|
||||||
|
|
||||||
|
~CublasPreference() {
|
||||||
|
CHECK_CUBLAS_ERROR(cublasLtMatmulPreferenceDestroy(pref_));
|
||||||
|
}
|
||||||
|
|
||||||
|
cublasLtMatmulPreference_t pref_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
cublasLtMatmulPreference_t cublas_preference(Device& device) {
|
||||||
|
static CublasPreference pref(device);
|
||||||
|
return pref.pref_;
|
||||||
|
}
|
||||||
|
|
||||||
class MatMul {
|
class MatMul {
|
||||||
public:
|
public:
|
||||||
MatMul(
|
MatMul(
|
||||||
@@ -43,7 +72,7 @@ class MatMul {
|
|||||||
int32_t batch_count,
|
int32_t batch_count,
|
||||||
int64_t a_batch_stride,
|
int64_t a_batch_stride,
|
||||||
int64_t b_batch_stride)
|
int64_t b_batch_stride)
|
||||||
: handle_(device.lt_handle()) {
|
: handle_(device.lt_handle()), pref_(cublas_preference(device)) {
|
||||||
heuristic_.state = CUBLAS_STATUS_NOT_INITIALIZED;
|
heuristic_.state = CUBLAS_STATUS_NOT_INITIALIZED;
|
||||||
|
|
||||||
auto scale_type = dtype_to_cuda_type(dtype);
|
auto scale_type = dtype_to_cuda_type(dtype);
|
||||||
@@ -77,20 +106,6 @@ class MatMul {
|
|||||||
type, b_rows, b_cols, b_transposed, ldb, batch_count, b_batch_stride);
|
type, b_rows, b_cols, b_transposed, ldb, batch_count, b_batch_stride);
|
||||||
out_desc_ = create_matrix_layout(
|
out_desc_ = create_matrix_layout(
|
||||||
type, a_rows, b_cols, false, b_cols, batch_count, a_rows * b_cols);
|
type, a_rows, b_cols, false, b_cols, batch_count, a_rows * b_cols);
|
||||||
|
|
||||||
// The recommended cublas workspace size is 4 MiB for pre-Hopper and 32 MiB
|
|
||||||
// for Hopper+:
|
|
||||||
// https://docs.nvidia.com/cuda/cublas/#cublassetworkspace
|
|
||||||
uint64_t MiB = 1024 * 1024;
|
|
||||||
uint64_t workspace_size =
|
|
||||||
device.compute_capability_major() >= 9 ? 32 * MiB : 4 * MiB;
|
|
||||||
|
|
||||||
CHECK_CUBLAS_ERROR(cublasLtMatmulPreferenceCreate(&pref_));
|
|
||||||
CHECK_CUBLAS_ERROR(cublasLtMatmulPreferenceSetAttribute(
|
|
||||||
pref_,
|
|
||||||
CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
|
|
||||||
&workspace_size,
|
|
||||||
sizeof(uint64_t)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MatMul(
|
MatMul(
|
||||||
@@ -104,7 +119,6 @@ class MatMul {
|
|||||||
uint64_t b_rows,
|
uint64_t b_rows,
|
||||||
uint64_t b_cols,
|
uint64_t b_cols,
|
||||||
int64_t ldb,
|
int64_t ldb,
|
||||||
bool c_transposed,
|
|
||||||
int64_t ldc,
|
int64_t ldc,
|
||||||
int32_t batch_count,
|
int32_t batch_count,
|
||||||
int64_t a_batch_stride,
|
int64_t a_batch_stride,
|
||||||
@@ -126,15 +140,15 @@ class MatMul {
|
|||||||
b_batch_stride) {
|
b_batch_stride) {
|
||||||
auto type = dtype_to_cuda_type(dtype);
|
auto type = dtype_to_cuda_type(dtype);
|
||||||
c_desc_ = create_matrix_layout(
|
c_desc_ = create_matrix_layout(
|
||||||
type, a_rows, b_cols, c_transposed, ldc, batch_count, c_batch_stride);
|
type, a_rows, b_cols, false, ldc, batch_count, c_batch_stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
~MatMul() {
|
~MatMul() {
|
||||||
cublasLtMatrixLayoutDestroy(a_desc_);
|
CHECK_CUBLAS_ERROR(cublasLtMatrixLayoutDestroy(a_desc_));
|
||||||
cublasLtMatrixLayoutDestroy(b_desc_);
|
CHECK_CUBLAS_ERROR(cublasLtMatrixLayoutDestroy(b_desc_));
|
||||||
cublasLtMatrixLayoutDestroy(c_desc_);
|
CHECK_CUBLAS_ERROR(cublasLtMatrixLayoutDestroy(c_desc_));
|
||||||
cublasLtMatrixLayoutDestroy(out_desc_);
|
CHECK_CUBLAS_ERROR(cublasLtMatrixLayoutDestroy(out_desc_));
|
||||||
cublasLtMatmulDescDestroy(matmul_desc_);
|
CHECK_CUBLAS_ERROR(cublasLtMatmulDescDestroy(matmul_desc_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void run(
|
void run(
|
||||||
@@ -259,9 +273,9 @@ class MatMul {
|
|||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cublasLtMatmulPreference_t pref_{nullptr};
|
||||||
cublasLtHandle_t handle_{nullptr};
|
cublasLtHandle_t handle_{nullptr};
|
||||||
cublasLtMatmulDesc_t matmul_desc_{nullptr};
|
cublasLtMatmulDesc_t matmul_desc_{nullptr};
|
||||||
cublasLtMatmulPreference_t pref_{nullptr};
|
|
||||||
cublasLtMatrixLayout_t a_desc_{nullptr};
|
cublasLtMatrixLayout_t a_desc_{nullptr};
|
||||||
cublasLtMatrixLayout_t b_desc_{nullptr};
|
cublasLtMatrixLayout_t b_desc_{nullptr};
|
||||||
cublasLtMatrixLayout_t c_desc_{nullptr};
|
cublasLtMatrixLayout_t c_desc_{nullptr};
|
||||||
@@ -282,8 +296,7 @@ check_transpose(cu::CommandEncoder& enc, const Stream& s, const array& arr) {
|
|||||||
} else if (stx == 1 && sty == arr.shape(-2)) {
|
} else if (stx == 1 && sty == arr.shape(-2)) {
|
||||||
return std::make_tuple(true, sty, arr);
|
return std::make_tuple(true, sty, arr);
|
||||||
} else {
|
} else {
|
||||||
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
array arr_copy = contiguous_copy_gpu(arr, s);
|
||||||
copy_gpu(arr, arr_copy, CopyType::General, s);
|
|
||||||
enc.add_temporary(arr_copy);
|
enc.add_temporary(arr_copy);
|
||||||
return std::make_tuple(false, arr.shape(-1), arr_copy);
|
return std::make_tuple(false, arr.shape(-1), arr_copy);
|
||||||
}
|
}
|
||||||
@@ -389,9 +402,7 @@ void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
assert(inputs.size() == 3);
|
assert(inputs.size() == 3);
|
||||||
auto& a_pre = inputs[0];
|
auto& a_pre = inputs[0];
|
||||||
auto& b_pre = inputs[1];
|
auto& b_pre = inputs[1];
|
||||||
auto& c_pre = inputs[2];
|
auto c = inputs[2];
|
||||||
|
|
||||||
out.set_data(allocator::malloc(out.nbytes()));
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// Init checks and prep
|
// Init checks and prep
|
||||||
@@ -404,7 +415,24 @@ void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
// the arrays
|
// the arrays
|
||||||
auto [a_transposed, lda, a] = check_transpose(encoder, s, a_pre);
|
auto [a_transposed, lda, a] = check_transpose(encoder, s, a_pre);
|
||||||
auto [b_transposed, ldb, b] = check_transpose(encoder, s, b_pre);
|
auto [b_transposed, ldb, b] = check_transpose(encoder, s, b_pre);
|
||||||
auto [c_transposed, ldc, c] = check_transpose(encoder, s, c_pre);
|
|
||||||
|
int64_t ldc;
|
||||||
|
{
|
||||||
|
auto stx = c.strides()[c.ndim() - 2];
|
||||||
|
auto sty = c.strides()[c.ndim() - 1];
|
||||||
|
if (sty == 1 && stx == c.shape(-1)) {
|
||||||
|
ldc = stx;
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
} else if (sty == 1 && stx == 0) {
|
||||||
|
ldc = 0;
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
} else {
|
||||||
|
// Copy C into out and set C to out
|
||||||
|
ldc = c.shape(-1);
|
||||||
|
copy_gpu(c, out, CopyType::General, s);
|
||||||
|
c = out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// Check and collapse batch dimensions
|
// Check and collapse batch dimensions
|
||||||
@@ -442,7 +470,6 @@ void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
K,
|
K,
|
||||||
N,
|
N,
|
||||||
ldb,
|
ldb,
|
||||||
c_transposed,
|
|
||||||
ldc,
|
ldc,
|
||||||
batch_shape.back(),
|
batch_shape.back(),
|
||||||
a_batch_strides.back(),
|
a_batch_strides.back(),
|
||||||
|
|||||||
108
mlx/backend/cuda/matmul/mma.cuh
Normal file
108
mlx/backend/cuda/matmul/mma.cuh
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/matmul/tiles.cuh"
|
||||||
|
|
||||||
|
namespace mlx::core::cu {
|
||||||
|
|
||||||
|
template <typename U, typename T>
|
||||||
|
__device__ inline void
|
||||||
|
mma_t(Tile16x16<U>& C, Tile16x16<T>& A, Tile16x16<T>& B) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply the 16x16 bfloat16 tiles and accumulate the result in one 16x16
|
||||||
|
* float tile.
|
||||||
|
*
|
||||||
|
* We actually perform C += A @ B.T
|
||||||
|
*/
|
||||||
|
__device__ inline void mma_t(
|
||||||
|
Tile16x16<float>& C,
|
||||||
|
Tile16x16<__nv_bfloat16>& A,
|
||||||
|
Tile16x16<__nv_bfloat16>& B) {
|
||||||
|
asm volatile(
|
||||||
|
"mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32 "
|
||||||
|
"{%0, %1, %2, %3}, "
|
||||||
|
"{%4, %5, %6, %7}, "
|
||||||
|
"{%8, %9}, "
|
||||||
|
"{%10, %11, %12, %13};"
|
||||||
|
|
||||||
|
// D matrix
|
||||||
|
: "+f"(C.values[0].x),
|
||||||
|
"+f"(C.values[0].y),
|
||||||
|
"+f"(C.values[1].x),
|
||||||
|
"+f"(C.values[1].y)
|
||||||
|
|
||||||
|
// A matrix
|
||||||
|
: "r"(*(uint32_t*)(&A.values[0])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[1])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[2])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[3])),
|
||||||
|
|
||||||
|
// B matrix
|
||||||
|
"r"(*(uint32_t*)(&B.values[0])),
|
||||||
|
"r"(*(uint32_t*)(&B.values[2])),
|
||||||
|
|
||||||
|
// C matrix
|
||||||
|
"f"(C.values[0].x),
|
||||||
|
"f"(C.values[0].y),
|
||||||
|
"f"(C.values[1].x),
|
||||||
|
"f"(C.values[1].y));
|
||||||
|
asm volatile(
|
||||||
|
"mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32 "
|
||||||
|
"{%0, %1, %2, %3}, "
|
||||||
|
"{%4, %5, %6, %7}, "
|
||||||
|
"{%8, %9}, "
|
||||||
|
"{%10, %11, %12, %13};"
|
||||||
|
|
||||||
|
// D matrix
|
||||||
|
: "+f"(C.values[2].x),
|
||||||
|
"+f"(C.values[2].y),
|
||||||
|
"+f"(C.values[3].x),
|
||||||
|
"+f"(C.values[3].y)
|
||||||
|
|
||||||
|
// A matrix
|
||||||
|
: "r"(*(uint32_t*)(&A.values[0])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[1])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[2])),
|
||||||
|
"r"(*(uint32_t*)(&A.values[3])),
|
||||||
|
|
||||||
|
// B matrix
|
||||||
|
"r"(*(uint32_t*)(&B.values[1])),
|
||||||
|
"r"(*(uint32_t*)(&B.values[3])),
|
||||||
|
|
||||||
|
// C matrix
|
||||||
|
"f"(C.values[2].x),
|
||||||
|
"f"(C.values[2].y),
|
||||||
|
"f"(C.values[3].x),
|
||||||
|
"f"(C.values[3].y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply larger register tiles by delegating to mma_t.
|
||||||
|
*/
|
||||||
|
template <typename U, typename T, int M, int N, int K>
|
||||||
|
__device__ inline void mma_t(
|
||||||
|
RegisterTile<U, M, N>& C,
|
||||||
|
RegisterTile<T, M, K>& A,
|
||||||
|
RegisterTile<T, N, K>& B) {
|
||||||
|
constexpr int TILES_M = RegisterTile<T, M, K>::TILES_Y;
|
||||||
|
constexpr int TILES_K = RegisterTile<T, M, K>::TILES_X;
|
||||||
|
constexpr int TILES_N = RegisterTile<T, N, K>::TILES_Y;
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int k = 0; k < TILES_K; k++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int m = 0; m < TILES_M; m++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int n = 0; n < TILES_N; n++) {
|
||||||
|
mma_t(
|
||||||
|
C.data[m * TILES_N + n],
|
||||||
|
A.data[m * TILES_K + k],
|
||||||
|
B.data[n * TILES_K + k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core::cu
|
||||||
419
mlx/backend/cuda/matmul/tiles.cuh
Normal file
419
mlx/backend/cuda/matmul/tiles.cuh
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MLX_UNROLL _Pragma("unroll")
|
||||||
|
|
||||||
|
namespace mlx::core::cu {
|
||||||
|
|
||||||
|
// Map types to their vector of 2 type float -> float2, double -> double2 etc
|
||||||
|
template <typename T>
|
||||||
|
struct Vector2;
|
||||||
|
template <>
|
||||||
|
struct Vector2<double> {
|
||||||
|
using type = double2;
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct Vector2<float> {
|
||||||
|
using type = float2;
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct Vector2<__half> {
|
||||||
|
using type = __half2;
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct Vector2<__nv_bfloat16> {
|
||||||
|
using type = __nv_bfloat162;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
using Vector2_t = typename Vector2<T>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The basic building block for Ampere mmas. A 16x16 tile distributed across
|
||||||
|
* the warp.
|
||||||
|
*
|
||||||
|
* Each thread holds 8 values. They are distributed according to
|
||||||
|
* https://docs.nvidia.com/cuda/parallel-thread-execution/#warp-level-matrix-fragment-mma-16816-float
|
||||||
|
*
|
||||||
|
* For use instructions see the individual methods eg load().
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct Tile16x16 {
|
||||||
|
using T2 = Vector2_t<T>;
|
||||||
|
|
||||||
|
T2 values[4];
|
||||||
|
|
||||||
|
__device__ inline void fill(T v) {
|
||||||
|
T2 v2 = {v, v};
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
values[i] = v2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a 16x16 tile from shared memory.
|
||||||
|
*
|
||||||
|
* The instruction is a bit weird in the sense that the address provided by
|
||||||
|
* each thread and the elements loaded are not the same.
|
||||||
|
*
|
||||||
|
* We load 4 8x8 tiles. The tile rows are stored contiguously in memory. As a
|
||||||
|
* result the warp provides 4*8 = 32 addresses one per row.
|
||||||
|
*
|
||||||
|
* Threads 0-7 provide the addresses for the first tile, 8-15 for the second
|
||||||
|
* and so on. For instance to load a non swizzled tile we would do
|
||||||
|
*
|
||||||
|
* base_addr + (laneid % 16) * BK + (laneid / 2) * 8
|
||||||
|
*
|
||||||
|
* See
|
||||||
|
* https://docs.nvidia.com/cuda/parallel-thread-execution/#warp-level-matrix-instructions-ldmatrix
|
||||||
|
*/
|
||||||
|
__device__ inline void load(uint32_t row_address) {
|
||||||
|
if constexpr (
|
||||||
|
std::is_same_v<T2, __nv_bfloat162> || std::is_same_v<T2, __half2>) {
|
||||||
|
asm volatile(
|
||||||
|
"ldmatrix.sync.aligned.m8n8.x4.shared::cta.b16 {%0, %1, %2, %3}, [%4];\n"
|
||||||
|
: "=r"(*(uint32_t*)&(values[0])),
|
||||||
|
"=r"(*(uint32_t*)&(values[1])),
|
||||||
|
"=r"(*(uint32_t*)&(values[2])),
|
||||||
|
"=r"(*(uint32_t*)&(values[3]))
|
||||||
|
: "r"(row_address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the tile to the address pointed to by `x`.
|
||||||
|
*
|
||||||
|
* The provided pointer is a generic pointer but this is meant to be used to
|
||||||
|
* store to global memory. For storing to shared memory we should use
|
||||||
|
* `stmatrix`.
|
||||||
|
*
|
||||||
|
* This also showcases the format of the tile quite nicely. Each register is
|
||||||
|
* holding to adjacent values. The indices are
|
||||||
|
*
|
||||||
|
* row + 0, col + 0
|
||||||
|
* row + 8, col + 0
|
||||||
|
* row + 0, col + 8
|
||||||
|
* row + 8, col + 8
|
||||||
|
*
|
||||||
|
* Given that we are dealing with Vector2_t<U> the column offsets are 4
|
||||||
|
* instead of 8.
|
||||||
|
*/
|
||||||
|
template <typename U>
|
||||||
|
__device__ inline void store_global(U* x, int N) {
|
||||||
|
using U2 = Vector2_t<U>;
|
||||||
|
U2* x2 = reinterpret_cast<U2*>(x);
|
||||||
|
const int laneid = threadIdx.x % 32;
|
||||||
|
const int row = laneid / 4;
|
||||||
|
const int col = laneid % 4;
|
||||||
|
if constexpr (std::is_same_v<U2, T2>) {
|
||||||
|
x2[(row + 0) * (N / 2) + col + 0] = values[0];
|
||||||
|
x2[(row + 0) * (N / 2) + col + 4] = values[2];
|
||||||
|
x2[(row + 8) * (N / 2) + col + 0] = values[1];
|
||||||
|
x2[(row + 8) * (N / 2) + col + 4] = values[3];
|
||||||
|
} else if constexpr (
|
||||||
|
std::is_same_v<T2, float2> && std::is_same_v<U, __nv_bfloat16>) {
|
||||||
|
x2[(row + 0) * (N / 2) + col + 0] =
|
||||||
|
__floats2bfloat162_rn(values[0].x, values[0].y);
|
||||||
|
x2[(row + 0) * (N / 2) + col + 4] =
|
||||||
|
__floats2bfloat162_rn(values[2].x, values[2].y);
|
||||||
|
x2[(row + 8) * (N / 2) + col + 0] =
|
||||||
|
__floats2bfloat162_rn(values[1].x, values[1].y);
|
||||||
|
x2[(row + 8) * (N / 2) + col + 4] =
|
||||||
|
__floats2bfloat162_rn(values[3].x, values[3].y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
__device__ inline void store_global_safe(U* x, int N, int max_rows) {
|
||||||
|
const int laneid = threadIdx.x % 32;
|
||||||
|
const int row = laneid / 4;
|
||||||
|
const int col = laneid % 4;
|
||||||
|
if (row < max_rows) {
|
||||||
|
x[(row + 0) * N + 2 * col + 0] = static_cast<U>(values[0].x);
|
||||||
|
x[(row + 0) * N + 2 * col + 1] = static_cast<U>(values[0].y);
|
||||||
|
x[(row + 0) * N + 2 * col + 8] = static_cast<U>(values[2].x);
|
||||||
|
x[(row + 0) * N + 2 * col + 9] = static_cast<U>(values[2].y);
|
||||||
|
}
|
||||||
|
if (row + 8 < max_rows) {
|
||||||
|
x[(row + 8) * N + 2 * col + 0] = static_cast<U>(values[1].x);
|
||||||
|
x[(row + 8) * N + 2 * col + 1] = static_cast<U>(values[1].y);
|
||||||
|
x[(row + 8) * N + 2 * col + 8] = static_cast<U>(values[3].x);
|
||||||
|
x[(row + 8) * N + 2 * col + 9] = static_cast<U>(values[3].y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple container of multiple Tile16x16.
|
||||||
|
*
|
||||||
|
* Provides utility functions for loading and manipulating collections of basic
|
||||||
|
* tiles.
|
||||||
|
*/
|
||||||
|
template <typename T, int ROWS_, int COLS_>
|
||||||
|
struct RegisterTile {
|
||||||
|
static constexpr int ROWS = ROWS_;
|
||||||
|
static constexpr int COLS = COLS_;
|
||||||
|
static constexpr int TILES_X = COLS / 16;
|
||||||
|
static constexpr int TILES_Y = ROWS / 16;
|
||||||
|
|
||||||
|
Tile16x16<T> data[TILES_X * TILES_Y];
|
||||||
|
|
||||||
|
__device__ inline void fill(T v) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < TILES_Y; i++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int j = 0; j < TILES_X; j++) {
|
||||||
|
data[i * TILES_X + j].fill(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Tile>
|
||||||
|
__device__ inline void
|
||||||
|
load(Tile& tile, uint32_t base_address, int row, int col) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < TILES_Y; i++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int j = 0; j < TILES_X; j++) {
|
||||||
|
data[i * TILES_X + j].load(
|
||||||
|
tile.loc(base_address, row + i * 16, col + j * 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
__device__ inline void store_global(U* x, int N, int row, int col) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < TILES_Y; i++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int j = 0; j < TILES_X; j++) {
|
||||||
|
data[i * TILES_X + j].store_global(
|
||||||
|
x + (row + i * 16) * N + col + j * 16, N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
__device__ inline void
|
||||||
|
store_global_safe(U* x, int N, int row, int col, int max_rows) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < TILES_Y; i++) {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int j = 0; j < TILES_X; j++) {
|
||||||
|
data[i * TILES_X + j].store_global_safe(
|
||||||
|
x + (row + i * 16) * N + col + j * 16, N, max_rows - row - i * 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, int ROWS_, int COLS_>
|
||||||
|
struct SharedTile {
|
||||||
|
static constexpr int ROWS = ROWS_;
|
||||||
|
static constexpr int COLS = COLS_;
|
||||||
|
static constexpr int TILES_X = COLS / 16;
|
||||||
|
static constexpr int TILES_Y = ROWS / 16;
|
||||||
|
static constexpr int NUMEL = ROWS * COLS;
|
||||||
|
|
||||||
|
// Swizzle taken from ThunderKittens.
|
||||||
|
//
|
||||||
|
// See inludes/types/shared/st.cuh
|
||||||
|
//
|
||||||
|
// I do feel that it is too math heavy and can be improved. Also the math is
|
||||||
|
// done every time although the addresses don't change from load to load. I
|
||||||
|
// guess we are expecting the compiler to figure that out.
|
||||||
|
static constexpr int swizzle_bytes =
|
||||||
|
(sizeof(T) == 2 ? (TILES_X % 4 == 0 ? 128 : (TILES_X % 2 == 0 ? 64 : 32))
|
||||||
|
: (sizeof(T) == 4 ? (TILES_X % 2 == 0 ? 128 : 64) : 0));
|
||||||
|
|
||||||
|
T data[ROWS * COLS];
|
||||||
|
|
||||||
|
// Return a pointer to the element at (row, col) using the swizzle.
|
||||||
|
__device__ static inline T* ptr(T* ptr, int row, int col) {
|
||||||
|
if constexpr (swizzle_bytes > 0) {
|
||||||
|
static constexpr int swizzle_repeat = swizzle_bytes * 8;
|
||||||
|
static constexpr int subtile_cols = swizzle_bytes / sizeof(T);
|
||||||
|
const int outer_idx = col / subtile_cols;
|
||||||
|
const uint64_t addr =
|
||||||
|
(uint64_t)(&ptr
|
||||||
|
[outer_idx * ROWS * subtile_cols + row * subtile_cols +
|
||||||
|
col % subtile_cols]);
|
||||||
|
const int swizzle = ((addr % swizzle_repeat) >> 7) << 4;
|
||||||
|
return (T*)(addr ^ swizzle);
|
||||||
|
} else {
|
||||||
|
return ptr + row * COLS + col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the location of the element at (row, col) using the swizzle.
|
||||||
|
__device__ static inline uint32_t loc(uint32_t ptr, int row, int col) {
|
||||||
|
if constexpr (swizzle_bytes > 0) {
|
||||||
|
static constexpr int swizzle_repeat = swizzle_bytes * 8;
|
||||||
|
static constexpr int subtile_cols = swizzle_bytes / sizeof(T);
|
||||||
|
const int outer_idx = col / subtile_cols;
|
||||||
|
const uint32_t addr = ptr +
|
||||||
|
sizeof(T) *
|
||||||
|
(outer_idx * ROWS * subtile_cols + row * subtile_cols +
|
||||||
|
col % subtile_cols);
|
||||||
|
const int swizzle = ((addr % swizzle_repeat) >> 7) << 4;
|
||||||
|
return (addr ^ swizzle);
|
||||||
|
} else {
|
||||||
|
return ptr + sizeof(T) * (row * COLS + col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience functions to edit elements going through the swizzle.
|
||||||
|
__device__ inline T& operator()(int row, int col) {
|
||||||
|
return *ptr(data, row, col);
|
||||||
|
}
|
||||||
|
__device__ inline void store(float4& v, int row, int col) {
|
||||||
|
*(reinterpret_cast<float4*>(ptr(data, row, col))) = v;
|
||||||
|
}
|
||||||
|
__device__ inline void store(float2& v, int row, int col) {
|
||||||
|
*(reinterpret_cast<float2*>(ptr(data, row, col))) = v;
|
||||||
|
}
|
||||||
|
__device__ inline void store(float& v, int row, int col) {
|
||||||
|
*(reinterpret_cast<float*>(ptr(data, row, col))) = v;
|
||||||
|
}
|
||||||
|
template <int N>
|
||||||
|
__device__ inline void store(T (&v)[N], int row, int col) {
|
||||||
|
if constexpr (sizeof(T) * N == 4) {
|
||||||
|
store(*(reinterpret_cast<float*>(&v[0])), row, col);
|
||||||
|
} else if constexpr (sizeof(T) * N == 8) {
|
||||||
|
store(*(reinterpret_cast<float2*>(&v[0])), row, col);
|
||||||
|
} else if constexpr (sizeof(T) * N == 16) {
|
||||||
|
store(*(reinterpret_cast<float4*>(&v[0])), row, col);
|
||||||
|
} else {
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
*ptr(data, row, col + i) = v[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the tile from global memory by loading 16 bytes at a time and storing
|
||||||
|
* them immediately.
|
||||||
|
*/
|
||||||
|
template <int NUM_WARPS, typename T, typename Tile>
|
||||||
|
__device__ inline void load(Tile& tile, const T* x, int N) {
|
||||||
|
constexpr int NUM_THREADS = NUM_WARPS * 32;
|
||||||
|
constexpr int ELEMENTS_PER_LOAD = sizeof(float4) / sizeof(T);
|
||||||
|
constexpr int NUM_LOADS = Tile::NUMEL / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int NUM_LOADS_PER_THREAD = NUM_LOADS / NUM_THREADS;
|
||||||
|
constexpr int NUM_LOADS_PER_ROW = Tile::COLS / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int STEP_ROWS = NUM_THREADS / NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
const int row = threadIdx.x / NUM_LOADS_PER_ROW;
|
||||||
|
const int col = threadIdx.x % NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
x += row * N + col * ELEMENTS_PER_LOAD;
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < NUM_LOADS_PER_THREAD; i++) {
|
||||||
|
float4 tmp;
|
||||||
|
tmp = *(reinterpret_cast<const float4*>(&x[i * STEP_ROWS * N]));
|
||||||
|
tile.store(tmp, row + i * STEP_ROWS, col * ELEMENTS_PER_LOAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy 16 bytes from the globale memory address pointed to by x to the smem
|
||||||
|
* address pointed to by row_address.
|
||||||
|
*
|
||||||
|
* A simple wrapper over the PTX.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
__device__ inline void cp_async_16(uint32_t row_address, const T* x) {
|
||||||
|
asm volatile(
|
||||||
|
"cp.async.ca.shared::cta.global [%0], [%1], 16;\n" ::"r"(row_address),
|
||||||
|
"l"(reinterpret_cast<const int4*>(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit all the previous async copies to be executed.
|
||||||
|
*/
|
||||||
|
__device__ inline void cp_async_commit() {
|
||||||
|
asm volatile("cp.async.commit_group;\n" ::);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for all the async copies to finish.
|
||||||
|
*/
|
||||||
|
__device__ inline void cp_async_wait_all() {
|
||||||
|
asm volatile("cp.async.wait_all;\n" ::);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The asynchronous equivalent of load.
|
||||||
|
*
|
||||||
|
* Loads the tile from global memory by submitting a bunch of async copy
|
||||||
|
* instructions. The copy won't start until commit is called and we don't have
|
||||||
|
* a guarantee it will finish until wait is called.
|
||||||
|
*
|
||||||
|
* It should be used as follows
|
||||||
|
*
|
||||||
|
* load(...)
|
||||||
|
* load(...)
|
||||||
|
* cp_async_commit()
|
||||||
|
* do_other_stuff()
|
||||||
|
* cp_async_wait_all()
|
||||||
|
* do_stuff_with_shmem()
|
||||||
|
*/
|
||||||
|
template <int NUM_WARPS, typename T, typename Tile>
|
||||||
|
__device__ inline void
|
||||||
|
load_async(Tile& tile, uint32_t base_address, const T* x, int N) {
|
||||||
|
constexpr int NUM_THREADS = NUM_WARPS * 32;
|
||||||
|
constexpr int ELEMENTS_PER_LOAD = sizeof(float4) / sizeof(T);
|
||||||
|
constexpr int NUM_LOADS = Tile::NUMEL / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int NUM_LOADS_PER_THREAD = NUM_LOADS / NUM_THREADS;
|
||||||
|
constexpr int NUM_LOADS_PER_ROW = Tile::COLS / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int STEP_ROWS = NUM_THREADS / NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
const int row = threadIdx.x / NUM_LOADS_PER_ROW;
|
||||||
|
const int col = threadIdx.x % NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
x += row * N + col * ELEMENTS_PER_LOAD;
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < NUM_LOADS_PER_THREAD; i++) {
|
||||||
|
cp_async_16(
|
||||||
|
tile.loc(base_address, row + i * STEP_ROWS, col * ELEMENTS_PER_LOAD),
|
||||||
|
x + i * STEP_ROWS * N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int NUM_WARPS, typename T, typename Tile>
|
||||||
|
__device__ inline void load_async_safe(
|
||||||
|
Tile& tile,
|
||||||
|
uint32_t base_address,
|
||||||
|
const T* x,
|
||||||
|
int N,
|
||||||
|
int max_rows) {
|
||||||
|
constexpr int NUM_THREADS = NUM_WARPS * 32;
|
||||||
|
constexpr int ELEMENTS_PER_LOAD = sizeof(float4) / sizeof(T);
|
||||||
|
constexpr int NUM_LOADS = Tile::NUMEL / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int NUM_LOADS_PER_THREAD = NUM_LOADS / NUM_THREADS;
|
||||||
|
constexpr int NUM_LOADS_PER_ROW = Tile::COLS / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int STEP_ROWS = NUM_THREADS / NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
const int row = threadIdx.x / NUM_LOADS_PER_ROW;
|
||||||
|
const int col = threadIdx.x % NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
x += row * N + col * ELEMENTS_PER_LOAD;
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < NUM_LOADS_PER_THREAD; i++) {
|
||||||
|
if (row + i * STEP_ROWS < max_rows) {
|
||||||
|
cp_async_16(
|
||||||
|
tile.loc(base_address, row + i * STEP_ROWS, col * ELEMENTS_PER_LOAD),
|
||||||
|
x + i * STEP_ROWS * N);
|
||||||
|
} else {
|
||||||
|
float4 tmp = {0, 0, 0, 0};
|
||||||
|
tile.store(tmp, row + i * STEP_ROWS, col * ELEMENTS_PER_LOAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core::cu
|
||||||
@@ -81,8 +81,7 @@ NO_GPU(Hadamard)
|
|||||||
NO_GPU(Load)
|
NO_GPU(Load)
|
||||||
NO_GPU_MULTI(LUF)
|
NO_GPU_MULTI(LUF)
|
||||||
NO_GPU_MULTI(QRF)
|
NO_GPU_MULTI(QRF)
|
||||||
NO_GPU(QuantizedMatmul)
|
NO_GPU(SegmentedMM)
|
||||||
NO_GPU(Scan)
|
|
||||||
NO_GPU_MULTI(SVD)
|
NO_GPU_MULTI(SVD)
|
||||||
NO_GPU(Inverse)
|
NO_GPU(Inverse)
|
||||||
NO_GPU(Cholesky)
|
NO_GPU(Cholesky)
|
||||||
@@ -91,7 +90,6 @@ NO_GPU_MULTI(Eigh)
|
|||||||
|
|
||||||
namespace fast {
|
namespace fast {
|
||||||
NO_GPU(ScaledDotProductAttention)
|
NO_GPU(ScaledDotProductAttention)
|
||||||
NO_GPU_MULTI(AffineQuantize)
|
|
||||||
NO_GPU_MULTI(CustomKernel)
|
NO_GPU_MULTI(CustomKernel)
|
||||||
} // namespace fast
|
} // namespace fast
|
||||||
|
|
||||||
|
|||||||
331
mlx/backend/cuda/quantized/affine_quantize.cu
Normal file
331
mlx/backend/cuda/quantized/affine_quantize.cu
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
|
#include "mlx/backend/cuda/quantized/quantized_utils.cuh"
|
||||||
|
#include "mlx/dtype_utils.h"
|
||||||
|
|
||||||
|
#include <cooperative_groups.h>
|
||||||
|
#include <cooperative_groups/reduce.h>
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
namespace cu {
|
||||||
|
|
||||||
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
|
template <typename T, int group_size, int bits>
|
||||||
|
__global__ void
|
||||||
|
affine_quantize(const T* w, uint8_t* out, T* scales, T* biases, size_t size) {
|
||||||
|
auto block_size = cg::this_thread_block().dim_threads();
|
||||||
|
auto block_idx = cg::this_thread_block().group_index();
|
||||||
|
auto idx_in_block = cg::this_thread_block().thread_index();
|
||||||
|
|
||||||
|
auto tidx = block_idx.x * block_size.x + idx_in_block.x;
|
||||||
|
auto tidy = block_idx.y * block_size.y + idx_in_block.y;
|
||||||
|
|
||||||
|
auto grid_dim_x =
|
||||||
|
cg::this_grid().dim_blocks().x * cg::this_grid().block_index().x;
|
||||||
|
constexpr float eps = 1e-7;
|
||||||
|
constexpr int simd_size = WARP_SIZE;
|
||||||
|
constexpr float n_bins = (1 << bits) - 1;
|
||||||
|
constexpr int pack_factor = get_pack_factor<bits, 8>();
|
||||||
|
constexpr int bytes_per_pack = get_bytes_per_pack<bits>();
|
||||||
|
constexpr int values_per_reduce = group_size / simd_size;
|
||||||
|
constexpr int writes_per_reduce = pack_factor / values_per_reduce;
|
||||||
|
constexpr int writes_per_pack =
|
||||||
|
writes_per_reduce > 1 ? 1 : values_per_reduce / pack_factor;
|
||||||
|
constexpr int power_of_2_bits = (bits & (bits - 1)) == 0;
|
||||||
|
|
||||||
|
size_t offset = tidx + grid_dim_x * size_t(tidy);
|
||||||
|
size_t in_index = offset * values_per_reduce;
|
||||||
|
if (in_index >= size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t out_index = power_of_2_bits
|
||||||
|
? offset * writes_per_pack
|
||||||
|
: offset * bytes_per_pack / writes_per_reduce;
|
||||||
|
|
||||||
|
float w_thread[values_per_reduce];
|
||||||
|
float w_min = Limits<float>::max();
|
||||||
|
float w_max = 0;
|
||||||
|
|
||||||
|
#pragma clang loop unroll(full)
|
||||||
|
for (int i = 0; i < values_per_reduce; i++) {
|
||||||
|
float val = w[in_index + i];
|
||||||
|
w_thread[i] = val;
|
||||||
|
w_min = min(w_min, val);
|
||||||
|
w_max = max(w_max, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
cg::greater<float> max_op;
|
||||||
|
cg::less<float> min_op;
|
||||||
|
auto warp = cg::tiled_partition<WARP_SIZE>(cg::this_thread_block());
|
||||||
|
|
||||||
|
w_min = cg::reduce(warp, w_min, min_op);
|
||||||
|
w_max = cg::reduce(warp, w_max, max_op);
|
||||||
|
|
||||||
|
float scale = max((w_max - w_min) / n_bins, eps);
|
||||||
|
bool side = abs(w_min) > abs(w_max);
|
||||||
|
scale = side ? scale : -scale;
|
||||||
|
float edge = side ? w_min : w_max;
|
||||||
|
float q0 = round(edge / scale);
|
||||||
|
bool at_zero = q0 == 0.0f;
|
||||||
|
scale = at_zero ? scale : edge / q0;
|
||||||
|
float bias = at_zero ? 0 : edge;
|
||||||
|
|
||||||
|
// Write out the scales and biases
|
||||||
|
size_t gindex = in_index / group_size;
|
||||||
|
if (in_index % group_size == 0) {
|
||||||
|
scales[gindex] = static_cast<T>(scale);
|
||||||
|
biases[gindex] = static_cast<T>(bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
using OutType = std::conditional_t<bits == 5, uint64_t, uint32_t>;
|
||||||
|
OutType output = 0;
|
||||||
|
|
||||||
|
#pragma clang loop unroll(full)
|
||||||
|
for (int i = 0; i < values_per_reduce; i++) {
|
||||||
|
uint8_t val = min(round((w_thread[i] - bias) / scale), n_bins);
|
||||||
|
if (bits == 8) {
|
||||||
|
output = val;
|
||||||
|
} else {
|
||||||
|
output |= val << (bits * (i % pack_factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pack_factor < values_per_reduce && i % pack_factor == pack_factor - 1) {
|
||||||
|
out[out_index + i / pack_factor] = output;
|
||||||
|
output = 0;
|
||||||
|
} else {
|
||||||
|
#pragma clang loop unroll(full)
|
||||||
|
for (int j = 1; j < writes_per_reduce; j++) {
|
||||||
|
uint8_t sval = warp.shfl_down(val, j);
|
||||||
|
output |= static_cast<OutType>(sval)
|
||||||
|
<< (bits * (j * values_per_reduce + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if constexpr (bits == 3 || bits == 6) {
|
||||||
|
if (in_index % pack_factor == 0 && out_index % bytes_per_pack == 0) {
|
||||||
|
out[out_index] = output & 0xff;
|
||||||
|
out[out_index + 1] = (output & 0xff00) >> 8;
|
||||||
|
out[out_index + 2] = (output & 0xff0000) >> 16;
|
||||||
|
}
|
||||||
|
} else if constexpr (bits == 5) {
|
||||||
|
if (in_index % pack_factor == 0 && out_index % bytes_per_pack == 0) {
|
||||||
|
out[out_index] = output & 0xff;
|
||||||
|
out[out_index + 1] = (output & 0xff00) >> 8;
|
||||||
|
out[out_index + 2] = (output & 0xff0000) >> 16;
|
||||||
|
out[out_index + 3] = (output & 0xff000000) >> 24;
|
||||||
|
out[out_index + 4] = (output & 0xff00000000) >> 32;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr (writes_per_reduce > 0) {
|
||||||
|
if (out_index % writes_per_reduce == 0) {
|
||||||
|
out[out_index / writes_per_reduce] = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, int group_size, int bits>
|
||||||
|
__global__ void affine_dequantize(
|
||||||
|
const uint8_t* w,
|
||||||
|
const T* scales,
|
||||||
|
const T* biases,
|
||||||
|
T* out,
|
||||||
|
size_t size) {
|
||||||
|
auto block_size = cg::this_thread_block().dim_threads();
|
||||||
|
auto block_idx = cg::this_thread_block().group_index();
|
||||||
|
auto idx_in_block = cg::this_thread_block().thread_index();
|
||||||
|
|
||||||
|
auto tidx = block_idx.x * block_size.x + idx_in_block.x;
|
||||||
|
auto tidy = block_idx.y * block_size.y + idx_in_block.y;
|
||||||
|
|
||||||
|
auto grid_dim_x =
|
||||||
|
cg::this_grid().dim_blocks().x * cg::this_grid().block_index().x;
|
||||||
|
|
||||||
|
constexpr int pack_factor = get_pack_factor<bits, 8>();
|
||||||
|
constexpr int bytes_per_pack = get_bytes_per_pack<bits>();
|
||||||
|
|
||||||
|
size_t offset = tidx + grid_dim_x * size_t(tidy);
|
||||||
|
size_t oindex = offset * pack_factor;
|
||||||
|
|
||||||
|
if (oindex >= size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t gindex = oindex / group_size;
|
||||||
|
T scale = scales[gindex];
|
||||||
|
T bias = biases[gindex];
|
||||||
|
out += oindex;
|
||||||
|
|
||||||
|
if constexpr (bits == 3) {
|
||||||
|
w += offset * bytes_per_pack;
|
||||||
|
out[0] = static_cast<T>(w[0] & 0x7) * scale + bias;
|
||||||
|
out[1] = static_cast<T>((w[0] & 0x38) >> 3) * scale + bias;
|
||||||
|
out[2] = (static_cast<T>((w[0] & 0xc0) >> 6) +
|
||||||
|
static_cast<T>((w[1] & 0x1) << 2)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[3] = static_cast<T>((w[1] & 0xe) >> 1) * scale + bias;
|
||||||
|
out[4] = static_cast<T>((w[1] & 0x70) >> 4) * scale + bias;
|
||||||
|
out[5] = (static_cast<T>((w[1] & 0x80) >> 7) +
|
||||||
|
static_cast<T>((w[2] & 0x3) << 1)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[6] = static_cast<T>((w[2] & 0x1c) >> 2) * scale + bias;
|
||||||
|
out[7] = static_cast<T>((w[2] & 0xe0) >> 5) * scale + bias;
|
||||||
|
} else if constexpr (bits == 5) {
|
||||||
|
w += offset * bytes_per_pack;
|
||||||
|
out[0] = static_cast<T>(w[0] & 0x1f) * scale + bias;
|
||||||
|
out[1] = (static_cast<T>((w[0] & 0xe0) >> 5) +
|
||||||
|
static_cast<T>((w[1] & 0x3) << 3)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[2] = static_cast<T>((w[1] & 0x7c) >> 2) * scale + bias;
|
||||||
|
out[3] = (static_cast<T>((w[1] & 0x80) >> 7) +
|
||||||
|
static_cast<T>((w[2] & 0xf) << 1)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[4] = (static_cast<T>((w[2] & 0xf0) >> 4) +
|
||||||
|
static_cast<T>((w[3] & 0x1) << 4)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[5] = static_cast<T>((w[3] & 0x3e) >> 1) * scale + bias;
|
||||||
|
out[6] = (static_cast<T>((w[3] & 0xc0) >> 6) +
|
||||||
|
static_cast<T>((w[4] & 0x7) << 2)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[7] = static_cast<T>((w[4] & 0xf8) >> 3) * scale + bias;
|
||||||
|
} else if constexpr (bits == 6) {
|
||||||
|
w += offset * bytes_per_pack;
|
||||||
|
out[0] = static_cast<T>(w[0] & 0x3f) * scale + bias;
|
||||||
|
out[1] = (static_cast<T>((w[0] >> 6) & 0x03) +
|
||||||
|
static_cast<T>((w[1] & 0x0f) << 2)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[2] = (static_cast<T>((w[1] >> 4) & 0x0f) +
|
||||||
|
static_cast<T>((w[2] & 0x03) << 4)) *
|
||||||
|
scale +
|
||||||
|
bias;
|
||||||
|
out[3] = static_cast<T>((w[2] >> 2) & 0x3f) * scale + bias;
|
||||||
|
} else {
|
||||||
|
uint val = w[offset];
|
||||||
|
#pragma clang loop unroll(full)
|
||||||
|
for (int i = 0; i < pack_factor; i++) {
|
||||||
|
uint8_t d;
|
||||||
|
if (bits == 2) {
|
||||||
|
d = (val >> (bits * i)) & 0x03;
|
||||||
|
} else if (bits == 4) {
|
||||||
|
d = (val >> (bits * i)) & 0x0f;
|
||||||
|
} else if (bits == 8) {
|
||||||
|
d = val;
|
||||||
|
}
|
||||||
|
out[i] = scale * static_cast<T>(d) + bias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cu
|
||||||
|
|
||||||
|
void affine_quantize(
|
||||||
|
const array& w,
|
||||||
|
array& wq,
|
||||||
|
array& scales,
|
||||||
|
array& biases,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s) {
|
||||||
|
// Calculate the number of elements per thread
|
||||||
|
int per_thread = group_size_ / WARP_SIZE;
|
||||||
|
size_t size = w.size() / per_thread;
|
||||||
|
|
||||||
|
// Calculate the thread grid that we need to launch
|
||||||
|
bool large = size > UINT_MAX;
|
||||||
|
auto grid_shape = w.shape();
|
||||||
|
grid_shape.back() /= per_thread;
|
||||||
|
|
||||||
|
enc.set_input_array(w);
|
||||||
|
enc.set_output_array(wq);
|
||||||
|
enc.set_output_array(scales);
|
||||||
|
enc.set_output_array(biases);
|
||||||
|
dispatch_float_types(w.dtype(), "affine_quantize", [&](auto type_tag) {
|
||||||
|
dispatch_groups(group_size_, [&](auto group_size) {
|
||||||
|
dispatch_bits(bits_, [&](auto bits) {
|
||||||
|
using T = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
||||||
|
auto kernel = cu::affine_quantize<T, group_size.value, bits.value>;
|
||||||
|
auto [num_blocks, block_dims] =
|
||||||
|
get_launch_args(kernel, size, grid_shape, w.strides(), large);
|
||||||
|
enc.add_kernel_node(
|
||||||
|
kernel,
|
||||||
|
num_blocks,
|
||||||
|
block_dims,
|
||||||
|
0,
|
||||||
|
w.data<T>(),
|
||||||
|
wq.data<uint8_t>(),
|
||||||
|
scales.data<T>(),
|
||||||
|
biases.data<T>(),
|
||||||
|
w.size());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void affine_dequantize(
|
||||||
|
const array& wq,
|
||||||
|
const array& scales,
|
||||||
|
const array& biases,
|
||||||
|
array& w,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s) {
|
||||||
|
// Calculate how many numbers we pack together. For 2, 4, 8 bits we pack in
|
||||||
|
// one uint8, for 3, 6 in 3 uint8 and for 5 in 5 uint8.
|
||||||
|
constexpr int uint8_per_uint32 = 4;
|
||||||
|
int packs_per_int;
|
||||||
|
switch (bits_) {
|
||||||
|
case 3:
|
||||||
|
case 5:
|
||||||
|
packs_per_int = 8;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
packs_per_int = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
packs_per_int = 8 / bits_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = w.size() / packs_per_int;
|
||||||
|
bool large = size > UINT_MAX;
|
||||||
|
auto grid_shape = w.shape();
|
||||||
|
grid_shape.back() *= uint8_per_uint32;
|
||||||
|
|
||||||
|
enc.set_input_array(wq);
|
||||||
|
enc.set_input_array(scales);
|
||||||
|
enc.set_input_array(biases);
|
||||||
|
enc.set_output_array(w);
|
||||||
|
dispatch_float_types(w.dtype(), "affine_quantize", [&](auto type_tag) {
|
||||||
|
dispatch_groups(group_size_, [&](auto group_size) {
|
||||||
|
dispatch_bits(bits_, [&](auto bits) {
|
||||||
|
using T = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
||||||
|
auto kernel = cu::affine_dequantize<T, group_size.value, bits.value>;
|
||||||
|
auto [num_blocks, block_dims] =
|
||||||
|
get_launch_args(kernel, size, grid_shape, w.strides(), large);
|
||||||
|
enc.add_kernel_node(
|
||||||
|
kernel,
|
||||||
|
num_blocks,
|
||||||
|
block_dims,
|
||||||
|
0,
|
||||||
|
wq.data<uint8_t>(),
|
||||||
|
scales.data<T>(),
|
||||||
|
biases.data<T>(),
|
||||||
|
w.data<T>(),
|
||||||
|
w.size());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
228
mlx/backend/cuda/quantized/qmm.cu
Normal file
228
mlx/backend/cuda/quantized/qmm.cu
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
|
#include "mlx/backend/cuda/matmul/mma.cuh"
|
||||||
|
#include "mlx/backend/cuda/matmul/tiles.cuh"
|
||||||
|
#include "mlx/backend/cuda/quantized/quantized_utils.cuh"
|
||||||
|
#include "mlx/dtype_utils.h"
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
|
||||||
|
namespace cu {
|
||||||
|
|
||||||
|
template <int NUM_WARPS, int group_size, int bits, typename T, typename Tile>
|
||||||
|
__device__ inline void load_quantized(
|
||||||
|
Tile& tile,
|
||||||
|
const uint8_t* x,
|
||||||
|
const T* scales,
|
||||||
|
const T* biases,
|
||||||
|
int N) {
|
||||||
|
constexpr int NUM_THREADS = NUM_WARPS * 32;
|
||||||
|
constexpr int ELEMENTS_PER_LOAD = sizeof(uint32_t) * get_pack_factor<bits>();
|
||||||
|
constexpr int NUM_LOADS = Tile::NUMEL / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int NUM_LOADS_PER_THREAD = NUM_LOADS / NUM_THREADS;
|
||||||
|
constexpr int NUM_LOADS_PER_ROW = Tile::COLS / ELEMENTS_PER_LOAD;
|
||||||
|
constexpr int STEP_ROWS = NUM_THREADS / NUM_LOADS_PER_ROW;
|
||||||
|
constexpr int MASK = (1 << bits) - 1;
|
||||||
|
|
||||||
|
const int row = threadIdx.x / NUM_LOADS_PER_ROW;
|
||||||
|
const int col = threadIdx.x % NUM_LOADS_PER_ROW;
|
||||||
|
|
||||||
|
const int Nx = N / get_pack_factor<bits>();
|
||||||
|
const int Ng = N / group_size;
|
||||||
|
|
||||||
|
x += row * Nx + col * (ELEMENTS_PER_LOAD / get_pack_factor<bits>());
|
||||||
|
scales += row * Ng + col * ELEMENTS_PER_LOAD / group_size;
|
||||||
|
biases += row * Ng + col * ELEMENTS_PER_LOAD / group_size;
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int i = 0; i < NUM_LOADS_PER_THREAD; i++) {
|
||||||
|
T vs[ELEMENTS_PER_LOAD];
|
||||||
|
uint32_t w = *reinterpret_cast<const uint32_t*>(x + i * STEP_ROWS * Nx);
|
||||||
|
T s = scales[i * STEP_ROWS * Ng];
|
||||||
|
T b = biases[i * STEP_ROWS * Ng];
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int j = 0; j < ELEMENTS_PER_LOAD; j++) {
|
||||||
|
vs[j] = static_cast<T>((w >> (j * bits)) & MASK) * s + b;
|
||||||
|
}
|
||||||
|
tile.store(vs, row + i * STEP_ROWS, col * ELEMENTS_PER_LOAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename T,
|
||||||
|
int BM,
|
||||||
|
int BN,
|
||||||
|
int BK,
|
||||||
|
int group_size,
|
||||||
|
int bits,
|
||||||
|
bool aligned_M>
|
||||||
|
__global__ void qmm_t(
|
||||||
|
const T* x,
|
||||||
|
const uint8_t* w,
|
||||||
|
const T* scales,
|
||||||
|
const T* biases,
|
||||||
|
T* y,
|
||||||
|
int M,
|
||||||
|
int N,
|
||||||
|
int K) {
|
||||||
|
constexpr int WARPS_M = 2;
|
||||||
|
constexpr int WARPS_N = 4;
|
||||||
|
constexpr int NUM_WARPS = WARPS_M * WARPS_N;
|
||||||
|
constexpr int WARP_STEP_M = BM / WARPS_M;
|
||||||
|
constexpr int WARP_STEP_N = BN / WARPS_N;
|
||||||
|
|
||||||
|
const int warpid = threadIdx.x / 32;
|
||||||
|
const int laneid = threadIdx.x % 32;
|
||||||
|
const int wm = warpid / WARPS_N;
|
||||||
|
const int wn = warpid % WARPS_N;
|
||||||
|
const int offset_m = wm * WARP_STEP_M;
|
||||||
|
const int offset_n = wn * WARP_STEP_N;
|
||||||
|
|
||||||
|
extern __shared__ char shmem[];
|
||||||
|
SharedTile<T, BM, BK>(&xs)[1] = *(SharedTile<T, BM, BK>(*)[1])(&shmem[0]);
|
||||||
|
SharedTile<T, BN, BK>(&ws)[1] =
|
||||||
|
*(SharedTile<T, BN, BK>(*)[1])(&shmem[1 * sizeof(T) * BM * BK]);
|
||||||
|
|
||||||
|
RegisterTile<float, BM / WARPS_M, BN / WARPS_N> C;
|
||||||
|
RegisterTile<T, BM / WARPS_M, 16> A;
|
||||||
|
RegisterTile<T, BN / WARPS_N, 16> B;
|
||||||
|
|
||||||
|
const int max_rows = M - blockIdx.y * BM;
|
||||||
|
|
||||||
|
x += blockIdx.y * BM * K;
|
||||||
|
w += blockIdx.x * BN * K / get_pack_factor<bits>();
|
||||||
|
scales += blockIdx.x * BN * K / group_size;
|
||||||
|
biases += blockIdx.x * BN * K / group_size;
|
||||||
|
y += blockIdx.y * BM * N + blockIdx.x * BN;
|
||||||
|
|
||||||
|
C.fill(0);
|
||||||
|
|
||||||
|
int tic = 0;
|
||||||
|
uint32_t base_addr_xs[1], base_addr_ws[1];
|
||||||
|
base_addr_xs[0] = __cvta_generic_to_shared(&xs[0].data[0]);
|
||||||
|
base_addr_ws[0] = __cvta_generic_to_shared(&ws[0].data[0]);
|
||||||
|
|
||||||
|
if (aligned_M || max_rows >= BM) {
|
||||||
|
for (int k_block = 0; k_block < K; k_block += BK) {
|
||||||
|
load_async<NUM_WARPS>(xs[tic], base_addr_xs[tic], x + k_block, K);
|
||||||
|
cp_async_commit();
|
||||||
|
load_quantized<NUM_WARPS, group_size, bits>(
|
||||||
|
ws[tic],
|
||||||
|
w + k_block / get_pack_factor<bits>(),
|
||||||
|
scales + k_block / group_size,
|
||||||
|
biases + k_block / group_size,
|
||||||
|
K);
|
||||||
|
cp_async_wait_all();
|
||||||
|
__syncthreads();
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int k = 0; k < BK / 16; k++) {
|
||||||
|
A.load(
|
||||||
|
xs[tic],
|
||||||
|
base_addr_xs[tic],
|
||||||
|
offset_m + laneid % 16,
|
||||||
|
k * 16 + laneid / 16 * 8);
|
||||||
|
B.load(
|
||||||
|
ws[tic],
|
||||||
|
base_addr_ws[tic],
|
||||||
|
offset_n + laneid % 16,
|
||||||
|
k * 16 + laneid / 16 * 8);
|
||||||
|
mma_t(C, A, B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
C.store_global(y, N, offset_m, offset_n);
|
||||||
|
} else {
|
||||||
|
for (int k_block = 0; k_block < K; k_block += BK) {
|
||||||
|
load_async_safe<NUM_WARPS>(
|
||||||
|
xs[tic], base_addr_xs[tic], x + k_block, K, max_rows);
|
||||||
|
cp_async_commit();
|
||||||
|
load_quantized<NUM_WARPS, group_size, bits>(
|
||||||
|
ws[tic],
|
||||||
|
w + k_block / get_pack_factor<bits>(),
|
||||||
|
scales + k_block / group_size,
|
||||||
|
biases + k_block / group_size,
|
||||||
|
K);
|
||||||
|
cp_async_wait_all();
|
||||||
|
__syncthreads();
|
||||||
|
|
||||||
|
MLX_UNROLL
|
||||||
|
for (int k = 0; k < BK / 16; k++) {
|
||||||
|
A.load(
|
||||||
|
xs[tic],
|
||||||
|
base_addr_xs[tic],
|
||||||
|
offset_m + laneid % 16,
|
||||||
|
k * 16 + laneid / 16 * 8);
|
||||||
|
B.load(
|
||||||
|
ws[tic],
|
||||||
|
base_addr_ws[tic],
|
||||||
|
offset_n + laneid % 16,
|
||||||
|
k * 16 + laneid / 16 * 8);
|
||||||
|
mma_t(C, A, B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
C.store_global_safe(y, N, offset_m, offset_n, max_rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cu
|
||||||
|
|
||||||
|
void qmm(
|
||||||
|
const array& x,
|
||||||
|
const array& w,
|
||||||
|
const array& scales,
|
||||||
|
const array& biases,
|
||||||
|
array& out,
|
||||||
|
bool transpose_,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
int M,
|
||||||
|
int N,
|
||||||
|
int K,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s) {
|
||||||
|
if (x.dtype() != bfloat16) {
|
||||||
|
throw std::invalid_argument("[qmm] Only bfloat16 is supported for now");
|
||||||
|
}
|
||||||
|
if (!transpose_) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"[qmm] Only transposed matmul is supported for now");
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_float_types(x.dtype(), "qmm", [&](auto type_tag) {
|
||||||
|
dispatch_groups(group_size_, [&](auto group_size) {
|
||||||
|
dispatch_bits(bits_, [&](auto bits) {
|
||||||
|
using DataType = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
||||||
|
|
||||||
|
constexpr int BM = 128;
|
||||||
|
constexpr int BN = 128;
|
||||||
|
constexpr int BK = 32;
|
||||||
|
auto kernel =
|
||||||
|
cu::qmm_t<DataType, BM, BN, BK, group_size.value, bits.value, true>;
|
||||||
|
if (M % BM != 0) {
|
||||||
|
kernel = cu::
|
||||||
|
qmm_t<DataType, BM, BN, BK, group_size.value, bits.value, false>;
|
||||||
|
}
|
||||||
|
|
||||||
|
dim3 grid((N + BN - 1) / BN, (M + BM - 1) / BM);
|
||||||
|
|
||||||
|
enc.add_kernel_node(
|
||||||
|
kernel,
|
||||||
|
grid,
|
||||||
|
2 * 4 * 32,
|
||||||
|
1 * sizeof(DataType) * (BM * BK + BN * BK),
|
||||||
|
x.data<DataType>(),
|
||||||
|
w.data<uint8_t>(),
|
||||||
|
scales.data<DataType>(),
|
||||||
|
biases.data<DataType>(),
|
||||||
|
out.data<DataType>(),
|
||||||
|
M,
|
||||||
|
N,
|
||||||
|
K);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
113
mlx/backend/cuda/quantized/quantized.cu
Normal file
113
mlx/backend/cuda/quantized/quantized.cu
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
|
#include "mlx/backend/cuda/quantized/quantized.cuh"
|
||||||
|
#include "mlx/backend/gpu/copy.h"
|
||||||
|
#include "mlx/dtype_utils.h"
|
||||||
|
#include "mlx/fast_primitives.h"
|
||||||
|
|
||||||
|
#include <cooperative_groups.h>
|
||||||
|
#include <cooperative_groups/reduce.h>
|
||||||
|
#include <nvtx3/nvtx3.hpp>
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
inline array ensure_row_contiguous(
|
||||||
|
const array& x,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s) {
|
||||||
|
if (!x.flags().row_contiguous) {
|
||||||
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
|
enc.add_temporary(x_copy);
|
||||||
|
return x_copy;
|
||||||
|
} else {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline array ensure_row_contiguous_matrix(
|
||||||
|
const array& x,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s) {
|
||||||
|
auto stride_0 = x.strides()[x.ndim() - 2];
|
||||||
|
auto stride_1 = x.strides()[x.ndim() - 1];
|
||||||
|
if (stride_0 == x.shape(-1) && stride_1 == 1) {
|
||||||
|
return x;
|
||||||
|
} else {
|
||||||
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
|
enc.add_temporary(x_copy);
|
||||||
|
return x_copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void QuantizedMatmul::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
auto& s = stream();
|
||||||
|
auto& d = cu::device(s.device);
|
||||||
|
auto& enc = d.get_command_encoder(s);
|
||||||
|
|
||||||
|
out.set_data(allocator::malloc(out.nbytes()));
|
||||||
|
|
||||||
|
// Make sure the last two dims of x and w, s, b are contiguous. This should
|
||||||
|
// be relaxed for x.
|
||||||
|
array x = ensure_row_contiguous_matrix(inputs[0], enc, s);
|
||||||
|
array w = ensure_row_contiguous_matrix(inputs[1], enc, s);
|
||||||
|
array scales = ensure_row_contiguous_matrix(inputs[2], enc, s);
|
||||||
|
array biases = ensure_row_contiguous_matrix(inputs[3], enc, s);
|
||||||
|
|
||||||
|
// Extract the matmul shapes
|
||||||
|
bool non_batched = w.ndim() == 2 && x.flags().row_contiguous;
|
||||||
|
int K = x.shape(-1);
|
||||||
|
int M = non_batched ? x.size() / K : x.shape(-2);
|
||||||
|
int N = out.shape(-1);
|
||||||
|
|
||||||
|
qmm(x,
|
||||||
|
w,
|
||||||
|
scales,
|
||||||
|
biases,
|
||||||
|
out,
|
||||||
|
transpose_,
|
||||||
|
group_size_,
|
||||||
|
bits_,
|
||||||
|
M,
|
||||||
|
N,
|
||||||
|
K,
|
||||||
|
enc,
|
||||||
|
s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast::AffineQuantize::eval_gpu(
|
||||||
|
const std::vector<array>& inputs,
|
||||||
|
std::vector<array>& outputs) {
|
||||||
|
auto& s = stream();
|
||||||
|
auto& d = cu::device(s.device);
|
||||||
|
auto& enc = d.get_command_encoder(s);
|
||||||
|
|
||||||
|
if (dequantize_) {
|
||||||
|
auto wq = ensure_row_contiguous(inputs[0], enc, s);
|
||||||
|
auto scales = ensure_row_contiguous(inputs[1], enc, s);
|
||||||
|
auto biases = ensure_row_contiguous(inputs[2], enc, s);
|
||||||
|
auto& w = outputs[0];
|
||||||
|
|
||||||
|
w.set_data(allocator::malloc(w.nbytes()));
|
||||||
|
|
||||||
|
affine_dequantize(wq, scales, biases, w, group_size_, bits_, enc, s);
|
||||||
|
} else {
|
||||||
|
auto w = ensure_row_contiguous(inputs[0], enc, s);
|
||||||
|
auto& wq = outputs[0];
|
||||||
|
auto& scales = outputs[1];
|
||||||
|
auto& biases = outputs[2];
|
||||||
|
|
||||||
|
wq.set_data(allocator::malloc(wq.nbytes()));
|
||||||
|
scales.set_data(allocator::malloc(scales.nbytes()));
|
||||||
|
biases.set_data(allocator::malloc(biases.nbytes()));
|
||||||
|
|
||||||
|
affine_quantize(w, wq, scales, biases, group_size_, bits_, enc, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
42
mlx/backend/cuda/quantized/quantized.cuh
Normal file
42
mlx/backend/cuda/quantized/quantized.cuh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
|
||||||
|
void affine_quantize(
|
||||||
|
const array& w,
|
||||||
|
array& wq,
|
||||||
|
array& scales,
|
||||||
|
array& biases,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s);
|
||||||
|
|
||||||
|
void affine_dequantize(
|
||||||
|
const array& wq,
|
||||||
|
const array& scales,
|
||||||
|
const array& biases,
|
||||||
|
array& w,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s);
|
||||||
|
|
||||||
|
void qmm(
|
||||||
|
const array& x,
|
||||||
|
const array& w,
|
||||||
|
const array& scales,
|
||||||
|
const array& biases,
|
||||||
|
array& out,
|
||||||
|
bool transpose_,
|
||||||
|
int group_size_,
|
||||||
|
int bits_,
|
||||||
|
int M,
|
||||||
|
int N,
|
||||||
|
int K,
|
||||||
|
cu::CommandEncoder& enc,
|
||||||
|
const Stream& s);
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
59
mlx/backend/cuda/quantized/quantized_utils.cuh
Normal file
59
mlx/backend/cuda/quantized/quantized_utils.cuh
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
|
||||||
|
namespace cu {
|
||||||
|
|
||||||
|
template <int bits, int wsize = 8>
|
||||||
|
inline constexpr __device__ short get_pack_factor() {
|
||||||
|
return (bits == 3 || bits == 5) ? 8 : (bits == 6 ? 4 : wsize / bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int bits, int wsize = 8>
|
||||||
|
inline constexpr __device__ short get_bytes_per_pack() {
|
||||||
|
constexpr int power_of_2_bits = (bits & (bits - 1)) == 0;
|
||||||
|
return power_of_2_bits ? (wsize / 8) : (bits == 5 ? 5 : 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cu
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void dispatch_groups(int group_size, F&& f) {
|
||||||
|
switch (group_size) {
|
||||||
|
case 32:
|
||||||
|
f(std::integral_constant<int, 32>{});
|
||||||
|
break;
|
||||||
|
case 64:
|
||||||
|
f(std::integral_constant<int, 64>{});
|
||||||
|
break;
|
||||||
|
case 128:
|
||||||
|
f(std::integral_constant<int, 128>{});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void dispatch_bits(int bits, F&& f) {
|
||||||
|
switch (bits) {
|
||||||
|
case 2:
|
||||||
|
f(std::integral_constant<int, 2>{});
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
f(std::integral_constant<int, 3>{});
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
f(std::integral_constant<int, 4>{});
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
f(std::integral_constant<int, 5>{});
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
f(std::integral_constant<int, 6>{});
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
f(std::integral_constant<int, 8>{});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
@@ -170,6 +170,7 @@ void RandomBits::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
cu::rbitsc,
|
cu::rbitsc,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
keys.data<uint32_t>(),
|
keys.data<uint32_t>(),
|
||||||
out.data<uint8_t>(),
|
out.data<uint8_t>(),
|
||||||
grid_dims,
|
grid_dims,
|
||||||
@@ -180,6 +181,7 @@ void RandomBits::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
cu::rbits,
|
cu::rbits,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
keys.data<uint32_t>(),
|
keys.data<uint32_t>(),
|
||||||
out.data<uint8_t>(),
|
out.data<uint8_t>(),
|
||||||
grid_dims,
|
grid_dims,
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ void Reduce::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (plan.type == GeneralReduce || broadcasted || !in.flags().contiguous) {
|
if (plan.type == GeneralReduce || broadcasted || !in.flags().contiguous) {
|
||||||
array in_copy(in.shape(), in.dtype(), nullptr, {});
|
array in_copy = contiguous_copy_gpu(in, s);
|
||||||
copy_gpu(in, in_copy, CopyType::General, s);
|
|
||||||
encoder.add_temporary(in_copy);
|
encoder.add_temporary(in_copy);
|
||||||
in = in_copy;
|
in = in_copy;
|
||||||
plan = get_reduction_plan(in, axes_);
|
plan = get_reduction_plan(in, axes_);
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ __global__ void all_reduce(T* in, U* out, size_t block_step, size_t size) {
|
|||||||
for (; i + block.size() * N <= check; i += block.size() * N) {
|
for (; i + block.size() * N <= check; i += block.size() * N) {
|
||||||
cub::LoadDirectBlockedVectorized<T, N>(block.thread_rank(), in + i, vals);
|
cub::LoadDirectBlockedVectorized<T, N>(block.thread_rank(), in + i, vals);
|
||||||
for (int j = 0; j < N; j++) {
|
for (int j = 0; j < N; j++) {
|
||||||
accs[0] = op(accs[0], __cast<U, T>(vals[j]));
|
accs[0] = op(accs[0], cast_to<U>(vals[j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < check) {
|
if (i < check) {
|
||||||
cub::LoadDirectBlocked(
|
cub::LoadDirectBlocked(
|
||||||
block.thread_rank(), in + i, vals, check - i, __cast<T, U>(init));
|
block.thread_rank(), in + i, vals, check - i, cast_to<T>(init));
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
accs[0] = op(accs[0], __cast<U, T>(vals[i]));
|
accs[0] = op(accs[0], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ void all_reduce(
|
|||||||
kernel,
|
kernel,
|
||||||
blocks,
|
blocks,
|
||||||
threads,
|
threads,
|
||||||
|
0,
|
||||||
static_cast<T*>(indata),
|
static_cast<T*>(indata),
|
||||||
intermediate.data<U>(),
|
intermediate.data<U>(),
|
||||||
block_step,
|
block_step,
|
||||||
@@ -146,6 +147,7 @@ void all_reduce(
|
|||||||
kernel,
|
kernel,
|
||||||
blocks,
|
blocks,
|
||||||
threads,
|
threads,
|
||||||
|
0,
|
||||||
static_cast<T*>(indata),
|
static_cast<T*>(indata),
|
||||||
out.data<U>(),
|
out.data<U>(),
|
||||||
block_step,
|
block_step,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
#include "mlx/backend/cuda/device/cast_op.cuh"
|
|
||||||
#include "mlx/backend/cuda/reduce/reduce.cuh"
|
#include "mlx/backend/cuda/reduce/reduce.cuh"
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
#include <cooperative_groups.h>
|
||||||
@@ -128,7 +127,7 @@ col_reduce_looped(T* in, U* out, const __grid_constant__ ColReduceArgs args) {
|
|||||||
T vals[N_READS];
|
T vals[N_READS];
|
||||||
cub::LoadDirectBlockedVectorized(thread_x, in + loop.location(), vals);
|
cub::LoadDirectBlockedVectorized(thread_x, in + loop.location(), vals);
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
totals[i] = op(totals[i], __cast<U, T>(vals[i]));
|
totals[i] = op(totals[i], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
||||||
}
|
}
|
||||||
@@ -137,7 +136,7 @@ col_reduce_looped(T* in, U* out, const __grid_constant__ ColReduceArgs args) {
|
|||||||
T vals[N_READS];
|
T vals[N_READS];
|
||||||
cub::LoadDirectBlocked(thread_x, in + loop.location(), vals);
|
cub::LoadDirectBlocked(thread_x, in + loop.location(), vals);
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
totals[i] = op(totals[i], __cast<U, T>(vals[i]));
|
totals[i] = op(totals[i], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
||||||
}
|
}
|
||||||
@@ -150,9 +149,9 @@ col_reduce_looped(T* in, U* out, const __grid_constant__ ColReduceArgs args) {
|
|||||||
in + loop.location(),
|
in + loop.location(),
|
||||||
vals,
|
vals,
|
||||||
args.reduction_stride - tile_x * BN,
|
args.reduction_stride - tile_x * BN,
|
||||||
__cast<T, U>(ReduceInit<Op, T>::value()));
|
cast_to<T>(ReduceInit<Op, T>::value()));
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
totals[i] = op(totals[i], __cast<U, T>(vals[i]));
|
totals[i] = op(totals[i], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
loop.next(BM, args.reduce_shape.data(), args.reduce_strides.data());
|
||||||
}
|
}
|
||||||
@@ -231,7 +230,7 @@ void col_reduce_looped(
|
|||||||
auto kernel =
|
auto kernel =
|
||||||
cu::col_reduce_looped<T, U, OP, reduce_ndim(), BM, BN, N_READS>;
|
cu::col_reduce_looped<T, U, OP, reduce_ndim(), BM, BN, N_READS>;
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel, grid, blocks, indata, out.data<U>(), args);
|
kernel, grid, blocks, 0, indata, out.data<U>(), args);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ void init_reduce(
|
|||||||
dim3 grid = get_2d_grid_dims(out.shape(), out.strides());
|
dim3 grid = get_2d_grid_dims(out.shape(), out.strides());
|
||||||
dim3 block(grid.x < 1024 ? grid.x : 1024, 1, 1);
|
dim3 block(grid.x < 1024 ? grid.x : 1024, 1, 1);
|
||||||
grid.x = (grid.x + 1023) / 1024;
|
grid.x = (grid.x + 1023) / 1024;
|
||||||
encoder.add_kernel_node(kernel, grid, block, out.data<U>(), out.size());
|
encoder.add_kernel_node(
|
||||||
|
kernel, grid, block, 0, out.data<U>(), out.size());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include "mlx/backend/common/reduce.h"
|
#include "mlx/backend/common/reduce.h"
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
#include "mlx/backend/cuda/reduce/reduce_ops.cuh"
|
#include "mlx/backend/cuda/reduce/reduce_ops.cuh"
|
||||||
#include "mlx/dtype_utils.h"
|
#include "mlx/dtype_utils.h"
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device/atomic_ops.cuh"
|
||||||
|
#include "mlx/backend/cuda/device/cast_op.cuh"
|
||||||
#include "mlx/backend/cuda/device/utils.cuh"
|
#include "mlx/backend/cuda/device/utils.cuh"
|
||||||
#include "mlx/backend/cuda/reduce/reduce_utils.cuh"
|
#include "mlx/backend/cuda/reduce/reduce_utils.cuh"
|
||||||
|
|
||||||
@@ -40,15 +42,15 @@ struct Sum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__device__ void atomic_update(__nv_bfloat16* x, __nv_bfloat16 y) {
|
__device__ void atomic_update(__nv_bfloat16* x, __nv_bfloat16 y) {
|
||||||
atomicAdd(x, y);
|
atomic_add(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ void atomic_update(int* x, int y) {
|
__device__ void atomic_update(int* x, int y) {
|
||||||
atomicAdd(x, y);
|
atomic_add(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
__device__ void atomic_update(float* x, float y) {
|
__device__ void atomic_update(float* x, float y) {
|
||||||
atomicAdd(x, y);
|
atomic_add(x, y);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,6 +69,18 @@ struct Prod {
|
|||||||
struct Min {
|
struct Min {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ __forceinline__ T operator()(T a, T b) {
|
__device__ __forceinline__ T operator()(T a, T b) {
|
||||||
|
if constexpr (is_complex_v<T>) {
|
||||||
|
if (isnan(a.real()) || isnan(a.imag())) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (isnan(b.real()) || isnan(b.imag())) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
} else if constexpr (!cuda::std::is_integral_v<T>) {
|
||||||
|
if (isnan(a) || isnan(b)) {
|
||||||
|
return cuda::std::numeric_limits<float>::quiet_NaN();
|
||||||
|
}
|
||||||
|
}
|
||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +93,18 @@ struct Min {
|
|||||||
struct Max {
|
struct Max {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
__device__ __forceinline__ T operator()(T a, T b) {
|
__device__ __forceinline__ T operator()(T a, T b) {
|
||||||
|
if constexpr (is_complex_v<T>) {
|
||||||
|
if (isnan(a.real()) || isnan(a.imag())) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (isnan(b.real()) || isnan(b.imag())) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
} else if constexpr (!cuda::std::is_integral_v<T>) {
|
||||||
|
if (isnan(a) || isnan(b)) {
|
||||||
|
return cuda::std::numeric_limits<float>::quiet_NaN();
|
||||||
|
}
|
||||||
|
}
|
||||||
return a > b ? a : b;
|
return a > b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,10 +175,10 @@ struct ReduceInit<Or, T> {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
struct ReduceInit<Sum, T> {
|
struct ReduceInit<Sum, T> {
|
||||||
static constexpr __host__ __device__ auto value() {
|
static constexpr __host__ __device__ auto value() {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return T{0, 0};
|
return T{0, 0};
|
||||||
} else {
|
} else {
|
||||||
return typename ReduceResult<Sum, T>::type{0};
|
return cast_to<typename ReduceResult<Sum, T>::type>(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,10 +186,10 @@ struct ReduceInit<Sum, T> {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
struct ReduceInit<Prod, T> {
|
struct ReduceInit<Prod, T> {
|
||||||
static constexpr __host__ __device__ auto value() {
|
static constexpr __host__ __device__ auto value() {
|
||||||
if constexpr (cuda::std::is_same_v<T, cuComplex>) {
|
if constexpr (is_complex_v<T>) {
|
||||||
return T{1, 0};
|
return T{1, 0};
|
||||||
} else {
|
} else {
|
||||||
return typename ReduceResult<Prod, T>::type{1};
|
return cast_to<typename ReduceResult<Prod, T>::type>(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
|
#include "mlx/backend/common/utils.h"
|
||||||
#include "mlx/backend/cuda/device/utils.cuh"
|
#include "mlx/backend/cuda/device/utils.cuh"
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
#include <cooperative_groups.h>
|
||||||
@@ -55,22 +56,6 @@ __device__ void atomic_reduce(T* x, T y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should make a custom complex type
|
|
||||||
template <typename U, typename T>
|
|
||||||
inline __device__ U __cast(T x) {
|
|
||||||
return static_cast<U>(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline __device__ bool __cast<bool, cuComplex>(cuComplex x) {
|
|
||||||
return x.x != 0 && x.y != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline __device__ cuComplex __cast<cuComplex, bool>(bool x) {
|
|
||||||
return x ? make_cuFloatComplex(1, 1) : make_cuFloatComplex(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, int N, typename Block, typename Warp, typename Op>
|
template <typename T, int N, typename Block, typename Warp, typename Op>
|
||||||
inline __device__ void
|
inline __device__ void
|
||||||
block_reduce(Block block, Warp warp, T (&vals)[N], T* smem, Op op, T init) {
|
block_reduce(Block block, Warp warp, T (&vals)[N], T* smem, Op op, T init) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
#include "mlx/backend/cuda/device/cast_op.cuh"
|
|
||||||
#include "mlx/backend/cuda/reduce/reduce.cuh"
|
#include "mlx/backend/cuda/reduce/reduce.cuh"
|
||||||
|
|
||||||
#include <cooperative_groups.h>
|
#include <cooperative_groups.h>
|
||||||
@@ -113,7 +112,7 @@ __global__ void row_reduce_simple(T* in, U* out, size_t n_rows, int size) {
|
|||||||
in + k * size + r * (block.size() * N),
|
in + k * size + r * (block.size() * N),
|
||||||
vals[k]);
|
vals[k]);
|
||||||
for (int j = 0; j < N; j++) {
|
for (int j = 0; j < N; j++) {
|
||||||
accs[k] = op(accs[k], __cast<U, T>(vals[k][j]));
|
accs[k] = op(accs[k], cast_to<U>(vals[k][j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +124,7 @@ __global__ void row_reduce_simple(T* in, U* out, size_t n_rows, int size) {
|
|||||||
in + k * size + r * (block.size() * N),
|
in + k * size + r * (block.size() * N),
|
||||||
vals[k]);
|
vals[k]);
|
||||||
for (int j = 0; j < N; j++) {
|
for (int j = 0; j < N; j++) {
|
||||||
accs[k] = op(accs[k], __cast<U, T>(vals[k][j]));
|
accs[k] = op(accs[k], cast_to<U>(vals[k][j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,9 +137,9 @@ __global__ void row_reduce_simple(T* in, U* out, size_t n_rows, int size) {
|
|||||||
in + k * size + final_offset,
|
in + k * size + final_offset,
|
||||||
vals[k],
|
vals[k],
|
||||||
size,
|
size,
|
||||||
__cast<T, U>(init));
|
cast_to<T>(init));
|
||||||
for (int j = 0; j < N; j++) {
|
for (int j = 0; j < N; j++) {
|
||||||
accs[k] = op(accs[k], __cast<U, T>(vals[k][j]));
|
accs[k] = op(accs[k], cast_to<U>(vals[k][j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +198,7 @@ __global__ void row_reduce_looped(
|
|||||||
in + loop.location() + r * BLOCK_DIM * N_READS,
|
in + loop.location() + r * BLOCK_DIM * N_READS,
|
||||||
vals);
|
vals);
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
total[0] = op(total[0], __cast<U, T>(vals[i]));
|
total[0] = op(total[0], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (final_offset < args.row_size) {
|
if (final_offset < args.row_size) {
|
||||||
@@ -209,9 +208,9 @@ __global__ void row_reduce_looped(
|
|||||||
in + loop.location() + final_offset,
|
in + loop.location() + final_offset,
|
||||||
vals,
|
vals,
|
||||||
args.row_size - final_offset,
|
args.row_size - final_offset,
|
||||||
__cast<T, U>(init));
|
cast_to<T>(init));
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
total[0] = op(total[0], __cast<U, T>(vals[i]));
|
total[0] = op(total[0], cast_to<U>(vals[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Maybe block.sync() here?
|
// TODO: Maybe block.sync() here?
|
||||||
@@ -270,7 +269,7 @@ void row_reduce_simple(
|
|||||||
|
|
||||||
int size = plan.shape.back();
|
int size = plan.shape.back();
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel, grid, block, indata, out.data<U>(), out.size(), size);
|
kernel, grid, block, 0, indata, out.data<U>(), out.size(), size);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -323,7 +322,7 @@ void row_reduce_looped(
|
|||||||
});
|
});
|
||||||
|
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel, grid, block, indata, out.data<U>(), out.size(), args);
|
kernel, grid, block, 0, indata, out.data<U>(), out.size(), args);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ __global__ void rms_norm(
|
|||||||
for (int r = 0; r < cuda::ceil_div(axis_size, BLOCK_DIM * N_READS); ++r) {
|
for (int r = 0; r < cuda::ceil_div(axis_size, BLOCK_DIM * N_READS); ++r) {
|
||||||
auto index = r * BLOCK_DIM + block.thread_rank();
|
auto index = r * BLOCK_DIM + block.thread_rank();
|
||||||
T xn[N_READS];
|
T xn[N_READS];
|
||||||
cub::LoadDirectBlocked(index, x, xn, axis_size, 0);
|
cub::LoadDirectBlocked(index, x, xn, axis_size, cast_to<T>(0));
|
||||||
for (int i = 0; i < N_READS; ++i) {
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
float t = static_cast<float>(xn[i]);
|
float t = static_cast<float>(xn[i]);
|
||||||
normalizer += t * t;
|
normalizer += t * t;
|
||||||
@@ -130,7 +130,7 @@ __global__ void rms_norm_vjp(
|
|||||||
T wn[N_READS] = {};
|
T wn[N_READS] = {};
|
||||||
T gn[N_READS] = {};
|
T gn[N_READS] = {};
|
||||||
auto index = r * BLOCK_DIM + block.thread_rank();
|
auto index = r * BLOCK_DIM + block.thread_rank();
|
||||||
cub::LoadDirectBlocked(index, x, xn, axis_size, 0);
|
cub::LoadDirectBlocked(index, x, xn, axis_size, cast_to<T>(0));
|
||||||
cub::LoadDirectBlocked(index, g, gn, axis_size);
|
cub::LoadDirectBlocked(index, g, gn, axis_size);
|
||||||
cub::LoadDirectBlocked(index, strided_iterator(w, w_stride), wn, axis_size);
|
cub::LoadDirectBlocked(index, strided_iterator(w, w_stride), wn, axis_size);
|
||||||
for (int i = 0; i < N_READS; i++) {
|
for (int i = 0; i < N_READS; i++) {
|
||||||
@@ -206,8 +206,7 @@ void RMSNorm::eval_gpu(
|
|||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
out.copy_shared_buffer(x_copy);
|
out.copy_shared_buffer(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
@@ -233,6 +232,7 @@ void RMSNorm::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
x.data<DataType>(),
|
x.data<DataType>(),
|
||||||
w.data<DataType>(),
|
w.data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
@@ -259,9 +259,7 @@ void RMSNormVJP::eval_gpu(
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
copied = true;
|
copied = true;
|
||||||
array x_copy(x.shape(), x.dtype(), nullptr, {});
|
return contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
return x_copy;
|
|
||||||
};
|
};
|
||||||
bool donate_x = inputs[0].is_donatable();
|
bool donate_x = inputs[0].is_donatable();
|
||||||
bool donate_g = inputs[2].is_donatable();
|
bool donate_g = inputs[2].is_donatable();
|
||||||
@@ -330,6 +328,7 @@ void RMSNormVJP::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
x.data<DataType>(),
|
x.data<DataType>(),
|
||||||
w.data<DataType>(),
|
w.data<DataType>(),
|
||||||
g.data<DataType>(),
|
g.data<DataType>(),
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ void RoPE::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
(donated ? out : in).data<DataType>(),
|
(donated ? out : in).data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
offset.data<int32_t>(),
|
offset.data<int32_t>(),
|
||||||
@@ -341,6 +342,7 @@ void RoPE::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
(donated ? out : in).data<DataType>(),
|
(donated ? out : in).data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
offset.data<int32_t>(),
|
offset.data<int32_t>(),
|
||||||
@@ -360,6 +362,7 @@ void RoPE::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
(donated ? out : in).data<DataType>(),
|
(donated ? out : in).data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
offset.data<int32_t>(),
|
offset.data<int32_t>(),
|
||||||
@@ -381,6 +384,7 @@ void RoPE::eval_gpu(
|
|||||||
kernel,
|
kernel,
|
||||||
grid,
|
grid,
|
||||||
block,
|
block,
|
||||||
|
0,
|
||||||
(donated ? out : in).data<DataType>(),
|
(donated ? out : in).data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
offset.data<int32_t>(),
|
offset.data<int32_t>(),
|
||||||
|
|||||||
467
mlx/backend/cuda/scan.cu
Normal file
467
mlx/backend/cuda/scan.cu
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
// Copyright © 2025 Apple Inc.
|
||||||
|
|
||||||
|
#include "mlx/backend/cuda/device.h"
|
||||||
|
#include "mlx/backend/cuda/device/binary_ops.cuh"
|
||||||
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
|
#include "mlx/backend/cuda/reduce/reduce_ops.cuh"
|
||||||
|
#include "mlx/backend/gpu/copy.h"
|
||||||
|
#include "mlx/dtype_utils.h"
|
||||||
|
#include "mlx/primitives.h"
|
||||||
|
|
||||||
|
#include <cooperative_groups.h>
|
||||||
|
#include <cooperative_groups/scan.h>
|
||||||
|
#include <nvtx3/nvtx3.hpp>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace mlx::core {
|
||||||
|
|
||||||
|
namespace cu {
|
||||||
|
|
||||||
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
|
template <typename Op, typename T>
|
||||||
|
struct ScanResult {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ScanResult<Sum, bool> {
|
||||||
|
using type = int32_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ReduceInit<LogAddExp, T> {
|
||||||
|
static constexpr __host__ __device__ T value() {
|
||||||
|
return Limits<T>::min();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <bool reverse, typename T, typename U, int N_READS>
|
||||||
|
inline __device__ void
|
||||||
|
load_values(int index, const T* in, U (&values)[N_READS], int size, U init) {
|
||||||
|
int remaining = size - index * N_READS;
|
||||||
|
if constexpr (reverse) {
|
||||||
|
in += remaining - N_READS;
|
||||||
|
if (remaining < N_READS) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
values[N_READS - i - 1] =
|
||||||
|
(N_READS - i - 1 < remaining) ? cast_to<U>(in[i]) : init;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
values[N_READS - i - 1] = cast_to<U>(in[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
in += index * N_READS;
|
||||||
|
if (remaining < N_READS) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
values[i] = (i < remaining) ? cast_to<U>(in[i]) : init;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
values[i] = cast_to<U>(in[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool reverse, int offset, typename T, int N_READS>
|
||||||
|
inline __device__ void
|
||||||
|
store_values(int index, T* out, T (&values)[N_READS], int size) {
|
||||||
|
int start = index * N_READS + offset;
|
||||||
|
int remaining = size - start;
|
||||||
|
if constexpr (reverse) {
|
||||||
|
out += remaining - N_READS;
|
||||||
|
if (remaining < N_READS) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
if (N_READS - i - 1 < remaining) {
|
||||||
|
out[i] = values[N_READS - i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out[i] = values[N_READS - i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out += start;
|
||||||
|
if (remaining < N_READS) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
if (i < remaining) {
|
||||||
|
out[i] = values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out[i] = values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename T,
|
||||||
|
typename U,
|
||||||
|
typename Op,
|
||||||
|
int N_READS,
|
||||||
|
bool inclusive,
|
||||||
|
bool reverse>
|
||||||
|
__global__ void contiguous_scan(const T* in, U* out, int32_t axis_size) {
|
||||||
|
auto grid = cg::this_grid();
|
||||||
|
auto block = cg::this_thread_block();
|
||||||
|
auto warp = cg::tiled_partition<WARP_SIZE>(block);
|
||||||
|
|
||||||
|
in += grid.block_rank() * axis_size;
|
||||||
|
out += grid.block_rank() * axis_size;
|
||||||
|
|
||||||
|
__shared__ U warp_sums[WARP_SIZE];
|
||||||
|
|
||||||
|
Op op;
|
||||||
|
U init = ReduceInit<Op, T>::value();
|
||||||
|
U prefix = init;
|
||||||
|
|
||||||
|
// Scan per block.
|
||||||
|
for (int r = 0; r < cuda::ceil_div(axis_size, block.size() * N_READS); ++r) {
|
||||||
|
int32_t index = r * block.size() + block.thread_rank();
|
||||||
|
U values[N_READS];
|
||||||
|
load_values<reverse>(index, in, values, axis_size, init);
|
||||||
|
|
||||||
|
// Compute an inclusive scan per thread.
|
||||||
|
for (int i = 1; i < N_READS; ++i) {
|
||||||
|
values[i] = op(values[i], values[i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute exclusive scan of thread sums.
|
||||||
|
U prev_thread_sum = cg::exclusive_scan(warp, values[N_READS - 1], op);
|
||||||
|
if (warp.thread_rank() == 0) {
|
||||||
|
prev_thread_sum = init;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write wrap's sum to shared memory.
|
||||||
|
if (warp.thread_rank() == WARP_SIZE - 1) {
|
||||||
|
warp_sums[warp.meta_group_rank()] =
|
||||||
|
op(prev_thread_sum, values[N_READS - 1]);
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
|
||||||
|
// Compute exclusive scan of warp sums.
|
||||||
|
if (warp.meta_group_rank() == 0) {
|
||||||
|
U prev_warp_sum =
|
||||||
|
cg::exclusive_scan(warp, warp_sums[warp.thread_rank()], op);
|
||||||
|
if (warp.thread_rank() == 0) {
|
||||||
|
prev_warp_sum = init;
|
||||||
|
}
|
||||||
|
warp_sums[warp.thread_rank()] = prev_warp_sum;
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
|
||||||
|
// Compute the output.
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
values[i] = op(values[i], prefix);
|
||||||
|
values[i] = op(values[i], warp_sums[warp.meta_group_rank()]);
|
||||||
|
values[i] = op(values[i], prev_thread_sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the values.
|
||||||
|
if (inclusive) {
|
||||||
|
store_values<reverse, 0>(index, out, values, axis_size);
|
||||||
|
} else {
|
||||||
|
store_values<reverse, 1>(index, out, values, axis_size);
|
||||||
|
if (reverse) {
|
||||||
|
if (block.thread_rank() == 0 && index == 0) {
|
||||||
|
out[axis_size - 1] = init;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (block.thread_rank() == 0 && index == 0) {
|
||||||
|
out[0] = init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
|
||||||
|
// Share the prefix.
|
||||||
|
if ((warp.meta_group_rank() == warp.meta_group_size() - 1) &&
|
||||||
|
(warp.thread_rank() == WARP_SIZE - 1)) {
|
||||||
|
warp_sums[0] = values[N_READS - 1];
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
prefix = warp_sums[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename T,
|
||||||
|
typename U,
|
||||||
|
typename Op,
|
||||||
|
int N_READS,
|
||||||
|
int BM,
|
||||||
|
int BN,
|
||||||
|
bool inclusive,
|
||||||
|
bool reverse>
|
||||||
|
__global__ void strided_scan(
|
||||||
|
const T* in,
|
||||||
|
U* out,
|
||||||
|
int32_t axis_size,
|
||||||
|
int64_t stride,
|
||||||
|
int64_t stride_blocks) {
|
||||||
|
auto grid = cg::this_grid();
|
||||||
|
auto block = cg::this_thread_block();
|
||||||
|
auto warp = cg::tiled_partition<WARP_SIZE>(block);
|
||||||
|
|
||||||
|
constexpr int BN_pad = WARP_SIZE + 16 / sizeof(U);
|
||||||
|
constexpr int n_warps = BN / N_READS;
|
||||||
|
constexpr int n_scans = BN / n_warps;
|
||||||
|
|
||||||
|
__shared__ U read_buffer[BM * BN_pad];
|
||||||
|
|
||||||
|
Op op;
|
||||||
|
U init = ReduceInit<Op, T>::value();
|
||||||
|
U values[n_scans];
|
||||||
|
U prefix[n_scans];
|
||||||
|
for (int i = 0; i < n_scans; ++i) {
|
||||||
|
prefix[i] = init;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute offsets.
|
||||||
|
int64_t offset = (grid.block_rank() / stride_blocks) * axis_size * stride;
|
||||||
|
int64_t global_index_x = (grid.block_rank() % stride_blocks) * BN;
|
||||||
|
uint read_offset_y = (block.thread_rank() * N_READS) / BN;
|
||||||
|
uint read_offset_x = (block.thread_rank() * N_READS) % BN;
|
||||||
|
uint scan_offset_y = warp.thread_rank();
|
||||||
|
uint scan_offset_x = warp.meta_group_rank() * n_scans;
|
||||||
|
|
||||||
|
uint stride_limit = stride - global_index_x;
|
||||||
|
in += offset + global_index_x + read_offset_x;
|
||||||
|
out += offset + global_index_x + read_offset_x;
|
||||||
|
U* read_into = read_buffer + read_offset_y * BN_pad + read_offset_x;
|
||||||
|
U* read_from = read_buffer + scan_offset_y * BN_pad + scan_offset_x;
|
||||||
|
|
||||||
|
for (uint j = 0; j < axis_size; j += BM) {
|
||||||
|
// Calculate the indices for the current thread.
|
||||||
|
uint index_y = j + read_offset_y;
|
||||||
|
uint check_index_y = index_y;
|
||||||
|
if (reverse) {
|
||||||
|
index_y = axis_size - 1 - index_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in SM.
|
||||||
|
if (check_index_y < axis_size && (read_offset_x + N_READS) < stride_limit) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
read_into[i] = in[index_y * stride + i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
if (check_index_y < axis_size && (read_offset_x + i) < stride_limit) {
|
||||||
|
read_into[i] = in[index_y * stride + i];
|
||||||
|
} else {
|
||||||
|
read_into[i] = init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
|
||||||
|
// Read strided into registers.
|
||||||
|
for (int i = 0; i < n_scans; ++i) {
|
||||||
|
values[i] = read_from[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the scan.
|
||||||
|
for (int i = 0; i < n_scans; ++i) {
|
||||||
|
values[i] = cg::inclusive_scan(warp, values[i], op);
|
||||||
|
values[i] = op(values[i], prefix[i]);
|
||||||
|
prefix[i] = warp.shfl(values[i], WARP_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to SM.
|
||||||
|
for (int i = 0; i < n_scans; ++i) {
|
||||||
|
read_from[i] = values[i];
|
||||||
|
}
|
||||||
|
block.sync();
|
||||||
|
|
||||||
|
// Write to device memory.
|
||||||
|
if (!inclusive) {
|
||||||
|
if (check_index_y == 0) {
|
||||||
|
if ((read_offset_x + N_READS) < stride_limit) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out[index_y * stride + i] = init;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
if ((read_offset_x + i) < stride_limit) {
|
||||||
|
out[index_y * stride + i] = init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reverse) {
|
||||||
|
index_y -= 1;
|
||||||
|
check_index_y += 1;
|
||||||
|
} else {
|
||||||
|
index_y += 1;
|
||||||
|
check_index_y += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (check_index_y < axis_size && (read_offset_x + N_READS) < stride_limit) {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out[index_y * stride + i] = read_into[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
if (check_index_y < axis_size && (read_offset_x + i) < stride_limit) {
|
||||||
|
out[index_y * stride + i] = read_into[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cu
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void dispatch_scan_ops(Scan::ReduceType scan_op, F&& f) {
|
||||||
|
if (scan_op == Scan::ReduceType::Max) {
|
||||||
|
f(type_identity<cu::Max>{});
|
||||||
|
} else if (scan_op == Scan::ReduceType::Min) {
|
||||||
|
f(type_identity<cu::Min>{});
|
||||||
|
} else if (scan_op == Scan::ReduceType::Sum) {
|
||||||
|
f(type_identity<cu::Sum>{});
|
||||||
|
} else if (scan_op == Scan::ReduceType::Prod) {
|
||||||
|
f(type_identity<cu::Prod>{});
|
||||||
|
} else if (scan_op == Scan::ReduceType::LogAddExp) {
|
||||||
|
f(type_identity<cu::LogAddExp>{});
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Unknown reduce type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Op>
|
||||||
|
const char* op_to_string() {
|
||||||
|
if (cuda::std::is_same_v<Op, cu::Max>) {
|
||||||
|
return "Max";
|
||||||
|
} else if (cuda::std::is_same_v<Op, cu::Min>) {
|
||||||
|
return "Min";
|
||||||
|
} else if (cuda::std::is_same_v<Op, cu::Sum>) {
|
||||||
|
return "Sum";
|
||||||
|
} else if (cuda::std::is_same_v<Op, cu::Prod>) {
|
||||||
|
return "Prod";
|
||||||
|
} else if (cuda::std::is_same_v<Op, cu::LogAddExp>) {
|
||||||
|
return "LogAddExp";
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Unknown op.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Op, typename T>
|
||||||
|
constexpr bool supports_scan_op() {
|
||||||
|
if constexpr (cuda::std::is_same_v<Op, LogAddExp>) {
|
||||||
|
return is_inexact_v<T>;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scan::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
|
nvtx3::scoped_range r("Scan::eval_gpu");
|
||||||
|
assert(inputs.size() == 1);
|
||||||
|
auto in = inputs[0];
|
||||||
|
auto& s = stream();
|
||||||
|
|
||||||
|
if (in.flags().contiguous && in.strides()[axis_] != 0) {
|
||||||
|
if (in.is_donatable() && in.itemsize() == out.itemsize()) {
|
||||||
|
out.copy_shared_buffer(in);
|
||||||
|
} else {
|
||||||
|
out.set_data(
|
||||||
|
allocator::malloc(in.data_size() * out.itemsize()),
|
||||||
|
in.data_size(),
|
||||||
|
in.strides(),
|
||||||
|
in.flags());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
in = contiguous_copy_gpu(in, s);
|
||||||
|
out.copy_shared_buffer(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
int32_t axis_size = in.shape(axis_);
|
||||||
|
bool contiguous = in.strides()[axis_] == 1;
|
||||||
|
|
||||||
|
auto& encoder = cu::get_command_encoder(s);
|
||||||
|
encoder.set_input_array(in);
|
||||||
|
encoder.set_output_array(out);
|
||||||
|
|
||||||
|
dispatch_all_types(in.dtype(), [&](auto type_tag) {
|
||||||
|
using T = cuda_type_t<MLX_GET_TYPE(type_tag)>;
|
||||||
|
dispatch_scan_ops(reduce_type_, [&](auto scan_op_tag) {
|
||||||
|
using Op = MLX_GET_TYPE(scan_op_tag);
|
||||||
|
if constexpr (supports_scan_op<Op, T>) {
|
||||||
|
using U = typename cu::ScanResult<Op, T>::type;
|
||||||
|
dispatch_bool(inclusive_, [&](auto inclusive) {
|
||||||
|
dispatch_bool(reverse_, [&](auto reverse) {
|
||||||
|
if (contiguous) {
|
||||||
|
auto kernel = cu::contiguous_scan<
|
||||||
|
T,
|
||||||
|
U,
|
||||||
|
Op,
|
||||||
|
N_READS,
|
||||||
|
inclusive.value,
|
||||||
|
reverse.value>;
|
||||||
|
int block_dim = cuda::ceil_div(axis_size, N_READS);
|
||||||
|
block_dim = cuda::ceil_div(block_dim, WARP_SIZE) * WARP_SIZE;
|
||||||
|
block_dim = std::min(block_dim, WARP_SIZE * WARP_SIZE);
|
||||||
|
encoder.add_kernel_node(
|
||||||
|
kernel,
|
||||||
|
in.data_size() / axis_size,
|
||||||
|
block_dim,
|
||||||
|
0,
|
||||||
|
in.data<T>(),
|
||||||
|
out.data<U>(),
|
||||||
|
axis_size);
|
||||||
|
} else {
|
||||||
|
constexpr int BM = WARP_SIZE;
|
||||||
|
constexpr int BN = WARP_SIZE;
|
||||||
|
auto kernel = cu::strided_scan<
|
||||||
|
T,
|
||||||
|
U,
|
||||||
|
Op,
|
||||||
|
N_READS,
|
||||||
|
BM,
|
||||||
|
BN,
|
||||||
|
inclusive.value,
|
||||||
|
reverse.value>;
|
||||||
|
int64_t stride = in.strides()[axis_];
|
||||||
|
int64_t stride_blocks = cuda::ceil_div(stride, BN);
|
||||||
|
dim3 num_blocks = get_2d_grid_dims(
|
||||||
|
in.shape(), in.strides(), axis_size * stride);
|
||||||
|
if (num_blocks.x * stride_blocks <= UINT32_MAX) {
|
||||||
|
num_blocks.x *= stride_blocks;
|
||||||
|
} else {
|
||||||
|
num_blocks.y *= stride_blocks;
|
||||||
|
}
|
||||||
|
int block_dim = (BN / N_READS) * WARP_SIZE;
|
||||||
|
encoder.add_kernel_node(
|
||||||
|
kernel,
|
||||||
|
num_blocks,
|
||||||
|
block_dim,
|
||||||
|
0,
|
||||||
|
in.data<T>(),
|
||||||
|
out.data<U>(),
|
||||||
|
axis_size,
|
||||||
|
stride,
|
||||||
|
stride_blocks);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error(fmt::format(
|
||||||
|
"Can not do scan op {} on inputs of {} with result of {}.",
|
||||||
|
op_to_string<Op>(),
|
||||||
|
dtype_to_string(in.dtype()),
|
||||||
|
dtype_to_string(out.dtype())));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mlx::core
|
||||||
@@ -43,7 +43,7 @@ __global__ void softmax(const T* in, T* out, int axis_size) {
|
|||||||
// Thread reduce.
|
// Thread reduce.
|
||||||
AccT prevmax;
|
AccT prevmax;
|
||||||
AccT maxval = Limits<AccT>::finite_min();
|
AccT maxval = Limits<AccT>::finite_min();
|
||||||
AccT normalizer = 0;
|
AccT normalizer = cast_to<AccT>(0);
|
||||||
for (int r = 0; r < cuda::ceil_div(axis_size, BLOCK_DIM * N_READS); r++) {
|
for (int r = 0; r < cuda::ceil_div(axis_size, BLOCK_DIM * N_READS); r++) {
|
||||||
AccT vals[N_READS];
|
AccT vals[N_READS];
|
||||||
cub::LoadDirectBlocked(
|
cub::LoadDirectBlocked(
|
||||||
@@ -125,8 +125,7 @@ void Softmax::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
auto x_copy = array(x.shape(), x.dtype(), nullptr, {});
|
array x_copy = contiguous_copy_gpu(x, s);
|
||||||
copy_gpu(x, x_copy, CopyType::General, s);
|
|
||||||
out.copy_shared_buffer(x_copy);
|
out.copy_shared_buffer(x_copy);
|
||||||
return x_copy;
|
return x_copy;
|
||||||
}
|
}
|
||||||
@@ -153,6 +152,7 @@ void Softmax::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
kernel,
|
kernel,
|
||||||
n_rows,
|
n_rows,
|
||||||
block_dim(),
|
block_dim(),
|
||||||
|
0,
|
||||||
in.data<DataType>(),
|
in.data<DataType>(),
|
||||||
out.data<DataType>(),
|
out.data<DataType>(),
|
||||||
axis_size);
|
axis_size);
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ void gpu_sort(const Stream& s, array in, array& out_, int axis, bool argsort) {
|
|||||||
bool is_segmented_sort = in.flags().contiguous && in.strides()[axis] == 1;
|
bool is_segmented_sort = in.flags().contiguous && in.strides()[axis] == 1;
|
||||||
if (!is_segmented_sort) {
|
if (!is_segmented_sort) {
|
||||||
array trans = swapaxes_in_eval(in, axis, last_dim);
|
array trans = swapaxes_in_eval(in, axis, last_dim);
|
||||||
in = array(trans.shape(), trans.dtype(), nullptr, {});
|
in = contiguous_copy_gpu(trans, s);
|
||||||
copy_gpu(trans, in, CopyType::General, s);
|
|
||||||
encoder.add_temporary(in);
|
encoder.add_temporary(in);
|
||||||
out = array(allocator::malloc(out.nbytes()), in.shape(), out.dtype());
|
out = array(allocator::malloc(out.nbytes()), in.shape(), out.dtype());
|
||||||
encoder.add_temporary(out);
|
encoder.add_temporary(out);
|
||||||
|
|||||||
@@ -15,12 +15,27 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename Op, typename T, typename IdxT>
|
template <typename Op, typename T, typename IdxT, int N_READS>
|
||||||
__global__ void
|
__global__ void
|
||||||
ternary_v(const bool* a, const T* b, const T* c, T* out, IdxT size) {
|
ternary_v(const bool* a, const T* b, const T* c, T* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(a[index], b[index], c[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(a[i], b[i], c[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto a_vec = load_vector<N_READS>(a, index);
|
||||||
|
auto b_vec = load_vector<N_READS>(b, index);
|
||||||
|
auto c_vec = load_vector<N_READS>(c, index);
|
||||||
|
|
||||||
|
AlignedVector<T, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(a_vec.val[i], b_vec.val[i], c_vec.val[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +133,7 @@ void ternary_op_gpu_inplace(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<bool>(),
|
a.data<bool>(),
|
||||||
b.data<DType>(),
|
b.data<DType>(),
|
||||||
c.data<DType>(),
|
c.data<DType>(),
|
||||||
@@ -136,6 +152,7 @@ void ternary_op_gpu_inplace(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<bool>(),
|
a.data<bool>(),
|
||||||
b.data<DType>(),
|
b.data<DType>(),
|
||||||
c.data<DType>(),
|
c.data<DType>(),
|
||||||
@@ -149,15 +166,23 @@ void ternary_op_gpu_inplace(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dispatch_bool(out.data_size() > INT32_MAX, [&](auto large) {
|
dispatch_bool(out.data_size() > UINT32_MAX, [&](auto large) {
|
||||||
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
||||||
auto kernel = cu::ternary_v<Op, DType, IdxT>;
|
// TODO: Choose optimized value based on type size.
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
auto kernel = cu::ternary_v<Op, DType, IdxT, N_READS>;
|
||||||
auto [num_blocks, block_dims] = get_launch_args(
|
auto [num_blocks, block_dims] = get_launch_args(
|
||||||
kernel, out.data_size(), out.shape(), out.strides(), large());
|
kernel,
|
||||||
|
out.data_size(),
|
||||||
|
out.shape(),
|
||||||
|
out.strides(),
|
||||||
|
large(),
|
||||||
|
N_READS);
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
a.data<bool>(),
|
a.data<bool>(),
|
||||||
b.data<DType>(),
|
b.data<DType>(),
|
||||||
c.data<DType>(),
|
c.data<DType>(),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "mlx/backend/common/unary.h"
|
#include "mlx/backend/common/unary.h"
|
||||||
#include "mlx/backend/cuda/device.h"
|
#include "mlx/backend/cuda/device.h"
|
||||||
#include "mlx/backend/cuda/device/cucomplex_math.cuh"
|
|
||||||
#include "mlx/backend/cuda/device/unary_ops.cuh"
|
#include "mlx/backend/cuda/device/unary_ops.cuh"
|
||||||
#include "mlx/backend/cuda/iterators/general_iterator.cuh"
|
#include "mlx/backend/cuda/iterators/general_iterator.cuh"
|
||||||
#include "mlx/backend/cuda/kernel_utils.cuh"
|
#include "mlx/backend/cuda/kernel_utils.cuh"
|
||||||
@@ -18,11 +17,24 @@ namespace cu {
|
|||||||
|
|
||||||
namespace cg = cooperative_groups;
|
namespace cg = cooperative_groups;
|
||||||
|
|
||||||
template <typename Op, typename In, typename Out, typename IdxT>
|
template <typename Op, typename In, typename Out, typename IdxT, int N_READS>
|
||||||
__global__ void unary_v(const In* in, Out* out, IdxT size) {
|
__global__ void unary_v(const In* in, Out* out, IdxT size) {
|
||||||
IdxT index = cg::this_grid().thread_rank();
|
IdxT index = cg::this_grid().thread_rank();
|
||||||
if (index < size) {
|
|
||||||
out[index] = Op{}(in[index]);
|
if ((index + 1) * N_READS > size) {
|
||||||
|
for (IdxT i = index * N_READS; i < size; ++i) {
|
||||||
|
out[i] = Op{}(in[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto in_vec = load_vector<N_READS>(in, index);
|
||||||
|
|
||||||
|
AlignedVector<Out, N_READS> out_vec;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < N_READS; ++i) {
|
||||||
|
out_vec.val[i] = Op{}(in_vec.val[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
store_vector<N_READS>(out, index, out_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +70,10 @@ constexpr bool supports_unary_op() {
|
|||||||
!std::is_same_v<In, bool>;
|
!std::is_same_v<In, bool>;
|
||||||
}
|
}
|
||||||
if (std::is_same_v<Op, Ceil> || std::is_same_v<Op, Floor>) {
|
if (std::is_same_v<Op, Ceil> || std::is_same_v<Op, Floor>) {
|
||||||
return std::is_same_v<In, Out> && !std::is_same_v<In, complex64_t>;
|
return std::is_same_v<In, Out> && !mlx::core::is_complex_v<In>;
|
||||||
}
|
}
|
||||||
if (std::is_same_v<Op, Conjugate>) {
|
if (std::is_same_v<Op, Conjugate>) {
|
||||||
return std::is_same_v<In, Out> && std::is_same_v<In, complex64_t>;
|
return std::is_same_v<In, Out> && mlx::core::is_complex_v<In>;
|
||||||
}
|
}
|
||||||
if (std::is_same_v<Op, ArcCos> || std::is_same_v<Op, ArcSin> ||
|
if (std::is_same_v<Op, ArcCos> || std::is_same_v<Op, ArcSin> ||
|
||||||
std::is_same_v<Op, ArcTan> || std::is_same_v<Op, Cos> ||
|
std::is_same_v<Op, ArcTan> || std::is_same_v<Op, Cos> ||
|
||||||
@@ -75,7 +87,7 @@ constexpr bool supports_unary_op() {
|
|||||||
return std::is_same_v<In, Out> && is_inexact_v<In>;
|
return std::is_same_v<In, Out> && is_inexact_v<In>;
|
||||||
}
|
}
|
||||||
if (std::is_same_v<Op, Imag> || std::is_same_v<Op, Real>) {
|
if (std::is_same_v<Op, Imag> || std::is_same_v<Op, Real>) {
|
||||||
return std::is_same_v<In, complex64_t> && std::is_same_v<Out, float>;
|
return mlx::core::is_complex_v<In> && std::is_same_v<Out, float>;
|
||||||
}
|
}
|
||||||
if (std::is_same_v<Op, LogicalNot>) {
|
if (std::is_same_v<Op, LogicalNot>) {
|
||||||
return std::is_same_v<In, Out> && std::is_same_v<In, bool>;
|
return std::is_same_v<In, Out> && std::is_same_v<In, bool>;
|
||||||
@@ -89,7 +101,7 @@ template <typename Op>
|
|||||||
void unary_op_gpu_inplace(
|
void unary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
auto& in = inputs[0];
|
auto& in = inputs[0];
|
||||||
if (in.size() == 0) {
|
if (in.size() == 0) {
|
||||||
@@ -112,22 +124,30 @@ void unary_op_gpu_inplace(
|
|||||||
using CTYPE_OUT = MLX_GET_TYPE(out_type_tag);
|
using CTYPE_OUT = MLX_GET_TYPE(out_type_tag);
|
||||||
if constexpr (cu::supports_unary_op<Op, CTYPE_IN, CTYPE_OUT>()) {
|
if constexpr (cu::supports_unary_op<Op, CTYPE_IN, CTYPE_OUT>()) {
|
||||||
dispatch_bool(large, [&](auto large) {
|
dispatch_bool(large, [&](auto large) {
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
|
||||||
using InType = cuda_type_t<CTYPE_IN>;
|
using InType = cuda_type_t<CTYPE_IN>;
|
||||||
using OutType = cuda_type_t<CTYPE_OUT>;
|
using OutType = cuda_type_t<CTYPE_OUT>;
|
||||||
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
|
||||||
if (contig) {
|
if (contig) {
|
||||||
auto kernel = cu::unary_v<Op, InType, OutType, IdxT>;
|
using IdxT = std::conditional_t<large(), int64_t, uint32_t>;
|
||||||
|
// TODO: Choose optimized value based on type size.
|
||||||
|
constexpr int N_READS = 4;
|
||||||
|
auto kernel = cu::unary_v<Op, InType, OutType, IdxT, N_READS>;
|
||||||
auto [num_blocks, block_dims] = get_launch_args(
|
auto [num_blocks, block_dims] = get_launch_args(
|
||||||
kernel, out.data_size(), out.shape(), out.strides(), large);
|
kernel,
|
||||||
|
out.data_size(),
|
||||||
|
out.shape(),
|
||||||
|
out.strides(),
|
||||||
|
large,
|
||||||
|
N_READS);
|
||||||
encoder.add_kernel_node(
|
encoder.add_kernel_node(
|
||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in.data<InType>(),
|
in.data<InType>(),
|
||||||
out.data<OutType>(),
|
out.data<OutType>(),
|
||||||
out.data_size());
|
out.data_size());
|
||||||
} else {
|
} else {
|
||||||
|
using IdxT = std::conditional_t<large(), int64_t, int32_t>;
|
||||||
auto [shape, strides] = collapse_contiguous_dims(in);
|
auto [shape, strides] = collapse_contiguous_dims(in);
|
||||||
auto kernel = cu::unary_g<Op, InType, OutType, IdxT>;
|
auto kernel = cu::unary_g<Op, InType, OutType, IdxT>;
|
||||||
auto [num_blocks, block_dims] = get_launch_args(kernel, out, large);
|
auto [num_blocks, block_dims] = get_launch_args(kernel, out, large);
|
||||||
@@ -135,6 +155,7 @@ void unary_op_gpu_inplace(
|
|||||||
kernel,
|
kernel,
|
||||||
num_blocks,
|
num_blocks,
|
||||||
block_dims,
|
block_dims,
|
||||||
|
0,
|
||||||
in.data<InType>(),
|
in.data<InType>(),
|
||||||
out.data<OutType>(),
|
out.data<OutType>(),
|
||||||
out.data_size(),
|
out.data_size(),
|
||||||
@@ -158,17 +179,17 @@ template <typename Op>
|
|||||||
void unary_op_gpu(
|
void unary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
set_unary_output_data(inputs[0], out);
|
set_unary_output_data(inputs[0], out);
|
||||||
unary_op_gpu_inplace<Op>(inputs, out, op, s);
|
unary_op_gpu_inplace<Op>(inputs, out, op, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define UNARY_GPU(func) \
|
#define UNARY_GPU(func) \
|
||||||
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
||||||
nvtx3::scoped_range r(#func "::eval_gpu"); \
|
nvtx3::scoped_range r(#func "::eval_gpu"); \
|
||||||
auto& s = out.primitive().stream(); \
|
auto& s = out.primitive().stream(); \
|
||||||
unary_op_gpu<cu::func>(inputs, out, get_primitive_string(this), s); \
|
unary_op_gpu<cu::func>(inputs, out, name(), s); \
|
||||||
}
|
}
|
||||||
|
|
||||||
UNARY_GPU(Abs)
|
UNARY_GPU(Abs)
|
||||||
@@ -204,16 +225,15 @@ UNARY_GPU(Tanh)
|
|||||||
void Log::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void Log::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
nvtx3::scoped_range r("Log::eval_gpu");
|
nvtx3::scoped_range r("Log::eval_gpu");
|
||||||
auto& s = out.primitive().stream();
|
auto& s = out.primitive().stream();
|
||||||
auto op = get_primitive_string(this);
|
|
||||||
switch (base_) {
|
switch (base_) {
|
||||||
case Base::e:
|
case Base::e:
|
||||||
unary_op_gpu<cu::Log>(inputs, out, op, s);
|
unary_op_gpu<cu::Log>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case Base::two:
|
case Base::two:
|
||||||
unary_op_gpu<cu::Log2>(inputs, out, op, s);
|
unary_op_gpu<cu::Log2>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
case Base::ten:
|
case Base::ten:
|
||||||
unary_op_gpu<cu::Log10>(inputs, out, op, s);
|
unary_op_gpu<cu::Log10>(inputs, out, name(), s);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +244,7 @@ void Round::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
const auto& in = inputs[0];
|
const auto& in = inputs[0];
|
||||||
auto& s = out.primitive().stream();
|
auto& s = out.primitive().stream();
|
||||||
if (issubdtype(in.dtype(), inexact)) {
|
if (issubdtype(in.dtype(), inexact)) {
|
||||||
unary_op_gpu<cu::Round>(inputs, out, get_primitive_string(this), s);
|
unary_op_gpu<cu::Round>(inputs, out, name(), s);
|
||||||
} else {
|
} else {
|
||||||
// No-op integer types
|
// No-op integer types
|
||||||
out.copy_shared_buffer(in);
|
out.copy_shared_buffer(in);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const char* dtype_to_cuda_type(const Dtype& dtype) {
|
|||||||
case float64:
|
case float64:
|
||||||
return "double";
|
return "double";
|
||||||
case complex64:
|
case complex64:
|
||||||
return "cuComplex";
|
return "complex64_t";
|
||||||
default:
|
default:
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,4 +46,10 @@ void copy_gpu_inplace(
|
|||||||
in, out, in.shape(), i_strides, out.strides(), i_offset, 0, ctype, s);
|
in, out, in.shape(), i_strides, out.strides(), i_offset, 0, ctype, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
array contiguous_copy_gpu(const array& arr, const Stream& s) {
|
||||||
|
array arr_copy(arr.shape(), arr.dtype(), nullptr, {});
|
||||||
|
copy_gpu(arr, arr_copy, CopyType::General, s);
|
||||||
|
return arr_copy;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -43,4 +43,7 @@ void copy_gpu_inplace(
|
|||||||
// Fill the output with the scalar val
|
// Fill the output with the scalar val
|
||||||
void fill_gpu(const array& val, array& out, const Stream& s);
|
void fill_gpu(const array& val, array& out, const Stream& s);
|
||||||
|
|
||||||
|
// Return a contiguous array with same shape that copies the data of |arr|.
|
||||||
|
array contiguous_copy_gpu(const array& arr, const Stream& s);
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ if(MLX_METAL_JIT)
|
|||||||
make_jit_source(steel/gemm/kernels/steel_gemm_masked kernels/steel/defines.h)
|
make_jit_source(steel/gemm/kernels/steel_gemm_masked kernels/steel/defines.h)
|
||||||
make_jit_source(steel/gemm/kernels/steel_gemm_gather)
|
make_jit_source(steel/gemm/kernels/steel_gemm_gather)
|
||||||
make_jit_source(steel/gemm/kernels/steel_gemm_splitk)
|
make_jit_source(steel/gemm/kernels/steel_gemm_splitk)
|
||||||
|
make_jit_source(steel/gemm/kernels/steel_gemm_segmented)
|
||||||
make_jit_source(
|
make_jit_source(
|
||||||
steel/conv/conv
|
steel/conv/conv
|
||||||
kernels/steel/utils.h
|
kernels/steel/utils.h
|
||||||
|
|||||||
@@ -7,20 +7,20 @@
|
|||||||
|
|
||||||
#define BINARY_GPU(func) \
|
#define BINARY_GPU(func) \
|
||||||
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
void func::eval_gpu(const std::vector<array>& inputs, array& out) { \
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this)); \
|
binary_op_gpu(inputs, out, name()); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BINARY_GPU_MULTI(func) \
|
#define BINARY_GPU_MULTI(func) \
|
||||||
void func::eval_gpu( \
|
void func::eval_gpu( \
|
||||||
const std::vector<array>& inputs, std::vector<array>& outputs) { \
|
const std::vector<array>& inputs, std::vector<array>& outputs) { \
|
||||||
binary_op_gpu(inputs, outputs, get_primitive_string(this)); \
|
binary_op_gpu(inputs, outputs, name()); \
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
std::string get_kernel_name(
|
std::string get_kernel_name(
|
||||||
BinaryOpType bopt,
|
BinaryOpType bopt,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const array& a,
|
const array& a,
|
||||||
bool large,
|
bool large,
|
||||||
int ndim,
|
int ndim,
|
||||||
@@ -65,7 +65,7 @@ std::string get_kernel_name(
|
|||||||
void binary_op_gpu_inplace(
|
void binary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
auto& a = inputs[0];
|
auto& a = inputs[0];
|
||||||
auto& b = inputs[1];
|
auto& b = inputs[1];
|
||||||
@@ -165,7 +165,7 @@ void binary_op_gpu_inplace(
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
assert(inputs.size() == 2);
|
assert(inputs.size() == 2);
|
||||||
auto& a = inputs[0];
|
auto& a = inputs[0];
|
||||||
@@ -179,7 +179,7 @@ void binary_op_gpu(
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::string& op) {
|
const char* op) {
|
||||||
auto& s = outputs[0].primitive().stream();
|
auto& s = outputs[0].primitive().stream();
|
||||||
binary_op_gpu(inputs, outputs, op, s);
|
binary_op_gpu(inputs, outputs, op, s);
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ void binary_op_gpu(
|
|||||||
void binary_op_gpu_inplace(
|
void binary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
std::vector<array> outputs = {out};
|
std::vector<array> outputs = {out};
|
||||||
binary_op_gpu_inplace(inputs, outputs, op, s);
|
binary_op_gpu_inplace(inputs, outputs, op, s);
|
||||||
@@ -196,7 +196,7 @@ void binary_op_gpu_inplace(
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s) {
|
const Stream& s) {
|
||||||
assert(inputs.size() == 2);
|
assert(inputs.size() == 2);
|
||||||
auto& a = inputs[0];
|
auto& a = inputs[0];
|
||||||
@@ -209,7 +209,7 @@ void binary_op_gpu(
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op) {
|
const char* op) {
|
||||||
auto& s = out.primitive().stream();
|
auto& s = out.primitive().stream();
|
||||||
binary_op_gpu(inputs, out, op, s);
|
binary_op_gpu(inputs, out, op, s);
|
||||||
}
|
}
|
||||||
@@ -237,19 +237,19 @@ BINARY_GPU(Subtract)
|
|||||||
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
void BitwiseBinary::eval_gpu(const std::vector<array>& inputs, array& out) {
|
||||||
switch (op_) {
|
switch (op_) {
|
||||||
case BitwiseBinary::And:
|
case BitwiseBinary::And:
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this));
|
binary_op_gpu(inputs, out, name());
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::Or:
|
case BitwiseBinary::Or:
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this));
|
binary_op_gpu(inputs, out, name());
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::Xor:
|
case BitwiseBinary::Xor:
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this));
|
binary_op_gpu(inputs, out, name());
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::LeftShift:
|
case BitwiseBinary::LeftShift:
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this));
|
binary_op_gpu(inputs, out, name());
|
||||||
break;
|
break;
|
||||||
case BitwiseBinary::RightShift:
|
case BitwiseBinary::RightShift:
|
||||||
binary_op_gpu(inputs, out, get_primitive_string(this));
|
binary_op_gpu(inputs, out, name());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,25 +9,25 @@ namespace mlx::core {
|
|||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s);
|
const Stream& s);
|
||||||
|
|
||||||
void binary_op_gpu(
|
void binary_op_gpu(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s);
|
const Stream& s);
|
||||||
|
|
||||||
void binary_op_gpu_inplace(
|
void binary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
std::vector<array>& outputs,
|
std::vector<array>& outputs,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s);
|
const Stream& s);
|
||||||
|
|
||||||
void binary_op_gpu_inplace(
|
void binary_op_gpu_inplace(
|
||||||
const std::vector<array>& inputs,
|
const std::vector<array>& inputs,
|
||||||
array& out,
|
array& out,
|
||||||
const std::string& op,
|
const char* op,
|
||||||
const Stream& s);
|
const Stream& s);
|
||||||
|
|
||||||
} // namespace mlx::core
|
} // namespace mlx::core
|
||||||
|
|||||||
@@ -212,9 +212,7 @@ inline void build_kernel(
|
|||||||
get_type_string(x.dtype()),
|
get_type_string(x.dtype()),
|
||||||
namer.get_name(x.inputs()[0]));
|
namer.get_name(x.inputs()[0]));
|
||||||
} else {
|
} else {
|
||||||
std::ostringstream ss;
|
os += x.primitive().name();
|
||||||
x.primitive().print(ss);
|
|
||||||
os += ss.str();
|
|
||||||
os += "()(";
|
os += "()(";
|
||||||
for (int i = 0; i < x.inputs().size() - 1; i++) {
|
for (int i = 0; i < x.inputs().size() - 1; i++) {
|
||||||
os += fmt::format("tmp_{0}, ", namer.get_name(x.inputs()[i]));
|
os += fmt::format("tmp_{0}, ", namer.get_name(x.inputs()[i]));
|
||||||
|
|||||||
@@ -149,8 +149,7 @@ void explicit_gemm_conv_group_ND_gpu(
|
|||||||
wt, {wt.strides(0), 1, C_per_group}, wt.flags(), wt.size());
|
wt, {wt.strides(0), 1, C_per_group}, wt.flags(), wt.size());
|
||||||
|
|
||||||
// Materialize
|
// Materialize
|
||||||
auto wt_transpose = array(wt_view.shape(), wt_view.dtype(), nullptr, {});
|
array wt_transpose = contiguous_copy_gpu(wt_view, s);
|
||||||
copy_gpu(wt_view, wt_transpose, CopyType::General, s);
|
|
||||||
|
|
||||||
// Perform gemm
|
// Perform gemm
|
||||||
std::vector<array> copies = {in_unfolded, wt_transpose};
|
std::vector<array> copies = {in_unfolded, wt_transpose};
|
||||||
@@ -961,16 +960,12 @@ void Convolution::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
auto in = inputs[0];
|
auto in = inputs[0];
|
||||||
auto wt = inputs[1];
|
auto wt = inputs[1];
|
||||||
if (!in.flags().row_contiguous) {
|
if (!in.flags().row_contiguous) {
|
||||||
array arr_copy(in.shape(), in.dtype(), nullptr, {});
|
in = contiguous_copy_gpu(in, s);
|
||||||
copy_gpu(in, arr_copy, CopyType::General, s);
|
copies.push_back(in);
|
||||||
copies.push_back(arr_copy);
|
|
||||||
in = arr_copy;
|
|
||||||
}
|
}
|
||||||
if (!wt.flags().row_contiguous) {
|
if (!wt.flags().row_contiguous) {
|
||||||
array arr_copy(wt.shape(), wt.dtype(), nullptr, {});
|
wt = contiguous_copy_gpu(wt, s);
|
||||||
copy_gpu(wt, arr_copy, CopyType::General, s);
|
copies.push_back(wt);
|
||||||
copies.push_back(arr_copy);
|
|
||||||
wt = arr_copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3D conv
|
// 3D conv
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ void copy_gpu_inplace(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
work_per_thread = get_work_per_thread(out.dtype(), out.data_size());
|
work_per_thread = get_work_per_thread(out.dtype(), out.data_size());
|
||||||
if (work_per_thread > 1) {
|
if (!large && work_per_thread > 1) {
|
||||||
kernel_name += "n";
|
kernel_name += "n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
// Copyright © 2023-2024 Apple Inc.
|
// Copyright © 2023-2024 Apple Inc.
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#define NS_PRIVATE_IMPLEMENTATION
|
#define NS_PRIVATE_IMPLEMENTATION
|
||||||
#define CA_PRIVATE_IMPLEMENTATION
|
#define CA_PRIVATE_IMPLEMENTATION
|
||||||
#define MTL_PRIVATE_IMPLEMENTATION
|
#define MTL_PRIVATE_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include "mlx/backend/common/utils.h"
|
||||||
#include "mlx/backend/metal/device.h"
|
#include "mlx/backend/metal/device.h"
|
||||||
#include "mlx/backend/metal/metal.h"
|
#include "mlx/backend/metal/metal.h"
|
||||||
#include "mlx/backend/metal/utils.h"
|
#include "mlx/backend/metal/utils.h"
|
||||||
#include "mlx/utils.h"
|
#include "mlx/utils.h"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace mlx::core::metal {
|
namespace mlx::core::metal {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -80,12 +78,7 @@ MTL::Library* try_load_bundle(
|
|||||||
std::pair<MTL::Library*, NS::Error*> load_colocated_library(
|
std::pair<MTL::Library*, NS::Error*> load_colocated_library(
|
||||||
MTL::Device* device,
|
MTL::Device* device,
|
||||||
const std::string& relative_path) {
|
const std::string& relative_path) {
|
||||||
std::string binary_dir = get_binary_directory();
|
auto path = current_binary_dir() / relative_path;
|
||||||
if (binary_dir.size() == 0) {
|
|
||||||
return {nullptr, nullptr};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto path = fs::path(binary_dir) / relative_path;
|
|
||||||
if (!path.has_extension()) {
|
if (!path.has_extension()) {
|
||||||
path.replace_extension(".metallib");
|
path.replace_extension(".metallib");
|
||||||
}
|
}
|
||||||
@@ -197,7 +190,7 @@ MTL::Library* load_library(
|
|||||||
|
|
||||||
std::ostringstream msg;
|
std::ostringstream msg;
|
||||||
msg << "Failed to load the metallib " << lib_name << ".metallib. "
|
msg << "Failed to load the metallib " << lib_name << ".metallib. "
|
||||||
<< "We attempted to load it from <" << get_binary_directory() << "/"
|
<< "We attempted to load it from <" << current_binary_dir() << "/"
|
||||||
<< lib_name << ".metallib" << ">";
|
<< lib_name << ".metallib" << ">";
|
||||||
#ifdef SWIFTPM_BUNDLE
|
#ifdef SWIFTPM_BUNDLE
|
||||||
msg << " and from the Swift PM bundle.";
|
msg << " and from the Swift PM bundle.";
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Metal/Metal.hpp>
|
#include <Metal/Metal.hpp>
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
@@ -15,22 +13,8 @@
|
|||||||
#include "mlx/array.h"
|
#include "mlx/array.h"
|
||||||
#include "mlx/device.h"
|
#include "mlx/device.h"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace mlx::core::metal {
|
namespace mlx::core::metal {
|
||||||
|
|
||||||
// Note, this function must be left inline in a header so that it is not
|
|
||||||
// dynamically linked.
|
|
||||||
inline std::string get_binary_directory() {
|
|
||||||
Dl_info info;
|
|
||||||
std::string directory;
|
|
||||||
int success = dladdr((void*)get_binary_directory, &info);
|
|
||||||
if (success) {
|
|
||||||
directory = fs::path(info.dli_fname).remove_filename().c_str();
|
|
||||||
}
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
using MTLFCList =
|
using MTLFCList =
|
||||||
std::vector<std::tuple<const void*, MTL::DataType, NS::UInteger>>;
|
std::vector<std::tuple<const void*, MTL::DataType, NS::UInteger>>;
|
||||||
|
|
||||||
|
|||||||
@@ -575,9 +575,17 @@ void ScatterAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
|
|||||||
compute_encoder.set_output_array(out, 2);
|
compute_encoder.set_output_array(out, 2);
|
||||||
|
|
||||||
// Set source info
|
// Set source info
|
||||||
compute_encoder.set_vector_bytes(remove_index(idx.shape(), axis_), 3);
|
if (ndim > 1) {
|
||||||
compute_encoder.set_vector_bytes(remove_index(upd.strides(), axis_), 4);
|
compute_encoder.set_vector_bytes(remove_index(idx.shape(), axis_), 3);
|
||||||
compute_encoder.set_vector_bytes(remove_index(idx.strides(), axis_), 5);
|
compute_encoder.set_vector_bytes(remove_index(upd.strides(), axis_), 4);
|
||||||
|
compute_encoder.set_vector_bytes(remove_index(idx.strides(), axis_), 5);
|
||||||
|
} else {
|
||||||
|
// The following will be ignored in the kernel but we still have to set
|
||||||
|
// some value so that metal validation passes.
|
||||||
|
compute_encoder.set_vector_bytes(idx.shape(), 3);
|
||||||
|
compute_encoder.set_vector_bytes(upd.strides(), 4);
|
||||||
|
compute_encoder.set_vector_bytes(idx.strides(), 5);
|
||||||
|
}
|
||||||
compute_encoder.set_bytes(ndim - 1, 6);
|
compute_encoder.set_bytes(ndim - 1, 6);
|
||||||
compute_encoder.set_bytes(axis_, 7);
|
compute_encoder.set_bytes(axis_, 7);
|
||||||
compute_encoder.set_bytes(out.shape(axis_), 8);
|
compute_encoder.set_bytes(out.shape(axis_), 8);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const char* steel_gemm_fused();
|
|||||||
const char* steel_gemm_masked();
|
const char* steel_gemm_masked();
|
||||||
const char* steel_gemm_splitk();
|
const char* steel_gemm_splitk();
|
||||||
const char* steel_gemm_gather();
|
const char* steel_gemm_gather();
|
||||||
|
const char* steel_gemm_segmented();
|
||||||
const char* conv();
|
const char* conv();
|
||||||
const char* steel_conv();
|
const char* steel_conv();
|
||||||
const char* steel_conv_general();
|
const char* steel_conv_general();
|
||||||
|
|||||||
@@ -8,12 +8,6 @@ using namespace fmt::literals;
|
|||||||
|
|
||||||
namespace mlx::core {
|
namespace mlx::core {
|
||||||
|
|
||||||
std::string op_name(const array& arr) {
|
|
||||||
std::ostringstream op_t;
|
|
||||||
arr.primitive().print(op_t);
|
|
||||||
return op_t.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_arange_kernel(
|
MTL::ComputePipelineState* get_arange_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
@@ -33,7 +27,7 @@ MTL::ComputePipelineState* get_unary_kernel(
|
|||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op) {
|
const char* op) {
|
||||||
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
||||||
auto lib = d.get_library(lib_name, [&]() {
|
auto lib = d.get_library(lib_name, [&]() {
|
||||||
auto in_t = get_type_string(in_type);
|
auto in_t = get_type_string(in_type);
|
||||||
@@ -58,10 +52,10 @@ MTL::ComputePipelineState* get_unary_kernel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void append_binary_kernels(
|
void append_binary_kernels(
|
||||||
const std::string lib_name,
|
const std::string& lib_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op,
|
const char* op,
|
||||||
std::string& kernel_source) {
|
std::string& kernel_source) {
|
||||||
const std::array<std::pair<std::string, std::string>, 7> kernel_types = {{
|
const std::array<std::pair<std::string, std::string>, 7> kernel_types = {{
|
||||||
{"ss", "binary_ss"},
|
{"ss", "binary_ss"},
|
||||||
@@ -112,7 +106,7 @@ MTL::ComputePipelineState* get_binary_kernel(
|
|||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op) {
|
const char* op) {
|
||||||
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
||||||
auto lib = d.get_library(lib_name, [&]() {
|
auto lib = d.get_library(lib_name, [&]() {
|
||||||
std::string kernel_source;
|
std::string kernel_source;
|
||||||
@@ -129,7 +123,7 @@ MTL::ComputePipelineState* get_binary_two_kernel(
|
|||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op) {
|
const char* op) {
|
||||||
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
||||||
auto lib = d.get_library(lib_name, [&]() {
|
auto lib = d.get_library(lib_name, [&]() {
|
||||||
std::string kernel_source = metal::utils();
|
std::string kernel_source = metal::utils();
|
||||||
@@ -144,7 +138,7 @@ MTL::ComputePipelineState* get_ternary_kernel(
|
|||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype type,
|
Dtype type,
|
||||||
const std::string op) {
|
const char* op) {
|
||||||
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
std::string lib_name = kernel_name.substr(kernel_name.find("_") + 1);
|
||||||
auto lib = d.get_library(lib_name, [&]() {
|
auto lib = d.get_library(lib_name, [&]() {
|
||||||
auto t_str = get_type_string(type);
|
auto t_str = get_type_string(type);
|
||||||
@@ -652,6 +646,43 @@ MTL::ComputePipelineState* get_steel_gemm_gather_kernel(
|
|||||||
return d.get_kernel(kernel_name, lib, hash_name, func_consts);
|
return d.get_kernel(kernel_name, lib, hash_name, func_consts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MTL::ComputePipelineState* get_steel_gemm_segmented_kernel(
|
||||||
|
metal::Device& d,
|
||||||
|
const std::string& kernel_name,
|
||||||
|
const std::string& hash_name,
|
||||||
|
const metal::MTLFCList& func_consts,
|
||||||
|
const array& out,
|
||||||
|
bool transpose_a,
|
||||||
|
bool transpose_b,
|
||||||
|
int bm,
|
||||||
|
int bn,
|
||||||
|
int bk,
|
||||||
|
int wm,
|
||||||
|
int wn) {
|
||||||
|
const auto& lib_name = kernel_name;
|
||||||
|
auto lib = d.get_library(lib_name, [&]() {
|
||||||
|
std::string kernel_source;
|
||||||
|
concatenate(
|
||||||
|
kernel_source,
|
||||||
|
metal::utils(),
|
||||||
|
metal::gemm(),
|
||||||
|
metal::steel_gemm_segmented(),
|
||||||
|
get_template_definition(
|
||||||
|
lib_name,
|
||||||
|
"segmented_mm",
|
||||||
|
get_type_string(out.dtype()),
|
||||||
|
bm,
|
||||||
|
bn,
|
||||||
|
bk,
|
||||||
|
wm,
|
||||||
|
wn,
|
||||||
|
transpose_a,
|
||||||
|
transpose_b));
|
||||||
|
return kernel_source;
|
||||||
|
});
|
||||||
|
return d.get_kernel(kernel_name, lib, hash_name, func_consts);
|
||||||
|
}
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_gemv_masked_kernel(
|
MTL::ComputePipelineState* get_gemv_masked_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
|
|||||||
@@ -19,27 +19,27 @@ MTL::ComputePipelineState* get_unary_kernel(
|
|||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op);
|
const char* op);
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_binary_kernel(
|
MTL::ComputePipelineState* get_binary_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op);
|
const char* op);
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_binary_two_kernel(
|
MTL::ComputePipelineState* get_binary_two_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype in_type,
|
Dtype in_type,
|
||||||
Dtype out_type,
|
Dtype out_type,
|
||||||
const std::string op);
|
const char* op);
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_ternary_kernel(
|
MTL::ComputePipelineState* get_ternary_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
Dtype type,
|
Dtype type,
|
||||||
const std::string op);
|
const char* op);
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_copy_kernel(
|
MTL::ComputePipelineState* get_copy_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
@@ -175,6 +175,20 @@ MTL::ComputePipelineState* get_steel_gemm_gather_kernel(
|
|||||||
int wn,
|
int wn,
|
||||||
bool rhs);
|
bool rhs);
|
||||||
|
|
||||||
|
MTL::ComputePipelineState* get_steel_gemm_segmented_kernel(
|
||||||
|
metal::Device& d,
|
||||||
|
const std::string& kernel_name,
|
||||||
|
const std::string& hash_name,
|
||||||
|
const metal::MTLFCList& func_consts,
|
||||||
|
const array& out,
|
||||||
|
bool transpose_a,
|
||||||
|
bool transpose_b,
|
||||||
|
int bm,
|
||||||
|
int bn,
|
||||||
|
int bk,
|
||||||
|
int wm,
|
||||||
|
int wn);
|
||||||
|
|
||||||
MTL::ComputePipelineState* get_steel_conv_kernel(
|
MTL::ComputePipelineState* get_steel_conv_kernel(
|
||||||
metal::Device& d,
|
metal::Device& d,
|
||||||
const std::string& kernel_name,
|
const std::string& kernel_name,
|
||||||
@@ -243,8 +257,10 @@ MTL::ComputePipelineState* get_gather_qmm_kernel(
|
|||||||
|
|
||||||
// Create a GPU kernel template definition for JIT compilation
|
// Create a GPU kernel template definition for JIT compilation
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::string
|
std::string get_template_definition(
|
||||||
get_template_definition(std::string name, std::string func, Args... args) {
|
std::string_view name,
|
||||||
|
std::string_view func,
|
||||||
|
Args... args) {
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
s << func << "<";
|
s << func << "<";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user