Files
TheRock/rocm-libraries/shared/tensile/Tensile/Source/lib/source/ContractionSolution.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

2229 lines
91 KiB
C++

/*******************************************************************************
*
* MIT License
*
* Copyright (C) 2019-2024 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*******************************************************************************/
#include <Tensile/ContractionSolution.hpp>
#include <Tensile/AMDGPU.hpp>
#include <Tensile/ContractionProblem.hpp>
#include <Tensile/Utils.hpp>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <random>
#include <iomanip>
#include <regex>
namespace Tensile
{
namespace streamk
{
namespace math
{
/**
* Performs `(n + d - 1) / d`, but is robust against the case where
* `(n + d - 1)` would overflow.
*/
template <typename N, typename D>
__device__ __host__ inline constexpr N safe_ceil_div(N n, D d)
{
// Static cast to undo integral promotion.
return static_cast<N>(n / d + (n % d != 0 ? 1 : 0));
}
} // namespace math
constexpr size_t num_iters_per_cta(
size_t BLK_M, size_t BLK_N, size_t BLK_K, size_t m, size_t n, size_t k, int g)
{
return math::safe_ceil_div(math::safe_ceil_div(m, BLK_M) * math::safe_ceil_div(n, BLK_N)
* math::safe_ceil_div(k, BLK_K),
g);
}
constexpr size_t number_of_output_tiles(size_t BLK_M, size_t BLK_N, size_t m, size_t n)
{
size_t m_tiles = math::safe_ceil_div(m, BLK_M);
size_t n_tiles = math::safe_ceil_div(n, BLK_N);
return m_tiles * n_tiles;
}
constexpr size_t num_fixup_peers(size_t BLK_K, size_t k, size_t iters_per_cta)
{
return math::safe_ceil_div(math::safe_ceil_div(k, BLK_K), iters_per_cta);
}
std::tuple<double, size_t, size_t> predicted_runtime(size_t BLK_M,
size_t BLK_N,
size_t BLK_K,
size_t m,
size_t n,
size_t k,
int g,
double a,
double b,
double c,
double d)
{
size_t iters_per_cta = num_iters_per_cta(BLK_M, BLK_N, BLK_K, m, n, k, g);
size_t fixup_peers = num_fixup_peers(BLK_K, k, iters_per_cta);
return {a + (b * (fixup_peers > 1)) + (c * iters_per_cta) + (d * (fixup_peers - 1)),
iters_per_cta,
fixup_peers};
}
int best_predicted_grid_size(size_t BLK_M,
size_t BLK_N,
size_t BLK_K,
size_t m,
size_t n,
size_t k,
int grid_start,
int grid_end)
{
static const bool debug = Debug::Instance().printStreamKGridInfo();
// Fixed overhead alpha (a), fixed-size cost incurred by
// each work-group, e.g. the grid launch latency, the initial
// compulsary cache misses, the cost of writing the final output tile
// to C.
double a = 5.04 + 8.30;
// Beta (b) incorporates conditional costs of outputting temporary partial
// sums for scenarios where the number of output tiles does not quantize
// perfectly across the number of processors.
double b = 5.47;
// c represents instruction and stall workload of each MAC-iteration.
double c = 4.17;
// Delta (d) is the cost of reading and accumulating the partial sums from
// other work-groups covering the same tile.
double d = 18.59;
// std::vector<double> runtimes;
std::pair<int, double> min_grid_runtime;
min_grid_runtime.second = std::numeric_limits<double>::max();
int g = grid_start;
// Predict the number of CTAs to use between 1 and 304
for(; g <= grid_end; ++g)
{
auto [runtime, iters_per_cta, fixup_peers]
= predicted_runtime(BLK_M, BLK_N, BLK_K, m, n, k, g, a, b, c, d);
if(debug)
{
std::cout << "grid size: " << g << ", runtime: " << runtime
<< ", iters_per_cta: " << iters_per_cta
<< ", fixup_peers: " << fixup_peers << ", m: " << m << ", n: " << n
<< ", k: " << k << ", a: " << a << ", b: " << b << ", c: " << c
<< ", d: " << d << std::endl;
}
if(min_grid_runtime.second > runtime)
{
min_grid_runtime.first = g;
min_grid_runtime.second = runtime;
}
}
if(debug)
{
std::cout << "Number of Output Tiles: "
<< number_of_output_tiles(BLK_M, BLK_N, m, n) << std::endl;
std::cout << "Minimum runtime: " << min_grid_runtime.second
<< " @ grid size: " << min_grid_runtime.first << std::endl;
}
return min_grid_runtime.first;
}
} // namespace streamk
PerfModel perf;
int32_t ContractionSolution::staggerUIter(ContractionSolution::Problem const& problem,
ContractionSolution::Inputs const& inputs,
Hardware const& hardware) const
{
uint32_t sizeL = problem.boundSize(0);
// how many stride-sized clicks to stagger start offset
unsigned int staggerUIter = sizeMapping.staggerU;
// /DepthU/GSU
int unrollLoopIters = sizeL / sizeMapping.depthU / sizeMapping.globalSplitU;
unsigned int shifted = 1 << sizeMapping.staggerStrideShift;
while(staggerUIter > 1)
{
if(unrollLoopIters >= (staggerUIter * shifted))
break;
staggerUIter /= 2; // step down to smaller stagger
}
if(staggerUIter >= 1)
staggerUIter -= 1;
return staggerUIter;
}
// Return magic number. If magicShift is 0, compute and return it.
uint32_t ContractionSolution::magicNumberAlg1(uint32_t x, uint32_t* magicShift) const
{
uint64_t magicNum;
*magicShift = 33;
magicNum = (1L << *magicShift) / x + 1;
if((magicNum >> 32) != 0)
{
*magicShift = 31;
magicNum = (1L << *magicShift) / x + 1;
}
assert(magicNum >> 32 == 0); // ensure magic number fits
return static_cast<uint32_t>(magicNum);
}
uint32_t ContractionSolution::magicNumberAlg2(uint32_t d, uint32_t* magicShift) const
{
struct mu
{
unsigned M; // Magic number,
int a; // "add" indicator,
int s;
}; // and shift amount.
struct mu magu;
if(d == 0)
{
// Make dividend of 0 return 0
magu.M = 0;
magu.a = 0;
magu.s = 0;
}
else
{
// Must have 1 <= d <= 2**32-1.
int p;
unsigned nc, delta, q1, r1, q2, r2;
magu.a = 0; // Initialize "add" indicator.
nc = -1 - (-d) % d; // Unsigned arithmetic here.
p = 31; // Init. p.
q1 = 0x80000000 / nc; // Init. q1 = 2**p/nc.
r1 = 0x80000000 - q1 * nc; // Init. r1 = rem(2**p, nc).
q2 = 0x7FFFFFFF / d; // Init. q2 = (2**p - 1)/d.
r2 = 0x7FFFFFFF - q2 * d; // Init. r2 = rem(2**p - 1, d).
do
{
p = p + 1;
if(r1 >= nc - r1)
{
q1 = 2 * q1 + 1; // Update q1.
r1 = 2 * r1 - nc;
} // Update r1.
else
{
q1 = 2 * q1;
r1 = 2 * r1;
}
if(r2 + 1 >= d - r2)
{
if(q2 >= 0x7FFFFFFF)
magu.a = 1;
q2 = 2 * q2 + 1; // Update q2.
r2 = 2 * r2 + 1 - d;
} // Update r2.
else
{
if(q2 >= 0x80000000)
magu.a = 1;
q2 = 2 * q2;
r2 = 2 * r2 + 1;
}
delta = d - 1 - r2;
} while(p < 64 && (q1 < delta || (q1 == delta && r1 == 0)));
magu.M = q2 + 1; // Magic number
magu.s = p - 32; // and shift amount to return
}
*magicShift = magu.s;
const uint32_t abit = 0x80000000;
if(magu.a)
*magicShift |= abit;
// std::cout << " d=" << d << " M=" << magu.M << " a=" << magu.a << " s=" <<
// magu.s << "\n";
return magu.M;
}
uint32_t
ContractionSolution::magicNumber(int magicDivAlg, uint32_t x, uint32_t* magicShift) const
{
if(magicDivAlg == 1)
return magicNumberAlg1(x, magicShift);
else if(magicDivAlg == 2)
return magicNumberAlg2(x, magicShift);
else
throw std::runtime_error("bad magicDivAlg");
}
uint32_t ContractionSolution::smallMagicNumber(uint32_t x) const
{
uint64_t magicNum;
const int smallMagicShift = 31;
magicNum = (1L << smallMagicShift) / x + 1;
assert(magicNum >> 32 == 0); // ensure magic number fits
return static_cast<uint32_t>(magicNum);
}
std::vector<size_t> generatePackedIndicesA(ContractionSolution::Problem const& problem,
size_t packBatchDims)
{
std::vector<size_t> packedIndices;
// TODO -move packedIndices calc to problem decode.
for(auto idx = 0; idx < problem.a().dimensions(); idx++)
{
bool isSum = problem.boundIndices().end()
!= std::find_if(problem.boundIndices().begin(),
problem.boundIndices().end(),
[idx](const ContractionProblem::BoundIndex& bi) {
return bi.a == idx;
});
bool nonPackableBatch = false;
// TODO - base this check on if the batch is SetConstStrideA=0 - if so,
// don't pack
if(!(packBatchDims & 0x1))
{
nonPackableBatch = problem.batchIndices().end()
!= std::find_if(problem.batchIndices().begin(),
problem.batchIndices().end(),
[idx](const ContractionProblem::BatchIndex& bi) {
return bi.a == idx;
});
}
if(!isSum && !nonPackableBatch)
packedIndices.push_back(idx);
}
return packedIndices;
}
std::vector<size_t> generatePackedIndicesB(ContractionSolution::Problem const& problem,
size_t packBatchDims)
{
std::vector<size_t> packedIndices;
// Pack in all non-summation indices, except don't need magic number for the
// last one
for(auto idx = 0; idx < problem.b().dimensions(); idx++)
{
bool isSum = problem.boundIndices().end()
!= std::find_if(problem.boundIndices().begin(),
problem.boundIndices().end(),
[idx](const ContractionProblem::BoundIndex& bi) {
return bi.b == idx;
});
bool nonPackableBatch = false;
// TODO - base this check on if the batch is SetConstStrideB=0 - if so,
// don't pack
if(!(packBatchDims & 0x2))
{
nonPackableBatch = problem.batchIndices().end()
!= std::find_if(problem.batchIndices().begin(),
problem.batchIndices().end(),
[idx](const ContractionProblem::BatchIndex& bi) {
return bi.b == idx;
});
}
if(!isSum && !nonPackableBatch)
packedIndices.push_back(idx);
}
return packedIndices;
}
template <typename TypedInputs, bool T_Debug>
KernelInvocation
ContractionSolution::generateSingleCall(ContractionSolution::Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
//TENSILE_ASSERT_EXC(sizeMapping.workGroupMapping >= 0);
TensorDescriptor const& a = problem.a();
TensorDescriptor const& b = problem.b();
TensorDescriptor const& c = problem.c();
TensorDescriptor const& d = problem.d();
KernelInvocation rv;
rv.args = KernelArguments(T_Debug);
rv.args.reserve(1024, 128);
rv.kernelName = kernelName;
rv.workGroupSize.x = sizeMapping.workGroupSize.x * sizeMapping.workGroupSize.y
* sizeMapping.workGroupSize.z;
rv.workGroupSize.y = 1;
rv.workGroupSize.z = 1;
rv.numWorkGroups.x = 1;
rv.numWorkGroups.y = 1;
for(size_t i = 0; i < problem.freeIndicesA().size(); i++)
{
rv.numWorkGroups.x *= problem.freeSizeA(i);
}
for(size_t i = 0; i < problem.freeIndicesB().size(); i++)
{
rv.numWorkGroups.y *= problem.freeSizeB(i);
}
rv.numWorkGroups.z = 1;
for(size_t i = 0; i < problem.batchIndices().size(); i++)
{
if(sizeMapping.packBatchDims & 0x1)
rv.numWorkGroups.x *= problem.batchSize(i);
if(sizeMapping.packBatchDims & 0x2)
rv.numWorkGroups.y *= problem.batchSize(i);
if(!sizeMapping.packBatchDims)
rv.numWorkGroups.z *= problem.batchSize(i);
}
if(problem.transposeC01())
std::swap(rv.numWorkGroups.x, rv.numWorkGroups.y);
rv.numWorkGroups.x = CeilDivide(rv.numWorkGroups.x, sizeMapping.macroTile.x);
rv.numWorkGroups.y = CeilDivide(rv.numWorkGroups.y, sizeMapping.macroTile.y);
uint32_t problemNumGroupTiles0 = rv.numWorkGroups.x;
uint32_t problemNumGroupTiles1 = rv.numWorkGroups.y;
// used only when persistent kernel along batch
uint32_t problemNumGroupTiles2 = rv.numWorkGroups.z;
rv.numWorkGroups.y *= sizeMapping.globalSplitU;
size_t cuCount = 0;
size_t skGrid = 0;
auto tiles = problem.getNumTiles(sizeMapping);
int fullTiles = 0;
if(sizeMapping.streamK != 0 || sizeMapping.persistentKernel != 0)
{
AMDGPU const* pAMDGPU = dynamic_cast<AMDGPU const*>(&hardware);
assert(pAMDGPU != nullptr && pAMDGPU->computeUnitCount != 0);
cuCount = pAMDGPU->computeUnitCount;
if(sizeMapping.streamK != 0)
{
skGrid = getSKGrid(problem, hardware, tiles);
rv.numWorkGroups.x = skGrid;
rv.numWorkGroups.y = 1;
rv.numWorkGroups.z = 1;
fullTiles = pAMDGPU->skFullTiles;
if(fullTiles == -1)
{
fullTiles = predictNumFullTiles(problem, hardware, tiles, skGrid);
}
}
}
if(sizeMapping.persistentKernel != 0)
{
size_t finalPKValue = sizeMapping.persistentKernel;
size_t problemGroups = rv.numWorkGroups.x * rv.numWorkGroups.y;
if(sizeMapping.persistentKernelAlongBatch)
{
problemGroups *= rv.numWorkGroups.z;
rv.numWorkGroups.z = 1;
}
if(finalPKValue == -1)
{
// 1. Get the largest pk value (ex.3)
// which can make the PK.G (ex.3*120=360) <= problemGroups (ex.433)
// 2. Scale by 5/8 (can try 0.5~1, to control the tiles-per-workgroup = 1~2)
finalPKValue = 5 * (problemGroups / cuCount) / 8;
finalPKValue = std::max(finalPKValue, (size_t)1);
//std::cout << "final persistent kernel value: " << finalPKValue << std::endl;
}
size_t persistentGroups = cuCount * finalPKValue;
rv.numWorkGroups.x = std::min(persistentGroups, problemGroups);
rv.numWorkGroups.y = 1;
}
rv.numWorkItems.x = rv.workGroupSize.x * rv.numWorkGroups.x;
rv.numWorkItems.y = rv.workGroupSize.y * rv.numWorkGroups.y;
rv.numWorkItems.z = rv.workGroupSize.z * rv.numWorkGroups.z;
if(debugKernel)
{
rv.args.appendUnbound<unsigned int*>("debugBuffer");
}
rv.sharedMemBytes = 0;
size_t startStrideCD = problemType.useInitialStridesCD ? 0 : 1;
size_t startStrideAB = problemType.useInitialStridesAB ? 0 : 1;
auto problemSizes = problem.problemSizes();
TENSILE_ASSERT_EXC(problemSizes.size() > 0);
if(!isSourceKernel())
{
uint64_t tensor2dSizeC = c.totalAllocatedElements();
uint64_t tensor2dSizeA = (sizeMapping.packBatchDims & 0x1)
? a.totalAllocatedElements()
: problem.allocatedElementsNonBatchA();
uint64_t tensor2dSizeB = (sizeMapping.packBatchDims & 0x2)
? b.totalAllocatedElements()
: problem.allocatedElementsNonBatchB();
if(sizeMapping.preloadKernargs)
{
TENSILE_ASSERT_EXC(problemType.stridedBatched);
TENSILE_ASSERT_EXC(problemType.useBeta);
TENSILE_ASSERT_EXC(startStrideAB == 1);
rv.args.append<uint64_t>("tensor2dSizeA", tensor2dSizeA);
rv.args.append<uint64_t>("tensor2dSizeB", tensor2dSizeB);
auto aptr = inputs.a + a.offset();
auto bptr = inputs.b + b.offset();
rv.args.append<typename TypedInputs::AType const*>("a", aptr);
rv.args.append<typename TypedInputs::BType const*>("b", bptr);
rv.args.append<typename TypedInputs::AlphaType>("alpha", inputs.alpha);
if(std::is_same<typename TypedInputs::AlphaType, Half>::value && !isSourceKernel())
rv.args.append<typename TypedInputs::AlphaType>("alpha_2", inputs.alpha);
for(size_t i = startStrideAB; i < a.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideA", i), a.strides()[i]);
for(size_t i = startStrideAB; i < b.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideB", i), b.strides()[i]);
rv.args.append<uint32_t>("sizeSum", problemSizes.back());
}
// rv.args.append<uint64_t>("tensor2dSizeC", tensor2dSizeC);
if(!sizeMapping.preloadKernargs)
{
rv.args.append<uint64_t>("tensor2dSizeA", tensor2dSizeA);
rv.args.append<uint64_t>("tensor2dSizeB", tensor2dSizeB);
}
}
if(sizeMapping.globalAccumulation && sizeMapping.streamK == 0)
{
rv.args.append<void const*>("ws_d", inputs.ws);
rv.args.append<void const*>("ws_c", inputs.ws);
}
else if(problemType.stridedBatched)
{
auto dptr = inputs.d + d.offset();
auto cptr = inputs.c + c.offset();
rv.args.append<typename TypedInputs::DType const*>("d", dptr);
rv.args.append<typename TypedInputs::CType const*>("c", cptr);
}
else
{
rv.args.append<typename TypedInputs::DType const* const*>("batchD", inputs.batchD);
rv.args.append<typename TypedInputs::CType const* const*>("batchC", inputs.batchC);
}
if(problemType.stridedBatched)
{
if(!sizeMapping.preloadKernargs)
{
auto aptr = inputs.a + a.offset();
auto bptr = inputs.b + b.offset();
rv.args.append<typename TypedInputs::AType const*>("a", aptr);
rv.args.append<typename TypedInputs::BType const*>("b", bptr);
}
}
else
{
rv.args.append<typename TypedInputs::AType const* const*>("batchA", inputs.batchA);
rv.args.append<typename TypedInputs::BType const* const*>("batchB", inputs.batchB);
}
if(sizeMapping.streamK > 0 && sizeMapping.streamKAtomic == 0)
{
// StreamK workspace + flags
rv.args.append<void const*>("ws", inputs.ws);
void* ws = inputs.ws;
size_t flagsOffset = partialTileSize(skGrid);
void* flags = (void*)(static_cast<char*>(ws) + flagsOffset);
rv.args.append<void*>("Flags", flags);
}
if(!problemType.stridedBatched)
{
rv.args.append<uint64_t>("offsetD", d.offset());
rv.args.append<uint64_t>("offsetC", c.offset());
rv.args.append<uint64_t>("offsetA", a.offset());
rv.args.append<uint64_t>("offsetB", b.offset());
}
if(!sizeMapping.preloadKernargs)
{
rv.args.append<typename TypedInputs::AlphaType>("alpha", inputs.alpha);
if(std::is_same<typename TypedInputs::AlphaType, Half>::value && !isSourceKernel())
rv.args.append<typename TypedInputs::AlphaType>("alpha_2", inputs.alpha);
}
if(problemType.useBeta)
{
rv.args.append<typename TypedInputs::BetaType>("beta", inputs.beta);
if(std::is_same<typename TypedInputs::BetaType, Half>::value && !isSourceKernel())
rv.args.append<typename TypedInputs::BetaType>("beta_2", inputs.beta);
}
if(sizeMapping.globalAccumulation && sizeMapping.streamK == 0)
{
size_t wsStride = startStrideCD ? d.sizes()[0] : 1;
for(size_t i = startStrideCD; i < d.dimensions(); i++)
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideW_D", i), wsStride);
wsStride *= d.sizes()[i];
}
wsStride = startStrideCD ? d.sizes()[0] : 1;
for(size_t i = startStrideCD; i < c.dimensions(); i++)
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideW_C", i), wsStride);
wsStride *= d.sizes()[i];
}
}
else
{
for(size_t i = startStrideCD; i < d.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideD", i), d.strides()[i]);
for(size_t i = startStrideCD; i < c.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideC", i), c.strides()[i]);
}
if(!sizeMapping.preloadKernargs)
{
for(size_t i = startStrideAB; i < a.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideA", i), a.strides()[i]);
for(size_t i = startStrideAB; i < b.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideB", i), b.strides()[i]);
}
{
int maxIdx = problemSizes.size();
if(sizeMapping.preloadKernargs)
maxIdx--;
for(int idx = 0; idx < maxIdx; idx++)
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("size_", idx), problemSizes[idx]);
}
}
if(sizeMapping.packSummationDims)
// boundIndices are ordered with unroll last.
// Magic numbers for all but first are needed to unpack other dims.
for(auto si = 1; si < problem.boundIndices().size(); si++)
{
auto numIter = problem.boundSize(si);
bool isUnroll = si == problem.boundIndices().size() - 1;
if(isUnroll)
{
numIter = numIter / sizeMapping.depthU / sizeMapping.globalSplitU
* sizeMapping.depthU;
}
uint32_t magicShift;
rv.args.append<uint32_t>(
concatenate_if<T_Debug>("magicNumberNumIter_", si),
magicNumber(sizeMapping.magicDivAlg, numIter, &magicShift));
rv.args.append<uint32_t>(concatenate_if<T_Debug>("magicShiftNumIter_", si),
magicShift);
if(isUnroll and sizeMapping.globalSplitU > 1)
{
// compute magic number for gsu remainder iterations:
// Kernel will select whether to use above or remainder portion based on work-group assignment
rv.args.append<uint32_t>(
concatenate_if<T_Debug>("magicNumberNumIter_GsuRemainder"),
magicNumber(
sizeMapping.magicDivAlg, numIter + sizeMapping.depthU, &magicShift));
rv.args.append<uint32_t>(
concatenate_if<T_Debug>("magicShiftNumIter_GsuRemainder"), magicShift);
}
}
if(problem.freeIndicesA().size() > 1 || sizeMapping.packBatchDims & 0x1)
{
std::vector<size_t> packedIndices
= generatePackedIndicesA(problem, sizeMapping.packBatchDims);
// Pack in all non-summation indices, except don't need magic number for the
// last one
for(auto pi = packedIndices.begin(); pi != packedIndices.end() - 1; pi++)
{
auto idx = *pi;
auto size = a.sizes()[idx];
uint32_t magicShift;
rv.args.append<uint32_t>(concatenate_if<T_Debug>("magicNumberSizeA_", idx),
magicNumber(sizeMapping.magicDivAlg, size, &magicShift));
rv.args.append<uint32_t>(concatenate_if<T_Debug>("magicShiftSizeA_", idx),
magicShift);
}
}
if(problem.freeIndicesB().size() > 1 || sizeMapping.packBatchDims & 0x2)
{
std::vector<size_t> packedIndices
= generatePackedIndicesB(problem, sizeMapping.packBatchDims);
// Pack in all non-summation indices, except don't need magic number for the
// last one
for(auto pi = packedIndices.begin(); pi != packedIndices.end() - 1; pi++)
{
auto idx = *pi;
auto size = b.sizes()[idx];
uint32_t magicShift;
rv.args.append<uint32_t>(concatenate_if<T_Debug>("magicNumberSizeB_", idx),
magicNumber(sizeMapping.magicDivAlg, size, &magicShift));
rv.args.append<uint32_t>(concatenate_if<T_Debug>("magicShiftSizeB_", idx),
magicShift);
}
}
for(auto si : problem.boundIndices())
{
if(si.aZeroPad.valid())
{
rv.args.append<int32_t>(concatenate_if<T_Debug>("padStartA_", si.a),
si.aZeroPad.padStart);
rv.args.append<int32_t>(concatenate_if<T_Debug>("padEndA_", si.a),
si.aZeroPad.padEnd);
}
if(si.bZeroPad.valid())
{
rv.args.append<int32_t>(concatenate_if<T_Debug>("padStartB_", si.b),
si.bZeroPad.padStart);
rv.args.append<int32_t>(concatenate_if<T_Debug>("padEndB_", si.b),
si.bZeroPad.padEnd);
}
}
if(sizeMapping.staggerU)
rv.args.append<int32_t>("staggerUIter", staggerUIter(problem, inputs, hardware));
rv.args.append<uint32_t>("problemNumGroupTiles0", problemNumGroupTiles0);
rv.args.append<uint32_t>("problemNumGroupTiles1", problemNumGroupTiles1);
if(!isSourceKernel())
{
uint32_t numFullBlocks = problemNumGroupTiles1;
uint32_t wgmRemainder1 = 0;
uint32_t magicNumberWgmRemainder1 = 0;
// conditional args, aligned with KernelWriterAssembly.py
if(sizeMapping.persistentKernel != 0 || sizeMapping.streamK != 0)
{
uint32_t magicShift;
rv.args.append<uint32_t>("magicNumberProblemNumGroupTiles0",
magicNumber(2, problemNumGroupTiles0, &magicShift));
rv.args.append<uint32_t>("magicShiftProblemNumGroupTiles0", magicShift);
}
if(sizeMapping.persistentKernel != 0)
{
rv.args.append<uint32_t>("gridNumWorkGroups0", rv.numWorkGroups.x);
}
if(sizeMapping.persistentKernelAlongBatch)
{
uint32_t numGroupTiles0x1 = problemNumGroupTiles0 * problemNumGroupTiles1;
uint32_t magicShift;
rv.args.append<uint32_t>("problemNumGroupTiles2", problemNumGroupTiles2);
rv.args.append<uint32_t>("magicNumberProblemNumGroupTiles0By1",
magicNumber(2, numGroupTiles0x1, &magicShift));
rv.args.append<uint32_t>("magicShiftProblemNumGroupTiles0By1", magicShift);
}
if(sizeMapping.streamK != 0)
{
auto itersPerTile = problem.getItersPerTile(sizeMapping);
auto totalIters = tiles * itersPerTile;
uint32_t magicNumberItersPerTile;
uint32_t magicShiftItersPerTile;
magicNumberItersPerTile = magicNumber(2, itersPerTile, &magicShiftItersPerTile);
rv.args.append<uint32_t>("itersPerTile", itersPerTile);
rv.args.append<uint32_t>("magicNumberItersPerTile", magicNumberItersPerTile);
rv.args.append<uint32_t>("magicShiftItersPerTile", magicShiftItersPerTile);
uint32_t numGroupTiles0x1 = problemNumGroupTiles0 * problemNumGroupTiles1;
uint32_t magicNumProblemNumGroupTiles0By1;
uint32_t magicShiftProblemNumGroupTiles0By1;
magicNumProblemNumGroupTiles0By1
= magicNumber(2, numGroupTiles0x1, &magicShiftProblemNumGroupTiles0By1);
rv.args.append<uint32_t>("magicNumProblemNumGroupTiles0By1",
magicNumProblemNumGroupTiles0By1);
rv.args.append<uint32_t>("magicShiftProblemNumGroupTiles0By1",
magicShiftProblemNumGroupTiles0By1);
rv.args.append<uint32_t>("totalIters", totalIters);
if(sizeMapping.streamK == 1) // Basic SK
{
uint32_t itersPerWave = CeilDivide(totalIters, rv.numWorkGroups.x);
rv.args.append<uint32_t>("SKItersPerWG", itersPerWave);
}
else if(sizeMapping.streamK >= 2) // Two-tile SK
{
// skTiles is number of Stream-K tiles to complete
// Two-tile algorithm causes each WG to run an even number of Stream-K iterations,
// followed by an even number of data-parllel tiles.
// If total tiles is evenly divisble by grid size,
// then no Stream-K tiles are needed, all data-parallel
uint32_t skTiles = skGrid;
if(tiles % skGrid != 0)
{
// Number of SK tiles determined by performance prediction
// Either runs remainder tiles, or remainder + 1 full grid of tiles
// Can be overridden by setting TENSILE_STREAMK_FULL_TILES
skTiles = std::min(skGrid * fullTiles + tiles % skGrid, tiles);
}
uint32_t skItersPerWG = skTiles * itersPerTile / skGrid;
uint32_t skExtraIters = skTiles * itersPerTile % (skGrid);
rv.args.append<uint32_t>("SKItersPerWG", skItersPerWG);
rv.args.append<uint32_t>("skGrid", skGrid);
rv.args.append<uint32_t>("skTiles", skTiles);
rv.args.append<uint32_t>("skExtraIters", skExtraIters);
}
}
if(sizeMapping.workGroupMapping != 0)
{
if(sizeMapping.workGroupMapping >= 0)
{
numFullBlocks = problemNumGroupTiles1 / sizeMapping.workGroupMapping;
wgmRemainder1 = problemNumGroupTiles1 % sizeMapping.workGroupMapping;
}
else
{
numFullBlocks = problemNumGroupTiles0 / (-sizeMapping.workGroupMapping);
wgmRemainder1 = problemNumGroupTiles0 % (-sizeMapping.workGroupMapping);
}
if(wgmRemainder1 == 0)
wgmRemainder1 = sizeMapping.workGroupMapping;
magicNumberWgmRemainder1 = smallMagicNumber(wgmRemainder1);
}
if(std::abs(sizeMapping.workGroupMapping) > 1)
{
rv.args.append<uint32_t>("numFullBlocks", numFullBlocks);
rv.args.append<uint32_t>("wgmRemainder1", wgmRemainder1);
rv.args.append<uint32_t>("magicNumberWgmRemainder1", magicNumberWgmRemainder1);
}
}
if(problemType.stochasticRounding)
{
// generate seed from random generator
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> distribution(0, 0xFFFFFFFF);
uint32_t seed = distribution(gen);
//rv.args.append<uint32_t>("RNDSeed", 0x0);
rv.args.append<uint32_t>("RNDSeed", seed);
}
if(!isSourceKernel())
{
rv.args.append<uint32_t>("pad", 0);
}
rv.codeObjectFile = codeObjectFilename.load();
return rv;
}
bool ContractionSolution::isSourceKernel() const
{
return sizeMapping.sourceKernel;
}
template <typename TypedInputs, bool T_Debug>
KernelInvocation ContractionSolution::generateStreamKInitCall(Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
TensorDescriptor const& c = problem.c();
TensorDescriptor const& d = problem.d();
KernelInvocation rv;
rv.args = KernelArguments(T_Debug);
rv.args.reserve(512, 64);
rv.kernelName = streamKInitKernelName(problem, inputs, hardware);
rv.workGroupSize.x = 256;
rv.workGroupSize.y = 1;
rv.workGroupSize.z = 1;
auto tiles = problem.getNumTiles(sizeMapping);
size_t skGrid = getSKGrid(problem, hardware, tiles);
size_t wiZ = 1;
for(size_t i = 0; i < problem.batchIndices().size(); i++)
wiZ *= problem.batchSize(i);
size_t flagCount = skGrid * wiZ;
rv.numWorkGroups.x = CeilDivide(flagCount, rv.workGroupSize.x);
rv.numWorkGroups.y = 1;
rv.numWorkGroups.z = 1;
rv.numWorkItems.x = rv.workGroupSize.x * rv.numWorkGroups.x;
rv.numWorkItems.y = rv.workGroupSize.y * rv.numWorkGroups.y;
rv.numWorkItems.z = rv.workGroupSize.z * rv.numWorkGroups.z;
void* ws = inputs.ws;
size_t flagsOffset = partialTileSize(skGrid);
void* flags = (void*)(static_cast<char*>(ws) + flagsOffset);
rv.args.append<void*>("Flags", flags);
rv.args.append<uint32_t>("flagCount", flagCount);
//Pass along code object dependency
// TODO check this
rv.codeObjectFile = codeObjectFilename.load();
return rv;
}
template <typename TypedInputs>
std::string ContractionSolution::streamKInitKernelName(Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
std::string name = "WSFlags";
return name;
}
template <typename TypedInputs, bool T_Debug>
KernelInvocation ContractionSolution::generateBetaOnlyCall(Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
TensorDescriptor const& c = problem.c();
TensorDescriptor const& d = problem.d();
KernelInvocation rv;
rv.args = KernelArguments(T_Debug);
rv.args.reserve(512, 64);
rv.kernelName = betaOnlyKernelName(problem, inputs, hardware);
rv.workGroupSize.x = 256;
rv.workGroupSize.y = 1;
rv.workGroupSize.z = 1;
size_t wiX = 1;
size_t wiY = 1;
size_t wiZ = 1;
for(size_t i = 0; i < problem.freeIndicesA().size(); i++)
wiX *= problem.freeSizeA(i);
for(size_t i = 0; i < problem.freeIndicesB().size(); i++)
wiY *= problem.freeSizeB(i);
for(size_t i = 0; i < problem.batchIndices().size(); i++)
wiZ *= problem.batchSize(i);
rv.numWorkGroups.x = CeilDivide(wiX * wiY * wiZ, rv.workGroupSize.x);
rv.numWorkGroups.y = 1;
rv.numWorkGroups.z = 1;
rv.numWorkItems.x = rv.workGroupSize.x * rv.numWorkGroups.x;
rv.numWorkItems.y = rv.workGroupSize.y * rv.numWorkGroups.y;
rv.numWorkItems.z = rv.workGroupSize.z * rv.numWorkGroups.z;
if(sizeMapping.globalAccumulation)
rv.args.append<void*>("WS", inputs.ws);
else if(problemType.stridedBatched)
{
auto dptr = inputs.d;
dptr += d.offset();
rv.args.append<typename TypedInputs::DType*>("D", dptr);
}
else
rv.args.append<typename TypedInputs::DType const* const*>("batchD", inputs.batchD);
if(problemType.stridedBatched)
{
auto cptr = inputs.c;
cptr += c.offset();
rv.args.append<typename TypedInputs::CType const*>("C", cptr);
}
else
rv.args.append<typename TypedInputs::CType const* const*>("batchC", inputs.batchC);
if(!problemType.stridedBatched)
{
rv.args.append<uint64_t>("offsetD", d.offset());
rv.args.append<uint64_t>("offsetC", c.offset());
}
if(sizeMapping.globalAccumulation)
{
size_t stride = d.sizes()[0];
for(size_t i = 1; i < d.dimensions(); i++)
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideW", i),
d.sizes()[i] == 1 ? 0 : stride);
stride *= d.sizes()[i];
}
}
else
{
for(size_t i = 1; i < d.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideD", i),
d.sizes()[i] == 1 ? 0 : d.strides()[i]);
}
for(size_t i = 1; i < c.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideC", i),
c.sizes()[i] == 1 ? 0 : c.strides()[i]);
int idx = 0;
for(auto size : problem.d().sizes())
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("size_", idx), size);
idx++;
}
rv.args.append<typename TypedInputs::BetaType>("beta", inputs.beta);
//Pass along code object dependency
rv.codeObjectFile = codeObjectFilename.load();
return rv;
}
template <typename TypedInputs>
std::string ContractionSolution::betaOnlyKernelName(Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
std::string name = concatenate(
"C", problem.cNames(), "_", TypeInfo<typename TypedInputs::DType>::Abbrev());
if(!problemType.stridedBatched)
{
name += "_GB";
}
if(sizeMapping.globalAccumulation)
{
name += "_GA";
}
return name;
}
template <typename TypedInputs, bool T_Debug>
KernelInvocation ContractionSolution::generateOutputConversionCall(
Problem const& problem, TypedInputs const& inputs, Hardware const& hardware) const
{
AMDGPU const* pAMDGPU = dynamic_cast<AMDGPU const*>(&hardware);
assert(pAMDGPU != nullptr && pAMDGPU->computeUnitCount != 0);
size_t cuCount = pAMDGPU->computeUnitCount;
TensorDescriptor const& c = problem.c();
TensorDescriptor const& d = problem.d();
KernelInvocation rv;
rv.args = KernelArguments(T_Debug);
rv.args.reserve(512, 64);
rv.workGroupSize.x = 256;
rv.workGroupSize.y = 1;
rv.workGroupSize.z = 1;
size_t wiX = 1;
size_t wiY = 1;
size_t wiZ = 1;
for(size_t i = 0; i < problem.freeIndicesA().size(); i++)
wiX *= problem.freeSizeA(i);
for(size_t i = 0; i < problem.freeIndicesB().size(); i++)
wiY *= problem.freeSizeB(i);
for(size_t i = 0; i < problem.batchIndices().size(); i++)
wiZ *= problem.batchSize(i);
const unsigned int numThreadsPerCU = 256;
unsigned int gsu = static_cast<int>(sizeMapping.globalSplitU);
if(sizeMapping.globalAccumulation == 1)
// globalAccumulation = 1 case, ignore globalSplitU and use 1
gsu = 1;
// wider global load for postGSU
// only for compute type = Float
bool supportedTypeForVWopt
= problem.alphaType() == DataType::Float || problem.alphaType() == DataType::Double;
size_t total = wiX * wiY * wiZ;
int vw = 1;
size_t threshVW2
= cuCount * numThreadsPerCU * 2; // should be more than number of physical threads * vw
if(supportedTypeForVWopt && total > threshVW2 && problem.freeSizeA(0) % 2 == 0)
vw = 2;
// parallel reduction width calculation
// only for compute type = Float
bool supportedTypeForReductionOpt
= problem.alphaType() == DataType::Float || problem.alphaType() == DataType::Double;
size_t threshReduction
= cuCount
* numThreadsPerCU; // should be less than number of physical threads / reduction
const unsigned int maxReductionConst = 4;
const unsigned int minGSUperReduction
= 32; // Minimum GSU=128 for Reduction=4, GSU=64 for Reduction2
unsigned int maxReduction = std::min(maxReductionConst, gsu / minGSUperReduction);
unsigned int reduction = 1;
if(supportedTypeForReductionOpt && (gsu & (gsu - 1)) == 0 && maxReduction > 1)
{
// apply reduction only if float compute type and gsu is power of 2 (for small array only)
reduction = maxReduction;
while(reduction > 1)
{
size_t totalThread = total * reduction / vw;
if(gsu % reduction == 0 && totalThread <= threshReduction)
// found an applicable reduction
break;
// for next loop
reduction /= 2;
}
if(reduction == 2 && problem.alphaType() == DataType::Float)
// so far, reduction=2 does not perform well
reduction = 1;
}
// GSU loop unroll opt
// only for compute type = Float, Double
bool supportedTypeForUnrollOpt
= problem.alphaType() == DataType::Float || problem.alphaType() == DataType::Double;
int gsuUnrollUnit = 16 * reduction; // must match Tensile generator code
if(supportedTypeForUnrollOpt == false || sizeMapping.globalAccumulation == 1)
gsuUnrollUnit = 1;
rv.kernelName = outputConversionKernelName(
problem, inputs, gsu, vw, reduction, gsuUnrollUnit, hardware);
rv.numWorkGroups.x = CeilDivide(wiX * wiY * wiZ * reduction, rv.workGroupSize.x * vw);
rv.numWorkGroups.y = 1;
rv.numWorkGroups.z = 1;
rv.numWorkItems.x = rv.workGroupSize.x * rv.numWorkGroups.x;
rv.numWorkItems.y = rv.workGroupSize.y * rv.numWorkGroups.y;
rv.numWorkItems.z = rv.workGroupSize.z * rv.numWorkGroups.z;
if(problemType.stridedBatched)
{
auto dptr = inputs.d;
dptr += d.offset();
rv.args.append<typename TypedInputs::DType*>("D", dptr);
}
else
rv.args.append<typename TypedInputs::DType const* const*>("batchD", inputs.batchD);
rv.args.append<void*>("WS", inputs.ws);
if(problemType.stridedBatched)
{
auto cptr = inputs.c;
cptr += c.offset();
rv.args.append<typename TypedInputs::CType const*>("C", cptr);
}
else
rv.args.append<typename TypedInputs::CType const* const*>("batchC", inputs.batchC);
if(!problemType.stridedBatched)
{
rv.args.append<uint64_t>("offsetD", d.offset());
rv.args.append<uint64_t>("offsetC", c.offset());
}
if(sizeMapping.globalAccumulation == 2)
rv.args.append<typename TypedInputs::AlphaType>("alpha", inputs.alpha);
else
rv.args.append<typename TypedInputs::AlphaType>("alpha", 1.0f);
if(sizeMapping.globalAccumulation == 2 and problemType.useBeta)
rv.args.append<typename TypedInputs::BetaType>("beta", inputs.beta);
else
rv.args.append<typename TypedInputs::BetaType>("beta", 0.0f);
for(size_t i = 1; i < d.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideD", i), d.strides()[i]);
uint32_t wsStride = d.sizes()[0];
for(size_t i = 1; i < d.dimensions(); i++)
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideW", i), wsStride);
wsStride *= d.sizes()[i];
}
for(size_t i = 1; i < c.dimensions(); i++)
rv.args.append<uint32_t>(concatenate_if<T_Debug>("strideC", i), c.strides()[i]);
int idx = 0;
for(auto size : problem.d().sizes())
{
rv.args.append<uint32_t>(concatenate_if<T_Debug>("size_", idx), size);
idx++;
}
rv.args.append<uint32_t>("gsu", gsu);
if(problemType.stochasticRounding)
{
// generate seed from random generator
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> distribution(0, 0xFFFFFFFF);
uint32_t seed = distribution(gen);
//rv.args.append<uint32_t>("RNDSeed", 0x0);
rv.args.append<uint32_t>("RNDSeed", seed);
}
//@TODO determine if this is needed, may not end up in the same code object file
rv.codeObjectFile = codeObjectFilename.load();
return rv;
}
bool ContractionSolution::canSolve(Problem const& problem, Hardware const& hardware) const
{
static const bool debug = Debug::Instance().printPredicateEvaluation();
Task task(hardware, problem, *this);
if(debug)
{
hardwarePredicate->debugEval(hardware, std::cout);
problemPredicate->debugEval(problem, std::cout);
taskPredicate->debugEval(task, std::cout);
}
return (*taskPredicate)(task) && (*problemPredicate)(problem)
&& (*hardwarePredicate)(hardware);
}
bool ContractionSolution::matchesProblemType(Problem const& problem,
Hardware const& hardware) const
{
bool matchesHardware = (*hardwarePredicate)(hardware);
bool matchesType
= (problemType.aType == problem.a().dataType())
&& (problemType.bType == problem.b().dataType())
&& (problemType.cType == problem.c().dataType())
&& (problemType.dType == problem.d().dataType())
&& (problemType.highPrecisionAccumulate == problem.highPrecisionAccumulate())
&& (problemType.stridedBatched == problem.stridedBatched())
&& (problemType.fp16AltImpl == problem.fp16AltImpl())
&& (problemType.fp16AltImplRound == problem.fp16AltImplRound());
return matchesHardware && matchesType;
}
template <typename TypedInputs>
std::string ContractionSolution::outputConversionKernelName(Problem const& problem,
TypedInputs const& inputs,
int gsu,
int vw,
int reduction,
int gsuUnrollUnit,
Hardware const& hardware) const
{
std::string name = concatenate(
"C", problem.cNames(), "_", TypeInfo<typename TypedInputs::DType>::Abbrev());
if(!problemType.stridedBatched)
{
name += "_GB";
}
name += "_PostGSU";
// add extra string for gsu
// This part must match tensile code generation (in KernelWriterConversion.py)
// add mod related str (only for gsuUnrollUnit > 1)
if(gsuUnrollUnit > 1)
{
size_t gsuMod = gsu % gsuUnrollUnit;
if(gsuMod == 0)
gsuMod = gsuUnrollUnit;
std::string modStr = "";
if(gsu > gsuUnrollUnit)
{
modStr = "_mod" + std::to_string(gsuUnrollUnit);
}
name += std::to_string(gsuMod) + modStr;
}
if(vw >= 2)
name += "_VW" + std::to_string(vw);
if(reduction >= 2)
name += "_R" + std::to_string(reduction);
return name;
}
template <typename TypedInputs>
std::vector<KernelInvocation> ContractionSolution::solveTyped(Problem const& problem,
TypedInputs const& inputs,
Hardware const& hardware) const
{
bool debug = Debug::Instance().printKernelArguments() || this->kernelArgsLog;
bool debugSkipInit = Debug::Instance().skipInitKernelLaunch();
int boundSize = 1;
for(size_t i = 0; i < problem.boundIndices().size(); i++)
boundSize *= problem.boundSize(i);
// Check for nullptrs if alpha is non-zero.
if(((inputs.alpha != static_cast<typename TypedInputs::AlphaType>(0)) && (boundSize != 0))
&& ((problem.stridedBatched() && (inputs.a == nullptr || inputs.b == nullptr))
|| (!problem.stridedBatched()
&& (inputs.batchA == nullptr || inputs.batchB == nullptr))))
{
std::string matrixID = inputs.a == nullptr ? "A" : "B";
std::string msg = std::string("Unsupported nullptr for ") + matrixID
+ std::string(" when (Alpha !=0) && (K != 0)\n");
throw std::runtime_error(msg.c_str());
}
// Check if alpha matches problem definition
if(problem.alphaRestriction() != ScalarValue::Any
&& problem.alphaRestriction() != toScalarValueEnum(inputs.alpha))
{
std::stringstream inputValue;
inputValue << inputs.alpha;
std::string msg = std::string("Alpha value ") + inputValue.str()
+ std::string(" doesn't match that set in problem: ")
+ ToString(problem.alphaRestriction());
throw std::runtime_error(msg.c_str());
}
// Check if beta matches problem definition
if(problem.betaRestriction() != ScalarValue::Any
&& problem.betaRestriction() != toScalarValueEnum(inputs.beta))
{
std::stringstream inputValue;
inputValue << inputs.beta;
std::string msg = std::string("Beta value ") + inputValue.str()
+ std::string(" doesn't match that set in problem: ")
+ ToString(problem.betaRestriction());
throw std::runtime_error(msg.c_str());
}
if(problem.cEqualsD() && inputs.c != inputs.d)
throw std::runtime_error(
"ContractionProblem has cEqualsD set, but pointers for c and d are not equal");
std::vector<KernelInvocation> rv;
if(sizeMapping.streamK > 0 && sizeMapping.streamKAtomic == 0 && !debugSkipInit)
{
auto tiles = problem.getNumTiles(sizeMapping);
size_t skGrid = getSKGrid(problem, hardware, tiles);
// If problem can run full data-parallel, flags are not needed since there is no fixup step
// Skipping init kernel when flags are not needed saves significant time for small problems
if(tiles % skGrid != 0)
{
if(debug)
rv.push_back(
generateStreamKInitCall<TypedInputs, true>(problem, inputs, hardware));
else
rv.push_back(
generateStreamKInitCall<TypedInputs, false>(problem, inputs, hardware));
}
}
if((sizeMapping.streamK > 0 && sizeMapping.streamKAtomic == 1)
|| (sizeMapping.globalSplitU > 1 && sizeMapping.globalAccumulation != 2))
{
if(debug)
rv.push_back(generateBetaOnlyCall<TypedInputs, true>(problem, inputs, hardware));
else
rv.push_back(generateBetaOnlyCall<TypedInputs, false>(problem, inputs, hardware));
}
if(debug)
rv.push_back(generateSingleCall<TypedInputs, true>(problem, inputs, hardware));
else
rv.push_back(generateSingleCall<TypedInputs, false>(problem, inputs, hardware));
if(sizeMapping.globalAccumulation && !sizeMapping.streamK)
{
// TODO Streamk May need conversion call for HPA??
if(debug)
rv.push_back(
generateOutputConversionCall<TypedInputs, true>(problem, inputs, hardware));
else
rv.push_back(
generateOutputConversionCall<TypedInputs, false>(problem, inputs, hardware));
}
return rv;
}
bool ContractionSolution::getMatrixInstructionFromKernelName(
vector4<std::uint32_t>& matrixInst) const
{
std::string regexp_string("_MI(\\d+)x(\\d+)x(\\d+)x(\\d+)_");
std::regex miRegex(regexp_string);
std::smatch matches;
std::string kName = this->KernelName();
bool matched
= std::regex_search(kName, matches, miRegex)
&& (matches.size()
== 5 /* 1 (index 0) for entire match and 4 for the sub-experssion matches */);
if(matched)
{
// the first sub_match element (index 0) corresponds to the entire match
matrixInst.x = atoi(matches[1].str().c_str());
matrixInst.y = atoi(matches[2].str().c_str());
matrixInst.z = atoi(matches[3].str().c_str());
matrixInst.w = atoi(matches[4].str().c_str());
}
return matched;
}
bool ContractionSolution::getGSUAlgorithmFromKernelName(std::string& gsuAlg) const
{
std::string regexp_string("_GSUA([S|M])B_");
std::regex miRegex(regexp_string);
std::smatch matches;
std::string kName = this->KernelName();
bool matched
= std::regex_search(kName, matches, miRegex)
&& (matches.size()
== 2 /* 1 (index 0) for entire match and 1 for the sub-experssion matches */);
if(matched)
{
// the first sub_match element (index 0) corresponds to the entire match
gsuAlg = (matches[1].compare("S") == 0) ? "SingleBuffer" : "MultipleBuffer";
}
return matched;
}
std::vector<KernelInvocation>
ContractionSolution::solve(ContractionSolution::Problem const& problem,
ContractionSolution::Inputs const& inputs,
Hardware const& hardware) const
{
if(Debug::Instance().printWinningKernelName())
std::cout << "Running kernel: " << this->KernelName() << std::endl;
if(Debug::Instance().printKernelCommonParams())
{
std::cout << "Kernel name: " << this->KernelName() << std::endl;
std::cout << "Kernel parameters: " << std::endl;
vector4<std::uint32_t> matrixInst;
if(this->getMatrixInstructionFromKernelName(matrixInst))
std::cout << std::right << std::setw(30) << "MatrixInstruction: " << matrixInst
<< std::endl;
std::string GSUAlg;
if(this->getGSUAlgorithmFromKernelName(GSUAlg))
std::cout << std::right << std::setw(30) << "GSUAlgorithm: " << GSUAlg << std::endl;
std::cout << this->sizeMapping << std::endl;
}
// retreive alpha/beta type set via setAlpha/BetaType()
auto alphaType = problem.alphaType();
auto betaType = problem.betaType();
// TODO: Some gtests are passing the "problem" without actually defining the
// alpha/beta type (alphaType and betaType remain None).
// Until we fix those gtests, we need to keep this condition to adjust the missing
// alpha/beta data types.
if(alphaType == DataType::None)
{
alphaType
= problemType.aType == DataType::BFloat16 ? DataType::Float : problemType.dType;
}
if(betaType == DataType::None)
{
betaType = alphaType;
}
auto contractionInputsTypeId = ContractionInputs::TypeId(problemType.aType,
problemType.bType,
problemType.cType,
problemType.dType,
alphaType,
betaType);
switch(contractionInputsTypeId)
{
case ContractionInputs_S_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_S_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_D_D_D::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_D_D_D const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_C_C_C::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_C_C_C const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_Z_Z_Z::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_Z_Z_Z const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
#ifdef TENSILE_USE_HALF
case ContractionInputs_H_H_H::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_H_H_H const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_H_H_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_H_H_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_H_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_H_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
#endif // TENSILE_USE_HALF
case ContractionInputs_I8x4_I32_I32::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_I8x4_I32_I32 const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_I32_I32_I32::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_I32_I32_I32 const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_I8_I32_I32::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_I8_I32_I32 const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
#ifdef TENSILE_USE_BF16
case ContractionInputs_B_B_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B_B_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
#endif // TENSILE_USE_BF16
#ifdef TENSILE_USE_FP8_BF8
case ContractionInputs_F8_F8_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8_F8_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_F8_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8_B8_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8_B8_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
// hybrid cases: F8B8SS, B8F8SS
case ContractionInputs_F8B8_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8B8_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8F8_S_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8F8_S_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
// hybrid cases with To = B8
case ContractionInputs_F8B8_B8_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8B8_B8_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8F8_B8_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8F8_B8_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
// hybrid with F16 output
case ContractionInputs_F8_H_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8_H_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8_H_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8_H_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_F8B8_H_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_F8B8_H_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
case ContractionInputs_B8F8_H_S::TypeId():
{
auto const& typedInputs = dynamic_cast<ContractionInputs_B8F8_H_S const&>(inputs);
return solveTyped(problem, typedInputs, hardware);
}
#endif // TENSILE_USE_FP8_BF8
default:;
}
throw std::runtime_error("Data type not implemented.");
}
ContractionSolution::StaticPerformanceModel
ContractionSolution::staticPerformanceModel(double M,
double N,
double K,
double NumBatches,
double MT0,
double MT1,
double NumCUs,
double TotalGranularity,
int GlobalSplitU) const
{
StaticPerformanceModel spm;
int beta = (int)problemType.useBeta;
int betaReads = 0, betaWrites = 0;
if(GlobalSplitU == 1)
{
if(beta != 0.0)
betaReads = 1.0;
}
else
{
if(beta == 0)
betaWrites = 1; // zero output
else if(beta != 1.0) // if 1.0, just atomic update output
{
// if not 1.0, read, scale, write, then atomic update in kernel
betaReads = 1; // initial read for scale
betaWrites = 1; // writeback after scale
}
}
auto aInfo = DataTypeInfo::Get(problemType.aType);
auto bInfo = DataTypeInfo::Get(problemType.bType);
auto cInfo = DataTypeInfo::Get(problemType.cType);
auto dInfo = DataTypeInfo::Get(problemType.dType);
spm.memReadBytesA = (NumBatches * M * N * K) / MT1 * aInfo.elementSize;
spm.memReadBytesB = (NumBatches * M * N * K) / MT0 * bInfo.elementSize;
spm.memReadBytesC = (NumBatches * M * N) * betaReads * cInfo.elementSize;
if(GlobalSplitU == 1)
spm.memWriteBytesD = (NumBatches * M * N) * (1 + betaWrites) * dInfo.elementSize;
else
{
bool hardwareAtomic = false; // TODO-model
double atomicOperations = hardwareAtomic ? 2 : 3; // read-mod-write or cas //TODO-model
double atomicCollisions = 1.0; // TODO-could be based on K, GSU
spm.memWriteBytesD = (NumBatches * M * N)
* (betaWrites + atomicOperations * atomicCollisions)
* dInfo.elementSize;
}
spm.memReadBytes = spm.memReadBytesA + spm.memReadBytesB + spm.memReadBytesC;
spm.memGlobalReads = spm.memReadBytesA / aInfo.elementSize
+ spm.memReadBytesB / bInfo.elementSize
+ spm.memReadBytesC / cInfo.elementSize;
spm.memGlobalWrites = spm.memWriteBytesD / dInfo.elementSize;
return spm;
}
size_t ContractionSolution::requiredWorkspaceSize(Problem const& problem,
Hardware const& hardware) const
{
size_t size = 0;
if(sizeMapping.streamK > 0 && sizeMapping.streamKAtomic == 0)
{
auto tiles = problem.getNumTiles(sizeMapping);
size_t skGrid = getSKGrid(problem, hardware, tiles);
// Get space required for partial tiles
size += partialTileSize(skGrid);
// Add space for flags
// Flags for partial tiles - dword per flag for fast addressing and comparisons
// If tiles is evenly divided by grid size flags are not needed (DP mode)
if(tiles % skGrid != 0)
size += skGrid * 4;
// size *= batches; // TODO need tile and flag per batch
}
else
size += problem.d().totalLogicalElements() * sizeMapping.workspaceSizePerElemC;
return size;
}
float lerp(float x, float x0, float x1, float y0, float y1)
{
return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
}
int ContractionSolution::predictNumFullTiles(Problem const& problem,
Hardware const& hardware,
size_t tiles,
size_t skGrid) const
{
int itersPerTile = problem.getItersPerTile(sizeMapping);
int k = itersPerTile * sizeMapping.depthU;
// TODO Test if prediction should be normalized to iterations (DepthU) or full size
auto remainder = tiles % skGrid;
if(remainder == 0)
return 0;
// Temporary check for correct hardware until prediction curves are made for additional devices
AMDGPU const* pAMDGPU = dynamic_cast<AMDGPU const*>(&hardware);
assert(pAMDGPU != nullptr && pAMDGPU->computeUnitCount != 0);
size_t cuCount = pAMDGPU->computeUnitCount;
if(cuCount != 304)
return 0;
// TODO Generalize code to allow prediction models for additional architectures
const std::vector<std::tuple<size_t, size_t>> predictionCurve = {{512, 304},
{768, 201},
{1024, 75},
{1536, 40},
{2048, 30},
{3072, 21},
{4096, 14},
{5120, 11},
{6144, 9},
{7168, 8},
{8192, 5},
{16384, 4}};
if(k < std::get<0>(predictionCurve[0]))
return 1;
for(int i = 1; i < predictionCurve.size(); ++i)
{
if(k < std::get<0>(predictionCurve[i]))
{
float threshold = lerp(k,
std::get<0>(predictionCurve[i - 1]),
std::get<0>(predictionCurve[i]),
std::get<1>(predictionCurve[i - 1]),
std::get<1>(predictionCurve[i]));
return remainder >= threshold ? 0 : 1;
}
}
return remainder >= std::get<1>(predictionCurve.back()) ? 0 : 1;
}
size_t ContractionSolution::getSKGrid(Problem const& problem,
Hardware const& hardware,
size_t tiles) const
{
AMDGPU const* pAMDGPU = dynamic_cast<AMDGPU const*>(&hardware);
assert(pAMDGPU != nullptr && pAMDGPU->computeUnitCount != 0);
size_t cuCount = pAMDGPU->computeUnitCount;
// User-specified grid size for Stream-K kernel.
if(pAMDGPU->skFixedGrid > 0)
{
return pAMDGPU->skFixedGrid;
}
// Dynamically pick the minimum between the cuCount or number of tiles.
else if(pAMDGPU->skDynamicGrid == 1)
{
return min(cuCount, tiles);
}
// Dynamically pick the minimum between the cuCount or number of tiles,
// and scale down really large sizes to use fewer CUs for power/energy savings.
else if(pAMDGPU->skDynamicGrid == 2)
{
size_t skGrid = cuCount;
if(tiles > skGrid)
{
for(size_t i = 1; i <= 32; i *= 2)
{
size_t tilesPerCU = CeilDivide(i * tiles, cuCount);
size_t reducedGrid = CeilDivide(i * tiles, tilesPerCU);
float utilization = ((float)reducedGrid) / ((float)cuCount);
if(utilization > 0.75f)
{
if(utilization < 1.0f)
skGrid = reducedGrid;
break;
}
}
}
return min(skGrid, tiles);
}
// Dynamically predict the best grid-size by weighing the cost of the fix-up
// step and the cost of processing MAC-loop instructions. When the cost of fix-up
// is the bottleneck, use smaller grid size.
// Architecture dependent.
else if(pAMDGPU->skDynamicGrid == 3)
{
size_t x = 1;
size_t y = 1;
size_t z = 1;
for(size_t i = 0; i < problem.freeIndicesA().size(); i++)
{
x *= problem.freeSizeA(i);
}
for(size_t i = 0; i < problem.freeIndicesB().size(); i++)
{
y *= problem.freeSizeB(i);
}
// TODO Batch dimension
for(size_t i = 0; i < problem.boundIndices().size(); ++i)
{
z *= problem.boundSize(i);
}
return streamk::best_predicted_grid_size(sizeMapping.macroTile.x,
sizeMapping.macroTile.y,
sizeMapping.depthU,
x,
y,
z,
1,
cuCount);
}
// Limit the CUs Stream-K is launched on either max or the specified,
// whichever is minimum.
else if(pAMDGPU->skMaxCUs > 0)
{
return min(cuCount, static_cast<size_t>(pAMDGPU->skMaxCUs));
}
// Multiply the cuCount with a constant factor (c), and launch
// c * cuCount number of workgroups for Stream-K.
else if(pAMDGPU->skGridMultiplier > 1)
{
return cuCount * pAMDGPU->skGridMultiplier;
}
// If no option is specified, launch exactly cuCount worth of workgroups.
else
{
return cuCount;
}
}
size_t ContractionSolution::partialTileSize(size_t skGrid) const
{
size_t size = 0;
size_t tileSize
= sizeMapping.macroTile.x * sizeMapping.macroTile.y * sizeMapping.workspaceSizePerElemC;
size += tileSize * skGrid; // Partials tile per WG
// TODO batches
// TODO round up for alignment?
return size;
}
float ContractionSolution::computeGranularity(float x)
{
return x / ceil(x);
}
ContractionSolution::Granularities ContractionSolution::computeGranularities(
Hardware const& hardware, double M, double N, double K, double NumBatches) const
{
ContractionSolution::Granularities granularities;
double MT0 = sizeMapping.macroTile.x;
double MT1 = sizeMapping.macroTile.y;
AMDGPU const* pAMDGPU = dynamic_cast<AMDGPU const*>(&hardware);
assert(pAMDGPU);
double NumCUs = pAMDGPU->computeUnitCount;
double wavefrontSize = pAMDGPU->wavefrontSize;
double simdPerCu = pAMDGPU->simdPerCu;
double GlobalSplitU = sizeMapping.globalSplitU;
double LocalSplitU = sizeMapping.workGroupSize.z;
granularities.MT0 = MT0;
granularities.MT1 = MT1;
granularities.GSU = GlobalSplitU;
granularities.LSU = LocalSplitU;
granularities.CUs = NumCUs;
granularities.numTiles0 = M / MT0;
granularities.numTiles1 = N / MT1;
granularities.tile0Granularity = computeGranularity(granularities.numTiles0);
granularities.tile1Granularity = computeGranularity(granularities.numTiles1);
granularities.tilesPerCu
= (NumBatches * ceil(granularities.numTiles0) * ceil(granularities.numTiles1))
/ (NumCUs / GlobalSplitU / LocalSplitU);
granularities.totalTiles = ceil(granularities.numTiles0) * ceil(granularities.numTiles1);
granularities.natTilesPerCu = NumBatches * granularities.totalTiles / NumCUs;
granularities.suTilesPerCu = (granularities.totalTiles * GlobalSplitU) / NumCUs;
granularities.suCuGranularity = computeGranularity(granularities.suTilesPerCu);
granularities.waveGranularity = std::min(
1.00,
static_cast<double>(floor(granularities.tilesPerCu + 1.0) * sizeMapping.workGroupSize.x
* sizeMapping.workGroupSize.y * sizeMapping.workGroupSize.z)
/ pAMDGPU->wavefrontSize / pAMDGPU->simdPerCu);
granularities.waves
= ceil((sizeMapping.workGroupSize.x * sizeMapping.workGroupSize.y) / wavefrontSize);
granularities.suWavesPerSimdx2
= (granularities.suTilesPerCu * granularities.waves) / (2 * simdPerCu);
granularities.suWaveGranularity
= granularities.suWavesPerSimdx2 * ceil(granularities.suWavesPerSimdx2);
double nat_tiles_per_cu
= NumBatches * ceil(granularities.numTiles0) * ceil(granularities.numTiles1) / NumCUs;
granularities.natCuGranularity = ceil(nat_tiles_per_cu) * ceil(nat_tiles_per_cu) / NumCUs;
granularities.cuGranularity = computeGranularity(granularities.tilesPerCu);
granularities.totalGranularity
= granularities.tile0Granularity * granularities.tile1Granularity
* granularities.cuGranularity * granularities.waveGranularity;
granularities.totalTileAwareGranularity
= granularities.tile0Granularity * granularities.tile1Granularity
* granularities.suCuGranularity * granularities.suWaveGranularity;
return granularities;
}
ContractionSolution::ProjectedPerformance
ContractionSolution::projectedPerformance(Problem const& problem,
Hardware const& hardware) const
{
ProjectedPerformance pp;
double M = 1.0, N = 1.0;
if(problem.freeIndicesA().size() > 1 || sizeMapping.packBatchDims & 0x1)
{
std::vector<size_t> packedIndices
= generatePackedIndicesA(problem, sizeMapping.packBatchDims);
for(auto pi = packedIndices.begin(); pi != packedIndices.end(); pi++)
M *= problem.a().sizes()[*pi];
}
else
M = problem.freeSizeA(0);
if(problem.freeIndicesB().size() > 1 || sizeMapping.packBatchDims & 0x2)
{
std::vector<size_t> packedIndices
= generatePackedIndicesB(problem, sizeMapping.packBatchDims);
for(auto pi = packedIndices.begin(); pi != packedIndices.end(); pi++)
N *= problem.b().sizes()[*pi];
}
else
N = problem.freeSizeB(0);
double NumBatches = 1;
if(sizeMapping.packBatchDims == 0)
{
for(size_t i = 0; i < problem.batchIndices().size(); i++)
NumBatches *= problem.batchSize(i);
}
double K = problem.boundSize(0); // TODO - fix for multiple summations
pp.granularities = ContractionSolution::computeGranularities(hardware, M, N, K, NumBatches);
auto it = ideals.begin();
int closestKMeasure = std::numeric_limits<int>::max();
double closestKPerformance = 0.0;
while(it != ideals.end())
{
int myK = it->first;
int myMeasure = std::abs(myK - K);
if(myMeasure < closestKMeasure)
{
closestKMeasure = myMeasure;
closestKPerformance = it->second;
}
it++;
}
double MT0 = pp.granularities.MT0;
double MT1 = pp.granularities.MT1;
double NumCUs = pp.granularities.CUs;
double GlobalSplitU = pp.granularities.GSU;
double IdealGranularityPerf = closestKPerformance;
pp.staticModel = staticPerformanceModel(
M, N, K, NumBatches, MT0, MT1, NumCUs, pp.granularities.totalGranularity, GlobalSplitU);
pp.speedGFlops = IdealGranularityPerf * pp.granularities.totalGranularity;
pp.CUs = NumCUs;
return pp;
}
ContractionSolution::TAMetricProblemScore ContractionSolution::computeProblemScore(
Hardware const& hardware, double M, double N, double K, double NumBatches) const
{
ContractionSolution::TAMetricProblemScore pp;
pp.granularites = ContractionSolution::computeGranularities(hardware, M, N, K, NumBatches);
pp.M = M;
pp.N = N;
pp.K = K;
double slope = linearModel.slope;
double intercept = linearModel.intercept;
double perf_max = linearModel.max;
double sum_value = K;
double sum_perf0 = sum_value / (intercept + (slope * sum_value));
pp.summationPerformance = 1000.0 * sum_perf0 / perf_max;
return pp;
}
double ContractionSolution::computeTileAwareMetric(
ContractionSolution::TAMetricProblemScore pp,
ContractionSolution::TAMetricProblemScore ppReference) const
{
double tile0GranularityDim = abs(log(ppReference.granularites.tile0Granularity)
- log(pp.granularites.tile0Granularity));
double metric = tile0GranularityDim;
double tile1GranularityDim = abs(log(ppReference.granularites.tile1Granularity)
- log(pp.granularites.tile1Granularity));
metric += tile1GranularityDim;
double natCuGranularityDim = abs(log(ppReference.granularites.natCuGranularity)
- log(pp.granularites.natCuGranularity));
metric += natCuGranularityDim;
double suCuGranularityDim = abs(log(ppReference.granularites.suCuGranularity)
- log(pp.granularites.suCuGranularity));
metric += suCuGranularityDim;
double suWaveGranularityDim = abs(log(ppReference.granularites.suWaveGranularity)
- log(pp.granularites.suWaveGranularity));
metric += suWaveGranularityDim;
double natTilesPerCuDim
= abs(log(ppReference.granularites.natTilesPerCu) - log(pp.granularites.natTilesPerCu));
metric += natTilesPerCuDim;
double suTilesPerCuDim
= abs(log(ppReference.granularites.suTilesPerCu) - log(pp.granularites.suTilesPerCu));
metric += suTilesPerCuDim;
double summationPerformanceDim
= abs(ppReference.summationPerformance - pp.summationPerformance);
metric += summationPerformanceDim;
return metric;
}
double ContractionSolution::computeTAMScore(Problem const& problem,
Hardware const& hardware,
double model_M,
double model_N,
double model_K,
double model_NumBatches) const
{
double M = 1.0, N = 1.0;
if(problem.freeIndicesA().size() > 1 || sizeMapping.packBatchDims & 0x1)
{
std::vector<size_t> packedIndices
= generatePackedIndicesA(problem, sizeMapping.packBatchDims);
for(auto pi = packedIndices.begin(); pi != packedIndices.end(); pi++)
M *= problem.a().sizes()[*pi];
}
else
M = problem.freeSizeA(0);
if(problem.freeIndicesB().size() > 1 || sizeMapping.packBatchDims & 0x2)
{
std::vector<size_t> packedIndices
= generatePackedIndicesB(problem, sizeMapping.packBatchDims);
for(auto pi = packedIndices.begin(); pi != packedIndices.end(); pi++)
N *= problem.b().sizes()[*pi];
}
else
N = problem.freeSizeB(0);
double NumBatches = 1;
if(sizeMapping.packBatchDims == 0)
{
for(size_t i = 0; i < problem.batchIndices().size(); i++)
NumBatches *= problem.batchSize(i);
}
double K = problem.boundSize(0); // TODO - fix for multiple summations
ContractionSolution::TAMetricProblemScore pp
= computeProblemScore(hardware, M, N, K, NumBatches);
ContractionSolution::TAMetricProblemScore ppReference
= computeProblemScore(hardware, model_M, model_N, model_K, model_NumBatches);
double distance = computeTileAwareMetric(pp, ppReference);
return distance;
}
std::ostream& operator<<(std::ostream& stream,
ContractionSolution::StaticPerformanceModel const& spm)
{
return stream << " memReadBytesA=" << spm.memReadBytesA
<< " memReadBytesB=" << spm.memReadBytesB
<< " memReadBytesC=" << spm.memReadBytesC
<< " memWriteBytesD=" << spm.memWriteBytesD;
}
std::ostream& operator<<(std::ostream& stream,
ContractionSolution::ProjectedPerformance const& pp)
{
return stream << " numTiles0=" << pp.granularities.numTiles0
<< " numTiles1=" << pp.granularities.numTiles1
<< " tilesPerCu=" << pp.granularities.tilesPerCu
<< " totalGranularity=" << pp.granularities.totalGranularity
<< " tile0Granularity=" << pp.granularities.tile0Granularity
<< " tile1Granularity=" << pp.granularities.tile1Granularity
<< " cuGranularity=" << pp.granularities.cuGranularity
<< " waveGranularity=" << pp.granularities.waveGranularity
<< " speedGFlops=" << pp.speedGFlops
<< " staticModel=[ " << pp.staticModel << " ]";
}
std::ostream& operator<<(std::ostream& stream, BufferLoadCheckPacket const& st)
{
return stream << " shiftPtrElemA=" << st.shiftPtrElemA
<< " shiftPtrElemB=" << st.shiftPtrElemB << " depthUorMT0=" << st.depthUorMT0
<< " depthUorMT1=" << st.depthUorMT1;
}
std::ostream& operator<<(std::ostream& stream, const SizeMapping& sizeMapping)
{
std::ios_base::fmtflags flags(stream.flags());
stream << std::right << std::setw(30) << "workGroupSize: " << sizeMapping.workGroupSize
<< std::endl
<< std::setw(30) << "threadTile: " << sizeMapping.threadTile << std::endl
<< std::setw(30) << "macroTile: " << sizeMapping.macroTile << std::endl
<< std::setw(30) << "staggerU: " << sizeMapping.staggerU << std::endl
<< std::setw(30) << "depthU: " << sizeMapping.depthU << std::endl
<< std::setw(30) << "globalSplitU: " << sizeMapping.globalSplitU << std::endl
<< std::setw(30) << "staggerStrideShift: " << sizeMapping.staggerStrideShift
<< std::endl
<< std::setw(30) << "workGroupMapping: " << sizeMapping.workGroupMapping << std::endl
<< std::setw(30) << "packBatchDims: " << sizeMapping.packBatchDims << std::endl
<< std::setw(30) << "packSummationDims: " << sizeMapping.packSummationDims
<< std::endl
<< std::setw(30) << "magicDivAlg: " << sizeMapping.magicDivAlg << std::endl
<< std::setw(30) << "streamK: " << sizeMapping.streamK << std::endl
<< std::setw(30) << "streamKAtomic: " << sizeMapping.streamKAtomic << std::endl
<< std::setw(30) << "persistentKernel: " << sizeMapping.persistentKernel << std::endl
<< std::setw(30)
<< "persistentKernelAlongBatch: " << sizeMapping.persistentKernelAlongBatch
<< std::endl
<< std::setw(30) << "sourceKernel: " << sizeMapping.sourceKernel << std::endl
<< std::setw(30) << "globalAccumulation: " << sizeMapping.globalAccumulation
<< std::endl
<< std::setw(30) << "workspaceSizePerElemC: " << sizeMapping.workspaceSizePerElemC
<< std::endl;
stream.flags(flags);
return stream;
}
} // namespace Tensile