/******************************************************************************* * Copyright 2020-2025 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. *******************************************************************************/ #ifndef ONEAPI_DNNL_DNNL_SYCL_HPP #define ONEAPI_DNNL_DNNL_SYCL_HPP /// @cond DO_NOT_DOCUMENT_THIS #include #include #include #include #include #include #include #if __has_include() #include #else #error "Unsupported compiler" #endif #include "oneapi/dnnl/dnnl.hpp" #include "oneapi/dnnl/dnnl_sycl.h" /// @endcond /// @addtogroup dnnl_api /// @{ namespace dnnl { /// @addtogroup dnnl_api_interop /// @{ /// @addtogroup dnnl_api_sycl_interop SYCL interoperability API /// API extensions to interact with the underlying SYCL run-time. /// /// @sa @ref dev_guide_dpcpp_interoperability in developer guide /// @{ /// SYCL interoperability namespace namespace sycl_interop { /// Memory allocation kind. enum class memory_kind { /// USM (device, shared, host, or unknown) memory allocation kind - default. usm = dnnl_sycl_interop_usm, /// Buffer memory allocation kind. buffer = dnnl_sycl_interop_buffer, }; /// Converts a memory allocation kind enum value from C++ API to C API type. /// /// @param akind C++ API memory allocation kind enum value. /// @returns Corresponding C API memory allocation kind enum value. inline dnnl_sycl_interop_memory_kind_t convert_to_c(memory_kind akind) { return static_cast(akind); } /// Constructs an engine from SYCL device and context objects. /// /// @param adevice SYCL device. /// @param acontext SYCL context. /// /// @returns Created engine. inline engine make_engine( const sycl::device &adevice, const sycl::context &acontext) { dnnl_engine_t aengine; error::wrap_c_api(dnnl_sycl_interop_engine_create(&aengine, static_cast(&adevice), static_cast(&acontext)), "could not create an engine"); return engine(aengine); } /// Returns the SYCL context associated with an engine. /// /// @param aengine Engine to query. /// /// @returns The underlying SYCL device of the engine. inline sycl::context get_context(const engine &aengine) { void *ctx_ptr; error::wrap_c_api( dnnl_sycl_interop_engine_get_context(aengine.get(), &ctx_ptr), "could not get a context handle"); auto ctx = *static_cast(ctx_ptr); return ctx; } /// Returns the SYCL device associated with an engine. /// /// @param aengine Engine to query. /// /// @returns The underlying SYCL context of the engine. inline sycl::device get_device(const engine &aengine) { void *dev_ptr; error::wrap_c_api( dnnl_sycl_interop_engine_get_device(aengine.get(), &dev_ptr), "could not get a device handle"); auto dev = *static_cast(dev_ptr); return dev; } /// Creates an execution stream for a given engine associated with a SYCL /// queue. /// /// @param aengine Engine object to use for the stream. /// @param aqueue SYCL queue to use for the stream. /// /// @returns An execution stream. inline stream make_stream(const engine &aengine, sycl::queue &aqueue) { dnnl_stream_t astream; error::wrap_c_api( dnnl_sycl_interop_stream_create(&astream, aengine.get(), &aqueue), "could not create a stream"); return stream(astream); } /// Returns the SYCL queue associated with an execution stream. /// /// @param astream Execution stream to query. /// /// @returns SYCL queue object. inline sycl::queue get_queue(const stream &astream) { void *queue_ptr; error::wrap_c_api( dnnl_sycl_interop_stream_get_queue(astream.get(), &queue_ptr), "could not get a stream handle"); auto queue = *static_cast(queue_ptr); return queue; } /// Returns the SYCL buffer associated with a memory object. /// /// Throws an exception if the memory allocation kind associated with the /// memory object is not equal to dnnl::sycl_interop::memory_kind::buffer. /// /// @tparam T Type of the requested buffer. /// @tparam ndims Number of dimensions of the requested buffer. /// @param amemory Memory object. /// /// @returns SYCL buffer associated with the memory object. template sycl::buffer get_buffer(const memory &amemory) { static_assert(ndims == 1, "only 1D buffers supported"); // XXX: workaround: when CPU runtime is not SYCL and amemory was created // for CPU engine `get_buffer` should return an error. Use interop API to // implement the check. dnnl_sycl_interop_memory_kind_t ckind; error::wrap_c_api( dnnl_sycl_interop_memory_get_memory_kind(amemory.get(), &ckind), "could not get SYCL buffer object"); void *handle_ptr; error::wrap_c_api(dnnl_memory_get_data_handle(amemory.get(), &handle_ptr), "could not get SYCL buffer object"); // XXX: workaround: zero-range buffer cannot be constructed. if (!handle_ptr) return sycl::buffer(sycl::range<1>(1)); auto &buf_u8 = *static_cast *>(handle_ptr); auto range = sycl::range<1>(buf_u8.byte_size() / sizeof(T)); return buf_u8.reinterpret(range); } /// Sets SYCL buffer associated with a memory object. /// /// @tparam T Type of the buffer. /// @tparam ndims Number of dimensions of the buffer. /// @param amemory Memory object to change. /// @param abuffer SYCL buffer. template void set_buffer(memory &amemory, sycl::buffer &abuffer) { auto range = sycl::range<1>(abuffer.byte_size()); auto buf_u8 = abuffer.template reinterpret(range); error::wrap_c_api(dnnl_sycl_interop_memory_set_buffer( amemory.get(), static_cast(&buf_u8)), "could not set SYCL buffer object"); } /// Returns the memory allocation kind associated with a memory object. /// /// @param amemory A memory object. /// /// @returns The underlying memory allocation kind of the memory object. inline memory_kind get_memory_kind(const memory &amemory) { dnnl_sycl_interop_memory_kind_t ckind; error::wrap_c_api( dnnl_sycl_interop_memory_get_memory_kind(amemory.get(), &ckind), "could not get memory kind"); return static_cast(ckind); } #ifdef DNNL_EXPERIMENTAL_SPARSE /// Creates a memory object with multiple handles. /// /// @param memory_desc Memory descriptor. /// @param aengine Engine to use. /// @param kind Memory allocation kind to specify the type of handles. /// @param handles Handles of the memory buffers to use as underlying storages. /// For each element of the @p handles array the following applies: /// - A USM pointer to the user-allocated buffer. In this case the library /// doesn't own the buffer. Requires @p memory_kind to be equal to /// dnnl::sycl_interop::memory_kind::usm. /// - A pointer to SYCL buffer. In this case the library doesn't own the /// buffer. Requires @p memory_kind be equal to be equal to /// dnnl::sycl_interop::memory_kind::buffer. /// - The DNNL_MEMORY_ALLOCATE special value. Instructs the library to /// allocate the buffer that corresponds to the memory allocation kind /// @p memory_kind for the memory object. In this case the library /// owns the buffer. /// - The DNNL_MEMORY_NONE specific value. Instructs the library to /// create memory object without an underlying buffer. /// /// If the @p handles vector is not provided the library will allocate all /// buffers as if all handles have the special value DNNL_MEMORY_ALLOCATE. /// /// @returns Created memory object. inline memory make_memory(const memory::desc &memory_desc, const engine &aengine, memory_kind kind, std::vector handles = {}) { if (handles.empty()) { const int nhandles = memory_desc.get_num_handles(); handles.resize(nhandles, DNNL_MEMORY_ALLOCATE); } dnnl_memory_t c_memory; error::wrap_c_api( dnnl_sycl_interop_memory_create_v2(&c_memory, memory_desc.get(), aengine.get(), convert_to_c(kind), (int)handles.size(), handles.data()), "could not create a memory"); return memory(c_memory); } /// Creates a memory object. /// /// Unless @p handle is equal to DNNL_MEMORY_NONE or DNNL_MEMORY_ALLOCATE, the /// constructed memory object will have the underlying buffer set. In this /// case, the buffer will be initialized as if: /// - dnnl::memory::set_data_handle() had been called, if @p memory_kind is /// equal to dnnl::sycl_interop::memory_kind::usm, or /// - dnnl::sycl_interop::set_buffer() has been called, if @p memory_kind is /// equal to dnnl::sycl_interop::memory_kind::buffer. /// /// @param memory_desc Memory descriptor. /// @param aengine Engine to use. /// @param kind Memory allocation kind to specify the type of handle. /// @param handle Handle of the memory buffer to use as an underlying storage. /// - A USM pointer to the user-allocated buffer. In this case the library /// doesn't own the buffer. Requires @p memory_kind to be equal to /// dnnl::sycl_interop::memory_kind::usm. /// - A pointer to SYCL buffer. In this case the library doesn't own the /// buffer. Requires @p memory_kind be equal to be equal to /// dnnl::sycl_interop::memory_kind::buffer. /// - The DNNL_MEMORY_ALLOCATE special value. Instructs the library to /// allocate the buffer that corresponds to the memory allocation kind /// @p memory_kind for the memory object. In this case the library /// owns the buffer. /// - The DNNL_MEMORY_NONE specific value. Instructs the library to /// create memory object without an underlying buffer. /// /// @returns Created memory object. inline memory make_memory(const memory::desc &memory_desc, const engine &aengine, memory_kind kind, void *handle) { return make_memory( memory_desc, aengine, kind, std::vector {handle}); } #else /// Creates a memory object. /// /// Unless @p handle is equal to DNNL_MEMORY_NONE or DNNL_MEMORY_ALLOCATE, the /// constructed memory object will have the underlying buffer set. In this /// case, the buffer will be initialized as if: /// - dnnl::memory::set_data_handle() had been called, if @p memory_kind is /// equal to dnnl::sycl_interop::memory_kind::usm, or /// - dnnl::sycl_interop::set_buffer() has been called, if @p memory_kind is /// equal to dnnl::sycl_interop::memory_kind::buffer. /// /// @param memory_desc Memory descriptor. /// @param aengine Engine to use. /// @param kind Memory allocation kind to specify the type of handle. /// @param handle Handle of the memory buffer to use as an underlying storage. /// - A USM pointer to the user-allocated buffer. In this case the library /// doesn't own the buffer. Requires @p memory_kind to be equal to /// dnnl::sycl_interop::memory_kind::usm. /// - A pointer to SYCL buffer. In this case the library doesn't own the /// buffer. Requires @p memory_kind be equal to be equal to /// dnnl::sycl_interop::memory_kind::buffer. /// - The DNNL_MEMORY_ALLOCATE special value. Instructs the library to /// allocate the buffer that corresponds to the memory allocation kind /// @p memory_kind for the memory object. In this case the library /// owns the buffer. /// - The DNNL_MEMORY_NONE specific value. Instructs the library to /// create memory object without an underlying buffer. /// /// @returns Created memory object. inline memory make_memory(const memory::desc &memory_desc, const engine &aengine, memory_kind kind, void *handle = DNNL_MEMORY_ALLOCATE) { dnnl_memory_t c_memory; error::wrap_c_api( dnnl_sycl_interop_memory_create(&c_memory, memory_desc.get(), aengine.get(), convert_to_c(kind), handle), "could not create a memory"); return memory(c_memory); } #endif /// Constructs a memory object from a SYCL buffer. /// /// @param memory_desc Memory descriptor. /// @param aengine Engine to use. /// @param abuffer A SYCL buffer to use. /// /// @returns Created memory object. template memory make_memory(const memory::desc &memory_desc, const engine &aengine, sycl::buffer &abuffer) { memory amemory(memory_desc, aengine, DNNL_MEMORY_NONE); set_buffer(amemory, abuffer); return amemory; } /// Executes computations specified by the primitive in a specified stream and /// returns a SYCL event. /// /// Arguments are passed via an arguments map containing /// pairs. The index must be one of the `DNNL_ARG_*` /// values such as `DNNL_ARG_SRC`, and the memory must have a memory descriptor /// matching the one returned by /// #dnnl::primitive_desc::query_md(#query::exec_arg_md, index) unless using /// dynamic shapes (see #DNNL_RUNTIME_DIM_VAL). /// /// @param aprimitive Primitive to execute. /// @param astream Stream object. The stream must belong to the same engine /// as the primitive. /// @param args Arguments map. /// @param deps Optional vector with `sycl::event` dependencies. /// /// @returns Output event. inline sycl::event execute(const dnnl::primitive &aprimitive, const stream &astream, const std::unordered_map &args, const std::vector &deps = {}) { std::vector c_args; c_args.reserve(args.size()); for (const auto &a : args) c_args.push_back({a.first, a.second.get()}); sycl::event return_event; error::wrap_c_api( dnnl_sycl_interop_primitive_execute(aprimitive.get(), astream.get(), (int)c_args.size(), c_args.data(), &deps, &return_event), "could not execute a primitive"); return return_event; } } // namespace sycl_interop /// @} dnnl_api_sycl_interop /// @} dnnl_api_interop } // namespace dnnl /// @} dnnl_api #endif // DNNL_SYCL_HPP