[Mlir-commits] [mlir] [mlir][shard, mpi] Allowing 2d-grids and simplifying lowering shard.all_gather (PR #180243)

Rolf Morel llvmlistbot at llvm.org
Mon Feb 9 09:48:18 PST 2026


================
@@ -645,94 +645,101 @@ struct ConvertAllGatherOp : public CommOpPattern<AllGatherOp> {
     if (!memref::isStaticShapeAndContiguousRowMajor(outType))
       return op.emitError(
           "Expected static shaped memref in contiguous row-major layout.");
-    int64_t gatherAxis = adaptor.getGatherAxisAttr().getInt();
-    auto ctx = op->getContext();
 
-    // Get the right communicator
-    Value comm = getComm(*gridOp, adaptor.getGridAxes(), iBuilder);
+    auto inputShape = cast<ShapedType>(adaptor.getInput().getType()).getShape();
+    auto outputShape = outType.getShape();
+    int64_t gatherAxis = adaptor.getGatherAxisAttr().getInt();
 
-    Value nRanks =
-        mpi::CommSizeOp::create(iBuilder, iBuilder.getI32Type(), comm)
-            .getSize();
-    nRanks =
-        arith::IndexCastOp::create(iBuilder, iBuilder.getIndexType(), nRanks);
+    // Get the right communicator.
+    Value comm = getComm(*gridOp, adaptor.getGridAxes(), ib);
+
+    Value nRanksV =
+        mpi::CommSizeOp::create(ib, ib.getI32Type(), comm).getSize();
+    nRanksV = arith::IndexCastOp::create(ib, ib.getIndexType(), nRanksV);
+    int64_t nRanks = outputShape[gatherAxis] / inputShape[gatherAxis];
+    Value nRanksC = arith::ConstantIndexOp::create(ib, nRanks);
+    Value notError =
+        arith::CmpIOp::create(ib, arith::CmpIPredicate::eq, nRanksV, nRanksC);
+    cf::AssertOp::create(ib, notError,
+                         "Expected number of ranks in the communicator to "
+                         "match the output size along the gather axis divided "
+                         "by the input size along the gather axis.");
+    for (size_t i = 0; i < outputShape.size(); ++i)
+      assert(outputShape[i] == inputShape[i] || i == (size_t)gatherAxis);
+
+    // mpi.allgather always concatenates along the first dimension, so
+    // get a output buffer of shape {nRanks, dim0, ...}.
+    SmallVector<int64_t> gatherShape;
+    gatherShape.emplace_back(nRanks);
+    gatherShape.append(inputShape.begin(), inputShape.end());
+    auto gatherType = MemRefType::get(gatherShape, outType.getElementType());
+    Value finalOutput = memref::AllocOp::create(ib, gatherType);
+    // Create the MPI AllGather operation.
+    mpi::AllGatherOp::create(ib, TypeRange(), input, finalOutput, comm);
 
-    Value tmpOutput, gatherDimSz;
     if (gatherAxis == 0) {
-      tmpOutput = memref::AllocOp::create(iBuilder, outType);
-    } else {
-      // MPI's allgather always concatenates along the first dimension.
-      // Create a memref type for the output buffer with adjusted (expanded)
-      // shape.
-      SmallVector<int64_t> gatherShape(1, ShapedType::kDynamic);
-      llvm::append_range(gatherShape, outType.getShape());
-      gatherShape[gatherAxis + 1] = ShapedType::kDynamic;
-      MemRefType gatherType =
-          MemRefType::get(gatherShape, outType.getElementType());
-      gatherDimSz = arith::ConstantIndexOp::create(
-          iBuilder, outType.getDimSize(gatherAxis));
-      gatherDimSz = arith::DivSIOp::create(iBuilder, iBuilder.getIndexType(),
-                                           gatherDimSz, nRanks);
-      // Allocate output buffer
-      tmpOutput =
-          memref::AllocOp::create(iBuilder, gatherType, {nRanks, gatherDimSz});
-    }
-    // Create the MPI AllGather operation.
-    mpi::AllGatherOp::create(iBuilder, TypeRange(), input, tmpOutput, comm);
-
-    // If gather-axis!=0, copy from gathered buffer to output with the right
-    // layout.
-    Value finalOutput = tmpOutput;
-    if (gatherAxis != 0) {
-      int64_t nSrcDims = cast<ShapedType>(tmpOutput.getType()).getRank();
-      assert(nSrcDims == outType.getRank() + 1 &&
-             "Expected gathered type to have rank one more than output type.");
-
-      // Create affine map for copying from gathered buffer to output.
-      SmallVector<AffineExpr> dims;
-      dims.reserve(nSrcDims);
-      for (unsigned i = 0; i < nSrcDims; ++i)
-        dims.emplace_back(getAffineDimExpr(i, ctx));
-      AffineExpr s = getAffineSymbolExpr(0, ctx);
-      SmallVector<AffineExpr> results;
-      results.reserve(nSrcDims);
-      for (unsigned i = 0; i < nSrcDims - 1; ++i) {
-        if (i == gatherAxis)
-          results.emplace_back(dims[0] * s + dims[gatherAxis + 1]);
-        else
-          results.emplace_back(dims[i + 1]);
+      // If gather axis == 0, simply collapse the first 2 dims from {nRanks,
+      // dim0, ...} to {nRanks*dim0, ...}.
+      SmallVector<ReassociationIndices> reassociation;
+      reassociation.push_back({0, 1});
+      for (int64_t i = 2; i < (int64_t)gatherShape.size(); ++i) {
+        reassociation.push_back({i});
       }
-      auto affineMap = AffineMap::get(nSrcDims, /*symbols=*/1, results, ctx);
-
-      finalOutput = memref::AllocOp::create(iBuilder, outType);
-
-      // Now build a loop nest to copy from gathered buffer to finalOutput
-      // It would be nicer to just use a memref.transpose/collapse_shape op but
-      // these currently only support simpler cases.
-      Value zero = arith::ConstantIndexOp::create(iBuilder, 0);
-      SmallVector<Value> lbs(nSrcDims, zero);
-      SmallVector<Value> ubs;
-      for (int64_t d = 0; d < nSrcDims; ++d)
-        ubs.emplace_back(memref::DimOp::create(iBuilder, tmpOutput, d));
-      SmallVector<int64_t> steps(nSrcDims, 1);
-      auto emitCopy = [&](OpBuilder &builder, Location loc, ValueRange ivs) {
-        Value v = memref::LoadOp::create(iBuilder, tmpOutput, ivs);
-        // set symbol value
-        SmallVector<Value> ivss(ivs.begin(), ivs.end());
-        ivss.emplace_back(gatherDimSz);
-        affine::AffineStoreOp::create(iBuilder, v, finalOutput, affineMap,
-                                      ivss);
-      };
-      affine::buildAffineLoopNest(iBuilder, op->getLoc(), lbs, ubs, steps,
-                                  emitCopy);
+      finalOutput = memref::CollapseShapeOp::create(ib, outType, finalOutput,
+                                                    reassociation);
 
-      memref::DeallocOp::create(iBuilder, tmpOutput);
+      // If the op's result is a tensor, cast it to a tensor.
+      if (isa<RankedTensorType>(op.getType()))
+        finalOutput = bufferization::ToTensorOp::create(ib, op.getType(),
+                                                        finalOutput, true);
+    } else {
+      // 1. Enter tensor-land.
+      auto inType =
+          RankedTensorType::get(gatherShape, outType.getElementType());
+      finalOutput =
+          bufferization::ToTensorOp::create(ib, inType, finalOutput, true);
+
+      // 2. Permute the output buffer from {nRanks, dim0, ..., gatherAxis, ...}
+      // to {dim0, ..., nRanks, dim1,...}.
+      SmallVector<int64_t> outShapePermuted, permutation;
----------------
rolfmorel wrote:

I think Copilot is on to something: could you avoid going back to tensors and just do the same transpose and collapse on memrefs?

https://github.com/llvm/llvm-project/pull/180243


More information about the Mlir-commits mailing list