Triton/GPU 编译布局优化

按地址连续性为 GPU 访存选择线程布局

Triton 的 Coalesce pass 根据指针连续性、对齐和线程数量选择分块布局,并按需插入布局转换以保持上下游接口。

e5e0f747e图处理与编译核验 2026-10-12
相关概念

工程问题#

张量的逻辑形状相同,不代表相邻线程会访问相邻地址。线程分工与地址步幅不匹配时,访问可能分散到更多内存事务;但修改访存布局又不能破坏上下游操作原有的张量布局。

实际实现#

编译器先运行 AxisInfo 分析,从连续性确定维度顺序,再结合可整除字节数、元素宽度、每线程上限及 CTA 内元素数量计算 sizePerThread。对相关同形同序访存综合考虑后构造 BlockedEncoding;随后转换需要分布式布局的操作数并重建访存操作。普通结果类型变化时再转换回原布局;异步 global-to-local copy 保持原有结果类型。

处理流程#

  1. 分析指针张量各维度的连续性与对齐信息
  2. 选择优先连续维,并计算受对齐和操作上限限制的每线程元素数
  3. 结合线程数量及相关访存,构造 BlockedEncoding
  4. 转换需要更换布局的操作数并重建操作;结果类型发生变化时再恢复原布局

源码片段#

固定版本 · 原文摘录
lib/Dialect/TritonGPU/Transforms/Coalesce.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L84–L105
查看原文件
84    llvm::MapVector<Operation *, Attribute> layoutMap;85    int threadsPerWarp = TritonGPUDialect::getThreadsPerWarp(moduleOp);86    moduleOp.walk([&](Operation *curr) {87      Value ptr = getMemAccessPtr(curr);88      if (!ptr)89        return;90      // We only convert `tensor<tt.ptr<>>` load/store91      bool isPtrTensor = false;92      if (auto tensorType = dyn_cast<RankedTensorType>(ptr.getType()))93        isPtrTensor = isa<PointerType>(tensorType.getElementType());94      if (!isPtrTensor)95        return;96      int numWarps = lookupNumWarps(curr);9798      auto tensorType = cast<RankedTensorType>(ptr.getType());99      CGAEncodingAttr cgaLayout = getCGALayout(tensorType.getEncoding());100      SmallVector<int64_t> shapePerCTA = getShapePerCTA(tensorType);101      auto layout =102          buildCoalescedEncoding(axisInfoAnalysis, curr, numWarps,103                                 threadsPerWarp, cgaLayout, shapePerCTA);104      layoutMap[curr] = layout;105    });
CoalescePass::runOnOperation

只对可识别的指针张量访存选择此布局,按每个操作的 numWarps 和 CTA 形状调用构造函数。

CPPMIT
lib/Dialect/TritonGPU/Transforms/CoalesceUtils.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L31–L51
查看原文件
31  auto contiguity = axisInfoAnalysis.getAxisInfo(ptr)->getContiguity();32  SmallVector<unsigned> order = getOrderFromContiguity(contiguity);33  LDBG("order=[" << triton::join(order, ", ") << "]");3435  auto matchesShape = [&refTensorType](const Value &val) {36    auto rttType = dyn_cast<RankedTensorType>(val.getType());37    return rttType && rttType.getShape() == refTensorType.getShape();38  };3940  // The desired divisibility is the maximum divisibility among all dependent41  // pointers which have the same shape and order as `ptr`.42  llvm::SmallSetVector<Operation *, 32> memAccessesSameOrder;43  memAccessesSameOrder.insert(op);44  if (ptr.getDefiningOp()) {45    for (Operation *use : mlir::getSlice(op)) {46      Value val = getMemAccessPtr(use);47      if (!val || !matchesShape(val) || memAccessesSameOrder.contains(use))48        continue;49      auto currOrder = getOrderFromContiguity(50          axisInfoAnalysis.getAxisInfo(val)->getContiguity());51      if (order == currOrder) {
buildCoalescedEncoding:维度顺序

由指针连续性确定维度顺序,并寻找同形且同顺序的相关访存;不是仅按张量的末维统一布局。

CPPMIT
lib/Dialect/TritonGPU/Transforms/Utility.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L177–L198
查看原文件
177unsigned getNumElementsPerThread(Operation *op, SmallVector<unsigned> order,178                                 ModuleAxisInfoAnalysis &axisInfoAnalysis,179                                 ArrayRef<int64_t> shapePerCTA) {180  Value val = getMemAccessPtr(op);181  auto ty = cast<RankedTensorType>(val.getType());182  AxisInfo &valInfo = *axisInfoAnalysis.getAxisInfo(val);183  unsigned elemNumBits = getElementBitWidth(ty);184  unsigned elemNumBytes = std::max(elemNumBits / 8, 1u);185  unsigned maxMultipleBytes = valInfo.getDivisibility(order[0]);186  unsigned maxMultiple = std::max(maxMultipleBytes / elemNumBytes, 1u);187  unsigned maxContig =188      std::min(valInfo.getContiguity(order[0]), shapePerCTA[order[0]]);189  unsigned alignment = std::min(maxMultiple, maxContig);190  unsigned maxElementsPerThread = getMaxElementsPerThread(op);191  unsigned currPerThread = std::min(alignment, maxElementsPerThread);192  LDBG("elemNumBytes: " << elemNumBytes193                        << ", divisibility: " << maxMultipleBytes194                        << ", contig: " << valInfo.getContiguity(order[0])195                        << ", maximum: " << maxElementsPerThread196                        << ", alignment: " << alignment);197  return currPerThread;198}
getNumElementsPerThread

把地址可整除性换算为元素对齐,再与连续长度、CTA 形状和操作限制取交集,决定每线程可处理的元素数。

CPPMIT
lib/Dialect/TritonGPU/Transforms/CoalesceUtils.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L76–L94
查看原文件
76  perThread = std::min<int>(perThread, std::max(numElems / numThreads, 1));77  LDBG("perThread: " << perThread);7879  if (!dyn_cast<triton::LoadOp>(op)) {80    // For ops that can result in a global memory write, we should enforce81    // that each thread handles at most 128 bits, which is the widest82    // available vectorized store op; otherwise, the store will have "gaps"83    // in the memory write at the warp level, resulting in worse performance.84    // For loads, we can expect that the gaps won't matter due to the L185    // cache.86    perThread = std::min<int>(87        perThread,88        getNumElementsPerThread(op, order, axisInfoAnalysis, shapePerCTA));89  }90  SmallVector<unsigned> sizePerThread(refTensorType.getRank(), 1);91  sizePerThread[order[0]] = perThread;92  return BlockedEncodingAttr::get(op->getContext(), refTensorType.getShape(),93                                  sizePerThread, order, numWarps,94                                  threadsPerWarp, cgaLayout);
buildCoalescedEncoding:构造布局

限制每线程元素数,并对非普通 load 的写入路径再次收紧上限,最后构造 sizePerThread 与 BlockedEncoding。

CPPMIT
lib/Dialect/TritonGPU/Transforms/Utility.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L1336–L1356
查看原文件
1336Operation *convertDistributedOpEncoding(Attribute encoding, Operation *op) {1337  OpBuilder builder(op);1338  // Convert operands1339  SmallVector<Value, 4> newArgs;1340  for (auto &opOperand : op->getOpOperands()) {1341    Value operand = opOperand.get();1342    if (isDistributedOpEncodingOperand(opOperand)) {1343      Type newType = getNewType(operand.getType(), encoding);1344      newArgs.push_back(triton::gpu::ConvertLayoutOp::create(1345          builder, op->getLoc(), newType, operand));1346    } else {1347      newArgs.push_back(operand);1348    }1349  }13501351  // Convert output types1352  SmallVector<Type, 4> newTypes;1353  for (auto t : op->getResultTypes()) {1354    bool isAsync = isa<triton::gpu::AsyncCopyGlobalToLocalOp>(op);1355    newTypes.push_back(isAsync ? t : getNewType(t, encoding));1356  }
convertDistributedOpEncoding:输入转换

为需要分布式布局的操作数插入 ConvertLayout,标量等不需转换的操作数原样保留;AsyncCopyGlobalToLocalOp 的结果类型也保持原样。

CPPMIT
lib/Dialect/TritonGPU/Transforms/Utility.cppe5e0f747e43010f30f9bfdadd546668e6b191fa0 · L1358–L1372
查看原文件
1358  // Construct new op with the new encoding1359  Operation *newOp = builder.create(op->getLoc(), op->getName().getIdentifier(),1360                                    newArgs, newTypes, op->getAttrs());13611362  // Cast the results back to the original layout1363  for (size_t i = 0; i < op->getNumResults(); i++) {1364    Value newResult = newOp->getResult(i);1365    if (newTypes[i] != op->getResultTypes()[i]) {1366      newResult = triton::gpu::ConvertLayoutOp::create(1367          builder, op->getLoc(), op->getResult(i).getType(), newResult);1368    }1369    op->getResult(i).replaceAllUsesWith(newResult);1370  }1371  op->erase();1372  return newOp;
convertDistributedOpEncoding:替换与还原

以选定的操作数与结果类型重建操作,仅在结果类型改变时插入恢复布局的 ConvertLayout,替换使用者后删除旧操作。

CPPMIT

片段保持固定提交的连续原文;完整源码与许可证保存在本地。每段说明交代所在分支,省略部分可通过出处查看。

收益与取舍#

工程分析

带来的收益

  • 让线程分工适应实际地址模式,增加合并访存和向量化的机会
  • 维持上下游可见的结果类型与布局,使其他操作不必在同一 pass 中全部重写

付出的代价

  • AxisInfo 提供的是编译期可证明的信息;无法证明连续或对齐时,布局选择会更保守
  • 布局转换本身可能产生指令或数据交换,最终收益还取决于后续优化、目标硬件和访问模式
  • 这是一项局部布局启发式,不保证最少内存事务,也不是 FlashAttention 那样的完整算子分块算法

可比较的选择

保持原始布局不引入额外转换,但原有线程分工可能导致分散访存。

在内核中手动指定访问与布局便于针对固定形状调优,但需要维护更多硬件和形状分支。

版本与适用范围#

对应固定提交的普通指针张量访存布局选择分支。descriptor load/store 使用单独选择函数,本文不把两者混为一谈;原子操作等每线程上限由 getMaxElementsPerThread 进一步限定。convertDistributedOpEncoding 对 AsyncCopyGlobalToLocalOp 保持结果类型,只有类型改变的结果才插入恢复转换,不能认为所有操作两侧都必定产生 ConvertLayout。

参考来源#

源码、官方文档与原始论文
01
Triton · CoalescePass::runOnOperation

lib/Dialect/TritonGPU/Transforms/Coalesce.cpp · CoalescePass::runOnOperation

源码e5e0f747e43010f30f9bfdadd546668e6b191fa0
02
Triton · buildCoalescedEncoding:维度顺序

lib/Dialect/TritonGPU/Transforms/CoalesceUtils.cpp · buildCoalescedEncoding:维度顺序

源码e5e0f747e43010f30f9bfdadd546668e6b191fa0
03
Triton · getNumElementsPerThread

lib/Dialect/TritonGPU/Transforms/Utility.cpp · getNumElementsPerThread

源码e5e0f747e43010f30f9bfdadd546668e6b191fa0
9 个匹配案例