blob: b618b7e276c74e9f387e68fd2b3a04b5164d9417 [file]
// 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 unsupported/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