#include "gpu/intel/sdpa/micro.hpp"
#include "gpu/intel/sdpa/configs.hpp"
#include "common/c_types_map.hpp"
#include "common/sdpa_utils.hpp"
#include "common/type_helpers.hpp"
#include "common/utils.hpp"
#include "cpu/ref_io_helper.hpp"
#include "gemmstone/microkernel/shim.hpp"
#include "gemmstone/microkernel_selector.hpp"
#include "gemmstone/strategy_parser.hpp"
#include "gpu/intel/compute/ukernels.hpp"
#include "gpu/intel/compute/utils.hpp"
#include "gpu/intel/gemm/jit/gen_kernel.hpp"
#include "gpu/intel/primitive_conf.hpp"
#include "gpu/intel/utils.hpp"
#include <cstdio>
#include <iostream>
#include <limits>
#include <vector>
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace sdpa {
namespace {
using namespace gemmstone;
bool with_quantize_common(const quant_entry_t &entry) {
return !entry.has_default_values() && ((entry.get_mask() & 12) == 0);
}
}
status_t update_config_from_devenv_values(
fwd_config_t *config, bool quantized) {
std::string q_config_str
= gpu_utils::dev_getenv("QUANTIZED_SDPA_CONFIG", std::string(""));
std::string config_str
= gpu_utils::dev_getenv("SDPA_CONFIG", std::string(""));
if ((!config_str.empty() && !quantized)
|| (!q_config_str.empty() && quantized)) {
std::array<int, 8> config_values;
int i;
int num_values = 0;
if (!q_config_str.empty() && quantized)
config_str = std::move(q_config_str);
stringstream_t ss(config_str);
while (ss >> i) {
config_values[num_values++] = i;
if (ss.peek() == ',') ss.ignore();
}
VCHECK_SDPA_COND(num_values == 8,
"(QUANTIZED_)SDPA_CONFIG(%s) is invalid. Must be 8 integers "
"separate by a comma: "
"<unroll_m_kq>,<unroll_n_kq>,<unroll_m_vs>,<unroll_n_vs>,<wg_m_"
"kq>,<wg_n_kq>,<wg_m_vs>,<wg_n_vs>",
config_str.c_str());
if (num_values == 8) {
config->unroll_m_kq = config_values[0];
config->unroll_n_kq = config_values[1];
config->unroll_m_vs = config_values[2];
config->unroll_n_vs = config_values[3];
config->wg_m_kq = config_values[4];
config->wg_n_kq = config_values[5];
config->wg_m_vs = config_values[6];
config->wg_n_vs = config_values[7];
}
}
return status::success;
}
status_t update_config_from_devenv_values(bwd_config_t *config) {
std::string bwd_config_str
= gpu_utils::dev_getenv("BWD_SDPA_CONFIG", std::string(""));
if (!bwd_config_str.empty()) {
std::array<int, 12> config_values;
int i;
int num_values = 0;
stringstream_t ss(bwd_config_str);
while (ss >> i) {
config_values[num_values++] = i;
if (ss.peek() == ',') ss.ignore();
}
VCHECK_SDPA_COND(num_values == 12,
"BWD_SDPA_CONFIG(%s) is invalid. Must be 12 integers "
"separate by a comma: "
"<unroll_m_BcBr>,<unroll_n_BcBr>,"
"<unroll_m_DBc>,<unroll_n_DBc>,"
"<unroll_m_DBr>,<unroll_n_DBr>,"
"<wg_m_BcBr>,<wg_n_BcBr>,"
"<wg_m_DBc>,<wg_n_DBc>,"
"<wg_m_DBr>,<wg_n_DBr>",
bwd_config_str.c_str());
if (num_values == 12) {
config->unroll_m_BcBr = config_values[0];
config->unroll_n_BcBr = config_values[1];
config->unroll_m_DBc = config_values[2];
config->unroll_n_DBc = config_values[3];
config->unroll_m_DBr = config_values[4];
config->unroll_n_DBr = config_values[5];
config->wg_m_BcBr = config_values[6];
config->wg_n_BcBr = config_values[7];
config->wg_m_DBc = config_values[8];
config->wg_n_DBc = config_values[9];
config->wg_m_DBr = config_values[10];
config->wg_n_DBr = config_values[11];
}
}
return status::success;
}
status_t micro_fwd_t::pd_t::init_conf_microkernels(impl::engine_t *engine) {
using namespace jit;
using gemm::jit::convert_dnnl_to_kernel_type;
assert(engine->kind() == engine_kind::gpu);
auto *intel_engine = utils::downcast<intel::engine_t *>(engine);
auto *dev_info = intel_engine->device_info();
arch_ = dev_info->gpu_arch();
auto *d = desc();
VCHECK_SDPA_COND(compute::mayiuse_microkernels(intel_engine),
"Microkernels not supported by the OpenCL driver.");
fwd_config_t *config = nullptr;
const dim_t thin_q_threshold = 16;
auto queries = d->queries();
if (queries == 1) { queries = (d->q_desc.dims[1] / d->num_kv_heads()); }
bool thin_q = (queries <= thin_q_threshold);
bool quantized = with_key_scales() || with_key_zp() || with_value_scales()
|| with_value_zp();
bool is_integrated = intel_engine->device_info()->is_integrated();
bool is_f32 = (desc()->qry_md()->data_type == data_type::f32);
use_systolic_ukernel_
= intel_engine->mayiuse(compute::device_ext_t::
intel_subgroup_matrix_multiply_accumulate)
&& !is_f32;
bool use_fma_config = !use_systolic_ukernel_;
bool is_f16_accumulate_gemm = (kq_acc_dt() == data_type::f16)
|| (vs_acc_dt() == data_type::f16);
VCHECK_SDPA_COND(
IMPLICATION(is_f16_accumulate_gemm, !use_systolic_ukernel_),
"f16 accumulate only available with FMA matmul."); config = choose_config(arch_, d->head_size(), d->keys(), thin_q, quantized,
is_integrated, use_fma_config, is_f32, is_f16_accumulate_gemm);
VCHECK_SDPA_COND(config != nullptr,
"No suitable kernel configuration found for the given problem "
"size and attributes.");
CHECK(update_config_from_devenv_values(config, quantized));
VDEBUGINFO(4, primitive, sdpa,
"D=%d,K=%d,%s%s%s"
"kq_tile(%d, %d): unroll_m=%d unroll_n=%d wg_m=%d wg_n=%d,"
"vs_tile(%d, %d): unroll_m=%d unroll_n=%d wg_m=%d wg_n=%d",
static_cast<int>(d->head_size()), static_cast<int>(d->keys()),
thin_q ? "thin_q," : "", quantized ? "quant," : "",
is_integrated ? "integrated" : "",
config->unroll_m_kq * config->wg_m_kq,
config->unroll_n_kq * config->wg_n_kq, config->unroll_m_kq,
config->unroll_n_kq, config->wg_m_kq, config->wg_n_kq,
config->unroll_m_vs * config->wg_m_vs,
config->unroll_n_vs * config->wg_n_vs, config->unroll_m_vs,
config->unroll_n_vs, config->wg_m_vs, config->wg_n_vs);
VCHECK_SDPA_COND(config->unroll_n_kq * config->wg_n_kq
== config->unroll_n_vs * config->wg_n_vs
&& config->unroll_n_kq % config->unroll_n_vs == 0,
"[CONFIG] The config KQ work_group tile N(%d) axis must equal "
"VS work_group tile N(%d) axis and KQ subgroup tile N(%d) axis "
"must be divisible by VS subgroup tile N(%d) axis",
config->unroll_n_kq * config->wg_n_kq,
config->unroll_n_vs * config->wg_n_vs, config->unroll_n_kq,
config->unroll_n_vs);
VCHECK_SDPA_COND(config->unroll_m_vs * config->wg_m_vs >= d->head_size(),
"The vs matmul config work_group tile M(%d*%d=%d) axis must be "
"greater than or equal to head size(%ld)",
config->unroll_m_vs, config->wg_m_vs,
config->unroll_m_vs * config->wg_m_vs,
static_cast<long int>(d->head_size()));
micro_fwd_ukernel_params_t ukernel_params;
ukernel_params.unroll_m_kq = config->unroll_m_kq;
ukernel_params.unroll_n_kq = config->unroll_n_kq;
ukernel_params.unroll_m_vs = config->unroll_m_vs;
ukernel_params.unroll_n_vs = config->unroll_n_vs;
ukernel_params.wg_m_kq = config->wg_m_kq;
ukernel_params.wg_n_kq = config->wg_n_kq;
ukernel_params.wg_m_vs = config->wg_m_vs;
ukernel_params.wg_n_vs = config->wg_n_vs;
micro::HWInformation hw_info;
hw_info.euCount = dev_info->eu_count();
hw_info.gmdid = dev_info->ip_version();
hw_info.systolicAvailable = use_systolic_ukernel_;
VDISPATCH_SDPA(
hw_info.gmdid != 0, "gmdid is 0, microkernels not supported.");
ukernel_params.hwinfo = {hw_info};
sg_size_ = dev_info->min_subgroup_size();
auto convert_dnnl_to_kernel_layout = [](const memory_desc_t *md) {
return (gemm_desc_t::get_trans(*md) == dnnl_trans) ? MatrixLayout::T
: MatrixLayout::N;
};
bool kq_common_scales = with_quantize_common(d->kq_scales);
bool kq_common_zp = with_quantize_common(d->kq_zero_points);
GEMMProblem problem;
problem.Ta_ext = convert_dnnl_to_kernel_type(desc()->key_md()->data_type);
problem.Tb_ext = convert_dnnl_to_kernel_type(desc()->qry_md()->data_type);
if (desc()->qry_md()->data_type == data_type::f16) {
problem.Ta = problem.Tb = Type::f16;
} else if (desc()->qry_md()->data_type == data_type::bf16) {
problem.Ta = problem.Tb = Type::bf16;
} else if (desc()->qry_md()->data_type == data_type::f32) {
problem.Ta = problem.Tb = Type::f32;
} else {
VCHECK_SDPA_COND(utils::one_of(desc()->qry_md()->data_type,
data_type::f16, data_type::bf16),
"Q tensor's data type must be bf16 or f16");
}
problem.Tc = problem.Tc_ext = Type::f32;
problem.Ts = problem.Tc;
auto problem_kq = problem;
problem_kq.Tc = problem_kq.Ts
= (kq_acc_dt() == data_type::f16) ? Type::f16 : Type::f32;
problem_kq.A.layout = convert_dnnl_to_kernel_layout(desc()->key_md());
if (with_key_scales() && !kq_common_scales) {
auto scale_dt = key_scales_dt();
problem_kq.Ta_scale = convert_dnnl_to_kernel_type(scale_dt);
problem_kq.A_scale.setAlignment(
int8_t(d->keys() * types::data_type_size(scale_dt)));
problem_kq.A_scale.layout = MatrixLayout::N;
const int matrix_scale = 2;
problem_kq.asPtrDims = matrix_scale;
}
if (with_key_zp()) {
auto zp_dt = key_zp_dt();
problem_kq.Tao = convert_dnnl_to_kernel_type(zp_dt);
problem_kq.AO.setAlignment(
int8_t(d->keys() * types::data_type_size(zp_dt)));
problem_kq.AO.layout = MatrixLayout::N;
problem_kq.aoPtrDims = kq_common_zp ? 0 : 2;
problem_kq.aOffset = ABOffset::Calc;
}
if (with_key_scales() || with_key_zp()) {
problem_kq.aqGroupM = 1;
problem_kq.aqGroupK
= (kq_common_scales || kq_common_zp) ? 1 : key_group_size();
}
problem_kq.B.layout = MatrixLayout::Pr;
problem_kq.C.layout = MatrixLayout::T;
const memory_desc_wrapper key_mdw(desc()->key_md());
auto ldk = static_cast<int>(
gemm_desc_t::get_ld(*desc()->key_md()) * key_mdw.data_type_size());
problem_kq.A.setAlignment(micro::alignmentForLD(int(ldk)));
problem_kq.B.setAlignment(64); if (use_systolic_ukernel()) {
problem_kq.B.crosspack = 2;
problem_kq.B.tileR = into<uint16_t>(d_max());
problem_kq.B.tileC = into<uint16_t>(sg_size_);
}
ukernel_params.problem_kq = {problem_kq};
micro::GEMMOptions opts_kq;
opts_kq.localB = true;
opts_kq.slmPtr = true;
opts_kq.scaleA = with_key_scales() && !kq_common_scales;
opts_kq.offsetA = with_key_zp();
ukernel_params.opts_kq = {opts_kq};
SizeParams heuristic_sizes;
heuristic_sizes.m = nearest_conf_seq_interval(arch_, d->head_size(),
d->keys(), thin_q, quantized, is_integrated, use_fma_config, is_f32,
is_f16_accumulate_gemm);
heuristic_sizes.n = (queries <= thin_q_threshold)
? thin_q_threshold
: utils::rnd_up_pow2(queries);
heuristic_sizes.k
= d->head_size(); heuristic_sizes.batch = utils::rnd_up_pow2(d->batch() * d->num_q_heads());
ukernel_params.sizes_kq = {heuristic_sizes};
auto problem_vs = std::move(problem);
problem_vs.Tc = problem_vs.Ts
= (vs_acc_dt() == data_type::f16) ? Type::f16 : Type::f32;
bool vs_common_scales = with_quantize_common(d->vs_scales);
bool vs_common_zp = with_quantize_common(d->vs_zero_points);
problem_vs.Ta_ext
= convert_dnnl_to_kernel_type(desc()->val_md()->data_type);
problem_vs.A.layout = convert_dnnl_to_kernel_layout(desc()->val_md());
if (with_value_scales() && !vs_common_scales) {
auto scale_dt = value_scales_dt();
problem_vs.Ta_scale = convert_dnnl_to_kernel_type(scale_dt);
problem_vs.A_scale.setAlignment(uint8_t(d->head_size()
/ value_group_size() * types::data_type_size(scale_dt)));
problem_vs.A_scale.layout = MatrixLayout::N;
const int matrix_scale = 2;
problem_vs.asPtrDims = matrix_scale;
}
if (with_value_zp()) {
auto zp_dt = value_zp_dt();
problem_vs.Tao = convert_dnnl_to_kernel_type(zp_dt);
problem_vs.AO.setAlignment(uint8_t(d->head_size() / value_group_size()
* types::data_type_size(zp_dt)));
problem_vs.AO.layout = MatrixLayout::N;
problem_vs.aoPtrDims = vs_common_zp ? 0 : 2;
problem_vs.aOffset = ABOffset::Calc;
}
if (with_value_scales() || with_value_zp()) {
problem_vs.aqGroupM = (vs_common_scales || vs_common_zp)
? 1
: utils::rnd_up_pow2(value_group_size());
problem_vs.aqGroupK = 1;
}
problem_vs.B.layout = MatrixLayout::Pr;
problem_vs.C.layout = MatrixLayout::N;
const memory_desc_wrapper val_mdw(desc()->val_md());
auto ldv = static_cast<int>(
gemm_desc_t::get_ld(*desc()->val_md()) * val_mdw.data_type_size());
problem_vs.A.setAlignment(micro::alignmentForLD(int(ldv)));
problem_vs.B.setAlignment(64); if (use_systolic_ukernel()) { problem_vs.B.crosspack = 16; }
ukernel_params.problem_vs = {problem_vs};
heuristic_sizes.m = d->values();
const int wg_tile_n = config->wg_n_kq * config->unroll_n_kq;
const int wg_tile_m = config->wg_m_kq * config->unroll_m_kq;
heuristic_sizes.n = wg_tile_n;
heuristic_sizes.k = wg_tile_m;
ukernel_params.sizes_vs = {heuristic_sizes};
micro::GEMMOptions opts_vs;
opts_vs.localB = true;
opts_vs.slmPtr = true;
opts_vs.scaleA = with_value_scales() && !vs_common_scales;
opts_vs.offsetA = with_value_zp();
ukernel_params.opts_vs = {opts_vs};
conf.ukernel_config = ukernel_params;
return status::success;
}
status_t micro_bwd_t::pd_t::init_conf_microkernels(impl::engine_t *engine) {
using namespace jit;
using gemm::jit::convert_dnnl_to_kernel_type;
assert(engine->kind() == engine_kind::gpu);
auto *intel_engine = utils::downcast<intel::engine_t *>(engine);
auto *dev_info = intel_engine->device_info();
arch_ = dev_info->gpu_arch();
auto *d = desc();
VCHECK_SDPA_COND(compute::mayiuse_microkernels(intel_engine),
"Microkernels not supported by the OpenCL driver.");
bwd_config_t *config = nullptr;
const dim_t thin_q_threshold = 16;
auto queries = d->queries();
bool thin_q = (queries <= thin_q_threshold);
bool quantized = false;
bool is_integrated = intel_engine->device_info()->is_integrated();
bool is_f32 = (desc()->qry_md()->data_type == data_type::f32);
use_systolic_ukernel_
= intel_engine->mayiuse(compute::device_ext_t::
intel_subgroup_matrix_multiply_accumulate)
&& !is_f32;
bool use_fma_config = !use_systolic_ukernel_;
config = choose_bwd_config(arch_, d->head_size(), d->queries(), d->keys(),
thin_q, quantized, is_integrated, use_fma_config, is_f32);
VCHECK_SDPA_COND(config != nullptr,
"No suitable kernel configuration found for the given problem "
"size and attributes.");
CHECK(update_config_from_devenv_values(config));
VDEBUGINFO(4, primitive, sdpa,
"D=%d,K=%d,%s%s%s"
"BcBr_tile(%d, %d): unroll_m=%d unroll_n=%d wg_m=%d wg_n=%d,"
"DBc_tile(%d, %d): unroll_m=%d unroll_n=%d wg_m=%d wg_n=%d"
"DBr_tile(%d, %d): unroll_m=%d unroll_n=%d wg_m=%d wg_n=%d",
static_cast<int>(d->head_size()), static_cast<int>(d->keys()),
thin_q ? "thin_q," : "", quantized ? "quant," : "",
is_integrated ? "integrated" : "",
config->unroll_m_BcBr * config->wg_m_BcBr,
config->unroll_n_BcBr * config->wg_n_BcBr, config->unroll_m_BcBr,
config->unroll_n_BcBr, config->wg_m_BcBr, config->wg_n_BcBr,
config->unroll_m_DBc * config->wg_m_DBc,
config->unroll_n_DBc * config->wg_n_DBc, config->unroll_m_DBc,
config->unroll_n_DBc, config->wg_m_DBc, config->wg_n_DBc,
config->unroll_m_DBr * config->wg_m_DBr,
config->unroll_n_DBr * config->wg_n_DBr, config->unroll_m_DBr,
config->unroll_n_DBr, config->wg_m_DBr, config->wg_n_DBr);
VCHECK_SDPA_COND(
((config->unroll_m_BcBr * config->wg_m_BcBr
== config->unroll_n_DBc * config->wg_n_DBc)
&& ((config->wg_m_DBc * config->wg_n_DBc)
<= (config->wg_m_BcBr * config->wg_n_BcBr))),
"[CONFIG] The config BcBr work_group tile M(%d) axis must equal "
"DBc work_group tile N(%d) axis and number of total subgroups "
"should be less than BcBr subgroups (%d ?<= %d)",
config->unroll_m_BcBr * config->wg_m_BcBr,
config->unroll_n_DBc * config->wg_n_DBc,
config->wg_m_DBc * config->wg_n_DBc,
config->wg_m_BcBr * config->wg_n_BcBr);
VCHECK_SDPA_COND(config->unroll_m_DBc * config->wg_m_DBc >= d->head_size(),
"The DBc matmul config work_group tile N(%d*%d=%d) axis must be "
"greater than or equal to head size(%ld)",
config->unroll_m_DBc, config->wg_m_DBc,
config->unroll_m_DBc * config->wg_m_DBc,
static_cast<long int>(d->head_size()));
VCHECK_SDPA_COND(((config->unroll_n_BcBr * config->wg_n_BcBr
== config->unroll_n_DBr * config->wg_n_DBr)
&& (config->wg_m_DBr * config->wg_n_DBr
<= config->wg_m_BcBr * config->wg_n_BcBr)),
"[CONFIG] The config BcBr work_group tile N(%d) axis must equal "
"DBr work_group tile N(%d) axis and number of total subgroups "
"should be less than BcBr subgroups (%d ?<= %d)",
config->unroll_n_BcBr * config->wg_n_BcBr,
config->unroll_n_DBr * config->wg_n_DBr,
config->wg_m_DBr * config->wg_n_DBr,
config->wg_m_BcBr * config->wg_n_BcBr);
VCHECK_SDPA_COND(config->unroll_m_DBr * config->wg_m_DBr >= d->head_size(),
"The DBr matmul config work_group tile M(%d*%d=%d) axis must be "
"greater than or equal to head size(%ld)",
config->unroll_m_DBr, config->wg_m_DBr,
config->unroll_m_DBr * config->wg_m_DBr,
static_cast<long int>(d->head_size()));
micro_bwd_ukernel_params_t ukernel_params;
ukernel_params.unroll_m_BcBr = config->unroll_m_BcBr;
ukernel_params.unroll_n_BcBr = config->unroll_n_BcBr;
ukernel_params.unroll_m_DBc = config->unroll_m_DBc;
ukernel_params.unroll_n_DBc = config->unroll_n_DBc;
ukernel_params.unroll_m_DBr = config->unroll_m_DBr;
ukernel_params.unroll_n_DBr = config->unroll_n_DBr;
ukernel_params.wg_m_BcBr = config->wg_m_BcBr;
ukernel_params.wg_n_BcBr = config->wg_n_BcBr;
ukernel_params.wg_m_DBc = config->wg_m_DBc;
ukernel_params.wg_n_DBc = config->wg_n_DBc;
ukernel_params.wg_m_DBr = config->wg_m_DBr;
ukernel_params.wg_n_DBr = config->wg_n_DBr;
micro::HWInformation hw_info;
hw_info.euCount = dev_info->eu_count();
hw_info.gmdid = dev_info->ip_version();
hw_info.systolicAvailable = use_systolic_ukernel_;
VDISPATCH_SDPA(
hw_info.gmdid != 0, "gmdid is 0, microkernels not supported.");
ukernel_params.hwinfo = {hw_info};
sg_size_ = dev_info->min_subgroup_size();
auto convert_dnnl_to_kernel_layout = [](const memory_desc_t *md) {
return (gemm_desc_t::get_trans(*md) == dnnl_trans) ? MatrixLayout::T
: MatrixLayout::N;
};
auto transpose_layout = [](const gemmstone::MatrixLayout l) {
switch (l) {
case MatrixLayout::N: return MatrixLayout::T;
case MatrixLayout::T: return MatrixLayout::N;
case MatrixLayout::Pr: return MatrixLayout::Pc;
case MatrixLayout::Pc: return MatrixLayout::Pr;
default: return l;
}
};
GEMMProblem problem;
problem.Ta_ext = convert_dnnl_to_kernel_type(desc()->key_md()->data_type);
problem.Tb_ext = convert_dnnl_to_kernel_type(desc()->qry_md()->data_type);
if (desc()->qry_md()->data_type == data_type::f16) {
problem.Ta = problem.Tb = Type::f16;
} else if (desc()->qry_md()->data_type == data_type::bf16) {
problem.Ta = problem.Tb = Type::bf16;
} else if (desc()->qry_md()->data_type == data_type::f32) {
problem.Ta = problem.Tb = Type::f32;
} else {
VCHECK_SDPA_COND(
utils::one_of(desc()->qry_md()->data_type, data_type::f16,
data_type::bf16, data_type::f32),
"Q tensor's data type must be bf16, f16, or f32");
}
problem.Tc = problem.Tc_ext = Type::f32;
problem.Ts = problem.Tc;
const int wg_tile_m_BcBr = config->wg_m_BcBr * config->unroll_m_BcBr;
const int wg_tile_n_BcBr = config->wg_n_BcBr * config->unroll_n_BcBr;
auto problem_kq = problem;
problem_kq.A.layout = MatrixLayout::Pc;
problem_kq.B.layout = MatrixLayout::N;
problem_kq.C.layout = MatrixLayout::N;
const memory_desc_wrapper key_mdw(desc()->key_md());
const memory_desc_wrapper qry_mdw(desc()->qry_md());
auto ldk = static_cast<int>(
gemm_desc_t::get_ld(*desc()->key_md()) * key_mdw.data_type_size());
auto ldq = static_cast<int>(
gemm_desc_t::get_ld(*desc()->qry_md()) * qry_mdw.data_type_size());
problem_kq.A.setAlignment(64); if (use_systolic_ukernel()) {
problem_kq.A.crosspack = 2;
problem_kq.A.tileR = into<uint16_t>(sg_size_);
problem_kq.A.tileC = into<uint16_t>(d_max());
}
problem_kq.B.setAlignment(micro::alignmentForLD(int(ldq)));
ukernel_params.problem_kq = {problem_kq};
micro::GEMMOptions opts_kq;
opts_kq.localA = true;
opts_kq.slmPtr = true;
opts_kq.scaleA = false;
opts_kq.offsetA = false;
ukernel_params.opts_kq = {opts_kq};
SizeParams heuristic_sizes;
heuristic_sizes.m = wg_tile_m_BcBr;
heuristic_sizes.n = wg_tile_n_BcBr;
heuristic_sizes.k = d->head_size();
heuristic_sizes.batch = 1;
ukernel_params.sizes_kq = {heuristic_sizes};
auto problem_vs = problem;
problem_vs.Tc = problem_vs.Ts
= (vs_acc_dt() == data_type::f16) ? Type::f16 : Type::f32;
problem_vs.Ta_ext
= convert_dnnl_to_kernel_type(desc()->val_md()->data_type);
problem_vs.A.layout = convert_dnnl_to_kernel_layout(diff_dst_md());
problem_vs.B.layout = MatrixLayout::Pr;
problem_vs.C.layout = MatrixLayout::N;
const memory_desc_wrapper diff_dst_mdw(diff_dst_md());
auto lda = static_cast<int>(gemm_desc_t::get_ld(*diff_dst_md())
* diff_dst_mdw.data_type_size());
problem_vs.A.setAlignment(micro::alignmentForLD(int(lda)));
problem_vs.B.setAlignment(64); if (use_systolic_ukernel()) { problem_vs.B.crosspack = 16; }
ukernel_params.problem_vs = {problem_vs};
heuristic_sizes.m = d->head_size();
heuristic_sizes.n = wg_tile_m_BcBr;
heuristic_sizes.k = wg_tile_n_BcBr;
ukernel_params.sizes_vs = {heuristic_sizes};
micro::GEMMOptions opts_vs;
opts_vs.localA = false;
opts_vs.localB = true;
opts_vs.slmPtr = true;
ukernel_params.opts_vs = {opts_vs};
auto problem_vtdA = problem;
problem_vtdA.Ta_ext
= convert_dnnl_to_kernel_type(desc()->val_md()->data_type);
problem_vtdA.A.layout
= transpose_layout(convert_dnnl_to_kernel_layout(desc()->val_md()));
problem_vtdA.B.layout = convert_dnnl_to_kernel_layout(diff_dst_md());
problem_vtdA.C.layout = MatrixLayout::N;
const memory_desc_wrapper val_mdw(desc()->val_md());
auto ldv
= gemm_desc_t::get_ld(*desc()->val_md()) * val_mdw.data_type_size();
problem_vtdA.A.setAlignment(micro::alignmentForLD(int(ldv)));
problem_vtdA.B.setAlignment(micro::alignmentForLD(int(lda)));
ukernel_params.problem_vtdA = {problem_vtdA};
heuristic_sizes.m = wg_tile_m_BcBr;
heuristic_sizes.n = wg_tile_n_BcBr;
heuristic_sizes.k = d->head_size();
ukernel_params.sizes_vtdA = {heuristic_sizes};
micro::GEMMOptions opts_vtdA;
opts_vtdA.localA = false;
opts_vtdA.localB = false;
opts_vtdA.slmPtr = true;
ukernel_params.opts_vtdA = {opts_vtdA};
auto problem_qdSt = problem;
problem_qdSt.Ta_ext
= convert_dnnl_to_kernel_type(desc()->qry_md()->data_type);
problem_qdSt.A.layout = MatrixLayout::Pc;
problem_qdSt.B.layout
= transpose_layout(convert_dnnl_to_kernel_layout(desc()->qry_md()));
problem_qdSt.C.layout = MatrixLayout::N;
problem_qdSt.A.setAlignment(64);
problem_qdSt.B.setAlignment(micro::alignmentForLD(int(ldq)));
if (use_systolic_ukernel()) {
problem_qdSt.A.crosspack = 2;
problem_qdSt.A.tileR = into<uint16_t>(
sg_size_); problem_qdSt.A.tileC = into<uint16_t>(wg_tile_n_BcBr);
}
ukernel_params.problem_qdSt = {problem_qdSt};
heuristic_sizes.m = wg_tile_m_BcBr;
heuristic_sizes.n = d->values();
heuristic_sizes.k = wg_tile_n_BcBr;
ukernel_params.sizes_qdSt = {heuristic_sizes};
micro::GEMMOptions opts_qdSt;
opts_qdSt.localA = true;
opts_qdSt.localB = false;
opts_qdSt.slmPtr = true;
ukernel_params.opts_qdSt = {opts_qdSt};
auto problem_ktq = std::move(problem);
problem_ktq.Ta_ext
= convert_dnnl_to_kernel_type(desc()->key_md()->data_type);
problem_ktq.A.layout
= transpose_layout(convert_dnnl_to_kernel_layout(desc()->key_md()));
problem_ktq.B.layout = MatrixLayout::Pr;
problem_ktq.C.layout = MatrixLayout::N;
problem_ktq.A.setAlignment(micro::alignmentForLD(int(ldk)));
problem_ktq.B.setAlignment(64); if (use_systolic_ukernel()) { problem_ktq.B.crosspack = 16; }
ukernel_params.problem_ktq = {problem_ktq};
heuristic_sizes.m = d->head_size();
heuristic_sizes.n = wg_tile_n_BcBr;
heuristic_sizes.k = wg_tile_m_BcBr;
ukernel_params.sizes_ktq = {heuristic_sizes};
micro::GEMMOptions opts_ktq;
opts_ktq.localA = false;
opts_ktq.localB = true;
opts_ktq.slmPtr = true;
ukernel_params.opts_ktq = {opts_ktq};
conf.ukernel_config = ukernel_params;
return status::success;
}
status_t micro_fwd_t::init(impl::engine_t *engine) {
CHECK(create_kernel(
engine, kernel_, pd()->conf.get_kernel_names()[0], pd()->conf));
if (!kernel_) return status::runtime_error;
return status::success;
}
status_t micro_bwd_t::init(impl::engine_t *engine) {
std::vector<const char *> kernel_names = pd()->conf.get_kernel_names();
std::vector<compute::kernel_t> kernels;
CHECK(create_kernels(engine, kernels, kernel_names, pd()->conf));
preprocess_ = kernels[0];
kernel_ = kernels[1];
postprocess_ = kernels[2];
if (!preprocess_) return status::runtime_error;
if (!kernel_) return status::runtime_error;
if (!postprocess_) return status::runtime_error;
return status::success;
}
template <typename conf_t, typename pd_type>
static void init_conf_common(conf_t &conf, pd_type *pd) {
using pd_t = sdpa_pd_t;
auto *d = pd->desc();
data_type_t data_t = pd->dst_md()->data_type;
conf.data_t = data_t;
conf.ndims = pd_t::ndims;
const memory_desc_wrapper qry_mdw(pd->desc()->qry_md());
const memory_desc_wrapper key_mdw(pd->desc()->key_md());
const memory_desc_wrapper val_mdw(pd->desc()->val_md());
const memory_desc_wrapper dst_mdw(pd->dst_md());
const memory_desc_wrapper msk_mdw(pd->desc()->attn_mask_md());
conf.key_data_t = key_mdw.data_type();
conf.qry_data_t = qry_mdw.data_type();
conf.val_data_t = val_mdw.data_type();
conf.dst_data_t = dst_mdw.data_type();
conf.msk_data_t = data_type::undef;
if (pd->with_attn_mask()) { conf.msk_data_t = msk_mdw.data_type(); }
auto Q_num_heads_dim = qry_mdw.dims()[1];
conf.kv_group_size = static_cast<int>(Q_num_heads_dim / d->num_kv_heads());
auto ldq = gemm_desc_t::get_ld(*pd->desc()->qry_md())
* qry_mdw.data_type_size();
auto ldk = gemm_desc_t::get_ld(*pd->desc()->key_md())
* key_mdw.data_type_size();
auto ldv = gemm_desc_t::get_ld(*pd->desc()->val_md())
* val_mdw.data_type_size();
auto lda = gemm_desc_t::get_ld(*pd->dst_md()) * dst_mdw.data_type_size();
conf.q_align = micro::alignmentForLD(int(ldq));
conf.k_align = micro::alignmentForLD(int(ldk));
conf.v_align = micro::alignmentForLD(int(ldv));
conf.a_align = micro::alignmentForLD(int(lda));
conf.transpose_k
= gemm_desc_t::get_trans(*pd->desc()->key_md()) == dnnl_trans;
conf.scale_data_t = pd->desc()->scale_md()->data_type;
conf.attn_mask_undef = attn_mask_type::undef;
conf.attn_mask_buffer = attn_mask_type::buffer;
conf.attn_mask_top_left = attn_mask_type::top_left;
conf.attn_mask_bottom_right = attn_mask_type::bottom_right;
conf.invert_scale = d->invert_scale;
conf.with_attn_scale = pd->with_attn_scale();
conf.with_host_scale = pd->with_host_scale();
conf.with_attn_mask = (pd->with_attn_mask() && !pd->with_causal_mask());
conf.broadcast_mask_q = (msk_mdw.dims()[pd_t::mask_q_index] == 1);
conf.with_causal_mask = pd->with_causal_mask();
conf.subgroup_size = pd->sg_size();
conf.d_max = pd->d_max();
bool d_full = (d->head_size() == pd->d_max());
conf.d_full = d_full;
conf.arch_gte_hpc = (pd->arch() >= compute::gpu_arch_t::xe_hpc);
conf.dropout = !pd->attr()->dropout_.has_default_values();
conf.dropout_output_mask = pd->attr()->dropout_.has_output_mask();
conf.dropout_offset = pd->attr()->dropout_.use_offset_;
conf.dropout_host_scalars = pd->attr()->dropout_.use_host_scalars_;
conf.use_systolic_ukernel = pd->use_systolic_ukernel();
}
status_t micro_fwd_t::pd_t::init_conf(impl::engine_t *engine) {
using namespace micro;
init_conf_common(conf, this);
conf.require_stateless_addressing = has_large_buffers();
const memory_desc_wrapper qry_mdw(desc()->qry_md());
const memory_desc_wrapper key_mdw(desc()->key_md());
const memory_desc_wrapper val_mdw(desc()->val_md());
const memory_desc_wrapper dst_mdw(dst_md());
conf.key_scales_data_t = key_scales_dt();
conf.value_scales_data_t = value_scales_dt();
conf.key_zp_data_t = key_zp_dt();
conf.value_zp_data_t = value_zp_dt();
auto ldq
= gemm_desc_t::get_ld(*desc()->qry_md()) * qry_mdw.data_type_size();
auto lda = gemm_desc_t::get_ld(*dst_md()) * dst_mdw.data_type_size();
int kq_scale_mask = (static_cast<int>(with_key_scales()) << 1)
| static_cast<int>(with_quantize_common(desc()->kq_scales));
conf.kq_scale_mask = kq_scale_mask;
int vs_scale_mask = (static_cast<int>(with_value_scales()) << 1)
| static_cast<int>(with_quantize_common(desc()->vs_scales));
conf.vs_scale_mask = vs_scale_mask;
int kq_zp_mask = (static_cast<int>(with_key_zp()) << 1)
| static_cast<int>(with_quantize_common(desc()->kq_zero_points));
conf.kq_zp_mask = kq_zp_mask;
int vs_zp_mask = (static_cast<int>(with_value_zp()) << 1)
| static_cast<int>(with_quantize_common(desc()->vs_zero_points));
conf.vs_zp_mask = vs_zp_mask;
using namespace data_type;
auto elems_per_byte = [](data_type_t dt) {
switch (dt) {
case u4:
case s4: return 2;
default: return 1;
}
};
conf.key_elements_per_byte = elems_per_byte(key_mdw.data_type());
conf.key_zp_elements_per_byte = elems_per_byte(key_zp_dt());
conf.val_elements_per_byte = elems_per_byte(val_mdw.data_type());
conf.val_zp_elements_per_byte = elems_per_byte(value_zp_dt());
conf.key_group_size = 1;
conf.val_group_size = 1;
if (with_key_scales() || with_key_zp())
conf.key_group_size = key_group_size();
if (with_value_scales() || with_value_zp())
conf.val_group_size = value_group_size();
const fwd_config_t config = {conf.ukernel_config.unroll_m_kq,
conf.ukernel_config.unroll_n_kq, conf.ukernel_config.unroll_m_vs,
conf.ukernel_config.unroll_n_vs, conf.ukernel_config.wg_m_kq,
conf.ukernel_config.wg_n_kq, conf.ukernel_config.wg_m_vs,
conf.ukernel_config.wg_n_vs};
const int kq_wg_tile_m = config.wg_m_kq * config.unroll_m_kq;
const int kq_wg_tile_n = config.wg_n_kq * config.unroll_n_kq;
const int vs_wg_tile_m = config.wg_m_vs * config.unroll_m_vs;
int tile_k = kq_wg_tile_m;
int tile_v = vs_wg_tile_m;
bool d_full = conf.d_full;
bool v_full = (desc()->head_size() == tile_v);
auto Q = desc()->queries();
const dim_t Q_per_kv_group = (Q == 1 ? Q * conf.kv_group_size : Q);
bool q_full = ((Q_per_kv_group % kq_wg_tile_n) != 0);
conf.remainder_q = d_full && q_full;
conf.block_q = conf.block_a = conf.block_2d_a = false;
if (d_full) {
conf.block_q = (ldq % 4 == 0);
conf.block_a = (lda % 4 == 0 && v_full);
} else if (arch() >= compute::gpu_arch_t::xe_hpc
&& config.unroll_m_vs < 64) {
auto vbytes = desc()->values() * val_mdw.data_type_size();
if (lda % 16 == 0 && vbytes % 4 == 0) conf.block_2d_a = true;
}
if (arch() >= compute::gpu_arch_t::xe_hpc) {
conf.prefetch_mask = true;
conf.prefetch_k0 = true;
conf.prefetch_k = true;
conf.prefetch_v = true;
conf.prefetch_d_max = nstl::min(d_max(), 64);
bool no_rem = d_full && v_full && (desc()->keys() % tile_k == 0);
conf.prefetch_remainder = !no_rem;
} else {
conf.prefetch_mask = conf.prefetch_k0 = conf.prefetch_k
= conf.prefetch_v = conf.prefetch_remainder = false;
conf.prefetch_d_max = 0;
}
conf.q_arrive_await_barrier = (Q > 1);
conf.softmax_inf_as_zero
= (desc()->softmax_alg == alg_kind::softmax_accurate_inf_as_zero);
conf.kq_f16_accumulate = (kq_acc_dt() == data_type::f16);
conf.vs_f16_accumulate = (vs_acc_dt() == data_type::f16);
bool is_training = desc()->prop_kind == prop_kind::forward_training;
conf.is_training = is_training;
if (is_training) { init_default_ws(); }
return status::success;
}
status_t micro_bwd_t::pd_t::init_conf(impl::engine_t *engine) {
init_conf_common(conf, this);
conf.require_stateless_addressing = has_large_buffers();
conf.with_dS = with_dS();
const memory_desc_wrapper key_mdw(desc()->key_md());
const memory_desc_wrapper val_mdw(desc()->val_md());
auto ldk
= gemm_desc_t::get_ld(*desc()->key_md()) * key_mdw.data_type_size();
auto ldv
= gemm_desc_t::get_ld(*desc()->val_md()) * val_mdw.data_type_size();
const bwd_config_t config = {conf.ukernel_config.unroll_m_BcBr,
conf.ukernel_config.unroll_n_BcBr, conf.ukernel_config.unroll_m_DBc,
conf.ukernel_config.unroll_n_DBc, conf.ukernel_config.unroll_m_DBr,
conf.ukernel_config.unroll_n_DBr, conf.ukernel_config.wg_m_BcBr,
conf.ukernel_config.wg_n_BcBr, conf.ukernel_config.wg_m_DBc,
conf.ukernel_config.wg_n_DBc, conf.ukernel_config.wg_m_DBr,
conf.ukernel_config.wg_n_DBr};
const int kq_wg_tile_m = config.wg_m_BcBr * config.unroll_m_BcBr;
const int tile_k = kq_wg_tile_m;
const int tile_dv = config.wg_n_DBc * config.unroll_n_DBc;
bool d_full = conf.d_full;
bool dv_full = (desc()->head_size() == tile_dv);
conf.block_k = conf.block_dK = conf.block_dV = false;
if (d_full) {
bool can_block_load_k
= (ldk % 4 == 0) && (desc()->keys() % tile_k == 0);
conf.block_k = can_block_load_k;
if (conf.transpose_k) {
const memory_desc_wrapper dk_mdw(desc()->diff_key_md());
auto lddk_bytes = std::max(dk_mdw.strides()[2], dk_mdw.strides()[3])
* dk_mdw.data_type_size();
conf.block_dK = (lddk_bytes % 4 == 0);
} else {
conf.block_dK = can_block_load_k;
}
conf.block_dV = (ldv % 4 == 0) && (dv_full);
}
return status::success;
}
status_t micro_bwd_t::pd_t::init_scratchpad(impl::engine_t *engine) {
auto scratchpad = scratchpad_registry().registrar();
auto gpu_align
= utils::downcast<gpu::engine_t *>(engine)->get_buffer_alignment();
size_t wspace_size = memory_desc_wrapper(desc()->diff_qry_md()).nelems();
if (conf.data_t != data_type::f32) {
scratchpad.book(memory_tracking::names::key_sdpa_dQ_reduction,
wspace_size, sizeof(float), gpu_align);
}
const bool needs_intermediate_dKV
= (conf.kv_group_size > 1 && conf.data_t != data_type::f32);
if (needs_intermediate_dKV) {
size_t dK_size = memory_desc_wrapper(desc()->diff_key_md()).nelems();
scratchpad.book(memory_tracking::names::key_sdpa_dK_reduction, dK_size,
sizeof(float), gpu_align);
size_t dV_size = memory_desc_wrapper(desc()->diff_val_md()).nelems();
scratchpad.book(memory_tracking::names::key_sdpa_dV_reduction, dV_size,
sizeof(float), gpu_align);
}
size_t Di_size
= desc()->batch() * desc()->num_q_heads() * desc()->queries();
scratchpad.book(memory_tracking::names::key_sdpa_Di, Di_size, sizeof(float),
gpu_align);
scratchpad.book(memory_tracking::names::key_sdpa_bwd_strides, 32,
sizeof(int64_t), gpu_align);
return status::success;
}
status_t micro_fwd_params_t::get_kernel_ctx(
compute::kernel_ctx_t &kernel_ctx) const {
using namespace micro;
kernel_ctx.require_stateless_addressing(require_stateless_addressing);
kernel_ctx.define_int("NDIMS", ndims);
kernel_ctx.set_data_type(data_t);
def_data_type(kernel_ctx, key_data_t, "KEY");
def_data_type(kernel_ctx, qry_data_t, "QRY");
def_data_type(kernel_ctx, val_data_t, "VAL");
def_data_type(kernel_ctx, dst_data_t, "DST");
if (with_attn_mask) { def_data_type(kernel_ctx, msk_data_t, "MSK"); }
def_data_type(kernel_ctx, key_scales_data_t, "KEY_ATTR_SCALES");
def_data_type(kernel_ctx, value_scales_data_t, "VAL_ATTR_SCALES");
def_data_type(kernel_ctx, key_zp_data_t, "KEY_ATTR_ZP");
def_data_type(kernel_ctx, value_zp_data_t, "VAL_ATTR_ZP");
kernel_ctx.define_int("KV_GROUP_SIZE", kv_group_size);
kernel_ctx.define_int("Q_ALIGN", q_align);
kernel_ctx.define_int("K_ALIGN", k_align);
kernel_ctx.define_int("V_ALIGN", v_align);
kernel_ctx.define_int("A_ALIGN", a_align);
kernel_ctx.define_int("TRANSPOSE_K", transpose_k);
kernel_ctx.define_int("KEY_SCALES", kq_scale_mask);
kernel_ctx.define_int("VAL_SCALES", vs_scale_mask);
kernel_ctx.define_int("KEY_ZERO_POINTS", kq_zp_mask);
kernel_ctx.define_int("VAL_ZERO_POINTS", vs_zp_mask);
kernel_ctx.define_int("KEY_ELEMENTS_PER_BYTE", key_elements_per_byte);
kernel_ctx.define_int("KEY_ZP_ELEMENTS_PER_BYTE", key_zp_elements_per_byte);
kernel_ctx.define_int("VAL_ELEMENTS_PER_BYTE", val_elements_per_byte);
kernel_ctx.define_int("VAL_ZP_ELEMENTS_PER_BYTE", val_zp_elements_per_byte);
kernel_ctx.define_int("KEY_GROUP_SIZE", key_group_size);
kernel_ctx.define_int("VAL_GROUP_SIZE", val_group_size);
def_data_type(kernel_ctx, scale_data_t, "SCALE", !with_host_scale);
kernel_ctx.define_int("INVERT_SCALE", invert_scale);
kernel_ctx.define_int("WITH_ATTN_SCALE", with_attn_scale);
kernel_ctx.define_int("WITH_HOST_SCALE", with_host_scale);
kernel_ctx.define_int("ATTN_MASK_UNDEF", attn_mask_undef);
kernel_ctx.define_int("ATTN_MASK_BUFFER", attn_mask_buffer);
kernel_ctx.define_int("ATTN_MASK_TOP_LEFT", attn_mask_top_left);
kernel_ctx.define_int("ATTN_MASK_BOTTOM_RIGHT", attn_mask_bottom_right);
kernel_ctx.define_int("WITH_ATTN_MASK", with_attn_mask);
kernel_ctx.define_int("BROADCAST_MASK_Q", broadcast_mask_q);
kernel_ctx.define_int("WITH_CAUSAL_MASK", with_causal_mask);
kernel_ctx.define_int("SUBGROUP_SIZE", subgroup_size);
kernel_ctx.define_int("D_MAX", d_max);
kernel_ctx.define_int("BLOCK_Q", block_q);
kernel_ctx.define_int("BLOCK_A", block_a);
kernel_ctx.define_int("BLOCK_2D_A", block_2d_a);
kernel_ctx.define_int("PREFETCH_MASK", prefetch_mask);
kernel_ctx.define_int("PREFETCH_K0", prefetch_k0);
kernel_ctx.define_int("PREFETCH_K", prefetch_k);
kernel_ctx.define_int("PREFETCH_V", prefetch_v);
kernel_ctx.define_int("PREFETCH_REMAINDER", prefetch_remainder);
kernel_ctx.define_int("PREFETCH_D_MAX", prefetch_d_max);
kernel_ctx.define_int("REMAINDER_Q", remainder_q);
kernel_ctx.define_int("Q_ARRIVE_AWAIT_BARRIER", q_arrive_await_barrier);
kernel_ctx.define_int("SOFTMAX_INF_AS_ZERO", softmax_inf_as_zero);
kernel_ctx.define_int("USE_SYSTOLIC_UKERNEL", use_systolic_ukernel);
kernel_ctx.define_int("KQ_F16_ACC", kq_f16_accumulate);
kernel_ctx.define_int("VS_F16_ACC", vs_f16_accumulate);
kernel_ctx.define_int("IS_TRAINING", is_training);
kernel_ctx.define_int("WITH_DROPOUT", dropout);
kernel_ctx.define_int("DROPOUT_HOST_SCALARS", dropout_host_scalars);
kernel_ctx.define_int("DROPOUT_OUTPUT_MASK", dropout_output_mask);
micro::HWInformation hw_info;
gemmstone::GEMMProblem problem_kq, problem_vs;
micro::GEMMOptions opts_kq, opts_vs;
gemmstone::SizeParams sizes_kq, sizes_vs;
deserialize_config_to_gemmstone(hw_info, problem_kq, problem_vs, opts_kq,
opts_vs, sizes_kq, sizes_vs, ukernel_config);
micro::Package gemm_kq, gemm_vs;
const fwd_config_t config
= {ukernel_config.unroll_m_kq, ukernel_config.unroll_n_kq,
ukernel_config.unroll_m_vs, ukernel_config.unroll_n_vs,
ukernel_config.wg_m_kq, ukernel_config.wg_n_kq,
ukernel_config.wg_m_vs, ukernel_config.wg_n_vs};
std::vector<StrategyRequirement> reqs_kq;
reqs_kq.push_back(StrategyRequirement::UnrollM == config.unroll_m_kq);
reqs_kq.push_back(StrategyRequirement::UnrollN == config.unroll_n_kq);
reqs_kq.push_back(StrategyRequirement::WGM == config.wg_m_kq);
reqs_kq.push_back(StrategyRequirement::WGN == config.wg_n_kq);
std::vector<StrategyRequirement> reqs_vs;
reqs_vs.push_back(StrategyRequirement::UnrollM == config.unroll_m_vs);
reqs_vs.push_back(StrategyRequirement::UnrollN == config.unroll_n_vs);
reqs_vs.push_back(StrategyRequirement::WGM == config.wg_m_vs);
reqs_vs.push_back(StrategyRequirement::WGN == config.wg_n_vs);
auto kq_strat_override = [&](gemmstone::GEMMStrategy &strat) {
std::string newStrat;
newStrat = gpu_utils::dev_getenv("SDPA_KQ_USTRATEGY", newStrat);
if (!newStrat.empty()) {
auto product = ngen::npack::decodeHWIPVersion(hw_info.gmdid);
auto hw = getCore(product.family);
auto stepping = hw_info.gmdid & 0xFF;
strat = gemmstone::GEMMStrategy(hw, stepping);
std::stringstream ss(newStrat);
ss >> strat.unroll[0];
ss >> strat.unroll[1];
std::string strategyString;
std::getline(ss >> std::ws, strategyString);
parseStrategy(strategyString, hw, problem_kq, strat);
adjustStrategy(hw, problem_kq, strat);
}
};
try {
gemm_kq = micro::selectGEMM(opts_kq, hw_info, sizes_kq, problem_kq,
reqs_kq, kq_strat_override);
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_kq microkernel generation failure with message: %s",
ex.what());
}
auto vs_strat_override = [&](gemmstone::GEMMStrategy &strat) {
std::string newStrat;
newStrat = gpu_utils::dev_getenv("SDPA_VS_USTRATEGY", newStrat);
if (!newStrat.empty()) {
auto product = ngen::npack::decodeHWIPVersion(hw_info.gmdid);
auto hw = getCore(product.family);
auto stepping = hw_info.gmdid & 0xFF;
strat = gemmstone::GEMMStrategy(hw, stepping);
std::stringstream ss(newStrat);
ss >> strat.unroll[0];
ss >> strat.unroll[1];
std::string strategyString;
std::getline(ss >> std::ws, strategyString);
parseStrategy(strategyString, hw, problem_vs, strat);
adjustStrategy(hw, problem_vs, strat);
}
};
try {
if (use_systolic_ukernel) {
auto adjust_vs = [&](GEMMStrategy &strategy) {
strategy.dpasw |= strategy.fused;
vs_strat_override(strategy);
};
gemm_vs = micro::selectGEMM(
opts_vs, hw_info, sizes_vs, problem_vs, reqs_vs, adjust_vs);
} else {
gemm_vs = micro::selectGEMM(opts_vs, hw_info, sizes_vs, problem_vs,
reqs_vs, vs_strat_override);
}
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_vs microkernel generation failure with message: %s",
ex.what());
}
VDEBUGINFO(4, primitive, sdpa, "kq_gemm: %s, vs_gemm: %s,",
problem_kq.toString().c_str(), problem_vs.toString().c_str());
micro::ShimOptions shimOptions;
shimOptions.subgroupSize = subgroup_size;
shimOptions.useTileOps = true;
shimOptions.decorator = "kq";
kernel_ctx.add_custom_header("gemm_kq.h",
micro::generateShim(gemm_kq, HostLanguage::OpenCL_C, shimOptions));
shimOptions.microkernelID++;
shimOptions.decorator = "vs";
kernel_ctx.add_custom_header("gemm_vs.h",
micro::generateShim(gemm_vs, HostLanguage::OpenCL_C, shimOptions));
if (gemm_kq.grfMin > 128 || gemm_vs.grfMin > 128)
kernel_ctx.add_option("-cl-intel-256-GRF-per-thread");
return status::success;
}
status_t micro_bwd_params_t::get_kernel_ctx(
compute::kernel_ctx_t &kernel_ctx) const {
using namespace micro;
kernel_ctx.require_stateless_addressing(require_stateless_addressing);
kernel_ctx.define_int("NDIMS", ndims);
kernel_ctx.set_data_type(data_t);
def_data_type(kernel_ctx, key_data_t, "KEY");
def_data_type(kernel_ctx, qry_data_t, "QRY");
def_data_type(kernel_ctx, val_data_t, "VAL");
def_data_type(kernel_ctx, dst_data_t, "DST");
if (with_attn_mask) { def_data_type(kernel_ctx, msk_data_t, "MSK"); }
kernel_ctx.define_int("KV_GROUP_SIZE", kv_group_size);
kernel_ctx.define_int("Q_ALIGN", q_align);
kernel_ctx.define_int("K_ALIGN", k_align);
kernel_ctx.define_int("V_ALIGN", v_align);
kernel_ctx.define_int("A_ALIGN", a_align);
kernel_ctx.define_int("TRANSPOSE_K", transpose_k);
def_data_type(kernel_ctx, scale_data_t, "SCALE", !with_host_scale);
kernel_ctx.define_int("INVERT_SCALE", invert_scale);
kernel_ctx.define_int("WITH_ATTN_SCALE", with_attn_scale);
kernel_ctx.define_int("WITH_HOST_SCALE", with_host_scale);
kernel_ctx.define_int("ATTN_MASK_UNDEF", attn_mask_undef);
kernel_ctx.define_int("ATTN_MASK_BUFFER", attn_mask_buffer);
kernel_ctx.define_int("ATTN_MASK_TOP_LEFT", attn_mask_top_left);
kernel_ctx.define_int("ATTN_MASK_BOTTOM_RIGHT", attn_mask_bottom_right);
kernel_ctx.define_int("WITH_ATTN_MASK", with_attn_mask);
kernel_ctx.define_int("BROADCAST_MASK_Q", broadcast_mask_q);
kernel_ctx.define_int("WITH_CAUSAL_MASK", with_causal_mask);
kernel_ctx.define_int("WITH_DS", with_dS);
kernel_ctx.define_int("SUBGROUP_SIZE", subgroup_size);
kernel_ctx.define_int("D_MAX", d_max);
kernel_ctx.define_int("BLOCK_K", block_k);
kernel_ctx.define_int("BLOCK_DK", block_dK);
kernel_ctx.define_int("BLOCK_DV", block_dV);
kernel_ctx.define_int("USE_SYSTOLIC_UKERNEL", use_systolic_ukernel);
kernel_ctx.define_int("WITH_DROPOUT", dropout);
kernel_ctx.define_int("DROPOUT_HOST_SCALARS", dropout_host_scalars);
micro::HWInformation hw_info;
gemmstone::GEMMProblem problem_kq, problem_vs;
micro::GEMMOptions opts_kq, opts_vs;
gemmstone::SizeParams sizes_kq, sizes_vs;
gemmstone::GEMMProblem problem_vtdA, problem_ktq, problem_qdSt;
micro::GEMMOptions opts_vtdA, opts_ktq, opts_qdSt;
gemmstone::SizeParams sizes_vtdA, sizes_ktq, sizes_qdSt;
deserialize_config_to_gemmstone(hw_info, problem_kq, problem_vs,
problem_vtdA, problem_ktq, problem_qdSt, opts_kq, opts_vs,
opts_vtdA, opts_ktq, opts_qdSt, sizes_kq, sizes_vs, sizes_vtdA,
sizes_ktq, sizes_qdSt, ukernel_config);
micro::Package gemm_kq, gemm_vs, gemm_vtdA, gemm_ktq, gemm_qdSt;
const bwd_config_t config
= {ukernel_config.unroll_m_BcBr, ukernel_config.unroll_n_BcBr,
ukernel_config.unroll_m_DBc, ukernel_config.unroll_n_DBc,
ukernel_config.unroll_m_DBr, ukernel_config.unroll_n_DBr,
ukernel_config.wg_m_BcBr, ukernel_config.wg_n_BcBr,
ukernel_config.wg_m_DBc, ukernel_config.wg_n_DBc,
ukernel_config.wg_m_DBr, ukernel_config.wg_n_DBr};
std::vector<StrategyRequirement> reqs_kq;
reqs_kq.push_back(StrategyRequirement::UnrollM == config.unroll_m_BcBr);
reqs_kq.push_back(StrategyRequirement::UnrollN == config.unroll_n_BcBr);
reqs_kq.push_back(StrategyRequirement::WGM == config.wg_m_BcBr);
reqs_kq.push_back(StrategyRequirement::WGN == config.wg_n_BcBr);
std::vector<StrategyRequirement> reqs_vs;
reqs_vs.push_back(StrategyRequirement::UnrollM == config.unroll_m_DBc);
reqs_vs.push_back(StrategyRequirement::UnrollN == config.unroll_n_DBc);
reqs_vs.push_back(StrategyRequirement::WGM == config.wg_m_DBc);
reqs_vs.push_back(StrategyRequirement::WGN == config.wg_n_DBc);
std::vector<StrategyRequirement> reqs_vtdA;
reqs_vtdA.push_back(StrategyRequirement::UnrollM == config.unroll_m_BcBr);
reqs_vtdA.push_back(StrategyRequirement::UnrollN == config.unroll_n_BcBr);
reqs_vtdA.push_back(StrategyRequirement::WGM == config.wg_m_BcBr);
reqs_vtdA.push_back(StrategyRequirement::WGN == config.wg_n_BcBr);
std::vector<StrategyRequirement> reqs_ktq;
reqs_ktq.push_back(StrategyRequirement::UnrollM == config.unroll_m_DBr);
reqs_ktq.push_back(StrategyRequirement::UnrollN == config.unroll_n_DBr);
reqs_ktq.push_back(StrategyRequirement::WGM == config.wg_m_DBr);
reqs_ktq.push_back(StrategyRequirement::WGN == config.wg_n_DBr);
std::vector<StrategyRequirement> reqs_qdSt;
reqs_qdSt.push_back(StrategyRequirement::UnrollM == config.unroll_n_DBc);
reqs_qdSt.push_back(StrategyRequirement::UnrollN == config.unroll_m_DBc);
reqs_qdSt.push_back(StrategyRequirement::WGM == config.wg_n_DBc);
reqs_qdSt.push_back(StrategyRequirement::WGN == config.wg_m_DBc);
try {
gemm_kq = micro::selectGEMM(
opts_kq, hw_info, sizes_kq, problem_kq, reqs_kq);
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_kq microkernel generation failure with message: %s",
ex.what());
}
try {
if (use_systolic_ukernel) {
auto adjust_vs = [](GEMMStrategy &strategy) {
strategy.dpasw |= strategy.fused;
};
gemm_vs = micro::selectGEMM(
opts_vs, hw_info, sizes_vs, problem_vs, reqs_vs, adjust_vs);
} else {
gemm_vs = micro::selectGEMM(
opts_vs, hw_info, sizes_vs, problem_vs, reqs_vs);
}
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_vs microkernel generation failure with message: %s",
ex.what());
}
VDEBUGINFO(4, primitive, sdpa,
"kq_gemm: %s, vs_gemm: %s, vtdA_gemm: %s, ktq_gemm: %s, qdSt: %s\n",
problem_kq.toString().c_str(), problem_vs.toString().c_str(),
problem_vtdA.toString().c_str(), problem_ktq.toString().c_str(),
problem_qdSt.toString().c_str());
micro::ShimOptions shimOptions;
shimOptions.subgroupSize = subgroup_size;
shimOptions.useTileOps = true;
shimOptions.decorator = "kq";
std::string gemm_kq_header
= micro::generateShim(gemm_kq, HostLanguage::OpenCL_C, shimOptions);
kernel_ctx.add_custom_header("gemm_kq.h", std::move(gemm_kq_header));
shimOptions.microkernelID++;
shimOptions.decorator = "vs";
std::string gemm_vs_header
= micro::generateShim(gemm_vs, HostLanguage::OpenCL_C, shimOptions);
kernel_ctx.add_custom_header("gemm_vs.h", std::move(gemm_vs_header));
try {
gemm_vtdA = micro::selectGEMM(
opts_vtdA, hw_info, sizes_vtdA, problem_vtdA, reqs_vtdA);
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_vtdA microkernel generation failure with message: %s",
ex.what());
}
shimOptions.microkernelID++;
shimOptions.decorator = "vtdA";
std::string gemm_vtdA_header = micro::generateShim(
gemm_vtdA, HostLanguage::OpenCL_C, shimOptions);
kernel_ctx.add_custom_header("gemm_vtdA.h", std::move(gemm_vtdA_header));
try {
gemm_ktq = micro::selectGEMM(
opts_ktq, hw_info, sizes_ktq, problem_ktq, reqs_ktq);
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_ktq microkernel generation failure with message: %s",
ex.what());
}
shimOptions.microkernelID++;
shimOptions.decorator = "ktq";
std::string gemm_ktq_header = micro::generateShim(
gemm_ktq, HostLanguage::OpenCL_C, shimOptions);
kernel_ctx.add_custom_header("gemm_ktq.h", std::move(gemm_ktq_header));
try {
gemm_qdSt = micro::selectGEMM(
opts_qdSt, hw_info, sizes_qdSt, problem_qdSt, reqs_qdSt);
} catch (const std::runtime_error &ex) {
VCHECK_SDPA_COND(false,
"gemm_qdSt microkernel generation failure with message: %s",
ex.what());
}
shimOptions.microkernelID++;
shimOptions.decorator = "qdSt";
std::string gemm_qdSt_header = micro::generateShim(
gemm_qdSt, HostLanguage::OpenCL_C, shimOptions);
kernel_ctx.add_custom_header("gemm_qdSt.h", std::move(gemm_qdSt_header));
if (gemm_kq.grfMin > 128 || gemm_vs.grfMin > 128 || gemm_vtdA.grfMin > 128
|| gemm_ktq.grfMin > 128 || gemm_qdSt.grfMin > 128)
kernel_ctx.add_option("-cl-intel-256-GRF-per-thread");
return status::success;
}
using fwd_offset_t = dim_t[4][MAX_NDIMS];
template <typename T>
static void append_key_offs(
compute::kernel_arg_list_t &arg_list, const fwd_offset_t &offs) {
arg_list.append(static_cast<T>(offs[1][0])); arg_list.append(static_cast<T>(offs[1][1])); arg_list.append(static_cast<T>(offs[1][2])); arg_list.append(static_cast<T>(offs[1][3])); arg_list.append(static_cast<T>(offs[3][3])); }
template <typename T>
static void append_qry_offs(
compute::kernel_arg_list_t &arg_list, const fwd_offset_t &offs) {
arg_list.append(static_cast<T>(offs[1][0])); arg_list.append(static_cast<T>(offs[1][1])); arg_list.append(static_cast<T>(offs[1][2])); }
template <typename T>
static void append_val_offs(
compute::kernel_arg_list_t &arg_list, const fwd_offset_t &offs) {
arg_list.append(static_cast<T>(offs[1][0])); arg_list.append(static_cast<T>(offs[1][1])); arg_list.append(static_cast<T>(offs[1][2])); }
template <typename T>
static void append_dst_offs(
compute::kernel_arg_list_t &arg_list, const fwd_offset_t &offs) {
arg_list.append(static_cast<T>(offs[1][0])); arg_list.append(static_cast<T>(offs[1][1])); arg_list.append(static_cast<T>(offs[1][2])); arg_list.append(static_cast<T>(offs[3][1])); }
template <typename T>
static void append_msk_offs(
compute::kernel_arg_list_t &arg_list, const fwd_offset_t &offs) {
arg_list.append(static_cast<T>(offs[1][0])); arg_list.append(static_cast<T>(offs[1][1])); arg_list.append(static_cast<T>(offs[1][2])); arg_list.append(static_cast<T>(offs[3][0])); arg_list.append(static_cast<T>(offs[3][1])); }
template <typename conf_t>
static status_t append_dropout_args(const exec_ctx_t &ctx,
compute::kernel_arg_list_t &arg_list, const conf_t &conf, bool is_fwd) {
if (!conf.dropout) return status::success;
const auto &dropout_p = CTX_IN_STORAGE(DNNL_ARG_ATTR_DROPOUT_PROBABILITY);
const auto &dropout_seed = CTX_IN_STORAGE(DNNL_ARG_ATTR_DROPOUT_SEED);
const auto &dropout_offset = CTX_IN_STORAGE(DNNL_ARG_ATTR_DROPOUT_OFFSET);
if (is_fwd) {
arg_list.append(CTX_OUT_STORAGE(DNNL_ARG_ATTR_DROPOUT_MASK));
}
arg_list.append(static_cast<int>(conf.dropout_offset));
if (conf.dropout_host_scalars) {
int64_t scalar_seed = 0;
int64_t scalar_offset = 0;
float scalar_prob = 0.f;
const host_scalar_memory_storage_t *seed_storage
= utils::downcast<const host_scalar_memory_storage_t *>(
&dropout_seed);
CHECK(seed_storage->get_scalar_value(
&scalar_seed, sizeof(scalar_seed)));
if (conf.dropout_offset) {
const host_scalar_memory_storage_t *offset_storage
= utils::downcast<const host_scalar_memory_storage_t *>(
&dropout_offset);
CHECK(offset_storage->get_scalar_value(
&scalar_offset, sizeof(scalar_offset)));
}
const host_scalar_memory_storage_t *prob_storage
= utils::downcast<const host_scalar_memory_storage_t *>(
&dropout_p);
CHECK(prob_storage->get_scalar_value(
&scalar_prob, sizeof(scalar_prob)));
arg_list.append(scalar_seed);
arg_list.append(scalar_offset);
arg_list.append(scalar_prob);
} else {
arg_list.append(dropout_seed);
arg_list.append(dropout_offset);
arg_list.append(dropout_p);
}
return status::success;
}
status_t micro_fwd_t::execute_forward(const exec_ctx_t &ctx) const {
const auto &conf = pd()->conf;
const auto &qry = CTX_IN_STORAGE(DNNL_ARG_QUERIES);
const auto &key = CTX_IN_STORAGE(DNNL_ARG_KEYS);
const auto &val = CTX_IN_STORAGE(DNNL_ARG_VALUES);
auto &dst = CTX_OUT_STORAGE(DNNL_ARG_DST);
auto &ws = CTX_OUT_STORAGE(DNNL_ARG_WORKSPACE);
const auto &scale = CTX_IN_STORAGE(DNNL_ARG_SCALE);
const auto &attn_mask = CTX_IN_STORAGE(DNNL_ARG_ATTN_MASK);
const auto &key_scales
= CTX_IN_STORAGE(DNNL_ARG_KEYS | DNNL_ARG_ATTR_SCALES);
const auto &key_zp
= CTX_IN_STORAGE(DNNL_ARG_KEYS | DNNL_ARG_ATTR_ZERO_POINTS);
const auto &value_scales
= CTX_IN_STORAGE(DNNL_ARG_VALUES | DNNL_ARG_ATTR_SCALES);
const auto &value_zp
= CTX_IN_STORAGE(DNNL_ARG_VALUES | DNNL_ARG_ATTR_ZERO_POINTS);
const int kv_group_size = pd()->conf.kv_group_size;
const dim_t Q = pd()->desc()->queries();
const dim_t K = pd()->desc()->keys();
const dim_t D = pd()->desc()->head_size();
const dim_t Q_per_kv_group = (Q == 1 ? Q * kv_group_size : Q);
const fwd_config_t config = {conf.ukernel_config.unroll_m_kq,
conf.ukernel_config.unroll_n_kq, conf.ukernel_config.unroll_m_vs,
conf.ukernel_config.unroll_n_vs, conf.ukernel_config.wg_m_kq,
conf.ukernel_config.wg_n_kq, conf.ukernel_config.wg_m_vs,
conf.ukernel_config.wg_n_vs};
const int kq_wg_tile_m = config.wg_m_kq * config.unroll_m_kq;
const int kq_wg_tile_n = config.wg_n_kq * config.unroll_n_kq;
auto wg_tile_q = kq_wg_tile_n;
auto sg_per_wg = config.wg_m_kq * config.wg_n_kq;
const memory_desc_wrapper qry_mdw(pd()->desc()->qry_md());
const memory_desc_wrapper key_mdw(pd()->desc()->key_md());
const memory_desc_wrapper val_mdw(pd()->desc()->val_md());
const memory_desc_wrapper dst_mdw(pd()->dst_md());
const memory_desc_wrapper msk_mdw(pd()->desc()->attn_mask_md());
fwd_offset_t key_off, qry_off, val_off, dst_off, msk_off;
set_offsets(key_mdw, key_off);
set_offsets(qry_mdw, qry_off);
set_offsets(val_mdw, val_off);
set_offsets(dst_mdw, dst_off);
set_offsets(msk_mdw, msk_off);
const memory_desc_wrapper scale_mdw(pd()->desc()->scale_md());
float scalar_scale = 1.f;
float inv_scalar_scale = 1.f;
if (pd()->with_host_scale()) {
auto scalar_storage = utils::downcast<
const dnnl::impl::host_scalar_memory_storage_t *>(&scale);
auto status = scalar_storage->get_scalar_value(
&scalar_scale, scale_mdw.data_type_size());
assert(status == status::success);
if (status != status::success) return status;
scalar_scale = dnnl::impl::cpu::io::load_float_value(
pd()->desc()->scale_md()->data_type, &scalar_scale, 0);
inv_scalar_scale = 1.f / scalar_scale;
}
int mask_type = static_cast<int>(pd()->desc()->mask_type);
compute::kernel_arg_list_t arg_list;
arg_list.append(key);
arg_list.append(qry);
arg_list.append(val);
arg_list.append(ws);
arg_list.append(dst);
if (pd()->with_host_scale()) {
arg_list.append(scalar_scale);
arg_list.append(inv_scalar_scale);
} else {
arg_list.append(scale);
}
arg_list.append((int)D);
arg_list.append((int)K);
arg_list.append((int)Q);
arg_list.append(key_scales);
arg_list.append(key_zp);
arg_list.append(value_scales);
arg_list.append(value_zp);
arg_list.append(mask_type);
if (pd()->with_attn_mask()) arg_list.append(attn_mask);
append_key_offs<int64_t>(arg_list, key_off);
append_qry_offs<int64_t>(arg_list, qry_off);
append_val_offs<int64_t>(arg_list, val_off);
append_dst_offs<int64_t>(arg_list, dst_off);
if (pd()->with_attn_mask()) { append_msk_offs<int64_t>(arg_list, msk_off); }
const int remainder_k = (K % kq_wg_tile_m) != 0;
arg_list.append(remainder_k);
CHECK(append_dropout_args(ctx, arg_list, pd()->conf, true));
compute::range_t lws = {(size_t)pd()->sg_size(), (size_t)sg_per_wg, 1};
compute::range_t gws = lws;
if (Q == 1) {
gws[0] *= utils::div_up(Q_per_kv_group, wg_tile_q);
gws[1] *= utils::div_up(pd()->dst_md()->dims[1], kv_group_size);
} else {
gws[0] *= utils::div_up(Q, wg_tile_q);
gws[1] *= pd()->dst_md()->dims[1];
}
gws[2] *= pd()->desc()->batch();
auto nd_range = compute::nd_range_t(gws, lws);
return parallel_for(ctx, nd_range, kernel_, arg_list);
}
status_t micro_bwd_t::execute_backward(const exec_ctx_t &ctx) const {
const auto &qry = CTX_IN_STORAGE(DNNL_ARG_QUERIES);
const auto &key = CTX_IN_STORAGE(DNNL_ARG_KEYS);
const auto &val = CTX_IN_STORAGE(DNNL_ARG_VALUES);
const auto &ws = CTX_IN_STORAGE(DNNL_ARG_WORKSPACE);
const auto &dst = CTX_IN_STORAGE(DNNL_ARG_DST);
const auto &diff_dst = CTX_IN_STORAGE(DNNL_ARG_DIFF_DST);
auto &diff_q = CTX_OUT_STORAGE(DNNL_ARG_DIFF_QUERIES);
auto &diff_k = CTX_OUT_STORAGE(DNNL_ARG_DIFF_KEYS);
auto &diff_v = CTX_OUT_STORAGE(DNNL_ARG_DIFF_VALUES);
const auto &scale = CTX_IN_STORAGE(DNNL_ARG_SCALE);
const auto &attn_mask = CTX_IN_STORAGE(DNNL_ARG_ATTN_MASK);
auto Di_scratch = ctx.get_scratchpad_grantor().get_memory_storage(
memory_tracking::names::key_sdpa_Di);
auto diff_q_scratch = ctx.get_scratchpad_grantor().get_memory_storage(
memory_tracking::names::key_sdpa_dQ_reduction);
auto diff_k_scratch = ctx.get_scratchpad_grantor().get_memory_storage(
memory_tracking::names::key_sdpa_dK_reduction);
auto diff_v_scratch = ctx.get_scratchpad_grantor().get_memory_storage(
memory_tracking::names::key_sdpa_dV_reduction);
auto strides_scratch = ctx.get_scratchpad_grantor().get_memory_storage(
memory_tracking::names::key_sdpa_bwd_strides);
const bool with_dS = pd()->with_dS();
const int kv_group_size = pd()->conf.kv_group_size;
const dim_t Q = pd()->desc()->queries();
const dim_t K = pd()->desc()->keys();
const dim_t D = pd()->desc()->head_size();
const data_type_t data_t = pd()->dst_md()->data_type;
const bool needs_intermediate_dQ = (data_t != data_type::f32);
const bool needs_intermediate_dKV
= (kv_group_size > 1 && data_t != data_type::f32);
const bool needs_zero_dKV = (kv_group_size > 1);
const auto &conf = pd()->conf;
const bwd_config_t config = {conf.ukernel_config.unroll_m_BcBr,
conf.ukernel_config.unroll_n_BcBr, conf.ukernel_config.unroll_m_DBc,
conf.ukernel_config.unroll_n_DBc, conf.ukernel_config.unroll_m_DBr,
conf.ukernel_config.unroll_n_DBr, conf.ukernel_config.wg_m_BcBr,
conf.ukernel_config.wg_n_BcBr, conf.ukernel_config.wg_m_DBc,
conf.ukernel_config.wg_n_DBc, conf.ukernel_config.wg_m_DBr,
conf.ukernel_config.wg_n_DBr};
auto wg_tile_k = config.unroll_m_BcBr * config.wg_m_BcBr;
auto wg_tile_q = config.unroll_n_BcBr * config.wg_n_BcBr;
auto sg_per_wg_BcBr = config.wg_m_BcBr * config.wg_n_BcBr;
auto sg_per_wg_DBc = config.wg_m_DBc * config.wg_n_DBc;
auto sg_per_wg_DBr = config.wg_m_DBr * config.wg_n_DBr;
auto sg_per_wg
= std::max(std::max(sg_per_wg_BcBr, sg_per_wg_DBc), sg_per_wg_DBr);
const memory_desc_wrapper qry_mdw(pd()->desc()->qry_md());
const memory_desc_wrapper key_mdw(pd()->desc()->key_md());
const memory_desc_wrapper val_mdw(pd()->desc()->val_md());
const memory_desc_wrapper dst_mdw(pd()->dst_md());
const memory_desc_wrapper msk_mdw(pd()->desc()->attn_mask_md());
const memory_desc_wrapper diff_dst_mdw(pd()->diff_dst_md());
const memory_desc_wrapper diff_qry_mdw(pd()->desc()->diff_qry_md());
const memory_desc_wrapper diff_key_mdw(pd()->desc()->diff_key_md());
const memory_desc_wrapper diff_val_mdw(pd()->desc()->diff_val_md());
using offset_t = decltype(offsets_t().src_off);
offset_t qry_off, key_off, val_off, dst_off, msk_off, da_off;
offset_t dq_off, dk_off, dv_off;
set_offsets(qry_mdw, qry_off);
set_offsets(key_mdw, key_off);
set_offsets(val_mdw, val_off);
set_offsets(dst_mdw, dst_off);
set_offsets(msk_mdw, msk_off);
set_offsets(diff_dst_mdw, da_off);
auto set_contiguous_offsets
= [](offset_t &offs, dim_t B, dim_t H, dim_t S, dim_t D) {
offs[3][0] = B;
offs[3][1] = H;
offs[3][2] = S;
offs[3][3] = D;
offs[1][0] = H * S * D;
offs[1][1] = S * D;
offs[1][2] = D;
offs[1][3] = 1;
};
if (needs_intermediate_dQ) {
set_contiguous_offsets(dq_off, pd()->desc()->batch(),
pd()->desc()->num_q_heads(), Q, D);
}
if (needs_intermediate_dKV) {
if (conf.transpose_k) {
set_contiguous_offsets(dk_off, pd()->desc()->batch(),
pd()->desc()->num_kv_heads(), K, D);
} else {
set_contiguous_offsets(dk_off, pd()->desc()->batch(),
pd()->desc()->num_kv_heads(), D, K);
}
set_contiguous_offsets(dv_off, pd()->desc()->batch(),
pd()->desc()->num_kv_heads(), K, D);
}
auto append_qry_offs
= [](compute::kernel_arg_list_t &arg_list, const offset_t &offs) {
arg_list.append((int64_t)offs[1][0]); arg_list.append((int64_t)offs[1][1]); arg_list.append((int64_t)offs[1][2]); };
auto append_dst_offs
= [](compute::kernel_arg_list_t &arg_list, const offset_t &offs) {
arg_list.append((int64_t)offs[1][0]); arg_list.append((int64_t)offs[1][1]); arg_list.append((int64_t)offs[1][2]); arg_list.append((int64_t)offs[3][1]); };
auto append_da_offs
= [](compute::kernel_arg_list_t &arg_list, const offset_t &offs) {
arg_list.append((int64_t)offs[1][0]); arg_list.append((int64_t)offs[1][1]); arg_list.append((int64_t)offs[1][2]); };
int mask_type = static_cast<int>(pd()->desc()->mask_type);
const memory_desc_wrapper scale_mdw(pd()->desc()->scale_md());
float scalar_scale = 1.f;
float inv_scalar_scale = 1.f;
if (pd()->with_host_scale()) {
auto scalar_storage = utils::downcast<
const dnnl::impl::host_scalar_memory_storage_t *>(&scale);
auto status = scalar_storage->get_scalar_value(
&scalar_scale, scale_mdw.data_type_size());
assert(status == status::success);
if (status != status::success) return status;
scalar_scale = dnnl::impl::cpu::io::load_float_value(
pd()->desc()->scale_md()->data_type, &scalar_scale, 0);
inv_scalar_scale = 1.f / scalar_scale;
}
compute::range_t lws = {(size_t)pd()->sg_size(), (size_t)sg_per_wg, 1};
compute::range_t gws_preprocess = lws;
gws_preprocess[0] *= utils::div_up(Q, wg_tile_q);
gws_preprocess[1] *= pd()->dst_md()->dims[1];
gws_preprocess[2] *= pd()->desc()->batch();
auto nd_range_preprocess = compute::nd_range_t(gws_preprocess, lws);
compute::kernel_arg_list_t preprocess_arg_list;
preprocess_arg_list.append(*Di_scratch);
preprocess_arg_list.append(dst);
preprocess_arg_list.append(diff_dst);
preprocess_arg_list.append((int)D);
preprocess_arg_list.append((int)K);
preprocess_arg_list.append((int)Q);
append_qry_offs(preprocess_arg_list, qry_off);
append_dst_offs(preprocess_arg_list, dst_off);
append_da_offs(preprocess_arg_list, da_off);
CHECK(parallel_for(
ctx, nd_range_preprocess, preprocess_, preprocess_arg_list));
auto *d = pd()->desc();
{
auto compute_stream = utils::downcast<intel::stream_t *>(ctx.stream());
auto &fill_deps = compute_stream->ctx().get_deps();
const dim_t batch = pd()->dst_md()->dims[0];
const dim_t num_kv_heads = d->num_kv_heads();
const dim_t num_q_heads = d->num_q_heads();
auto zero_fill
= [&](const memory_storage_t &buf, size_t bytes) -> status_t {
return compute_stream->fill(buf, 0, bytes, fill_deps, fill_deps);
};
auto &dQ_buf = needs_intermediate_dQ ? *diff_q_scratch : diff_q;
const size_t dQ_bytes = needs_intermediate_dQ
? size_t(batch * num_q_heads * Q * D) * sizeof(float)
: diff_qry_mdw.size();
CHECK(zero_fill(dQ_buf, dQ_bytes));
if (needs_zero_dKV) {
auto &dK_buf = needs_intermediate_dKV ? *diff_k_scratch : diff_k;
auto &dV_buf = needs_intermediate_dKV ? *diff_v_scratch : diff_v;
const size_t scratch_kv_bytes
= size_t(batch * num_kv_heads * K * D) * sizeof(float);
const size_t dK_bytes = needs_intermediate_dKV
? scratch_kv_bytes
: diff_key_mdw.size();
const size_t dV_bytes = needs_intermediate_dKV
? scratch_kv_bytes
: diff_val_mdw.size();
CHECK(zero_fill(dK_buf, dK_bytes));
CHECK(zero_fill(dV_buf, dV_bytes));
}
}
compute::kernel_arg_list_t arg_list;
arg_list.append(key);
arg_list.append(qry);
arg_list.append(val);
arg_list.append(ws);
arg_list.append(*Di_scratch);
arg_list.append(dst);
arg_list.append(diff_dst);
if (with_dS) arg_list.append(CTX_OUT_STORAGE(DNNL_ARG_DS));
arg_list.append(needs_intermediate_dKV ? *diff_k_scratch : diff_k);
arg_list.append(needs_intermediate_dQ ? *diff_q_scratch : diff_q);
arg_list.append(needs_intermediate_dKV ? *diff_v_scratch : diff_v);
if (pd()->with_host_scale()) {
arg_list.append(scalar_scale);
arg_list.append(inv_scalar_scale);
} else {
arg_list.append(scale);
}
CHECK(append_dropout_args(ctx, arg_list, pd()->conf, false));
arg_list.append((int)D);
arg_list.append((int)K);
arg_list.append((int)Q);
arg_list.append(mask_type);
if (pd()->with_attn_mask()) arg_list.append(attn_mask);
offset_t diff_key_off, diff_qry_off, diff_val_off;
set_offsets(diff_key_mdw, diff_key_off);
set_offsets(diff_qry_mdw, diff_qry_off);
set_offsets(diff_val_mdw, diff_val_off);
const auto &dk_off_ref = needs_intermediate_dKV ? dk_off : diff_key_off;
const auto &dq_off_ref = needs_intermediate_dQ ? dq_off : diff_qry_off;
const auto &dv_off_ref = needs_intermediate_dKV ? dv_off : diff_val_off;
{
void *mapped = nullptr;
CHECK(strides_scratch->map_data(
&mapped, ctx.stream(), 32 * sizeof(int64_t)));
auto *buf = static_cast<int64_t *>(mapped);
buf[0] = key_off[1][0]; buf[1] = key_off[1][1]; buf[2] = key_off[1][2]; buf[3] = key_off[1][3]; buf[4] = qry_off[1][0]; buf[5] = qry_off[1][1]; buf[6] = qry_off[1][2]; buf[7] = val_off[1][0]; buf[8] = val_off[1][1]; buf[9] = val_off[1][2]; buf[10] = dst_off[1][0]; buf[11] = dst_off[1][1]; buf[12] = dst_off[1][2]; buf[13] = dst_off[3][1]; buf[14] = da_off[1][0]; buf[15] = da_off[1][1]; buf[16] = da_off[1][2]; buf[17] = dk_off_ref[1][0]; buf[18] = dk_off_ref[1][1]; buf[19] = dk_off_ref[1][2]; buf[20] = dk_off_ref[1][3]; buf[21] = dq_off_ref[1][0]; buf[22] = dq_off_ref[1][1]; buf[23] = dq_off_ref[1][2]; buf[24] = dv_off_ref[1][0]; buf[25] = dv_off_ref[1][1]; buf[26] = dv_off_ref[1][2]; if (pd()->with_attn_mask()) {
buf[27] = msk_off[1][0]; buf[28] = msk_off[1][1]; buf[29] = msk_off[1][2]; buf[30] = msk_off[3][0]; buf[31] = msk_off[3][1]; }
CHECK(strides_scratch->unmap_data(mapped, ctx.stream()));
}
arg_list.append(*strides_scratch);
const int remainder_k = (K % wg_tile_k) != 0;
const bool d_full = (d->head_size() == pd()->d_max());
const int remainder_q = d_full && ((Q % wg_tile_q) != 0);
arg_list.append(remainder_k);
arg_list.append(remainder_q);
compute::range_t gws = lws;
gws[0] *= utils::div_up(K, wg_tile_k);
gws[1] *= pd()->dst_md()->dims[1];
gws[2] *= pd()->desc()->batch();
auto nd_range = compute::nd_range_t(gws, lws);
CHECK(parallel_for(ctx, nd_range, kernel_, arg_list));
auto append_offs
= [](compute::kernel_arg_list_t &arg_list, const offset_t &offs) {
arg_list.append((int64_t)offs[3][0]);
arg_list.append((int64_t)offs[3][1]);
arg_list.append((int64_t)offs[3][2]);
arg_list.append((int64_t)offs[3][3]);
arg_list.append((int64_t)offs[1][0]);
arg_list.append((int64_t)offs[1][1]);
arg_list.append((int64_t)offs[1][2]);
arg_list.append((int64_t)offs[1][3]);
};
auto append_strides
= [](compute::kernel_arg_list_t &arg_list, const offset_t &offs) {
arg_list.append((int64_t)offs[1][0]);
arg_list.append((int64_t)offs[1][1]);
arg_list.append((int64_t)offs[1][2]);
arg_list.append((int64_t)offs[1][3]);
};
if (needs_intermediate_dQ) {
static constexpr size_t lws_pp = 256;
compute::range_t lws_p = {(size_t)lws_pp, 1, 1};
compute::range_t gws_p = lws_p;
gws_p[0] *= utils::div_up(Q * D, lws_pp);
gws_p[1] *= pd()->dst_md()->dims[1]; gws_p[2] *= pd()->desc()->batch();
compute::kernel_arg_list_t pp;
pp.append(diff_q);
pp.append(*diff_q_scratch);
pp.append((int)(Q * D));
append_strides(pp, dq_off);
append_offs(pp, diff_qry_off);
CHECK(parallel_for(
ctx, compute::nd_range_t(gws_p, lws_p), postprocess_, pp));
}
if (needs_intermediate_dKV) {
const dim_t num_kv_heads = d->num_kv_heads();
static constexpr size_t lws_pp = 256;
compute::range_t lws_p = {(size_t)lws_pp, 1, 1};
{
compute::range_t gws_p = lws_p;
gws_p[0] *= utils::div_up(K * D, lws_pp);
gws_p[1] *= num_kv_heads; gws_p[2] *= pd()->desc()->batch();
compute::kernel_arg_list_t pp;
pp.append(diff_k);
pp.append(*diff_k_scratch);
pp.append((int)(K * D));
pp.append((int64_t)dk_off[1][0]);
pp.append((int64_t)dk_off[1][1]);
if (conf.transpose_k) {
pp.append((int64_t)dk_off[1][3]);
pp.append((int64_t)dk_off[1][2]);
} else {
pp.append((int64_t)dk_off[1][2]);
pp.append((int64_t)dk_off[1][3]);
}
append_offs(pp, diff_key_off);
CHECK(parallel_for(
ctx, compute::nd_range_t(gws_p, lws_p), postprocess_, pp));
}
{
compute::range_t gws_p = lws_p;
gws_p[0] *= utils::div_up(K * D, lws_pp);
gws_p[1] *= num_kv_heads;
gws_p[2] *= pd()->desc()->batch();
compute::kernel_arg_list_t pp;
pp.append(diff_v);
pp.append(*diff_v_scratch);
pp.append((int)(K * D));
append_strides(pp, dv_off);
append_offs(pp, diff_val_off);
CHECK(parallel_for(
ctx, compute::nd_range_t(gws_p, lws_p), postprocess_, pp));
}
}
return status::success;
}
} } } } }