#include "gpu/intel/gemm/jit/gen_kernel.hpp"
#include "common/c_types_map.hpp"
#include "common/impl_registration.hpp"
#include "common/type_helpers.hpp"
#include "common/utils.hpp"
#include "gemmstone/../../generator/pieces/compute_utils.hpp"
#include "gemmstone/../../generator_dsl/builder.hpp"
#include "gemmstone/../../generator_dsl/kernel_desc.hpp"
#include "gemmstone/dsl/dsl.hpp"
#include "gemmstone/generator.hpp"
#include "gemmstone/kernel_evaluator.hpp"
#include "gemmstone/kernel_selector.hpp"
#include "gemmstone/strategy_parser.hpp"
#include "gpu/intel/compute/device_info.hpp"
#include "gpu/intel/gemm/jit/gen_kernel_db.hpp"
#include "gpu/intel/gemm/jit/pd.hpp"
#include "gpu/intel/jit/ir/hw.hpp"
#include "gpu/intel/jit/utils/type_bridge.hpp"
#include "gpu/intel/utils.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace gemm {
namespace jit {
using namespace gemmstone;
using namespace intel::jit;
namespace {
void entryObserver(
const kcatalog::Entry *entry, double score, EvaluateAuxOutput aux) {
if (get_verbose(verbose_t::debuginfo) >= 5) {
dnnl::impl::verbose_printf("info,gpu,gemm,consider:%s,score:%f\n",
entry->str().c_str(), score);
}
}
}
bool enable_generator_dsl() {
static const bool ret
= gpu_utils::dev_getenv("enable_generator_dsl", false);
return ret;
}
status_t gen_desc_t::create_generator(
const intel::engine_t &engine, compute::kernel_t &kernel) const {
gen_kernel_t kd(*this);
return engine.create_kernel(&kernel, &kd);
}
compute::scalar_type_t gen_desc_t::scalar_type() const {
switch (problem_.Ts) {
case Type::s4: return compute::scalar_type_t::_int4;
case Type::u4: return compute::scalar_type_t::_uint4;
case Type::s8: return compute::scalar_type_t::_char;
case Type::u8: return compute::scalar_type_t::_uchar;
case Type::s16: return compute::scalar_type_t::_short;
case Type::u16: return compute::scalar_type_t::_ushort;
case Type::s32: return compute::scalar_type_t::_int;
case Type::u32: return compute::scalar_type_t::_uint;
case Type::s64: return compute::scalar_type_t::_long;
case Type::u64: return compute::scalar_type_t::_ulong;
case Type::f4_e2m1: return compute::scalar_type_t::_f4_e2m1;
case Type::f4_e3m0: return compute::scalar_type_t::_f4_e3m0;
case Type::bf8: return compute::scalar_type_t::_bfloat8;
case Type::hf8: return compute::scalar_type_t::_hfloat8;
case Type::bf16: return compute::scalar_type_t::_bfloat16;
case Type::f16: return compute::scalar_type_t::_half;
case Type::f32: return compute::scalar_type_t::_float;
case Type::f64: return compute::scalar_type_t::_double;
default: return compute::scalar_type_t::undef;
}
}
#ifdef DNNL_DEV_MODE
static gemmstone::Scalar stringToScalar(std::string val) {
using namespace gemmstone;
switch (val.c_str()[0]) {
case '-': return Scalar(Scalar::Variable);
default: return Scalar(std::stoi(val));
}
}
#endif
status_t gen_desc_t::finalize(const char *tags) {
if (!isPacked(problem_.A.layout)
&& problem_.Ta_ext.paddedSize() >= problem_.Ta.paddedSize()) {
problem_.A.setAlignment(std::max(
problem_.Ta_ext.paddedSize(), entry_->driverInfo.alignment[0]));
}
if (!isPacked(problem_.B.layout)
&& problem_.Tb_ext.paddedSize() >= problem_.Tb.paddedSize()) {
problem_.B.setAlignment(std::max(
problem_.Tb_ext.paddedSize(), entry_->driverInfo.alignment[1]));
}
if (!isPacked(problem_.C.layout)) {
problem_.C.setAlignment(std::max(problem_.Tc_ext.paddedSize(),
entry_->restrictions.alignment[2]));
}
problem_.CO.setAlignment(problem_.Tco.paddedSize());
strategy_ = GEMMStrategy(hw_, stepping_);
#ifdef DNNL_DEV_MODE
std::string ovr_strategy;
ovr_strategy = gpu_utils::dev_getenv("GEMM_KERNEL", ovr_strategy);
if (!ovr_strategy.empty()) {
std::stringstream ss(ovr_strategy);
std::string val;
ss >> val;
gpu_assert(val == "gemm");
ss >> val;
const char *pstr = val.c_str();
pstr = parsePrecisions(pstr, problem_.Ta_ext, problem_.Ta);
pstr = parsePrecisions(pstr, problem_.Tb_ext, problem_.Tb);
pstr = parsePrecisions(pstr, problem_.Tc, problem_.Tc_ext);
ss >> val;
pstr = val.c_str();
pstr = parseLayout(pstr, problem_.A);
pstr = parseLayout(pstr, problem_.B);
pstr = parseLayout(pstr, problem_.C);
if (problem_.A.alignment == 0)
problem_.A.setAlignment(
problem_.A.defaultAlignment(problem_.Ta_ext));
if (problem_.B.alignment == 0)
problem_.B.setAlignment(
problem_.B.defaultAlignment(problem_.Tb_ext));
if (problem_.C.alignment == 0)
problem_.C.setAlignment(
problem_.C.defaultAlignment(problem_.Tc_ext));
strategy_ = GEMMStrategy(hw_, stepping_);
ss >> strategy_.unroll[LoopM];
ss >> strategy_.unroll[LoopN];
ss >> val;
problem_.alpha = stringToScalar(val);
ss >> val;
problem_.beta = stringToScalar(val);
ovr_strategy = ss.str().substr(ss.tellg()); parseStrategy(ovr_strategy, hw_, problem_, strategy_);
if (strategy_.kParallelLocal) {
aux_params_.k0
= utils::rnd_up(utils::div_up(k_, strategy_.wg[LoopK]),
strategy_.unroll[LoopK]);
aux_params_.wgK = std::max(1,
std::min(strategy_.wg[LoopK],
int(utils::div_up(k_, aux_params_.k0))));
} else {
aux_params_.k0 = EvaluateAuxOutput().k0;
aux_params_.wgK = EvaluateAuxOutput().wgK;
}
} else {
#endif
strategy_.unroll[LoopM] = entry_->driverInfo.unroll[LoopM];
strategy_.unroll[LoopN] = entry_->driverInfo.unroll[LoopN];
parseStrategy(entry_->strategy, hw_, problem_, strategy_);
modifyStrategy(strategy_, aux_params_);
#ifdef DNNL_DEV_MODE
}
#endif
strategy_.panelCheck
|= (isPacked(problem_.A.layout) || isPacked(problem_.B.layout));
if (enable_generator_dsl()) { fixup_dsl_strategy(strategy_); }
if (strategy_.kParallelLocal) {
if (problem_.quantized2DA())
aux_params_.k0 = utils::rnd_up(aux_params_.k0, problem_.aqGroupK);
if (problem_.quantized2DB())
aux_params_.k0 = utils::rnd_up(aux_params_.k0, problem_.bqGroupK);
}
if (hw_ == ngen::HW::Xe2 || hw_ == ngen::HW::Xe3) {
strategy_.raHW = ngen::HW::XeHPC;
bool block_2d_a = false, block_2d_b = false;
for (auto c = tags; *c; c++) {
block_2d_a |= (*c == kcatalog::ReqBlock2DA);
block_2d_b |= (*c == kcatalog::ReqBlock2DB);
}
if (block_2d_a && strategy_.legalAAlignment(problem_, 16))
problem_.A.setAlignment(nstl::max<int>(problem_.A.alignment, 16));
if (block_2d_b && strategy_.legalBAlignment(problem_, 16))
problem_.B.setAlignment(nstl::max<int>(problem_.B.alignment, 16));
}
if (utils::one_of(hw_, ngen::HW::XE3P_35_10, ngen::HW::XE3P_35_11,
ngen::HW::XE3P_UNKNOWN)) {
if (!efficient_64b_) strategy_.raHW = ngen::HW::XeHPC;
strategy_.namedBarriers[0] = 0;
strategy_.namedBarriers[1] = 0;
}
if (strategy_.kParallel && k_ >= 0) {
auto k_min = aux_params_.k0 * aux_params_.wgK;
if (k_ <= k_min) {
strategy_.kParallel = false;
strategy_.C.atomic = false;
strategy_.CO.atomic = false;
}
}
if (strategy_.kParallel && !strategy_.fuseBeta) problem_.beta = Scalar();
if (strategy_.barrierFreq > 0 && k_ >= 0 && k_ < 2 * strategy_.barrierFreq)
strategy_.barrierFreq = 0;
if (strategy_.fixedSystolic) strategy_.GRFs = 256;
if (m_ >= 0 && n_ >= 0 && eu_count_ >= 0) {
int wg_tile_m = strategy_.wg[LoopM] * strategy_.unroll[LoopM];
int wg_tile_n = strategy_.wg[LoopN] * strategy_.unroll[LoopN];
if (wg_tile_m > 0 && wg_tile_n > 0) {
dim_t m_tiles = dim_t(utils::div_up(m_, wg_tile_m));
dim_t n_tiles = dim_t(utils::div_up(n_, wg_tile_n));
dim_t thread_per_tg = strategy_.wg[LoopM] * strategy_.wg[LoopN];
if (!strategy_.kParallelVariable)
thread_per_tg *= std::max(strategy_.wg[LoopK], 1);
dim_t thread_gpu = eu_count_
* compute::device_info_t::threads_per_eu(
arch_, strategy_.GRFs > 128);
dim_t tiles_gpu = thread_gpu / thread_per_tg;
bool use_linear = (m_tiles * n_tiles <= tiles_gpu);
bool use_linear_m = (m_tiles * m_tiles <= 2 * tiles_gpu);
bool use_linear_n = (n_tiles * n_tiles <= 2 * tiles_gpu);
if (strategy_.fused)
if (strategy_.wg[LoopM] % 2 || strategy_.wg[LoopN] % 2)
use_linear_m = use_linear_n = false;
if (use_linear) {
if (strategy_.kParallelVariable)
strategy_.cWalkOrder = WalkOrder::SimpleLinear;
else if (strategy_.kParallel
&& (strategy_.fuseBeta || strategy_.fusePostOps)) {
strategy_.persistent = false;
strategy_.cWalkOrder = WalkOrder::SimpleLinear;
} else {
strategy_.persistent = false;
strategy_.cWalkOrder = WalkOrder::HW2D;
strategy_.blocking[LoopM] = 16777216;
strategy_.blocking[LoopN] = 16777216;
}
} else if (use_linear_m || use_linear_n) {
if (use_linear_n && !use_linear_m) {
strategy_.loopOrder[0] = LoopN;
strategy_.loopOrder[1] = LoopM;
} else if (use_linear_m && !use_linear_n) {
strategy_.loopOrder[0] = LoopM;
strategy_.loopOrder[1] = LoopN;
}
strategy_.cWalkOrder = WalkOrder::SimpleLinear;
}
}
}
strategy_.relaxedAccumulation |= relaxed_acc_;
strategy_.systolicAvailable &= !disable_systolic_;
if (problem_.needsAGroupSums() || problem_.needsBGroupSums())
problem_.autoTypeConversions(hw_, strategy_.systolicAvailable);
adjustStrategy(hw_, problem_, strategy_, tags);
try {
strategy_.preflight(hw_, problem_);
} catch (...) { return status::unimplemented; }
if (problem_.aOffset2D() || problem_.aScale2D())
if (problem_.aqGroupK % strategy_.aqGroupKGranularity())
return status::unimplemented;
if (problem_.bOffset2D() || problem_.bScale2D())
if (problem_.bqGroupK % strategy_.bqGroupKGranularity())
return status::unimplemented;
if (problem_.aScale2D()
&& problem_.aqGroupK
% minOuterProductCount(hw_, problem_, strategy_)
!= 0) {
if (!problem_.Ta.isF4() || !problem_.Tb.isF4())
return status::unimplemented;
}
if (problem_.bScale2D()
&& problem_.bqGroupK
% minOuterProductCount(hw_, problem_, strategy_)
!= 0) {
if (!problem_.Ta.isF4() || !problem_.Tb.isF4())
return status::unimplemented;
}
if (problem_.preferBDPAS(hw_)) { strategy_.kChain = 1; }
constexpr int perMNGroupSize = 1 << 24;
if (problem_.aqGroupM == m_
&& ((!problem_.forceGroupSumsA && !problem_.preferBDPAS(hw_))
|| m_ > 1)) {
problem_.aqGroupM = std::max(problem_.aqGroupM, perMNGroupSize);
problem_.aqGroupM
= utils::rnd_up(problem_.aqGroupM, strategy_.unroll[LoopM]);
}
if (problem_.bqGroupN == n_
&& ((!problem_.forceGroupSumsB && !problem_.preferBDPAS(hw_))
|| n_ > 1)) {
problem_.bqGroupN = std::max(problem_.bqGroupN, perMNGroupSize);
problem_.bqGroupN
= utils::rnd_up(problem_.bqGroupN, strategy_.unroll[LoopN]);
}
strategy_.kInterleaveChunk
= std::min(strategy_.kInterleaveChunk, (int)aux_params_.k0);
if (strategy_.kInterleave) aux_params_.wgK = strategy_.wg[LoopK];
if (aux_params_.wgK > strategy_.wg[LoopK])
aux_params_.wgK = strategy_.wg[LoopK];
update_driver_info();
return status::success;
}
void gen_desc_t::update_driver_info() {
#define ARCH_DISPATCH(arch) \
case ngen::HW::arch: \
driver_info_ = gemm_kernel_generator_t<ngen::HW::arch>::driverInfo( \
problem_, strategy_); \
break;
switch (hw_) {
REG_XELP_ISA(ARCH_DISPATCH(XeLP))
REG_XEHP_ISA(ARCH_DISPATCH(XeHP))
REG_XEHPG_ISA(ARCH_DISPATCH(XeHPG))
REG_XEHPC_ISA(ARCH_DISPATCH(XeHPC))
REG_XE2_ISA(ARCH_DISPATCH(Xe2))
REG_XE3_ISA(ARCH_DISPATCH(Xe3))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_35_10))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_35_11))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_UNKNOWN))
default:
assert(!"Unsupported architecture");
driver_info_ = entry_->driverInfo;
break;
}
#undef ARCH_DISPATCH
}
std::vector<const gemmstone::kcatalog::Entry *>
gen_nocopy_desc_t::select_kernel(compute::gpu_arch_t arch, int stepping,
int eu_count, bool has_systolic, bool is_integrated, compute_mode mode,
const gemmstone::GEMMProblem &problem, float alpha, float beta, dim_t m,
dim_t n, dim_t k, dim_t lda, dim_t ldb, dim_t ldc, dim_t batch) {
using namespace ngen;
using namespace kcatalog;
arch_ = arch;
hw_ = convert_dnnl_arch_to_ngen(arch);
stepping_ = stepping;
m_ = into<int>(m);
n_ = into<int>(n);
k_ = into<int>(k);
eu_count_ = eu_count;
disable_systolic_ = !has_systolic;
relaxed_acc_ = mode & mode_relaxed_acc;
std::vector<MatchParams> match_params;
MatchParams base(hw_, has_systolic, is_integrated, problem);
if (utils::one_of(hw_, ngen::HW::XE3P_35_10, ngen::HW::XE3P_35_11,
ngen::HW::XE3P_UNKNOWN)
&& !efficient_64b_)
base.selector.hw = kcatalog::HWTagXeHPC;
base.precisionCExt = '\0';
base.sizes.m = m;
base.sizes.n = n;
base.sizes.k = k;
base.sizes.batch = batch;
base.stepping = stepping;
base.ignoreCase = true;
bool can_2d_a = (lda * problem.Ta_ext <= 16777216);
bool can_2d_b = (ldb * problem.Tb_ext <= 16777216);
bool can_2d_c = (ldc * problem.Tc_ext <= 16777216);
if (arch == compute::gpu_arch_t::xe2 || arch == compute::gpu_arch_t::xe3) {
can_2d_a &= (problem.A.alignment % 16 == 0);
can_2d_b &= (problem.B.alignment % 16 == 0);
can_2d_c &= (problem.C.alignment % 16 == 0);
}
auto tags = const_cast<char *>(base.tags);
while (*tags)
tags++;
if (problem.A.needA64 || problem.B.needA64 || problem.C.needA64)
*tags++ = kcatalog::ReqBatchN;
if (can_2d_a) *tags++ = kcatalog::ReqBlock2DA;
if (can_2d_b) *tags++ = kcatalog::ReqBlock2DB;
if (can_2d_c) *tags++ = kcatalog::ReqBlock2DC;
auto mod_match = [&](MatchParams ¶ms, bool has_mode,
const char *(*match)(Type)) {
if (!has_mode) return;
if (match(problem.Ta)) {
params.selector.precisions[0] = match(problem.Ta);
}
if (match(problem.Tb)) {
params.selector.precisions[1] = match(problem.Tb);
}
};
mod_match(base,
(((problem.asPtrDims >= 2 || problem.bsPtrDims >= 2)
|| problem.boPtrDims > -1)
&& problem.aoPtrDims > -1 && problem.Ta_ext.isInt8()
&& problem.Tb_ext.isInt8() && problem.Tc.isFP()
&& !problem.forceGroupSumsA && !problem.forceGroupSumsB),
[](Type dt) -> const char * {
if (dt.isInt8()) return "[OH]";
return nullptr;
});
match_params.push_back(base);
bool fpmath_tf32 = mode & mode_tf32;
bool fpmath_bf16 = mode & mode_bf16x1;
bool fpmath_f16 = mode & mode_f16x1;
auto add_matches = [&](MatchParams start, const char *(*match)(Type)) {
if (match(problem.Ta_ext)) {
match_params.push_back(start);
match_params.back().selector.precisions[0] = match(problem.Ta_ext);
}
if (match(problem.Tb_ext)) {
match_params.push_back(start);
match_params.back().selector.precisions[1] = match(problem.Tb_ext);
}
if (match(problem.Ta_ext) && match(problem.Tb_ext)) {
match_params.push_back(start);
match_params.back().selector.precisions[0] = match(problem.Ta_ext);
match_params.back().selector.precisions[1] = match(problem.Tb_ext);
}
};
auto add_mode_matches = [&](bool has_mode, const char *(*match)(Type)) {
if (!has_mode) return;
add_matches(base, match);
};
add_mode_matches(fpmath_tf32, [](Type dt) -> const char * {
if (dt == Type::f32) { return "T"; }
return nullptr;
});
add_mode_matches(fpmath_bf16, [](Type dt) -> const char * {
if (dt == Type::f32) { return "[SB]"; }
if (dt.isInt8() || dt.isInt4()) return "[OB]";
if (dt.isF4()) return "F";
return nullptr;
});
add_mode_matches(fpmath_f16, [](Type dt) -> const char * {
if (dt == Type::f32) { return "[SH]"; }
if (dt.isInt8() || dt.isInt4()) return "[OH]";
if (dt.isF4()) return "F";
return nullptr;
});
add_mode_matches(!(fpmath_f16 || fpmath_bf16), [](Type dt) -> const char * {
if (dt.isInt4()) return "[FO]";
return nullptr;
});
auto add_variants = [&](const char *(*match)(Type)) {
size_t npatterns = match_params.size();
for (size_t i = 0; i < npatterns; i++) {
add_matches(match_params[i], match);
}
};
add_variants([](Type dt) -> const char * {
if (dt == Type::bf16) return "H";
return nullptr;
});
bool allow = gpu_utils::dev_getenv("ALLOW_IACC", true);
bool is_int = problem.Ta_ext.isInteger() && problem.Tb_ext.isInteger();
if (problem.asPtrDims < 1 && problem.bsPtrDims < 1 && is_int && allow) {
match_params.push_back(base);
match_params.back().selector.precisions[2] = "I";
}
eval_params_.sizes = base.sizes;
eval_params_.alpha = alpha;
eval_params_.beta = beta;
eval_params_.postOps = !problem.postOps.empty();
eval_params_.cConvert = (problem.Tc != problem.Tc_ext);
eval_params_.euCount = eu_count;
eval_params_.batch = (problem.batchDims > 0);
eval_params_.deterministic = (mode & mode_deterministic);
SelectionObserver observer = entryObserver;
tags_ = match_params[0].tags;
Ts_ = problem.Ts;
beta_ = problem.beta;
return select(catalog(), static_cast<int>(match_params.size()),
match_params.data(), eval_params_, aux_params_, &observer);
}
status_t gen_nocopy_desc_t::finalize() {
Type Ta_new, Ta_ext_new, Tb_new, Tb_ext_new, Tc_new;
parsePrecisions(entry_->selector.precisions[0], Ta_ext_new, Ta_new);
parsePrecisions(entry_->selector.precisions[1], Tb_ext_new, Tb_new);
Tc_new = charToType(entry_->selector.precisions[2][0]);
auto update_type = [](Type &T, Type T_new) {
if (T.isF8() && T_new.isF8()) return;
if (T.isFP() && T.bits() == 16 && T_new.isFP() && T_new.bits() == 16)
return;
if (T.isF4() && (T_new.isF4() || T_new.isInt4())) return;
T = T.isSigned() ? T_new.asSigned() : T_new.asUnsigned();
};
update_type(problem_.Ta, Ta_new);
update_type(problem_.Tb, Tb_new);
update_type(problem_.Tc, Tc_new);
auto use_tf32 = [](Type &T, const Type &newT) {
if (newT == Type::tf32) {
gpu_assert(T == Type::f32)
<< "Unexpected use of tf32 for non-fp32 type";
T = Type::tf32;
}
};
use_tf32(problem_.Ta_ext, Ta_ext_new);
use_tf32(problem_.Tb_ext, Tb_ext_new);
problem_.Ts = Ts_;
if (problem_.Ts == Type::invalid) problem_.Ts = problem_.Tc;
auto block_k = entry_->driverInfo.blocking[LoopK];
problem_.beta = beta_;
if (block_k > 0 && k_ > block_k && eval_params_.beta != 1.0f)
problem_.beta = Scalar();
evaluate(*entry_, eval_params_, aux_params_);
return gen_desc_t::finalize(tags_.c_str());
}
status_t gen_xe_systolic_kernel_desc_t::select_kernel(compute::gpu_arch_t arch,
int stepping, int eu_count, bool is_integrated, int batch_dims,
bool packed_c, bool trans_co, bool a_offset, bool b_offset,
bool c_offset, bool bias, float alpha, float beta, data_type_t a_type,
data_type_t b_type, data_type_t c_type, data_type_t ao_type,
data_type_t bo_type, data_type_t co_type, data_type_t acc_type, dim_t m,
dim_t n, dim_t k, dim_t batch, int unroll_m, int unroll_n, bool alt,
gpu_post_ops_t &&post_ops) {
using namespace ngen;
using namespace kcatalog;
arch_ = arch;
hw_ = convert_dnnl_arch_to_ngen(arch);
stepping_ = stepping;
m_ = into<int>(m);
n_ = into<int>(n);
k_ = into<int>(k);
eu_count_ = eu_count;
if (!utils::one_of(hw_, HW::XeHP, HW::XeHPG, HW::XeHPC, HW::Xe2, HW::Xe3))
return status::unimplemented;
bool xehpc = (hw_ >= HW::XeHPC);
auto osys = xehpc ? 16 : 8;
auto ksys = int(32 / types::data_type_size(a_type));
auto csys = int(4 / types::data_type_size(a_type));
problem_.Ta = problem_.Ta_ext = convert_dnnl_to_kernel_type(a_type);
problem_.Tb = problem_.Tb_ext = convert_dnnl_to_kernel_type(b_type);
problem_.Tc = convert_dnnl_to_kernel_type(acc_type);
problem_.Tc_ext = convert_dnnl_to_kernel_type(c_type);
problem_.Ts = Type::f32;
problem_.Tao = convert_dnnl_to_kernel_type(ao_type);
problem_.Tbo = convert_dnnl_to_kernel_type(bo_type);
problem_.Tco = convert_dnnl_to_kernel_type(co_type);
problem_.A.layout = MatrixLayout::PackedColumns;
problem_.B.layout = MatrixLayout::PackedRows;
problem_.C.layout = MatrixLayout::N;
problem_.A.crosspack = csys;
problem_.B.crosspack = ksys;
problem_.C.crosspack = 1;
problem_.A.packSize = unroll_m;
problem_.B.packSize = unroll_n;
problem_.C.packSize = 0;
if (osys < unroll_m) {
problem_.A.tileR = osys;
problem_.A.tileC = ksys;
}
problem_.A.setAlignment(32);
problem_.B.setAlignment(32);
problem_.C.setAlignment(int(types::data_type_size(c_type)));
if (packed_c) problem_.C = problem_.B;
if (batch_dims > 0) {
problem_.batch = BatchMode::Strided;
problem_.batchDims = batch_dims;
}
if (a_offset) {
problem_.aOffset = ABOffset::Load;
problem_.aoPtrDims = 0;
}
if (b_offset) {
problem_.bOffset = ABOffset::Load;
problem_.boPtrDims = 0;
}
if (alpha == 1.0f) problem_.alpha = (int)alpha;
if (beta == 0.0f || beta == 1.0f) problem_.beta = (int)beta;
auto status = transfer_post_ops(problem_, std::move(post_ops));
if (status != status::success) return status;
if (c_offset) problem_.cOffset = COffset::Post;
if (bias) {
if (problem_.cOffset != COffset::None) return status::unimplemented;
problem_.cOffset = COffset::Pre;
problem_.CO.layout = trans_co ? MatrixLayout::T : MatrixLayout::N;
}
if (problem_.cOffset != COffset::None) {
problem_.CO.crosspack = 1;
problem_.CO.alignment = problem_.C.alignment;
}
MatchParams match_params(hw_, true, is_integrated, problem_);
match_params.precisionCExt = '\0';
match_params.sizes.m = m;
match_params.sizes.n = n;
match_params.sizes.k = k;
match_params.sizes.batch = batch;
StrategyRequirement reqs[2] = {StrategyRequirement::UnrollM == unroll_m,
StrategyRequirement::UnrollN == unroll_n};
match_params.extraReqs = reqs;
match_params.nExtraReqs = 2;
auto tags = const_cast<char *>(match_params.tags);
while (*tags)
tags++;
*tags++ = kcatalog::ReqSystolic;
if (alt) *tags++ = kcatalog::ReqCustom1;
EvaluateParams eval_params;
eval_params.sizes = match_params.sizes;
eval_params.alpha = alpha;
eval_params.beta = beta;
eval_params.euCount = eu_count;
eval_params.postOps = !problem_.postOps.empty();
eval_params.cConvert = (acc_type != c_type);
eval_params.batch = (batch_dims > 0);
SelectionObserver observer = entryObserver;
auto entries = select(
catalog(), match_params, eval_params, aux_params_, &observer);
if (entries.size() < 1) return status::unimplemented;
entry_ = entries[0];
return finalize(match_params.tags);
}
void gen_xe_systolic_kernel_desc_t::choose_unrolls(compute::gpu_arch_t arch,
int eu_count, data_type_t a_type, data_type_t b_type,
data_type_t c_type, dim_t m, dim_t n, dim_t k, dim_t batch,
int &unroll_m, int &unroll_n, bool &alt) {
using namespace data_type;
alt = false;
switch (arch) {
case compute::gpu_arch_t::xe_hp:
case compute::gpu_arch_t::xe_hpg:
if (unroll_m == 0) unroll_m = 32;
if (unroll_n == 0) unroll_n = (m * n >= 6144 * eu_count) ? 48 : 32;
if (unroll_n == 48) alt = (m * n >= 13824 * eu_count);
break;
case compute::gpu_arch_t::xe_hpc:
case compute::gpu_arch_t::xe2:
case compute::gpu_arch_t::xe3:
case compute::gpu_arch_t::xe3p_35_10:
case compute::gpu_arch_t::xe3p_35_11:
case compute::gpu_arch_t::xe3p_35_unknown:
if (utils::one_of(a_type, f16, bf16)) {
if (unroll_m != 0)
unroll_n = (unroll_m > 16) ? 32 : 16;
else if (unroll_n != 0)
unroll_m = (unroll_n > 16) ? 64 : 16;
else if (m * n < 4096 * eu_count)
unroll_m = unroll_n = 16;
else {
unroll_m = 64;
unroll_n = 32;
}
} else {
unroll_m = 64;
unroll_n = 32;
}
break;
default: assert(!"Unsupported architecture.");
}
}
void gen_kernel_t::init_interface() {
using namespace ngen;
auto &problem = *desc()->problem();
auto &strategy = *desc()->strategy();
interface_ = NEOInterfaceHandler {desc()->hw_};
auto s_type_ngen = problem.Ts.ngen();
auto a_access = strategy.A.getGlobalAccessType();
auto b_access = strategy.B.getGlobalAccessType();
auto c_access = strategy.C.getGlobalAccessType();
auto ao_access = strategy.AO.getGlobalAccessType();
auto bo_access = strategy.BO.getGlobalAccessType();
auto co_access = strategy.CO.getGlobalAccessType();
auto as_access = strategy.A_scale.getGlobalAccessType();
auto bs_access = strategy.B_scale.getGlobalAccessType();
auto ag_access = strategy.Ag.getGlobalAccessType();
auto bg_access = strategy.Bg.getGlobalAccessType();
interface_.newArgument("A", ExternalArgumentType::GlobalPtr, a_access);
interface_.newArgument("B", ExternalArgumentType::GlobalPtr, b_access);
interface_.newArgument("C", ExternalArgumentType::GlobalPtr, c_access);
interface_.newArgument("offset_A", DataType::q);
interface_.newArgument("offset_B", DataType::q);
interface_.newArgument("offset_C", DataType::q);
interface_.newArgument("lda", DataType::d);
interface_.newArgument("ldb", DataType::d);
interface_.newArgument("ldc", DataType::d);
interface_.newArgument("m", DataType::d);
interface_.newArgument("n", DataType::d);
interface_.newArgument("k", DataType::d);
interface_.newArgument("alpha_real", s_type_ngen);
interface_.newArgument("beta_real", s_type_ngen);
if (problem.aoPtrDims >= 0)
interface_.newArgument(
"ao_ptr", ExternalArgumentType::GlobalPtr, ao_access);
if (problem.boPtrDims >= 0)
interface_.newArgument(
"bo_ptr", ExternalArgumentType::GlobalPtr, bo_access);
if (problem.aOffsetHostScalar()) interface_.newArgument("ao", DataType::w);
if (problem.bOffsetHostScalar()) interface_.newArgument("bo", DataType::w);
if (problem.aScale2D())
interface_.newArgument(
"a_scale_ptr", ExternalArgumentType::GlobalPtr, as_access);
if (problem.bScale2D())
interface_.newArgument(
"b_scale_ptr", ExternalArgumentType::GlobalPtr, bs_access);
if (problem.hasCMXScale())
interface_.newArgument(
"c_scale_ptr", ExternalArgumentType::GlobalPtr, c_access);
if (problem.needsAGroupSums())
interface_.newArgument(
"ag_ptr", ExternalArgumentType::GlobalPtr, ag_access);
if (problem.needsBGroupSums())
interface_.newArgument(
"bg_ptr", ExternalArgumentType::GlobalPtr, bg_access);
if (problem.aOffset2D() || problem.aScale2D()
|| problem.needsAGroupSums()) {
interface_.newArgument("ldaq", DataType::d);
}
if (problem.bOffset2D() || problem.bScale2D()
|| problem.needsBGroupSums()) {
interface_.newArgument("ldbq", DataType::d);
}
if (problem.hasCMXScale()) interface_.newArgument("ldcq", DataType::d);
if (problem.usesCOPtr()) {
interface_.newArgument(
"co_ptr", ExternalArgumentType::GlobalPtr, co_access);
interface_.newArgument("offset_CO", DataType::q);
if (problem.cOffset == COffset::Pre)
interface_.newArgument("ldco", DataType::d);
} else if (problem.cOffsetHostScalar()) {
interface_.newArgument("co", DataType::w);
}
if (problem.postOps.cStochasticRound) {
interface_.newArgument("sround_seed", ExternalArgumentType::GlobalPtr);
}
if (strategy.needsTempC(problem))
interface_.newArgument(
"temp_C", ExternalArgumentType::GlobalPtr, c_access);
interface_.newArgument("flags", DataType::ud);
if ((strategy.kParallel || strategy.kParallelLocal)
&& !strategy.kParallelVariable)
interface_.newArgument("k0", DataType::d);
for (size_t i = 0; i < problem.postOps.len(); i++) {
if (!problem.postOps[i].is_binary()) continue;
auto bname = "binary" + std::to_string(i);
interface_.newArgument(bname, ExternalArgumentType::GlobalPtr,
strategy.binary[i].getGlobalAccessType());
interface_.newArgument("offset_" + bname, DataType::q);
if (problem.postOps.binaryRow[i] && problem.postOps.binaryCol[i])
interface_.newArgument("ld" + bname, DataType::d);
}
if (problem.batch == BatchMode::Strided) {
for (int i = 0; i < problem.batchDims; i++) {
interface_.newArgument("stride_A" + std::to_string(i), DataType::d);
interface_.newArgument("stride_B" + std::to_string(i), DataType::d);
interface_.newArgument("stride_C" + std::to_string(i), DataType::d);
if (problem.hasAScalePtr()) {
interface_.newArgument(
"scale_stride_A" + std::to_string(i), DataType::d);
}
if (problem.hasBScalePtr()) {
interface_.newArgument(
"scale_stride_B" + std::to_string(i), DataType::d);
}
if (problem.hasCMXScale()) {
interface_.newArgument(
"scale_stride_C" + std::to_string(i), DataType::d);
}
if (problem.hasAOffsetPtr()) {
interface_.newArgument(
"offset_stride_A" + std::to_string(i), DataType::d);
}
if (problem.hasBOffsetPtr()) {
interface_.newArgument(
"offset_stride_B" + std::to_string(i), DataType::d);
}
if (problem.needsAGroupSums()) {
interface_.newArgument(
"group_sums_stride_A" + std::to_string(i), DataType::d);
}
if (problem.needsBGroupSums()) {
interface_.newArgument(
"group_sums_stride_B" + std::to_string(i), DataType::d);
}
}
for (size_t i = 0; i < problem.postOps.len(); i++) {
if (problem.postOps[i].is_binary()
&& problem.postOps.binaryBatch[i]) {
for (int b = 0; b < problem.batchDims; b++) {
interface_.newArgument("stride" + std::to_string(b)
+ "binary" + std::to_string(i),
DataType::d);
}
}
}
for (int i = 0; i < problem.batchDims - 1; i++) {
interface_.newArgument(
"batch_size" + std::to_string(i), DataType::ud);
if (enable_generator_dsl()) {
interface_.newArgument(
"batch_magic" + std::to_string(i), DataType::uq);
} else {
interface_.newArgument(
"recip_batch_size" + std::to_string(i), DataType::ud);
}
}
}
if (strategy.fuseBeta || strategy.fusePostOps)
interface_.newArgument("status", ExternalArgumentType::GlobalPtr,
GlobalAccessType::Stateless);
if (strategy.fuseBeta && strategy.kParallel)
interface_.newArgument("group_count_k", DataType::ud);
if (strategy.linearOrder()) {
interface_.newArgument("group_count_m", DataType::ud);
interface_.newArgument("group_count_n", DataType::ud);
}
if (strategy.cWalkOrder == WalkOrder::SimpleLinear)
interface_.newArgument("group_count_recip", DataType::ud);
else if (strategy.cWalkOrder == WalkOrder::Hilbertlike) {
interface_.newArgument("hilbert_vd", DataType::ud);
interface_.newArgument("hilbert_uvd_recip", DataType::ud);
interface_.newArgument("hilbert_bail", DataType::ud);
} else if (strategy.cWalkOrder == WalkOrder::Boustrophedon) {
interface_.newArgument("bslice", DataType::d);
interface_.newArgument("bthresh", DataType::d);
}
if (strategy.kParallelVariable) {
interface_.newArgument("k0", DataType::ud);
interface_.newArgument("kv_config", DataType::ud);
interface_.newArgument("k_recip", DataType::ud);
}
if (strategy.persistent)
interface_.newArgument("group_stride", DataType::ud);
if (strategy.variableSLM())
interface_.newArgument("local_mem", ExternalArgumentType::LocalPtr);
if (problem.aoPtrDims >= 1 || problem.aScale2D())
interface_.newArgument("offset_Aq", DataType::q);
if (problem.boPtrDims >= 1 || problem.bScale2D())
interface_.newArgument("offset_Bq", DataType::q);
if (desc()->hw_ >= HW::XeHPG) interface_.allowArgumentRearrangement(false);
interface_.externalName(kernel_name());
interface_.setEfficient64Bit(desc_.efficient_64b_);
}
dsl::kernel_t get_dsl_kernel(const GEMMProblem &problem,
const GEMMStrategy &strategy, const ngen::InterfaceHandler &iface,
const dsl::hw_t &hw, int m, int n, int k) {
auto gemm_desc
= gemmstone::generator_dsl_desc_t(problem, strategy, iface, hw);
if (gpu_utils::dev_getenv("generator_dsl_specialize", false)) {
auto &opt = gemm_desc.options;
if (n != -1) opt.assume(gemm_desc.kernel_iface().find_arg("m") == m);
if (m != -1) opt.assume(gemm_desc.kernel_iface().find_arg("n") == n);
if (k != -1) opt.assume(gemm_desc.kernel_iface().find_arg("k") == k);
}
return make_kernel(gemm_desc);
}
std::string dump_kernel(ngen::HW hw, const gemmstone::GEMMProblem &problem,
const gemmstone::GEMMStrategy &strategy) {
auto pstr = problem.toString();
auto astr = problem.scalarsToString();
auto sstr = unparseStrategy(hw, problem, strategy);
if (!astr.empty()) astr += ' ';
return pstr + ' ' + std::to_string(strategy.unroll[LoopM]) + ' '
+ std::to_string(strategy.unroll[LoopN]) + ' ' + astr + sstr;
}
status_t gen_kernel_t::get_kernel(
compute::kernel_t &kernel, const intel::engine_t *engine) {
init_interface();
maybe_print_verbose();
if (enable_generator_dsl()) {
auto k = get_dsl_kernel(*desc()->problem(), *desc()->strategy(),
interface_, make_ir_hw(engine), desc()->m_, desc()->n_,
desc()->k_);
if (k.body.is_empty()) return status::runtime_error;
return engine->create_kernel(kernel, k);
}
#define ARCH_DISPATCH(arch) \
case ngen::HW::arch: { \
gemm_kernel_generator_t<ngen::HW::arch> generator; \
generator.setStepping(desc()->stepping_); \
generator.gemm(*desc()->problem(), *desc()->strategy(), interface_); \
return generator.get_kernel(kernel, engine); \
break; \
}
try {
switch (desc()->hw_) {
REG_XELP_ISA(ARCH_DISPATCH(XeLP))
REG_XEHP_ISA(ARCH_DISPATCH(XeHP))
REG_XEHPG_ISA(ARCH_DISPATCH(XeHPG))
REG_XEHPC_ISA(ARCH_DISPATCH(XeHPC))
REG_XE2_ISA(ARCH_DISPATCH(Xe2))
REG_XE3_ISA(ARCH_DISPATCH(Xe3))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_35_10))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_35_11))
REG_XE3P_ISA(ARCH_DISPATCH(XE3P_UNKNOWN))
default: assert(!"Unsupported architecture"); break;
}
} catch (const ngen::out_of_registers_exception &err) {
VDEBUGINFO(1, primitive, gpu, "%s,%s,%s", "jit::gemm", err.what(),
dump_kernel(desc()->hw_, desc()->problem_, desc()->strategy_)
.c_str());
} catch (const std::runtime_error &err) {
VERROR(primitive, gpu, "%s,%s", "jit::gemm", err.what());
}
#undef ARCH_DISPATCH
return status::runtime_error;
}
void gen_kernel_t::maybe_print_verbose() {
int level = get_verbose(verbose_t::debuginfo);
if (level < 2) return;
if (level >= 10)
verbose_printf("info,gpu,gemm,catalog entry:%s\n",
desc()->entry().str().c_str());
verbose_printf("info,gpu,gemm,kernel:%s\n",
dump_kernel(desc()->hw_, desc()->problem_, desc()->strategy_)
.c_str());
}
} } } } } }