diff --git a/docs/2026.html b/docs/2026.html
index 8bf59e6abf..940986a59c 100644
--- a/docs/2026.html
+++ b/docs/2026.html
@@ -76,6 +76,7 @@
New features
C++ wrapper Simd::SynetAdd16b.
C++ wrapper Simd::SynetQuantizedAdd.
C++ wrapper Simd::SynetQuantizedMul.
+ C++ wrapper Simd::SynetGatherElements.
Improving
@@ -146,6 +147,7 @@ Improving
- Added description of class Simd::SynetAdd16b.
- Added description of class Simd::SynetQuantizedMul.
+ - Added description of class Simd::SynetGatherElements.
- Improved description of function Simd::BackgroundGrowRangeSlow.
- Improved description of function Simd::BackgroundGrowRangeFast.
- Improved description of function Simd::BackgroundIncrementCount.
diff --git a/prj/txt/DoxygenGroups.txt b/prj/txt/DoxygenGroups.txt
index cdca1043cd..1bdec23c9f 100644
--- a/prj/txt/DoxygenGroups.txt
+++ b/prj/txt/DoxygenGroups.txt
@@ -93,7 +93,7 @@
/*! @ingroup cpp_types
@defgroup cpp_synet Synet Wrappers
- \short Simd::SynetAdd16b, Simd::SynetQuantizedAdd and Simd::SynetQuantizedMul classes (C++ wrappers of Synet operations).
+ \short Simd::SynetAdd16b, Simd::SynetQuantizedAdd, Simd::SynetQuantizedMul and Simd::SynetGatherElements classes (C++ wrappers of Synet operations).
*/
/*! @ingroup cpp_types
diff --git a/src/Simd/SimdLib.h b/src/Simd/SimdLib.h
index 5a2fcfcac0..c23e00f7f9 100644
--- a/src/Simd/SimdLib.h
+++ b/src/Simd/SimdLib.h
@@ -8482,6 +8482,8 @@ extern "C"
If \a indexConst is SimdTrue, constant indexes can be analyzed by ::SimdSynetGatherElementsSetIndex to avoid
repeated negative-index checks and to reduce repeated outer index processing when possible.
+ \note This function has a C++ wrapper: Simd::SynetGatherElements.
+
\param [in] dataType - a type of input and output tensor. It can be FP32, BF16 or UINT8.
\param [in] indexType - a type of index tensor. It can be INT32 or INT64.
\param [in] indexConst - a flag indicating that index tensor is constant and can be set once.
@@ -8507,6 +8509,8 @@ extern "C"
whether negative-index correction is needed and may collapse repeated outer batches of identical indexes.
The current implementation still expects the index pointer to be passed to ::SimdSynetGatherElementsForward.
+ \note This function has a C++ wrapper: Simd::SynetGatherElements.
+
\param [in] context - a pointer to gather elements context. It must be created by function ::SimdSynetGatherElementsInit and released by function ::SimdRelease.
\param [in] idx - a pointer to INT32 or INT64 index tensor. Its shape is outer[0] * ... * outer[outerSize - 1] * idxCount * inner.
*/
@@ -8520,6 +8524,8 @@ extern "C"
The returned value reports implementation-specific buffers used by the context.
+ \note This function has a C++ wrapper: Simd::SynetGatherElements.
+
\param [in] context - a pointer to gather elements context. It must be created by function ::SimdSynetGatherElementsInit and released by function ::SimdRelease.
\return size of internal buffer in bytes used inside gather elements algorithm.
*/
@@ -8535,6 +8541,8 @@ extern "C"
the context can use the analysis results, but \a idx must still point to the index tensor in the current
implementation. Negative indexes are interpreted relative to \a srcCount.
+ \note This function has a C++ wrapper: Simd::SynetGatherElements.
+
\param [in] context - a pointer to gather elements context. It must be created by function ::SimdSynetGatherElementsInit and released by function ::SimdRelease.
\param [in] src - a pointer to input tensor. Its shape is outer[0] * ... * outer[outerSize - 1] * srcCount * inner.
\param [in] idx - a pointer to INT32 or INT64 index tensor. Its shape is outer[0] * ... * outer[outerSize - 1] * idxCount * inner.
diff --git a/src/Simd/SimdSynet.hpp b/src/Simd/SimdSynet.hpp
index daa1d6aa7f..df0d29aeeb 100644
--- a/src/Simd/SimdSynet.hpp
+++ b/src/Simd/SimdSynet.hpp
@@ -457,6 +457,195 @@ namespace Simd
void * _context;
Shape _aShape, _bShape;
};
+
+ //-------------------------------------------------------------------------------------------------
+
+ /*! @ingroup cpp_synet
+
+ \short The SynetGatherElements class is a C++ wrapper of ONNX-style GatherElements.
+
+ The class wraps C API functions ::SimdSynetGatherElementsInit, ::SimdSynetGatherElementsSetIndex,
+ ::SimdSynetGatherElementsInternalBufferSize and ::SimdSynetGatherElementsForward.
+ It gathers elements from an input tensor along one dimension according to an index tensor.
+ It supports FP32, BF16 and UINT8 data tensors and INT32 or INT64 index tensors. The input tensor shape is:
+ \verbatim
+ outer[0] * ... * outer[outer.size() - 1] * srcCount * inner
+ \endverbatim
+ The index and output tensor shape is:
+ \verbatim
+ outer[0] * ... * outer[outer.size() - 1] * idxCount * inner
+ \endverbatim
+
+ Algorithm's details:
+ \verbatim
+ for(b = 0; b < outer[0]*...*outer[outer.size() - 1]; ++b)
+ for(c = 0; c < idxCount; ++c)
+ for(i = 0; i < inner; ++i)
+ {
+ ic = idx[b, c, i];
+ if (ic < 0)
+ ic += srcCount;
+ dst[b, c, i] = src[b, ic, i];
+ }
+ \endverbatim
+
+ If \a indexConst is ::SimdTrue, constant indexes can be analyzed by SetIndex() to avoid
+ repeated negative-index checks and to reduce repeated outer index processing when possible.
+ Call Init() before Forward(). Use Enable() to check that a context was created.
+ The context is released by Clear() or by the destructor.
+
+ Using example:
+ \verbatim
+ #include "Simd/SimdSynet.hpp"
+
+ int main()
+ {
+ const size_t srcCount = 4, inner = 1, idxCount = 3;
+ std::vector src(8), dst(6);
+ std::vector idx(6);
+ for (size_t i = 0; i < src.size(); ++i)
+ src[i] = float(i);
+ idx[0] = 0; idx[1] = 2; idx[2] = 1;
+ idx[3] = 3; idx[4] = 1; idx[5] = 0;
+ Simd::Shape outer = Simd::Shape({ 2 });
+
+ Simd::SynetGatherElements gather;
+ gather.Init(SimdTensorData32f, SimdTensorData32i, SimdFalse, 1, outer, srcCount, inner, idxCount);
+ if (gather.Enable())
+ gather.Forward((const uint8_t*)src.data(), (const uint8_t*)idx.data(), (uint8_t*)dst.data());
+
+ return 0;
+ }
+ \endverbatim
+ */
+ class SynetGatherElements
+ {
+ public:
+ /*!
+ Creates a new empty SynetGatherElements class.
+ */
+ SynetGatherElements()
+ : _context(NULL)
+ , _srcCount(0)
+ , _inner(0)
+ , _idxCount(0)
+ {
+ }
+
+ /*!
+ SynetGatherElements class destructor. Releases internal context.
+ */
+ virtual ~SynetGatherElements()
+ {
+ Clear();
+ }
+
+ /*!
+ Initializes (or re-initializes) a gather-elements context.
+
+ Creates an internal context with using of function ::SimdSynetGatherElementsInit.
+ The context is recreated only if outer shape, srcCount, inner or idxCount were changed.
+
+ \note This function is a C++ wrapper for function ::SimdSynetGatherElementsInit.
+
+ \param [in] dataType - a type of input and output tensor. It can be ::SimdTensorData32f, ::SimdTensorData16b or ::SimdTensorData8u.
+ \param [in] indexType - a type of index tensor. It can be ::SimdTensorData32i or ::SimdTensorData64i.
+ \param [in] indexConst - a flag indicating that index tensor is constant and can be set once.
+ \param [in] indexUsers - a number of consumers sharing the same constant index tensor.
+ \param [in] outer - outer shape dimensions before the gathered dimension.
+ \param [in] srcCount - a length of the gathered dimension in the input tensor.
+ \param [in] inner - a product of dimensions after the gathered dimension.
+ \param [in] idxCount - a length of the gathered dimension in the index and output tensors.
+ */
+ SIMD_INLINE void Init(SimdTensorDataType dataType, SimdTensorDataType indexType, SimdBool indexConst, size_t indexUsers,
+ const Shape & outer, size_t srcCount, size_t inner, size_t idxCount)
+ {
+ if (_outer != outer || _srcCount != srcCount || _inner != inner || _idxCount != idxCount)
+ {
+ Clear();
+ _outer = outer;
+ _srcCount = srcCount;
+ _inner = inner;
+ _idxCount = idxCount;
+ _context = SimdSynetGatherElementsInit(dataType, indexType, indexConst, indexUsers,
+ _outer.data(), _outer.size(), _srcCount, _inner, _idxCount);
+ }
+ }
+
+ /*!
+ Checks that the internal gather-elements context was created.
+
+ \return true if the context exists and Forward() can be called.
+ */
+ SIMD_INLINE bool Enable() const
+ {
+ return _context != NULL;
+ }
+
+ /*!
+ Gets the size in bytes of internal storage used by the gather-elements context.
+
+ \note This function is a C++ wrapper for function ::SimdSynetGatherElementsInternalBufferSize.
+
+ \return size of internal buffer in bytes used inside gather elements algorithm.
+ */
+ SIMD_INLINE size_t InternalBufferSize() const
+ {
+ return _context ? SimdSynetGatherElementsInternalBufferSize(_context) : 0;
+ }
+
+ /*!
+ Sets and analyzes constant gather-elements indexes.
+
+ The function has an effect only when the context was created with \a indexConst equal to ::SimdTrue.
+
+ \note This function is a C++ wrapper for function ::SimdSynetGatherElementsSetIndex.
+
+ \param [in] idx - a pointer to INT32 or INT64 index tensor.
+ */
+ SIMD_INLINE void SetIndex(const uint8_t * idx)
+ {
+ if (_context)
+ SimdSynetGatherElementsSetIndex(_context, idx);
+ }
+
+ /*!
+ Performs gather-elements forward propagation.
+
+ The function gathers elements from \a src according to \a idx. If SetIndex() was called,
+ the context can use the analysis results, but \a idx must still point to the index tensor
+ in the current implementation. Negative indexes are interpreted relative to srcCount.
+
+ \note This function is a C++ wrapper for function ::SimdSynetGatherElementsForward.
+
+ \param [in] src - a pointer to input tensor.
+ \param [in] idx - a pointer to INT32 or INT64 index tensor.
+ \param [out] dst - a pointer to output tensor.
+ */
+ SIMD_INLINE void Forward(const uint8_t * src, const uint8_t * idx, uint8_t * dst)
+ {
+ if (_context)
+ SimdSynetGatherElementsForward(_context, src, idx, dst);
+ }
+
+ /*!
+ Releases internal context and clears stored tensor parameters.
+ */
+ SIMD_INLINE void Clear()
+ {
+ if (_context)
+ SimdRelease(_context), _context = NULL;
+ _outer.clear();
+ _srcCount = 0;
+ _inner = 0;
+ _idxCount = 0;
+ }
+
+ private:
+ void * _context;
+ Shape _outer;
+ size_t _srcCount, _inner, _idxCount;
+ };
}
#endif
diff --git a/src/Test/TestCheckCpp.cpp b/src/Test/TestCheckCpp.cpp
index 7731a4942e..0c3f24f9fc 100644
--- a/src/Test/TestCheckCpp.cpp
+++ b/src/Test/TestCheckCpp.cpp
@@ -287,6 +287,43 @@ namespace Test
std::cout << "TestSynetQuantizedMul is failed at " << i << " : " << (int)dst1[i] << " != " << (int)dst2[i] << std::endl;
}
}
+
+ static void TestSynetGatherElements()
+ {
+ const size_t srcCount = 4, inner = 1, idxCount = 3;
+ Simd::Shape outer = Simd::Shape({ 2 });
+ std::vector src(outer[0] * srcCount * inner, 0.0f), dst1(outer[0] * idxCount * inner, 0.0f), dst2(outer[0] * idxCount * inner, 0.0f);
+ std::vector idx(outer[0] * idxCount * inner, 0);
+ for (size_t i = 0; i < src.size(); ++i)
+ src[i] = float(i);
+ idx[0] = 0; idx[1] = 2; idx[2] = 1;
+ idx[3] = 3; idx[4] = 1; idx[5] = 0;
+
+ Simd::SynetGatherElements gather;
+ gather.Init(SimdTensorData32f, SimdTensorData32i, SimdTrue, 1, outer, srcCount, inner, idxCount);
+ if (gather.Enable())
+ {
+ gather.SetIndex((const uint8_t*)idx.data());
+ gather.Forward((const uint8_t*)src.data(), (const uint8_t*)idx.data(), (uint8_t*)dst1.data());
+ }
+
+ void* context = SimdSynetGatherElementsInit(SimdTensorData32f, SimdTensorData32i, SimdTrue, 1,
+ outer.data(), outer.size(), srcCount, inner, idxCount);
+ if (context)
+ {
+ SimdSynetGatherElementsSetIndex(context, (const uint8_t*)idx.data());
+ SimdSynetGatherElementsForward(context, (const uint8_t*)src.data(), (const uint8_t*)idx.data(), (uint8_t*)dst2.data());
+ if (gather.InternalBufferSize() != SimdSynetGatherElementsInternalBufferSize(context))
+ std::cout << "TestSynetGatherElements is failed : InternalBufferSize mismatch" << std::endl;
+ SimdRelease(context);
+ }
+
+ for (size_t i = 0; i < dst1.size(); ++i)
+ {
+ if (dst1[i] != dst2[i])
+ std::cout << "TestSynetGatherElements is failed at " << i << " : " << dst1[i] << " != " << dst2[i] << std::endl;
+ }
+ }
#endif
void CheckCpp()
@@ -323,6 +360,7 @@ namespace Test
TestSynetAdd16b();
TestSynetQuantizedAdd();
TestSynetQuantizedMul();
+ TestSynetGatherElements();
#endif
}
}