You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by "kimm240 (via GitHub)" <gi...@apache.org> on 2025/11/04 09:31:20 UTC
[PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
kimm240 opened a new pull request, #18418:
URL: https://github.com/apache/tvm/pull/18418
Currently it is not possible to fuse an epilogue operation (e.g., bias addition) into a reduction block's initialization statement. This limitation prevents leveraging hardware-specific instructions that support bias accumulation in vector ISAs, such as MACC (multiply-accumulate with bias) instructions.
This commit implements a new schedule primitive 'fuse_reduction_epilogue' that addresses the problem described in:
https://discuss.tvm.apache.org/t/tir-problem-inlining-addition-into-matmul-block/18066
The primitive transforms the following pattern:
Before:
for i, j, k in T.grid(M, N, K):
with T.block("matmul"):
with T.init():
temp[vi, vj] = 0
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(M, N):
with T.block("bias_add"):
D[vi, vj] = temp[vi, vj] + C[vi, vj]
After:
for i, j, k in T.grid(M, N, K):
with T.block("matmul"):
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = C[vi, vj] # Fused epilogue into init
D[vi, vj] = D[vi, vj] + A[vi, vk] * B[vj, vk]
The transformation removes the intermediate temp buffer and the separate epilogue block, enabling better tensorization opportunities for hardware with bias accumulation support.
Implementation:
- ReductionEpilogueFuser class for pattern validation and IR transformation
- BodyPatternAllowFusion: Validates epilogue can be fused
- AnalyzeEpiloguePattern: Detects addition pattern (D = temp + C)
- ExtractEpilogueInfo: Extracts buffer and region information
- CreateFusedReductionBlock: Creates single block with modified T.init()
- SingleBlockFusionReplacer: Replaces blocks and removes temp buffer
- Variable mapping between epilogue and reduction block iter vars
- Proper buffer and region updates with correct read/write ordering
- FFI bindings and Python API following TVM conventions
Changes:
- src/tir/schedule/primitive/compute_inline.cc: Core implementation (~430 lines)
- src/tir/schedule/primitive.h: Function declaration
- include/tvm/tir/schedule/schedule.h: Virtual method in ScheduleNode
- src/tir/schedule/concrete_schedule.{h,cc}: ConcreteScheduleNode implementation
- src/tir/schedule/traced_schedule.{h,cc}: TracedScheduleNode implementation
- src/tir/schedule/schedule.cc: FFI binding registration
- python/tvm/tir/schedule/schedule.py: Python API with documentation
- tests/python/tir-schedule/test_tir_schedule_fuse_reduction_epilogue.py: Comprehensive tests including basic fusion, float32 variant, numerical correctness verification, and trace roundtrip validation
Run tests with:
pytest tests/python/tir-schedule/test_tir_schedule_fuse_reduction_epilogue.py -v
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "kimm240 (via GitHub)" <gi...@apache.org>.
kimm240 commented on PR #18418:
URL: https://github.com/apache/tvm/pull/18418#issuecomment-3546182740
@wrongtest-intellif
I've applied your suggestions to the test and verified that it works well. Please let me know if there are any other dataflows I should apply this to.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For additional commands, e-mail: discuss-archive-help@tvm.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "gemini-code-assist[bot] (via GitHub)" <gi...@apache.org>.
gemini-code-assist[bot] commented on PR #18418:
URL: https://github.com/apache/tvm/pull/18418#issuecomment-3484877992
## Summary of Changes
Hello @kimm240, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a significant optimization to the TVM Tensor IR (TIR) schedule by adding a new primitive, `FuseReductionEpilogue`. This primitive addresses the current limitation of fusing epilogue operations, such as bias addition, directly into reduction blocks. By transforming the IR to integrate the epilogue into the reduction block's initialization, it eliminates intermediate buffer usage and unlocks opportunities for more efficient hardware utilization, particularly for architectures supporting bias accumulation instructions.
### Highlights
* **New Primitive**: Introduced `FuseReductionEpilogue` to the TVM TIR schedule, allowing the fusion of epilogue operations (like bias addition) directly into reduction blocks.
* **Optimization Enablement**: This fusion enables leveraging hardware-specific instructions (e.g., MACC) for bias accumulation, leading to improved performance and more efficient hardware utilization.
* **IR Transformation**: The primitive transforms a two-block pattern (reduction followed by an epilogue) into a single, optimized reduction block by modifying its initialization statement and removing intermediate buffers.
* **Comprehensive Implementation**: Includes C++ backend logic (`ReductionEpilogueFuser`, `SingleBlockFusionReplacer`), FFI bindings, a Python API with clear documentation, and extensive test cases to ensure correctness and functionality.
<details>
<summary><b>Using Gemini Code Assist</b></summary>
<br>
The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.
<b>Invoking Gemini</b>
You can request assistance from Gemini at any point by creating a comment using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands on the current page.
Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help | `/gemini help` | Displays a list of available commands.
<b>Customization</b>
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github).
<b>Limitations & Feedback</b>
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up [here](https://google.qualtrics.com/jfe/form/SV_2cyuGuTWsEw84yG).
<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>
</details>
[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "wrongtest-intellif (via GitHub)" <gi...@apache.org>.
wrongtest-intellif merged PR #18418:
URL: https://github.com/apache/tvm/pull/18418
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For additional commands, e-mail: discuss-archive-help@tvm.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "tlopex (via GitHub)" <gi...@apache.org>.
tlopex commented on PR #18418:
URL: https://github.com/apache/tvm/pull/18418#issuecomment-3504297729
cc @tqchen @Hzfengsy
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "gemini-code-assist[bot] (via GitHub)" <gi...@apache.org>.
gemini-code-assist[bot] commented on code in PR #18418:
URL: https://github.com/apache/tvm/pull/18418#discussion_r2489513727
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,393 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ const Block& epilogue_block = epilogue_block_realize->block;
+
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
+ if (const auto* add = value.as<AddNode>()) {
+ // Check if one operand is BufferLoad from reduction buffer
+ const auto* load_a = add->a.as<BufferLoadNode>();
+ const auto* load_b = add->b.as<BufferLoadNode>();
+
+ if (load_a && load_a->buffer.same_as(inlined_buffer_)) {
+ // Pattern: temp[...] + C[...]
+ epilogue_addend_ = add->b;
+ return true;
+ } else if (load_b && load_b->buffer.same_as(inlined_buffer_)) {
+ // Pattern: C[...] + temp[...]
+ epilogue_addend_ = add->a;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ReductionEpilogueFuser::IsReductionBlock(const BlockNode* block) {
+ // Check if block has reduction iter vars
+ for (const IterVar& iter : block->iter_vars) {
+ if (iter->iter_type == kCommReduce) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ReductionEpilogueFuser::ExtractEpilogueInfo() {
+ // Extract epilogue output buffer and indices
+ epilogue_output_buffer_ = inlined_store_->buffer;
+ epilogue_output_indices_ = inlined_store_->indices;
+
+ // Extract epilogue output region from epilogue block writes
+ for (const BufferRegion& write : epilogue_block_->writes) {
+ if (write->buffer.same_as(epilogue_output_buffer_)) {
+ epilogue_output_region_ = write;
+ break;
+ }
+ }
+
+ // Extract epilogue addend buffer and region from epilogue_addend_
+ if (const auto* load = epilogue_addend_.as<BufferLoadNode>()) {
+ epilogue_addend_buffer_ = load->buffer;
+ // Find the read region from epilogue block reads
+ for (const BufferRegion& read : epilogue_block_->reads) {
+ if (read->buffer.same_as(epilogue_addend_buffer_)) {
+ epilogue_addend_region_ = read;
+ break;
+ }
+ }
+ }
+}
+
+Block ReductionEpilogueFuser::CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize) {
+ ObjectPtr<BlockNode> new_block = ffi::make_object<BlockNode>(*reduction_block);
+
+ // 1. Keep all iter vars (data parallel + reduction)
+ new_block->iter_vars = reduction_block->iter_vars;
+
+ // 2. Map epilogue block vars to reduction block vars
+ std::unordered_map<Var, Var> var_map;
+ int reduction_data_par_idx = 0;
+ for (int i = 0; i < static_cast<int>(reduction_block->iter_vars.size()); ++i) {
+ const IterVar& iter_var = reduction_block->iter_vars[i];
+ if (iter_var->iter_type == IterVarType::kDataPar) {
+ // Map corresponding data parallel var from epilogue block
+ int epilogue_data_par_idx = 0;
+ for (const IterVar& epilogue_iter_var : epilogue_block_->iter_vars) {
+ if (epilogue_iter_var->iter_type == IterVarType::kDataPar) {
+ if (epilogue_data_par_idx == reduction_data_par_idx) {
+ var_map[epilogue_iter_var->var] = iter_var->var;
+ break;
+ }
+ epilogue_data_par_idx++;
+ }
+ }
+ reduction_data_par_idx++;
+ }
+ }
+
+ // 3. Change init to epilogue value: D[vi, vj] = C[vi, vj]
+ BufferStore new_init_store(epilogue_output_buffer_, Substitute(epilogue_addend_, var_map),
+ Substitute(epilogue_output_indices_, var_map));
+ new_block->init = new_init_store;
+
+ // 4. Replace output buffer from temp to D in body
+ class BufferReplacer : public StmtExprMutator {
+ public:
+ BufferReplacer(Buffer old_buf, Buffer new_buf) : old_buffer_(old_buf), new_buffer_(new_buf) {}
+
+ Stmt VisitStmt_(const BufferStoreNode* op) final {
+ BufferStore store = Downcast<BufferStore>(StmtExprMutator::VisitStmt_(op));
+ if (store->buffer.same_as(old_buffer_)) {
+ return BufferStore(new_buffer_, store->value, store->indices);
+ }
+ return store;
+ }
+
+ PrimExpr VisitExpr_(const BufferLoadNode* op) final {
+ BufferLoad load = Downcast<BufferLoad>(StmtExprMutator::VisitExpr_(op));
+ if (load->buffer.same_as(old_buffer_)) {
+ return BufferLoad(new_buffer_, load->indices);
+ }
+ return load;
+ }
+
+ private:
+ Buffer old_buffer_;
+ Buffer new_buffer_;
+ };
+
+ BufferReplacer replacer(inlined_buffer_, epilogue_output_buffer_);
+ new_block->body = replacer(reduction_block->body);
+
+ // 5. Update write regions
+ ffi::Array<BufferRegion> new_writes;
+ for (const BufferRegion& write : reduction_block->writes) {
+ if (write->buffer.same_as(inlined_buffer_)) {
+ new_writes.push_back(
+ BufferRegion(epilogue_output_buffer_, Substitute(write->region, var_map)));
+ } else {
+ new_writes.push_back(write);
+ }
+ }
+ new_block->writes = new_writes;
+
+ // 6. Update read regions (C first, then A, B)
+ ffi::Array<BufferRegion> new_reads;
+ std::unordered_set<const BufferNode*> read_bufs;
+
+ // Add C buffer read first (used in init)
+ if (epilogue_addend_buffer_.defined()) {
+ new_reads.push_back(BufferRegion(epilogue_addend_buffer_,
+ Substitute(epilogue_addend_region_->region, var_map)));
+ read_bufs.insert(epilogue_addend_buffer_.get());
+ }
+
+ // Add existing read regions (A, B, etc.)
+ for (const BufferRegion& read : reduction_block->reads) {
+ if (!read->buffer.same_as(inlined_buffer_)) {
+ // Only add non-temp buffers
+ if (read_bufs.find(read->buffer.get()) == read_bufs.end()) {
+ new_reads.push_back(read);
+ read_bufs.insert(read->buffer.get());
+ }
+ }
+ }
+
+ new_block->reads = new_reads;
+
+ return Block(new_block);
+}
+
+/*!
+ * \brief Helper class to replace reduction and epilogue blocks with a single fused block
+ */
+class SingleBlockFusionReplacer : public StmtMutator {
+ public:
+ static Block Replace(Block old_scope_root, Block new_fused_block, Block old_reduction_block,
+ Block old_epilogue_block, Buffer reduction_buffer) {
+ SingleBlockFusionReplacer replacer(std::move(new_fused_block), std::move(old_reduction_block),
+ std::move(old_epilogue_block), std::move(reduction_buffer));
+ Block result = Downcast<Block>(replacer(std::move(old_scope_root)));
+
+ // Remove intermediate temp buffer
+ BlockNode* p = result.CopyOnWrite();
+ ffi::Array<Buffer> new_alloc_buffers;
+ for (const Buffer& buf : p->alloc_buffers) {
+ if (!buf.same_as(replacer.reduction_buffer_)) {
+ new_alloc_buffers.push_back(buf);
+ }
+ }
+ p->alloc_buffers = new_alloc_buffers;
+
+ return result;
+ }
+
+ private:
+ explicit SingleBlockFusionReplacer(Block new_fused_block, Block old_reduction_block,
+ Block old_epilogue_block, Buffer reduction_buffer)
+ : new_fused_block_(std::move(new_fused_block)),
+ old_reduction_block_(std::move(old_reduction_block)),
+ old_epilogue_block_(std::move(old_epilogue_block)),
+ reduction_buffer_(std::move(reduction_buffer)) {}
+
+ Stmt VisitStmt_(const ForNode* loop) final {
+ Stmt mutated_body = StmtMutator::VisitStmt(loop->body);
+ // Remove empty loops (containing only Evaluate(0))
+ if (auto eval = mutated_body.as<EvaluateNode>()) {
+ return mutated_body; // Return Evaluate(0) to be removed by SeqStmt
+ }
Review Comment:

The variable `eval` is declared but never used. This can be simplified to improve code clarity.
```c
if (mutated_body.as<EvaluateNode>()) {
return mutated_body; // Return Evaluate(0) to be removed by SeqStmt
}
```
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,393 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ const Block& epilogue_block = epilogue_block_realize->block;
+
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
+ if (const auto* add = value.as<AddNode>()) {
+ // Check if one operand is BufferLoad from reduction buffer
+ const auto* load_a = add->a.as<BufferLoadNode>();
+ const auto* load_b = add->b.as<BufferLoadNode>();
+
+ if (load_a && load_a->buffer.same_as(inlined_buffer_)) {
+ // Pattern: temp[...] + C[...]
+ epilogue_addend_ = add->b;
+ return true;
+ } else if (load_b && load_b->buffer.same_as(inlined_buffer_)) {
+ // Pattern: C[...] + temp[...]
+ epilogue_addend_ = add->a;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ReductionEpilogueFuser::IsReductionBlock(const BlockNode* block) {
+ // Check if block has reduction iter vars
+ for (const IterVar& iter : block->iter_vars) {
+ if (iter->iter_type == kCommReduce) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ReductionEpilogueFuser::ExtractEpilogueInfo() {
+ // Extract epilogue output buffer and indices
+ epilogue_output_buffer_ = inlined_store_->buffer;
+ epilogue_output_indices_ = inlined_store_->indices;
+
+ // Extract epilogue output region from epilogue block writes
+ for (const BufferRegion& write : epilogue_block_->writes) {
+ if (write->buffer.same_as(epilogue_output_buffer_)) {
+ epilogue_output_region_ = write;
+ break;
+ }
+ }
+
+ // Extract epilogue addend buffer and region from epilogue_addend_
+ if (const auto* load = epilogue_addend_.as<BufferLoadNode>()) {
+ epilogue_addend_buffer_ = load->buffer;
+ // Find the read region from epilogue block reads
+ for (const BufferRegion& read : epilogue_block_->reads) {
+ if (read->buffer.same_as(epilogue_addend_buffer_)) {
+ epilogue_addend_region_ = read;
+ break;
+ }
+ }
+ }
+}
+
+Block ReductionEpilogueFuser::CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize) {
+ ObjectPtr<BlockNode> new_block = ffi::make_object<BlockNode>(*reduction_block);
+
+ // 1. Keep all iter vars (data parallel + reduction)
+ new_block->iter_vars = reduction_block->iter_vars;
+
+ // 2. Map epilogue block vars to reduction block vars
+ std::unordered_map<Var, Var> var_map;
+ int reduction_data_par_idx = 0;
+ for (int i = 0; i < static_cast<int>(reduction_block->iter_vars.size()); ++i) {
+ const IterVar& iter_var = reduction_block->iter_vars[i];
+ if (iter_var->iter_type == IterVarType::kDataPar) {
+ // Map corresponding data parallel var from epilogue block
+ int epilogue_data_par_idx = 0;
+ for (const IterVar& epilogue_iter_var : epilogue_block_->iter_vars) {
+ if (epilogue_iter_var->iter_type == IterVarType::kDataPar) {
+ if (epilogue_data_par_idx == reduction_data_par_idx) {
+ var_map[epilogue_iter_var->var] = iter_var->var;
+ break;
+ }
+ epilogue_data_par_idx++;
+ }
+ }
+ reduction_data_par_idx++;
+ }
+ }
Review Comment:

The nested loop for mapping data-parallel `IterVar`s is functionally correct but could be simplified for better readability and maintainability. A clearer approach would be to extract the data-parallel variables from both blocks into separate lists and then map them by index. This also provides a natural place to add a check to ensure the number of data-parallel variables match, improving robustness.
```c
std::vector<Var> reduction_data_vars;
for (const IterVar& iter_var : reduction_block->iter_vars) {
if (iter_var->iter_type == IterVarType::kDataPar) {
reduction_data_vars.push_back(iter_var->var);
}
}
std::vector<Var> epilogue_data_vars;
for (const IterVar& iter_var : epilogue_block_->iter_vars) {
if (iter_var->iter_type == IterVarType::kDataPar) {
epilogue_data_vars.push_back(iter_var->var);
}
}
ICHECK_EQ(reduction_data_vars.size(), epilogue_data_vars.size())
<< "ValueError: The number of data parallel iter vars must be the same in the reduction "
"and epilogue blocks.";
std::unordered_map<Var, Var> var_map;
for (size_t i = 0; i < reduction_data_vars.size(); ++i) {
var_map[epilogue_data_vars[i]] = reduction_data_vars[i];
}
```
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,393 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ const Block& epilogue_block = epilogue_block_realize->block;
+
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
+ if (const auto* add = value.as<AddNode>()) {
+ // Check if one operand is BufferLoad from reduction buffer
+ const auto* load_a = add->a.as<BufferLoadNode>();
+ const auto* load_b = add->b.as<BufferLoadNode>();
+
+ if (load_a && load_a->buffer.same_as(inlined_buffer_)) {
+ // Pattern: temp[...] + C[...]
+ epilogue_addend_ = add->b;
+ return true;
+ } else if (load_b && load_b->buffer.same_as(inlined_buffer_)) {
+ // Pattern: C[...] + temp[...]
+ epilogue_addend_ = add->a;
+ return true;
+ }
+ }
+
+ return false;
+}
Review Comment:

The current logic in `AnalyzeEpiloguePattern` can incorrectly succeed for expressions like `temp + temp`, where `temp` is the output of the reduction. The `if`/`else if` structure will match the first operand and return `true`, setting `epilogue_addend_` to the second `temp` operand. This leads to an incorrect transformation. The pattern should only be matched if exactly one of the addition operands is a load from the reduction buffer.
```c
bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
// Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
if (const auto* add = value.as<AddNode>()) {
const auto* load_a = add->a.as<BufferLoadNode>();
const auto* load_b = add->b.as<BufferLoadNode>();
bool a_is_target = load_a && load_a->buffer.same_as(inlined_buffer_);
bool b_is_target = load_b && load_b->buffer.same_as(inlined_buffer_);
if (a_is_target != b_is_target) {
epilogue_addend_ = a_is_target ? add->b : add->a;
return true;
}
}
return false;
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "wrongtest-intellif (via GitHub)" <gi...@apache.org>.
wrongtest-intellif commented on code in PR #18418:
URL: https://github.com/apache/tvm/pull/18418#discussion_r2540310664
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,472 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
Review Comment:
could we extent this to many other kind of epilogue form in the future? (hopefully)
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,472 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
+ if (const auto* add = value.as<AddNode>()) {
+ const auto* load_a = add->a.as<BufferLoadNode>();
+ const auto* load_b = add->b.as<BufferLoadNode>();
+
+ bool a_is_target = load_a && load_a->buffer.same_as(inlined_buffer_);
+ bool b_is_target = load_b && load_b->buffer.same_as(inlined_buffer_);
+
+ // Ensure exactly one operand is from the reduction buffer
+ if (a_is_target != b_is_target) {
+ epilogue_addend_ = a_is_target ? add->b : add->a;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ReductionEpilogueFuser::IsReductionBlock(const BlockNode* block) {
+ // Check if block has reduction iter vars
+ for (const IterVar& iter : block->iter_vars) {
+ if (iter->iter_type == kCommReduce) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ReductionEpilogueFuser::ExtractEpilogueInfo() {
+ // Extract epilogue output buffer and indices
+ epilogue_output_buffer_ = inlined_store_->buffer;
+ epilogue_output_indices_ = inlined_store_->indices;
+
+ // Extract epilogue output region from epilogue block writes
+ for (const BufferRegion& write : epilogue_block_->writes) {
+ if (write->buffer.same_as(epilogue_output_buffer_)) {
+ epilogue_output_region_ = write;
+ break;
+ }
+ }
+
+ // Extract epilogue addend buffer and region from epilogue_addend_
+ if (const auto* load = epilogue_addend_.as<BufferLoadNode>()) {
+ epilogue_addend_buffer_ = load->buffer;
+ // Find the read region from epilogue block reads
+ for (const BufferRegion& read : epilogue_block_->reads) {
+ if (read->buffer.same_as(epilogue_addend_buffer_)) {
+ epilogue_addend_region_ = read;
+ break;
+ }
+ }
+ }
+}
+
+Block ReductionEpilogueFuser::CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize) {
+ ObjectPtr<BlockNode> new_block = ffi::make_object<BlockNode>(*reduction_block);
+
+ // 1. Keep all iter vars (data parallel + reduction)
+ new_block->iter_vars = reduction_block->iter_vars;
Review Comment:
seems unneccesary since `new_block` copy constructed from origin block
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For additional commands, e-mail: discuss-archive-help@tvm.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "kimm240 (via GitHub)" <gi...@apache.org>.
kimm240 commented on code in PR #18418:
URL: https://github.com/apache/tvm/pull/18418#discussion_r2540572389
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,472 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
+ if (const auto* add = value.as<AddNode>()) {
+ const auto* load_a = add->a.as<BufferLoadNode>();
+ const auto* load_b = add->b.as<BufferLoadNode>();
+
+ bool a_is_target = load_a && load_a->buffer.same_as(inlined_buffer_);
+ bool b_is_target = load_b && load_b->buffer.same_as(inlined_buffer_);
+
+ // Ensure exactly one operand is from the reduction buffer
+ if (a_is_target != b_is_target) {
+ epilogue_addend_ = a_is_target ? add->b : add->a;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ReductionEpilogueFuser::IsReductionBlock(const BlockNode* block) {
+ // Check if block has reduction iter vars
+ for (const IterVar& iter : block->iter_vars) {
+ if (iter->iter_type == kCommReduce) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ReductionEpilogueFuser::ExtractEpilogueInfo() {
+ // Extract epilogue output buffer and indices
+ epilogue_output_buffer_ = inlined_store_->buffer;
+ epilogue_output_indices_ = inlined_store_->indices;
+
+ // Extract epilogue output region from epilogue block writes
+ for (const BufferRegion& write : epilogue_block_->writes) {
+ if (write->buffer.same_as(epilogue_output_buffer_)) {
+ epilogue_output_region_ = write;
+ break;
+ }
+ }
+
+ // Extract epilogue addend buffer and region from epilogue_addend_
+ if (const auto* load = epilogue_addend_.as<BufferLoadNode>()) {
+ epilogue_addend_buffer_ = load->buffer;
+ // Find the read region from epilogue block reads
+ for (const BufferRegion& read : epilogue_block_->reads) {
+ if (read->buffer.same_as(epilogue_addend_buffer_)) {
+ epilogue_addend_region_ = read;
+ break;
+ }
+ }
+ }
+}
+
+Block ReductionEpilogueFuser::CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize) {
+ ObjectPtr<BlockNode> new_block = ffi::make_object<BlockNode>(*reduction_block);
+
+ // 1. Keep all iter vars (data parallel + reduction)
+ new_block->iter_vars = reduction_block->iter_vars;
Review Comment:
@wrongtest-intellif I've addressed your feedback and pushed the changes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For additional commands, e-mail: discuss-archive-help@tvm.apache.org
Re: [PR] [TIR][Schedule] Add FuseReductionEpilogue primitive to fuse epilogue … [tvm]
Posted by "kimm240 (via GitHub)" <gi...@apache.org>.
kimm240 commented on code in PR #18418:
URL: https://github.com/apache/tvm/pull/18418#discussion_r2540576672
##########
src/tir/schedule/primitive/compute_inline.cc:
##########
@@ -984,6 +984,472 @@ void ReverseComputeInline(ScheduleState self, const StmtSRef& consumer_block_sre
ReverseComputeInlineImpl(self, consumer_block_sref);
}
+/*!
+ * \brief Helper to fuse epilogue block into reduction block
+ * Analyzes epilogue pattern and transforms reduction init/update
+ */
+class ReductionEpilogueFuser : public BaseInliner {
+ public:
+ explicit ReductionEpilogueFuser(const Buffer& reduction_buffer, const BlockNode* reduction_block,
+ const BlockRealize& epilogue_block_realize,
+ const StmtSRef& scope_root_sref, const IRModule& mod)
+ : BaseInliner(reduction_buffer, epilogue_block_realize->block, scope_root_sref),
+ reduction_block_(reduction_block),
+ epilogue_block_(epilogue_block_realize->block.get()),
+ mod_(mod) {}
+
+ bool BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize);
+
+ // Step 2: Create single fused reduction block
+ Block CreateFusedReductionBlock(const BlockNode* reduction_block,
+ const BlockRealizeNode* reduction_realize);
+
+ private:
+ bool AnalyzeEpiloguePattern(const PrimExpr& value);
+ bool IsReductionBlock(const BlockNode* block);
+ void ExtractEpilogueInfo();
+ // Helper function to extract BufferLoad nodes from BufferStore
+ static std::vector<const BufferLoadNode*> ExtractBufferLoad(const Buffer& buffer,
+ const BufferStoreNode* from) {
+ struct Extractor : public ExprVisitor {
+ void VisitExpr_(const BufferLoadNode* load) final {
+ if (load->buffer.get() == buffer) {
+ result.push_back(load);
+ }
+ ExprVisitor::VisitExpr_(load);
+ }
+ const BufferNode* buffer;
+ std::vector<const BufferLoadNode*> result;
+ } extractor;
+ extractor.buffer = buffer.get();
+ for (const PrimExpr& expr : from->indices) {
+ extractor(expr);
+ }
+ extractor(from->value);
+ return std::move(extractor.result);
+ }
+
+ const BlockNode* reduction_block_;
+ const BlockNode* epilogue_block_;
+ const IRModule& mod_;
+ PrimExpr epilogue_addend_{nullptr}; // C[vi, vj] in D = temp + C
+ Buffer epilogue_output_buffer_{nullptr}; // Output buffer D
+ ffi::Array<PrimExpr> epilogue_output_indices_{nullptr}; // Indices of D[vi, vj]
+ BufferRegion epilogue_output_region_{nullptr}; // Write region of D
+ Buffer epilogue_addend_buffer_{nullptr}; // Addend buffer C
+ BufferRegion epilogue_addend_region_{nullptr}; // Read region of C
+};
+
+bool ReductionEpilogueFuser::BodyPatternAllowFusion(const BlockRealize& epilogue_block_realize) {
+ // 1. Validate predicate
+ if (!is_one(epilogue_block_realize->predicate)) {
+ // Failure: Predicate in epilogue block is not supported
+ return false;
+ }
+
+ // 2. Check if epilogue body is BufferStore
+ if (inlined_store_ == nullptr) {
+ // Failure: epilogue block body is not BufferStore
+ return false;
+ }
+
+ // 3. Check if epilogue reads from reduction buffer
+ std::vector<const BufferLoadNode*> loads = ExtractBufferLoad(inlined_buffer_, inlined_store_);
+ if (loads.size() == 0) {
+ // Failure: no BufferLoad from the reduction buffer
+ return false;
+ }
+
+ // 4. Analyze epilogue pattern: D[i,j] = temp[i,j] + C[i,j]
+ if (!AnalyzeEpiloguePattern(inlined_store_->value)) {
+ // Failure: epilogue is not a simple addition pattern
+ return false;
+ }
+
+ // 5. Check if producer is a reduction block
+ if (!IsReductionBlock(reduction_block_)) {
+ // Failure: producer is not a reduction block
+ return false;
+ }
+
+ // 6. Extract epilogue information (output buffer, indices, regions, etc.)
+ ExtractEpilogueInfo();
+
+ return true;
+}
+
+bool ReductionEpilogueFuser::AnalyzeEpiloguePattern(const PrimExpr& value) {
+ // Pattern: temp[i,j] + C[i,j] or C[i,j] + temp[i,j]
Review Comment:
@wrongtest-intellif Yes! Please share any technical issues that need to be resolved or any anticipated problems in pursuing this expansion.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: discuss-archive-unsubscribe@tvm.apache.org
For additional commands, e-mail: discuss-archive-help@tvm.apache.org