GitHub

@@ -11,14 +11,11 @@

1111

#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"

1212

#include "mlir/Dialect/Func/IR/FuncOps.h"

1313

#include "mlir/Dialect/GPU/IR/GPUDialect.h"

14-

#include "mlir/Dialect/Linalg/Utils/Utils.h"

1514

#include "mlir/Dialect/MemRef/IR/MemRef.h"

16-

#include "mlir/Dialect/PDL/IR/PDLOps.h"

1715

#include "mlir/Dialect/SCF/IR/SCF.h"

1816

#include "mlir/Dialect/Vector/IR/VectorOps.h"

1917

#include "mlir/Dialect/Vector/Transforms/VectorDistribution.h"

2018

#include "mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h"

21-

#include "mlir/IR/OpImplementation.h"

2219

#include "mlir/IR/Region.h"

2320

#include "mlir/Transforms/GreedyPatternRewriteDriver.h"

2421

@@ -40,20 +37,20 @@ void mlir::iree_compiler::registerTransformDialectLLVMGPUExtension(

4037

// TODO: Maybe we need both a transform.iree.cpu.bufferize and a

4138

// transform.iree.gpu.bufferize rather than a single common bufferize op?

423943-

/// Apply the permutation `perm` to `vals.

44-

/// Return failure if perm is not a permutation.

40+

/// Apply the permutation `perm` to `vals; i.e. vals[i] is stored into

41+

/// res[perm[i]] Return failure if perm is not a permutation.

4542

// TODO: upstream as extraClassDeclaration once stabilized.

4643

template <typename T>

4744

static FailureOr<SmallVector<T>> permute(const SmallVector<T> &vals,

4845

ArrayRef<int64_t> perm) {

4946

if (vals.size() != perm.size()) return failure();

5047

SmallVector<T> result(vals.size());

5148

SmallVector<bool> seen(vals.size());

52-

for (const auto &it : llvm::zip(perm, vals)) {

49+

for (auto [idx, val] : llvm::zip(perm, vals)) {

5350

// Already seen, invalid thread_dim_mapping.

54-

if (seen[std::get<0>(it)]) return failure();

55-

result[std::get<0>(it)] = std::get<1>(it);

56-

seen[std::get<0>(it)] = true;

51+

if (seen[idx]) return failure();

52+

result[idx] = val;

53+

seen[idx] = true;

5754

}

5855

// Some not seen, invalid thread_dim_mapping.

5956

if (!llvm::all_of(seen, [](bool b) { return b; })) return failure();

@@ -64,7 +61,7 @@ static FailureOr<SmallVector<T>> permute(const SmallVector<T> &vals,

6461

/// `foreachThreadOp` to `values`.

6562

// TODO: upstream as extraClassDeclaration once stabilized.

6663

template <typename T>

67-

static FailureOr<SmallVector<T>> getPermuted(

64+

static FailureOr<SmallVector<T>> getValuesPermutedByThreadMapping(

6865

scf::ForeachThreadOp foreachThreadOp, const SmallVector<T> &values) {

6966

// Apply mapping permutation if specified.

7067

auto mapping = foreachThreadOp.getThreadDimMapping();

@@ -84,7 +81,7 @@ static FailureOr<SmallVector<OpFoldResult>> getNumThreads(

8481

OpBuilder &b, scf::ForeachThreadOp foreachThreadOp) {

8582

SmallVector<OpFoldResult> threadCount = foreachThreadOp.getNumThreads();

8683

threadCount.resize(3, b.getIndexAttr(1));

87-

return getPermuted(foreachThreadOp, threadCount);

84+

return getValuesPermutedByThreadMapping(foreachThreadOp, threadCount);

8885

}

89869087

/// Helper to get the thread indices of a `foreachThreadOp` after applying the

@@ -94,7 +91,7 @@ static FailureOr<SmallVector<Value>> getThreadIndices(

9491

OpBuilder &b, scf::ForeachThreadOp foreachThreadOp) {

9592

SmallVector<Value> threadCount = foreachThreadOp.getThreadIndices();

9693

threadCount.resize(3, Value());

97-

return getPermuted(foreachThreadOp, threadCount);

94+

return getValuesPermutedByThreadMapping(foreachThreadOp, threadCount);

9895

}

999610097

//===---------------------------------------------------------------------===//

@@ -120,9 +117,9 @@ mlir::iree_compiler::rewriteForeachThreadToGpu(

120117

}))

121118

return foreachThreadOp->emitError("unsupported dynamic workgroup size");

122119123-

SmallVector<int64_t> workgroupSizes;

124-

for (OpFoldResult ofr : *maybeWorkgroupSizes)

125-

workgroupSizes.push_back(getConstantIntValue(ofr).value());

120+

SmallVector<int64_t> workgroupSizes = llvm::to_vector(llvm::map_range(

121+

*maybeWorkgroupSizes,

122+

[](OpFoldResult ofr) { return getConstantIntValue(ofr).value(); }));

126123127124

// Step 1. Create the gpu.thread ops

128125

Location loc = foreachThreadOp.getLoc();

@@ -138,11 +135,12 @@ mlir::iree_compiler::rewriteForeachThreadToGpu(

138135139136

// Step 2. Maybe create conditionals to predicate the region.

140137

Value predicate;

141-

for (auto it : llvm::zip(threadOps, workgroupSizes, globalWorkgroupSizes)) {

142-

auto threadId = std::get<0>(it);

143-

auto workgroupSize = std::get<1>(it);

144-

auto globalWorkgroupSize = std::get<2>(it);

145-

assert(workgroupSize <= globalWorkgroupSize && "workgroup size overflow");

138+

for (auto [threadId, workgroupSize, globalWorkgroupSize] :

139+

llvm::zip(threadOps, workgroupSizes, globalWorkgroupSizes)) {

140+

if (workgroupSize > globalWorkgroupSize) {

141+

return foreachThreadOp.emitOpError("workgroup size overflow: ")

142+

<< workgroupSize << " > " << globalWorkgroupSize;

143+

}

146144

if (workgroupSize == globalWorkgroupSize) continue;

147145

Value tmpPredicate = rewriter.create<arith::CmpIOp>(

148146

loc, arith::CmpIPredicate::ult, threadId,

@@ -220,6 +218,7 @@ transform_dialect::ForeachThreadToGpuAndTranslationInfo::applyToOne(

220218221219

SmallVector<int64_t> workgroupSize =

222220

extractFromI64ArrayAttr(getWorkgroupSize());

221+

// TODO: no magic constant but IREE uses this extensively.

223222

workgroupSize.resize(/*size=*/3, /*value=*/1);

224223

SimplePatternRewriter rewriter(target);

225224

auto walkResult = target->walk([&](scf::ForeachThreadOp foreachThreadOp) {

@@ -242,13 +241,39 @@ transform_dialect::ForeachThreadToGpuAndTranslationInfo::applyToOne(

242241

}

243242244243

//===---------------------------------------------------------------------===//

245-

// VectorWarpExecuteOnLane0Op.

244+

// VectorToWarpExecuteOnLane0Op.

246245

//===---------------------------------------------------------------------===//

247246247+

/// Helper method to replace all uses of the laneId operand by the constant

248+

/// 0 inside the region. This is a necessary prerequisite to perform any kind of

249+

/// hoisting of IR that is inside the region.

250+

/// Return success if any replacement occurred, failure otherwise.

251+

// TODO: this is currently brittle, what we really need here is a scope-aware

252+

// SCCP.

253+

static LogicalResult replaceAllUsesOfLaneWithin(

254+

RewriterBase &b, vector::WarpExecuteOnLane0Op executeOp) {

255+

OpBuilder::InsertionGuard g(b);

256+

b.setInsertionPoint(executeOp);

257+

Value zero = b.create<arith::ConstantIndexOp>(executeOp.getLoc(), 0);

258+

b.setInsertionPointToStart(&executeOp.getWarpRegion().front());

259+

Value laneId = executeOp.getLaneid();

260+

bool applied = false;

261+

for (Operation *user : llvm::make_early_inc_range(laneId.getUsers())) {

262+

if (!executeOp->isProperAncestor(user)) continue;

263+

b.startRootUpdate(user);

264+

user->replaceUsesOfWith(laneId, zero);

265+

b.finalizeRootUpdate(user);

266+

applied = true;

267+

}

268+

return success(applied);

269+

}

270+271+

/// Return the gpu::ThreadIdOp for which the predicate if equivalent to

272+

/// `if (threadIdx.x == 0)`.

248273

// TODO: Figure out the proper canonicalization and drop the complexity here.

249274

// TODO: More sophisticated detection for matching

250275

// (threadIdx.x == 0 && other stuff not involving threadIdx.x)

251-

static LogicalResult isThreadIdxxZeroPredicate(scf::IfOp ifOp) {

276+

static FailureOr<gpu::ThreadIdOp> isThreadIdxxZeroPredicate(scf::IfOp ifOp) {

252277

if (!ifOp || ifOp.getNumResults() > 0 ||

253278

ifOp.getThenRegion().getBlocks().size() != 1 ||

254279

!ifOp.getElseRegion().empty())

@@ -261,32 +286,34 @@ static LogicalResult isThreadIdxxZeroPredicate(scf::IfOp ifOp) {

261286

auto ULT = arith::CmpIPredicate::ult;

262287

auto ULE = arith::CmpIPredicate::ule;

263288

if (auto threadIdOp = pred.getLhs().getDefiningOp<gpu::ThreadIdOp>()) {

289+

if (threadIdOp.dimension() != gpu::Dimension::x) return failure();

264290

if (pred.getPredicate() == EQ && isConstantIntValue(pred.getRhs(), 0))

265-

return success();

291+

return threadIdOp;

266292

if (pred.getPredicate() == SLE && isConstantIntValue(pred.getRhs(), 0))

267-

return success();

293+

return threadIdOp;

268294

if (pred.getPredicate() == ULE && isConstantIntValue(pred.getRhs(), 0))

269-

return success();

295+

return threadIdOp;

270296

if (pred.getPredicate() == SLT && isConstantIntValue(pred.getRhs(), 1))

271-

return success();

297+

return threadIdOp;

272298

if (pred.getPredicate() == ULT && isConstantIntValue(pred.getRhs(), 1))

273-

return success();

299+

return threadIdOp;

274300

}

275301

auto SGT = arith::CmpIPredicate::sgt;

276302

auto SGE = arith::CmpIPredicate::sge;

277303

auto UGT = arith::CmpIPredicate::ugt;

278304

auto UGE = arith::CmpIPredicate::uge;

279305

if (auto threadIdOp = pred.getRhs().getDefiningOp<gpu::ThreadIdOp>()) {

306+

if (threadIdOp.dimension() != gpu::Dimension::x) return failure();

280307

if (pred.getPredicate() == EQ && isConstantIntValue(pred.getLhs(), 0))

281-

return success();

308+

return threadIdOp;

282309

if (pred.getPredicate() == SGE && isConstantIntValue(pred.getLhs(), 0))

283-

return success();

310+

return threadIdOp;

284311

if (pred.getPredicate() == UGE && isConstantIntValue(pred.getLhs(), 0))

285-

return success();

312+

return threadIdOp;

286313

if (pred.getPredicate() == SGT && isConstantIntValue(pred.getLhs(), 1))

287-

return success();

314+

return threadIdOp;

288315

if (pred.getPredicate() == UGT && isConstantIntValue(pred.getLhs(), 1))

289-

return success();

316+

return threadIdOp;

290317

}

291318

return failure();

292319

}

@@ -295,16 +322,19 @@ struct VectorDistributionResult {

295322

vector::WarpExecuteOnLane0Op warpOp;

296323

};

297324298-

static FailureOr<VectorDistributionResult> vectorDistribution(

325+

static FailureOr<VectorDistributionResult> rewriteScfIfAsWarpExecuteOnLane0(

299326

PatternRewriter &rewriter, Location loc, scf::IfOp ifOp,

300327

int64_t workgroupSizeX, int64_t warpSize) {

301328

// Bail if cond is not `if (threadIdx.x == 0)`.

302-

if (failed(isThreadIdxxZeroPredicate(ifOp))) return failure();

329+

FailureOr<gpu::ThreadIdOp> maybeThreadIdxxOp =

330+

isThreadIdxxZeroPredicate(ifOp);

331+

if (failed(maybeThreadIdxxOp)) return failure();

303332304333

// All the code below will be executed on a single warp given a fixed

305334

// (threadIdxy, threadIdxz).

306-

Value threadIdxx = rewriter.create<gpu::ThreadIdOp>(

307-

loc, rewriter.getIndexType(), gpu::Dimension::x);

335+

// Note, we reuse `maybeThreadIdxxOp` here because we later want to replace

336+

// this op instance by 0 without relying on CSE or canonicalizations.

337+

Value threadIdxx = *maybeThreadIdxxOp;

308338309339

assert(workgroupSizeX % warpSize == 0);

310340

if (workgroupSizeX != warpSize) {

@@ -334,6 +364,13 @@ static FailureOr<VectorDistributionResult> vectorDistribution(

334364

// Erase old op.

335365

rewriter.eraseOp(ifOp);

336366367+

// This simple rewrite propagates zero in lieu of laneId within the

368+

// warp_execute_on_lane_0 op.

369+

// Atm, this **must** occur before any hoisting of code.

370+

// TODO: Replace this by a more robust scoped SCCP that will make it more

371+

// robust re. hoisting.

372+

(void)replaceAllUsesOfLaneWithin(rewriter, warpOp);

373+337374

// Hoist the scalar code outside of the warp region.

338375

// Note: moving code does not require a listener.

339376

vector::moveScalarUniformCode(warpOp);

@@ -355,15 +392,15 @@ static HAL::ExecutableExportOp getExecutableExportOpForFunc(

355392

}

356393357394

DiagnosedSilenceableFailure

358-

transform_dialect::VectorWarpExecuteOnLane0Op::applyToOne(

395+

transform_dialect::VectorToWarpExecuteOnLane0Op::applyToOne(

359396

scf::IfOp target, SmallVectorImpl<Operation *> &results,

360397

transform::TransformState &state) {

361398

if (!isa<HAL::ExecutableOp, HAL::ExecutableVariantOp>(state.getTopLevel())) {

362399

state.getTopLevel()->emitOpError(

363400

"requires HAL::ExecutableOp or HAL::ExecutableVariantOp toplevel so "

364-

"that "

365-

"IR is properly isolated. This is required so we can safely inspect "

366-

"the HAL::ExecutableExportOp under multi-threaded pass assumptions.");

401+

"that IR is properly isolated. This is required so we can safely "

402+

"inspect the HAL::ExecutableExportOp under multi-threaded pass "

403+

"assumptions.");

367404

return DiagnosedSilenceableFailure(reportUnknownTransformError(target));

368405

}

369406

@@ -402,8 +439,8 @@ transform_dialect::VectorWarpExecuteOnLane0Op::applyToOne(

402439403440

SimplePatternRewriter rewriter(target);

404441

FailureOr<VectorDistributionResult> vectorDistributionResult =

405-

vectorDistribution(rewriter, target->getLoc(), target, workgroupSizeX,

406-

warpSize);

442+

rewriteScfIfAsWarpExecuteOnLane0(rewriter, target->getLoc(), target,

443+

workgroupSizeX, warpSize);

407444

if (failed(vectorDistributionResult)) {

408445

// Return a silenceable failure and set the expected 1 result to nullptr.

409446

results.assign(1, nullptr);

@@ -477,6 +514,7 @@ static LogicalResult applyMultiReductionLoweringPatterns(Operation *target) {

477514478515

MLIRContext *ctx = target->getContext();

479516

RewritePatternSet patterns(ctx);

517+480518

vector::populateVectorMultiReductionLoweringPatterns(

481519

patterns, vector::VectorMultiReductionLowering::InnerReduction);

482520

patterns.add<InsertElementToBroadcast>(ctx);

Read the original on github.com ↗