diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1ee212e7..9a0b078206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- setup.py builds C++ backend for develop and install commands. ## [0.4.0] - 2020-11-04 ### Added @@ -9,6 +11,7 @@ All notable changes to this project will be documented in this file. - MemoryUSM* classes moved to `dpctl.memory` module, added support for aligned allocation, added support for `prefetch` and `mem_advise` (sychronous) methods, implemented `copy_to_host`, `copy_from_host` and `copy_from_device` methods, pickling support, and zero-copy interoperability with Python objects which implement `__sycl_usm_array_inerface__` protocol. - Helper scripts to generate API documentation for both C API and Python. + ### Fixed - Compiler warnings when building libDPPLSyclInterface and the Cython extensions. diff --git a/README.md b/README.md index fd54be2f92..4a09b2b7bc 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,16 @@ Build and Install Conda Package conda create -n build-env conda-build conda activate build-env ``` -2. Build conda package +2. Set environment variable `ONEAPI_ROOT` and build conda package ```bash -conda build conda-recipe +export ONEAPI_ROOT=/opt/intel/oneapi +conda build conda-recipe -c ${ONEAPI_ROOT}/conda_channel ``` -On Windows to cope with [long file names](https://github.com/IntelPython/dpctl/issues/15): +On Windows to cope with [long file names](https://github.com/IntelPython/dpctl/issues/15) +use `croot` with short folder path: ```cmd -conda build --croot=C:/tmp conda-recipe +set "ONEAPI_ROOT=C:\Program Files (x86)\Intel\oneAPI\" +conda build --croot=C:/tmp conda-recipe -c "%ONEAPI_ROOT%\conda_channel" ``` :warning: **You could face issues with conda-build=3.20**: Use conda-build=3.18! @@ -36,9 +39,23 @@ conda build --croot=C:/tmp conda-recipe conda install dpctl ``` +Build and Install with setuptools +================================= +dpCtl relies on DPC++ runtime. With Intel oneAPI installed you should activate it. + +For install: +```cmd +python setup.py install +``` + +For development: +```cmd +python setup.py develop +``` + Using dpCtl =========== -dpCtl relies on DPC++ runtime. With Intel oneAPI installed you should activate it. +dpCtl relies on DPC++ runtime. With Intel oneAPI installed you could activate it. On Windows: ```cmd @@ -49,6 +66,12 @@ On Linux: source ${ONEAPI_ROOT}/compiler/latest/env/vars.sh ``` +When dpCtl is installed via conda package +then it uses DPC++ runtime from `dpcpp_cpp_rt` package +and it is not necessary to activate oneAPI DPC++ compiler environment. + +`dpcpp_cpp_rt` package is provided by oneAPI `conda_channel`. + Examples ======== See examples in folder `examples`. diff --git a/backends/CMakeLists.txt b/backends/CMakeLists.txt index 0485dba1f9..de5af9fb73 100644 --- a/backends/CMakeLists.txt +++ b/backends/CMakeLists.txt @@ -77,6 +77,7 @@ add_library( source/dppl_sycl_queue_manager.cpp source/dppl_sycl_usm_interface.cpp source/dppl_utils.cpp + helper/source/dppl_utils_helper.cpp ) # Install DPPLSyclInterface @@ -84,6 +85,7 @@ target_include_directories( DPPLSyclInterface PRIVATE ${CMAKE_SOURCE_DIR}/include/ + ${CMAKE_SOURCE_DIR}/helper/include/ ) if(WIN32) @@ -123,6 +125,12 @@ foreach(HEADER ${HEADERS}) install(FILES "${HEADER}" DESTINATION include/Support) endforeach() +# Install all headers in helper/include +file(GLOB HEADERS "${CMAKE_SOURCE_DIR}/helper/include/*.h*") +foreach(HEADER ${HEADERS}) + install(FILES "${HEADER}" DESTINATION helper/include) +endforeach() + option( BUILD_CAPI_TESTS "Build dpctl C API google tests" diff --git a/backends/helper/include/dppl_utils_helper.h b/backends/helper/include/dppl_utils_helper.h new file mode 100644 index 0000000000..1ddd898696 --- /dev/null +++ b/backends/helper/include/dppl_utils_helper.h @@ -0,0 +1,31 @@ +//===------------------- dppl_utils.h - dpctl-C_API ---*--- C++ -----*-----===// +// +// Data Parallel Control Library (dpCtl) +// +// Copyright 2020 Intel Corporation +// +// 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. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines common helper functions used in other places in DPPL. +//===----------------------------------------------------------------------===// + +#pragma once + +#include +using namespace cl::sycl; + +std::string DPPL_DeviceTypeToStr(info::device_type devTy); +info::device_type DPPL_StrToDeviceType(std::string devTyStr); diff --git a/backends/helper/source/dppl_utils_helper.cpp b/backends/helper/source/dppl_utils_helper.cpp new file mode 100644 index 0000000000..90991b2ccd --- /dev/null +++ b/backends/helper/source/dppl_utils_helper.cpp @@ -0,0 +1,82 @@ +//===------ dppl_utils_helper.cpp - dpctl-C_API ----*---- C++ -----*-----===// +// +// Data Parallel Control Library (dpCtl) +// +// Copyright 2020 Intel Corporation +// +// 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. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements the helper functions defined in dppl_utils_helper.h. +/// +//===----------------------------------------------------------------------===// + +#include "dppl_utils_helper.h" +#include +#include + +using namespace cl::sycl; + +/*! +* Transforms enum info::device_type to string. +*/ +std::string DPPL_DeviceTypeToStr(info::device_type devTy) +{ + std::stringstream ss; + switch (devTy) + { + case info::device_type::cpu: + ss << "cpu" << '\n'; + break; + case info::device_type::gpu: + ss << "gpu" << '\n'; + break; + case info::device_type::accelerator: + ss << "accelerator" << '\n'; + break; + case info::device_type::custom: + ss << "custom" << '\n'; + break; + case info::device_type::host: + ss << "host" << '\n'; + break; + default: + ss << "unknown" << '\n'; + } + return ss.str(); +} + +/*! +* Transforms string to enum info::device_type. +*/ +info::device_type DPPL_StrToDeviceType(std::string devTyStr) +{ + info::device_type devTy; + if (devTyStr == "cpu") { + devTy = info::device_type::cpu; + } else if(devTyStr == "gpu") { + devTy = info::device_type::gpu; + } else if(devTyStr == "accelerator") { + devTy = info::device_type::accelerator; + } else if(devTyStr == "custom") { + devTy = info::device_type::custom; + } else if(devTyStr == "host") { + devTy = info::device_type::host; + } else { + // \todo handle the error + throw std::runtime_error("Unknown device type."); + } + return devTy; +} diff --git a/backends/source/dppl_sycl_device_interface.cpp b/backends/source/dppl_sycl_device_interface.cpp index 5aca623418..5d862e47ec 100644 --- a/backends/source/dppl_sycl_device_interface.cpp +++ b/backends/source/dppl_sycl_device_interface.cpp @@ -30,6 +30,7 @@ #include #include #include /* SYCL headers */ +#include "../helper/include/dppl_utils_helper.h" using namespace cl::sycl; @@ -58,26 +59,7 @@ void dump_device_info (const device & Device) ss << std::setw(4) << " " << std::left << std::setw(16) << "Device type"; auto devTy = Device.get_info(); - switch(devTy) - { - case info::device_type::cpu: - ss << "cpu" << '\n'; - break; - case info::device_type::gpu: - ss << "gpu" << '\n'; - break; - case info::device_type::accelerator: - ss << "accelerator" << '\n'; - break; - case info::device_type::custom: - ss << "custom" << '\n'; - break; - case info::device_type::host: - ss << "host" << '\n'; - break; - default: - ss << "unknown" << '\n'; - } + ss << DPPL_DeviceTypeToStr(devTy); std::cout << ss.str(); } diff --git a/backends/source/dppl_sycl_platform_interface.cpp b/backends/source/dppl_sycl_platform_interface.cpp index 1db0987145..efee45d2c1 100644 --- a/backends/source/dppl_sycl_platform_interface.cpp +++ b/backends/source/dppl_sycl_platform_interface.cpp @@ -29,6 +29,7 @@ #include #include #include +#include "../helper/include/dppl_utils_helper.h" #include @@ -124,26 +125,7 @@ void DPPLPlatform_DumpInfo () << "Device type"; auto devTy = devices[dn].get_info(); - switch (devTy) - { - case info::device_type::cpu: - ss << "cpu" << '\n'; - break; - case info::device_type::gpu: - ss << "gpu" << '\n'; - break; - case info::device_type::accelerator: - ss << "accelerator" << '\n'; - break; - case info::device_type::custom: - ss << "custom" << '\n'; - break; - case info::device_type::host: - ss << "host" << '\n'; - break; - default: - ss << "unknown" << '\n'; - } + ss << DPPL_DeviceTypeToStr(devTy); } std::cout << ss.str(); ++i; diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat index 08a1987817..0793695ed8 100644 --- a/conda-recipe/bld.bat +++ b/conda-recipe/bld.bat @@ -3,47 +3,9 @@ IF %ERRORLEVEL% NEQ 0 ( echo "oneAPI compiler activation failed" exit /b 1 ) -REM conda uses %ERRORLEVEL% but FPGA scripts can set it. So it should be reseted. -set ERRORLEVEL= - -set "CC=clang-cl.exe" -set "CXX=dpcpp.exe" - -rmdir /S /Q build_cmake -mkdir build_cmake -cd build_cmake - -set "DPCPP_ROOT=%ONEAPI_ROOT%\compiler\latest\windows" -set "INSTALL_PREFIX=%cd%\..\install" - -rmdir /S /Q "%INSTALL_PREFIX%" - -cmake -G Ninja ^ - -DCMAKE_BUILD_TYPE=Release ^ - "-DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX%" ^ - "-DCMAKE_PREFIX_PATH=%LIBRARY_PREFIX%" ^ - "-DDPCPP_ROOT=%DPCPP_ROOT%" ^ - "%SRC_DIR%\backends" -IF %ERRORLEVEL% NEQ 0 exit /b 1 - -ninja -n -ninja install -IF %ERRORLEVEL% NEQ 0 exit /b 1 - -cd .. -xcopy install\lib\*.lib dpctl /E /Y -xcopy install\bin\*.dll dpctl /E /Y - -mkdir dpctl\include -xcopy backends\include dpctl\include /E /Y - - -REM required by _sycl_core(dpctl) -set "DPPL_SYCL_INTERFACE_LIBDIR=dpctl" -set "DPPL_SYCL_INTERFACE_INCLDIR=dpctl\include" "%PYTHON%" setup.py clean --all -"%PYTHON%" setup.py build install +"%PYTHON%" setup.py install IF %ERRORLEVEL% NEQ 0 exit /b 1 rem Build wheel package diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh index 670786ac88..1c962f6ddd 100755 --- a/conda-recipe/build.sh +++ b/conda-recipe/build.sh @@ -4,53 +4,13 @@ if [ ! -z "${ONEAPI_ROOT}" ]; then # Suppress error b/c it could fail on Ubuntu 18.04 source ${ONEAPI_ROOT}/compiler/latest/env/vars.sh || true - export CC=clang - export CXX=clang++ else echo "DPCPP is needed to build DPPL. Abort!" exit 1 fi -rm -rf build_cmake -mkdir build_cmake -pushd build_cmake - -INSTALL_PREFIX=`pwd`/../install -rm -rf ${INSTALL_PREFIX} - -PYTHON_INC=`${PYTHON} -c "import distutils.sysconfig; \ - print(distutils.sysconfig.get_python_inc())"` -NUMPY_INC=`${PYTHON} -c "import numpy; print(numpy.get_include())"` -DPCPP_ROOT=${ONEAPI_ROOT}/compiler/latest/linux/ - -cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ - -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ - -DDPCPP_ROOT=${DPCPP_ROOT} \ - -DPYTHON_INCLUDE_DIR=${PYTHON_INC} \ - -DNUMPY_INCLUDE_DIR=${NUMPY_INC} \ - ../backends - -make -j 4 && make install - -popd -cp install/lib/*.so dpctl/ - -mkdir -p dpctl/include -cp -r backends/include/* dpctl/include - - -# required by dpctl.sycl_core -export DPPL_SYCL_INTERFACE_LIBDIR=dpctl -export DPPL_SYCL_INTERFACE_INCLDIR=dpctl/include - - -# FIXME: How to pass this using setup.py? This flags is needed when -# dpcpp compiles the generated cpp file. -export CFLAGS="-fPIC -O3 ${CFLAGS}" ${PYTHON} setup.py clean --all -${PYTHON} setup.py build install +${PYTHON} setup.py install # Build wheel package if [ -n "${WHEELS_OUTPUT_FOLDER}" ]; then diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index e25ebad3a0..72f5868d09 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -28,6 +28,7 @@ requirements: run: - python - numpy >=1.17 + - dpcpp_cpp_rt about: home: https://github.com/IntelPython/dpCtl.git diff --git a/dpctl/memory/__init__.pxd b/dpctl/memory/__init__.pxd index d969a6fbfd..62b84d5eba 100644 --- a/dpctl/memory/__init__.pxd +++ b/dpctl/memory/__init__.pxd @@ -1,4 +1,4 @@ -##===------------- __init__.pxd - dpctl module --------*- Cython -*-------===## +##===----------- __init__.pxd - dpctl.memory module ----*- Cython -*-------===## ## ## Data Parallel Control (dpCtl) ## @@ -20,7 +20,7 @@ ## ## \file ## This file declares the extension types and functions for the Cython API -## implemented in sycl_core.pyx. +## implemented in dpctl.memory_memory.pyx. ## ##===----------------------------------------------------------------------===## diff --git a/dpctl/memory/__init__.py b/dpctl/memory/__init__.py index a00a368075..fc8c25f217 100644 --- a/dpctl/memory/__init__.py +++ b/dpctl/memory/__init__.py @@ -1,4 +1,4 @@ -##===---------- memory/__init__.py - dpctl module -------*- Python -*------===## +##===---------- __init__.py - dpctl.memory module -------*- Python -*------===## ## ## Data Parallel Control (dpCtl) ## @@ -19,7 +19,8 @@ ##===----------------------------------------------------------------------===## ## ## \file -## This top-level dpctl module. +## This is the dpctl.memory module containing the USM memory manager features +## of dpctl. ## ##===----------------------------------------------------------------------===## """ diff --git a/dpctl/memory/_memory.pxd b/dpctl/memory/_memory.pxd index b475627800..231b2a9c18 100644 --- a/dpctl/memory/_memory.pxd +++ b/dpctl/memory/_memory.pxd @@ -45,6 +45,8 @@ cdef class _Memory: @staticmethod cdef SyclDevice get_pointer_device(DPPLSyclUSMRef p, SyclContext ctx) + @staticmethod + cdef bytes get_pointer_type(DPPLSyclUSMRef p, SyclContext ctx) cdef class MemoryUSMShared(_Memory): diff --git a/dpctl/memory/_memory.pyx b/dpctl/memory/_memory.pyx index d985b519d7..a5d82171f7 100644 --- a/dpctl/memory/_memory.pyx +++ b/dpctl/memory/_memory.pyx @@ -429,10 +429,18 @@ cdef class _Memory: @staticmethod cdef SyclDevice get_pointer_device(DPPLSyclUSMRef p, SyclContext ctx): + """Returns sycl device used to allocate given pointer `p` in given sycl context `ctx`""" cdef DPPLSyclDeviceRef dref = DPPLUSM_GetPointerDevice(p, ctx.get_context_ref()) return SyclDevice._create(dref) + @staticmethod + cdef bytes get_pointer_type(DPPLSyclUSMRef p, SyclContext ctx): + """Returns USM-type of given pointer `p` in given sycl context `ctx`""" + cdef const char * usm_type = DPPLUSM_GetPointerType(p, ctx.get_context_ref()) + + return usm_type + cdef class MemoryUSMShared(_Memory): """ diff --git a/scripts/build_backend.py b/scripts/build_backend.py new file mode 100644 index 0000000000..57d9cd3ef0 --- /dev/null +++ b/scripts/build_backend.py @@ -0,0 +1,82 @@ +import os +import sys +import subprocess +import shutil +import glob + +IS_WIN = False +IS_LIN = False + +if "linux" in sys.platform: + IS_LIN = True +elif sys.platform in ["win32", "cygwin"]: + IS_WIN = True +else: + assert False, sys.platform + " not supported" + +ONEAPI_ROOT = os.environ.get("ONEAPI_ROOT") + +if IS_LIN: + DPCPP_ROOT = os.path.join(ONEAPI_ROOT, "compiler/latest/linux") +if IS_WIN: + DPCPP_ROOT = os.path.join(ONEAPI_ROOT, "compiler\latest\windows") + +dpctl_dir = os.getcwd() +build_cmake_dir = os.path.join(dpctl_dir, "build_cmake") +if os.path.exists(build_cmake_dir): + shutil.rmtree(build_cmake_dir) +os.mkdir(build_cmake_dir) +os.chdir(build_cmake_dir) + +INSTALL_PREFIX = os.path.join(dpctl_dir, "install") +if os.path.exists(INSTALL_PREFIX): + shutil.rmtree(INSTALL_PREFIX) + +backends_dir = os.path.join(dpctl_dir, "backends") + +if IS_LIN: + cmake_args = [ + "cmake", + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX=" + INSTALL_PREFIX, + "-DCMAKE_PREFIX_PATH=" + INSTALL_PREFIX, + "-DDPCPP_ROOT=" + DPCPP_ROOT, + "-DCMAKE_C_COMPILER:PATH=" + os.path.join(DPCPP_ROOT, "bin", "clang"), + "-DCMAKE_CXX_COMPILER:PATH=" + os.path.join(DPCPP_ROOT, "bin", "clang++"), + backends_dir, + ] + subprocess.check_call(cmake_args, stderr=subprocess.STDOUT, shell=False) + subprocess.check_call(["make", "-j", "4"]) + subprocess.check_call(["make", "install"]) + + os.chdir(dpctl_dir) + for file in glob.glob(os.path.join(dpctl_dir, "install", "lib", "*.so")): + shutil.copy(file, os.path.join(dpctl_dir, "dpctl")) + +if IS_WIN: + cmake_args = [ + "cmake", + "-G", + "Ninja", + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX=" + INSTALL_PREFIX, + "-DCMAKE_PREFIX_PATH=" + INSTALL_PREFIX, + "-DDPCPP_ROOT=" + DPCPP_ROOT, + backends_dir, + ] + subprocess.check_call(cmake_args, stderr=subprocess.STDOUT, shell=True) + subprocess.check_call(["ninja", "-n"]) + subprocess.check_call(["ninja", "install"]) + + os.chdir(dpctl_dir) + for file in glob.glob(os.path.join(dpctl_dir, "install", "lib", "*.lib")): + shutil.copy(file, os.path.join(dpctl_dir, "dpctl")) + + for file in glob.glob(os.path.join(dpctl_dir, "install", "bin", "*.dll")): + shutil.copy(file, os.path.join(dpctl_dir, "dpctl")) + +include_dir = os.path.join(dpctl_dir, "dpctl", "include") +if os.path.exists(include_dir): + shutil.rmtree(include_dir) + +shutil.copytree(os.path.join(dpctl_dir, "backends", "include"), include_dir) diff --git a/setup.py b/setup.py index ba9fbd0f21..f358b4ed43 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,10 @@ import os.path import sys import versioneer +import subprocess +import setuptools.command.install as orig_install +import setuptools.command.develop as orig_develop from setuptools import setup, Extension, find_packages from Cython.Build import cythonize @@ -48,6 +51,20 @@ else: assert False, sys.platform + " not supported" +if IS_LIN: + DPCPP_ROOT = os.environ["ONEAPI_ROOT"] + "/compiler/latest/linux" + os.environ["CC"] = DPCPP_ROOT + "/bin/clang" + os.environ["CXX"] = DPCPP_ROOT + "/bin/clang++" + os.environ["DPPL_SYCL_INTERFACE_LIBDIR"] = "dpctl" + os.environ["DPPL_SYCL_INTERFACE_INCLDIR"] = "dpctl/include" + os.environ["CFLAGS"] = "-fPIC" + +elif IS_WIN: + os.environ["CC"] = "clang-cl.exe" + os.environ["CXX"] = "dpcpp.exe" + os.environ["DPPL_SYCL_INTERFACE_LIBDIR"] = "dpctl" + os.environ["DPPL_SYCL_INTERFACE_INCLDIR"] = "dpctl\include" + dppl_sycl_interface_lib = os.environ["DPPL_SYCL_INTERFACE_LIBDIR"] dppl_sycl_interface_include = os.environ["DPPL_SYCL_INTERFACE_INCLDIR"] sycl_lib = os.environ["ONEAPI_ROOT"] + "\compiler\latest\windows\lib" @@ -99,6 +116,11 @@ def get_suppressed_warning_flags(): return [] +def build_backend(): + build_script = os.path.join(os.getcwd(), "scripts", "build_backend.py") + subprocess.check_call([sys.executable, build_script]) + + def extensions(): # Security flags eca = get_sdl_cflags() @@ -161,10 +183,29 @@ def extensions(): return exts +class install(orig_install.install): + def run(self): + build_backend() + return super().run() + + +class develop(orig_develop.develop): + def run(self): + build_backend() + return super().run() + + +def _get_cmdclass(): + cmdclass = versioneer.get_cmdclass() + cmdclass["install"] = install + cmdclass["develop"] = develop + return cmdclass + + setup( name="dpctl", version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), + cmdclass=_get_cmdclass(), description="A lightweight Python wrapper for a subset of OpenCL and SYCL.", license="Apache 2.0", author="Intel Corporation",