diff --git a/include/aidge/scheduler/MemoryManager.hpp b/include/aidge/scheduler/MemoryManager.hpp index 94add56e8afdebb8e42f7ae49a32da2aeed9e9cb..2e397d1dbaa1cc8d8f586d15363cbd2245963152 100644 --- a/include/aidge/scheduler/MemoryManager.hpp +++ b/include/aidge/scheduler/MemoryManager.hpp @@ -19,6 +19,25 @@ #include "aidge/graph/Node.hpp" namespace Aidge { +/** + * @brief The MemoryManager can be used to generate an optimized static memory + * layout for a computing graph in a global memory space. + * The are some assumptions: + * - A MemoryManager represents a single global memory space, filled with + * contiguous, non-overlapping MemorySpace chunks. + * - A MemorySpace contains one or multiple MemoryPlane, each MemoryPlane + * corresponding to the allocation of a specific Tensor. When a Tensor can re- + * use the memory of the preceding one (for in-place or partially in-place + * operators), multiple overlapping MemoryPlane can be created in the same + * MemorySpace (remember, MemorySpace **cannot** be overlapping!). + * - A MemoryPlane is tailored for handling (N)HWC data with two properties: + * - Possibility of wrapping: on the H axis (each W*C block is contiguous). + * - Possibility of concatenation: on the C axis (C1+C2+...+Cn). + * - All the sizes and offets specified in a MemoryManager are expressed in + * number of data elements, or **words**, meaning currently a uniform data + * precision is expected in a MemoryManager (for instance, if the precision is + * 16-bits, each data element will be 2 bytes, which will be the size of a word). + */ class MemoryManager { public: typedef int Clock_T; @@ -45,18 +64,45 @@ public: allocated(clock_), released(-1) {} + /// Offset of the MemorySpace in the MemoryManager global memory space (in words) unsigned int offset; + /// Size of the MemorySpace (in words) unsigned int size; std::set<std::shared_ptr<Node> > dependencies; Clock_T allocated; Clock_T released; }; - // MemoryPlane belongs to a MemorySpace. Any number of potentially - // overlapping planes can be associated to a MemorySpace. - // MemoryPlane can be non-contiguous (in case of stride, or wrapping, when - // offset + size > memSpace.size). - // MemoryPlane cannot be re-arranged inside a MemorySpace. + /** + * @brief MemoryPlane belongs to a MemorySpace. Any number of potentiall + * overlapping planes can be associated to a MemorySpace. + * MemoryPlane can be non-contiguous (in case of stride, or wrapping, when + * offset + size > memSpace.size). + * MemoryPlane cannot be re-arranged inside a MemorySpace. + * + * A MemoryPlane is tailored for handling (N)HWC data with two properties: + * - Possibility of wrapping: on the H axis (each W*C block is contiguous). + * - Possibility of concatenation: on the C axis (C1+C2+...+Cn). + * + * Detail of (N)HWC data handling: + * - \p length is the size of contiguous and non-breakable memory line (W in HWC); + * - \p count is the number of memory lines of size \p length constituting a memory block (H in HWC); + * - \p stride is the number of channels, or memory blocks, *in total*, + * of \p count lines of size \p length (C in NHWC); + * - \p size is the number of channels, or memory blocks, *in this MemoryPlane*, + * of \p count lines of size \p length. + * In the case of concatenation, there can be multiple overlapping MemoryPlane + * with different size, like NHWC = NHW(C1+C2): + * - MemoryPlane#1: \p size = C1 and \p stride = C=C1+C2 + * - MemoryPlane#2: \p size = C2 and \p stride = C=C1+C2 + * (with an additionnal relative offset of +C1) + * In this mode, wrapping can only occur on the H (\p count) axis. W*C chunks + * are garanteed to be contiguous (\p length * \p stride). + * + * By default, \p stride = \p size, \p count = 1 and \p length = 1, meaning + * there is no NHWC layout and the MemoryPlane can be wrapped **anywhere**. + * In this case, \p size is the total size of the MemoryPlane (H*W*C, in words). + */ struct MemoryPlane { MemoryPlane(std::shared_ptr<MemorySpace> memSpace_, Clock_T clock_, @@ -92,36 +138,91 @@ public: <= memSpace->offset + memSpace->size); } + /** + * @brief Get the total size of the MemoryPlane, including the stride. + * + * @return unsigned int Total size in words + */ inline unsigned int getSize() const { return stride * length * count; } + /** + * @brief Get the useful size of the MemoryPlane, as if its memory blocks + * were contiguous, without stride. + * + * @return unsigned int Useful size in words + */ inline unsigned int getUsefulSize() const { return size * length * count; } + /** + * @brief Get the absolute offset of the beginning of the memory plane. + * + * @return unsigned int Contiguous offset in words + */ inline unsigned int getContiguousOffset() const { return memSpace->offset + offset; } + /** + * @brief Get the size of the contiguous part of the memory plane, from + * its beginning to the limit of the MemorySpace size. + * If the MemoryPlane fill the MemorySpace without wrapping, the contiguous + * size will be the same as the total size of the MemoryPlane. + * + * @return unsigned int Contiguous size in words + */ inline unsigned int getContiguousSize() const { return std::min(getSize(), getLimit()); } + /** + * @brief Get the absolute offset of the wrapped part of the memory plane. + * Since the wrapped part of the memory plane begins at the beginning of + * the MemorySpace, the returned offset is always the same as the MemorySpace + * offset. + * + * @return unsigned int Wrapped offset in words + */ inline unsigned int getWrappedOffset() const { return memSpace->offset; } + /** + * @brief Get the size of the wrapped part of the memory plane, from + * the beginning of the MemorySpace to the total size of the MemoryPlane, + * including the stride. + * If the MemoryPlane fill the MemorySpace without wrapping, the wrapped + * size will 0. + * + * @return unsigned int Wrapped size in words + */ inline unsigned int getWrappedSize() const { return getSize() - getContiguousSize(); } + /** + * @brief Get the absolute offset after the end of the memory plane (if it + * is wrapped, the offset will correspond to the end of the wrapped part). + * The word at the final offset is not included in the MemoryPlane. + * + * @return unsigned int Final offset in words + */ inline unsigned int getFinalOffset() const { return (getWrappedSize() > 0) ? getWrappedOffset() + getWrappedSize() : getContiguousOffset() + getContiguousSize(); } + /** + * @brief Get the absolute offset after the end of the contiguous part + * of the memory plane. + * The word at the upper offset is not included in the MemoryPlane. + * + * @return unsigned int Upper offset in words + */ inline unsigned int getUpperOffset() const { return (getContiguousOffset() + getContiguousSize()); } @@ -146,10 +247,29 @@ public: std::shared_ptr<MemorySpace> memSpace; Clock_T allocated; + /// Relative offset of the MemoryPlane in the MemorySpace (in words) unsigned int offset; + /// Number of channels, or memory blocks, *in this MemoryPlane*, + /// of \p count lines of size \p length. + /// In the case of concatenation, there can be multiple overlapping MemoryPlane + /// with different size, like NHWC = NHW(C1+C2): + /// - MemoryPlane#1: \p size = C1 and \p stride = C=C1+C2 + /// - MemoryPlane#2: \p size = C2 and \p stride = C=C1+C2 + /// (with an additionnal relative offset of +C1) + /// By default, \p stride = \p size, \p count = 1 and \p length = 1, meaning + /// there is no NHWC layout and the MemoryPlane can be wrapped **anywhere**. + /// In this case, \p size is the total size of the MemoryPlane (H*W*C, in words). unsigned int size; + /// Number of channels, or memory blocks *in total*, + /// of \p count lines of size \p length (the C in NHWC). + /// There should be C blocks of H*W size. unsigned int stride; + /// Size of an elementary, contiguous and non-breakable, memory line + /// (the W in NHWC), in words. A MemoryPlane wrapping cannot occur in + /// the middle of a memory line. unsigned int length; + /// Number of memory lines of size \p length constituting a memory block + /// (the H in NHWC). The size of a memory block is H*W. unsigned int count; };