@@ -1399,6 +1399,347 @@ struct ReduceConverter : public OpConversionPattern<triton::ReduceOp> {
13991399 }
14001400};
14011401
1402+ class VarMeanConverter : public OpConversionPattern <triton::ReduceOp> {
1403+ using OpConversionPattern<triton::ReduceOp>::OpConversionPattern;
1404+ // We're looking for an op that looks like this:
1405+ //
1406+ // %26:3 = "tt.reduce"(%25#1, %25#2, %25#0) <{axis = 1 : i32}> ({
1407+ // ^bb0(%arg6: f32, %arg7: f32, %arg8: f32, %arg9: f32, %arg10: f32,
1408+ // %arg11: f32):
1409+ // %33 = arith.addf %arg7, %arg10 : f32
1410+ // %34 = arith.maxnumf %33, %cst : f32
1411+ // %35 = arith.mulf %arg6, %arg7 : f32
1412+ // %36 = arith.mulf %arg9, %arg10 : f32
1413+ // %37 = arith.addf %35, %36 : f32
1414+ // %38 = arith.divf %37, %34 : f32
1415+ // %39 = arith.mulf %35, %arg6 : f32
1416+ // %40 = arith.addf %arg8, %39 : f32
1417+ // %41 = arith.addf %40, %arg11 : f32
1418+ // %42 = arith.mulf %36, %arg9 : f32
1419+ // %43 = arith.addf %41, %42 : f32
1420+ // %44 = arith.mulf %33, %38 : f32
1421+ // %45 = arith.mulf %44, %38 : f32
1422+ // %46 = arith.subf %43, %45 : f32
1423+ // tt.reduce.return %38, %33, %46 : f32, f32, f32
1424+ // }) : (tensor<8x2048xf32>, tensor<8x2048xf32>, tensor<8x2048xf32>) ->
1425+ // (tensor<8xf32>, tensor<8xf32>, tensor<8xf32>)
1426+ //
1427+ // The above mlir code is lowered from this combinator in triton's
1428+ // standard.py:
1429+ //
1430+ // def welford_func(mean_x, count_x, M_x, mean_y, count_y, M_y):
1431+ // count = count_x + count_y
1432+ // _count = tl.maximum(count, 1)
1433+ // mc_x = mean_x * count_x
1434+ // mc_y = mean_y * count_y
1435+ // mean = (mc_x + mc_y) / _count
1436+ // M = M_x + mc_x * mean_x + M_y + mc_y * mean_y - count * mean * mean
1437+ // return mean, count, M
1438+
1439+ Value getInitTensor (ConversionPatternRewriter &rewriter,
1440+ ArrayRef<int64_t > shape, Value fillValue,
1441+ Location loc) const {
1442+ Value initTensor =
1443+ rewriter.create <tensor::EmptyOp>(loc, shape, fillValue.getType ());
1444+ return rewriter
1445+ .create <linalg::FillOp>(loc, ValueRange{fillValue},
1446+ ValueRange{initTensor})
1447+ .result ();
1448+ }
1449+
1450+ LogicalResult checkConstFloat (Value value, float val) const {
1451+ if (auto constOp = dyn_cast<arith::ConstantOp>(value.getDefiningOp ())) {
1452+ if (auto floatAttr = dyn_cast<FloatAttr>(constOp.getValue ())) {
1453+ if (floatAttr.getValueAsDouble () == val) {
1454+ return success ();
1455+ }
1456+ }
1457+ }
1458+ return failure ();
1459+ }
1460+
1461+ LogicalResult matchVarMeanBody (Value mean_x, Value count_x, Value M_x,
1462+ Value mean_y, Value count_y, Value M_y,
1463+ mlir::Block::iterator &it,
1464+ Operation *block_teminator) const {
1465+
1466+ // %33 = arith.addf %arg7, %arg10 : f32 // count = count_x + count_y
1467+ // %34 = arith.maxnumf %33, %cst : f32 // _count = tl.maximum(count, 1)
1468+
1469+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1470+ auto addOp0 = dyn_cast<arith::AddFOp>(*it++);
1471+ if (addOp0) {
1472+ if (count_x != addOp0.getLhs () || count_y != addOp0.getRhs ()) {
1473+ return failure ();
1474+ }
1475+ } else {
1476+ return failure ();
1477+ }
1478+
1479+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1480+ auto maxOp0 = dyn_cast<arith::MaxNumFOp>(*it++);
1481+ if (maxOp0) {
1482+ if (maxOp0.getLhs () != addOp0) {
1483+ return failure ();
1484+ }
1485+ if (failed (checkConstFloat (maxOp0.getRhs (), 1 .f ))) {
1486+ return failure ();
1487+ }
1488+ } else {
1489+ return failure ();
1490+ }
1491+
1492+ // %35 = arith.mulf %arg6, %arg7 : f32 //mc_x = mean_x * count_x
1493+ // %36 = arith.mulf %arg9, %arg10 : f32 //mc_y = mean_y * count_y
1494+
1495+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1496+ auto mulOp0 = dyn_cast<arith::MulFOp>(*it++);
1497+ if (mulOp0) {
1498+ if (mean_x != mulOp0.getLhs () || count_x != mulOp0.getRhs ()) {
1499+ return failure ();
1500+ }
1501+ } else {
1502+ return failure ();
1503+ }
1504+
1505+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1506+ auto mulOp1 = dyn_cast<arith::MulFOp>(*it++);
1507+ if (mulOp1) {
1508+ if (mean_y != mulOp1.getLhs () || count_y != mulOp1.getRhs ()) {
1509+ return failure ();
1510+ }
1511+ } else {
1512+ return failure ();
1513+ }
1514+
1515+ // mean = (mc_x + mc_y) / _count
1516+ //
1517+ // %37 = arith.addf %35, %36 : f32 // sum_mc = mc_x + mc_y
1518+ // %38 = arith.divf %37, %34 : f32 // mean = sum_mc / _count
1519+
1520+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1521+ auto addOp1 = dyn_cast<arith::AddFOp>(*it++);
1522+ if (addOp1) {
1523+ if (addOp1.getLhs () != mulOp0 || addOp1.getRhs () != mulOp1) {
1524+ return failure ();
1525+ }
1526+ } else {
1527+ return failure ();
1528+ }
1529+
1530+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1531+ auto divOp0 = dyn_cast<arith::DivFOp>(*it++);
1532+ if (divOp0) {
1533+ if (divOp0.getLhs () != addOp1 || divOp0.getRhs () != maxOp0) {
1534+ return failure ();
1535+ }
1536+ } else {
1537+ return failure ();
1538+ }
1539+
1540+ // M = M_x + mc_x * mean_x + M_y + mc_y * mean_y - count * mean * mean
1541+ //
1542+ // %39 = arith.mulf %35, %arg6 : f32 // part_x = mc_x * mean_x
1543+ // %40 = arith.addf %arg8, %39 : f32 // item_1 = M_x + part_x
1544+ // %41 = arith.addf %40, %arg11 : f32 // item_2 = item_1 + M_y
1545+ // %42 = arith.mulf %36, %arg9 : f32 // part_y = mc_y * mean_y
1546+ // %43 = arith.addf %41, %42 : f32 // item_3 = iterm_2 + part_y
1547+ // %44 = arith.mulf %33, %38 : f32 // mean_0 = count * mean
1548+ // %45 = arith.mulf %44, %38 : f32 // mean_1 = mean_0 * mean
1549+ // %46 = arith.subf %43, %45 : f32 // M = item_3 - mean_1
1550+
1551+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1552+ auto mulOp2 = dyn_cast<arith::MulFOp>(*it++);
1553+ if (mulOp2) {
1554+ if (mulOp2.getLhs () != mulOp0 || mulOp2.getRhs () != mean_x) {
1555+ return failure ();
1556+ }
1557+ } else {
1558+ return failure ();
1559+ }
1560+
1561+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1562+ auto addOp2 = dyn_cast<arith::AddFOp>(*it++);
1563+ if (addOp2) {
1564+ if (addOp2.getLhs () != M_x || addOp2.getRhs () != mulOp2) {
1565+ return failure ();
1566+ }
1567+ } else {
1568+ return failure ();
1569+ }
1570+
1571+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1572+ auto addOp3 = dyn_cast<arith::AddFOp>(*it++);
1573+ if (addOp3) {
1574+ if (addOp3.getLhs () != addOp2 || addOp3.getRhs () != M_y) {
1575+ return failure ();
1576+ }
1577+ } else {
1578+ return failure ();
1579+ }
1580+
1581+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1582+ auto mulOp3 = dyn_cast<arith::MulFOp>(*it++);
1583+ if (mulOp3) {
1584+ if (mulOp3.getLhs () != mulOp1 || mulOp3.getRhs () != mean_y) {
1585+ return failure ();
1586+ }
1587+ } else {
1588+ return failure ();
1589+ }
1590+
1591+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1592+ auto addOp4 = dyn_cast<arith::AddFOp>(*it++);
1593+ if (addOp4) {
1594+ if (addOp4.getLhs () != addOp3 || addOp4.getRhs () != mulOp3) {
1595+ return failure ();
1596+ }
1597+ } else {
1598+ return failure ();
1599+ }
1600+
1601+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1602+ auto mulOp4 = dyn_cast<arith::MulFOp>(*it++);
1603+ if (mulOp4) {
1604+ if (mulOp4.getLhs () != addOp0 || mulOp4.getRhs () != divOp0) {
1605+ return failure ();
1606+ }
1607+ } else {
1608+ return failure ();
1609+ }
1610+
1611+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1612+ auto mulOp5 = dyn_cast<arith::MulFOp>(*it++);
1613+ if (mulOp5) {
1614+ if (mulOp5.getLhs () != mulOp4 || mulOp5.getRhs () != divOp0) {
1615+ return failure ();
1616+ }
1617+ } else {
1618+ return failure ();
1619+ }
1620+
1621+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1622+ auto subOp = dyn_cast<arith::SubFOp>(*it++);
1623+ if (subOp) {
1624+ if (subOp.getLhs () != addOp4 || subOp.getRhs () != mulOp5) {
1625+ return failure ();
1626+ }
1627+ } else {
1628+ return failure ();
1629+ }
1630+
1631+ // tt.reduce.return %38, %33, %46 : f32, f32, f32 //return mean, count, M
1632+
1633+ LLVM_DEBUG (llvm::dbgs () << " Matching: " << *it << " \n " );
1634+ auto termOp = dyn_cast<triton::ReduceReturnOp>(*it++);
1635+ if (termOp && termOp == block_teminator) {
1636+ auto opnds = termOp.getOperands ();
1637+ if (opnds != ArrayRef<Value>{divOp0, addOp0, subOp}) {
1638+ return failure ();
1639+ }
1640+ } else {
1641+ return failure ();
1642+ }
1643+
1644+ return success ();
1645+ }
1646+
1647+ public:
1648+ VarMeanConverter (MLIRContext *context) : OpConversionPattern(context) {}
1649+
1650+ LogicalResult
1651+ matchAndRewrite (ReduceOp op, OpAdaptor adaptor,
1652+ ConversionPatternRewriter &rewriter) const override final {
1653+ // check num_args = 6 : mean_x, count_x, M_x, mean_y, count_y, M_y
1654+ if (op.getBody ()->getNumArguments () != 6 ) {
1655+ return failure ();
1656+ }
1657+
1658+ auto block = op.getBody ();
1659+ auto ops = block->without_terminator ();
1660+
1661+ Value mean_x = block->getArgument (0 );
1662+ Value count_x = block->getArgument (1 );
1663+ Value M_x = block->getArgument (2 );
1664+ Value mean_y = block->getArgument (3 );
1665+ Value count_y = block->getArgument (4 );
1666+ Value M_y = block->getArgument (5 );
1667+
1668+ auto opsIt = ops.begin ();
1669+ if (failed (matchVarMeanBody (mean_x, count_x, M_x, mean_y, count_y, M_y,
1670+ opsIt, block->getTerminator ()))) {
1671+ return failure ();
1672+ }
1673+ auto loc = op.getLoc ();
1674+ auto elemTypes = op.getElementTypes ();
1675+
1676+ auto meanType = elemTypes[0 ];
1677+ auto countType = elemTypes[1 ];
1678+ auto MType = elemTypes[2 ];
1679+
1680+ Value zeroMean = rewriter.create <arith::ConstantOp>(
1681+ loc, meanType, rewriter.getFloatAttr (meanType, 0 .f ));
1682+ Value zeroCount = rewriter.create <arith::ConstantOp>(
1683+ loc, countType, rewriter.getFloatAttr (countType, 0 .f ));
1684+ Value zeroM = rewriter.create <arith::ConstantOp>(
1685+ loc, MType, rewriter.getFloatAttr (MType, 0 .f ));
1686+
1687+ auto valueResultType = dyn_cast<RankedTensorType>(op.getType (0 ));
1688+ const auto isScalarReduce = valueResultType == nullptr ;
1689+ SmallVector<int64_t > reductionResultShape{
1690+ isScalarReduce ? SmallVector<int64_t >{}
1691+ : SmallVector<int64_t >(valueResultType.getShape ())};
1692+
1693+ auto initTensorMean =
1694+ getInitTensor (rewriter, reductionResultShape, zeroMean, loc);
1695+ auto initTensorCount =
1696+ getInitTensor (rewriter, reductionResultShape, zeroCount, loc);
1697+ auto initTensorM =
1698+ getInitTensor (rewriter, reductionResultShape, zeroM, loc);
1699+
1700+ SmallVector<Value> outputs = {initTensorMean, initTensorCount, initTensorM};
1701+
1702+ auto linalgOp = rewriter.create <linalg::ReduceOp>(
1703+ loc, adaptor.getOperands (), outputs,
1704+ SmallVector<int64_t >{adaptor.getAxis ()},
1705+ [&](OpBuilder &b, Location loc, ValueRange inputs) {
1706+ assert (inputs.size () == 6 &&
1707+ " Expected 6 inputs to varmean reduce block" );
1708+
1709+ auto tritonReduceBlock = op.getBody ();
1710+ IRMapping mapping;
1711+ mapping.map (tritonReduceBlock->getArguments (), inputs);
1712+
1713+ for (auto &op : tritonReduceBlock->without_terminator ()) {
1714+ b.clone (op, mapping);
1715+ }
1716+
1717+ auto tritonYield = tritonReduceBlock->getTerminator ();
1718+ auto results =
1719+ llvm::map_to_vector (tritonYield->getOperands (), [&](Value val) {
1720+ return mapping.lookup (val);
1721+ });
1722+ b.create <linalg::YieldOp>(loc, results);
1723+ });
1724+
1725+ if (isScalarReduce) {
1726+ SmallVector<Value> reduceResults{
1727+ rewriter.create <tensor::ExtractOp>(
1728+ loc, meanType, linalgOp.getResults ()[0 ], ValueRange{}),
1729+ rewriter.create <tensor::ExtractOp>(
1730+ loc, countType, linalgOp.getResults ()[1 ], ValueRange{}),
1731+ rewriter.create <tensor::ExtractOp>(
1732+ loc, MType, linalgOp.getResults ()[2 ], ValueRange{}),
1733+ };
1734+ rewriter.replaceOp (op, reduceResults);
1735+ } else {
1736+ rewriter.replaceOp (op, linalgOp);
1737+ }
1738+
1739+ return success ();
1740+ }
1741+ };
1742+
14021743template <typename T>
14031744class ArgMinMaxBaseConverter : public OpConversionPattern <triton::ReduceOp> {
14041745 using OpConversionPattern<triton::ReduceOp>::OpConversionPattern;
0 commit comments