Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

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_