Exchangers

A set of classes responsible to:

  • exchange ghost paticles between neighbouring ranks
  • redistribite particles accross ranks

The implementation is split into two parts:

Exchanger classes

Different kind of exchange are implemented in Mirheo:

In general, the process consists in:

  1. Create a map from particle/object to buffer(s) (this step might be unnecessary for e.g. mirheo::ObjectExtraExchanger and mirheo::ObjectReverseExchanger)
  2. Pack the data into the send buffers according to the map
  3. The communication engines communicate the data to the recv buffers (not the exchangers job)
  4. Unpack the data from recv buffers to a local container.

Interface

class Exchanger

Pack and unpack ParticleVector objects for exchange.

The user should register one (or more) ExchangeEntity objects that represent the data to exchange. The functions interface functions can then be called in the correct order to pack and unpack the data.

Designed to be used with an ExchangeEngine.

Subclassed by mirheo::ObjectExtraExchanger, mirheo::ObjectHaloExchanger, mirheo::ObjectRedistributor, mirheo::ObjectReverseExchanger, mirheo::ParticleHaloExchanger, mirheo::ParticleRedistributor

Public Functions

void addExchangeEntity(std::unique_ptr<ExchangeEntity> &&e)

register an ExchangeEntity in this exchanger.

Parameters

ExchangeEntity *getExchangeEntity(size_t id)

Return
ExchangeEntity with the given id (0 <= id < getNumExchangeEntities()).

const ExchangeEntity *getExchangeEntity(size_t id) const

see getExchangeEntity()

size_t getNumExchangeEntities() const

Return
The number of registered ExchangeEntity.

virtual void prepareSizes(size_t id, cudaStream_t stream) = 0

Compute the sizes of the data to be communicated in the given ExchangeEntity.

After this call, the

send.sizes, send.sizeBytes, send.offsets and send.offsetsBytes of the ExchangeEntity are available on the CPU.
Parameters
  • id: The index of the concerned ExchangeEntity
  • stream: Execution stream

virtual void prepareData(size_t id, cudaStream_t stream) = 0

Pack the data managed by the given ExchangeEntity.

Note
Must be executed after prepareSizes()
Parameters
  • id: The index of the concerned ExchangeEntity
  • stream: Execution stream

virtual void combineAndUploadData(size_t id, cudaStream_t stream) = 0

Unpack the received data.

After this call, the

recv.sizes, recv.sizeBytes, recv.offsets and recv.offsetsBytes of the ExchangeEntity must be available on the CPU and GPU before this call. Furthermore, the recv buffers must already be on the device memory.
Parameters
  • id: The index of the concerned ExchangeEntity
  • stream: Execution stream

Note
Must be executed after prepareData()

virtual bool needExchange(size_t id) = 0

Stats if the data of an ExchangeEntity needs to be exchanged.

If the

ParticleVector didn’t change since the last exchange, there is no need to run the exchange again. This function controls such behaviour.
Return
true if exchange is required, false otherwise
Parameters

Derived classes

class ParticleRedistributor : public mirheo::Exchanger

Pack and unpack data for particle redistribution.

The redistribution consists in moving (not copying) the particles from one rank to the other. It affects all particles that have left the current subdomain. The redistribution is accelerated by making use of the cell-lists of the ParticleVector. This allows to check only the particles that are on the boundary cells; However, this assumes that only those particles leave the domain.

Public Functions

ParticleRedistributor()

default constructor

void attach(ParticleVector *pv, CellList *cl)

Add a ParticleVector to the redistribution.

Multiple

ParticleVector objects can be attached to the same redistribution object.
Parameters
  • pv: The ParticleVector to attach
  • cl: The associated cell-list of pv.

class ObjectRedistributor : public mirheo::Exchanger

Pack and unpack data for object redistribution.

As opposed to particles, objects must be redistributed as a whole for two reasons:

  • the particles of one object must stay into a contiguous chunk in memory
  • the objects might have associated data per object (or per bisegments for rods)

The redistribution consists in moving (not copying) the object data from one rank to the other. It affects all objects that have left the current subdomain (an object belongs to a subdomain if its center of mass is inside).

Public Functions

ObjectRedistributor()

default constructor

void attach(ObjectVector *ov)

Add an ObjectVector to the redistribution.

Multiple

ObjectVector objects can be attached to the same redistribution object.
Parameters

class ParticleHaloExchanger : public mirheo::Exchanger

Pack and unpack data for halo particles exchange.

The halo exchange consists in copying an image of all particles that are within one cut-off radius away to the neighbouring ranks. This leaves the original ParticleVector local data untouched. The result of this operation is stored in the halo LocalParticleVector.

The halo exchange is accelerated by making use of the associated CellList of the ParticleVector.

Public Functions

ParticleHaloExchanger()

default constructor

void attach(ParticleVector *pv, CellList *cl, const std::vector<std::string> &extraChannelNames)

Add a ParticleVector for halo exchange.

Multiple

ParticleVector objects can be attached to the same halo exchanger.
Parameters
  • pv: The ParticleVector to attach
  • cl: The associated cell-list of pv
  • extraChannelNames: The list of channels to exchange (additionally to the default positions and velocities)

class ObjectHaloExchanger : public mirheo::Exchanger

Pack and unpack data for halo object exchange.

The halo exchange consists in copying an image of all objects with bounding box that is within one cut-off radius away to the neighbouring ranks. This leaves the original ObjectVector local data untouched. The result of this operation is stored in the halo LocalObjectVector.

This is needed only when the full object is needed on the neighbour ranks (e.g. Bouncer or ObjectBelongingChecker).

Public Functions

ObjectHaloExchanger()

default constructor

void attach(ObjectVector *ov, real rc, const std::vector<std::string> &extraChannelNames)

Add a ObjectVector for halo exchange.

Multiple

ObjectVector objects can be attached to the same halo exchanger.
Parameters
  • ov: The ObjectVector to attach
  • rc: The required cut-off radius
  • extraChannelNames: The list of channels to exchange (additionally to the default positions and velocities)

PinnedBuffer<int> &getSendOffsets(size_t id)

Return
send offset within the send buffer (in number of elements) of the given ov

PinnedBuffer<int> &getRecvOffsets(size_t id)

Return
recv offset within the send buffer (in number of elements) of the given ov

DeviceBuffer<MapEntry> &getMap(size_t id)

Return
The map from LocalObjectVector to send buffer ids

class ObjectExtraExchanger : public mirheo::Exchanger

Pack and unpack extra data for halo object exchange.

This class only exchanges the additional data (not e.g. the default particle’s positions and velocities). It uses the packing map from an external

ObjectHaloExchanger. The attached ObjectVector objects must be the same as the ones in the external ObjectHaloExchanger (and in the same order).
See
ObjectHaloExchanger

Public Functions

ObjectExtraExchanger(ObjectHaloExchanger *entangledHaloExchanger)

Construct a ObjectExtraExchanger.

Parameters
  • entangledHaloExchanger: The object that will contain the packing maps.

void attach(ObjectVector *ov, const std::vector<std::string> &extraChannelNames)

Add a ObjectVector for halo exchange.

Parameters
  • ov: The ObjectVector to attach
  • extraChannelNames: The list of channels to exchange

class ObjectReverseExchanger : public mirheo::Exchanger

Pack and unpack data from ghost particles back to the original bulk data.

The ghost particles data must come from a ObjectHaloExchanger object. The attached ObjectVector objects must be the same as the ones in the external ObjectHaloExchanger (and in the same order).

Public Functions

ObjectReverseExchanger(ObjectHaloExchanger *entangledHaloExchanger)

Construct a ObjectReverseExchanger.

Parameters
  • entangledHaloExchanger: The object that will create the ghost particles.

void attach(ObjectVector *ov, std::vector<std::string> channelNames)

Add an ObjectVector for reverse halo exchange.

Parameters
  • ov: The ObjectVector to attach
  • channelNames: The list of channels to send back

Exchange Entity

Helper classes responsible to hold the buffers of the packed data to be communicated.

struct BufferOffsetsSizesWrap

A device-compatible structure that holds buffer information for packing / unpacking data.

In general, there is one buffer per source/destination rank. The current implementation uses a single array that contains all buffers in a contiguous way. The offsetsBytes values (one per buffer) state where each buffer start within the array.

Public Functions

char *getBuffer(int bufId)

Return
buffer with id bufId

Public Members

int nBuffers

number of buffers

char *buffer

device data pointer to the array containing all buffers

int *offsets

device array of size nBuffers+1 with i-th buffer start index (in number of elements)

int *sizes

device array of size nBuffers with i-th buffer size (in number of elements)

size_t *offsetsBytes

device array of size nBuffers+1 with i-th buffer start index (in number of bytes)

struct BufferInfos

Structure held on the host only that contains pack/unpack buffers and their sizes/offsets (see BufferOffsetsSizesWrap).

Public Functions

void clearAllSizes(cudaStream_t stream)

set sizes and sizesBytes to zero on host and device

void resizeInfos(int nBuffers)

resize the size and offset buffers to support a given number of buffers

void uploadInfosToDevice(cudaStream_t stream)

upload all size and offset information from host to device

char *getBufferDevPtr(int bufId)

Return
The device pointer to the buffer with the given index

Public Members

PinnedBuffer<int> sizes

number of elements in each buffer

PinnedBuffer<int> offsets

prefix sum of the above

PinnedBuffer<size_t> sizesBytes

number of bytes per buffer

PinnedBuffer<size_t> offsetsBytes

start of each buffer (in bytes) within the array

PinnedBuffer<char> buffer

all buffers in contiguous memory.

std::vector<MPI_Request> requests

send or recv requests associated to each buffer; only relevant for MPIExchangeEngine

class ExchangeEntity

Manages communication data per ParticleVector.

Each ExchangeEntity holds send and recv BufferInfos object for a given ParticleVector.

Public Functions

ExchangeEntity(std::string name, int uniqueId, ParticlePacker *packer)

Construct an ExchangeEntity object.

Parameters
  • name: The name of the Corresponding ParticleVector
  • uniqueId: A positive integer. This must be unique when a collection of ExchangeEntity objects is registered in a single Exchanger.
  • packer: The class used to pack/unpack the data into buffers

void computeRecvOffsets()

Compute the recv offsets and offsetsBytes on the host.

Note
the recv sizes must be available on the host.

void computeSendOffsets()

Compute the send offsets and offsetsBytes on the host.

Note
the send sizes must be available on the host.

void computeSendOffsets_Dev2Dev(cudaStream_t stream)

Compute the send offsets and offsetBytes on the device and download all sizes and offsets on the host.

Note
The send sizes must be available on the device

void resizeSendBuf()

resize the internal send buffers; requires send offsetsBytes to be available on the host

void resizeRecvBuf()

resize the internal recv buffers; requires recv offsetsBytes to be available on the host

int getUniqueId() const

Return
the unique id

BufferOffsetsSizesWrap wrapSendData()

Return
a BufferOffsetsSizesWrap from the send BufferInfos

BufferOffsetsSizesWrap wrapRecvData()

Return
a BufferOffsetsSizesWrap from the recv BufferInfos

const std::string &getName() const

Return
the name of the attached ParticleVector

const char *getCName() const

Return
the name of the attached ParticleVector in c-style string

Public Members

const int nBuffers = fragment_mapping::numFragments

equal to number of neighbours + 1 (for bulk)

const int bulkId = fragment_mapping::bulkId

The index of the bulk buffer.

BufferInfos send

buffers for the send data

BufferInfos recv

buffers for the recv data

std::vector<int> recvRequestIdxs

only relevant for MPIExchangeEngine

Communication engines

Interface

class ExchangeEngine

Base communication engine class.

Responsible to communicate the data managed by an Exchanger between different subdomains. The communication is split into two parts so that asynchronous communication can be used. Every init() call must have a single finalize() call that follows.

Subclassed by mirheo::MPIExchangeEngine, mirheo::SingleNodeExchangeEngine

Public Functions

ExchangeEngine(std::unique_ptr<Exchanger> &&exchanger)

Construct a communication engine.

Parameters
  • exchanger: The Exchanger object that will prepare the data to communicate. The ownership of exchanger is transfered to the engine.

virtual void init(cudaStream_t stream) = 0

Initialize the communication.

The data packing from the exchanger happens in this step.

Parameters
  • stream: Execution stream used to prepare / download the data

virtual void finalize(cudaStream_t stream) = 0

Finalize the communication.

Must follow a pending

init() call. The data unpacking from the exchanger happens in this step.
Parameters
  • stream: Execution stream used to upload / unpack the data

Derived classes

class MPIExchangeEngine : public mirheo::ExchangeEngine

Engine implementing asynchronous MPI communication.

The pipeline is as follows:

  • init() prepares the data into buffers, exchange the sizes, allocate recv buffers and post the asynchronous communication calls.
  • finalize() waits for the communication to finish and unpacks the data.

Public Functions

MPIExchangeEngine(std::unique_ptr<Exchanger> &&exchanger, MPI_Comm comm, bool gpuAwareMPI)

Construct a MPIExchangeEngine.

Parameters
  • exchanger: The class responsible to pack and unpack the data.
  • comm: The cartesian communicator that represents the simulation domain.
  • gpuAwareMPI: true to enable RDMA implementation. Only works if the MPI library has this feature implemented.

void init(cudaStream_t stream)

Initialize the communication.

The data packing from the exchanger happens in this step.

Parameters
  • stream: Execution stream used to prepare / download the data

void finalize(cudaStream_t stream)

Finalize the communication.

Must follow a pending

init() call. The data unpacking from the exchanger happens in this step.
Parameters
  • stream: Execution stream used to upload / unpack the data

class SingleNodeExchangeEngine : public mirheo::ExchangeEngine

Special engine optimized for single node simulations.

Instead of communicating thedata through MPI, the send and recv buffers are simply swapped.

Public Functions

SingleNodeExchangeEngine(std::unique_ptr<Exchanger> &&exchanger)

Construct a SingleNodeExchangeEngine.

Parameters
  • exchanger: The class responsible to pack and unpack the data.

void init(cudaStream_t stream)

Initialize the communication.

The data packing from the exchanger happens in this step.

Parameters
  • stream: Execution stream used to prepare / download the data

void finalize(cudaStream_t stream)

Finalize the communication.

Must follow a pending

init() call. The data unpacking from the exchanger happens in this step.
Parameters
  • stream: Execution stream used to upload / unpack the data