Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/transform/inject_assumes.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

#include "tvm/arith/analyzer.h"
#include "tvm/ir/expr.h"
#include "tvm/ir/transform.h"
#include "tvm/node/structural_hash.h"
#include "tvm/tir/expr.h"
#include "tvm/tir/stmt.h"
#include "tvm/tir/stmt_functor.h"
#include "tvm/tir/transform.h"
Comment on lines +2 to +11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing headers to prevent build failures.

std::unordered_map, std::vector, and std::find_if require STL headers; StructuralEqual needs its header too.

Apply:

 #include "tvm/arith/analyzer.h"
+#include <algorithm>
+#include <unordered_map>
+#include <vector>
+#include "tvm/node/structural_equal.h"
 #include "tvm/ir/expr.h"
 #include "tvm/ir/transform.h"
 #include "tvm/node/structural_hash.h"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include "tvm/arith/analyzer.h"
#include "tvm/ir/expr.h"
#include "tvm/ir/transform.h"
#include "tvm/node/structural_hash.h"
#include "tvm/tir/expr.h"
#include "tvm/tir/stmt.h"
#include "tvm/tir/stmt_functor.h"
#include "tvm/tir/transform.h"
#include "tvm/arith/analyzer.h"
#include <algorithm>
#include <unordered_map>
#include <vector>
#include "tvm/node/structural_equal.h"
#include "tvm/ir/expr.h"
#include "tvm/ir/transform.h"
#include "tvm/node/structural_hash.h"
#include "tvm/tir/expr.h"
#include "tvm/tir/stmt.h"
#include "tvm/tir/stmt_functor.h"
#include "tvm/tir/transform.h"
🤖 Prompt for AI Agents
In src/transform/inject_assumes.cc around lines 2 to 9, the file is missing STL
headers and the StructuralEqual definition: add #include <unordered_map>,
#include <vector>, and #include <algorithm> to provide std::unordered_map,
std::vector, and std::find_if, and include the appropriate TVM header for
StructuralEqual (e.g., #include "tvm/node/structural_equal.h") near the other
TVM includes; reorder/includes should follow project style and then rebuild to
verify the missing symbol errors are resolved.


namespace tvm::tl {
using namespace tir;

class AssumeInjector : public tvm::tir::StmtExprMutator {
using Base = tvm::tir::StmtExprMutator;
public:
AssumeInjector(PrimFunc f): f(f) {}
static PrimFunc Substitute(PrimFunc f) {
auto injector = AssumeInjector(f);
f.CopyOnWrite()->body = injector(f->body);
return f;
}
private:
struct AssertCreator {
tvm::StructuralHash sh;
tvm::StructuralEqual se;
std::unordered_map<size_t, std::vector<tvm::PrimExpr>> buckets;
std::vector<PrimExpr> exprs;
void addExpr(PrimExpr e) {
size_t h = sh(e);
auto bucket = buckets[h];
auto it = std::find_if(bucket.begin(), bucket.end(), [&](auto y) {
return se(e, y, true);
});
if(it == bucket.end()) {
exprs.push_back(e);
buckets[h].push_back(e);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing the buckets map with buckets[h] creates a copy of the std::vector<tvm::PrimExpr>, which is inefficient. You should use a reference (auto&) to avoid this unnecessary copy. This also allows you to simplify the code by using the reference to push back the new element.

Suggested change
auto bucket = buckets[h];
auto it = std::find_if(bucket.begin(), bucket.end(), [&](auto y) {
return se(e, y, true);
});
if(it == bucket.end()) {
exprs.push_back(e);
buckets[h].push_back(e);
}
auto& bucket = buckets[h];
auto it = std::find_if(bucket.begin(), bucket.end(), [&](auto y) {
return se(e, y, true);
});
if(it == bucket.end()) {
exprs.push_back(e);
bucket.push_back(e);
}

}
void addBuffer(Buffer buf) {
for(auto shape: buf->shape) {
if(shape->IsInstance<IntImmNode>()) continue;
addExpr(shape);
}
}
Stmt build(Stmt body) {
if(exprs.empty()) return body;
PrimExpr red = GT(exprs[0], 0);
for(size_t i = 1; i < exprs.size(); ++i) {
red = And(GT(exprs[i], 0), red);
}
auto simplified = arith::Analyzer{}.Simplify(red, 10);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
auto msg = StringImm("Invalid Buffer Shape: buffer shape should be greater than 0");
return AssertStmt(simplified, msg, body);
}
};
Stmt VisitStmt_(const DeclBufferNode* op) final {
auto body = VisitStmt(op->body);
AssertCreator c;
c.addBuffer(op->buffer);
return DeclBuffer(op->buffer, c.build(body), op->span);
}
Stmt VisitStmt_(const BlockNode* op) final {
auto body = VisitStmt(op->body);
AssertCreator c;
if(root_node) {
for(auto item: f->buffer_map) {
c.addBuffer(item.second);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The root_node flag is intended to process function parameters only for the outermost block. However, it's never set to false, causing f->buffer_map to be processed for every BlockNode in the function body, which is incorrect and inefficient. You should capture the root_node state, set it to false before recursion, and then use the captured state.

  Stmt VisitStmt_(const BlockNode* op) final {
    bool is_root = root_node;
    if (is_root) root_node = false;
    auto body = VisitStmt(op->body);
    AssertCreator c;
    if(is_root) {
      for(auto item: f->buffer_map) {
        c.addBuffer(item.second);
      }
    }

for(auto item: op->alloc_buffers) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Root-block detection is broken; toggle before recursion and only once.

root_node is never flipped and you recurse before toggling, so every nested Block acts like root and duplicates buffer_map asserts.

-  Stmt VisitStmt_(const BlockNode* op) final {
-    auto body = VisitStmt(op->body);
-    AssertCreator c;
-    if(root_node) {
+  Stmt VisitStmt_(const BlockNode* op) final {
+    bool was_root = root_node;
+    root_node = false;  // ensure children are not treated as root
+    AssertCreator c;
+    if (was_root) {
       for(auto item: f->buffer_map) {
         c.addBuffer(item.second);
       }
     }
+    auto body = VisitStmt(op->body);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Stmt VisitStmt_(const BlockNode* op) final {
auto body = VisitStmt(op->body);
AssertCreator c;
if(root_node) {
for(auto item: f->buffer_map) {
c.addBuffer(item.second);
}
}
for(auto item: op->alloc_buffers) {
Stmt VisitStmt_(const BlockNode* op) final {
bool was_root = root_node;
root_node = false; // ensure children are not treated as root
AssertCreator c;
if (was_root) {
for(auto item: f->buffer_map) {
c.addBuffer(item.second);
}
}
auto body = VisitStmt(op->body);
for(auto item: op->alloc_buffers) {
🤖 Prompt for AI Agents
In src/transform/inject_assumes.cc around lines 63-71, root_block detection is
broken because root_node is never flipped and you recurse before toggling;
change the logic so you detect and set root_node true before recursing into the
block body (but only if it was false), run the root-only buffer_map handling
while root_node is true, then restore root_node to its previous value after the
recursive visit so nested Blocks don't all act as root and duplicate asserts.

c.addBuffer(item);
}
for(auto item: op->match_buffers) {
c.addBuffer(item->buffer);
}
return Block(
op->iter_vars,
op->reads,
op->writes,
op->name_hint,
c.build(body),
op->init,
op->alloc_buffers,
op->match_buffers,
op->annotations,
op->span
);
}
PrimFunc f;
bool root_node { true };
};

using namespace tir::transform;

tvm::transform::Pass InjectAssumes() {
auto pass_func = [=](PrimFunc f, IRModule m, PassContext ctx) {
return AssumeInjector::Substitute(f);
};
return CreatePrimFuncPass(pass_func, 0, "tl.InjectAssumes", {});
}

TVM_FFI_STATIC_INIT_BLOCK({
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("tl.transform.InjectAssumes", InjectAssumes);
});

} // namespace tvm::tl
12 changes: 12 additions & 0 deletions tilelang/engine/phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from tilelang.transform import PassContext
from tilelang.contrib.nvcc import have_tma
from typing import Optional
from pathlib import Path


def allow_warp_specialized(pass_ctx: Optional[PassContext] = None,
Expand Down Expand Up @@ -61,6 +62,10 @@ def should_enable_aggressive_merge(pass_ctx: Optional[PassContext] = None,
return enable_aggressive_merge


debug_path = Path('debug')
debug_path.mkdir(exist_ok=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This code unconditionally creates a debug directory in the current working directory. This is a side effect that can be problematic (e.g., if write permissions are not available) and is generally not desirable in library code. This, along with the various debug_path.joinpath(...).write_text(...) calls (e.g., on lines 103, 106, 145, 147, 211), appears to be debugging code that should be removed before merging.



def LowerAndLegalize(mod: IRModule, target: Target) -> IRModule:
# Bind the target device information to the module
"""
Expand All @@ -87,14 +92,18 @@ def LowerAndLegalize(mod: IRModule, target: Target) -> IRModule:

# Legalize the frontend IR to make it compatible with TVM
mod = tilelang.transform.FrontendLegalize()(mod)
# Inject assumes to speedup tvm prover
mod = tilelang.transform.InjectAssumes()(mod)
# Simplify the IR expressions
mod = tir.transform.Simplify()(mod)
# Set layouts for reducers
mod = tilelang.transform.LayoutReducer()(mod)
# Infer memory layouts for fragments and shared memory
mod = tilelang.transform.LayoutInference()(mod)
debug_path.joinpath('LowerTileOp.0.py').write_text(mod.script(show_meta=True))
# Lower high-level tile operations to low-level operations
mod = tilelang.transform.LowerTileOp()(mod)
debug_path.joinpath('LowerTileOp.1.py').write_text(mod.script(show_meta=True))
# Lower l2 persistent map
mod = tilelang.transform.LowerL2Persistent()(mod)
# Legalize vectorized loops to ensure they are valid
Expand Down Expand Up @@ -133,7 +142,9 @@ def OptimizeForTarget(mod: IRModule, target: Target) -> IRModule:
mod = tilelang.transform.LowerOpaqueBlock()(mod)
mod = tilelang.transform.MergeIfStmt()(mod)
mod = tilelang.transform.RewriteWgmmaSync()(mod)
debug_path.joinpath('InjectFenceProxy.0.py').write_text(mod.script(show_meta=True))
mod = tilelang.transform.InjectFenceProxy()(mod)
debug_path.joinpath('InjectFenceProxy.1.py').write_text(mod.script(show_meta=True))
else:
mod = tilelang.transform.IfStmtBinding()(mod)
mod = tir.transform.PlanAndUpdateBufferAllocationLocation()(mod)
Expand Down Expand Up @@ -197,5 +208,6 @@ def OptimizeForTarget(mod: IRModule, target: Target) -> IRModule:

# Transform threadblock to persistent threadblock
mod = tilelang.transform.PersistThreadblock()(mod)
debug_path.joinpath('PersistThreadblock.1.py').write_text(mod.script(show_meta=True))

return mod
10 changes: 10 additions & 0 deletions tilelang/transform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ def FrontendLegalize():
"""
return _ffi_api.FrontendLegalize() # type: ignore

def InjectAssumes():
"""Inject Assumes

Returns:
-------
fpass : tvm.transform.Pass
The result pass
"""
Comment on lines +83 to +89

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The docstring is a bit sparse and the Returns: section is inconsistent with other functions in this file. It would be helpful to explain what is being injected and why, and to align with the existing docstring format.

Suggested change
"""Inject Assumes
Returns:
-------
fpass : tvm.transform.Pass
The result pass
"""
"""Inject assertions that buffer shapes are positive to speed up the prover.
Returns
-------
fpass : tvm.transform.Pass
The result pass
"""

return _ffi_api.InjectAssumes()


def LowerHopperIntrin():
"""LowerHopperIntrin
Expand Down
Loading