Files
paddlepaddle--paddle/paddle/cinn/optim/eliminate_invariant_loop.h
T
2026-07-13 12:40:42 +08:00

111 lines
3.6 KiB
C++

// Copyright (c) 2024 CINN Authors. All Rights Reserved.
//
// Licensed 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.
#pragma once
#include "paddle/cinn/ir/ir.h"
#include "paddle/cinn/ir/lowered_func.h"
namespace cinn {
namespace optim {
/**
* Eliminate serial or bound loops that have a loop-invariant body.
*
* We can eliminate a serial loop if it satisfies rule (1) and (2):
* (1) The loop variable is not used in any load/store indices or computation
* within child schedule blocks. This ensures that the loop writes the same
* value to the same index in each iteration.
* (2) It is not a Reduce (e.g. a[0] = a[0] + b[k]), because for a Reduce, even
* though its indices don't change in each iteration, its value changes.
*
* We can eliminate a bound loop if it also satisfies rule (3) and (4):
* (3) It doesn't write to the local buffer (for thread-bound loop) or shared
* memory (for block-bound loop), because the index for these storages
* implicitly includes the thread/block index, thereby violating (1).
* (4) Its child schedule blocks don't have consumers, otherwise consumers in
* other blocks/threads may read an undefined value.
*
* When a loop can be eliminated, we:
* (a) For serial loop, we just replace the For node with its body, and replace
* all use of the loop variable to 0.
* (b) For bound loop, we cannot remove the For node because it contains the
* binding info. Instead, we warp the loop body in `if (loop_var == 0)`.
*
*
* Example 1:
* serial for (k, 0, 32):
* ScheduleBlock(A):
* A[0] = B[0]
* =>
* ScheduleBlock(A):
* A[0] = B[0]
*
*
* Example 2:
* bind for (threadIdx.x, 0, 32):
* ScheduleBlock(A):
* A[0] = B[0] # assume that A is global
* =>
* bind for (threadIdx.x, 0, 32):
* if threadIdx.x == 0:
* ScheduleBlock(A):
* A[0] = B[0]
* Note:
* We don't remove a bound loop, but wrap its body in `threadIdx.x == 0`.
*
*
* Example 3:
* serial for (k, 0, 32):
* ScheduleBlock(A):
* A[0] = A[0] + B[0]
* =>
* DO NOTHING!
* Note:
* Even though k is not used in the body, as ScheduleBlock(A) contains an
* inplace operation, each iteration updates A, so we cannot eliminate it.
*
*
* Example 4:
* bind for (blockIdx.x, 0, 32):
* ScheduleBlock(A):
* A[0] = 3.14
* bind for (blockIdx.x, 0, 32):
* ScheduleBlock(B):
* B[blockIdx.x] = A[0]
* =>
* DO NOTHING!
* Note:
* If we eliminate the For node of A, only the block at `blockIdx.x == 0`
* can get the correct value of A[0] when computing B.
*
*
* Example 5:
* serial for (k, 0, 32):
* if (k * 256) + threadIdx.x < 8000:
* ScheduleBlock(A):
* A[threadIdx.x] = B[threadIdx.x]
* =>
* if threadIdx.x < 8000:
* ScheduleBlock(A):
* A[threadIdx.x] = B[threadIdx.x]
* Note:
* Although k is used in the if condition, it doesn't affect the index to
* update, but just limits how many times to update. So rewriting k to 0
* doesn't change the semantics.
*/
void EliminateInvariantLoop(ir::Expr *expr);
} // namespace optim
} // namespace cinn