2016-09-19 12:44:13 +01:00
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Mehdi Goli Codeplay Software Ltd.
// Ralph Potter Codeplay Software Ltd.
// Luke Iwanski Codeplay Software Ltd.
// Contact: <eigen@codeplay.com>
2016-11-04 18:18:19 +00:00
// Copyright (C) 2016 Benoit Steiner <benoit.steiner.goog@gmail.com>
2016-09-19 12:44:13 +01:00
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
# if defined(EIGEN_USE_SYCL) && !defined(EIGEN_CXX11_TENSOR_TENSOR_DEVICE_SYCL_H)
# define EIGEN_CXX11_TENSOR_TENSOR_DEVICE_SYCL_H
namespace Eigen {
2016-11-11 19:06:34 +00:00
2016-11-25 16:19:07 +00:00
auto get_sycl_supported_devices ( ) - > decltype ( cl : : sycl : : device : : get_devices ( ) ) {
auto devices = cl : : sycl : : device : : get_devices ( ) ;
std : : vector < cl : : sycl : : device > : : iterator it = devices . begin ( ) ;
while ( it ! = devices . end ( ) ) {
/// get_devices returns all the available opencl devices. Either use device_selector or exclude devices that computecpp does not support (AMD OpenCL for CPU )
auto s = ( * it ) . template get_info < cl : : sycl : : info : : device : : vendor > ( ) ;
std : : transform ( s . begin ( ) , s . end ( ) , s . begin ( ) , : : tolower ) ;
if ( ( * it ) . is_cpu ( ) & & s . find ( " amd " ) ! = std : : string : : npos ) {
it = devices . erase ( it ) ;
}
else {
+ + it ;
}
}
return devices ;
}
2016-11-18 16:20:42 +00:00
# define ConvertToActualTypeSycl(T, buf_acc) reinterpret_cast<typename cl::sycl::global_ptr<T>::pointer_t>((&(*buf_acc.get_pointer())))
struct QueueInterface {
/// class members:
2016-11-18 16:34:54 +00:00
bool exception_caught_ = false ;
2016-11-20 13:17:08 -08:00
mutable std : : mutex mutex_ ;
2016-09-19 12:44:13 +01:00
/// std::map is the container used to make sure that we create only one buffer
2016-11-08 17:08:02 +00:00
/// per pointer. The lifespan of the buffer now depends on the lifespan of SyclDevice.
/// If a non-read-only pointer is needed to be accessed on the host we should manually deallocate it.
2016-11-18 16:20:42 +00:00
mutable std : : map < const uint8_t * , cl : : sycl : : buffer < uint8_t , 1 > > buffer_map ;
/// sycl queue
mutable cl : : sycl : : queue m_queue ;
2016-11-23 16:30:41 +00:00
/// creating device by using cl::sycl::selector or cl::sycl::device both are the same and can be captured throufh dev_Selector typename
2016-11-18 16:20:42 +00:00
/// SyclStreamDevice is not owned. it is the caller's responsibility to destroy it.
2016-11-18 12:38:06 -08:00
template < typename dev_Selector > explicit QueueInterface ( dev_Selector s ) :
2016-11-08 22:01:14 +00:00
# ifdef EIGEN_EXCEPTIONS
2016-11-18 12:38:06 -08:00
m_queue ( cl : : sycl : : queue ( s , [ & ] ( cl : : sycl : : exception_list l ) {
2016-11-08 21:56:31 +00:00
for ( const auto & e : l ) {
2016-11-08 17:08:02 +00:00
try {
2016-11-18 12:38:06 -08:00
if ( e ) {
exception_caught_ = true ;
std : : rethrow_exception ( e ) ;
2016-11-08 17:08:02 +00:00
}
2016-11-18 16:20:42 +00:00
} catch ( cl : : sycl : : exception e ) {
2016-11-18 12:38:06 -08:00
std : : cerr < < e . what ( ) < < std : : endl ;
}
2016-11-08 21:56:31 +00:00
}
} ) )
2016-11-08 21:08:53 +00:00
# else
2016-11-08 21:56:31 +00:00
m_queue ( cl : : sycl : : queue ( s ) )
2016-11-08 21:08:53 +00:00
# endif
2016-11-08 21:56:31 +00:00
{ }
2016-11-11 19:06:34 +00:00
2016-11-18 16:20:42 +00:00
/// Allocating device pointer. This pointer is actually an 8 bytes host pointer used as key to access the sycl device buffer.
/// The reason is that we cannot use device buffer as a pointer as a m_data in Eigen leafNode expressions. So we create a key
/// pointer to be used in Eigen expression construction. When we convert the Eigen construction into the sycl construction we
/// use this pointer as a key in our buffer_map and we make sure that we dedicate only one buffer only for this pointer.
/// The device pointer would be deleted by calling deallocate function.
EIGEN_STRONG_INLINE void * allocate ( size_t num_bytes ) const {
auto buf = cl : : sycl : : buffer < uint8_t , 1 > ( cl : : sycl : : range < 1 > ( num_bytes ) ) ;
auto ptr = buf . get_access < cl : : sycl : : access : : mode : : discard_write , cl : : sycl : : access : : target : : host_buffer > ( ) . get_pointer ( ) ;
buf . set_final_data ( nullptr ) ;
2016-11-20 13:17:08 -08:00
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
2016-11-18 16:20:42 +00:00
buffer_map . insert ( std : : pair < const uint8_t * , cl : : sycl : : buffer < uint8_t , 1 > > ( ptr , buf ) ) ;
return static_cast < void * > ( ptr ) ;
}
2016-09-19 12:44:13 +01:00
2016-11-11 19:06:34 +00:00
/// This is used to deallocate the device pointer. p is used as a key inside
/// the map to find the device buffer and delete it.
2016-11-18 16:20:42 +00:00
EIGEN_STRONG_INLINE void deallocate ( const void * p ) const {
2016-11-20 13:17:08 -08:00
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
2016-11-18 16:20:42 +00:00
auto it = buffer_map . find ( static_cast < const uint8_t * > ( p ) ) ;
2016-09-19 12:44:13 +01:00
if ( it ! = buffer_map . end ( ) ) {
buffer_map . erase ( it ) ;
2016-11-08 17:08:02 +00:00
}
}
2016-11-11 19:06:34 +00:00
2016-11-18 16:20:42 +00:00
EIGEN_STRONG_INLINE std : : map < const uint8_t * , cl : : sycl : : buffer < uint8_t , 1 > > : : iterator find_buffer ( const void * ptr ) const {
2016-11-20 13:17:08 -08:00
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
2016-11-18 16:20:42 +00:00
auto it1 = buffer_map . find ( static_cast < const uint8_t * > ( ptr ) ) ;
if ( it1 ! = buffer_map . end ( ) ) {
return it1 ;
}
else {
for ( std : : map < const uint8_t * , cl : : sycl : : buffer < uint8_t , 1 > > : : iterator it = buffer_map . begin ( ) ; it ! = buffer_map . end ( ) ; + + it ) {
auto size = it - > second . get_size ( ) ;
if ( ( it - > first < ( static_cast < const uint8_t * > ( ptr ) ) ) & & ( ( static_cast < const uint8_t * > ( ptr ) ) < ( it - > first + size ) ) ) return it ;
}
}
std : : cerr < < " No sycl buffer found. Make sure that you have allocated memory for your buffer by calling allocate function in SyclDevice " < < std : : endl ;
abort ( ) ;
}
2016-11-18 16:34:54 +00:00
// This function checks if the runtime recorded an error for the
// underlying stream device.
2016-11-18 12:38:06 -08:00
EIGEN_STRONG_INLINE bool ok ( ) const {
2016-11-19 10:56:24 -08:00
if ( ! exception_caught_ ) {
2016-11-29 15:30:42 +00:00
m_queue . wait_and_throw ( ) ;
2016-11-19 10:56:24 -08:00
}
2016-11-18 12:38:06 -08:00
return ! exception_caught_ ;
2016-11-18 16:34:54 +00:00
}
2016-11-29 15:30:42 +00:00
2016-11-18 16:20:42 +00:00
// destructor
~ QueueInterface ( ) { buffer_map . clear ( ) ; }
} ;
struct SyclDevice {
// class member.
2016-11-18 12:38:06 -08:00
QueueInterface * m_queue_stream ;
2016-11-18 16:20:42 +00:00
/// QueueInterface is not owned. it is the caller's responsibility to destroy it.
2016-11-18 12:38:06 -08:00
explicit SyclDevice ( QueueInterface * queue_stream ) : m_queue_stream ( queue_stream ) { }
2016-09-19 12:44:13 +01:00
2016-11-11 19:06:34 +00:00
/// Creation of sycl accessor for a buffer. This function first tries to find
2016-11-08 17:08:02 +00:00
/// the buffer in the buffer_map. If found it gets the accessor from it, if not,
2016-11-11 19:06:34 +00:00
/// the function then adds an entry by creating a sycl buffer for that particular pointer.
2016-11-18 16:20:42 +00:00
template < cl : : sycl : : access : : mode AcMd > EIGEN_STRONG_INLINE cl : : sycl : : accessor < uint8_t , 1 , AcMd , cl : : sycl : : access : : target : : global_buffer >
2016-11-23 16:30:41 +00:00
get_sycl_accessor ( cl : : sycl : : handler & cgh , const void * ptr ) const {
return ( get_sycl_buffer ( ptr ) . template get_access < AcMd , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ) ;
2016-11-08 17:08:02 +00:00
}
2016-11-11 19:06:34 +00:00
/// Accessing the created sycl device buffer for the device pointer
2016-11-23 16:30:41 +00:00
EIGEN_STRONG_INLINE cl : : sycl : : buffer < uint8_t , 1 > & get_sycl_buffer ( const void * ptr ) const {
2016-11-18 12:38:06 -08:00
return m_queue_stream - > find_buffer ( ptr ) - > second ;
2016-09-19 12:44:13 +01:00
}
2016-11-11 19:06:34 +00:00
/// This is used to prepare the number of threads and also the number of threads per block for sycl kernels
2016-11-25 16:19:07 +00:00
template < typename Index >
EIGEN_STRONG_INLINE void parallel_for_setup ( Index n , Index & tileSize , Index & rng , Index & GRange ) const {
tileSize = static_cast < Index > ( sycl_queue ( ) . get_device ( ) . template get_info < cl : : sycl : : info : : device : : max_work_group_size > ( ) / 2 ) ;
2016-11-23 16:30:41 +00:00
rng = n ;
2016-11-25 16:19:07 +00:00
if ( rng = = 0 ) rng = static_cast < Index > ( 1 ) ;
2016-11-23 16:30:41 +00:00
GRange = rng ;
if ( tileSize > GRange ) tileSize = GRange ;
else if ( GRange > tileSize ) {
2016-11-25 16:19:07 +00:00
Index xMode = static_cast < Index > ( GRange % tileSize ) ;
if ( xMode ! = 0 ) GRange + = static_cast < Index > ( tileSize - xMode ) ;
2016-11-11 19:06:34 +00:00
}
2016-11-23 16:30:41 +00:00
}
2016-11-18 16:20:42 +00:00
/// allocate device memory
EIGEN_STRONG_INLINE void * allocate ( size_t num_bytes ) const {
2016-11-18 12:38:06 -08:00
return m_queue_stream - > allocate ( num_bytes ) ;
2016-09-19 12:44:13 +01:00
}
2016-11-18 16:20:42 +00:00
/// deallocate device memory
EIGEN_STRONG_INLINE void deallocate ( const void * p ) const {
2016-11-18 12:38:06 -08:00
m_queue_stream - > deallocate ( p ) ;
2016-11-18 16:20:42 +00:00
}
2016-09-19 12:44:13 +01:00
// some runtime conditions that can be applied here
2016-11-10 19:16:31 +00:00
EIGEN_STRONG_INLINE bool isDeviceSuitable ( ) const { return true ; }
2016-09-19 12:44:13 +01:00
2016-11-14 17:51:57 +00:00
2016-11-11 19:06:34 +00:00
/// the memcpy function
2016-11-14 17:51:57 +00:00
template < typename T > EIGEN_STRONG_INLINE void memcpy ( void * dst , const T * src , size_t n ) const {
2016-11-18 12:38:06 -08:00
auto it1 = m_queue_stream - > find_buffer ( ( void * ) src ) ;
auto it2 = m_queue_stream - > find_buffer ( dst ) ;
2016-11-18 16:20:42 +00:00
auto offset = ( static_cast < const uint8_t * > ( static_cast < const void * > ( src ) ) ) - it1 - > first ;
auto i = ( static_cast < const uint8_t * > ( dst ) ) - it2 - > first ;
offset / = sizeof ( T ) ;
i / = sizeof ( T ) ;
size_t rng , GRange , tileSize ;
parallel_for_setup ( n / sizeof ( T ) , tileSize , rng , GRange ) ;
sycl_queue ( ) . submit ( [ & ] ( cl : : sycl : : handler & cgh ) {
auto src_acc = it1 - > second . template get_access < cl : : sycl : : access : : mode : : read , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ;
auto dst_acc = it2 - > second . template get_access < cl : : sycl : : access : : mode : : discard_write , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ;
2016-11-25 16:19:07 +00:00
cgh . parallel_for ( cl : : sycl : : nd_range < 1 > ( cl : : sycl : : range < 1 > ( GRange ) , cl : : sycl : : range < 1 > ( tileSize ) ) , TensorSycl : : internal : : MemCopyFunctor < T > ( src_acc , dst_acc , rng , 0 , offset ) ) ;
2016-11-18 16:20:42 +00:00
} ) ;
2016-11-29 15:30:42 +00:00
synchronize ( ) ;
2016-09-19 12:44:13 +01:00
}
2016-11-08 17:08:02 +00:00
2016-11-11 19:06:34 +00:00
/// The memcpyHostToDevice is used to copy the device only pointer to a host pointer. Using the device
/// pointer created as a key we find the sycl buffer and get the host accessor with discard_write mode
/// on it. Using a discard_write accessor guarantees that we do not bring back the current value of the
/// buffer to host. Then we use the memcpy to copy the data to the host accessor. The first time that
/// this buffer is accessed, the data will be copied to the device.
2016-11-10 19:16:31 +00:00
template < typename T > EIGEN_STRONG_INLINE void memcpyHostToDevice ( T * dst , const T * src , size_t n ) const {
2016-11-23 16:30:41 +00:00
auto host_acc = get_sycl_buffer ( dst ) . template get_access < cl : : sycl : : access : : mode : : discard_write , cl : : sycl : : access : : target : : host_buffer > ( ) ;
2016-11-14 17:51:57 +00:00
: : memcpy ( host_acc . get_pointer ( ) , src , n ) ;
2016-09-19 12:44:13 +01:00
}
2016-11-11 19:06:34 +00:00
/// The memcpyDeviceToHost is used to copy the data from host to device. Here, in order to avoid double copying the data. We create a sycl
/// buffer with map_allocator for the destination pointer with a discard_write accessor on it. The lifespan of the buffer is bound to the
/// lifespan of the memcpyDeviceToHost function. We create a kernel to copy the data, from the device- only source buffer to the destination
/// buffer with map_allocator on the gpu in parallel. At the end of the function call the destination buffer would be destroyed and the data
/// would be available on the dst pointer using fast copy technique (map_allocator). In this case we can make sure that we copy the data back
/// to the cpu only once per function call.
2016-11-18 16:20:42 +00:00
template < typename T > EIGEN_STRONG_INLINE void memcpyDeviceToHost ( void * dst , const T * src , size_t n ) const {
2016-11-18 12:38:06 -08:00
auto it = m_queue_stream - > find_buffer ( src ) ;
2016-11-18 16:20:42 +00:00
auto offset = static_cast < const uint8_t * > ( static_cast < const void * > ( src ) ) - it - > first ;
offset / = sizeof ( T ) ;
2016-11-10 18:45:12 +00:00
size_t rng , GRange , tileSize ;
parallel_for_setup ( n / sizeof ( T ) , tileSize , rng , GRange ) ;
2016-11-14 17:51:57 +00:00
// Assuming that the dst is the start of the destination pointer
2016-11-18 16:20:42 +00:00
auto dest_buf = cl : : sycl : : buffer < uint8_t , 1 , cl : : sycl : : map_allocator < uint8_t > > ( static_cast < uint8_t * > ( dst ) , cl : : sycl : : range < 1 > ( rng * sizeof ( T ) ) ) ;
sycl_queue ( ) . submit ( [ & ] ( cl : : sycl : : handler & cgh ) {
auto src_acc = it - > second . template get_access < cl : : sycl : : access : : mode : : read , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ;
2016-11-10 18:45:12 +00:00
auto dst_acc = dest_buf . template get_access < cl : : sycl : : access : : mode : : discard_write , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ;
2016-11-25 16:19:07 +00:00
cgh . parallel_for ( cl : : sycl : : nd_range < 1 > ( cl : : sycl : : range < 1 > ( GRange ) , cl : : sycl : : range < 1 > ( tileSize ) ) , TensorSycl : : internal : : MemCopyFunctor < T > ( src_acc , dst_acc , rng , 0 , offset ) ) ;
2016-11-10 18:45:12 +00:00
} ) ;
2016-11-29 15:30:42 +00:00
synchronize ( ) ;
2016-09-19 12:44:13 +01:00
}
2016-11-18 16:20:42 +00:00
/// returning the sycl queue
2016-11-18 12:38:06 -08:00
EIGEN_STRONG_INLINE cl : : sycl : : queue & sycl_queue ( ) const { return m_queue_stream - > m_queue ; }
2016-11-11 19:06:34 +00:00
/// Here is the implementation of memset function on sycl.
2016-11-10 19:16:31 +00:00
template < typename T > EIGEN_STRONG_INLINE void memset ( T * buff , int c , size_t n ) const {
2016-11-18 16:20:42 +00:00
size_t rng , GRange , tileSize ;
parallel_for_setup ( n / sizeof ( T ) , tileSize , rng , GRange ) ;
sycl_queue ( ) . submit ( [ & ] ( cl : : sycl : : handler & cgh ) {
2016-11-23 16:30:41 +00:00
auto buf_acc = get_sycl_buffer ( static_cast < uint8_t * > ( static_cast < void * > ( buff ) ) ) . template get_access < cl : : sycl : : access : : mode : : discard_write , cl : : sycl : : access : : target : : global_buffer > ( cgh ) ;
2016-11-18 16:20:42 +00:00
cgh . parallel_for < SyclDevice > ( cl : : sycl : : nd_range < 1 > ( cl : : sycl : : range < 1 > ( GRange ) , cl : : sycl : : range < 1 > ( tileSize ) ) , [ = ] ( cl : : sycl : : nd_item < 1 > itemID ) {
auto globalid = itemID . get_global_linear_id ( ) ;
2016-11-23 16:30:41 +00:00
if ( globalid < n ) {
2016-11-18 16:20:42 +00:00
for ( size_t i = 0 ; i < sizeof ( T ) ; i + + )
buf_acc [ globalid * sizeof ( T ) + i ] = c ;
}
2016-11-10 18:45:12 +00:00
} ) ;
2016-11-18 16:20:42 +00:00
} ) ;
2016-11-29 15:30:42 +00:00
synchronize ( ) ;
2016-09-19 12:44:13 +01:00
}
2016-11-11 19:06:34 +00:00
/// No need for sycl it should act the same as CPU version
2016-11-18 16:20:42 +00:00
EIGEN_STRONG_INLINE int majorDeviceVersion ( ) const { return 1 ; }
2016-11-29 15:30:42 +00:00
2016-11-17 21:29:15 -08:00
EIGEN_STRONG_INLINE void synchronize ( ) const {
2016-11-29 15:30:42 +00:00
sycl_queue ( ) . wait_and_throw ( ) ; //pass
2016-11-17 21:51:48 -08:00
}
2016-11-18 16:34:54 +00:00
// This function checks if the runtime recorded an error for the
// underlying stream device.
2016-11-18 12:38:06 -08:00
EIGEN_STRONG_INLINE bool ok ( ) const {
return m_queue_stream - > ok ( ) ;
2016-11-18 16:34:54 +00:00
}
2016-09-19 12:44:13 +01:00
} ;
2016-11-08 17:08:02 +00:00
2016-11-18 16:34:54 +00:00
2016-09-19 12:44:13 +01:00
} // end namespace Eigen
# endif // EIGEN_CXX11_TENSOR_TENSOR_DEVICE_SYCL_H