Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
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
15 changes: 15 additions & 0 deletions src/layout/layout.cc
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,21 @@ bool FragmentNode::IsCompletedReplicated() const {
ReplicationPlaceholder());
}

arith::IterMapResult FragmentNode::DetectInjective() const {
Comment thread
SiriusNEO marked this conversation as resolved.
// lei:To perform injective check, we need to reverse the layout
// and use surjective check, now we use bijective check for convenience
// can be relaxed in future
arith::Analyzer analyzer;
// Build a flat indices array: [forward_thread_, forward_index_[...]]
Array<PrimExpr> indices;
indices.push_back(forward_thread_);
for (const auto &e : forward_index_) {
indices.push_back(e);
}
return arith::DetectIterMap(indices, getVarMap(), 1,
arith::IterMapLevel::Bijective, &analyzer);
}

PrimExpr FragmentNode::ThreadExtent() const {
Array<PrimExpr> ret(OutputDim(), 1);
arith::Analyzer analyzer;
Expand Down
2 changes: 2 additions & 0 deletions src/layout/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class FragmentNode : public LayoutNode {

bool IsCompletedReplicated() const;

arith::IterMapResult DetectInjective() const;

static void RegisterReflection();

TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tl.Fragment", FragmentNode, LayoutNode);
Expand Down
8 changes: 8 additions & 0 deletions src/op/parallel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ LayoutMap ParallelOpNode::InferLayout(const LayoutInferArgs &T,
InferLevel level) const {
if (loop_layout_.defined())
return {};

if (level == InferLevel::kStrict) {
LayoutMap results;
// Deduce buffers that should be complicated replicated.
Expand Down Expand Up @@ -562,6 +563,13 @@ LayoutMap ParallelOpNode::InferLayout(const LayoutInferArgs &T,
} else {
return {};
}
// check loop_layout_ is injective
auto injective_res = loop_layout_->DetectInjective();
if (!injective_res->errors.empty()) {
std::ostringstream oss;
oss << "Loop layout is not injective: " << loop_layout_->DebugOutput();
Comment thread
LeiWang1999 marked this conversation as resolved.
Outdated
throw LoopLayoutInjectiveException(oss.str());
}

PrimExpr loop_thread_extent = loop_layout_->ThreadExtent();

Expand Down
9 changes: 9 additions & 0 deletions src/op/parallel.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class LayoutConflictException : public std::exception {
std::string msg_;
};

class LoopLayoutInjectiveException : public std::exception {
Comment thread
LeiWang1999 marked this conversation as resolved.
Outdated
public:
const char *what() const noexcept override { return msg_.c_str(); }
LoopLayoutInjectiveException(const std::string &msg) : msg_(msg) {}

private:
std::string msg_;
};

bool ProveFragmentContains(Fragment small_frag, Fragment large_frag,
Array<PrimExpr> small_frag_indices,
Array<PrimExpr> large_frag_indices,
Expand Down
149 changes: 133 additions & 16 deletions src/transform/layout_inference.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <tvm/tir/utils.h>

#include <algorithm>
#include <deque>
#include <memory>
#include <queue>

Expand Down Expand Up @@ -72,7 +73,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {

void RunInferStep(int cur_infer_id, InferLevel level, bool update_queue,
LayoutMap &layout_map, const LayoutMap &strict_layout_map,
std::queue<int> &q, std::vector<bool> &in_queue) {
std::deque<int> &q, std::vector<bool> &in_queue) {
Comment thread
LeiWang1999 marked this conversation as resolved.
auto num_infer = infer_list_.size();

// Range check for cur_infer_id
Expand Down Expand Up @@ -112,9 +113,9 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
next->InferLayout(LayoutInferArgs{target_, thread_bounds, layout_map,
cur_analyzer, buffer_oob},
level);

// Process the returned updates
for (const auto &[buffer, layout] : updates) {

// Basic validity checks
ICHECK(buffer.defined()) << "InferLayout returned an undefined buffer.";
ICHECK(layout.defined()) << "InferLayout returned an undefined layout.";
Expand Down Expand Up @@ -152,10 +153,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
layout_map.Set(sib, target_layout);
if (update_queue && use_list_.count(sib)) {
for (int idx : use_list_[sib]) {
if (!in_queue[idx] && idx != cur_infer_id) {
in_queue[idx] = true;
q.push(idx);
}
EnqueueWithPriority(idx, q, in_queue, cur_infer_id, layout_map);
}
}
}
Expand Down Expand Up @@ -233,22 +231,20 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
<< "Index in use_list_ for buffer " << buffer
<< " out of range: " << idx << " >= " << num_infer << ".";

if (!in_queue[idx] && idx != cur_infer_id) {
in_queue[idx] = true;
q.push(idx);
}
EnqueueWithPriority(idx, q, in_queue, cur_infer_id, layout_map);
}
}
}
};

void FinishInferQueue(InferLevel level, LayoutMap &layout_map,
const LayoutMap &strict_layout_map, std::queue<int> &q,
const LayoutMap &strict_layout_map, std::deque<int> &q,
std::vector<bool> &in_queue) {
auto num_infer = infer_list_.size();

while (!q.empty()) {
int cur_infer_id = q.front();
q.pop();
q.pop_front();
// Range check again, just to be safe
ICHECK_GE(cur_infer_id, 0);
ICHECK_LT(cur_infer_id, num_infer);
Expand Down Expand Up @@ -289,7 +285,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
int num_infer = infer_list_.size();

// Prepare BFS queue for iterative inference
std::queue<int> q;
std::deque<int> q;
std::vector<bool> in_queue(num_infer, true);
for (int i = 0; i < num_infer; i++) {
// Check that each infer_list_ entry is valid
Expand All @@ -301,7 +297,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
if (!thread_var_vec_[i].defined() && skip_thread_partition_) {
thread_var_vec_[i] = thread_var_;
}
q.push(i);
q.push_back(i);
}

// step 1: infer strict layout
Expand Down Expand Up @@ -431,6 +427,38 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
return buffer_map;
}

// Return true if all buffers that this op (idx) touches already have
// inferred layouts in layout_map. Used to prioritize enqueue order.
bool ShouldPrioritize(int idx, const LayoutMap &layout_map) const {
auto it = op_touched_buffers_.find(idx);
if (it == op_touched_buffers_.end() || it->second.empty())
return false;
for (const auto &buf : it->second) {
if (!layout_map.count(buf))
return false;
}
return true;
}

// Enqueue idx to q with priority if all its buffers already
// have layouts. Also guards against duplicates and self-enqueue.
void EnqueueWithPriority(int idx, std::deque<int> &q,
std::vector<bool> &in_queue, int cur_infer_id,
const LayoutMap &layout_map) const {
if (idx == cur_infer_id)
return;
if (idx < 0 || idx >= static_cast<int>(in_queue.size()))
return;
if (in_queue[idx])
return;
in_queue[idx] = true;
if (ShouldPrioritize(idx, layout_map)) {
q.push_front(idx);
} else {
q.push_back(idx);
}
}

void VisitExpr_(const CallNode *op) final {
IRVisitorWithAnalyzer::VisitExpr_(op);
// Do not analysis the call node to the global function.
Expand Down Expand Up @@ -536,11 +564,28 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
}

void addToUseList(const Buffer &buffer) {
// buffer scope must be local.fragment
if (buffer.scope() != "local.fragment") {
return;
}
int infer_idx = infer_list_.size();
if (use_list_.find(buffer) == use_list_.end()) {
use_list_[buffer] = {};
}
use_list_[buffer].push_back(infer_idx);

// Track which buffers this op (infer_idx) touches for prioritization.
// Avoid duplicates.
auto &vec = op_touched_buffers_[infer_idx];
bool exists = false;
for (const auto &b : vec) {
if (b.same_as(buffer)) {
exists = true;
break;
}
}
if (!exists)
vec.push_back(buffer);
}

void VisitStmt_(const ForNode *op) final {
Expand All @@ -549,6 +594,71 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
for (const auto &[buffer, _] : infer->GetIndiceMap()) {
addToUseList(buffer);
}

PostOrderVisit(op->body, [this](const ObjectRef &node) {
if (auto *buffer_load = node.as<BufferLoadNode>()) {
if (buffer_load->buffer.defined() &&
buffer_load->buffer->data.defined()) {
if (buffer_data_to_buffers_.count(buffer_load->buffer->data)) {
// Check if this buffer is already in the list
auto buffers = buffer_data_to_buffers_[buffer_load->buffer->data];
bool found = false;
for (const auto &buf : buffers) {
if (buf.same_as(buffer_load->buffer)) {
found = true;
break;
}
}
if (!found) {
buffers.push_back(buffer_load->buffer);
buffer_data_to_buffers_.Set(buffer_load->buffer->data, buffers);
DLOG(INFO) << "[LayoutInference] BufferStore: added buffer "
<< buffer_load->buffer
<< " buffer.get() = " << buffer_load->buffer.get()
<< " data = " << buffer_load->buffer->data.get();
}
} else {
buffer_data_to_buffers_.Set(buffer_load->buffer->data,
{buffer_load->buffer});
DLOG(INFO) << "[LayoutInference] BufferStore: new buffer "
<< buffer_load->buffer
<< " buffer.get() = " << buffer_load->buffer.get()
<< " data = " << buffer_load->buffer->data.get();
}
}
} else if (auto *buffer_store = node.as<BufferStoreNode>()) {
if (buffer_store->buffer.defined() &&
buffer_store->buffer->data.defined()) {
if (buffer_data_to_buffers_.count(buffer_store->buffer->data)) {
auto buffers =
buffer_data_to_buffers_[buffer_store->buffer->data];
bool found = false;
for (const auto &buf : buffers) {
if (buf.same_as(buffer_store->buffer)) {
found = true;
break;
}
}
if (!found) {
buffers.push_back(buffer_store->buffer);
buffer_data_to_buffers_.Set(buffer_store->buffer->data,
buffers);
DLOG(INFO) << "[LayoutInference] BufferStore: added buffer "
<< buffer_store->buffer
<< " buffer.get() = " << buffer_store->buffer.get()
<< " data = " << buffer_store->buffer->data.get();
}
} else {
buffer_data_to_buffers_.Set(buffer_store->buffer->data,
{buffer_store->buffer});
DLOG(INFO) << "[LayoutInference] BufferStore: new buffer "
<< buffer_store->buffer
<< " buffer.get() = " << buffer_store->buffer.get()
<< " data = " << buffer_store->buffer->data.get();
}
}
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
infer_list_stmt_.push_back(tvm::ffi::GetRef<ObjectRef>(op));
infer_list_.push_back(std::move(infer));
thread_var_vec_.push_back(thread_var_);
Expand Down Expand Up @@ -699,6 +809,8 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
std::vector<TileOperator> infer_list_;
std::unordered_map<Buffer, std::vector<int>, ObjectPtrHash, ObjectPtrEqual>
use_list_;
// Per-op list of buffers it touches (fragment scope), used for prioritization
std::unordered_map<int, std::vector<Buffer>> op_touched_buffers_;
// This is a workaround for cpu backend,
// we need to define a thread_var for the serial loop.
IterVar thread_var_ = IterVar(Range::FromMinExtent(0, 1), Var("v_thread"),
Expand Down Expand Up @@ -765,6 +877,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
}
}
}

std::unordered_map<int, std::vector<int>> components;
for (int i = 0; i < infer_list_.size(); i++) {
int root = uf.Find(i);
Expand All @@ -781,7 +894,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {

// For each component, try each op as root, and determine the least
// replicated one
std::queue<int> q;
std::deque<int> q;
std::vector<bool> in_queue(infer_list_.size(), false);

for (auto &&[root, members] : components) {
Expand All @@ -795,7 +908,7 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
// Try each member as the root of inference for this component
for (int attempt_infer_root : members) {
DLOG(INFO) << "----------------------- try root " << attempt_infer_root
<< '\n';
<< " members " << members.size() << '\n';
// Backup the current infer_list_ state
auto back_infer_list = BackupInferList();
// Copy the current layout_map for temporary use
Expand Down Expand Up @@ -826,6 +939,10 @@ class BufferUseDefCollector : public IRVisitorWithAnalyzer {
do_update = false;
DLOG(INFO) << "attempt failed due to NormalizeIterException "
<< e.what() << '\n';
} catch (const LoopLayoutInjectiveException &e) {
do_update = false;
DLOG(INFO) << "attempt failed due to LoopLayoutInjectiveException "
<< e.what() << '\n';
}

if (do_update) {
Expand Down
Loading