| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com> | |
| // | |
| // 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/. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #ifndef EIGEN_TENSOR_TENSOR_CONCATENATION_H | |
| #define EIGEN_TENSOR_TENSOR_CONCATENATION_H | |
| // IWYU pragma: private | |
| #include "./InternalHeaderCheck.h" | |
| namespace Eigen { | |
| namespace internal { | |
| template <typename Axis, typename LhsXprType, typename RhsXprType> | |
| struct traits<TensorConcatenationOp<Axis, LhsXprType, RhsXprType>> { | |
| // Type promotion to handle the case where the types of the lhs and the rhs are different. | |
| typedef typename promote_storage_type<typename LhsXprType::Scalar, typename RhsXprType::Scalar>::ret Scalar; | |
| typedef typename promote_storage_type<typename traits<LhsXprType>::StorageKind, | |
| typename traits<RhsXprType>::StorageKind>::ret StorageKind; | |
| typedef | |
| typename promote_index_type<typename traits<LhsXprType>::Index, typename traits<RhsXprType>::Index>::type Index; | |
| static constexpr int NumDimensions = traits<LhsXprType>::NumDimensions; | |
| static constexpr int Layout = traits<LhsXprType>::Layout; | |
| enum { Flags = 0 }; | |
| typedef std::conditional_t<Pointer_type_promotion<typename LhsXprType::Scalar, Scalar>::val, | |
| typename traits<LhsXprType>::PointerType, typename traits<RhsXprType>::PointerType> | |
| PointerType; | |
| }; | |
| template <typename Axis, typename LhsXprType, typename RhsXprType> | |
| struct eval<TensorConcatenationOp<Axis, LhsXprType, RhsXprType>, Eigen::Dense> { | |
| typedef const TensorConcatenationOp<Axis, LhsXprType, RhsXprType>& type; | |
| }; | |
| } // end namespace internal | |
| /** | |
| * \ingroup Tensor_Module | |
| * | |
| * \brief Tensor concatenation class. | |
| */ | |
| template <typename Axis, typename LhsXprType, typename RhsXprType> | |
| class TensorConcatenationOp : public TensorBase<TensorConcatenationOp<Axis, LhsXprType, RhsXprType>, WriteAccessors> { | |
| public: | |
| typedef TensorBase<TensorConcatenationOp<Axis, LhsXprType, RhsXprType>, WriteAccessors> Base; | |
| typedef typename internal::traits<TensorConcatenationOp>::Scalar Scalar; | |
| typedef typename internal::traits<TensorConcatenationOp>::StorageKind StorageKind; | |
| typedef typename internal::traits<TensorConcatenationOp>::Index Index; | |
| typedef typename internal::ref_selector<TensorConcatenationOp>::type Nested; | |
| typedef typename internal::promote_storage_type<typename LhsXprType::CoeffReturnType, | |
| typename RhsXprType::CoeffReturnType>::ret CoeffReturnType; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE TensorConcatenationOp(const LhsXprType& lhs, const RhsXprType& rhs, Axis axis) | |
| : m_lhs_xpr(lhs), m_rhs_xpr(rhs), m_axis(axis) {} | |
| EIGEN_DEVICE_FUNC const internal::remove_all_t<typename LhsXprType::Nested>& lhsExpression() const { | |
| return m_lhs_xpr; | |
| } | |
| EIGEN_DEVICE_FUNC const internal::remove_all_t<typename RhsXprType::Nested>& rhsExpression() const { | |
| return m_rhs_xpr; | |
| } | |
| EIGEN_DEVICE_FUNC const Axis& axis() const { return m_axis; } | |
| EIGEN_INHERIT_ASSIGNMENT_OPERATORS(TensorConcatenationOp) | |
| protected: | |
| typename LhsXprType::Nested m_lhs_xpr; | |
| typename RhsXprType::Nested m_rhs_xpr; | |
| const Axis m_axis; | |
| }; | |
| // Eval as rvalue | |
| template <typename Axis, typename LeftArgType, typename RightArgType, typename Device> | |
| struct TensorEvaluator<const TensorConcatenationOp<Axis, LeftArgType, RightArgType>, Device> { | |
| typedef TensorConcatenationOp<Axis, LeftArgType, RightArgType> XprType; | |
| typedef typename XprType::Index Index; | |
| static constexpr int NumDims = internal::array_size<typename TensorEvaluator<LeftArgType, Device>::Dimensions>::value; | |
| static constexpr int RightNumDims = | |
| internal::array_size<typename TensorEvaluator<RightArgType, Device>::Dimensions>::value; | |
| typedef DSizes<Index, NumDims> Dimensions; | |
| typedef typename XprType::Scalar Scalar; | |
| typedef typename XprType::CoeffReturnType CoeffReturnType; | |
| typedef typename PacketType<CoeffReturnType, Device>::type PacketReturnType; | |
| typedef StorageMemory<CoeffReturnType, Device> Storage; | |
| typedef typename Storage::Type EvaluatorPointerType; | |
| static constexpr int Layout = TensorEvaluator<LeftArgType, Device>::Layout; | |
| enum { | |
| IsAligned = false, | |
| PacketAccess = | |
| TensorEvaluator<LeftArgType, Device>::PacketAccess && TensorEvaluator<RightArgType, Device>::PacketAccess, | |
| // block() reads each operand's data() pointer directly, so both must | |
| // expose raw storage. Scalar-changing block consumers | |
| // (TensorCwiseUnaryOp, TensorConversionOp) drop the forwarded destination | |
| // buffer before reaching us, so prepareStorage always sees either a | |
| // matching-Scalar buffer or no buffer at all. | |
| BlockAccess = TensorEvaluator<LeftArgType, Device>::RawAccess && TensorEvaluator<RightArgType, Device>::RawAccess, | |
| // Matches TensorShuffling / TensorBroadcasting / TensorPadding: bulk copy | |
| // wins over the per-element coeff/packet path (which pays div/mod per | |
| // access) at every size we benchmarked, so always prefer block. | |
| PreferBlockAccess = true, | |
| RawAccess = false | |
| }; | |
| typedef std::remove_const_t<Scalar> ScalarNoConst; | |
| //===- Tensor block evaluation strategy (see TensorBlock.h) -------------===// | |
| typedef internal::TensorBlockDescriptor<NumDims, Index> TensorBlockDesc; | |
| typedef internal::TensorBlockScratchAllocator<Device> TensorBlockScratch; | |
| typedef typename internal::TensorMaterializedBlock<ScalarNoConst, NumDims, Layout, Index> TensorBlock; | |
| //===--------------------------------------------------------------------===// | |
| EIGEN_STRONG_INLINE TensorEvaluator(const XprType& op, const Device& device) | |
| : m_leftImpl(op.lhsExpression(), device), | |
| m_rightImpl(op.rhsExpression(), device), | |
| m_device(device), | |
| m_axis(op.axis()) { | |
| EIGEN_STATIC_ASSERT((static_cast<int>(TensorEvaluator<LeftArgType, Device>::Layout) == | |
| static_cast<int>(TensorEvaluator<RightArgType, Device>::Layout) || | |
| NumDims == 1), | |
| YOU_MADE_A_PROGRAMMING_MISTAKE); | |
| // TensorConcatenationOp requires both operands to have the same static | |
| // rank. Reshape the lower-rank operand explicitly if you need to mix | |
| // ranks; see the concatenate() entry in contrib/Eigen/src/Tensor/README.md. | |
| EIGEN_STATIC_ASSERT((NumDims == RightNumDims), YOU_MADE_A_PROGRAMMING_MISTAKE); | |
| EIGEN_STATIC_ASSERT((NumDims > 0), YOU_MADE_A_PROGRAMMING_MISTAKE); | |
| eigen_assert(0 <= m_axis && m_axis < NumDims); | |
| m_leftAxisSize = m_leftImpl.dimensions()[m_axis]; | |
| const Dimensions& lhs_dims = m_leftImpl.dimensions(); | |
| const Dimensions& rhs_dims = m_rightImpl.dimensions(); | |
| { | |
| int i = 0; | |
| for (; i < m_axis; ++i) { | |
| eigen_assert(lhs_dims[i] >= 0); | |
| eigen_assert(lhs_dims[i] == rhs_dims[i]); | |
| m_dimensions[i] = lhs_dims[i]; | |
| } | |
| eigen_assert(lhs_dims[i] >= 0); // Now i == m_axis. | |
| eigen_assert(rhs_dims[i] >= 0); | |
| m_dimensions[i] = lhs_dims[i] + rhs_dims[i]; | |
| for (++i; i < NumDims; ++i) { | |
| eigen_assert(lhs_dims[i] >= 0); | |
| eigen_assert(lhs_dims[i] == rhs_dims[i]); | |
| m_dimensions[i] = lhs_dims[i]; | |
| } | |
| } | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| m_leftStrides[0] = 1; | |
| m_rightStrides[0] = 1; | |
| m_outputStrides[0] = 1; | |
| for (int j = 1; j < NumDims; ++j) { | |
| m_leftStrides[j] = m_leftStrides[j - 1] * lhs_dims[j - 1]; | |
| m_rightStrides[j] = m_rightStrides[j - 1] * rhs_dims[j - 1]; | |
| m_outputStrides[j] = m_outputStrides[j - 1] * m_dimensions[j - 1]; | |
| } | |
| } else { | |
| m_leftStrides[NumDims - 1] = 1; | |
| m_rightStrides[NumDims - 1] = 1; | |
| m_outputStrides[NumDims - 1] = 1; | |
| for (int j = NumDims - 2; j >= 0; --j) { | |
| m_leftStrides[j] = m_leftStrides[j + 1] * lhs_dims[j + 1]; | |
| m_rightStrides[j] = m_rightStrides[j + 1] * rhs_dims[j + 1]; | |
| m_outputStrides[j] = m_outputStrides[j + 1] * m_dimensions[j + 1]; | |
| } | |
| } | |
| } | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const Dimensions& dimensions() const { return m_dimensions; } | |
| // TODO(phli): Add short-circuit memcpy evaluation if underlying data are linear. | |
| EIGEN_STRONG_INLINE bool evalSubExprsIfNeeded(EvaluatorPointerType) { | |
| m_leftImpl.evalSubExprsIfNeeded(nullptr); | |
| m_rightImpl.evalSubExprsIfNeeded(nullptr); | |
| return true; | |
| } | |
| EIGEN_STRONG_INLINE void cleanup() { | |
| m_leftImpl.cleanup(); | |
| m_rightImpl.cleanup(); | |
| } | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE internal::TensorBlockResourceRequirements getResourceRequirements() const { | |
| // Target the L1 cache. A block straddling the concat axis is materialized | |
| // into a merged scratch slab that the cwise consumer reads straight back; | |
| // sizing the block to L1 keeps that round-trip out of the last-level | |
| // cache. It also splits an otherwise cache-resident output into many | |
| // blocks, so the off-axis ones qualify for the zero-copy view path in | |
| // block(). Matches TensorBroadcasting, which likewise targets L1 for its | |
| // materialized block path. | |
| const size_t target_size = m_device.firstLevelCacheSize(); | |
| return internal::TensorBlockResourceRequirements::merge( | |
| internal::TensorBlockResourceRequirements::skewed<Scalar>(target_size), | |
| internal::TensorBlockResourceRequirements::merge(m_leftImpl.getResourceRequirements(), | |
| m_rightImpl.getResourceRequirements())); | |
| } | |
| // True when a block of shape `block_dims` is a contiguous run of an operand | |
| // whose shape is `operand_dims` -- i.e. it can be addressed as a plain | |
| // pointer offset with no per-row stride walk. Mirrors the direct-access test | |
| // in TensorMaterializedBlock::materialize(). | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool isContiguousOperandSlab(const Dimensions& operand_dims, | |
| const Dimensions& block_dims) const { | |
| static constexpr bool IsColMajor = Layout == static_cast<int>(ColMajor); | |
| int matching_inner_dims = 0; | |
| for (int i = 0; i < NumDims; ++i) { | |
| const int dim = IsColMajor ? i : NumDims - i - 1; | |
| if (operand_dims[dim] != block_dims[dim]) break; | |
| ++matching_inner_dims; | |
| } | |
| // Every dimension above the single partial dimension must be of size 1. | |
| for (int i = matching_inner_dims + 1; i < NumDims; ++i) { | |
| const int dim = IsColMajor ? i : NumDims - i - 1; | |
| if (block_dims[dim] != 1) return false; | |
| } | |
| return true; | |
| } | |
| // Returns a zero-copy view when the block lies within a single operand and | |
| // is contiguous there; otherwise materializes the slab(s) with | |
| // TensorBlockIO::Copy, which collapses to a memcpy per contiguous slab. | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE TensorBlock block(TensorBlockDesc& desc, TensorBlockScratch& scratch, | |
| bool root_of_expr_ast = false) const { | |
| static constexpr bool IsColMajor = Layout == static_cast<int>(ColMajor); | |
| if (desc.size() == 0) { | |
| return TensorBlock(internal::TensorBlockKind::kView, nullptr, desc.dimensions()); | |
| } | |
| Index remaining = desc.offset(); | |
| DSizes<Index, NumDims> out_coords; | |
| EIGEN_IF_CONSTEXPR (IsColMajor) { | |
| for (int i = NumDims - 1; i > 0; --i) { | |
| out_coords[i] = remaining / m_outputStrides[i]; | |
| remaining -= out_coords[i] * m_outputStrides[i]; | |
| } | |
| out_coords[0] = remaining; | |
| } else { | |
| for (int i = 0; i < NumDims - 1; ++i) { | |
| out_coords[i] = remaining / m_outputStrides[i]; | |
| remaining -= out_coords[i] * m_outputStrides[i]; | |
| } | |
| out_coords[NumDims - 1] = remaining; | |
| } | |
| const Index axis_start = out_coords[m_axis]; | |
| const Index axis_size = desc.dimension(static_cast<int>(m_axis)); | |
| const Index axis_end = axis_start + axis_size; | |
| // Fast path: a block that lies entirely within one operand and forms a | |
| // contiguous run of its storage needs no copy at all -- return a view | |
| // straight into A / B. This is what keeps a cwise expression that reads | |
| // the concatenation (e.g. `(A.concatenate(B, axis) + C)`) streaming: a | |
| // cwise consumer drops our destination buffer before calling block() (see | |
| // TensorCwiseBinaryOp::block), so without the view the slab would be | |
| // bounced through a scratch buffer and read straight back, doubling cache | |
| // traffic. Straddling blocks still need the merged buffer materialized | |
| // below. | |
| if (axis_end <= m_leftAxisSize) { | |
| if (isContiguousOperandSlab(m_leftImpl.dimensions(), desc.dimensions())) { | |
| Index left_src_offset = 0; | |
| for (int i = 0; i < NumDims; ++i) { | |
| left_src_offset += out_coords[i] * m_leftStrides[i]; | |
| } | |
| return TensorBlock(internal::TensorBlockKind::kView, m_leftImpl.data() + left_src_offset, desc.dimensions()); | |
| } | |
| } else if (axis_start >= m_leftAxisSize) { | |
| if (isContiguousOperandSlab(m_rightImpl.dimensions(), desc.dimensions())) { | |
| Index right_src_offset = (axis_start - m_leftAxisSize) * m_rightStrides[m_axis]; | |
| for (int i = 0; i < NumDims; ++i) { | |
| if (i != m_axis) { | |
| right_src_offset += out_coords[i] * m_rightStrides[i]; | |
| } | |
| } | |
| return TensorBlock(internal::TensorBlockKind::kView, m_rightImpl.data() + right_src_offset, desc.dimensions()); | |
| } | |
| } | |
| typedef internal::TensorBlockIO<ScalarNoConst, Index, NumDims, Layout> TensorBlockIO; | |
| typedef typename TensorBlockIO::Dst TensorBlockIODst; | |
| typedef typename TensorBlockIO::Src TensorBlockIOSrc; | |
| // Strided destination buffers are safe here because we only ever write | |
| // dense slabs into them; allowing strided storage lets a root-of-AST | |
| // assignment materialize directly into the output tensor. | |
| typename TensorBlock::Storage block_storage = | |
| TensorBlock::prepareStorage(desc, scratch, /*allow_strided_storage=*/root_of_expr_ast); | |
| if (axis_start < m_leftAxisSize) { | |
| const Index left_rows_in_block = numext::mini(m_leftAxisSize, axis_end) - axis_start; | |
| DSizes<Index, NumDims> left_sub_dims = desc.dimensions(); | |
| left_sub_dims[m_axis] = left_rows_in_block; | |
| Index left_src_offset = 0; | |
| for (int i = 0; i < NumDims; ++i) { | |
| left_src_offset += out_coords[i] * m_leftStrides[i]; | |
| } | |
| typename TensorBlockIO::Dimensions left_strides(m_leftStrides); | |
| TensorBlockIOSrc src(left_strides, m_leftImpl.data(), left_src_offset); | |
| TensorBlockIODst dst(left_sub_dims, block_storage.strides(), block_storage.data(), | |
| /*dst_offset=*/0); | |
| TensorBlockIO::Copy(dst, src); | |
| } | |
| if (axis_end > m_leftAxisSize) { | |
| const Index right_rows_in_block = axis_end - numext::maxi(m_leftAxisSize, axis_start); | |
| DSizes<Index, NumDims> right_sub_dims = desc.dimensions(); | |
| right_sub_dims[m_axis] = right_rows_in_block; | |
| // Right operand has the same per-dim coords as out_coords except along | |
| // the concat axis, where it starts at max(0, axis_start - left_size). | |
| const Index right_axis_start = numext::maxi(Index(0), axis_start - m_leftAxisSize); | |
| Index right_src_offset = right_axis_start * m_rightStrides[m_axis]; | |
| for (int i = 0; i < NumDims; ++i) { | |
| if (i != m_axis) { | |
| right_src_offset += out_coords[i] * m_rightStrides[i]; | |
| } | |
| } | |
| // Offset within the materialized buffer where the right-side slab starts. | |
| const Index dst_axis_offset = numext::maxi(Index(0), m_leftAxisSize - axis_start); | |
| const Index dst_offset = dst_axis_offset * block_storage.strides()[m_axis]; | |
| typename TensorBlockIO::Dimensions right_strides(m_rightStrides); | |
| TensorBlockIOSrc src(right_strides, m_rightImpl.data(), right_src_offset); | |
| TensorBlockIODst dst(right_sub_dims, block_storage.strides(), block_storage.data(), dst_offset); | |
| TensorBlockIO::Copy(dst, src); | |
| } | |
| return block_storage.AsTensorMaterializedBlock(); | |
| } | |
| // The per-dim integer div/mod in this loop are the obvious "slow" candidates, | |
| // but the same TensorIntDivisor substitution was measured net-negative on | |
| // Intel Raptor Lake when prototyped for TensorBroadcasting (see the comment | |
| // on TensorBroadcasting::indexColMajor). Modern x86 hardware div (~20 | |
| // cycles) amortizes well and the mul+shifts don't win back the added state. | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE CoeffReturnType coeff(Index index) const { | |
| // Collect dimension-wise indices (subs). | |
| array<Index, NumDims> subs; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| for (int i = NumDims - 1; i > 0; --i) { | |
| subs[i] = index / m_outputStrides[i]; | |
| index -= subs[i] * m_outputStrides[i]; | |
| } | |
| subs[0] = index; | |
| } else { | |
| for (int i = 0; i < NumDims - 1; ++i) { | |
| subs[i] = index / m_outputStrides[i]; | |
| index -= subs[i] * m_outputStrides[i]; | |
| } | |
| subs[NumDims - 1] = index; | |
| } | |
| const Dimensions& left_dims = m_leftImpl.dimensions(); | |
| if (subs[m_axis] < left_dims[m_axis]) { | |
| Index left_index; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| left_index = subs[0]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 1; i < NumDims; ++i) { | |
| left_index += (subs[i] % left_dims[i]) * m_leftStrides[i]; | |
| } | |
| } else { | |
| left_index = subs[NumDims - 1]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = NumDims - 2; i >= 0; --i) { | |
| left_index += (subs[i] % left_dims[i]) * m_leftStrides[i]; | |
| } | |
| } | |
| return m_leftImpl.coeff(left_index); | |
| } else { | |
| subs[m_axis] -= left_dims[m_axis]; | |
| const Dimensions& right_dims = m_rightImpl.dimensions(); | |
| Index right_index; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| right_index = subs[0]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 1; i < NumDims; ++i) { | |
| right_index += (subs[i] % right_dims[i]) * m_rightStrides[i]; | |
| } | |
| } else { | |
| right_index = subs[NumDims - 1]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = NumDims - 2; i >= 0; --i) { | |
| right_index += (subs[i] % right_dims[i]) * m_rightStrides[i]; | |
| } | |
| } | |
| return m_rightImpl.coeff(right_index); | |
| } | |
| } | |
| // When the packet sits entirely on one side of the concat boundary, delegate | |
| // to that operand's packet<>() rather than assembling PacketSize coeff() | |
| // calls. The packet stays on one side iff only the innermost dim varies | |
| // across the packet -- i.e. all other subs match between the first and last | |
| // index. When that holds, subs[m_axis] is either constant (m_axis is not | |
| // innermost) or monotonic non-decreasing (m_axis is innermost), so checking | |
| // just the endpoints decides the side. Otherwise subs[m_axis] can wrap back | |
| // through the boundary mid-packet (as when the inner dim has fewer than | |
| // PacketSize elements and the packet spills past the concat axis), so fall | |
| // back to scalars. | |
| template <int LoadMode> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketReturnType packet(Index index) const { | |
| const int packetSize = PacketType<CoeffReturnType, Device>::size; | |
| EIGEN_STATIC_ASSERT((packetSize > 1), YOU_MADE_A_PROGRAMMING_MISTAKE) | |
| eigen_assert(index + packetSize - 1 < dimensions().TotalSize()); | |
| array<Index, NumDims> subs; | |
| array<Index, NumDims> subs_end; | |
| Index remaining = index; | |
| Index remaining_end = index + packetSize - 1; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| for (int i = NumDims - 1; i > 0; --i) { | |
| subs[i] = remaining / m_outputStrides[i]; | |
| remaining -= subs[i] * m_outputStrides[i]; | |
| subs_end[i] = remaining_end / m_outputStrides[i]; | |
| remaining_end -= subs_end[i] * m_outputStrides[i]; | |
| } | |
| subs[0] = remaining; | |
| subs_end[0] = remaining_end; | |
| } else { | |
| for (int i = 0; i < NumDims - 1; ++i) { | |
| subs[i] = remaining / m_outputStrides[i]; | |
| remaining -= subs[i] * m_outputStrides[i]; | |
| subs_end[i] = remaining_end / m_outputStrides[i]; | |
| remaining_end -= subs_end[i] * m_outputStrides[i]; | |
| } | |
| subs[NumDims - 1] = remaining; | |
| subs_end[NumDims - 1] = remaining_end; | |
| } | |
| const Dimensions& left_dims = m_leftImpl.dimensions(); | |
| const Index left_axis_size = left_dims[m_axis]; | |
| constexpr int innermost = (static_cast<int>(Layout) == static_cast<int>(ColMajor)) ? 0 : NumDims - 1; | |
| bool packet_in_single_inner_row = true; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 0; i < NumDims; ++i) { | |
| if (i != innermost && subs[i] != subs_end[i]) { | |
| packet_in_single_inner_row = false; | |
| } | |
| } | |
| const bool on_left = | |
| packet_in_single_inner_row && subs[m_axis] < left_axis_size && subs_end[m_axis] < left_axis_size; | |
| const bool on_right = | |
| packet_in_single_inner_row && subs[m_axis] >= left_axis_size && subs_end[m_axis] >= left_axis_size; | |
| if (on_left) { | |
| Index left_index; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| left_index = subs[0]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 1; i < NumDims; ++i) { | |
| left_index += subs[i] * m_leftStrides[i]; | |
| } | |
| } else { | |
| left_index = subs[NumDims - 1]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = NumDims - 2; i >= 0; --i) { | |
| left_index += subs[i] * m_leftStrides[i]; | |
| } | |
| } | |
| return m_leftImpl.template packet<LoadMode>(left_index); | |
| } | |
| if (on_right) { | |
| subs[m_axis] -= left_axis_size; | |
| Index right_index; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| right_index = subs[0]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 1; i < NumDims; ++i) { | |
| right_index += subs[i] * m_rightStrides[i]; | |
| } | |
| } else { | |
| right_index = subs[NumDims - 1]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = NumDims - 2; i >= 0; --i) { | |
| right_index += subs[i] * m_rightStrides[i]; | |
| } | |
| } | |
| return m_rightImpl.template packet<LoadMode>(right_index); | |
| } | |
| // The packet straddles the boundary or spans multiple inner rows: fall | |
| // back to assembling scalars. | |
| EIGEN_ALIGN_TO_BOUNDARY(internal::unpacket_traits<PacketReturnType>::alignment) CoeffReturnType values[packetSize]; | |
| EIGEN_UNROLL_LOOP | |
| for (int i = 0; i < packetSize; ++i) { | |
| values[i] = coeff(index + i); | |
| } | |
| PacketReturnType rslt = internal::pload<PacketReturnType>(values); | |
| return rslt; | |
| } | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE TensorOpCost costPerCoeff(bool vectorized) const { | |
| const double compute_cost = NumDims * (2 * TensorOpCost::AddCost<Index>() + 2 * TensorOpCost::MulCost<Index>() + | |
| TensorOpCost::DivCost<Index>() + TensorOpCost::ModCost<Index>()); | |
| const double lhs_size = m_leftImpl.dimensions().TotalSize(); | |
| const double rhs_size = m_rightImpl.dimensions().TotalSize(); | |
| return (lhs_size / (lhs_size + rhs_size)) * m_leftImpl.costPerCoeff(vectorized) + | |
| (rhs_size / (lhs_size + rhs_size)) * m_rightImpl.costPerCoeff(vectorized) + TensorOpCost(0, 0, compute_cost); | |
| } | |
| EIGEN_DEVICE_FUNC EvaluatorPointerType data() const { return nullptr; } | |
| protected: | |
| Dimensions m_dimensions; | |
| array<Index, NumDims> m_outputStrides; | |
| array<Index, NumDims> m_leftStrides; | |
| array<Index, NumDims> m_rightStrides; | |
| TensorEvaluator<LeftArgType, Device> m_leftImpl; | |
| TensorEvaluator<RightArgType, Device> m_rightImpl; | |
| const Device EIGEN_DEVICE_REF m_device; | |
| const Axis m_axis; | |
| Index m_leftAxisSize; | |
| }; | |
| // Eval as lvalue | |
| template <typename Axis, typename LeftArgType, typename RightArgType, typename Device> | |
| struct TensorEvaluator<TensorConcatenationOp<Axis, LeftArgType, RightArgType>, Device> | |
| : public TensorEvaluator<const TensorConcatenationOp<Axis, LeftArgType, RightArgType>, Device> { | |
| typedef TensorEvaluator<const TensorConcatenationOp<Axis, LeftArgType, RightArgType>, Device> Base; | |
| typedef TensorConcatenationOp<Axis, LeftArgType, RightArgType> XprType; | |
| typedef typename Base::Dimensions Dimensions; | |
| static constexpr int NumDims = Base::NumDims; | |
| static constexpr int Layout = TensorEvaluator<LeftArgType, Device>::Layout; | |
| enum { | |
| IsAligned = false, | |
| PacketAccess = | |
| TensorEvaluator<LeftArgType, Device>::PacketAccess && TensorEvaluator<RightArgType, Device>::PacketAccess, | |
| // writeBlock() splits the block at the concat axis and copies each piece | |
| // straight into the operand's buffer, so both must expose raw storage. | |
| BlockAccess = TensorEvaluator<LeftArgType, Device>::RawAccess && TensorEvaluator<RightArgType, Device>::RawAccess, | |
| // The coeff/packet write path pays a div/mod cascade plus a per-dim mod | |
| // for every scalar; the block path is a pair of bulk copies. Mirrors the | |
| // rvalue evaluator. | |
| PreferBlockAccess = true, | |
| RawAccess = false | |
| }; | |
| typedef std::remove_const_t<typename XprType::Scalar> ScalarNoConst; | |
| //===- Tensor block evaluation strategy (see TensorBlock.h) -------------===// | |
| typedef internal::TensorBlockDescriptor<NumDims, typename XprType::Index> TensorBlockDesc; | |
| //===--------------------------------------------------------------------===// | |
| // The ColMajor-only static_assert lives in coeffRef/writePacket rather than | |
| // here so that passthrough evaluators (e.g. TensorSlicingOp's) can | |
| // instantiate this type for RowMajor concat operands without ever calling | |
| // its lvalue methods. writeBlock() below is layout-generic, so tiled | |
| // assignments through a RowMajor concatenation are supported. | |
| EIGEN_STRONG_INLINE TensorEvaluator(const XprType& op, const Device& device) : Base(op, device) {} | |
| typedef typename XprType::Index Index; | |
| typedef typename XprType::Scalar Scalar; | |
| typedef typename XprType::CoeffReturnType CoeffReturnType; | |
| typedef typename PacketType<CoeffReturnType, Device>::type PacketReturnType; | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE CoeffReturnType& coeffRef(Index index) const { | |
| EIGEN_STATIC_ASSERT((static_cast<int>(Layout) == static_cast<int>(ColMajor)), YOU_MADE_A_PROGRAMMING_MISTAKE); | |
| // Collect dimension-wise indices (subs). | |
| array<Index, Base::NumDims> subs; | |
| for (int i = Base::NumDims - 1; i > 0; --i) { | |
| subs[i] = index / this->m_outputStrides[i]; | |
| index -= subs[i] * this->m_outputStrides[i]; | |
| } | |
| subs[0] = index; | |
| const Dimensions& left_dims = this->m_leftImpl.dimensions(); | |
| if (subs[this->m_axis] < left_dims[this->m_axis]) { | |
| Index left_index = subs[0]; | |
| for (int i = 1; i < Base::NumDims; ++i) { | |
| left_index += (subs[i] % left_dims[i]) * this->m_leftStrides[i]; | |
| } | |
| return this->m_leftImpl.coeffRef(left_index); | |
| } else { | |
| subs[this->m_axis] -= left_dims[this->m_axis]; | |
| const Dimensions& right_dims = this->m_rightImpl.dimensions(); | |
| Index right_index = subs[0]; | |
| for (int i = 1; i < Base::NumDims; ++i) { | |
| right_index += (subs[i] % right_dims[i]) * this->m_rightStrides[i]; | |
| } | |
| return this->m_rightImpl.coeffRef(right_index); | |
| } | |
| } | |
| template <int StoreMode> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void writePacket(Index index, const PacketReturnType& x) const { | |
| EIGEN_STATIC_ASSERT((static_cast<int>(Layout) == static_cast<int>(ColMajor)), YOU_MADE_A_PROGRAMMING_MISTAKE); | |
| const int packetSize = PacketType<CoeffReturnType, Device>::size; | |
| EIGEN_STATIC_ASSERT((packetSize > 1), YOU_MADE_A_PROGRAMMING_MISTAKE) | |
| eigen_assert(index + packetSize - 1 < this->dimensions().TotalSize()); | |
| EIGEN_ALIGN_TO_BOUNDARY(internal::unpacket_traits<PacketReturnType>::alignment) CoeffReturnType values[packetSize]; | |
| internal::pstore<CoeffReturnType, PacketReturnType>(values, x); | |
| for (int i = 0; i < packetSize; ++i) { | |
| coeffRef(index + i) = values[i]; | |
| } | |
| } | |
| // Mirror of the rvalue block(): split the block at the concat axis and copy | |
| // each piece into the corresponding operand's buffer. | |
| template <typename TensorBlock> | |
| EIGEN_STRONG_INLINE void writeBlock(const TensorBlockDesc& desc, const TensorBlock& block) { | |
| if (desc.size() == 0) return; | |
| eigen_assert(this->m_leftImpl.data() != nullptr && this->m_rightImpl.data() != nullptr); | |
| const DSizes<Index, NumDims> block_strides = internal::strides<Layout>(desc.dimensions()); | |
| // Materialize the block into a temporary buffer if it is lazy. | |
| const ScalarNoConst* block_buffer = block.data(); | |
| void* mem = nullptr; | |
| if (block_buffer == nullptr) { | |
| mem = this->m_device.allocate(desc.size() * sizeof(Scalar)); | |
| ScalarNoConst* buf = static_cast<ScalarNoConst*>(mem); | |
| typedef internal::TensorBlockAssignment<ScalarNoConst, NumDims, typename TensorBlock::XprType, Index> | |
| TensorBlockAssignment; | |
| TensorBlockAssignment::Run(TensorBlockAssignment::target(desc.dimensions(), block_strides, buf), block.expr()); | |
| block_buffer = buf; | |
| } | |
| // Decompose the block's offset into output coordinates. | |
| Index remaining = desc.offset(); | |
| DSizes<Index, NumDims> out_coords; | |
| EIGEN_IF_CONSTEXPR (static_cast<int>(Layout) == static_cast<int>(ColMajor)) { | |
| for (int i = NumDims - 1; i > 0; --i) { | |
| out_coords[i] = remaining / this->m_outputStrides[i]; | |
| remaining -= out_coords[i] * this->m_outputStrides[i]; | |
| } | |
| out_coords[0] = remaining; | |
| } else { | |
| for (int i = 0; i < NumDims - 1; ++i) { | |
| out_coords[i] = remaining / this->m_outputStrides[i]; | |
| remaining -= out_coords[i] * this->m_outputStrides[i]; | |
| } | |
| out_coords[NumDims - 1] = remaining; | |
| } | |
| const Index axis_start = out_coords[this->m_axis]; | |
| const Index axis_size = desc.dimension(static_cast<int>(this->m_axis)); | |
| const Index axis_end = axis_start + axis_size; | |
| const Index left_axis_size = this->m_leftAxisSize; | |
| typedef internal::TensorBlockIO<ScalarNoConst, Index, NumDims, Layout> TensorBlockIO; | |
| typedef typename TensorBlockIO::Dst TensorBlockIODst; | |
| typedef typename TensorBlockIO::Src TensorBlockIOSrc; | |
| if (axis_start < left_axis_size) { | |
| DSizes<Index, NumDims> left_sub_dims = desc.dimensions(); | |
| left_sub_dims[this->m_axis] = numext::mini(left_axis_size, axis_end) - axis_start; | |
| Index left_dst_offset = 0; | |
| for (int i = 0; i < NumDims; ++i) { | |
| left_dst_offset += out_coords[i] * this->m_leftStrides[i]; | |
| } | |
| TensorBlockIOSrc src(block_strides, block_buffer, /*src_offset=*/0); | |
| TensorBlockIODst dst(left_sub_dims, typename TensorBlockIO::Dimensions(this->m_leftStrides), | |
| this->m_leftImpl.data(), left_dst_offset); | |
| TensorBlockIO::Copy(dst, src); | |
| } | |
| if (axis_end > left_axis_size) { | |
| DSizes<Index, NumDims> right_sub_dims = desc.dimensions(); | |
| right_sub_dims[this->m_axis] = axis_end - numext::maxi(left_axis_size, axis_start); | |
| const Index right_axis_start = numext::maxi(Index(0), axis_start - left_axis_size); | |
| Index right_dst_offset = right_axis_start * this->m_rightStrides[this->m_axis]; | |
| for (int i = 0; i < NumDims; ++i) { | |
| if (i != this->m_axis) { | |
| right_dst_offset += out_coords[i] * this->m_rightStrides[i]; | |
| } | |
| } | |
| // Offset within the block buffer where the right-side piece starts. | |
| const Index src_offset = numext::maxi(Index(0), left_axis_size - axis_start) * block_strides[this->m_axis]; | |
| TensorBlockIOSrc src(block_strides, block_buffer, src_offset); | |
| TensorBlockIODst dst(right_sub_dims, typename TensorBlockIO::Dimensions(this->m_rightStrides), | |
| this->m_rightImpl.data(), right_dst_offset); | |
| TensorBlockIO::Copy(dst, src); | |
| } | |
| // Deallocate temporary buffer used for the block materialization. | |
| if (mem != nullptr) this->m_device.deallocate(mem); | |
| } | |
| }; | |
| } // end namespace Eigen | |
| #endif // EIGEN_TENSOR_TENSOR_CONCATENATION_H |