// Copyright Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include #include // TODO: Extract most of this to a detail header file and add tests namespace rocRoller { namespace KernelGraph { namespace CT = rocRoller::KernelGraph::CoordinateGraph; using GD = rocRoller::Graph::Direction; using namespace ControlGraph; using namespace CoordinateGraph; namespace Expression = rocRoller::Expression; using namespace Expression; std::map> SwizzleScaleDetail::collectScaleLoadInfo( KernelGraph& graph, NaryArgument arg, int loopTag) { std::map> scaleLoads; auto loopBodies = graph.control.getOutputNodeIndices(loopTag).to(); for(auto loopBodyTag : loopBodies) { std::unordered_map scaleTileForMultiply; for(auto const multiplyTag : filter(graph.control.isElemType(), graph.control.depthFirstVisit(loopBodyTag))) { auto scaleMacTag = graph.mapper.get(multiplyTag, Connections::typeArgument(arg)); if(scaleMacTag != -1) scaleTileForMultiply[scaleMacTag] = multiplyTag; } auto isLoad = [&](int tag) { auto const& elem = graph.control.getElement(tag); return isOperation(elem) || isOperation(elem); }; for(auto const loadTag : filter(isLoad, graph.control.depthFirstVisit(loopBodyTag))) { auto tileTag = graph.mapper.get(loadTag); if(scaleTileForMultiply.contains(tileTag)) { scaleLoads[loadTag] = {scaleTileForMultiply[tileTag], tileTag}; } } } return scaleLoads; } void SwizzleScaleDetail::orderExchangesBeforeMultipliesInLoopBody( KernelGraph& graph, ContextPtr context, NaryArgument arg, std::map tileExchangeMap, std::map> scaleLoads, int loopTag) { auto loopBodies = graph.control.getOutputNodeIndices(loopTag).to(); for(auto loopBodyTag : loopBodies) { for(auto const multiplyTag : filter(graph.control.isElemType(), graph.control.depthFirstVisit(loopBodyTag))) { auto [tileTag, tile] = graph.getDimension( multiplyTag, Connections::typeArgument(arg)); if(not tileExchangeMap.contains(tileTag)) continue; Log::debug("Adding exchange-before-multiply Sequence edge from {} to {} for {}", tileExchangeMap.at(tileTag), multiplyTag, toString(arg)); graph.control.addElement( Sequence(), {tileExchangeMap.at(tileTag)}, {multiplyTag}); } } } std::map> SwizzleScaleDetail::filterLoadUnrollColouring( UnrollColouring const& colouring, std::map> const& scaleLoads) { AssertFatal(!scaleLoads.empty(), "Scale loads are not found"); std::map> rv; for(auto load = scaleLoads.cbegin(); load != scaleLoads.cend(); load++) { auto unrollMap = colouring.operationColour.at(load->first); rv.insert(std::make_pair(load->first, unrollMap)); } return rv; } std::vector SwizzleScaleDetail::addExchangeCT(KernelGraph& graph, ContextPtr context, int macTileTag, int waveTileTag, NaryArgument arg) { auto macTile = graph.coordinates.getNode(macTileTag); AssertFatal(macTile.memoryType == MemoryType::WAVE_SWIZZLE, "Exchange: MacroTile memory type not supported yet."); AssertFatal(macTile.subTileSizes.at(0) == 64 || macTile.subTileSizes.at(0) == 32, ShowValue(macTile.subTileSizes.at(0))); std::vector connections; auto waveTile = graph.coordinates.getNode(waveTileTag); auto iWaveX = graph.coordinates.addElement(waveTile.tileIndex(0)); auto iWaveY = graph.coordinates.addElement(waveTile.tileIndex(1)); auto wavefrontSize = context->kernel()->wavefront_size(); uint const lanesPerSIMD = 16; uint const nSIMDsPerWave = wavefrontSize / lanesPerSIMD; uint const nSIMDIndex = macTile.subTileSizes.at(0) / lanesPerSIMD; uint const nSIMDBlock = nSIMDsPerWave / nSIMDIndex; auto SIMDBlock = graph.coordinates.addElement(Adhoc("SIMDBlock", literal(nSIMDBlock), nullptr)); auto SIMDIndex = graph.coordinates.addElement(Adhoc("SIMDIndex", literal(nSIMDIndex), nullptr)); auto laneInSIMD = graph.coordinates.addElement(Lane(literal(lanesPerSIMD), nullptr)); uint const numElements = waveTile.elements(); uint const activeLanesInWave = static_cast(wavefrontSize); uint const numVgpr = numElements / activeLanesInWave; uint const nVgprIndex = std::min(nSIMDIndex, static_cast(macTile.miTileSizes.at(2))); // Minimal swizzle tile size 64x4 or 32x8 = 256 uint const numElementsPerMinimalSwizzleTile = 256; uint const nVgprBlock = numElementsPerMinimalSwizzleTile / macTile.subTileSizes.at(0) / nSIMDBlock / nVgprIndex; uint const nBlocks = numVgpr / nVgprBlock / nVgprIndex; auto vgprBlock = graph.coordinates.addElement(VGPRBlockNumber(literal(nVgprBlock), literal(1u))); auto vgprIndex = graph.coordinates.addElement(VGPRBlockIndex(literal(nVgprIndex), literal(1u))); auto block = graph.coordinates.addElement(Adhoc("Block", literal(nBlocks), literal(1u))); auto vgpr = graph.coordinates.addElement(VGPR(literal(numVgpr), literal(1u))); graph.coordinates.addElement(Tile(), {vgpr}, {block, vgprBlock, vgprIndex}); uint const nSIMDIndexBlock = nVgprIndex; uint const nSIMDIndexIndex = nSIMDIndex / nSIMDIndexBlock; auto SIMDIndexBlock = graph.coordinates.addElement( Adhoc("SIMDIndexBlock", literal(nSIMDIndexBlock), nullptr)); auto SIMDIndexIndex = graph.coordinates.addElement( Adhoc("SIMDIndexIndex", literal(nSIMDIndexIndex), nullptr)); graph.coordinates.addElement(Flatten(), {SIMDIndexBlock, SIMDIndexIndex}, {SIMDIndex}); connections.push_back(DC(waveTileTag)); connections.push_back(DC(SIMDBlock, 0)); connections.push_back(DC(SIMDIndex, 1)); connections.push_back(DC(SIMDIndexBlock, 2)); connections.push_back(DC(SIMDIndexIndex, 3)); connections.push_back(DC(laneInSIMD)); connections.push_back(DC(vgprBlock)); connections.push_back(DC(vgprIndex)); connections.push_back(DC(vgpr)); if(arg == NaryArgument::LHS_SCALE) { if(macTile.subTileSizes.at(0) == 64) { graph.coordinates.addElement( Flatten(), {vgprIndex, SIMDIndexIndex, laneInSIMD}, {iWaveX}); graph.coordinates.addElement( Flatten(), {block, SIMDBlock, vgprBlock, SIMDIndexBlock}, {iWaveY}); graph.coordinates.addElement(Flatten(), {iWaveX, iWaveY}, {waveTileTag}); } else if(macTile.subTileSizes.at(0) == 32 && macTile.miTileSizes.at(0) == 16) { graph.coordinates.addElement(Flatten(), {vgprIndex, laneInSIMD}, {iWaveX}); graph.coordinates.addElement( Flatten(), {block, vgprBlock, SIMDBlock, SIMDIndex}, {iWaveY}); graph.coordinates.addElement(Flatten(), {iWaveX, iWaveY}, {waveTileTag}); } else if(macTile.subTileSizes.at(0) == 32 && macTile.miTileSizes.at(0) == 32) { graph.coordinates.addElement(Flatten(), {SIMDIndex, laneInSIMD}, {iWaveX}); graph.coordinates.addElement( Flatten(), {block, vgprIndex, SIMDBlock, vgprBlock}, {iWaveY}); graph.coordinates.addElement(Flatten(), {iWaveX, iWaveY}, {waveTileTag}); } } if(arg == NaryArgument::RHS_SCALE) { if(macTile.subTileSizes.at(0) == 64) { graph.coordinates.addElement( Flatten(), {vgprIndex, SIMDIndexIndex, laneInSIMD}, {iWaveY}); graph.coordinates.addElement( Flatten(), {block, SIMDBlock, vgprBlock, SIMDIndexBlock}, {iWaveX}); graph.coordinates.addElement(Flatten(), {iWaveY, iWaveX}, {waveTileTag}); } else if(macTile.subTileSizes.at(0) == 32 && macTile.miTileSizes.at(0) == 16) { graph.coordinates.addElement(Flatten(), {vgprIndex, laneInSIMD}, {iWaveY}); graph.coordinates.addElement( Flatten(), {block, vgprBlock, SIMDBlock, SIMDIndex}, {iWaveX}); graph.coordinates.addElement(Flatten(), {iWaveY, iWaveX}, {waveTileTag}); } else if(macTile.subTileSizes.at(0) == 32 && macTile.miTileSizes.at(0) == 32) { graph.coordinates.addElement(Flatten(), {SIMDIndex, laneInSIMD}, {iWaveY}); graph.coordinates.addElement( Flatten(), {block, vgprIndex, SIMDBlock, vgprBlock}, {iWaveX}); graph.coordinates.addElement(Flatten(), {iWaveY, iWaveX}, {waveTileTag}); } } return connections; } std::tuple, std::vector, std::map> SwizzleScaleDetail::addSwizzleLoadCT(KernelGraph& graph, ContextPtr context, int tag, NaryArgument arg) { AssertFatal(arg == NaryArgument::LHS_SCALE || arg == NaryArgument::RHS_SCALE); std::vector connections; auto wavefrontSize = context->kernel()->wavefront_size(); auto existingMacTile = graph.coordinates.getNode(graph.mapper.get(tag)); AssertFatal(existingMacTile.subTileSizes.size() == 4, "Invalid tile specification"); // create new macrotile auto macTile = MacroTile(existingMacTile.sizes, existingMacTile.layoutType, existingMacTile.swizzleTileSizes, MemoryType::WAVE_SWIZZLE, existingMacTile.subTileSizes); auto macTileTag = graph.coordinates.addElement(macTile); connections.push_back(DC(macTileTag)); int iMac0, iMac1; auto isLoadTiled = graph.control.get(tag).has_value(); if(isLoadTiled) { auto userTag = graph.mapper.get(tag); AssertFatal(userTag != -1, "User coordinate associated with LoadTiled not found"); // copy user auto user = graph.coordinates.addElement(graph.coordinates.getElement(userTag)); connections.push_back(DC(user)); // copy sdims auto existingSDims = graph.coordinates.getOutputNodeIndices(userTag, CT::isEdge) .to(); std::vector sDims; for(int i = 0; i < existingSDims.size(); i++) { sDims.push_back(graph.coordinates.addElement( graph.coordinates.getElement(existingSDims[i]))); } graph.coordinates.addElement(Split(), std::vector{user}, sDims); int nMac0, nMac1; std::tie(nMac0, iMac0, nMac1, iMac1) = addLoadMacroTileCT(graph, connections, macTileTag, sDims, false); auto existingMacTileNum0 = graph.mapper.get(tag, 0); AssertFatal(existingMacTileNum0 != -1, "MacroTileNumber 0 coordinate associated with LoadTiled not found"); auto location = graph.coordinates.getLocation(existingMacTileNum0); for(auto const& output : location.outgoing) { auto edge = graph.coordinates.getElement(output); auto outTags = graph.coordinates.getNeighbours(output); graph.coordinates.addElement(edge, std::vector{nMac0}, outTags); } auto existingMacTileNum1 = graph.mapper.get(tag, 1); AssertFatal(existingMacTileNum1 != -1, "MacroTileNumber 1 coordinate associated with LoadTiled not found"); location = graph.coordinates.getLocation(existingMacTileNum1); for(auto const& output : location.outgoing) { auto edge = graph.coordinates.getElement(output); auto outTags = graph.coordinates.getNeighbours(output); graph.coordinates.addElement(edge, std::vector{nMac1}, outTags); } graph.coordinates.addElement(DataFlow(), {user}, {macTileTag}); } auto isLoadLDSTile = graph.control.get(tag).has_value(); if(isLoadLDSTile) { iMac0 = graph.coordinates.addElement(macTile.tileIndex(0)); iMac1 = graph.coordinates.addElement(macTile.tileIndex(1)); auto ldsTag = graph.mapper.get(tag); AssertFatal(ldsTag != -1, "LDS coordinate associated with LoadLDSTile not found"); auto lds = graph.coordinates.addElement(graph.coordinates.getElement(ldsTag)); graph.coordinates.addElement(View(), {lds}, {ldsTag}); if(arg == NaryArgument::LHS_SCALE) graph.coordinates.addElement(Tile(), {lds}, {iMac0, iMac1}); if(arg == NaryArgument::RHS_SCALE) graph.coordinates.addElement(Tile(), {lds}, {iMac1, iMac0}); } auto waveTile = WaveTile(macTile); auto waveTileTag = graph.coordinates.addElement(waveTile); connections.push_back(DC(waveTileTag)); auto nWave0 = graph.coordinates.addElement(waveTile.tileNumber(0)); auto nWave1 = graph.coordinates.addElement(waveTile.tileNumber(1)); auto iWave0 = graph.coordinates.addElement(waveTile.tileIndex(0)); auto iWave1 = graph.coordinates.addElement(waveTile.tileIndex(1)); graph.coordinates.addElement(Tile(), {iMac0}, {nWave0, iWave0}); graph.coordinates.addElement(Tile(), {iMac1}, {nWave1, iWave1}); graph.coordinates.addElement(Tile(), {waveTileTag}, {iWave0, iWave1}); connections.push_back(DC(nWave0, 0)); connections.push_back(DC(nWave1, 1)); uint const nLanesInSIMD = 16; uint const numElements = waveTile.elements(); uint const activeLanesInWave = static_cast(wavefrontSize); uint const numVgpr = numElements / activeLanesInWave; uint const nLanesInSIMDIndex = macTile.subTileSizes.at(2) / numVgpr; // k dimension uint const nLanesInSIMDBlock = nLanesInSIMD / nLanesInSIMDIndex; auto laneInSIMD = graph.coordinates.addElement(Lane(literal(nLanesInSIMD), literal(1u))); auto laneInSIMDIndex = graph.coordinates.addElement(Lane(literal(nLanesInSIMDIndex), literal(1u))); auto laneInSIMDBlock = graph.coordinates.addElement(Lane(literal(nLanesInSIMDBlock), literal(1u))); uint const nSIMDsPerWave = wavefrontSize / nLanesInSIMD; uint const nSIMDIndex = macTile.subTileSizes.at(0) / nLanesInSIMD; auto SIMDIndex = graph.coordinates.addElement(Adhoc("SIMDIndex", literal(nSIMDIndex), nullptr)); uint const nSIMDBlock = nSIMDsPerWave / nSIMDIndex; auto SIMDBlock = graph.coordinates.addElement(Adhoc("SIMDBlock", literal(nSIMDBlock), nullptr)); uint const nVgprIndex = std::min(nSIMDIndex, static_cast(macTile.miTileSizes.at(2))); // Minimal swizzle tile size 64x4 or 32x8 = 256 uint const numElementsPerMinimalSwizzleTile = 256; uint const nVgprBlock = numElementsPerMinimalSwizzleTile / macTile.subTileSizes.at(0) / nSIMDBlock / nVgprIndex; uint const nBlocks = numVgpr / nVgprBlock / nVgprIndex; auto vgprBlock = graph.coordinates.addElement(VGPRBlockNumber(literal(nVgprBlock), literal(1u))); auto vgprIndex = graph.coordinates.addElement(VGPRBlockIndex(literal(nVgprIndex), literal(1u))); auto vgpr = graph.coordinates.addElement(VGPR(literal(numVgpr), literal(1u))); auto block = graph.coordinates.addElement(Adhoc("Block", literal(nBlocks), literal(1u))); graph.coordinates.addElement(Flatten(), {block, vgprBlock, vgprIndex}, {vgpr}); connections.push_back(DC(vgprBlock)); connections.push_back(DC(vgprIndex)); connections.push_back(DC(vgpr)); auto activeLanesInWaveLiteral = literal(activeLanesInWave); auto wave = graph.coordinates.addElement(Wavefront(-1)); auto wave0 = graph.coordinates.addElement(Wavefront(0)); auto wave1 = graph.coordinates.addElement(Wavefront(1)); graph.coordinates.addElement(Flatten(), {wave0, wave1}, {wave}); auto workitem = graph.coordinates.addElement(Workitem(0)); auto lane = graph.coordinates.addElement(Lane(activeLanesInWaveLiteral, literal(1u))); graph.coordinates.addElement(Flatten(), {wave, lane}, {workitem}); graph.coordinates.addElement(Flatten(), {SIMDBlock, SIMDIndex, laneInSIMD}, {lane}); graph.coordinates.addElement( Flatten(), {laneInSIMDBlock, laneInSIMDIndex}, {laneInSIMD}); std::map unrolls; auto existingUnroll0 = graph.mapper.get(tag, rocRoller::XLOOP_UNROLL); auto existingUnroll1 = graph.mapper.get(tag, rocRoller::YLOOP_UNROLL); auto existingUnroll2 = graph.mapper.get(tag, rocRoller::KLOOP_UNROLL); if(arg == NaryArgument::LHS_SCALE) { if(context->kernelOptions()->scaleSkipPermlane == ScaleSkipPermlaneMode::PreSwizzleScaleGFX950) { graph.coordinates.addElement( Tile(), {iWave0}, {SIMDBlock, SIMDIndex, laneInSIMDBlock}); graph.coordinates.addElement( Tile(), {iWave1}, {laneInSIMDIndex, vgprBlock, vgprIndex}); } else { graph.coordinates.addElement(Tile(), {iWave0}, {SIMDIndex, laneInSIMD}); graph.coordinates.addElement( Tile(), {iWave1}, {block, SIMDBlock, vgprBlock, vgprIndex}); } if(existingUnroll0 != -1) { auto factor = macTile.subTileSizes.at(0) / existingMacTile.subTileSizes.at(0); auto jammedWaveTile0 = graph.coordinates.addElement(JammedWaveTileNumber( 0, literal(getUnrollSize(graph, existingUnroll0) / factor), literal(1))); graph.coordinates.addElement(Tile(), {nWave0}, {wave0, jammedWaveTile0}); connections.push_back(DC(jammedWaveTile0, 0)); auto unroll0 = existingUnroll0; graph.coordinates.addElement(PassThrough(), {jammedWaveTile0}, {unroll0}); connections.push_back(DC(unroll0, 0)); unrolls[existingUnroll0] = unroll0; } if(existingUnroll1 != -1) { auto factor = macTile.subTileSizes.at(2) / existingMacTile.subTileSizes.at(2); auto unroll1 = existingUnroll1; graph.coordinates.addElement(PassThrough(), {nWave1}, {unroll1}); connections.push_back(DC(unroll1, 1)); unrolls[existingUnroll1] = unroll1; } if(existingUnroll2 != -1) { unrolls[existingUnroll2] = existingUnroll2; } } if(arg == NaryArgument::RHS_SCALE) { if(context->kernelOptions()->scaleSkipPermlane == ScaleSkipPermlaneMode::PreSwizzleScaleGFX950) { graph.coordinates.addElement( Tile(), {iWave1}, {SIMDBlock, SIMDIndex, laneInSIMDBlock}); graph.coordinates.addElement( Tile(), {iWave0}, {laneInSIMDIndex, vgprBlock, vgprIndex}); } else { graph.coordinates.addElement(Tile(), {iWave1}, {SIMDIndex, laneInSIMD}); graph.coordinates.addElement( Tile(), {iWave0}, {block, SIMDBlock, vgprBlock, vgprIndex}); } if(existingUnroll1 != -1) { auto factor = macTile.subTileSizes.at(1) / existingMacTile.subTileSizes.at(1); auto jammedWaveTile1 = graph.coordinates.addElement(JammedWaveTileNumber( 1, literal(getUnrollSize(graph, existingUnroll1) / factor), literal(1))); graph.coordinates.addElement(Tile(), {nWave1}, {wave1, jammedWaveTile1}); connections.push_back(DC(jammedWaveTile1, 1)); auto unroll1 = existingUnroll1; graph.coordinates.addElement(PassThrough(), {jammedWaveTile1}, {unroll1}); connections.push_back(DC(unroll1, 1)); unrolls[existingUnroll1] = unroll1; } if(existingUnroll0 != -1) { auto factor = macTile.subTileSizes.at(2) / existingMacTile.subTileSizes.at(2); auto unroll0 = existingUnroll0; graph.coordinates.addElement(PassThrough(), {nWave0}, {unroll0}); connections.push_back(DC(unroll0, 0)); unrolls[existingUnroll0] = unroll0; } if(existingUnroll2 != -1) { unrolls[existingUnroll2] = existingUnroll2; } } auto exchangeConnections = addExchangeCT(graph, context, macTileTag, waveTileTag, arg); return {connections, exchangeConnections, unrolls}; } std::pair SwizzleScaleDetail::getOuterMergeFactors(KernelGraph const& graph, int macTileTag) { auto macTile = graph.coordinates.getNode(macTileTag); auto waveM = macTile.subTileSizes.at(0); auto waveN = macTile.subTileSizes.at(1); auto waveK = macTile.subTileSizes.at(2); AssertFatal( waveM == waveN, "waveM is not equal to waveN", ShowValue(waveM), ShowValue(waveN)); auto waveSwizzleM = macTile.swizzleTileSizes.at(0); auto waveSwizzleN = macTile.swizzleTileSizes.at(1); auto waveSwizzleK = macTile.swizzleTileSizes.at(2); AssertFatal(waveSwizzleM == waveSwizzleN && waveSwizzleM > 0, "waveSwizzleM is not equal to waveSwizzleN or is zero", ShowValue(waveSwizzleM), ShowValue(waveSwizzleN)); return std::make_pair(waveSwizzleM / waveM, waveSwizzleK / waveK); } std::pair SwizzleScaleDetail::getInnerMergeFactors(KernelGraph const& graph, int macTileTag) { auto macTile = graph.coordinates.getNode(macTileTag); auto waveM = macTile.subTileSizes.at(0); auto waveN = macTile.subTileSizes.at(1); auto waveK = macTile.subTileSizes.at(2); AssertFatal( waveM == waveN, "waveM is not equal to waveN", ShowValue(waveM), ShowValue(waveN)); auto const waveSwizzleM = macTile.swizzleTileSizes.at(0); auto const waveSwizzleN = macTile.swizzleTileSizes.at(1); AssertFatal(waveSwizzleM == waveSwizzleN && waveSwizzleM > 0, "waveSwizzleM is not equal to waveSwizzleN", ShowValue(waveSwizzleM), ShowValue(waveSwizzleN)); // Minimal swizzle tile size 64x4 or 32x8 = 256 uint const numElementsPerMinimalSwizzleTile = 256; auto const waveSwizzleK = numElementsPerMinimalSwizzleTile / waveSwizzleM; return std::make_pair(waveSwizzleM / waveM, waveSwizzleK / waveK); } std::map>> SwizzleScaleDetail::findMergeableLoads( KernelGraph const& graph, std::map> const& scaleLoads, std::map>& loadUnrollMap, NaryArgument arg) { AssertFatal(!scaleLoads.empty() && !loadUnrollMap.empty()); std::map>> mergeables; auto mergeLoadsByUnroll = [&](int fastDim, int slowDim0, int slowDim1, int innerFactor, int outerFactor, int indexFactor = 0) { AssertFatal(innerFactor <= outerFactor); if(innerFactor <= 1 && outerFactor <= 1) return; // (slowDimVal1, slowDimVal0, fastDimVal, load) std::map>> unrollLoadMap; for(auto const load : loadUnrollMap) { auto unrollMap = loadUnrollMap[load.first]; AssertFatal( unrollMap.contains(fastDim), ShowValue(load.first), ShowValue(fastDim)); AssertFatal( unrollMap.contains(slowDim0), ShowValue(load.first), ShowValue(slowDim0)); AssertFatal(slowDim1 == -1 || unrollMap.contains(slowDim1), ShowValue(load.first), ShowValue(slowDim1)); int slowDimVal1 = (slowDim1 == -1) ? 0 : unrollMap[slowDim1]; for(auto const unroll : unrollMap) { if(unroll.first == fastDim) { unrollLoadMap[slowDimVal1][unrollMap[slowDim0]][unrollMap[fastDim]] = load.first; } } } for(auto const sDim1 : unrollLoadMap) { for(auto const sDim0 : sDim1.second) { int mergeOp = -1; for(auto const fDim : sDim0.second) { if(fDim.first % outerFactor == 0) { mergeOp = fDim.second; loadUnrollMap[mergeOp][fastDim] /= outerFactor; } else { AssertFatal(mergeOp != -1); auto order = graph.control.compareNodes( rocRoller::UpdateCache, mergeOp, fDim.second); AssertFatal(graph.control.compareNodes( rocRoller::UpdateCache, mergeOp, fDim.second) == NodeOrdering::LeftFirst, ShowValue(mergeOp), ShowValue(fDim.second), ShowValue(order)); loadUnrollMap.erase(fDim.second); int index = 0; int factor = fDim.first * indexFactor; if(fDim.first % innerFactor == 0) index = fDim.first / innerFactor; // insertion order matters here mergeables[mergeOp].push_back( std::make_pair(fDim.second, index + factor)); if(mergeables.count(fDim.second) > 0) { for(auto& pair : mergeables[fDim.second]) { if(pair.second > 0) pair.second += factor; } mergeables[mergeOp].insert(mergeables[mergeOp].end(), mergeables[fDim.second].begin(), mergeables[fDim.second].end()); mergeables.erase(fDim.second); } } } } } }; auto sampleLoad = loadUnrollMap.begin()->first; auto unroll0 = graph.mapper.get(sampleLoad, rocRoller::XLOOP_UNROLL); auto unroll1 = graph.mapper.get(sampleLoad, rocRoller::YLOOP_UNROLL); auto unroll2 = graph.mapper.get(sampleLoad, rocRoller::KLOOP_UNROLL); // if unroll2 is -1, this returns 1. auto unrollKSize = getUnrollSize(graph, unroll2); auto sampleTile = scaleLoads.begin()->second.second; auto [innerFactorMN, innerFactorK] = getInnerMergeFactors(graph, sampleTile); auto [outerFactorMN, outerFactorK] = getOuterMergeFactors(graph, sampleTile); AssertFatal( innerFactorMN == outerFactorMN, ShowValue(innerFactorMN), ShowValue(outerFactorMN)); AssertFatal(outerFactorMN % innerFactorMN == 0, ShowValue(innerFactorMN), ShowValue(outerFactorMN)); if(arg == NaryArgument::LHS_SCALE) { // A : M x K auto xUnrollSize = getUnrollSize(graph, unroll0); auto macKUnrollSize = getUnrollSize(graph, unroll1); // merge scale loads if(xUnrollSize % innerFactorMN == 0 && macKUnrollSize % innerFactorK == 0) { AssertFatal((macKUnrollSize * unrollKSize) % outerFactorK == 0, ShowValue(macKUnrollSize), ShowValue(unrollKSize), ShowValue(outerFactorK)); mergeLoadsByUnroll(unroll0, unroll1, unroll2, innerFactorMN, outerFactorMN); if(macKUnrollSize % outerFactorK == 0) mergeLoadsByUnroll(unroll1, unroll0, unroll2, innerFactorK, outerFactorK); else { mergeLoadsByUnroll(unroll1, unroll0, unroll2, innerFactorK, macKUnrollSize); mergeLoadsByUnroll(unroll2, unroll1, unroll0, outerFactorK / macKUnrollSize, outerFactorK / macKUnrollSize, macKUnrollSize / innerFactorK); } } } if(arg == NaryArgument::RHS_SCALE) { // B : K x N auto yUnrollSize = getUnrollSize(graph, unroll1); auto macKUnrollSize = getUnrollSize(graph, unroll0); // merge scale loads if(yUnrollSize % innerFactorMN == 0 && macKUnrollSize % innerFactorK == 0) { AssertFatal((macKUnrollSize * unrollKSize) % outerFactorK == 0, ShowValue(macKUnrollSize), ShowValue(unrollKSize), ShowValue(outerFactorK)); mergeLoadsByUnroll(unroll1, unroll0, unroll2, innerFactorMN, outerFactorMN); if(macKUnrollSize % outerFactorK == 0) mergeLoadsByUnroll(unroll0, unroll1, unroll2, innerFactorK, outerFactorK); else { mergeLoadsByUnroll(unroll0, unroll1, unroll2, innerFactorK, macKUnrollSize); mergeLoadsByUnroll(unroll2, unroll0, unroll1, outerFactorK / macKUnrollSize, outerFactorK / macKUnrollSize, macKUnrollSize / innerFactorK); } } } return mergeables; } void swizzleScaleLoads(KernelGraph& graph, ContextPtr context, NaryArgument arg, int loopTag) { auto scaleLoads = SwizzleScaleDetail::collectScaleLoadInfo(graph, arg, loopTag); if(scaleLoads.empty()) { // TODO: Change this to let RR know that the SwizzleScale transform was applied but didn't do anything Log::debug("Unable to find SwizzleScale candidates"); return; } auto colouring = colourByUnrollValue(graph); auto loadUnrollMap = SwizzleScaleDetail::filterLoadUnrollColouring(colouring, scaleLoads); if(loadUnrollMap.empty()) return; auto mergeables = SwizzleScaleDetail::findMergeableLoads(graph, scaleLoads, loadUnrollMap, arg); if(mergeables.empty()) return; auto sampleLoad = mergeables.begin()->first; auto [loadConnections, exchangeConnections, unrollReindexMap] = SwizzleScaleDetail::addSwizzleLoadCT(graph, context, sampleLoad, arg); std::map tileExchangeMap; // Mapping from original LDS coordinate tags to new LDS coordinate tags. // // The new LDS coordinates will be: // // 1. Connected to the new LDS coordinate created by // addSwizzleLoadCT by a Duplicate edge. // // This means that, when computing indexes, the LDS // coordinate created by addSwizzleLoadCT will be used. // // 2. Connected to the original LDS coordinates by a View // edge. // // This means that they will use the correct LDS // allocation when loading. // std::map newLDSTags; int originalLDSTag = -1; auto maybeSampleLoadLDSTile = graph.control.get(sampleLoad); if(maybeSampleLoadLDSTile) { originalLDSTag = graph.mapper.get(sampleLoad); // A View edge was added by `addSwizzleLoadCT`. To // get the new LDS tag we can follow the View edge. // // There may be multiple View edges if this LDS tag // was also used by a previous loop. We want the most // recently added one (highest node ID). auto viewInputs = graph.coordinates.getInputNodeIndices(originalLDSTag, CT::isEdge) .to(); AssertFatal(!viewInputs.empty(), "Expected at least one View edge into originalLDSTag from " "addSwizzleLoadCT", ShowValue(originalLDSTag)); // Use the most recently added View edge (highest node ID) auto newLDSTag = *std::max_element(viewInputs.begin(), viewInputs.end()); newLDSTags[originalLDSTag] = newLDSTag; } for(auto const [load, redundantLoads] : mergeables) { auto maybeLoadLDSTile = graph.control.get(load); if(maybeLoadLDSTile) { auto ldsTag = graph.mapper.get(load); if(not newLDSTags.contains(ldsTag)) { auto newLDSTag = graph.coordinates.addElement(LDS()); graph.coordinates.addElement( Duplicate(), {newLDSTag}, {newLDSTags[originalLDSTag]}); graph.coordinates.addElement(View(), {newLDSTag}, {ldsTag}); newLDSTags[ldsTag] = newLDSTag; } graph.mapper.connect(load, newLDSTags[ldsTag]); } // add coordinate connections for LoadTiled for(auto const& dc : loadConnections) { graph.mapper.connect(load, dc.coordinate, dc.connectionSpec); } // make a copy of MacroTile for separate register tagging if(load != sampleLoad) duplicateMacroTile(graph, load); // add exchange node after load auto exchange = graph.control.addElement(Exchange(getVariableType(graph, load))); auto topOp = getTopSetCoordinate(graph, load); graph.control.addElement(Sequence(), {topOp}, {exchange}); // add coordinate connections for Exchange for(auto const& dc : exchangeConnections) { graph.mapper.connect(exchange, dc.coordinate, dc.connectionSpec); } // Since the load tile size (e.g. 64x4, 64x8, 64x12, 64x16) can be // greater than equal to the exchange tile size (64x4), // add index edge to point to the register allocation (subset). auto tileTag = graph.mapper.get(load); auto tile = graph.coordinates.getNode(tileTag); AssertFatal(tile.miTileSizes.size() == 4, ShowValue(tile.miTileSizes.size())); auto exchangeTileTag = graph.coordinates.addElement(tile); graph.coordinates.addElement(Index(0), {exchangeTileTag}, {tileTag}); graph.mapper.connect(exchange, exchangeTileTag); auto destMacTileTag = context->kernelOptions()->scaleSkipPermlane != ScaleSkipPermlaneMode::None ? exchangeTileTag : graph.coordinates.addElement(MacroTile()); graph.mapper.connect(exchange, destMacTileTag, NaryArgument::DEST); auto createNode = [&context](int idx) -> rocRoller::KernelGraph::CoordinateGraph::Edge { if(context->kernelOptions()->scaleSkipPermlane != ScaleSkipPermlaneMode::None) return Segment(idx); return Index(idx); }; // add index edge to point to exchange output tile. int index = 0; graph.coordinates.addElement( createNode(index++), {scaleLoads.at(load).second}, {destMacTileTag}); tileExchangeMap[scaleLoads.at(load).second] = exchange; // merge the loads for(auto const [mergeOp, mergeTile] : redundantLoads) { auto mergeTopOp = getTopSetCoordinate(graph, mergeOp); auto ordering = graph.control.compareNodes( rocRoller::UseCacheIfAvailable, topOp, mergeTopOp); AssertFatal(ordering == NodeOrdering::LeftFirst); auto replaceOp = graph.control.addElement(NOP()); if(mergeTile > 0) { exchange = graph.control.addElement(Exchange(getVariableType(graph, load))); graph.control.addElement(Sequence(), {replaceOp}, {exchange}); // add coordinate connections for Exchange for(auto const& dc : exchangeConnections) { graph.mapper.connect(exchange, dc.coordinate, dc.connectionSpec); } // Since the load tile size (e.g. 64x4, 64x8, 64x12, 64x16) can be // greater than equal to the exchange tile size (64x4), // add index edge to point to the register allocation (subset). exchangeTileTag = graph.coordinates.addElement(tile); graph.coordinates.addElement( Index(mergeTile), {exchangeTileTag}, {tileTag}); graph.mapper.connect(exchange, exchangeTileTag); destMacTileTag = context->kernelOptions()->scaleSkipPermlane != ScaleSkipPermlaneMode::None ? exchangeTileTag : graph.coordinates.addElement(MacroTile()); graph.mapper.connect(exchange, destMacTileTag, NaryArgument::DEST); // reset the index index = 0; } replaceWith(graph, mergeTopOp, replaceOp, false); purgeNodeAndChildren(graph, mergeTopOp); graph.coordinates.addElement( createNode(index++), {scaleLoads.at(mergeOp).second}, {destMacTileTag}); tileExchangeMap[scaleLoads.at(mergeOp).second] = exchange; } // Update the SetCoordinate value and its Unroll coordinate connection auto maybeSetCoordinate = findContainingOperation(load, graph); while(maybeSetCoordinate.has_value()) { auto tag = maybeSetCoordinate.value(); auto unroll = graph.mapper.get(tag); // Skip SetCoordinates that aren't connected to an Unroll dimension // (e.g., outer scope SetCoordinates) if(unroll <= 0) { maybeSetCoordinate = findContainingOperation(tag, graph); continue; } // Skip unrolls that aren't in our unroll reindex map if(!unrollReindexMap.contains(unroll)) { maybeSetCoordinate = findContainingOperation(tag, graph); continue; } auto newValue = loadUnrollMap[load][unroll]; auto newOp = SetCoordinate(Expression::literal(newValue)); graph.control.setElement(tag, newOp); auto newUnroll = unrollReindexMap.at(unroll); graph.mapper.disconnect(tag, unroll); graph.mapper.connect(tag, newUnroll); maybeSetCoordinate = findContainingOperation(tag, graph); } } SwizzleScaleDetail::orderExchangesBeforeMultipliesInLoopBody( graph, context, arg, tileExchangeMap, scaleLoads, loopTag); } KernelGraph SwizzleScale::apply(KernelGraph const& original) { if(!m_params->swizzleScale) return original; // TODO: enable SwizzleScale when transA == T or transB == N AssertFatal(m_params->transposeMemoryAccess[LayoutType::MATRIX_A] && !m_params->transposeMemoryAccess[LayoutType::MATRIX_B], "Non-TN is not supported by SwizzleScale"); auto newGraph = original; auto const rootTag = newGraph.control.roots().only().value(); auto findNamedLoopsBelow = [&](auto startTag, auto name) { std::vector loopTags; for(auto const loop : filter(newGraph.control.isElemType(), newGraph.control.depthFirstVisit(startTag))) { auto forloop = newGraph.control.get(loop).value(); if(forloop.loopName == name) { loopTags.push_back(loop); } } return loopTags; }; // Support kernels with multiple distinct KLoops auto kLoopTags = findNamedLoopsBelow(rootTag, KLOOP); AssertFatal(not kLoopTags.empty(), "Kernel must contain at least one KLoop"); for(auto const kLoopTag : kLoopTags) { swizzleScaleLoads(newGraph, m_context, NaryArgument::LHS_SCALE, kLoopTag); swizzleScaleLoads(newGraph, m_context, NaryArgument::RHS_SCALE, kLoopTag); auto kLoopTailTags = findNamedLoopsBelow(kLoopTag, KLOOPTAIL); AssertFatal(kLoopTailTags.size() <= 1, "Each KLoop can have at most one KLoopTail"); if(not kLoopTailTags.empty()) { auto const kLoopTailTag = kLoopTailTags[0]; swizzleScaleLoads(newGraph, m_context, NaryArgument::LHS_SCALE, kLoopTailTag); swizzleScaleLoads(newGraph, m_context, NaryArgument::RHS_SCALE, kLoopTailTag); } } removeRedundantSequenceEdges(newGraph); return newGraph; } } }