412 lines
15 KiB
C++
412 lines
15 KiB
C++
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/*!
|
|
* \file tvm/s_tir/data_layout.h
|
|
* \brief SLayout expression to describe the data organization of a tensor.
|
|
* And SBijectiveLayout to mapping two data layouts between each other.
|
|
*/
|
|
#ifndef TVM_S_TIR_DATA_LAYOUT_H_
|
|
#define TVM_S_TIR_DATA_LAYOUT_H_
|
|
|
|
#include <tvm/ffi/reflection/registry.h>
|
|
#include <tvm/tirx/expr.h>
|
|
#include <tvm/tirx/op.h>
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "tvm/tirx/var.h"
|
|
|
|
namespace tvm {
|
|
namespace tirx {
|
|
|
|
class SLayout;
|
|
|
|
class SLayoutAxis {
|
|
public:
|
|
static const SLayoutAxis& Get(const char name);
|
|
|
|
// Get the singleton SLayoutAxis using itvar->var->name_hint
|
|
static const SLayoutAxis& Get(const tirx::IterVar& itvar);
|
|
|
|
// Get the singleton SLayoutAxis using name[0] (size of name must be 1).
|
|
static const SLayoutAxis& Get(const std::string& name);
|
|
|
|
inline bool IsPrimal() const { return name_ >= 'A' && name_ <= 'Z'; }
|
|
inline std::string name() const { return std::string(1, name_); }
|
|
|
|
// if current axis is primal, switch the axis to its subordinate one,
|
|
// else switch to the primal.
|
|
inline const SLayoutAxis& ToDual() const {
|
|
if (name_ >= 'A' && name_ <= 'Z') {
|
|
return SLayoutAxis::Get(name_ - 'A' + 'a');
|
|
} else {
|
|
return SLayoutAxis::Get(name_ - 'a' + 'A');
|
|
}
|
|
}
|
|
|
|
// return the primal axis. If it is already primal, return itself.
|
|
const SLayoutAxis& ToPrimal() const { return IsPrimal() ? *this : ToDual(); }
|
|
|
|
// return the subordinate axis. If it is already subordinate, return itself.
|
|
const SLayoutAxis& ToSubordinate() const { return IsPrimal() ? ToDual() : *this; }
|
|
|
|
inline bool operator==(const SLayoutAxis& rhs) const { return name_ == rhs.name_; }
|
|
|
|
friend std::ostream& operator<<(std::ostream& os, const SLayoutAxis& l) {
|
|
os << l.name();
|
|
return os;
|
|
}
|
|
|
|
private:
|
|
static const SLayoutAxis UPPER_CASE[];
|
|
static const SLayoutAxis LOWER_CASE[];
|
|
SLayoutAxis(const SLayoutAxis&);
|
|
SLayoutAxis& operator=(const SLayoutAxis&);
|
|
explicit SLayoutAxis(const char name) : name_(name) {}
|
|
|
|
const char name_;
|
|
};
|
|
|
|
/*!
|
|
* \brief SLayout is to describe how data is organized within an N-dimention tensor.
|
|
* It is composed of upper cases, lower cases and numbers,
|
|
* where upper case indicates a primal axis and
|
|
* the corresponding lower case with factor size indicates the subordinate axis.
|
|
* For example, NCHW16c can describe a 5-D tensor of
|
|
* [batch_size, channel, height, width, channel_block].
|
|
* Here subordinate axis channel_block=16 is the factor size of the primal axis C (channel).
|
|
* SLayout for scalar is defined, while both its name and axes have size 0.
|
|
*/
|
|
class SLayoutNode : public ffi::Object {
|
|
public:
|
|
/*! \brief string representation of layout, "" for scalar. */
|
|
ffi::String name;
|
|
/*! \brief specify each axis of the layout,
|
|
* in which the variable name is the name of the axis.
|
|
* The IterVar's extent indicates the size of the axis,
|
|
* it is a variable for a primal axis, but a constant for a subordinate axis.
|
|
* Empty for scalar's layout.
|
|
*/
|
|
ffi::Array<tirx::IterVar> axes;
|
|
|
|
static void RegisterReflection() {
|
|
namespace refl = tvm::ffi::reflection;
|
|
refl::ObjectDef<SLayoutNode>()
|
|
.def_ro("name", &SLayoutNode::name)
|
|
.def_ro("axes", &SLayoutNode::axes);
|
|
}
|
|
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.SLayout", SLayoutNode, ffi::Object);
|
|
};
|
|
|
|
/*!
|
|
* \brief Managed reference to SLayoutNode
|
|
* \sa SLayoutNode
|
|
*/
|
|
class SLayout : public ffi::ObjectRef {
|
|
public:
|
|
explicit SLayout(const ffi::Array<tirx::IterVar>& axes);
|
|
|
|
/*! \brief construct from a string */
|
|
SLayout(const tvm::ffi::String& name) : SLayout(name.operator std::string()) {} // NOLINT(*)
|
|
|
|
/*! \brief construct from a string */
|
|
SLayout(const char* name) : SLayout(std::string(name)) {} // NOLINT(*)
|
|
|
|
/*!
|
|
* \brief construct from a string.
|
|
* \param name input in layout convention:
|
|
* upper case indicates a dimension and
|
|
* the corresponding lower case with factor size
|
|
* indicates the split dimension.
|
|
* return undefined layout if "__undef__" is passed.
|
|
* \param index_ty The type of generated axes vars in the returned layout.
|
|
* It is required to be integer type.
|
|
*/
|
|
TVM_DLL SLayout(const std::string& name, PrimType index_ty = PrimType::Int(32)); // NOLINT(*)
|
|
|
|
/*!
|
|
* \brief access the internal node container
|
|
* \return the pointer to the internal node container
|
|
*/
|
|
SLayoutNode* operator->() { return static_cast<SLayoutNode*>(get_mutable()); }
|
|
|
|
/*!
|
|
* \brief Return an undefined layout.
|
|
* \return a (global) undefined layout.
|
|
*/
|
|
static const SLayout& Undef() {
|
|
static SLayout undef;
|
|
return undef;
|
|
}
|
|
|
|
/*!
|
|
* \brief Packs the Given Array of IterVars into a Single IterVar. Each IterVar in the Array
|
|
* should represent either a single primal axis or one or more subordinate axis
|
|
* \param iters Array of iter vars to be packed
|
|
* \return A packed iter var
|
|
*/
|
|
static IterVar PackIterVar(ffi::Array<IterVar> iters);
|
|
|
|
/*!
|
|
* \brief Unpacks a Packed IterVar into its constituents
|
|
* \param packed_iter A Packed IterVar containing a single primal axis or one or more subordinate
|
|
* axis
|
|
* \return Constituent IterVars
|
|
*/
|
|
static ffi::Array<IterVar> UnpackIterVar(IterVar packed_iter);
|
|
|
|
/*!
|
|
* \brief Returns a sub-layout which is the portion of the object
|
|
* that starts at dimension \p pos and spans \p len dimensions
|
|
* (or until the end of the layout, whichever comes first).
|
|
* \param pos The start position.
|
|
* \param len The length of the sub-layout. if 0, return layout of scalar
|
|
* \return A newly constructed SLayout object.
|
|
*/
|
|
SLayout SubLayout(size_t pos, size_t len) const;
|
|
|
|
/*!
|
|
* \brief Split \p axis by \p size and put the sub-axis to position \p target_pos.
|
|
* \param axis The source axis to be split. It must be a primal-axis;
|
|
* \param target_pos The target position of the newly split subordinate-axis.
|
|
* \param factor size of the sub-dimension.
|
|
* \return A newly constructed SLayout object.
|
|
*/
|
|
SLayout Split(const SLayoutAxis& axis, size_t target_pos, int32_t factor) const;
|
|
|
|
/*! \return number of dimensions */
|
|
inline size_t ndim() const {
|
|
if (!defined()) return 0;
|
|
return operator->()->axes.size();
|
|
}
|
|
|
|
/*! \return number of super dimensions */
|
|
inline size_t ndim_primal() const {
|
|
if (!defined()) return 0;
|
|
size_t ct = 0;
|
|
for (auto px : operator->()->axes) {
|
|
auto iter_vars = UnpackIterVar(px);
|
|
for (auto x : iter_vars) {
|
|
if (SLayoutAxis::Get(x).IsPrimal()) {
|
|
ct++;
|
|
}
|
|
}
|
|
}
|
|
return ct;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns a new layout where the dims have been expanded to match the primal dimensions.
|
|
* \param dst_layout The dst layout to which current layout has to be expanded.
|
|
* \return The expanded SLayout.
|
|
*/
|
|
inline SLayout ExpandPrimal(const SLayout& dst_layout) {
|
|
SLayout new_src_layout;
|
|
// 1) Find the axis which are missing in the current layout. Make them the prefix.
|
|
std::string new_src_layout_str = "";
|
|
for (auto packed_axis : dst_layout->axes) {
|
|
auto iter_vars = UnpackIterVar(packed_axis);
|
|
for (auto dst_axis : iter_vars) {
|
|
if (SLayoutAxis::Get(dst_axis).IsPrimal()) {
|
|
if (!this->Contains(SLayoutAxis::Get(dst_axis))) {
|
|
new_src_layout_str += dst_axis->var->name_hint;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 2) Now, add the primal axis of the current layout.
|
|
new_src_layout_str += this->name();
|
|
new_src_layout = SLayout(new_src_layout_str);
|
|
return new_src_layout;
|
|
}
|
|
|
|
/*!
|
|
* \brief return the index of the input axis.
|
|
* If it is not found in the layout or the layout is undefined,
|
|
* return -1.
|
|
* \param axis The input axis either a layout axis, or a packed axis
|
|
* \return the index or -1 if not found.
|
|
*/
|
|
inline int32_t IndexOf(const std::string& axis) const {
|
|
if (!this->defined()) return -1;
|
|
const auto axes = operator->()->axes;
|
|
for (size_t i = 0; i < axes.size(); ++i) {
|
|
if (axes[i]->var->name_hint == axis) return static_cast<int32_t>(i);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*!
|
|
* \brief return the index of the input axis.
|
|
* If it is not found in the layout or the layout is undefined,
|
|
* return -1.
|
|
* \param axis the input layout axis.
|
|
* \return the index or -1 if not found.
|
|
*/
|
|
inline int32_t IndexOf(const SLayoutAxis& axis) const { return IndexOf(axis.name()); }
|
|
|
|
/*!
|
|
* \brief return the index of the input axis.
|
|
* If it is not found in the layout or the layout is undefined,
|
|
* return -1.
|
|
* \param iter the input iter var.
|
|
* \return the index or -1 if not found.
|
|
*/
|
|
inline int32_t IndexOf(const tirx::IterVar& iter) const { return IndexOf(iter->var->name_hint); }
|
|
|
|
/*!
|
|
* \brief Get the factor size of the subordinate axis.
|
|
* \param axis the input primal-axis or subordinate-axis.
|
|
* \return the size of the subordinate-axis of \p axis (if \p axis is a primal-axis),
|
|
* or the size of \p axis itself (if \p axis is a subordinate-axis).
|
|
* Return -1 if \p axis is not in the layout the layout is undefined.
|
|
*/
|
|
int32_t FactorOf(const SLayoutAxis& axis) const;
|
|
|
|
/*!
|
|
* \brief Whether the layout contains an axis.
|
|
* \param axis axis to be checked.
|
|
* \return Whether the layout contains the axis.
|
|
*/
|
|
bool Contains(const SLayoutAxis& axis) const {
|
|
if (!defined()) return false;
|
|
for (const tirx::IterVar packed_var : operator->()->axes) {
|
|
auto iter_vars = UnpackIterVar(packed_var);
|
|
for (auto var : iter_vars) {
|
|
if (var->var->name_hint == axis.name()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const SLayoutAxis& operator[](int32_t i) const {
|
|
TVM_FFI_ICHECK(defined()) << "Try to access axis from an undefined layout.";
|
|
int32_t index = i < 0 ? static_cast<int32_t>(ndim() + i) : i;
|
|
TVM_FFI_ICHECK(index >= 0 && static_cast<size_t>(index) < ndim()) << "Invalid index " << i;
|
|
const tirx::IterVar axis = operator->()->axes[index];
|
|
return SLayoutAxis::Get(axis);
|
|
}
|
|
|
|
IterVar PackedAxisAt(int32_t i) const {
|
|
TVM_FFI_ICHECK(defined()) << "Try to access axis from an undefined layout.";
|
|
int32_t index = i < 0 ? static_cast<int32_t>(ndim() + i) : i;
|
|
TVM_FFI_ICHECK(index >= 0 && static_cast<size_t>(index) < ndim()) << "Invalid index " << i;
|
|
const tirx::IterVar axis = operator->()->axes[index];
|
|
return axis;
|
|
}
|
|
|
|
/*! \return the string description of the layout */
|
|
inline std::string name() const {
|
|
if (!defined()) return "__undef__";
|
|
return operator->()->name;
|
|
}
|
|
|
|
/*!
|
|
* \brief Whether the two layouts are equal.
|
|
* \param rhs Another layout.
|
|
* \return whether the two layouts are equal.
|
|
*/
|
|
inline bool Equals(const SLayout& rhs) const { return name() == rhs.name(); }
|
|
|
|
/*!
|
|
* \brief allow output string of layout to ostream
|
|
* \param os the output stream
|
|
* \param l the layout
|
|
* \return the ostream
|
|
*/
|
|
friend std::ostream& operator<<(std::ostream& os, const SLayout& l) {
|
|
os << l.name();
|
|
return os;
|
|
}
|
|
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SLayout, ffi::ObjectRef, SLayoutNode);
|
|
};
|
|
|
|
// Internal node container SBijectiveLayout
|
|
class SBijectiveLayoutNode : public ffi::Object {
|
|
public:
|
|
/*! \brief Describes how source axes can be mapped to the destination axes,
|
|
* e.g., [i0 / 16, i1, i0 % 16] can describe NC -> NC16n
|
|
*/
|
|
ffi::Array<PrimExpr> index_forward_rule;
|
|
/*! \brief Describes how destination axes can be mapped to the source axes */
|
|
ffi::Array<PrimExpr> index_backward_rule;
|
|
/*! \brief Describes how source shapes can be mapped to the destination shapes */
|
|
ffi::Array<PrimExpr> shape_forward_rule;
|
|
/*! \brief Describes how destination shapes can be mapped to the source shapes */
|
|
ffi::Array<PrimExpr> shape_backward_rule;
|
|
|
|
/*! \brief The source layout */
|
|
SLayout src_layout;
|
|
/*! \brief The destination layout */
|
|
SLayout dst_layout;
|
|
|
|
static void RegisterReflection() {
|
|
namespace refl = tvm::ffi::reflection;
|
|
refl::ObjectDef<SBijectiveLayoutNode>()
|
|
.def_ro("src_layout", &SBijectiveLayoutNode::src_layout)
|
|
.def_ro("dst_layout", &SBijectiveLayoutNode::dst_layout)
|
|
.def_ro("index_forward_rule", &SBijectiveLayoutNode::index_forward_rule)
|
|
.def_ro("index_backward_rule", &SBijectiveLayoutNode::index_backward_rule)
|
|
.def_ro("shape_forward_rule", &SBijectiveLayoutNode::shape_forward_rule)
|
|
.def_ro("shape_backward_rule", &SBijectiveLayoutNode::shape_backward_rule);
|
|
}
|
|
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.SBijectiveLayout", SBijectiveLayoutNode, ffi::Object);
|
|
};
|
|
|
|
/*!
|
|
* \brief Bijective function mapping for data layout transformation.
|
|
* Given two SLayout, SBijectiveLayout build and store the mapping rules,
|
|
* provides API to transform N-dimention tensor from the source indices (i0, i1, .., im)
|
|
* to the destination indices (j0, j1, .., jm).
|
|
*/
|
|
class SBijectiveLayout : public ffi::ObjectRef {
|
|
public:
|
|
/*!
|
|
* \brief The constructor
|
|
* \param src_layout The source layout
|
|
* \param dst_layout The destination layout
|
|
*/
|
|
TVM_DLL SBijectiveLayout(SLayout src_layout, SLayout dst_layout);
|
|
|
|
// Given the source shape, infer the destination shape.
|
|
TVM_DLL ffi::Array<PrimExpr> ForwardShape(const ffi::Array<PrimExpr>& shape) const;
|
|
// Given the destination shape, recover the source shape.
|
|
TVM_DLL ffi::Array<PrimExpr> BackwardShape(const ffi::Array<PrimExpr>& dst_shape) const;
|
|
// Given the destination indices, infer the destination indices.
|
|
TVM_DLL ffi::Array<PrimExpr> ForwardIndex(const ffi::Array<PrimExpr>& index) const;
|
|
// Given the destination indices, recover the source indices.
|
|
TVM_DLL ffi::Array<PrimExpr> BackwardIndex(const ffi::Array<PrimExpr>& dst_index) const;
|
|
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SBijectiveLayout, ffi::ObjectRef,
|
|
SBijectiveLayoutNode);
|
|
};
|
|
|
|
} // namespace tirx
|
|
} // namespace tvm
|
|
|
|
#endif // TVM_S_TIR_DATA_LAYOUT_H_
|