This is an automated email from the ASF dual-hosted git repository.
leandron pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 20d8c53731 [SVE] Add support for representing and creating
buffer-level predicates (#16966)
20d8c53731 is described below
commit 20d8c537316758ba13017f2c7dc9e5de77ecf069
Author: Luke Hutton <[email protected]>
AuthorDate: Tue May 28 11:15:29 2024 +0100
[SVE] Add support for representing and creating buffer-level predicates
(#16966)
* [SVE] Add support for representing and creating buffer-level predicates
Representation
--------------
This commit extends `BufferLoad` and `BufferStore` to accept a predicate
mask argument indicating which lanes in a vectorized buffer load/store
should be read/written.
As a simple example, we can load all lanes:
```
tir.BufferLoad(buf, [tir.Ramp(0, 1, 8)], predicate=tir.Broadcast(1, 8))
```
Or disable loading all lanes:
```
tir.BufferLoad(buf, [tir.Ramp(0, 1, 8)], predicate=tir.Broadcast(0, 8))
```
In TVMScript, buffer loads and stores are currently displayed using a
"short-hand" notation e.g. `A[0:4]`, but there was no clear path for
extending this notation to support predicates. Therefore, a "long-hand"
notation is introduced e.g. `A.load([T.Ramp(0, 1, 4)], predicate=...)`.
The TVMScript printer falls back to the long-hand notation whenever
predicates are specified.
Creation
--------
Buffer-level predication becomes more motivating when combined with the
`tir.get_active_lane_mask` intrinsic. It can be used to mask off lanes
when the vectorized axis is not divisible by the vector length. A
detailed example and rationale can be found in the
[RFC](https://github.com/apache/tvm-rfcs/blob/main/rfcs/0104-scalable-vectors-in-tir.md#predication).
Predicated buffer load/stores are created in the `VectorizeLoop` pass
via `TryPredicateBufferAccesses`. This pass aims to convert block-level
predicates e.g.
```
for i_0 in T.serial(4):
for i_1 in T.vectorized(4):
if i_0 * 4 + i_1 < 14:
B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
```
to buffer-level predicates, e.g.
```
for i_0 in T.serial(4):
predicate = T.get_active_lane_mask("int1x4", i_0 * 4, 14)
A_load = T.meta_var(A.load([T.Ramp(i_0 * 4, 1, 4)],
predicate=predicate))
B.store(A_load, [T.Ramp(i_0 * 4, 1, 4)], predicate=predicate)
```
It takes a conservative approach for now, focussing only on expressions
produced by the split scheduling primitive, but more complex expressions
could be supported in the future.
`TryPredicateBufferAccesses` can be explicitly enabled/disabled with the
`tir.enable_buffer_level_predication` pass context option. By default it
will be disabled, unless the target supports SVE, in which case it will
be enabled by default.
Co-authored-by: Elen Kalda <[email protected]>
Co-authored-by: Neil Hickey <[email protected]>
Change-Id: Idde259a7d7e4536f00ed3a1dafedd0a5d24a1593
* Fix lint and correct test config option name
Change-Id: I864475c3d03e9b426ce5ef987989216d57f3e019
* Address review comments
This includes:
* Taking into account possibility of target being overridden in
the vectorize pass.
* Predicate PrimExpr -> Optional<PrimExpr>
* Checking that predicate is not used for any target that doesn't
support it.
* Use vload/vstore API as opposed to load/store
* int1 mask -> uint1 mask for boolean representation. This is converted
to int1 in the LLVM backend.
Change-Id: I4da0705352e321f6be6333a5bb777caa6a6ca9ef
* Fix lint
Change-Id: Idd3f3593fe524f3444487c520d947dfd53386db0
* Fix some failing tests
* vload/vstore updates that were missed previously
* int1 -> bool updates
* fix gpu target tests
Fixes a test and updates comments referencing old load/store api
Change-Id: I26a0c480d2dedee442ca0116909a7751d1dfa9ac
* Address comments
- Correct doc strings
- Correct typo in error message
- Add some additional checks for BufferLoad
Change-Id: Ie25563d569c0ed729ac915a6ba3a724a9e191014
* Account for buffer lanes in predicate lane check
Change-Id: I821210665e36c26bfa37fc9ed380b5d03c9e816e
---
include/tvm/script/ir_builder/tir/ir.h | 5 +-
include/tvm/tir/buffer.h | 10 +-
include/tvm/tir/expr.h | 7 +-
include/tvm/tir/stmt.h | 6 +-
python/tvm/ir/json_compact.py | 27 ++
python/tvm/script/ir_builder/tir/ir.py | 8 +-
python/tvm/script/parser/tir/parser.py | 2 +
python/tvm/tir/buffer.py | 17 +-
python/tvm/tir/expr.py | 14 +-
python/tvm/tir/stmt.py | 9 +-
src/arith/analyzer.cc | 5 +-
src/arith/const_int_bound.cc | 2 +-
src/arith/scalable_expression.cc | 3 +-
src/arith/scalable_expression.h | 4 +-
src/driver/driver_api.cc | 1 +
src/script/ir_builder/tir/ir.cc | 5 +-
src/script/printer/tir/buffer.cc | 23 +-
src/target/llvm/codegen_llvm.cc | 70 +++--
src/target/llvm/codegen_llvm.h | 12 +-
src/target/source/codegen_c.cc | 2 +
src/target/source/codegen_webgpu.cc | 3 +
src/te/operation/create_primfunc.cc | 4 +-
src/tir/analysis/device_constraint_utils.cc | 5 +-
src/tir/contrib/ethosu/passes.cc | 3 +-
src/tir/ir/buffer.cc | 31 ++-
src/tir/ir/expr.cc | 31 ++-
src/tir/ir/expr_functor.cc | 2 +-
src/tir/ir/stmt.cc | 46 ++--
src/tir/transforms/inject_rolling_buffer.cc | 8 +-
src/tir/transforms/lower_match_buffer.cc | 4 +
.../manifest_shared_memory_local_stage.cc | 2 +
src/tir/transforms/remove_no_op.cc | 3 +-
.../remove_weight_layout_rewrite_block.cc | 2 +-
src/tir/transforms/storage_flatten.cc | 22 +-
src/tir/transforms/unsupported_dtype_legalize.cc | 8 +
src/tir/transforms/vectorize_loop.cc | 172 +++++++++++-
tests/python/codegen/test_target_codegen.py | 92 +++++++
.../python/codegen/test_target_codegen_aarch64.py | 28 +-
tests/python/codegen/test_target_codegen_llvm.py | 29 +++
tests/python/relay/test_json_compact.py | 94 +++++++
tests/python/tir-base/test_tir_nodes.py | 69 +++++
.../tir-transform/test_tir_transform_vectorize.py | 287 ++++++++++++++++++++-
.../tvmscript/test_tvmscript_ir_builder_tir.py | 14 +
.../python/tvmscript/test_tvmscript_printer_tir.py | 97 +++++++
tests/python/tvmscript/test_tvmscript_roundtrip.py | 16 ++
45 files changed, 1196 insertions(+), 108 deletions(-)
diff --git a/include/tvm/script/ir_builder/tir/ir.h
b/include/tvm/script/ir_builder/tir/ir.h
index 5b44f79ad7..380c2fcce2 100644
--- a/include/tvm/script/ir_builder/tir/ir.h
+++ b/include/tvm/script/ir_builder/tir/ir.h
@@ -411,8 +411,11 @@ Var EnvThread(String thread_tag, DataType dtype =
DataType::Int(32));
* \param buffer The buffer.
* \param value The value to be stored.
* \param indices The indices location to be stored.
+ * \param predicate A vector mask of boolean values indicating which lanes of
a vector are to be
+ * stored. The number lanes of the mask must be equal to the number of lanes
in value.
*/
-void BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr> indices);
+void BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr> indices,
+ Optional<PrimExpr> predicate);
/*!
* \brief The prefetch hint for a buffer
diff --git a/include/tvm/tir/buffer.h b/include/tvm/tir/buffer.h
index b2736a30e4..276198abb8 100644
--- a/include/tvm/tir/buffer.h
+++ b/include/tvm/tir/buffer.h
@@ -209,14 +209,20 @@ class Buffer : public ObjectRef {
* \brief Create an Expr that does a vector load at begin index.
* \param begin The beginning index
* \param dtype The data type to be loaded.
+ * \param predicate A vector mask of boolean values indicating which lanes
of a vector are to be
+ * loaded. The number lanes of the mask must be equal to the number of lanes
in being loaded.
*/
- TVM_DLL PrimExpr vload(Array<PrimExpr> begin, DataType dtype) const;
+ TVM_DLL PrimExpr vload(Array<PrimExpr> begin, DataType dtype,
+ Optional<PrimExpr> predicate = NullOpt) const;
/*!
* \brief Create a Stmt that does a vector store at begin index.
* \param begin The beginning index
* \param value The value to be stored.
+ * \param predicate A vector mask of boolean values indicating which lanes
of a vector are to be
+ * stored. The number lanes of the mask must be equal to the number of lanes
in value.
*/
- TVM_DLL Stmt vstore(Array<PrimExpr> begin, PrimExpr value) const;
+ TVM_DLL Stmt vstore(Array<PrimExpr> begin, PrimExpr value,
+ Optional<PrimExpr> predicate = NullOpt) const;
/*!
* \brief Get a flattened version of the buffer
diff --git a/include/tvm/tir/expr.h b/include/tvm/tir/expr.h
index 39b32f5633..d9b65dc874 100644
--- a/include/tvm/tir/expr.h
+++ b/include/tvm/tir/expr.h
@@ -630,11 +630,14 @@ class BufferLoadNode : public PrimExprNode {
Buffer buffer;
/*! \brief The indices location to be loaded. */
Array<PrimExpr> indices;
+ /*! \brief The predicate mask for loading values. */
+ Optional<PrimExpr> predicate;
void VisitAttrs(AttrVisitor* v) {
v->Visit("dtype", &(this->dtype));
v->Visit("buffer", &buffer);
v->Visit("indices", &indices);
+ v->Visit("predicate", &predicate);
v->Visit("span", &span);
}
@@ -647,6 +650,7 @@ class BufferLoadNode : public PrimExprNode {
hash_reduce(dtype);
hash_reduce(buffer);
hash_reduce(indices);
+ hash_reduce(predicate);
}
static constexpr const char* _type_key = "tir.BufferLoad";
@@ -675,7 +679,8 @@ class BufferLoadNode : public PrimExprNode {
*/
class BufferLoad : public PrimExpr {
public:
- TVM_DLL explicit BufferLoad(Buffer buffer, Array<PrimExpr> indices, Span
span = Span());
+ TVM_DLL explicit BufferLoad(Buffer buffer, Array<PrimExpr> indices,
+ Optional<PrimExpr> predicate = NullOpt, Span
span = Span());
TVM_DEFINE_OBJECT_REF_METHODS(BufferLoad, PrimExpr, BufferLoadNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(BufferLoadNode);
};
diff --git a/include/tvm/tir/stmt.h b/include/tvm/tir/stmt.h
index 07cc9b5ad0..c77254ed34 100644
--- a/include/tvm/tir/stmt.h
+++ b/include/tvm/tir/stmt.h
@@ -231,11 +231,14 @@ class BufferStoreNode : public StmtNode {
PrimExpr value;
/*! \brief The indices location to be stored. */
Array<PrimExpr> indices;
+ /*! \brief The predicate mask for storing values. */
+ Optional<PrimExpr> predicate;
void VisitAttrs(AttrVisitor* v) {
v->Visit("buffer", &buffer);
v->Visit("value", &value);
v->Visit("indices", &indices);
+ v->Visit("predicate", &predicate);
v->Visit("span", &span);
}
@@ -248,6 +251,7 @@ class BufferStoreNode : public StmtNode {
hash_reduce(buffer);
hash_reduce(value);
hash_reduce(indices);
+ hash_reduce(predicate);
}
static constexpr const char* _type_key = "tir.BufferStore";
@@ -261,7 +265,7 @@ class BufferStoreNode : public StmtNode {
class BufferStore : public Stmt {
public:
TVM_DLL explicit BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr>
indices,
- Span span = Span());
+ Optional<PrimExpr> predicate = NullOpt, Span
span = Span());
TVM_DEFINE_OBJECT_REF_METHODS(BufferStore, Stmt, BufferStoreNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(BufferStoreNode);
diff --git a/python/tvm/ir/json_compact.py b/python/tvm/ir/json_compact.py
index cb6e031667..756dbc4992 100644
--- a/python/tvm/ir/json_compact.py
+++ b/python/tvm/ir/json_compact.py
@@ -57,6 +57,31 @@ def create_updater(node_map, from_ver, to_ver):
return _updater
+def create_updater_16_to_17():
+ """
+ Create an update to upgrade json from v0.16 to v0.17
+
+ Returns
+ -------
+ fupdater : function
+ The updater function
+ """
+
+ def _update_predicate_argument(item, nodes):
+ null_value_idx = 0
+ null_value = nodes[null_value_idx]
+ assert str(null_value) == "{'type_key': ''}", f"Expected a null value
but got {null_value}"
+ item["attrs"]["predicate"] = str(null_value_idx)
+ return item
+
+ node_map = {
+ "tir.BufferLoad": _update_predicate_argument,
+ "tir.BufferStore": _update_predicate_argument,
+ }
+
+ return create_updater(node_map, "0.16", "0.17")
+
+
def create_updater_15_to_16():
"""
Create an update to upgrade json from v0.15 to v0.16
@@ -316,5 +341,7 @@ def upgrade_json(json_str):
data = create_updater({}, "0.14", "0.15")(data)
if _from_version(data).startswith("0.15"):
data = create_updater_15_to_16()(data)
+ if _from_version(data).startswith("0.16"):
+ data = create_updater_16_to_17()(data)
return json.dumps(data, indent=2)
diff --git a/python/tvm/script/ir_builder/tir/ir.py
b/python/tvm/script/ir_builder/tir/ir.py
index 5a0a564a2a..8289ea96ae 100644
--- a/python/tvm/script/ir_builder/tir/ir.py
+++ b/python/tvm/script/ir_builder/tir/ir.py
@@ -1265,6 +1265,7 @@ def buffer_store(
buffer: Buffer, # pylint: disable=redefined-outer-name
value: PrimExpr,
indices: List[Union[PrimExpr, slice]],
+ predicate: Optional[PrimExpr] = None,
) -> None:
"""Buffer store node.
@@ -1278,6 +1279,11 @@ def buffer_store(
indices : List[Union[PrimExpr, slice]]
The indices location to be stored.
+
+ predicate : Optional[PrimExpr]
+ A vector mask of boolean values indicating which lanes of a vector are
to be
+ stored. The number lanes of the mask must be equal to the number of
lanes in
+ value.
"""
from tvm.arith import Analyzer # pylint: disable=import-outside-toplevel
@@ -1298,7 +1304,7 @@ def buffer_store(
if isinstance(value, bool) and buffer.dtype == "bool":
value = IntImm("bool", value)
return _ffi_api.BufferStore( # type: ignore[attr-defined] # pylint:
disable=no-member
- buffer, value, expr_indices
+ buffer, value, expr_indices, predicate
)
diff --git a/python/tvm/script/parser/tir/parser.py
b/python/tvm/script/parser/tir/parser.py
index 313e6c5f44..e545bc3a5e 100644
--- a/python/tvm/script/parser/tir/parser.py
+++ b/python/tvm/script/parser/tir/parser.py
@@ -462,6 +462,8 @@ def visit_expr_stmt(self: Parser, node: doc.Expr) -> None:
elif isinstance(res, str):
# Ignore docstrings
pass
+ elif isinstance(res, tvm.tir.stmt.BufferStore):
+ T.buffer_store(res.buffer, res.value, res.indices, res.predicate)
else:
self.report_error(node, f"Parsing resulted in unexpected type
{type(res)}")
diff --git a/python/tvm/tir/buffer.py b/python/tvm/tir/buffer.py
index ec57ad7801..501d13b17e 100644
--- a/python/tvm/tir/buffer.py
+++ b/python/tvm/tir/buffer.py
@@ -101,7 +101,7 @@ class Buffer(Object, Scriptable):
self, access_mask, ptr_type, content_lanes, offset, extent #
type: ignore
)
- def vload(self, begin, dtype=None):
+ def vload(self, begin, dtype=None, predicate=None):
"""Generate an Expr that loads dtype from begin index.
Parameters
@@ -113,6 +113,10 @@ class Buffer(Object, Scriptable):
The data type to be loaded,
can be vector type which have lanes that is multiple of
Buffer.dtype
+ predicate : Optional[PrimExpr]
+ A vector mask of boolean values indicating which lanes of a vector
are to be
+ loaded. The number lanes of the mask must be equal to the number
of lanes being loaded.
+
Returns
-------
load : Expr
@@ -120,9 +124,9 @@ class Buffer(Object, Scriptable):
"""
begin = (begin,) if isinstance(begin, (int, PrimExpr)) else begin
dtype = dtype if dtype else self.dtype
- return _ffi_api.BufferVLoad(self, begin, dtype) # type: ignore
+ return _ffi_api.BufferVLoad(self, begin, dtype, predicate) # type:
ignore
- def vstore(self, begin, value):
+ def vstore(self, begin, value, predicate=None):
"""Generate a Stmt that store value into begin index.
Parameters
@@ -133,13 +137,18 @@ class Buffer(Object, Scriptable):
value : Expr
The value to be stored.
+ predicate : Optional[PrimExpr]
+ A vector mask of boolean values indicating which lanes of a vector
are to be
+ stored. The number lanes of the mask must be equal to the number
of lanes in
+ value.
+
Returns
-------
store : Stmt
The corresponding store stmt.
"""
begin = (begin,) if isinstance(begin, (int, PrimExpr)) else begin
- return _ffi_api.BufferVStore(self, begin, value) # type: ignore
+ return _ffi_api.BufferVStore(self, begin, value, predicate) # type:
ignore
def scope(self):
"""Return the storage scope associated with this buffer.
diff --git a/python/tvm/tir/expr.py b/python/tvm/tir/expr.py
index fca501874d..c78bb9e7ec 100644
--- a/python/tvm/tir/expr.py
+++ b/python/tvm/tir/expr.py
@@ -1093,20 +1093,28 @@ class BufferLoad(PrimExprWithOp):
The buffer to be loaded.
indices : List[PrimExpr]
- The buffer indices.
+ The buffer indices to load values from.
span : Optional[Span]
The location of this expression in the source code.
+
+ predicate : Optional[PrimExpr]
+ A vector mask of boolean values indicating which lanes of a vector are
to be
+ loaded. The number lanes of the mask must be equal to the number of
lanes being loaded.
"""
buffer: Buffer
indices: List[PrimExpr]
def __init__(
- self, buffer: Buffer, indices: List[PrimExpr], span: Optional[Span] =
None
+ self,
+ buffer: Buffer,
+ indices: List[PrimExpr],
+ predicate: Optional[PrimExpr] = None,
+ span: Optional[Span] = None,
) -> None:
self.__init_handle_by_constructor__(
- _ffi_api.BufferLoad, buffer, indices, span # type: ignore
+ _ffi_api.BufferLoad, buffer, indices, predicate, span # type:
ignore
)
diff --git a/python/tvm/tir/stmt.py b/python/tvm/tir/stmt.py
index 992c388e27..aa3b17a7a1 100644
--- a/python/tvm/tir/stmt.py
+++ b/python/tvm/tir/stmt.py
@@ -224,6 +224,11 @@ class BufferStore(Stmt):
indices : List[PrimExpr]
The indices location to be stored.
+ predicate : Optional[PrimExpr]
+ A vector mask of boolean values indicating which lanes of a vector are
to be
+ stored. The number lanes of the mask must be equal to the number of
lanes in
+ value.
+
span : Optional[Span]
The location of the stmt in the source code.
"""
@@ -231,6 +236,7 @@ class BufferStore(Stmt):
buffer: Buffer
value: PrimExpr
indices: List[PrimExpr]
+ predicate: Optional[PrimExpr]
span: Optional[Span]
def __init__(
@@ -238,10 +244,11 @@ class BufferStore(Stmt):
buffer: Buffer,
value: PrimExpr,
indices: List[PrimExpr],
+ predicate: Optional[PrimExpr] = None,
span: Optional[Span] = None,
) -> None:
self.__init_handle_by_constructor__(
- _ffi_api.BufferStore, buffer, value, indices, span # type: ignore
+ _ffi_api.BufferStore, buffer, value, indices, predicate, span #
type: ignore
)
diff --git a/src/arith/analyzer.cc b/src/arith/analyzer.cc
index 0c4248bd3f..08d5e9379d 100644
--- a/src/arith/analyzer.cc
+++ b/src/arith/analyzer.cc
@@ -233,15 +233,16 @@ bool Analyzer::CanProve(const PrimExpr& expr,
ProofStrength strength) {
// "T.vscale" and the compile target uses a scalable architecture extension
like
// SVE, we can make some assumptions about the value of vscale and iterate
over a
// space of pre-defined values to attempt to prove the expression.
+ Target curr_target = Target::Current();
if (ContainsVscaleCall(simplified)) {
- if (TargetHasSVE()) {
+ if (TargetHasSVE(curr_target)) {
return CanProveVscaleExpressionFromKnownValues(this, simplified,
kAArch64VScaleValues);
}
LOG(WARNING)
<< "The expression contains scalable values. An attempt to prove by
substituting "
"with known values of vscale was not performed. This proof
currently only supports "
"AArch64 SVE targets, but the target was "
- << Target::Current();
+ << curr_target;
}
return false;
}
diff --git a/src/arith/const_int_bound.cc b/src/arith/const_int_bound.cc
index 2f9d640ee7..ecd3b25bfc 100644
--- a/src/arith/const_int_bound.cc
+++ b/src/arith/const_int_bound.cc
@@ -370,7 +370,7 @@ class ConstIntBoundAnalyzer::Impl
return VisitLeftShift(op);
} else if (op->op.same_as(tir::builtin::bitwise_and())) {
return VisitBitwiseAnd(op);
- } else if (op->op.same_as(tir::builtin::vscale()) && TargetHasSVE()) {
+ } else if (op->op.same_as(tir::builtin::vscale()) &&
TargetHasSVE(Target::Current())) {
unsigned int max_val =
*std::max_element(kAArch64VScaleValues.begin(),
kAArch64VScaleValues.end());
return MakeBound(1, max_val);
diff --git a/src/arith/scalable_expression.cc b/src/arith/scalable_expression.cc
index 2df035d615..e5f3bc28ba 100644
--- a/src/arith/scalable_expression.cc
+++ b/src/arith/scalable_expression.cc
@@ -93,8 +93,7 @@ bool CanProveVscaleExpressionFromKnownValues(arith::Analyzer*
analyzer, const Pr
return can_prove_expr;
}
-bool TargetHasSVE() {
- Target current_target = Target::Current();
+bool TargetHasSVE(Target current_target) {
bool has_sve{false};
if (current_target.defined()) {
has_sve =
current_target->GetFeature<Bool>("has_sve").value_or(Bool(false));
diff --git a/src/arith/scalable_expression.h b/src/arith/scalable_expression.h
index 8e807eb3b8..06ff8104e9 100644
--- a/src/arith/scalable_expression.h
+++ b/src/arith/scalable_expression.h
@@ -27,6 +27,7 @@
#include <tvm/arith/analyzer.h>
#include <tvm/ir/expr.h>
+#include <tvm/target/target.h>
#include <optional>
#include <vector>
@@ -79,9 +80,10 @@ bool
CanProveVscaleExpressionFromKnownValues(arith::Analyzer* analyzer, const Pr
/*!
* \brief Check whether the compilation target supports SVE
+ * \param target The target to check.
* \return Whether SVE is supported
*/
-bool TargetHasSVE();
+bool TargetHasSVE(Target target);
} // namespace arith
} // namespace tvm
diff --git a/src/driver/driver_api.cc b/src/driver/driver_api.cc
index 7ea5032fa0..3026f6e58f 100644
--- a/src/driver/driver_api.cc
+++ b/src/driver/driver_api.cc
@@ -44,6 +44,7 @@ TVM_REGISTER_PASS_CONFIG_OPTION("tir.detect_global_barrier",
Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.instrument_bound_checkers", Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.disable_assert", Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.disable_vectorize", Bool);
+TVM_REGISTER_PASS_CONFIG_OPTION("tir.enable_buffer_level_predication", Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.disable_cse_tir", Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.enable_debug", Bool);
TVM_REGISTER_PASS_CONFIG_OPTION("tir.enable_equiv_terms_in_cse_tir", Bool);
diff --git a/src/script/ir_builder/tir/ir.cc b/src/script/ir_builder/tir/ir.cc
index 3ce5c15e6c..17353561ee 100644
--- a/src/script/ir_builder/tir/ir.cc
+++ b/src/script/ir_builder/tir/ir.cc
@@ -524,7 +524,8 @@ Var EnvThread(String thread_tag, DataType dtype) {
return var;
}
-void BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr> indices) {
+void BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr> indices,
+ Optional<PrimExpr> predicate = NullOpt) {
runtime::DataType buffer_dtype = buffer->dtype;
bool is_index_scalable = indices.empty() ? false :
indices.back().dtype().is_scalable_vector();
bool is_buffer_dtype_scalable = buffer_dtype.is_scalable_vector();
@@ -586,7 +587,7 @@ void BufferStore(Buffer buffer, PrimExpr value,
Array<PrimExpr> indices) {
}
value = tvm::cast(lhs_dtype, value);
}
- AddToParent(tvm::tir::BufferStore(buffer, value, indices));
+ AddToParent(tvm::tir::BufferStore(buffer, value, indices, predicate));
}
void Prefetch(Buffer buffer, Array<Range> bounds) {
diff --git a/src/script/printer/tir/buffer.cc b/src/script/printer/tir/buffer.cc
index 45a0dfd2ae..87db53061c 100644
--- a/src/script/printer/tir/buffer.cc
+++ b/src/script/printer/tir/buffer.cc
@@ -273,14 +273,33 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<tir::BufferStore>( //
"", [](tir::BufferStore store, ObjectPath p, IRDocsifier d) -> Doc {
ExprDoc buffer = d->AsDoc<ExprDoc>(store->buffer, p->Attr("buffer"));
- return AssignDoc(/*lhs=*/buffer[BufferIndices(store->indices,
p->Attr("indices"), d)],
- /*rhs=*/d->AsDoc<ExprDoc>(store->value,
p->Attr("value")), NullOpt);
+ ExprDoc value = d->AsDoc<ExprDoc>(store->value, p->Attr("value"));
+
+ // Use .vstore(...) syntax when there is a predicate
+ if (store->predicate.defined()) {
+ ExprDoc indices = d->AsDoc<ExprDoc>(store->indices,
p->Attr("indices"));
+ ExprDoc predicate = d->AsDoc<ExprDoc>(store->predicate,
p->Attr("predicate"));
+ return ExprStmtDoc(
+ buffer->Attr("vstore")->Call({indices, value}, {"predicate"},
{predicate}));
+ }
+
+ return AssignDoc(
+ /*lhs=*/buffer[BufferIndices(store->indices, p->Attr("indices"),
d)],
+ /*rhs=*/value, NullOpt);
});
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
.set_dispatch<tir::BufferLoad>( //
"", [](tir::BufferLoad load, ObjectPath p, IRDocsifier d) -> Doc {
ExprDoc buffer = d->AsDoc<ExprDoc>(load->buffer, p->Attr("buffer"));
+
+ // Use .vload(...) syntax when there is a predicate
+ if (load->predicate.defined()) {
+ ExprDoc indices = d->AsDoc<ExprDoc>(load->indices,
p->Attr("indices"));
+ ExprDoc predicate = d->AsDoc<ExprDoc>(load->predicate,
p->Attr("predicate"));
+ return buffer->Attr("vload")->Call({indices}, {"predicate"},
{predicate});
+ }
+
return buffer[BufferIndices(load->indices, p->Attr("indices"), d)];
});
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index 6fc083d17c..6098a3f32f 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -1668,9 +1668,9 @@ bool CodeGenLLVM::HasAlignmentPadding(DataType dtype) {
}
void CodeGenLLVM::BufferAccessHelper(
- Buffer buffer, Array<PrimExpr> indices, DataType value_dtype,
- std::function<llvm::Instruction*(TypedPointer buffer_ptr, int
subelement_i, int alignment,
- bool is_volatile)>
+ Buffer buffer, Array<PrimExpr> indices, Optional<PrimExpr> predicate,
DataType value_dtype,
+ std::function<llvm::Instruction*(TypedPointer buffer_ptr, int subelement_i,
+ llvm::Value* predicate, int alignment,
bool is_volatile)>
make_instruction) {
DataType buffer_element_dtype = buffer->dtype;
@@ -1750,6 +1750,11 @@ void CodeGenLLVM::BufferAccessHelper(
std::vector<llvm::Value*> all_index_values = earlier_index_values;
all_index_values.push_back(last_index_value);
+ llvm::Value* predicate_value = nullptr;
+ if (predicate.defined()) {
+ predicate_value = MakeValue(predicate.value());
+ }
+
TypedPointer buffer_ptr =
value_dtype.is_scalable_vector()
? CreateBufferPtr(MakeValue(buffer->data), buffer_element_dtype,
all_index_values,
@@ -1758,7 +1763,8 @@ void CodeGenLLVM::BufferAccessHelper(
: CreateBufferPtr(
MakeValue(buffer->data), buffer_element_dtype,
all_index_values,
value_dtype.with_lanes(value_dtype.lanes() /
last_index.dtype().lanes()));
- auto instruction = make_instruction(buffer_ptr, subelement_i, alignment,
is_volatile);
+ auto instruction =
+ make_instruction(buffer_ptr, subelement_i, predicate_value, alignment,
is_volatile);
AddAliasInfo(instruction, buffer->data.get(), last_index_origin,
buffer_element_dtype_origin);
}
}
@@ -1768,17 +1774,30 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const
BufferLoadNode* op) {
std::vector<llvm::Value*> loads;
- auto make_load = [this, &loads](TypedPointer buffer_ptr, int /* subelement_i
*/, int alignment,
- bool is_volatile) {
+ auto make_load = [this, &loads](TypedPointer buffer_ptr, int /* subelement_i
*/,
+ llvm::Value* predicate, int alignment, bool
is_volatile) {
+ llvm::Instruction* load = nullptr;
+ if (predicate != NULL) {
+ ICHECK(!is_volatile)
+ << "The masked load intrinsic does not support declaring load as
volatile.";
+#if TVM_LLVM_VERSION >= 130
+ load = builder_->CreateMaskedLoad(buffer_ptr.type, buffer_ptr.addr,
llvm::Align(alignment),
+ predicate);
+#elif TVM_LLVM_VERSION >= 110
+ load = builder_->CreateMaskedLoad(buffer_ptr.addr,
llvm::Align(alignment), predicate);
+#else
+ load = builder_->CreateMaskedLoad(buffer_ptr.addr, alignment, predicate);
+#endif
+ } else {
#if TVM_LLVM_VERSION >= 110
- auto load = builder_->CreateAlignedLoad(buffer_ptr.type, buffer_ptr.addr,
- llvm::Align(alignment),
is_volatile);
+ load = builder_->CreateAlignedLoad(buffer_ptr.type, buffer_ptr.addr,
llvm::Align(alignment),
+ is_volatile);
#elif TVM_LLVM_VERSION >= 80
- auto load =
- builder_->CreateAlignedLoad(buffer_ptr.type, buffer_ptr.addr,
alignment, is_volatile);
+ load = builder_->CreateAlignedLoad(buffer_ptr.type, buffer_ptr.addr,
alignment, is_volatile);
#else
- auto load = builder_->CreateAlignedLoad(buffer_ptr.addr, alignment,
is_volatile);
+ load = builder_->CreateAlignedLoad(buffer_ptr.addr, alignment,
is_volatile);
#endif
+ }
loads.push_back(load);
return load;
@@ -1787,7 +1806,7 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const
BufferLoadNode* op) {
// Pass all indices into BufferAccessHelper. In CodeGenLLVM,
// non-flat indices will result in an error in CreateBufferPtr, but
// a subclass may override CreateBufferPtr.
- BufferAccessHelper(op->buffer, op->indices, value_dtype, make_load);
+ BufferAccessHelper(op->buffer, op->indices, op->predicate, value_dtype,
make_load);
if (loads.size() == 1) {
return loads[0];
@@ -1902,24 +1921,39 @@ void CodeGenLLVM::VisitStmt_(const BufferStoreNode* op)
{
llvm::Value* value = MakeValue(op->value);
- auto make_store = [this, value](TypedPointer buffer_ptr, int subelement_i,
int alignment,
- bool is_volatile) {
+ auto make_store = [this, value](TypedPointer buffer_ptr, int subelement_i,
llvm::Value* predicate,
+ int alignment, bool is_volatile) {
llvm::Value* to_store = value;
+ llvm::Instruction* store;
+
if (subelement_i != -1) {
to_store = builder_->CreateExtractElement(value, subelement_i);
}
+
+ if (predicate != NULL) {
+ ICHECK(!is_volatile)
+ << "The masked store intrinsic does not support declaring store as
volatile.";
#if TVM_LLVM_VERSION >= 110
- return builder_->CreateAlignedStore(to_store, buffer_ptr.addr,
llvm::Align(alignment),
- is_volatile);
+ store =
+ builder_->CreateMaskedStore(to_store, buffer_ptr.addr,
llvm::Align(alignment), predicate);
#else
- return builder_->CreateAlignedStore(to_store, buffer_ptr.addr, alignment,
is_volatile);
+ store = builder_->CreateMaskedStore(to_store, buffer_ptr.addr,
alignment, predicate);
#endif
+ } else {
+#if TVM_LLVM_VERSION >= 110
+ store = builder_->CreateAlignedStore(to_store, buffer_ptr.addr,
llvm::Align(alignment),
+ is_volatile);
+#else
+ store = builder_->CreateAlignedStore(to_store, buffer_ptr.addr,
alignment, is_volatile);
+#endif
+ }
+ return store;
};
// Pass all indices into BufferAccessHelper. In CodeGenLLVM,
// non-flat indices will result in an error in CreateBufferPtr, but
// a subclass may override CreateBufferPtr.
- BufferAccessHelper(op->buffer, op->indices, value_dtype, make_store);
+ BufferAccessHelper(op->buffer, op->indices, op->predicate, value_dtype,
make_store);
}
void CodeGenLLVM::VisitStmt_(const ForNode* op) {
diff --git a/src/target/llvm/codegen_llvm.h b/src/target/llvm/codegen_llvm.h
index 06b36cb183..302a0d97b3 100644
--- a/src/target/llvm/codegen_llvm.h
+++ b/src/target/llvm/codegen_llvm.h
@@ -330,6 +330,10 @@ class CodeGenLLVM : public ExprFunctor<llvm::Value*(const
PrimExpr&)>,
*
* \param indices The indices at which the buffer is being accessed.
*
+ * \param predicate A vector mask of boolean values indicating which lanes
of a
+ * vector are to be accessed. The number lanes of the mask must be equal to
the
+ * number of lanes being accessed.
+ *
* \param value_dtype The datatype to be read from (BufferLoad) or
* written to (BufferStore) the buffer.
*
@@ -342,6 +346,8 @@ class CodeGenLLVM : public ExprFunctor<llvm::Value*(const
PrimExpr&)>,
* stored/loaded. If -1, indicates that the entire type,
* vector or scalar, should be written.
*
+ * - predicate: The predicate mask of the buffer.
+ *
* - alignment: The alignment to be used for the read/write.
*
* - is_volatile: Whether the read/write should be volatile.
@@ -349,9 +355,9 @@ class CodeGenLLVM : public ExprFunctor<llvm::Value*(const
PrimExpr&)>,
* - Should return the generated expression.
*/
void BufferAccessHelper(
- Buffer buffer, Array<PrimExpr> indices, DataType value_dtype,
- std::function<llvm::Instruction*(TypedPointer buffer_ptr, int
subelement_i, int alignment,
- bool is_volatile)>
+ Buffer buffer, Array<PrimExpr> indices, Optional<PrimExpr> predicate,
DataType value_dtype,
+ std::function<llvm::Instruction*(TypedPointer buffer_ptr, int
subelement_i,
+ llvm::Value* predicate, int alignment,
bool is_volatile)>
make_instruction);
// Initialize target
virtual void InitTarget();
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index 344d0392d4..03c3e3af66 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -764,6 +764,7 @@ void CodeGenC::VisitStmt_(const DeclBufferNode* op) {
this->PrintStmt(op->body);
void CodeGenC::VisitExpr_(const BufferLoadNode* op, std::ostream& os) { //
NOLINT(*)
ICHECK_EQ(op->indices.size(), 1) << "Load from non-flat memory not
supported.";
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
supported.";
DataType value_dtype = op->dtype;
PrimExpr index = op->indices[0];
@@ -823,6 +824,7 @@ void CodeGenC::VisitExpr_(const BufferLoadNode* op,
std::ostream& os) { // NOLI
void CodeGenC::VisitStmt_(const BufferStoreNode* op) {
ICHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not
supported.";
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
supported.";
DataType value_dtype = op->value.dtype();
DataType element_dtype = op->buffer->dtype;
diff --git a/src/target/source/codegen_webgpu.cc
b/src/target/source/codegen_webgpu.cc
index ba925056a3..f62e0db7ff 100644
--- a/src/target/source/codegen_webgpu.cc
+++ b/src/target/source/codegen_webgpu.cc
@@ -459,6 +459,7 @@ void CodeGenWebGPU::VisitExpr_(const BufferLoadNode* op,
std::ostream& os) { //
// to ensure correctness in the case of nested-expression
// do not try to lift common printings from each case
ICHECK_EQ(op->indices.size(), 1) << "Load from non-flat memory not
supported.";
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
supported.";
DataType value_dtype = op->dtype;
PrimExpr index = op->indices[0];
@@ -531,6 +532,8 @@ void CodeGenWebGPU::VisitStmt_(const LetStmtNode* op) {
void CodeGenWebGPU::VisitStmt_(const BufferStoreNode* op) {
CHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not supported.";
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
supported.";
+
DataType value_dtype = op->value.dtype();
DataType element_dtype = op->buffer->dtype;
PrimExpr index = op->indices[0];
diff --git a/src/te/operation/create_primfunc.cc
b/src/te/operation/create_primfunc.cc
index 03de68e326..c7dbf3f5e0 100644
--- a/src/te/operation/create_primfunc.cc
+++ b/src/te/operation/create_primfunc.cc
@@ -79,7 +79,7 @@ class BufferSubstituter : public StmtExprMutator {
auto load = Downcast<BufferLoad>(StmtExprMutator::VisitExpr_(op));
auto it = buffer_map_.find(load->buffer.get());
if (it != buffer_map_.end()) {
- return BufferLoad(it->second, load->indices, load->span);
+ return BufferLoad(it->second, load->indices, load->predicate,
load->span);
}
return load;
}
@@ -88,7 +88,7 @@ class BufferSubstituter : public StmtExprMutator {
auto store = Downcast<BufferStore>(StmtExprMutator::VisitStmt_(op));
auto it = buffer_map_.find(store->buffer.get());
if (it != buffer_map_.end()) {
- return BufferStore(it->second, store->value, store->indices,
store->span);
+ return BufferStore(it->second, store->value, store->indices,
store->predicate, store->span);
}
return store;
}
diff --git a/src/tir/analysis/device_constraint_utils.cc
b/src/tir/analysis/device_constraint_utils.cc
index 4554038bc7..40df8b65c2 100644
--- a/src/tir/analysis/device_constraint_utils.cc
+++ b/src/tir/analysis/device_constraint_utils.cc
@@ -254,7 +254,8 @@ class ApplyDeviceConstraintsMutator : public
StmtExprMutator {
Downcast<BufferLoad>(StmtExprMutator::VisitExpr_(buffer_load_node));
Buffer new_buffer = Subst(new_buffer_load->buffer.get());
if (!new_buffer.same_as(new_buffer_load->buffer)) {
- return BufferLoad(new_buffer, new_buffer_load->indices,
new_buffer_load->span);
+ return BufferLoad(new_buffer, new_buffer_load->indices,
new_buffer_load->predicate,
+ new_buffer_load->span);
}
return std::move(new_buffer_load);
}
@@ -293,7 +294,7 @@ class ApplyDeviceConstraintsMutator : public
StmtExprMutator {
Buffer new_buffer = Subst(new_buffer_store->buffer.get());
if (!new_buffer.same_as(new_buffer_store->buffer)) {
return BufferStore(new_buffer, new_buffer_store->value,
new_buffer_store->indices,
- new_buffer_store->span);
+ new_buffer_store->predicate, new_buffer_store->span);
}
return std::move(new_buffer_store);
}
diff --git a/src/tir/contrib/ethosu/passes.cc b/src/tir/contrib/ethosu/passes.cc
index 0c0d47571c..ac1cf0ef11 100644
--- a/src/tir/contrib/ethosu/passes.cc
+++ b/src/tir/contrib/ethosu/passes.cc
@@ -718,7 +718,8 @@ class MergeConstantsMutator : public StmtExprMutator {
buffer->axis_separators,
buffer->span};
old_to_new_read_buffers[buffer.as<BufferNode>()] = new_buffer;
- new_args.push_back(BufferLoad(new_buffer, buffer_load->indices,
buffer_load->span));
+ new_args.push_back(BufferLoad(new_buffer, buffer_load->indices,
buffer_load->predicate,
+ buffer_load->span));
break;
}
case 2: /* length */ {
diff --git a/src/tir/ir/buffer.cc b/src/tir/ir/buffer.cc
index d711879228..0256053331 100644
--- a/src/tir/ir/buffer.cc
+++ b/src/tir/ir/buffer.cc
@@ -399,37 +399,44 @@ Buffer Buffer::GetFlattenedBuffer() const {
}
}
-PrimExpr Buffer::vload(Array<PrimExpr> begin, DataType value_dtype) const {
+PrimExpr Buffer::vload(Array<PrimExpr> begin, DataType value_dtype,
+ Optional<PrimExpr> predicate) const {
// specially handle bool, stored as DataType::Int(8)
const BufferNode* n = operator->();
ICHECK(n != nullptr);
ICHECK(value_dtype.element_of() == n->dtype.element_of() &&
- value_dtype.lanes() % n->dtype.lanes() == 0)
+ value_dtype.get_lanes_or_vscale_factor() % n->dtype.lanes() == 0)
<< "Cannot load " << value_dtype << " from buffer of " << n->dtype;
Array<PrimExpr> indices = begin;
- int factor = value_dtype.lanes() / n->dtype.lanes();
- if (factor > 1) {
- indices.Set(indices.size() - 1, Ramp(indices[indices.size() - 1], 1,
factor));
+ PrimExpr base = indices[indices.size() - 1];
+ if (value_dtype.is_fixed_length_vector()) {
+ int factor = value_dtype.lanes() / n->dtype.lanes();
+ if (factor > 1 && base.dtype().is_scalar()) {
+ indices.Set(indices.size() - 1, Ramp(base, 1, factor));
+ }
}
- return BufferLoad(*this, indices);
+ return BufferLoad(*this, indices, predicate);
}
-Stmt Buffer::vstore(Array<PrimExpr> begin, PrimExpr value) const {
+Stmt Buffer::vstore(Array<PrimExpr> begin, PrimExpr value, Optional<PrimExpr>
predicate) const {
// specially handle bool, stored as DataType::Int(8)
const BufferNode* n = operator->();
ICHECK(n != nullptr);
DataType value_dtype = value.dtype();
ICHECK(value_dtype.element_of() == n->dtype.element_of() &&
- value_dtype.lanes() % n->dtype.lanes() == 0)
+ value_dtype.get_lanes_or_vscale_factor() % n->dtype.lanes() == 0)
<< "Cannot store " << value_dtype << " to buffer of " << n->dtype;
Array<PrimExpr> indices = begin;
- int factor = value_dtype.lanes() / n->dtype.lanes();
- if (factor > 1) {
- indices.Set(indices.size() - 1, Ramp(indices[indices.size() - 1], 1,
factor));
+ PrimExpr base = indices[indices.size() - 1];
+ if (value_dtype.is_fixed_length_vector()) {
+ int factor = value_dtype.lanes() / n->dtype.lanes();
+ if (factor > 1 && base.dtype().is_scalar()) {
+ indices.Set(indices.size() - 1, Ramp(base, 1, factor));
+ }
}
- return BufferStore(*this, value, indices);
+ return BufferStore(*this, value, indices, predicate);
}
String Buffer::scope() const {
diff --git a/src/tir/ir/expr.cc b/src/tir/ir/expr.cc
index 2cd2a698de..1506082003 100644
--- a/src/tir/ir/expr.cc
+++ b/src/tir/ir/expr.cc
@@ -772,24 +772,47 @@ void BufferLoadNode::LegalizeDType() {
}
}
-BufferLoad::BufferLoad(Buffer buffer, Array<PrimExpr> indices, Span span) {
+BufferLoad::BufferLoad(Buffer buffer, Array<PrimExpr> indices,
Optional<PrimExpr> predicate,
+ Span span) {
ICHECK_EQ(buffer->shape.size(), indices.size())
<< "Buffer " << buffer->name << " is " << buffer->shape.size()
<< "-dimensional, cannot be indexed with the " << indices.size()
<< "-dimensional indices provided.";
+ if (predicate.defined()) {
+ DataType predicate_dtype = predicate.value().dtype();
+
+ bool is_index_scalable = indices.empty() ? false :
indices.back().dtype().is_scalable_vector();
+ bool is_predicate_scalable = predicate_dtype.is_scalable_vector();
+ ICHECK_EQ(is_index_scalable, is_predicate_scalable)
+ << "Predicate mask dtype and load indices must both be scalable.";
+
+ int buffer_lanes = buffer->dtype.get_lanes_or_vscale_factor();
+ int index_lanes = indices.empty() ? 1 :
indices.back().dtype().get_lanes_or_vscale_factor();
+ int predicate_lanes = predicate_dtype.get_lanes_or_vscale_factor();
+ ICHECK_EQ(index_lanes * buffer_lanes, predicate_lanes)
+ << "Got a predicate mask with " << predicate_lanes
+ << " lanes, but trying to load a vector with " << index_lanes
+ << " lanes. The number of lanes must match.";
+
+ DataType predicate_element_dtype = predicate_dtype.element_of();
+ ICHECK(predicate_element_dtype.is_bool())
+ << "Predicate mask elements must be boolean values, but got " <<
predicate_element_dtype
+ << ".";
+ }
+
ObjectPtr<BufferLoadNode> node = make_object<BufferLoadNode>();
node->buffer = std::move(buffer);
node->indices = std::move(indices);
+ node->predicate = std::move(predicate);
node->span = std::move(span);
node->LegalizeDType();
data_ = std::move(node);
}
TVM_REGISTER_GLOBAL("tir.BufferLoad")
- .set_body_typed([](Buffer buffer, Array<PrimExpr> indices, Span span) {
- return BufferLoad(buffer, indices, span);
- });
+ .set_body_typed([](Buffer buffer, Array<PrimExpr> indices,
Optional<PrimExpr> predicate,
+ Span span) { return BufferLoad(buffer, indices,
predicate, span); });
TVM_REGISTER_NODE_TYPE(BufferLoadNode);
diff --git a/src/tir/ir/expr_functor.cc b/src/tir/ir/expr_functor.cc
index 089a1d31e7..34b46583d5 100644
--- a/src/tir/ir/expr_functor.cc
+++ b/src/tir/ir/expr_functor.cc
@@ -127,7 +127,7 @@ PrimExpr ExprMutator::VisitExpr_(const BufferLoadNode* op) {
if (indices.same_as(op->indices)) {
return GetRef<PrimExpr>(op);
} else {
- return BufferLoad(op->buffer, indices);
+ return BufferLoad(op->buffer, indices, op->predicate);
}
}
diff --git a/src/tir/ir/stmt.cc b/src/tir/ir/stmt.cc
index 4774471afc..5df76450ff 100644
--- a/src/tir/ir/stmt.cc
+++ b/src/tir/ir/stmt.cc
@@ -458,7 +458,8 @@
TVM_REGISTER_GLOBAL("tir.Evaluate").set_body_typed([](PrimExpr value, Span span)
TVM_REGISTER_NODE_TYPE(EvaluateNode);
// BufferStore
-BufferStore::BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr>
indices, Span span) {
+BufferStore::BufferStore(Buffer buffer, PrimExpr value, Array<PrimExpr>
indices,
+ Optional<PrimExpr> predicate, Span span) {
ICHECK_EQ(buffer->shape.size(), indices.size())
<< "Buffer " << buffer->name << " is " << buffer->shape.size()
<< "-dimensional, cannot be indexed with the " << indices.size()
@@ -476,29 +477,39 @@ BufferStore::BufferStore(Buffer buffer, PrimExpr value,
Array<PrimExpr> indices,
ICHECK(!(is_index_scalable && is_buffer_dtype_scalable))
<< "Index dtype and buffer dtype can't both be scalable.";
- if (is_index_scalable || is_buffer_dtype_scalable) {
- ICHECK(is_value_dtype_scalable) << "Can't store non-scalable data into
scalable buffer";
+ if (predicate.defined()) {
+ bool is_predicate_dtype_scalable =
predicate.value().dtype().is_scalable_vector();
+ ICHECK_EQ(is_value_dtype_scalable, is_predicate_dtype_scalable)
+ << "Predicate mask dtype and value dtype must both be scalable.";
}
- int index_lanes;
- if (indices.empty()) {
- index_lanes = 1;
- } else if (is_index_scalable) {
- index_lanes = indices.back().dtype().vscale_factor();
- } else {
- index_lanes = indices.back().dtype().lanes();
+ if (is_index_scalable || is_buffer_dtype_scalable) {
+ ICHECK(is_value_dtype_scalable) << "Can't store non-scalable data into
scalable buffer";
}
- int buffer_lanes =
- is_buffer_dtype_scalable ? buffer->dtype.vscale_factor() :
buffer->dtype.lanes();
- int value_dtype_lanes =
- is_value_dtype_scalable ? value.dtype().vscale_factor() :
value.dtype().lanes();
+ int index_lanes = indices.empty() ? 1 :
indices.back().dtype().get_lanes_or_vscale_factor();
+ int buffer_lanes = buffer->dtype.get_lanes_or_vscale_factor();
+ int value_dtype_lanes = value.dtype().get_lanes_or_vscale_factor();
ICHECK_EQ(index_lanes * buffer_lanes, value_dtype_lanes)
<< "Cannot store value with " << value_dtype_lanes << ", expected value
with "
<< index_lanes * buffer_lanes << " (" << index_lanes << " index lanes *
" << buffer_lanes
<< " buffer element lanes)";
+ if (predicate.defined()) {
+ DataType predicate_dtype = predicate.value().dtype();
+ int predicate_dtype_lanes = predicate_dtype.get_lanes_or_vscale_factor();
+ ICHECK_EQ(value_dtype_lanes, predicate_dtype_lanes)
+ << "Got a predicate mask with " << predicate_dtype_lanes
+ << " lanes, but trying to store a value with " << value_dtype_lanes
+ << " lanes. The number of lanes must match.";
+
+ DataType predicate_element_dtype = predicate_dtype.element_of();
+ ICHECK(predicate_element_dtype.is_bool())
+ << "Predicate mask elements must be boolean values, but got " <<
predicate_element_dtype
+ << ".";
+ }
+
runtime::DataType buffer_dtype;
if (is_index_scalable || is_buffer_dtype_scalable) {
buffer_dtype = buffer->dtype.with_scalable_vscale_factor(buffer_lanes *
index_lanes);
@@ -517,14 +528,15 @@ BufferStore::BufferStore(Buffer buffer, PrimExpr value,
Array<PrimExpr> indices,
node->buffer = std::move(buffer);
node->value = std::move(value);
node->indices = std::move(indices);
+ node->predicate = std::move(predicate);
node->span = std::move(span);
data_ = std::move(node);
}
TVM_REGISTER_GLOBAL("tir.BufferStore")
- .set_body_typed([](Buffer buffer, PrimExpr value, Array<PrimExpr> indices,
Span span) {
- return BufferStore(buffer, value, indices, span);
- });
+ .set_body_typed([](Buffer buffer, PrimExpr value, Array<PrimExpr> indices,
+ Optional<PrimExpr> predicate,
+ Span span) { return BufferStore(buffer, value, indices,
predicate, span); });
TVM_REGISTER_NODE_TYPE(BufferStoreNode);
diff --git a/src/tir/transforms/inject_rolling_buffer.cc
b/src/tir/transforms/inject_rolling_buffer.cc
index 5f7b9b4156..03f94e3e91 100644
--- a/src/tir/transforms/inject_rolling_buffer.cc
+++ b/src/tir/transforms/inject_rolling_buffer.cc
@@ -257,7 +257,9 @@ class RollingBufferInjector : public StmtExprMutator {
indices.push_back(index);
}
}
- Stmt buffer_store = BufferStore(op->buffer, op->value, indices,
op->span);
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "the inject rolling buffer pass.";
+ Stmt buffer_store = BufferStore(op->buffer, op->value, indices,
op->predicate, op->span);
// Then wrap the BufferStores in some Ifs to avoid recomputing elements
for (size_t i{0}; i < rolling_buffer_info.axis_iter_vars.size(); ++i) {
auto iter_var{rolling_buffer_info.axis_iter_vars[i]};
@@ -293,7 +295,9 @@ class RollingBufferInjector : public StmtExprMutator {
indices.push_back(index);
}
}
- return BufferLoad(op->buffer, indices, op->span);
+ ICHECK(!op->predicate.defined())
+ << "Predicated buffer load is not currently supported in inject
rolling buffer pass.";
+ return BufferLoad(op->buffer, indices, op->predicate, op->span);
} else {
return expr;
}
diff --git a/src/tir/transforms/lower_match_buffer.cc
b/src/tir/transforms/lower_match_buffer.cc
index 700587fe0e..3c2c6b67e6 100644
--- a/src/tir/transforms/lower_match_buffer.cc
+++ b/src/tir/transforms/lower_match_buffer.cc
@@ -97,6 +97,8 @@ class MatchBufferLower : public StmtExprMutator {
auto n = CopyOnWrite(op);
n->indices = ConvertIndices(MatchBufferRegion(buffer, source),
op->indices);
n->buffer = source->buffer;
+ ICHECK(!op->predicate.defined())
+ << "Predicated buffer store is not currently supported in lower
match buffer pass.";
return Stmt(n);
}
}
@@ -113,6 +115,8 @@ class MatchBufferLower : public StmtExprMutator {
const Buffer& buffer = (*it).first;
const BufferRegion& source = (*it).second;
Array<PrimExpr> indices = ConvertIndices(MatchBufferRegion(buffer,
source), op->indices);
+ ICHECK(!op->predicate.defined())
+ << "Predicated buffer load is not currently supported in lower match
buffer pass.";
return BufferLoad(source->buffer, indices);
}
}
diff --git a/src/tir/transforms/manifest_shared_memory_local_stage.cc
b/src/tir/transforms/manifest_shared_memory_local_stage.cc
index 619a9f0a9e..885d591713 100644
--- a/src/tir/transforms/manifest_shared_memory_local_stage.cc
+++ b/src/tir/transforms/manifest_shared_memory_local_stage.cc
@@ -67,6 +67,8 @@ class IntermediateStageRewriter {
Stmt local_stage = MakeLocalStage(block, new_buffer, buffer_indices,
relaxed_loops, store);
// Step 3: Create BufferLoad from the intermediate buffer
+ ICHECK(!store->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "manifest shared memory local stage
pass.";
BufferLoad new_buffer_load = BufferLoad(new_buffer, buffer_indices);
BufferStore new_buffer_store = Downcast<BufferStore>(block->body);
new_buffer_store.CopyOnWrite()->value = new_buffer_load;
diff --git a/src/tir/transforms/remove_no_op.cc
b/src/tir/transforms/remove_no_op.cc
index bc606aa0b7..3b418aac0c 100644
--- a/src/tir/transforms/remove_no_op.cc
+++ b/src/tir/transforms/remove_no_op.cc
@@ -213,7 +213,8 @@ class NoOpRemover : public arith::IRMutatorWithAnalyzer {
// A write whose destination is known to already contain the
// values to be written is a no-op.
// PrimExpr stores_existing_value = store->value ==
BufferLoad(store->buffer, store->indices);
- PrimExpr stores_existing_value = store->value - BufferLoad(store->buffer,
store->indices) == 0;
+ PrimExpr stores_existing_value =
+ store->value - BufferLoad(store->buffer, store->indices,
store->predicate) == 0;
if (touch_pattern_.has_value()) {
Stmt context_arg = context_ ? GetRef<Stmt>(context_) : Stmt(store);
stores_existing_value =
diff --git a/src/tir/transforms/remove_weight_layout_rewrite_block.cc
b/src/tir/transforms/remove_weight_layout_rewrite_block.cc
index 05b636f114..e8d89bfb57 100644
--- a/src/tir/transforms/remove_weight_layout_rewrite_block.cc
+++ b/src/tir/transforms/remove_weight_layout_rewrite_block.cc
@@ -196,7 +196,7 @@ class AllocateConstRewrite : public StmtExprMutator {
op->buffer->elem_offset, it->second->name_hint,
op->buffer->data_alignment,
op->buffer->offset_factor, op->buffer->buffer_type);
new_load_buf_[op->buffer->data.get()] = new_buffer;
- return BufferLoad(new_buffer, op->indices);
+ return BufferLoad(new_buffer, op->indices, op->predicate);
}
return ExprMutator::VisitExpr_(op);
}
diff --git a/src/tir/transforms/storage_flatten.cc
b/src/tir/transforms/storage_flatten.cc
index c51dfd7913..06554f5f1d 100644
--- a/src/tir/transforms/storage_flatten.cc
+++ b/src/tir/transforms/storage_flatten.cc
@@ -730,7 +730,7 @@ class ThreadScopePropagate : public StmtExprMutator {
auto it = buf_remap_.find(op->buffer->data);
if (it != buf_remap_.end()) {
- return BufferLoad(it->second, op->indices, op->span);
+ return BufferLoad(it->second, op->indices, op->predicate, op->span);
} else {
return expr;
}
@@ -743,7 +743,7 @@ class ThreadScopePropagate : public StmtExprMutator {
auto it = buf_remap_.find(op->buffer->data);
if (it != buf_remap_.end()) {
- return BufferStore(it->second, op->value, op->indices, op->span);
+ return BufferStore(it->second, op->value, op->indices, op->predicate,
op->span);
} else {
return stmt;
}
@@ -938,8 +938,11 @@ class BufferBindUnwrapper : public StmtExprMutator {
const BufferEntry& e = GetBufferEntry(op->buffer);
if (e.remap) {
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
currently supported in "
+ "storage flatten pass.";
return BufferLoad(e.remap->target,
- remap_indices(op->indices, e.remap->begins,
e.remap->extents), op->span);
+ remap_indices(op->indices, e.remap->begins,
e.remap->extents),
+ op->predicate, op->span);
} else {
return expr;
}
@@ -952,8 +955,11 @@ class BufferBindUnwrapper : public StmtExprMutator {
const BufferEntry& e = GetBufferEntry(op->buffer);
if (e.remap) {
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "storage flatten pass.";
return BufferStore(e.remap->target, op->value,
- remap_indices(op->indices, e.remap->begins,
e.remap->extents), op->span);
+ remap_indices(op->indices, e.remap->begins,
e.remap->extents),
+ op->predicate, op->span);
} else {
return stmt;
}
@@ -1418,7 +1424,9 @@ class StorageFlattener : public StmtExprMutator {
auto flattened_indices = e.buffer->ElemOffset(op->indices);
- Stmt body = BufferStore(e.flattened_buffer, value, flattened_indices,
op->span);
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "storage flatten pass.";
+ Stmt body = BufferStore(e.flattened_buffer, value, flattened_indices,
op->predicate, op->span);
if (create_bound_attributes_ && ShapeIsValid(e.buffer->shape)) {
shape_collector_.push_back(std::make_pair(e.buffer->data,
e.buffer->shape));
}
@@ -1573,8 +1581,10 @@ class StorageFlattener : public StmtExprMutator {
shape_collector_.push_back(std::make_pair(e.buffer->data,
e.buffer->shape));
}
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
currently supported in "
+ "storage flatten pass.";
auto flattened_indices = e.buffer->ElemOffset(op->indices);
- PrimExpr val = BufferLoad(e.flattened_buffer, flattened_indices, op->span);
+ PrimExpr val = BufferLoad(e.flattened_buffer, flattened_indices,
op->predicate, op->span);
if (op->dtype == DataType::Bool()) {
ICHECK_EQ(e.flattened_buffer->dtype, DataType::Int(8))
diff --git a/src/tir/transforms/unsupported_dtype_legalize.cc
b/src/tir/transforms/unsupported_dtype_legalize.cc
index 5a14beb6dc..c75ecf77e7 100644
--- a/src/tir/transforms/unsupported_dtype_legalize.cc
+++ b/src/tir/transforms/unsupported_dtype_legalize.cc
@@ -330,6 +330,8 @@ class ComputeLegalizer : public StmtExprMutator {
ICHECK(MatchDType(value->dtype));
value = cast(new_buf->dtype.with_lanes(value.dtype().lanes()), value);
}
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "data type legalizer pass.";
return BufferStore(new_buf, value, indices);
}
}
@@ -401,6 +403,8 @@ class ComputeLegalizer : public StmtExprMutator {
if (new_buf.same_as(op->buffer)) {
return ret;
} else {
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
currently supported in "
+ "data type legalizer pass.";
return BufferLoad(new_buf, op->indices);
}
}
@@ -562,6 +566,8 @@ class StorageLegalizer : public StmtExprMutator {
if (MatchDType(op->value.dtype())) {
ICHECK(new_buf->dtype.is_uint());
}
+ ICHECK(!op->predicate.defined()) << "Predicated buffer store is not
currently supported in "
+ "data type legalizer pass.";
return BufferStore(new_buf, value, indices);
}
}
@@ -595,6 +601,8 @@ class StorageLegalizer : public StmtExprMutator {
if (new_buf.same_as(op->buffer)) {
return ret;
} else {
+ ICHECK(!op->predicate.defined()) << "Predicated buffer load is not
currently supported in "
+ "data type legalizer pass.";
return BufferLoad(new_buf, op->indices);
}
}
diff --git a/src/tir/transforms/vectorize_loop.cc
b/src/tir/transforms/vectorize_loop.cc
index c4dde01b8f..aa62d58505 100644
--- a/src/tir/transforms/vectorize_loop.cc
+++ b/src/tir/transforms/vectorize_loop.cc
@@ -72,6 +72,126 @@ inline PrimExpr BroadcastTo(PrimExpr e, int lanes, bool
is_scalable) {
return Broadcast(e, CreateNewLanes(is_scalable, lanes));
}
+bool EnableBufferLevelPredication(Target target) {
+ transform::PassContext pass_ctx = transform::PassContext::Current();
+ Optional<Bool> enable_buffer_predication =
+ pass_ctx->GetConfig<Bool>("tir.enable_buffer_level_predication");
+ if (enable_buffer_predication.defined()) {
+ return enable_buffer_predication.value();
+ }
+
+ // Use buffer-level predication by default for AArch64 SVE targets
+ return arith::TargetHasSVE(target);
+}
+
+/*!
+ * \brief A pass that tries to rewrite buffer accesses (loads and stores) with
a
+ * predicate expression where possible.
+ *
+ * \note For now we start with a minimal case targeting block-level predicates
+ * produced by the split schedule primitive, with the potential for predicating
+ * more complex terms in the future if needed.
+ *
+ * \example
+ * Before:
+ * for i_0 in T.serial(4):
+ * for i_1 in T.vectorized(4):
+ * if i_0 * 4 + i_1 < 14:
+ * B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
+ *
+ * After:
+ * for i_0 in T.serial(4):
+ * predicate = T.get_active_lane_mask("uint1x4", i_0 * 4, 14)
+ * A_load = T.meta_var(A.vload([T.Ramp(i_0 * 4, 1, 4)], predicate=predicate))
+ * B.vstore([T.Ramp(i_0 * 4, 1, 4)], A_load, predicate=predicate)
+ */
+class TryPredicateBufferAccesses : public StmtExprMutator {
+ public:
+ TryPredicateBufferAccesses() {}
+
+ /*!
+ * \brief Run the pass to try to exact predicates.
+ * \param stmt - The statement containing buffer accesses (loads and stores)
+ * we want to attempt to predicate.
+ * \param condition - The conditional expression (block-level predicate)
+ * that we will try to remove.
+ * \return pair<success, stmt> - Boolean value for success/failure, the
rewritten
+ * stmt if successful.
+ */
+ std::pair<bool, Stmt> Run(Stmt stmt, PrimExpr condition) {
+ // Check that the condition provided is of the form a < b, for now.
+ if (!condition->IsInstance<LTNode>()) {
+ return {false, stmt};
+ }
+
+ LT lt = Downcast<LT>(condition);
+
+ // Check the form of the vectorized condition, we're expecting
+ // Ramp(...) < Broadcast(...)
+ if (!lt->a->IsInstance<RampNode>() || !lt->b->IsInstance<BroadcastNode>())
{
+ return {false, stmt};
+ }
+
+ base_ = Downcast<Ramp>(lt->a)->base;
+ limit_ = Downcast<Broadcast>(lt->b)->value;
+
+ // Now we can try to predicate
+ Stmt predicated_stmt = StmtExprMutator::operator()(std::move(stmt));
+ if (num_accesses_analyzed_ > 0 && num_accesses_analyzed_ ==
num_accesses_rewritten_) {
+ return {true, predicated_stmt};
+ }
+ return {false, stmt};
+ }
+
+ private:
+ PrimExpr VisitExpr_(const BufferLoadNode* op) final {
+ auto load = Downcast<BufferLoad>(StmtExprMutator::VisitExpr_(op));
+ return TryPredicateBufferAccess(load);
+ }
+
+ Stmt VisitStmt_(const BufferStoreNode* op) final {
+ auto store = Downcast<BufferStore>(StmtExprMutator::VisitStmt_(op));
+ return TryPredicateBufferAccess(store);
+ }
+
+ template <typename AccessNode>
+ AccessNode TryPredicateBufferAccess(AccessNode node) {
+ num_accesses_analyzed_ += 1;
+
+ // Do not try to predicate non-vectorized accesses
+ Array<PrimExpr> indices = node->indices;
+ if (!indices.size() || !indices[0]->IsInstance<RampNode>()) {
+ return node;
+ }
+ Ramp ramp = Downcast<Ramp>(node->indices[0]);
+
+ // The vectorized access pattern must match the base of the predicate
+ if (!tvm::StructuralEqual()(ramp->base, base_)) {
+ return node;
+ }
+
+ DataType buf_predicate_dtype =
+ DataType(DataType::kUInt, 1, ramp->dtype.get_lanes_or_vscale_factor(),
+ ramp->dtype.is_scalable_vector());
+ Call lane_mask = Call(buf_predicate_dtype,
builtin::get_active_lane_mask(), {base_, limit_});
+
+ num_accesses_rewritten_ += 1;
+ auto writer = node.CopyOnWrite();
+ writer->predicate = lane_mask;
+ return node;
+ }
+
+ /*! \brief The variable base expr of the predicate. */
+ PrimExpr base_;
+ /*! \brief The limit of the predicate. The expr specifies the upper bound of
the base's
+ * evaluated value. */
+ PrimExpr limit_;
+ /*! \brief The number of buffer accesses in the stmt we will analyze. */
+ size_t num_accesses_analyzed_ = 0;
+ /*! \brief The number of buffer accesses rewritten with predicates. */
+ size_t num_accesses_rewritten_ = 0;
+};
+
// Rewrite vectorized allocation access
// This is necessary for making each vector component containing its own
workspace.
// Originates from Halide's loop vectorizer
@@ -171,7 +291,8 @@ class Vectorizer : public StmtMutator, public
ExprFunctor<PrimExpr(const PrimExp
using ExprFunctor::VisitExpr;
using StmtMutator::operator();
- Vectorizer(Var var, PrimExpr var_lanes) : var_(var), var_lanes_(var_lanes) {
+ Vectorizer(Var var, PrimExpr var_lanes, Target target)
+ : var_(var), var_lanes_(var_lanes), target_(target) {
ramp_ = Ramp(IntImm(var->dtype, 0), IntImm(var->dtype, 1), var_lanes);
}
@@ -555,14 +676,26 @@ class Vectorizer : public StmtMutator, public
ExprFunctor<PrimExpr(const PrimExp
Stmt VisitStmt_(const IfThenElseNode* op) final {
ICHECK(!op->condition.dtype().is_scalable_or_fixed_length_vector());
PrimExpr condition = this->VisitExpr(op->condition);
- if (condition.dtype().is_scalable_or_fixed_length_vector()) {
- return Scalarize(GetRef<Stmt>(op));
- }
Stmt then_case = this->VisitStmt(op->then_case);
Optional<Stmt> else_case = NullOpt;
if (op->else_case) {
else_case = this->VisitStmt(op->else_case.value());
}
+
+ // Check if we can rewrite the condition with predicated buffers
+ if (EnableBufferLevelPredication(target_) &&
+ condition.dtype().is_scalable_or_fixed_length_vector() &&
!else_case.defined()) {
+ std::pair<bool, Stmt> success_stmt_pair =
+ TryPredicateBufferAccesses().Run(then_case, condition);
+ bool can_remove_if_then_else = success_stmt_pair.first;
+ if (can_remove_if_then_else) {
+ return success_stmt_pair.second;
+ }
+ }
+
+ if (condition.dtype().is_scalable_or_fixed_length_vector()) {
+ return Scalarize(GetRef<Stmt>(op));
+ }
if (condition.same_as(op->condition) && then_case.same_as(op->then_case) &&
else_case.same_as(op->else_case)) {
return GetRef<Stmt>(op);
@@ -659,6 +792,8 @@ class Vectorizer : public StmtMutator, public
ExprFunctor<PrimExpr(const PrimExp
std::unordered_map<Var, PrimExpr> let_binding_;
// vectorizable property
OpAttrMap<TVectorizable> op_vectorizable_ =
Op::GetAttrMap<TVectorizable>("TVectorizable");
+ /*! \brief The current target context. */
+ Target target_;
// mutate array, with given lane requirement
// when finished, p_lane updates the lane requirement.
@@ -728,22 +863,41 @@ class Vectorizer : public StmtMutator, public
ExprFunctor<PrimExpr(const PrimExp
class LoopVectorizer : public StmtMutator {
public:
+ explicit LoopVectorizer(DictAttrs attrs) {
+ if (auto opt_target = attrs.GetAttr<Target>(tvm::attr::kTarget)) {
+ target_ = opt_target.value();
+ }
+ }
+
Stmt VisitStmt_(const ForNode* op) final {
if (op->kind == ForKind::kVectorized) {
auto* extent_as_int = op->extent.as<IntImmNode>();
if (!extent_as_int || extent_as_int->value < 1) {
bool is_scalable_expr = CheckContains::ExprContains(op->extent,
arith::IsVScaleCall);
- ICHECK(is_scalable_expr && arith::TargetHasSVE())
- << "Failed to vectorize loop with extent " << op->extent << " for
target "
- << Target::Current();
+ ICHECK(is_scalable_expr && arith::TargetHasSVE(target_))
+ << "Failed to vectorize loop with extent " << op->extent << " for
target " << target_;
}
ICHECK(is_zero(op->min));
- return Vectorizer(op->loop_var, op->extent)(op->body);
+ return Vectorizer(op->loop_var, op->extent, target_)(op->body);
} else {
return StmtMutator::VisitStmt_(op);
}
}
+
+ Stmt VisitStmt_(const AttrStmtNode* op) final {
+ if (op->attr_key == tvm::attr::kTarget) {
+ Target previous_target = target_;
+ target_ = op->node.as<Target>().value();
+ Stmt new_op = StmtMutator::VisitStmt_(op);
+ target_ = previous_target;
+ return new_op;
+ }
+ return StmtMutator::VisitStmt_(op);
+ }
+
+ private:
+ Target target_ = Target::Current();
};
class VectorizeSkipper : public StmtMutator {
@@ -768,7 +922,7 @@ Pass VectorizeLoop(bool enable_vectorize) {
auto pass_func = [=](PrimFunc f, IRModule m, PassContext ctx) {
auto* n = f.CopyOnWrite();
if (enable_vectorize) {
- n->body = LoopVectorizer()(std::move(n->body));
+ n->body = LoopVectorizer(n->attrs)(std::move(n->body));
} else {
n->body = VectorizeSkipper()(std::move(n->body));
}
diff --git a/tests/python/codegen/test_target_codegen.py
b/tests/python/codegen/test_target_codegen.py
new file mode 100644
index 0000000000..bae15b5377
--- /dev/null
+++ b/tests/python/codegen/test_target_codegen.py
@@ -0,0 +1,92 @@
+# 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.
+
+import pytest
+
+import tvm
+from tvm.script import tir as T
+
+
[email protected]_targets("c")
+def test_buffer_store_predicate_not_supported(target):
+ @T.prim_func
+ def func(b: T.handle):
+ B = T.match_buffer(b, (8,), "float32")
+ B.vstore([T.Ramp(0, 2, 4)], T.Broadcast(1.0, 4),
predicate=T.Broadcast(T.bool(True), 4))
+
+ err_msg = "Predicated buffer store is not supported."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target(target):
+ tvm.build(func)
+
+
[email protected]_targets("cuda", "opencl", "metal", "rocm", "vulkan
-from_device=0")
+def test_buffer_store_predicate_not_supported_gpu(target):
+ @T.prim_func
+ def func(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (2, 3), "float32")
+ B = T.match_buffer(b, (6,), "float32")
+ T.func_attr({"global_symbol": "main"})
+ for i_0 in T.thread_binding(3, thread="threadIdx.x"):
+ B.vstore(
+ [T.Ramp(i_0, 1, 4)], T.Broadcast(1.0, 4),
predicate=T.Broadcast(T.bool(True), 4)
+ )
+
+ err_msg = "Predicated buffer store is not supported."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target(target):
+ tvm.build(func)
+
+
[email protected]_targets("c")
+def test_buffer_load_predicate_not_supported(target):
+ @T.prim_func
+ def func(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (8,), "float32")
+ B = T.match_buffer(b, (8,), "float32")
+ for i_0 in range(4):
+ B.vstore(
+ [T.Ramp(0, 2, 4)],
+ A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4)),
+ )
+
+ err_msg = "Predicated buffer load is not supported."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target(target):
+ tvm.build(func)
+
+
[email protected]_targets("cuda", "opencl", "metal", "rocm", "vulkan
-from_device=0")
+def test_buffer_load_predicate_not_supported_gpu(target):
+ @T.prim_func
+ def func(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (8,), "float32")
+ B = T.match_buffer(b, (8,), "float32")
+ for i_0 in T.thread_binding(3, thread="threadIdx.x"):
+ B.vstore(
+ [T.Ramp(0, 2, 4)],
+ A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4)),
+ )
+
+ err_msg = "Predicated buffer load is not supported."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target(target):
+ tvm.build(func)
+
+
+if __name__ == "__main__":
+ tvm.testing.main()
diff --git a/tests/python/codegen/test_target_codegen_aarch64.py
b/tests/python/codegen/test_target_codegen_aarch64.py
index f73d96e7c9..251e625b81 100644
--- a/tests/python/codegen/test_target_codegen_aarch64.py
+++ b/tests/python/codegen/test_target_codegen_aarch64.py
@@ -771,7 +771,7 @@ def test_get_active_lane_mask():
def before(a: T.handle):
A = T.match_buffer(a, (30,), "int1")
for i in range(T.ceildiv(30, T.vscale() * 4)):
- A[i : i + T.vscale() * 4] =
T.get_active_lane_mask("int1xvscalex4", i, 30)
+ A[i : i + T.vscale() * 4] =
T.get_active_lane_mask("uint1xvscalex4", i, 30)
with tvm.target.Target(target):
out = tvm.build(before)
@@ -780,5 +780,31 @@ def test_get_active_lane_mask():
assert "get.active.lane.mask" in ll
[email protected](
+ llvm_version_major() < 11,
+ reason="Vscale and get.active.lane.mask are not supported in earlier
versions of LLVM",
+)
+def test_predicated_scalable_buffer():
+ target = "llvm -mtriple=aarch64-linux-gnu -mattr=+sve"
+
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(16, 4 * T.vscale())):
+ for i_1 in T.vectorized(4 * T.vscale()):
+ if i_0 * 4 * T.vscale() + i_1 < 14:
+ B[i_0 * 4 * T.vscale() + i_1] = A[i_0 * 4 * T.vscale() +
i_1] + 1.0
+
+ with tvm.target.Target(target):
+ out = tvm.build(before)
+
+ ll = out.get_source("ll")
+ assert "get.active.lane.mask" in ll
+ assert "llvm.masked.load" in ll
+ assert "llvm.masked.store" in ll
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/codegen/test_target_codegen_llvm.py
b/tests/python/codegen/test_target_codegen_llvm.py
index f1316ae3ce..f50d63878e 100644
--- a/tests/python/codegen/test_target_codegen_llvm.py
+++ b/tests/python/codegen/test_target_codegen_llvm.py
@@ -1109,5 +1109,34 @@ def test_call_extern_returning_void():
built = tvm.build(func, target="llvm")
+def test_invalid_volatile_masked_buffer_load():
+ @T.prim_func
+ def func(b: T.handle):
+ B = T.match_buffer(b, [4])
+ a = T.allocate([4], "float32", scope="global")
+ T.attr(a, "volatile_scope", 1)
+ A = T.Buffer([4], data=a)
+ B[0:4] = A.vload([T.Ramp(0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4))
+
+ err_msg = "The masked load intrinsic does not support declaring load as
volatile."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target("llvm"):
+ tvm.build(func)
+
+
+def test_invalid_volatile_masked_buffer_store():
+ @T.prim_func
+ def func():
+ a = T.allocate([4], "float32", scope="global")
+ T.attr(a, "volatile_scope", 1)
+ A = T.Buffer([4], data=a)
+ A.vstore([T.Ramp(0, 1, 4)], T.Broadcast(0.0, 4),
predicate=T.Broadcast(T.bool(True), 4))
+
+ err_msg = "The masked store intrinsic does not support declaring store as
volatile."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ with tvm.target.Target("llvm"):
+ tvm.build(func)
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/relay/test_json_compact.py
b/tests/python/relay/test_json_compact.py
index d4fa17bf8f..65381a0eb9 100644
--- a/tests/python/relay/test_json_compact.py
+++ b/tests/python/relay/test_json_compact.py
@@ -348,5 +348,99 @@ def test_v0_16_ramp_broadcast_lanes():
assert graph.value.lanes == 12
+def test_v0_17_load_store_predicate():
+ json_graph_v0_16 = {
+ "root": 1,
+ "nodes": [
+ {"type_key": ""},
+ {
+ "type_key": "tir.BufferStore",
+ "attrs": {
+ "buffer": "2",
+ "indices": "19",
+ "predicate": "0",
+ "span": "0",
+ "value": "13",
+ },
+ },
+ {
+ "type_key": "tir.Buffer",
+ "attrs": {
+ "axis_separators": "11",
+ "buffer_type": "1",
+ "data": "3",
+ "data_alignment": "64",
+ "dtype": "float32",
+ "elem_offset": "12",
+ "name": "4",
+ "offset_factor": "1",
+ "shape": "8",
+ "span": "0",
+ "strides": "10",
+ },
+ },
+ {
+ "type_key": "tir.Var",
+ "attrs": {"dtype": "handle", "name": "4", "span": "0",
"type_annotation": "5"},
+ },
+ {"type_key": "runtime.String"},
+ {"type_key": "PointerType", "attrs": {"element_type": "6",
"storage_scope": "7"}},
+ {"type_key": "PrimType", "attrs": {"dtype": "float32"}},
+ {"type_key": "runtime.String", "repr_str": "global"},
+ {"type_key": "Array", "data": [9]},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "8"}},
+ {"type_key": "Array"},
+ {"type_key": "Array"},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "0"}},
+ {
+ "type_key": "tir.BufferLoad",
+ "attrs": {
+ "buffer": "2",
+ "dtype": "float32x4",
+ "indices": "14",
+ "predicate": "0",
+ "span": "0",
+ },
+ },
+ {"type_key": "Array", "data": [15]},
+ {
+ "type_key": "tir.Ramp",
+ "attrs": {
+ "base": "16",
+ "dtype": "int32x4",
+ "lanes": "18",
+ "span": "0",
+ "stride": "17",
+ },
+ },
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "0"}},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "1"}},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "4"}},
+ {"type_key": "Array", "data": [20]},
+ {
+ "type_key": "tir.Ramp",
+ "attrs": {
+ "base": "21",
+ "dtype": "int32x4",
+ "lanes": "23",
+ "span": "0",
+ "stride": "22",
+ },
+ },
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "4"}},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "1"}},
+ {"type_key": "IntImm", "attrs": {"dtype": "int32", "span": "0",
"value": "4"}},
+ ],
+ "b64ndarrays": [],
+ "attrs": {"tvm_version": "0.16.0"},
+ }
+
+ expr = tvm.ir.load_json(json.dumps(json_graph_v0_16))
+ buffer_store = expr
+ buffer_load = buffer_store.value
+ assert not buffer_store.predicate
+ assert not buffer_load.predicate
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/tir-base/test_tir_nodes.py
b/tests/python/tir-base/test_tir_nodes.py
index 31a1317e68..eeedae1f12 100644
--- a/tests/python/tir-base/test_tir_nodes.py
+++ b/tests/python/tir-base/test_tir_nodes.py
@@ -468,6 +468,75 @@ def test_buffer_store_scalable_vec():
assert store.value.dtype == "int32xvscalex4"
+def test_buffer_store_predicate_invalid_scalability():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ value = tvm.tir.expr.Broadcast(1, 4 * tvm.tir.vscale())
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(tvm.tir.IntImm("int1", 1), 4)
+
+ err_msg = "Predicate mask dtype and value dtype must both be scalable."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferStore(b, value, [index], predicate)
+
+
+def test_buffer_store_predicate_invalid_lanes():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ value = tvm.tir.expr.Broadcast(1, 4 * tvm.tir.vscale())
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(tvm.tir.IntImm("int1", 1), 8 *
tvm.tir.vscale())
+
+ err_msg = (
+ "Got a predicate mask with 8 lanes, but trying to store a "
+ "value with 4 lanes. The number of lanes must match."
+ )
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferStore(b, value, [index], predicate)
+
+
+def test_buffer_store_predicate_elements_invalid_type():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ value = tvm.tir.expr.Broadcast(1, 4 * tvm.tir.vscale())
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(1, 4 * tvm.tir.vscale())
+
+ err_msg = "Predicate mask elements must be boolean values, but got int32."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferStore(b, value, [index], predicate)
+
+
+def test_buffer_load_predicate_elements_invalid_type():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(1, 4 * tvm.tir.vscale())
+
+ err_msg = "Predicate mask elements must be boolean values, but got int32."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferLoad(b, [index], predicate)
+
+
+def test_buffer_store_predicate_invalid_scalability():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(tvm.tir.IntImm("int1", 1), 4)
+
+ err_msg = "Predicate mask dtype and load indices must both be scalable."
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferLoad(b, [index], predicate)
+
+
+def test_buffer_store_predicate_invalid_lanes():
+ b = tvm.tir.decl_buffer((24,), "int32")
+ index = tvm.tir.expr.Ramp(0, 1, 4 * tvm.tir.vscale())
+ predicate = tvm.tir.expr.Broadcast(tvm.tir.IntImm("int1", 1), 8 *
tvm.tir.vscale())
+
+ err_msg = (
+ "Got a predicate mask with 8 lanes, but trying to load a "
+ "vector with 4 lanes. The number of lanes must match."
+ )
+ with pytest.raises(tvm.TVMError, match=err_msg):
+ tvm.tir.BufferLoad(b, [index], predicate)
+
+
def test_scalable_vec_cast():
b = tvm.tir.decl_buffer((24,), "float32")
value = tvm.tir.expr.Broadcast(1, 12 *
tvm.tir.vscale()).astype("float32xvscalex12")
diff --git a/tests/python/tir-transform/test_tir_transform_vectorize.py
b/tests/python/tir-transform/test_tir_transform_vectorize.py
index de5453eb5c..e02c227b05 100644
--- a/tests/python/tir-transform/test_tir_transform_vectorize.py
+++ b/tests/python/tir-transform/test_tir_transform_vectorize.py
@@ -125,12 +125,15 @@ def test_vectorize_vector_scalable_error4():
tvm.tir.transform.VectorizeLoop()(Module)
[email protected]("extent, target", [(4, simple_target), (T.vscale() *
4, sve_target)])
-def test_vectorize_with_if(extent, target):
+def test_vectorize_with_if():
+ extent = 4
+ target = simple_target
+
@I.ir_module
class Before:
@T.prim_func
- def main(A: T.Buffer((25,), "float32"), n: T.int32, x: T.int32):
+ def main(a: T.handle, n: T.int32, x: T.int32):
+ A = T.match_buffer(a, (25,), "float32")
for i in T.vectorized(extent):
if x < n:
A[i] = A[i] + T.float32(1)
@@ -141,7 +144,8 @@ def test_vectorize_with_if(extent, target):
@I.ir_module
class After:
@T.prim_func
- def main(A: T.Buffer((25,), "float32"), n: T.int32, x: T.int32):
+ def main(a: T.handle, n: T.int32, x: T.int32):
+ A = T.match_buffer(a, (25,), "float32")
if x < n:
A[T.Ramp(0, 1, extent)] = A[T.Ramp(0, 1, extent)] +
T.Broadcast(
T.float32(1), extent
@@ -156,6 +160,43 @@ def test_vectorize_with_if(extent, target):
tvm.ir.assert_structural_equal(mod, After)
+def test_vectorize_if_scalable_extent():
+ extent = T.vscale() * 4
+ target = sve_target
+
+ @I.ir_module
+ class Before:
+ @T.prim_func
+ def main(a: T.handle, n: T.int32, x: T.int32):
+ A = T.match_buffer(a, (25,), "float32")
+ for i in T.vectorized(extent):
+ if x < n:
+ A[i] = A[i] + T.float32(1)
+ else:
+ if i < n:
+ A[i] = T.float32(2)
+
+ @I.ir_module
+ class After:
+ @T.prim_func
+ def main(a: T.handle, n: T.int32, x: T.int32):
+ A = T.match_buffer(a, (25,), "float32")
+ if x < n:
+ A[T.Ramp(0, 1, extent)] = A[T.Ramp(0, 1, extent)] +
T.Broadcast(
+ T.float32(1), extent
+ )
+ else:
+ A.vstore(
+ [T.Ramp(0, 1, T.vscale() * 4)],
+ T.Broadcast(T.float32(2), T.vscale() * 4),
+ predicate=T.get_active_lane_mask("uint1xvscalex4", 0, n),
+ )
+
+ with tvm.target.Target(target):
+ mod = tvm.tir.transform.VectorizeLoop()(Before)
+ tvm.ir.assert_structural_equal(mod, After)
+
+
def test_vectorize_with_if_cond_int64():
m = te.size_var("m", dtype="int64")
A = te.placeholder((m,), name="A", dtype="float32")
@@ -488,5 +529,243 @@ def test_illegal_vscale_in_non_sve_compilation():
tvm.tir.transform.VectorizeLoop()(Mod)
+def test_vectorize_and_predicate_all_buffer_loads_stores():
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True)})
+ for i_0 in range(4):
+ load_a = T.meta_var(
+ A.vload(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+ )
+ add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
+ B.vstore(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ add_1,
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+
+ mod = tvm.IRModule.from_expr(before)
+ with
tvm.transform.PassContext(config={"tir.enable_buffer_level_predication": True}):
+ after = tvm.tir.transform.VectorizeLoop()(mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def test_vectorize_and_predicate_some_buffer_loads_stores():
+ # Currently revert to scalarizing the block if not all accesses
+ # have been predicated, otherwise incorrect code is generated.
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ B[i_0 * 4 + i_1] = A[i_0] + 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True)})
+ for i_0, i_1_s in T.grid(4, 4):
+ if i_0 * 4 + i_1_s < 14:
+ B[i_0 * 4 + i_1_s] = A[i_0] + T.float32(1)
+
+ mod = tvm.IRModule.from_expr(before)
+ with
tvm.transform.PassContext(config={"tir.enable_buffer_level_predication": True}):
+ after = tvm.tir.transform.VectorizeLoop()(mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def test_vectorize_and_predicate_multiple_access_statements():
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ A[i_0 * 4 + i_1] = 2.0
+ B[i_0 * 4 + i_1] = 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True)})
+ for i_0 in range(4):
+ A.vstore(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ T.Broadcast(T.float32(2), 4),
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+ B.vstore(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ T.Broadcast(T.float32(1), 4),
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+
+ before_mod = tvm.IRModule.from_expr(before)
+ with
tvm.transform.PassContext(config={"tir.enable_buffer_level_predication": True}):
+ after = tvm.tir.transform.VectorizeLoop()(before_mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def test_vectorize_and_predicate_invalid_conditions():
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 > 14:
+ A[i_0 * 4 + i_1] = 2.0
+ if 14 < i_0 * 4 + i_1:
+ A[i_0 * 4 + i_1] = 2.0
+ if i_0 * 4 + i_1 < i_0 * 4 + i_1:
+ A[i_0 * 4 + i_1] = 2.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True)})
+ for i_0 in range(4):
+ for i_1_s in range(4):
+ if i_0 * 4 + i_1_s > 14:
+ A[i_0 * 4 + i_1_s] = T.float32(2)
+ for i_1_s in range(4):
+ if 14 < i_0 * 4 + i_1_s:
+ A[i_0 * 4 + i_1_s] = T.float32(2)
+ for i_1_s in range(4):
+ if i_0 * 4 + i_1_s < i_0 * 4 + i_1_s:
+ A[i_0 * 4 + i_1_s] = T.float32(2)
+
+ before_mod = tvm.IRModule.from_expr(before)
+ with
tvm.transform.PassContext(config={"tir.enable_buffer_level_predication": True}):
+ after = tvm.tir.transform.VectorizeLoop()(before_mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def test_vectorize_with_explicitly_disabled_buffer_level_predication():
+ # Since the target has the SVE feature, buffer level predication is enabled
+ # by default. However, it has been explicitly disabled by the pass context
+ # option, so no buffer-level predicates should be added.
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ for i_0, i_1_s in T.grid(4, 4):
+ if i_0 * 4 + i_1_s < 14:
+ B[i_0 * 4 + i_1_s] = A[i_0 * 4 + i_1_s] + T.float32(1)
+
+ mod = tvm.IRModule.from_expr(before)
+ with
tvm.transform.PassContext(config={"tir.enable_buffer_level_predication":
False}):
+ with tvm.target.Target(sve_target):
+ after = tvm.tir.transform.VectorizeLoop()(mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def
test_vectorize_and_predicate_buffer_load_stores_with_sve_func_attr_target():
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True, "target":
sve_target})
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True),
"target": sve_target})
+ for i_0 in range(4):
+ load_a = T.meta_var(
+ A.vload(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+ )
+ add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
+ B.vstore(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ add_1,
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+
+ mod = tvm.IRModule.from_expr(before)
+ after = tvm.tir.transform.VectorizeLoop()(mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
+def
test_vectorize_and_predicate_buffer_load_stores_with_sve_attr_scope_target():
+ @T.prim_func
+ def before(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": True})
+ with T.attr(sve_target, "target", 0):
+ for i_0 in T.serial(T.ceildiv(14, 4)):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 14:
+ B[i_0 * 4 + i_1] = A[i_0 * 4 + i_1] + 1.0
+
+ @T.prim_func
+ def expected(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (16,), "float32")
+ B = T.match_buffer(b, (16,), "float32")
+ T.func_attr({"global_symbol": "main", "tir.noalias": T.bool(True)})
+ with T.attr(sve_target, "target", 0):
+ for i_0 in range(4):
+ load_a = T.meta_var(
+ A.vload(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4,
14),
+ )
+ )
+ add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
+ B.vstore(
+ [T.Ramp(i_0 * 4, 1, 4)],
+ add_1,
+ predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
+
+ mod = tvm.IRModule.from_expr(before)
+ after = tvm.tir.transform.VectorizeLoop()(mod)["main"]
+ tvm.ir.assert_structural_equal(after, expected)
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
b/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
index c20784b4bf..daad7f5314 100644
--- a/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
@@ -468,6 +468,20 @@ def test_ir_builder_tir_buffer_store_scalable_vec():
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
+def test_ir_builder_tir_buffer_store_predicate():
+ buffer_a = T.Buffer((30,), "float32")
+ value = T.broadcast(0.11, T.vscale() * 4)
+ index = T.ramp(0, 1, T.vscale() * 4)
+ predicate = T.broadcast(T.bool(True), T.vscale() * 4)
+
+ with IRBuilder() as ib:
+ T.buffer_store(buffer_a, value, [index], predicate)
+
+ ir_actual = ib.get()
+ ir_expected = tir.BufferStore(buffer_a, value, [index], predicate)
+ assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
+
+
def test_ir_builder_tir_prefetch():
with IRBuilder() as ib:
buffer_a = T.Buffer((128, 128), "float32")
diff --git a/tests/python/tvmscript/test_tvmscript_printer_tir.py
b/tests/python/tvmscript/test_tvmscript_printer_tir.py
index edc6da3163..9e77fa0900 100644
--- a/tests/python/tvmscript/test_tvmscript_printer_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_printer_tir.py
@@ -948,5 +948,102 @@ def func():
_assert_print(func, expected_output)
+def test_predicated_load_store():
+ from tvm.script import tir as T
+
+ @T.prim_func
+ def main(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (128, 128), "float32")
+ B = T.match_buffer(b, (256, 256), "float32")
+ T.func_attr({"global_symbol": "func"})
+ a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)))
+ A.vstore([0, T.Ramp(0, 2, 4)], a_load,
predicate=T.Broadcast(T.bool(False), 4))
+
+ expected_output = """
+# from tvm.script import tir as T
+
[email protected]_func
+def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
+ A.vstore([0, T.Ramp(0, 2, 4)], A.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False),
4))
+ """
+ _assert_print(main, expected_output)
+
+
+def test_predicated_buffer_load_store():
+ a = tir.Var("a", "handle")
+ b = tir.Var("b", "handle")
+ buffer_map = {
+ a: tir.decl_buffer(shape=[128, 128], dtype="float32", name="A"),
+ b: tir.decl_buffer(shape=[256, 256], dtype="float32", name="B"),
+ }
+ buffer_load = tir.BufferLoad(
+ buffer=buffer_map[b],
+ indices=[0, tir.Ramp(0, 4, 4)],
+ predicate=tir.Broadcast(tir.IntImm("uint1", 0), 4),
+ )
+ body = tir.BufferStore(
+ buffer=buffer_map[a],
+ value=buffer_load,
+ indices=[0, tir.Ramp(0, 2, 4)],
+ predicate=tir.Broadcast(tir.IntImm("uint1", 0), 4),
+ )
+ func = tir.PrimFunc(
+ params=[a, b],
+ ret_type=None,
+ buffer_map=buffer_map,
+ body=body,
+ )
+
+ expected_output = """
+# from tvm.script import tir as T
+
[email protected]_func(private=True)
+def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
+ A.vstore([0, T.Ramp(0, 2, 4)], B.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False),
4))
+ """
+ _assert_print(func, expected_output)
+
+
+def test_predicated_scalable_load_store():
+ from tvm.script import tir as T
+
+ @T.prim_func
+ def main(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (128, 128), "float32")
+ B = T.match_buffer(b, (256, 256), "float32")
+ T.func_attr({"global_symbol": "func"})
+ mask = T.meta_var(T.get_active_lane_mask("uint1xvscalex4", 0, 13))
+ a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, T.vscale() * 4)],
predicate=mask))
+ A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], a_load, predicate=mask)
+
+ expected_output = """
+# from tvm.script import tir as T
+
[email protected]_func
+def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
+ A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], A.vload([0, T.Ramp(0, 4,
T.vscale() * 4)], predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13)),
predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13))
+ """
+ _assert_print(main, expected_output)
+
+
+def test_vload_with_explicit_scalable_data_type():
+ from tvm.script import tir as T
+
+ @T.prim_func
+ def main(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (128,), "float32")
+ B = T.match_buffer(b, (128,), "float32")
+ B[0 : T.vscale() * 4] = A.vload([T.Ramp(0, 1, T.vscale() * 4)],
dtype="float32xvscalex4")
+
+ expected_output = """
+# from tvm.script import tir as T
+
[email protected]_func
+def main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
+ B[0:T.vscale() * 4] = A[0:T.vscale() * 4]
+ """
+ _assert_print(main, expected_output)
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/tvmscript/test_tvmscript_roundtrip.py
b/tests/python/tvmscript/test_tvmscript_roundtrip.py
index 73bf200bb2..ee404f08ef 100644
--- a/tests/python/tvmscript/test_tvmscript_roundtrip.py
+++ b/tests/python/tvmscript/test_tvmscript_roundtrip.py
@@ -3352,6 +3352,20 @@ def scalable_vectors():
return func
+def predicated_buffer_load_store():
+ @T.prim_func
+ def func(a: T.handle, b: T.handle):
+ A = T.match_buffer(a, (4,), "float32")
+ B = T.match_buffer(b, (8,), "float32")
+ for i_0 in range(4):
+ load_a = T.meta_var(
+ A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4))
+ )
+ B.vstore([T.Ramp(0, 2, 4)], load_a,
predicate=T.Broadcast(T.bool(True), 4))
+
+ return func
+
+
def let_expression():
@T.prim_func
def func():
@@ -4116,6 +4130,8 @@ ir_generator = tvm.testing.parameter(
buffer_axis_separator,
buffer_ramp_access_as_slice_index,
ramp_int64,
+ scalable_vectors,
+ predicated_buffer_load_store,
let_expression,
void_ptr,
decl_buffer,