跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 29 章 编译器工程:写 pass、测试与回归

/code/29-compiler-engineering/

03-verify-pass-template/verify_local_alloc_limit.cc4069 字节
/*!
 * \file verify_local_alloc_limit.cc
 * \brief Warn when an on-chip allocation is too large to be a register array.
 *
 * 这是「verify pass」的第二个模板。它和 02 的报告型 pass 有三个刻意的不同,
 * 正好对应本章 7.4 节讲的 verify pass 三条设计规则:
 *
 *   1. **只报不改**:pass_func 永远返回原 func,检查结果走日志;
 *   2. **聚合报告**:所有发现收集进 reports_,最后合成一条警告,不刷屏;
 *   3. **给补救办法**:每条发现都带上「应该怎么改」,而不只是「你错了」。
 *
 * 骨架来源(都能在 _vendor/tilelang-v0.1.15/ 里核到行号):
 *   * `src/transform/verify_parallel_loop.cc:28-56`(visitor + For 栈)
 *   * `src/transform/verify_parallel_loop.cc:125-148`(EmitReport 的聚合与措辞)
 *   * `src/transform/verify_buffer_init.cc:216-247`(带补救建议的 Report 结构)
 *   * `src/transform/verify_buffer_init.cc:400-422`(pass 工厂 + 注册)
 *
 * 文件放置位置:``src/transform/verify_local_alloc_limit.cc``。
 *
 * 验证级别:**仅静态核对**,本文件从未编译过。
 */

#include "span_utils.h"

#include <sstream>
#include <string>
#include <vector>

#include <tvm/runtime/logging.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/stmt.h>
#include <tvm/tirx/stmt_functor.h>
#include <tvm/tirx/transform.h>

namespace tvm::tl {

using namespace tirx;
using namespace ffi;

namespace {

/*! \brief 单个缓冲区的元素数上限;超过就认为它不该待在寄存器里。 */
constexpr int64_t kMaxLocalElements = 1 << 16;

/*! \brief 作用域白名单:这些是 on-chip 分配,其余交给别的 pass 管。 */
bool IsOnChipScope(const String &scope) {
  const std::string s = scope;
  return s == "local" || s == "local.fragment" || s == "local.var" ||
         s.rfind("local.", 0) == 0;
}

/*!
 * \brief 把形状折成静态元素数;任一维不是常量就返回 -1。
 *
 * 「折不动就不报」是 verify pass 的一条通用纪律:动态形状本机证明不了,
 * 报了也是假警报,而假警报会让所有人把这条检查关掉。
 */
int64_t StaticElementCount(const Buffer &buffer) {
  int64_t total = 1;
  for (const PrimExpr &dim : buffer->shape) {
    const auto *imm = dim.as<IntImmNode>();
    if (imm == nullptr) {
      return -1;
    }
    total *= imm->value;
  }
  return total;
}

struct LocalAllocLimitVerifier : public StmtExprVisitor {
  struct Report {
    Buffer buffer;
    int64_t elements;
    Span span;
  };

  std::vector<Report> reports_;

  void VisitStmt_(const SBlockNode *op) final {
    // TileLang 的 SBlock 用 alloc_buffers 声明本块的分配;这是
    // verify_parallel_loop.cc:113-123 读 annotations 的同一位置。
    for (const Buffer &buffer : op->alloc_buffers) {
      if (!IsOnChipScope(buffer.scope())) {
        continue;
      }
      const int64_t elements = StaticElementCount(buffer);
      if (elements >= 0 && elements > kMaxLocalElements) {
        reports_.push_back({buffer, elements, op->span});
      }
    }
    StmtExprVisitor::VisitStmt_(op);
  }

  /*! \brief 全部发现合成一条警告;报告里带文件行号与补救建议。 */
  void EmitReport() const {
    if (reports_.empty()) {
      return;
    }
    std::ostringstream os;
    os << "On-chip allocation too large: " << reports_.size()
       << " buffer(s) exceed " << kMaxLocalElements << " elements\n";
    for (size_t index = 0; index < reports_.size(); ++index) {
      const Report &report = reports_[index];
      os << "  [" << index + 1 << "] `" << report.buffer->name << "` ("
         << report.buffer.scope() << ") holds " << report.elements
         << " elements" << SpanHintSuffix({report.span}) << "\n"
         << "      Keep register arrays small; move this buffer to `shared` "
            "(or a smaller tile) and copy in chunks.\n";
    }
    os << "If this is intentional, disable the check with "
          "`PassConfigKey.TL_DISABLE_LOCAL_ALLOC_LIMIT_CHECK`.";
    LOG(WARNING) << os.str();
  }
};

} // namespace

using namespace tirx::transform;

tvm::transform::Pass VerifyLocalAllocLimit() {
  auto pass_func = [=](PrimFunc f, IRModule m, PassContext ctx) {
    // 有开关的检查照这个模式写:先读 config,被关掉就原地返回。
    if (ctx->GetConfig(kDisableLocalAllocLimitCheck, Bool(false)).value()->value) {
      return f;
    }
    LocalAllocLimitVerifier verifier;
    verifier(f->body);
    verifier.EmitReport();
    return f;
  };
  return CreatePrimFuncPass(pass_func, 0, "tl.VerifyLocalAllocLimit", {});
}

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

} // namespace tvm::tl

← 浏览本章全部代码