#ifndef GPU_INTEL_SDPA_MICRO_HPP
#define GPU_INTEL_SDPA_MICRO_HPP
#include <assert.h>
#include "common/c_types_map.hpp"
#include "common/gemm_types.hpp"
#include "common/gemm_utils.hpp"
#include "common/math_utils.hpp"
#include "common/primitive.hpp"
#include "common/sdpa_pd.hpp"
#include "common/utils.hpp"
#include "gpu/intel/primitive.hpp"
#include "gpu/intel/sdpa/config.hpp"
#include "gpu/intel/sdpa/configs.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace sdpa {
struct micro_fwd_params_t : trivially_serializable_t<micro_fwd_params_t> {
const std::vector<const char *> &get_kernel_names() const {
static const std::vector<const char *> kernel_names_fwd
= {"micro_sdpa"};
return kernel_names_fwd;
}
status_t create_generator(const intel::engine_t &engine,
compute::kernel_bundle_t &bundle) const {
compute::kernel_ctx_t kernel_ctx;
CHECK(get_kernel_ctx(kernel_ctx));
auto status = engine.create_kernel_bundle(
bundle, get_kernel_names(), kernel_ctx);
return status;
}
status_t get_kernel_ctx(compute::kernel_ctx_t &) const;
int ndims;
data_type_t data_t;
data_type_t dst_data_t, key_data_t, qry_data_t, val_data_t, msk_data_t;
data_type_t key_scales_data_t, value_scales_data_t;
data_type_t key_zp_data_t, value_zp_data_t;
int kv_group_size;
int q_align, k_align, v_align, a_align;
bool transpose_k;
uint8_t padding0[3] = {0};
int kq_scale_mask, vs_scale_mask, kq_zp_mask, vs_zp_mask;
int key_elements_per_byte, key_zp_elements_per_byte, val_elements_per_byte,
val_zp_elements_per_byte;
int key_group_size, val_group_size;
data_type_t scale_data_t;
int attn_mask_undef, attn_mask_buffer, attn_mask_top_left,
attn_mask_bottom_right;
bool invert_scale, with_attn_scale, with_host_scale, with_attn_mask,
broadcast_mask_q, with_causal_mask;
uint8_t padding1[2] = {0};
int subgroup_size, d_max;
bool d_full, arch_gte_hpc;
bool block_q, block_a, block_2d_a;
bool prefetch_mask, prefetch_k0, prefetch_k, prefetch_v, prefetch_remainder;
bool remainder_q;
uint8_t padding2[5] = {0};
int prefetch_d_max;
bool softmax_inf_as_zero;
bool q_arrive_await_barrier;
bool use_systolic_ukernel;
bool kq_f16_accumulate, vs_f16_accumulate;
bool require_stateless_addressing;
bool is_training;
bool dropout, dropout_output_mask, dropout_offset, dropout_host_scalars;
uint8_t padding3[1] = {0};
micro_fwd_ukernel_params_t ukernel_config;
};
DNNL_ASSERT_TRIVIALLY_SERIALIZABLE(micro_fwd_params_t);
struct micro_bwd_params_t : trivially_serializable_t<micro_bwd_params_t> {
const std::vector<const char *> &get_kernel_names() const {
static const std::vector<const char *> kernel_names_bwd
= {"preprocess_Di", "micro_sdpa_bwd", "postprocess_dQ"};
return kernel_names_bwd;
}
status_t create_generator(const intel::engine_t &engine,
compute::kernel_bundle_t &bundle) const {
compute::kernel_ctx_t kernel_ctx;
CHECK(get_kernel_ctx(kernel_ctx));
auto status = engine.create_kernel_bundle(
bundle, get_kernel_names(), kernel_ctx);
return status;
}
status_t get_kernel_ctx(compute::kernel_ctx_t &) const;
int ndims;
int kv_group_size;
data_type_t data_t;
data_type_t dst_data_t, key_data_t, qry_data_t, val_data_t, msk_data_t;
int q_align, k_align, v_align, a_align;
bool transpose_k;
uint8_t padding0[3] = {0};
int key_group_size, val_group_size;
data_type_t scale_data_t;
int attn_mask_undef, attn_mask_buffer, attn_mask_top_left,
attn_mask_bottom_right;
bool invert_scale, with_attn_scale, with_host_scale, with_attn_mask,
broadcast_mask_q, with_causal_mask;
uint8_t padding1[2] = {0};
int subgroup_size, d_max;
bool d_full, arch_gte_hpc;
bool block_k, block_dK, block_dV;
bool remainder_q;
bool use_systolic_ukernel;
bool with_dS;
bool require_stateless_addressing;
bool dropout, dropout_output_mask, dropout_offset, dropout_host_scalars;
uint8_t padding2[3] = {0};
micro_bwd_ukernel_params_t ukernel_config;
};
DNNL_ASSERT_TRIVIALLY_SERIALIZABLE(micro_bwd_params_t);
struct micro_fwd_t : public primitive_t {
using primitive_t::primitive_t;
struct pd_t : public sdpa_fwd_pd_t {
using sdpa_fwd_pd_t::sdpa_fwd_pd_t;
DECLARE_COMMON_PD_T("ocl:micro:reusable", micro_fwd_t);
status_t init(impl::engine_t *engine) {
using namespace data_type;
using smask_t = primitive_attr_t::skip_mask_t;
VCHECK_SDPA_COND(is_fwd(), VERBOSE_BAD_PROPKIND);
VCHECK_SDPA_COND(utils::everyone_is(4, desc()->qry_md()->ndims,
desc()->key_md()->ndims,
desc()->val_md()->ndims, dst_md()->ndims),
VERBOSE_UNSUPPORTED_TAG);
memory_desc_wrapper qry_mdw(desc()->qry_md());
memory_desc_wrapper key_mdw(desc()->key_md());
memory_desc_wrapper val_mdw(desc()->val_md());
memory_desc_wrapper dst_mdw(dst_md());
VCHECK_SDPA_COND(utils::everyone_is(true, qry_mdw.is_plain(),
key_mdw.is_plain(), val_mdw.is_plain(),
dst_mdw.is_plain()),
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(attr()->has_default_values(smask_t::dropout),
VERBOSE_UNSUPPORTED_ATTR);
if (with_attn_mask()) {
VCHECK_SDPA_COND(desc()->attn_mask_md()->ndims == 4,
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(
utils::one_of(
desc()->attn_mask_md()->dims[mask_q_index],
desc()->queries(), 1),
VERBOSE_INVALID_BROADCAST, "attn_mask", mask_q_index);
VCHECK_SDPA_COND(desc()->attn_mask_md()->dims[mask_k_index]
== desc()->keys(),
VERBOSE_INVALID_BROADCAST, "attn_mask", mask_k_index);
if (desc()->qry_md()->data_type == data_type::f32) {
VCHECK_SDPA_COND(desc()->attn_mask_md()->data_type
== desc()->qry_md()->data_type,
"Mask data type(%s) should match Qry/Dst data "
"type(%s).",
dnnl_dt2str(desc()->attn_mask_md()->data_type),
dnnl_dt2str(desc()->qry_md()->data_type));
} else {
VCHECK_SDPA_COND((desc()->attn_mask_md()->data_type
== desc()->qry_md()->data_type)
|| (desc()->attn_mask_md()->data_type
== data_type::f32),
"Mask data type(%s) should be xf16 or f32 when "
"Qry/Dst(%s) is xf16.",
dnnl_dt2str(desc()->attn_mask_md()->data_type),
dnnl_dt2str(desc()->qry_md()->data_type));
}
}
VCHECK_SDPA_COND(
(utils::everyone_is(data_type::f16,
desc()->qry_md()->data_type, dst_md()->data_type)
|| utils::everyone_is(data_type::bf16,
desc()->qry_md()->data_type,
dst_md()->data_type)
|| utils::everyone_is(data_type::f32,
desc()->qry_md()->data_type,
dst_md()->data_type)),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(utils::one_of(desc()->key_md()->data_type, f32,
bf16, f16, u8, s8, u4, s4),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(utils::one_of(desc()->val_md()->data_type, f32,
bf16, f16, u8, s8, u4, s4),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(set_default_formats() == status::success,
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(desc()->values() == desc()->head_size(),
"values does not match head size");
if (utils::one_of(desc()->key_md()->data_type, u4, s4)) {
VCHECK_SDPA_COND(desc()->keys() % 2 == 0,
"The number of keys must be an even size with the data "
"type is u4 or s4.");
}
if (utils::one_of(desc()->val_md()->data_type, u4, s4)) {
VCHECK_SDPA_COND(desc()->values() % 2 == 0,
"The number of values must be an even size with the "
"data type is u4 or s4.");
}
VCHECK_SDPA_COND(
desc()->qry_md()->dims[1] >= desc()->key_md()->dims[1]
&& desc()->qry_md()->dims[1]
>= desc()->val_md()->dims[1],
"number of heads in query tensor(%ld) must be greater "
"than the number of heads in the key(%ld) and value(%ld) "
"tensors",
static_cast<long int>(desc()->qry_md()->dims[1]),
static_cast<long int>(desc()->key_md()->dims[1]),
static_cast<long int>(desc()->val_md()->dims[1]));
VCHECK_SDPA_COND(utils::one_of(kq_acc_dt(), f16, f32),
"KQ accumulation data type should be f16 or f32");
VCHECK_SDPA_COND(utils::one_of(vs_acc_dt(), f16, f32),
"VS accumulation data type should be f16 or f32");
int kq_scales_mask = desc()->kq_scales.get_mask();
int kq_zp_mask = desc()->kq_zero_points.get_mask();
if (!desc()->kq_scales.has_default_values()
&& !desc()->kq_zero_points.has_default_values())
VCHECK_SDPA_COND(kq_scales_mask == kq_zp_mask,
"kq scales mask(%d) must equal kq zero point(%d) "
"mask",
kq_scales_mask, kq_zp_mask);
if (!desc()->kq_scales.has_default_values())
VCHECK_SDPA_COND(utils::one_of(kq_scales_mask, 0, 1, 3, 11, 15),
"unsupported mask for kq matmul(%d). must be 0, 1, 3, "
"11, or 15",
kq_scales_mask);
if (!desc()->kq_zero_points.has_default_values())
VCHECK_SDPA_COND(utils::one_of(kq_zp_mask, 0, 1, 3, 11, 15),
"unsupported mask for kq matmul(%d). must be 0, 1, 3, "
"11, or 15",
kq_zp_mask);
int vs_scales_mask = desc()->vs_scales.get_mask();
int vs_zp_mask = desc()->vs_zero_points.get_mask();
if (!desc()->vs_scales.has_default_values()
&& !desc()->vs_zero_points.has_default_values())
VCHECK_SDPA_COND(vs_scales_mask == vs_zp_mask,
"vs scales mask(%d) must equal vs zero point(%d) "
"mask",
vs_scales_mask, vs_zp_mask);
if (!desc()->vs_scales.has_default_values())
VCHECK_SDPA_COND(utils::one_of(vs_scales_mask, 0, 1, 3, 7, 15),
"unsupported mask for vs matmul(%d). must be 0, 1, 3, "
"7, or 15",
vs_scales_mask);
if (!desc()->vs_zero_points.has_default_values())
VCHECK_SDPA_COND(utils::one_of(vs_zp_mask, 0, 1, 3, 7, 15),
"unsupported mask for vs matmul(%d). must be 0, 1, 3, "
"7, or 15",
vs_zp_mask);
if (utils::one_of(desc()->vs_zero_points.get_data_type(), s4, u4)) {
VCHECK_SDPA_COND(value_group_size() == 16,
"if vs zero points data type is s4 or u4 then the "
"group size(%d) must be 16.",
value_group_size());
}
if (!desc()->vs_scales.has_default_values()
|| !desc()->vs_zero_points.has_default_values()) {
int vgs = value_group_size();
VCHECK_SDPA_COND(utils::one_of(vs_scales_mask, 0, 1, 3)
|| (math::is_pow2<int>(vgs)
|| vgs == desc()->val_md()->dims[3]),
"the value group size(%d) must be a power of 2 or "
"equal to the number of values(%ld).",
vgs, static_cast<long int>(desc()->val_md()->dims[3]));
}
CHECK(init_conf_microkernels(engine));
CHECK(init_conf(engine));
VCHECK_SDPA_COND(IMPLICATION((arch() == compute::gpu_arch_t::xe_hpc)
&& (desc()->qry_md()->data_type
== data_type::f32),
with_causal_mask()),
"fused f32 SDPA only optimized for causal mask");
return status::success;
}
status_t set_default_formats() {
CHECK(set_default_format(desc_.q_desc, false));
CHECK(set_default_format(desc_.k_desc, true));
CHECK(set_default_format(desc_.v_desc, false));
CHECK(set_default_format(desc_.dst_desc, false));
return status::success;
}
int sg_size() const { return sg_size_; }
bool use_systolic_ukernel() const { return use_systolic_ukernel_; }
int d_max() const {
int head_size = into<int>(desc()->head_size());
for (int i = 32; i <= 1024; i *= 2)
if (head_size <= i) return i;
return head_size;
}
compute::gpu_arch_t arch() const { return arch_; }
micro_fwd_params_t conf;
private:
int sg_size_ = 0;
bool use_systolic_ukernel_ = true;
compute::gpu_arch_t arch_ = compute::gpu_arch_t::unknown;
status_t init_conf_microkernels(impl::engine_t *engine);
status_t init_conf(impl::engine_t *engine);
status_t set_default_format(memory_desc_t &md, bool allow_transpose) {
using namespace format_tag;
memory_desc_wrapper mdw(md);
VCHECK_SDPA_UNIMPL(!mdw.format_any(), VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_UNIMPL(is_md_gemm_compatible_plain_format(&md),
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_UNIMPL(
IMPLICATION(gemm_desc_t::get_trans(md) == dnnl_trans,
allow_transpose),
VERBOSE_UNSUPPORTED_TAG);
return status::success;
}
};
status_t init(impl::engine_t *engine) override;
status_t execute(const exec_ctx_t &ctx) const override {
return execute_forward(ctx);
}
private:
const pd_t *pd() const { return (const pd_t *)primitive_t::pd().get(); }
status_t execute_forward(const exec_ctx_t &ctx) const;
compute::kernel_t kernel_;
};
struct micro_bwd_t : public primitive_t {
using primitive_t::primitive_t;
struct pd_t : public sdpa_bwd_pd_t {
using sdpa_bwd_pd_t::sdpa_bwd_pd_t;
DECLARE_COMMON_PD_T("ocl:micro:reusable", micro_bwd_t);
status_t init(impl::engine_t *engine) {
using namespace data_type;
using smask_t = primitive_attr_t::skip_mask_t;
VCHECK_SDPA_COND(!is_fwd(), VERBOSE_BAD_PROPKIND);
VCHECK_SDPA_COND(utils::everyone_is(4, desc()->qry_md()->ndims,
desc()->key_md()->ndims,
desc()->val_md()->ndims, dst_md()->ndims),
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(
utils::everyone_is(4, desc()->diff_qry_md()->ndims,
desc()->diff_key_md()->ndims,
desc()->diff_val_md()->ndims, diff_dst_md()->ndims),
VERBOSE_UNSUPPORTED_TAG);
if (with_attn_mask()) {
VCHECK_SDPA_COND(desc()->attn_mask_md()->ndims == 4,
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(
utils::one_of(
desc()->attn_mask_md()->dims[mask_q_index],
desc()->queries(), 1),
VERBOSE_INVALID_BROADCAST, "attn_mask", mask_q_index);
VCHECK_SDPA_COND(desc()->attn_mask_md()->dims[mask_k_index]
== desc()->keys(),
VERBOSE_INVALID_BROADCAST, "attn_mask", mask_k_index);
VCHECK_SDPA_COND(desc()->attn_mask_md()->data_type
== desc()->qry_md()->data_type,
"Mask data type should match Qry/Dst data type.");
}
VCHECK_SDPA_COND(
(utils::everyone_is(data_type::f16,
desc()->qry_md()->data_type, dst_md()->data_type)
|| utils::everyone_is(data_type::bf16,
desc()->qry_md()->data_type,
dst_md()->data_type)
|| utils::everyone_is(data_type::f32,
desc()->qry_md()->data_type,
dst_md()->data_type)),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(
utils::one_of(desc()->key_md()->data_type, f32, bf16, f16),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(
utils::one_of(desc()->val_md()->data_type, f32, bf16, f16),
VERBOSE_UNSUPPORTED_DT);
VCHECK_SDPA_COND(set_default_formats() == status::success,
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_COND(desc()->values() == desc()->head_size(),
"values does not match head size");
VCHECK_SDPA_COND(
desc()->qry_md()->dims[1] >= desc()->key_md()->dims[1]
&& desc()->qry_md()->dims[1]
>= desc()->val_md()->dims[1],
"number of heads in query tensor(%ld) must be greater "
"than the number of heads in the key(%ld) and value(%ld) "
"tensors",
static_cast<long int>(desc()->qry_md()->dims[1]),
static_cast<long int>(desc()->key_md()->dims[1]),
static_cast<long int>(desc()->val_md()->dims[1]));
{
memory_desc_wrapper diff_qry_mdw(desc()->diff_qry_md());
memory_desc_wrapper diff_key_mdw(desc()->diff_key_md());
memory_desc_wrapper diff_val_mdw(desc()->diff_val_md());
memory_desc_wrapper diff_dst_mdw(diff_dst_md());
VCHECK_SDPA_COND(
utils::everyone_is(true, diff_qry_mdw.is_plain(),
diff_key_mdw.is_plain(),
diff_val_mdw.is_plain(),
diff_dst_mdw.is_plain()),
VERBOSE_UNSUPPORTED_TAG);
}
VCHECK_SDPA_COND(utils::everyone_is(desc()->qry_md()->data_type,
desc()->diff_qry_md()->data_type,
desc()->diff_key_md()->data_type,
desc()->diff_val_md()->data_type,
diff_dst_md()->data_type),
"diff tensor data types must match qry data type(%s) "
" ?= dQ(%s), dK(%s), dV(%s), dO(%s)",
dnnl_dt2str(desc()->qry_md()->data_type),
dnnl_dt2str(desc()->diff_qry_md()->data_type),
dnnl_dt2str(desc()->diff_key_md()->data_type),
dnnl_dt2str(desc()->diff_val_md()->data_type),
dnnl_dt2str(diff_dst_md()->data_type));
VCHECK_SDPA_COND(attr()->has_default_values(smask_t::dropout),
VERBOSE_UNSUPPORTED_ATTR);
CHECK(init_default_ws());
VCHECK_SDPA_COND(compare_ws(hint_fwd_pd_), VERBOSE_WS_MISMATCH);
VCHECK_SDPA_COND(arch() != compute::gpu_arch_t::xe_hpg,
"fused SDPA BWD not supported for xe_hpg");
CHECK(init_conf_microkernels(engine));
CHECK(init_conf(engine));
CHECK(init_scratchpad(engine));
return status::success;
}
status_t set_default_formats() {
CHECK(set_default_format(desc_.q_desc, false));
CHECK(set_default_format(desc_.k_desc, true));
CHECK(set_default_format(desc_.v_desc, false));
CHECK(set_default_format(desc_.dst_desc, false));
CHECK(set_default_format(desc_.diff_dst_desc, false));
CHECK(set_default_format(desc_.diff_q_desc, false));
CHECK(set_default_format(desc_.diff_k_desc, true));
CHECK(set_default_format(desc_.diff_v_desc, false));
return status::success;
}
int sg_size() const { return sg_size_; }
bool use_systolic_ukernel() const { return use_systolic_ukernel_; }
int d_max() const {
int head_size = into<int>(desc()->head_size());
for (int i = 32; i <= 1024; i *= 2)
if (head_size <= i) return i;
return head_size;
}
compute::gpu_arch_t arch() const { return arch_; }
micro_bwd_params_t conf;
private:
int sg_size_ = 0;
bool use_systolic_ukernel_ = true;
compute::gpu_arch_t arch_ = compute::gpu_arch_t::unknown;
status_t init_scratchpad(impl::engine_t *engine);
status_t init_conf_microkernels(impl::engine_t *engine);
status_t init_conf(impl::engine_t *engine);
status_t set_default_format(memory_desc_t &md, bool allow_transpose) {
using namespace format_tag;
memory_desc_wrapper mdw(md);
VCHECK_SDPA_UNIMPL(!mdw.format_any(), VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_UNIMPL(is_md_gemm_compatible_plain_format(&md),
VERBOSE_UNSUPPORTED_TAG);
VCHECK_SDPA_UNIMPL(
IMPLICATION(gemm_desc_t::get_trans(md) == dnnl_trans,
allow_transpose),
VERBOSE_UNSUPPORTED_TAG);
return status::success;
}
};
status_t init(impl::engine_t *engine) override;
status_t execute(const exec_ctx_t &ctx) const override {
return execute_backward(ctx);
}
private:
const pd_t *pd() const { return (const pd_t *)primitive_t::pd().get(); }
status_t execute_backward(const exec_ctx_t &ctx) const;
compute::kernel_t kernel_, preprocess_, postprocess_;
};
} } } } }
#endif