diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 41265746c..a9c6c6231 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -50,7 +50,7 @@ set( root_files "graphblas/io.hpp" "graphblas/iomode.hpp" "graphblas/matrix.hpp" "graphblas/monoid.hpp" "graphblas/ops.hpp" "graphblas/phase.hpp" "graphblas/pinnedvector.hpp" "graphblas/properties.hpp" "graphblas/rc.hpp" - "graphblas/semiring.hpp" "graphblas/spmd.hpp" "graphblas/tags.hpp" + "graphblas/semiring.hpp" "graphblas/spmd.hpp" "graphblas/rdma.hpp" "graphblas/tags.hpp" "graphblas/type_traits.hpp" "graphblas/utils.hpp" "graphblas/vector.hpp" "graphblas/synchronizedNonzeroIterator.hpp" "graphblas/nonzeroStorage.hpp" "graphblas/selection_ops.hpp" diff --git a/include/graphblas.hpp b/include/graphblas.hpp index dd2f63102..4de4cec42 100644 --- a/include/graphblas.hpp +++ b/include/graphblas.hpp @@ -545,6 +545,7 @@ namespace grb { #include #include #include +#include #ifdef _GRB_WITH_LPF // collects various BSP utilities diff --git a/include/graphblas/base/rdma.hpp b/include/graphblas/base/rdma.hpp new file mode 100644 index 000000000..f790ab5ff --- /dev/null +++ b/include/graphblas/base/rdma.hpp @@ -0,0 +1,93 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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 + * + * Exposes facilities for Remote Direct Memory Access + * + * @author G. Gaio + * @date 16th of December, 2025 + */ + +#ifndef _H_GRB_BASE_RDMA +#define _H_GRB_BASE_RDMA + +#include //size_t + +#include // SIZE_MAX + +#include +#include + +#include "config.hpp" + + +namespace grb { + + /** + * For backends that support multiple user processes this class defines some + * basic primitives to support RDMA programming. + * + * All backends must implement this interface, including backends that do not + * support multiple user processes. The interface herein defined hence ensures + * to allow for trivial implementations for single user process backends. + */ + template< Backend implementation > + class rdma { + public: + template< typename T > + static inline grb::RC register_global( const T &buf ); + + template< typename T > + static inline grb::RC register_global( const grb::Vector< T, grb::reference > &buf ); + + template< typename T > + static inline grb::RC deregister( const T &buf ); + + template< typename T > + static inline grb::RC deregister( const grb::Vector< T, grb::reference > &buf ); + + static inline grb::RC localRegisterSize( const size_t size ); + + template< typename T > + static inline grb::RC get( const size_t src_pid, T &src, T &dst ); + + template< + grb::Descriptor descr, + grb::Backend backend, + typename T, + typename Coords + > + static inline grb::RC get( const size_t src_pid, const grb::Vector< T, backend, Coords > &src, grb::Vector< T, backend, Coords > &dst ); + + template< typename T > + static inline grb::RC put( const T &src, const size_t dst_pid, T &dst ); + + template< + grb::Descriptor descr, + grb::Backend backend, + typename T, + typename Coords + > + static inline grb::RC put( const grb::Vector< T, backend, Coords > &src, const size_t dst_pid, grb::Vector< T, backend, Coords > &dst ); + }; // end class ``rdma'' + +} // namespace grb + +#endif // end _H_GRB_BASE_SPMD + diff --git a/include/graphblas/bsp/rdma.hpp b/include/graphblas/bsp/rdma.hpp new file mode 100644 index 000000000..daef166be --- /dev/null +++ b/include/graphblas/bsp/rdma.hpp @@ -0,0 +1,476 @@ +/* + * Copyright 2025 Huawei Technologies Co., Ltd. + * + * 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. + */ + +/* + * @author G. Gaio + * @date December, 2025 + */ + +#ifndef _H_GRB_LPF_RDMA +#define _H_GRB_LPF_RDMA + +#include +#include //size_t + +#include + +#include +#include // get_request, put_request + + +namespace grb { + + /** Superclass implementation for all LPF-backed implementations. */ + template<> + class rdma< GENERIC_BSP > { + private: + + /* + * Registers a global buffer for RDMA + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * + * @param[in] buf Pointer to the start of the buffer of memory to be registered. + * @param[in] size Size of the buffer in bytes. + * + * @return grb::SUCCESS When registration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + static grb::RC register_global( const void *buf, const size_t size ) { + grb::internal::BSP1D_Data & data = grb::internal::grb_BSP1D.load(); + lpf_err_t lpf_rc = LPF_SUCCESS; + grb::RC rc = grb::SUCCESS; + lpf_memslot_t memslot = LPF_INVALID_MEMSLOT; + + const bool buffer_already_registered = (data.registered_slots.find( buf ) != data.registered_slots.end()); + if( buffer_already_registered ){ +#ifdef _DEBUG + std::cerr << "Buffer already registered. Nothing to do." << std::endl; +#endif + return grb::SUCCESS; + } + + rc = rc ? rc : data.ensureMemslotAvailable( 1 ); + + lpf_rc = lpf_rc ? lpf_rc : lpf_register_global( + data.context, const_cast< void* >( buf ), + size, &memslot + ); + lpf_rc = lpf_rc ? lpf_rc : lpf_sync( data.context, LPF_SYNC_DEFAULT ); + + data.signalMemslotTaken(); + data.registered_slots.insert({ buf, grb::internal::registered_slot(buf, memslot, size, true ) }); + data.global_memslots.insert({ memslot, buf }); + + if( lpf_rc == LPF_SUCCESS ) { + return rc; + } else { + return grb::PANIC; + } + } + + /** + * Deregisters a buffer for RDMA. If the buffer is global, this is a collective + * function + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * @param[in] buf Pointer to the start of the buffer of memory to be deregistered. + * + * @return grb::SUCCESS When deregistration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + static grb::RC deregister( const void *buf ) { +#ifdef _DEBUG + std::cout << "deregister: memslot " << memslot << std::endl; +#endif + grb::internal::BSP1D_Data & data = grb::internal::grb_BSP1D.load(); + lpf_err_t lpf_rc = LPF_SUCCESS; + lpf_memslot_t memslot = LPF_INVALID_MEMSLOT; + + const auto it0 = data.registered_slots.find( buf ); + assert( it0 != data.registered_slots.end() ); + + memslot = it0->second.slot; + data.registered_slots.erase( it0 ); + + const auto it1 = data.global_memslots.find( memslot ); + assert( it1 != data.global_memslots.end() ); + data.global_memslots.erase( it1 ); + + lpf_rc = lpf_rc ? lpf_rc : lpf_deregister( data.context, memslot ); + + data.signalMemslotReleased( 1 ); + + if( lpf_rc == LPF_SUCCESS ) { + return grb::SUCCESS; + } else { + return grb::PANIC; + } + } + + /** + * Reads a message from another process' registered memory + * + * Before calling this function, the user is responsible for ensuring that + * enough registers are free, using the function localRegisterSize. Each call + * to function will use one register if the source pointer has not been + * already registered (locally or globally), and zero otherwise. + * + * @param[in] src_pid PID of process whose memory to access. + * @param[in] src Local pointer to the buffer whose associated pointer in the source process where to read. + * @param[out] dst Pointer to the begin of the output buffer. + * @param[in] size Number of bytes to copy. + * + * @return grb::SUCCESS When all queued communication is executed succesfully. + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + static grb::RC get( const size_t &src_pid, const void *src, void *dst, const size_t size ) { +#ifdef _DEBUG + std::cout << "rdma::get( " << src << ", " << size << ", " << src_pid << ", " << src_memslot << ") called" << std::endl; +#endif + const auto lpf_attr = LPF_MSG_DEFAULT; + grb::internal::BSP1D_Data & data = grb::internal::grb_BSP1D.load(); + lpf_memslot_t src_memslot = LPF_INVALID_MEMSLOT; + lpf_memslot_t dst_memslot = LPF_INVALID_MEMSLOT; + lpf_err_t lpf_rc = LPF_SUCCESS; + + // dynamic checks + if( src_pid >= data.P ) { + return grb::ILLEGAL; + } + + // check trivial dispatch + if( size == 0 ) { + return grb::SUCCESS; + } + + { + const auto it = data.registered_slots.find( src ); + assert( it != data.registered_slots.end() ); + assert( it->second.size >= size ); + assert( it->second.global ); + src_memslot = it->second.slot; + } + + const auto it = data.registered_slots.find( dst ); + if( it == data.registered_slots.end() ){ + lpf_rc = lpf_rc ? lpf_rc : lpf_register_local( data.context, const_cast< void* >( dst ), size, &dst_memslot ); + data.signalMemslotTaken(); + data.registered_slots.insert({dst, grb::internal::registered_slot( dst, dst_memslot, size, false )}); + } else { + assert( it->second.buf == dst ); + assert( it->second.size >= size ); + dst_memslot = it->second.slot; + } + + lpf_rc = lpf_rc ? lpf_rc : lpf_get( data.context, src_pid, src_memslot , 0, dst_memslot, 0, size, lpf_attr ); + data.get_requests.emplace_back( src_pid, src_memslot, 0, src, size ); + + if( lpf_rc == LPF_SUCCESS ) { + return grb::SUCCESS; + } else { + return grb::PANIC; + } + } + + /** + * Writes a message to another process' registered memory. + * + * Before calling this function, the user is responsible for ensuring that + * enough registers are free, using the function localRegisterSize. Each call + * to function will use one register if the destination pointer has not been + * already registered (locally or globally), and zero otherwise. + * + * @param[in] src Pointer to the begin of the input buffer. + * @param[in] dst_pid PID of process whose memory to access. + * @param[out] dst Local pointer to the buffer whose associated pointer in the source process where to write. + * @param[in] size Number of bytes to copy. + * + * @return grb::SUCCESS When all queued communication is executed succesfully. + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + static grb::RC put( const void *src, const size_t dst_pid, void *dst, const size_t &size ) { +#ifdef _DEBUG + std::cout << "rdma::put( " << src << ", " << size << ", " << dst_pid << ", " << dst << ") called" << std::endl; +#endif + + const auto lpf_attr = LPF_MSG_DEFAULT; + grb::internal::BSP1D_Data & data = grb::internal::grb_BSP1D.load(); + lpf_err_t lpf_rc = LPF_SUCCESS; + lpf_memslot_t src_memslot = LPF_INVALID_MEMSLOT; + lpf_memslot_t dst_memslot = LPF_INVALID_MEMSLOT; + + // dynamic checks + if( dst_pid >= data.P ) { + return grb::ILLEGAL; + } + + // check trivial dispatch + if( size == 0 ) { + return grb::SUCCESS; + } + + // rc = rc ? rc : data.ensureMemslotAvailable( 1 ); // this function calls lpf_sync + { + const auto it = data.registered_slots.find( dst ); + assert( it != data.registered_slots.end() ); + assert( it->second.buf == dst ); + assert( it->second.size >= size ); + assert( it->second.global ); + dst_memslot = it->second.slot; + } + + const auto it = data.registered_slots.find( src ); + if( it == data.registered_slots.end() ){ + lpf_rc = lpf_rc ? lpf_rc : lpf_register_local( data.context, const_cast< void* >( src ), size, &src_memslot ); + data.signalMemslotTaken(); + data.registered_slots.insert({src, grb::internal::registered_slot( src, src_memslot, size, false )}); + } else { + assert( it->second.buf == src ); + assert( it->second.size >= size ); // is there enough space? + src_memslot = it->second.slot; + } + + lpf_rc = lpf_rc ? lpf_rc : lpf_put( data.context, src_memslot, 0, dst_pid, dst_memslot, 0, size, lpf_attr ); + data.put_requests.emplace_back( src, dst_pid, dst_memslot, 0, size ); + + if( lpf_rc == LPF_SUCCESS ) { + return grb::SUCCESS; + } else { + return grb::PANIC; + } + } + + public: + + /* + * Registers a global buffer for RDMA on a POD variable. + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * + * @param[in] buf Scalar to be registered + * + * @return grb::SUCCESS When registration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + template< typename T > + static inline grb::RC register_global( const T &buf ) { + return register_global( reinterpret_cast< const void* >( &buf ), sizeof(T) ); + } + + /* + * Registers a global buffer for RDMA + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * + * @param[in] buf Pointer to the start of the buffer of memory to be reserved. + * @param[in] size Size of the buffer in bytes. + * + * @return grb::SUCCESS When registration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + template< + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC register_global( const grb::Vector< T, backend, Coords > &buf ) { + const size_t size = grb::internal::getCoordinates( buf ).size(); + const size_t bsize = size * sizeof( T ); + const void *raw_ptr = reinterpret_cast< const void* >( grb::internal::getRaw( buf ) ); + + return register_global( raw_ptr, bsize ); + } + + /* + * Deregisters a global buffer on a POD variable. + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * + * @param[in] buf Scalar to be deregistered + * + * @return grb::SUCCESS When registration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + template< typename T > + static inline grb::RC deregister( const T &buf ) { + return deregister( reinterpret_cast< const void* >( &buf ) ); + } + + /* + * Deregisters a global buffer. + * + * \warning This is a collective operation, therefore it must be called by all the processes! + * + * @param[in] buf Pointer to the start of the buffer of memory to be deregistered. + * + * @return grb::SUCCESS When registration is completed successfully + * @return grb::PANIC When an unrecoverable error occurs. When this value is + * returned, the library enters an undefined state. + */ + template< + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC deregister( const grb::Vector< T, backend, Coords > &buf ) { + const void *raw_ptr = reinterpret_cast< const void* >( grb::internal::getRaw( buf ) ); + + return deregister( raw_ptr ); + } + + /* + * Reserve space for size additional registers. This function should be used + * before calling put and/or get. These RDMA functions automatically register + * local unregistered buffers, so need space in LPF register to do so. + * One register is needed for each put/get between successive a syncs. + * + * \warning This is a collective operation. A sync could be called internally, therefore it must be called by all the processes! + * + * @param[in] size The number of registers to reserve space for. + * + * @return grb::SUCCESS The register space was successfully ensured. + * @return PANIC Could not ensure a large enough buffer space. The state + * of the library has become undefined. + */ + static inline grb::RC localRegisterSize( const size_t size ) { + grb::internal::BSP1D_Data & data = grb::internal::grb_BSP1D.load(); + + grb::RC rc = data.ensureMemslotAvailable( size ); + return rc; + } + + /* + * Copy a variable from a remote process. + * Source variable must have been globally registered. + * + * See private get function for more details. + * + * @param[in] src_pid Source Process ID. + * @param[in] src Object associated with the remote object to read from. + * @param[out] dst Destination object to write to. + * + * @return grb::SUCCESS The register space was successfully ensured. + * @return PANIC Could not ensure a large enough buffer space. The state + * of the library has become undefined. + */ + template< typename T > + static inline grb::RC get( const size_t src_pid, const T &src, T &dst ) { + + const void *src_ptr = reinterpret_cast< const void* >( &src ); + void *dst_ptr = reinterpret_cast< void* >( &dst ); + constexpr size_t size = sizeof(T); + + return get( src_pid, src_ptr, dst_ptr, size ); + } + + /* + * + * Copy a grb::Vector from a remote process. + * Source grb::Vector must have been globally registered. + * + * See private get function for more details. + * + * @param[in] src_pid Source Process ID. + * @param[in] src Object associated with the remote Vector to read from. + * @param[out] dst Destination Vector to write to. + * + * @return grb::SUCCESS The register space was successfully ensured. + * @return PANIC Could not ensure a large enough buffer space. The state + * of the library has become undefined. + */ + template< + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC get( const size_t src_pid, const grb::Vector< T, backend, Coords > &src, grb::Vector< T, backend, Coords > &dst ) { + + // we only support grb::reference for now + static_assert( grb::reference == backend ); + + const size_t size = grb::internal::getCoordinates( dst ).size(); + const size_t bsize = size * sizeof( T ); + const void *src_ptr = reinterpret_cast< const void* >( grb::internal::getRaw( src ) ); + void *dst_ptr = reinterpret_cast< void* >( grb::internal::getRaw( dst ) ); + + return get( src_pid, src_ptr, dst_ptr, bsize ); + } + + /* + * Write a scalar variable to another process. + * + * Vectors must be of the same size (in the respective processes)! + * + * See private put function for more details. + * + * @param[in] src Variable to read from. + * @param[in] dst_pid Destination Process ID. + * @param[out] dst Variable associated with the scalar in the destination process to write to. + * + * @return grb::SUCCESS The register space was successfully ensured. + * @return PANIC Could not ensure a large enough buffer space. The state + * of the library has become undefined. + */ + template< typename T > + static inline grb::RC put( const T &src, const size_t dst_pid, T &dst ) { + + const void *src_ptr = reinterpret_cast< const void* >( &src ); + void *dst_ptr = reinterpret_cast< void* >( &dst ); + constexpr size_t size = sizeof(T); + + return put( src_ptr, dst_pid, dst_ptr, size ); + } + + /* + * Write a grb::Vector to another process. + * + * @param[in] src grb::Vector to read from. + * @param[in] dst_pid Destination Process ID. + * @param[out] dst grb::Vector whose associated object in the destination process to write to. + * + * @return grb::SUCCESS The register space was successfully ensured. + * @return PANIC Could not ensure a large enough buffer space. The state + * of the library has become undefined. + */ + template< + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC put( const grb::Vector< T, backend, Coords > &src, const size_t dst_pid, grb::Vector< T, backend, Coords > &dst) { + // we only support grb::reference for now + static_assert( grb::reference == backend ); + + const size_t size = grb::internal::getCoordinates( src ).size(); + const size_t bsize = size * sizeof( T ); + const void *src_ptr = reinterpret_cast< const void* >( grb::internal::getRaw( src ) ); + void *dst_ptr = reinterpret_cast< void* >( grb::internal::getRaw( dst ) ); + + return put( src_ptr, dst_pid, dst_ptr, bsize ); + } + }; // end class ``rdma'' generic LPF implementation + +} // namespace grb + +#endif // end _H_GRB_LPF_RDMA diff --git a/include/graphblas/bsp1d/init.hpp b/include/graphblas/bsp1d/init.hpp index fc7c1df16..e49ee5b57 100644 --- a/include/graphblas/bsp1d/init.hpp +++ b/include/graphblas/bsp1d/init.hpp @@ -52,17 +52,64 @@ namespace grb { lpf_pid_t src_pid; lpf_memslot_t src; size_t src_offset; - void * dst; + const void * dst; size_t size; + + get_request( + const lpf_pid_t src_pid, + const lpf_memslot_t src, + const size_t src_offset, + const void * dst, + const size_t size + ) + : src_pid(src_pid) + , src(src) + , src_offset(src_offset) + , dst(dst) + , size(size) + {} }; /** All information corresponding to a put request. */ struct put_request { - void * src; + const void * src; lpf_pid_t dst_pid; lpf_memslot_t dst; size_t dst_offset; size_t size; + + put_request( + const void * src, + const lpf_pid_t dst_pid, + const lpf_memslot_t dst, + const size_t dst_offset, + const size_t size + ) + : src(src) + , dst_pid(dst_pid) + , dst(dst) + , dst_offset(dst_offset) + , size(size) + {} + }; + + struct registered_slot { + const void * buf; + lpf_memslot_t slot; + size_t size; + bool global; + + registered_slot( + const void * buf, + const lpf_memslot_t slot, + const size_t size, + const bool global + ) + : buf(buf) + , slot(slot) + , size(size) + , global(global) + {} }; /** @@ -191,9 +238,15 @@ namespace grb { /** Whether a finalize has been called. */ bool destroyed; - /** Mapper to assign IDs to BSP1D containers .*/ + /** Mapper to assign IDs to BSP1D containers. */ utils::DMapper< uintptr_t > mapper; + /** Map of globally registered addresses. */ + std::map< const void* , registered_slot > registered_slots; + + /** Map of registered memory slots to their address. */ + std::map< const lpf_memslot_t , const void* > global_memslots; + /** * This class is default-constructible. * diff --git a/include/graphblas/bsp1d/rdma.hpp b/include/graphblas/bsp1d/rdma.hpp new file mode 100644 index 000000000..3d7b5778e --- /dev/null +++ b/include/graphblas/bsp1d/rdma.hpp @@ -0,0 +1,31 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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 _H_GRB_BSP1D_RDMA +#define _H_GRB_BSP1D_RDMA + +#include + +namespace grb { + + /** Superclass implementation for all BSP-backed implementations. */ + template<> + class rdma< BSP1D > : public rdma< GENERIC_BSP > {}; + +} // namespace grb + +#endif // end _H_GRB_BSP1D_SPMD diff --git a/include/graphblas/hyperdags/rdma.hpp b/include/graphblas/hyperdags/rdma.hpp new file mode 100644 index 000000000..d1339538f --- /dev/null +++ b/include/graphblas/hyperdags/rdma.hpp @@ -0,0 +1,39 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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 + * + * Provides the RDMA API for the HyperDAGs backend + * + * @author G. Gaio + * @date 16th of December 2025 + */ + +#include //size_t + +#include + +namespace grb { + + template<> + class rdma< hyperdags > : rdma< reference > { + + }; // end class ``rdma'' reference implementation + +} // namespace grb + diff --git a/include/graphblas/nonblocking/rdma.hpp b/include/graphblas/nonblocking/rdma.hpp new file mode 100644 index 000000000..1aaae1220 --- /dev/null +++ b/include/graphblas/nonblocking/rdma.hpp @@ -0,0 +1,47 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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 + * + * Provides the RDMA functions for the nonblocking backend. + * + * @author G. Gaio + * @date 16th of December 2025 + */ + +#ifndef _H_GRB_NONBLOCKING_RDMA +#define _H_GRB_NONBLOCKING_RDMA + +#include //size_t + +#include +#include + + +namespace grb { + + /** The rdma class is based on that of the reference backend */ + template<> + class rdma< nonblocking > : grb::rdma< reference > { + + }; // end class ``rdma'' nonblocking implementation + +} // namespace grb + +#endif // end _H_GRB_NONBLOCKING_RDMA + diff --git a/include/graphblas/rdma.hpp b/include/graphblas/rdma.hpp new file mode 100644 index 000000000..451c2a22b --- /dev/null +++ b/include/graphblas/rdma.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Huawei Technologies Co., Ltd. + * + * 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. + */ + +/* + * @author G. Gaio + * @date December, 2025 + */ + +#ifndef _H_GRB_RDMA +#define _H_GRB_RDMA + +#include "base/config.hpp" +#include "base/rdma.hpp" + +#ifdef _GRB_WITH_REFERENCE + #include "graphblas/reference/rdma.hpp" +#endif +#ifdef _GRB_WITH_HYPERDAGS + #include +#endif +#ifdef _GRB_WITH_NONBLOCKING + #include "graphblas/nonblocking/rdma.hpp" +#endif +#ifdef _GRB_WITH_LPF + #include "graphblas/bsp1d/rdma.hpp" +#endif + +// specify default only if requested during compilation +#ifdef _GRB_BACKEND +namespace grb { + template< Backend implementation = config::default_backend > + class rdma; +} +#endif + +#endif // end _H_GRB_RDMA diff --git a/include/graphblas/reference/rdma.hpp b/include/graphblas/reference/rdma.hpp new file mode 100644 index 000000000..47b28a5ab --- /dev/null +++ b/include/graphblas/reference/rdma.hpp @@ -0,0 +1,115 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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. + */ + +/* + * @author A. N. Yzelman + * @date 28th of April, 2017 + */ + +#if ! defined _H_GRB_REFERENCE_RDMA || defined _H_GRB_REFERENCE_OMP_RDMA +#define _H_GRB_REFERENCE_RDMA + +#include //size_t + +#include + +namespace grb { + + /** No implementation notes. */ + template<> + class rdma< reference > { + public: + template< typename T > + static inline grb::RC register_global( const T &buf ) { + (void) buf; + return grb::SUCCESS; + } + + template< typename T > + static inline grb::RC register_global( const grb::Vector< T, grb::reference > &buf ) { + (void) buf; + return grb::SUCCESS; + } + + template< typename T > + static inline grb::RC deregister( const T &buf ) { + (void) buf; + return grb::SUCCESS; + } + + template< typename T > + static inline grb::RC deregister( const grb::Vector< T, grb::reference > &buf ) { + (void) buf; + return grb::SUCCESS; + } + + static inline grb::RC localRegisterSize( const size_t size ) { + (void) size; + return grb::SUCCESS; + } + + template< typename T > + static inline grb::RC get( const size_t src_pid, T &src, T &dst ) { + assert( src_pid == 0 ); + dst = src; + return grb::SUCCESS; + } + + template< + grb::Descriptor descr = descriptors::no_operation, + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC get( const size_t src_pid, const grb::Vector< T, backend, Coords > &src, grb::Vector< T, backend, Coords > &dst ) { + assert( src_pid == 0 ); + return grb::set< descr >( dst, src ); + } + + template< typename T > + static inline grb::RC put( const T &src, const size_t dst_pid, T &dst ) { + assert( dst_pid == 0 ); + dst = src; + return grb::SUCCESS; + } + + template< + grb::Descriptor descr = descriptors::no_operation, + grb::Backend backend = grb::reference, + typename T, + typename Coords + > + static inline grb::RC put( const grb::Vector< T, backend, Coords > &src, const size_t dst_pid, grb::Vector< T, backend, Coords > &dst ) { + assert( dst_pid == 0 ); + return grb::set< descr >( dst, src ); + } + }; // end class ``rdma'' reference implementation + +} // namespace grb + +// parse again for reference_omp backend +#ifdef _GRB_WITH_OMP +#ifndef _H_GRB_REFERENCE_OMP_RDMA +#define _H_GRB_REFERENCE_OMP_RDMA +#define reference reference_omp +#include "graphblas/reference/rdma.hpp" +#undef reference +#undef _H_GRB_REFERENCE_OMP_RDMA +#endif +#endif + +#endif // end _H_GRB_REFERENCE_RDMA diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 5cd03cf77..e197c1ea7 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -374,6 +374,10 @@ add_grb_executables( launch_benchmark_frommpi_manual launcherAndBenchmarker.cpp COMPILE_DEFINITIONS NO_LPF_AUTO_INIT ) +add_grb_executables( rdma rdma.cpp + BACKENDS bsp1d +) + # targets to list and build the test for this category get_property( unit_tests_list GLOBAL PROPERTY tests_category_unit ) add_custom_target( "list_tests_category_unit" diff --git a/tests/unit/rdma.cpp b/tests/unit/rdma.cpp new file mode 100644 index 000000000..c893ab67d --- /dev/null +++ b/tests/unit/rdma.cpp @@ -0,0 +1,161 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * 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. + */ + +#include +#include +#include + +#include + +const int LPF_MPI_AUTO_INITIALIZE = 0; + +void grbProgram( const size_t &n, grb::RC &rc ) { + const size_t s = grb::spmd<>::pid(); + const size_t P = grb::spmd<>::nprocs(); + + assert( s < P ); + assert( P > 1 ); + + if( s == 0 ){ + std::cout << "Testing RDMA of POD" << std::endl; + } + int x = 42, y = 69; + + if( s == 0 ){ + std::cout << "\t register" << std::endl; + } + rc = rc ? rc : grb::rdma< >::register_global( x ); + assert( rc == grb::SUCCESS ); + + if( s == 0 ){ + std::cout << "\t put" << std::endl; + } + if( s == 0 ){ + rc = rc ? rc : grb::rdma< >::put( y, 1, x ); + x = 0; + } + rc = rc ? rc : grb::spmd<>::sync(); + assert( rc == grb::SUCCESS ); + assert( s != 1 || x == 69 ); + + rc = rc ? rc : grb::spmd<>::sync(); + + if( s == 0 ){ + std::cout << "\t get" << std::endl; + } + if( s == 1 ){ + rc = rc ? rc : grb::rdma< >::get( 0, x, y ); + } + rc = rc ? rc : grb::spmd<>::sync(); + assert( s != 1 || y == 0 ); + rc = rc ? rc : grb::rdma< >::deregister( x ); + rc = rc ? rc : grb::spmd<>::sync(); + + if( rc != grb::SUCCESS ) return; + if( s == 0 ){ + std::cout << "Testing RDMA of grb::Vector" << std::endl; + } + + using T=float; + grb::Vector< T, grb::reference > a ( n ); + grb::Vector< T, grb::reference > b ( n ); + constexpr T v1 = 42.69; + + if( s == 0 ){ + std::cout << "\t register" << std::endl; + } + + rc = rc ? rc : grb::set( a, static_cast( s ) ); + rc = rc ? rc : grb::set( b, static_cast( s+1 ) ); + + rc = rc ? rc : grb::rdma<>::register_global( a ); + assert( rc == grb::SUCCESS ); + rc = rc ? rc : grb::spmd<>::sync(); + + rc = rc ? rc : grb::rdma<>::localRegisterSize( 2 ); + + if( s == 0 ){ + std::cout << "\t put" << std::endl; + } + if( s == 0 ){ + rc = rc ? rc : grb::rdma<>::put( a, 1, a ); + } + rc = rc ? rc : grb::spmd<>::sync(); + assert( rc == grb::SUCCESS ); + + if( s == 1 ){ + for(const auto &i : a ){ + assert( i.second == static_cast< T >( 0 ) ); + } + } + + rc = rc ? rc : grb::rdma<>::localRegisterSize( 2 ); + rc = rc ? rc : grb::spmd<>::sync(); + assert( rc == grb::SUCCESS ); + if( s == 0 ){ + std::cout << "\t get" << std::endl; + rc = rc ? rc : grb::set( a, v1 ); + } + rc = rc ? rc : grb::spmd<>::sync(); + if( s == 1 ){ + rc = rc ? rc : grb::rdma<>::get( 0, a, b ); + } + rc = rc ? rc : grb::spmd<>::sync(); + assert( rc == grb::SUCCESS ); + + if( s == 1 ){ + sleep(1); + for(const auto &i : b ){ + assert( i.second == v1 ); + } + } + rc = rc ? rc : grb::rdma<>::deregister( a ); + rc = rc ? rc : grb::spmd<>::sync(); + + return; +} + +int main( int argc, char ** argv ) { + (void) argc; + size_t n = 42; + grb::RC out = grb::SUCCESS; + + if( MPI_Init( &argc, &argv ) != MPI_SUCCESS ) { + std::cerr << "MPI_Init returns with non-SUCCESS exit code." << std::endl; + return 10; + } + + std::cout << "This is a functional test " << argv[ 0 ] << "\n"; + grb::Launcher< grb::FROM_MPI > launcher; + if( launcher.exec( &grbProgram, n, out, true ) != grb::SUCCESS ) { + std::cerr << "Launching test FAILED\n"; + return 255; + } + + if( MPI_Finalize() != MPI_SUCCESS ) { + std::cerr << "MPI_Finalize returns with non-SUCCESS exit code." << std::endl; + return 50; + } + + if( out == grb::SUCCESS ){ + std::cerr << "Test OK\n"; + }else{ + std::cerr << "Test FAILED\n"; + } + +} + diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 0b49d2421..936ad9a56 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -889,6 +889,12 @@ for MODE in ${MODES}; do echo ">>> [x] [ ] Testing BSP1D distribution." echo " " ${LPFRUN} -np 1 ${TEST_BIN_DIR}/distribution_${MODE} + + echo "RDMA unit tests for the BSP1D backend:" + echo " " + echo ">>> [x] [ ] Testing Remote Direct Memory Access interface" + echo " " + ${LPFRUN} -np 2 ${TEST_BIN_DIR}/rdma_${MODE}_bsp1d fi done