#ifndef COMMON_SDPA_UTILS_HPP
#define COMMON_SDPA_UTILS_HPP
#include "oneapi/dnnl/dnnl.h"
#include "common/c_types_map.hpp"
#include "common/nstl.hpp"
#include "common/primitive_desc_iterator.hpp"
#include "common/sdpa_types.hpp"
#include "common/utils.hpp"
namespace dnnl {
namespace impl {
#define VCHECK_SDPA(f, msg, ...) \
VCHECK(primitive, create, check, sdpa, (f), msg, ##__VA_ARGS__);
#define VCHECK_SDPA_COND(cond, msg, ...) \
VCONDCHECK(primitive, create, check, sdpa, (cond), \
status::invalid_arguments, msg, ##__VA_ARGS__);
#define VCHECK_SDPA_ATTR_TYPE( \
variable_check, variable, attribute_member_name, expected_types) \
VCONDCHECK(primitive, create, check, sdpa, (variable_check), \
status::invalid_arguments, VERBOSE_INVALID_DATATYPE, \
format_verbose_string(#variable attribute_member_name \
"(%s). must be " expected_types, \
attr2str(variable).c_str()) \
.c_str())
#define VCHECK_SDPA_UNIMPL(cond, msg, ...) \
VCONDCHECK(primitive, create, check, sdpa, (cond), status::unimplemented, \
msg, ##__VA_ARGS__);
static inline status_t sdpa_desc_check(const memory_desc_t *q_desc,
const memory_desc_t *k_desc, const memory_desc_t *v_desc,
const memory_desc_t *dst_desc, const memory_desc_t *attn_mask_md,
const engine_t *engine, const primitive_attr_t *attr,
const primitive_attr_t *kq_attr, const primitive_attr_t *vs_attr) {
int ndims = dst_desc->ndims;
int r = ndims - 2, c = ndims - 1;
VCHECK_SDPA_COND(utils::everyone_is(ndims, q_desc->ndims, k_desc->ndims,
v_desc->ndims),
"number of dimensions have to match. expected: %d q: %d k: %d v: "
"%d",
ndims, q_desc->ndims, k_desc->ndims, v_desc->ndims);
VCHECK_SDPA_COND(q_desc->dims[c] == k_desc->dims[r],
"q_desc->dims[%d](%s) must match k_desc->dims[%d](%s)", c,
md2dim_str(q_desc).c_str(), r, md2dim_str(k_desc).c_str());
VCHECK_SDPA_COND(k_desc->dims[c] == v_desc->dims[r],
"k_desc->dims[%d](%s) must match v_desc->dims[%d](%s)", c,
md2dim_str(k_desc).c_str(), r, md2dim_str(v_desc).c_str());
VCHECK_SDPA_COND(dst_desc->dims[r] == q_desc->dims[r],
"dst_desc->dims[%d](%s) == q_desc->dims[%d](%s)", r,
md2dim_str(dst_desc).c_str(), r, md2dim_str(q_desc).c_str());
VCHECK_SDPA_COND(dst_desc->dims[c] == v_desc->dims[c],
"dst_desc->dims[%d](%s) == v_desc->dims[%d](%s)", c,
md2dim_str(dst_desc).c_str(), c, md2dim_str(v_desc).c_str());
VCHECK_SDPA_COND(!any_memory_desc_host_scalar(
q_desc, k_desc, v_desc, dst_desc, attn_mask_md),
VERBOSE_UNSUPPORTED_FORMAT_KIND);
return status::success;
}
static inline status_t sdpa_desc_check(const memory_desc_t *q_desc,
const memory_desc_t *k_desc, const memory_desc_t *v_desc,
const memory_desc_t *dst_desc, const memory_desc_t *attn_mask_md,
const memory_desc_t *diff_q_desc, const memory_desc_t *diff_k_desc,
const memory_desc_t *diff_v_desc, const memory_desc_t *diff_dst_desc,
const engine_t *engine, const primitive_attr_t *attr) {
int ndims = dst_desc->ndims;
int r = ndims - 2, c = ndims - 1;
VCHECK_SDPA_COND(
utils::everyone_is(ndims, q_desc->ndims, k_desc->ndims,
v_desc->ndims, diff_q_desc->ndims, diff_k_desc->ndims,
diff_v_desc->ndims, diff_dst_desc->ndims),
"number of dimensions have to match. expected: %d q: %d k: %d v: "
"%d dQ: %d dK: %d dV: %d dO: %d",
ndims, q_desc->ndims, k_desc->ndims, v_desc->ndims,
diff_q_desc->ndims, diff_k_desc->ndims, diff_v_desc->ndims,
diff_dst_desc->ndims);
VCHECK_SDPA_COND(q_desc->dims[c] == k_desc->dims[r],
"q_desc->dims[%d](%s) must match k_desc->dims[%d](%s)", c,
md2dim_str(q_desc).c_str(), r, md2dim_str(k_desc).c_str());
VCHECK_SDPA_COND(k_desc->dims[c] == v_desc->dims[r],
"k_desc->dims[%d](%s) must match v_desc->dims[%d](%s)", c,
md2dim_str(k_desc).c_str(), r, md2dim_str(v_desc).c_str());
VCHECK_SDPA_COND(dst_desc->dims[r] == q_desc->dims[r],
"dst_desc->dims[%d](%s) == q_desc->dims[%d](%s)", r,
md2dim_str(dst_desc).c_str(), r, md2dim_str(q_desc).c_str());
VCHECK_SDPA_COND(dst_desc->dims[c] == v_desc->dims[c],
"dst_desc->dims[%d](%s) == v_desc->dims[%d](%s)", c,
md2dim_str(dst_desc).c_str(), c, md2dim_str(v_desc).c_str());
for (int i = 0; i < ndims; i++) {
VCHECK_SDPA_COND(diff_q_desc->dims[i] == q_desc->dims[i],
"diff_q_desc->dims[%d](%s) must match q_desc->dims[%d](%s)", i,
md2dim_str(diff_q_desc).c_str(), i, md2dim_str(q_desc).c_str());
VCHECK_SDPA_COND(diff_k_desc->dims[i] == k_desc->dims[i],
"diff_k_desc->dims[%d](%s) must match k_desc->dims[%d](%s)", i,
md2dim_str(diff_k_desc).c_str(), i, md2dim_str(k_desc).c_str());
VCHECK_SDPA_COND(diff_v_desc->dims[i] == v_desc->dims[i],
"diff_v_desc->dims[%d](%s) must match v_desc->dims[%d](%s)", i,
md2dim_str(diff_v_desc).c_str(), i, md2dim_str(v_desc).c_str());
VCHECK_SDPA_COND(diff_dst_desc->dims[i] == dst_desc->dims[i],
"diff_dst_desc->dims[%d](%s) must match dst_desc->dims[%d](%s)",
i, md2dim_str(diff_dst_desc).c_str(), i,
md2dim_str(dst_desc).c_str());
}
VCHECK_SDPA_COND(!any_memory_desc_host_scalar(q_desc, k_desc, v_desc,
dst_desc, attn_mask_md, diff_q_desc, diff_k_desc,
diff_v_desc, diff_dst_desc),
VERBOSE_UNSUPPORTED_FORMAT_KIND);
return status::success;
}
static inline status_t sdpa_dropout_desc_check(const memory_desc_t *dst_desc,
const memory_desc_t *k_desc, const primitive_attr_t *attr) {
if (!attr->dropout_.has_output_mask()) return status::success;
using namespace format_tag;
const auto &mask_desc = attr->dropout_.dropout_desc_;
const int ndims = dst_desc->ndims;
VCHECK_SDPA_UNIMPL(mask_desc.ndims == ndims, VERBOSE_INCONSISTENT_NDIMS,
"mask", "dst");
for (int i = 0; i < ndims - 1; ++i) {
VCHECK_SDPA_UNIMPL(mask_desc.dims[i] == dst_desc->dims[i],
VERBOSE_INCONSISTENT_DIM, "mask", i, "dst", i);
}
VCHECK_SDPA_UNIMPL(mask_desc.dims[ndims - 1] == k_desc->dims[ndims - 1],
VERBOSE_INCONSISTENT_DIM, "mask", ndims - 1, "k", ndims - 1);
VCHECK_SDPA_UNIMPL(
memory_desc_matches_one_of_tag(mask_desc, ncdhw, nchw, ncw, nc),
VERBOSE_UNSUPPORTED_TAG_S, "mask");
return status::success;
}
static inline status_t sdpa_attr_check(const memory_desc_t *q_desc,
const memory_desc_t *k_desc, const memory_desc_t *v_desc,
const memory_desc_t *dst_desc, const engine_t *engine,
const primitive_attr_t *attr, const primitive_attr_t *kq_attr,
const primitive_attr_t *vs_attr) {
using smask_t = primitive_attr_t::skip_mask_t;
if (utils::everyone_is(nullptr, attr, kq_attr, vs_attr))
return status::success;
if (attr && attr->has_default_values() && kq_attr
&& kq_attr->has_default_values() && vs_attr
&& vs_attr->has_default_values()) {
return status::success;
}
using namespace dnnl::impl::data_type;
if (kq_attr && !kq_attr->has_default_values()) {
const auto &sc = kq_attr->scales_;
const auto &zp = kq_attr->zero_points_;
if (!sc.has_default_values()) {
const auto &scale_dt = sc.get_data_type(DNNL_ARG_WEIGHTS);
VCHECK_SDPA_ATTR_TYPE(utils::one_of(scale_dt, f16, bf16, f32),
kq_attr, "scales", "f16, bf16, or f32");
}
if (!zp.has_default_values()) {
const auto &zp_dt = zp.get_data_type(DNNL_ARG_WEIGHTS);
VCHECK_SDPA_ATTR_TYPE(utils::one_of(zp_dt, s4, u4, u8, s8, s32),
kq_attr, "zero_points", "u4, s4, u8, s8, or s32");
}
}
if (vs_attr && !vs_attr->has_default_values()) {
const auto &sc = vs_attr->scales_;
const auto &zp = vs_attr->zero_points_;
if (!sc.has_default_values()) {
const auto &scale_dt = sc.get_data_type(DNNL_ARG_WEIGHTS);
VCHECK_SDPA_ATTR_TYPE(utils::one_of(scale_dt, f16, bf16, f32),
vs_attr, "scales", "f16, bf16, or f32");
VCHECK_SDPA_UNIMPL(IMPLICATION(engine->kind() == engine_kind::gpu,
!sc.has_host_scalars()),
VERBOSE_UNSUPPORTED_SCALES_CFG);
}
if (!zp.has_default_values()) {
const auto &zp_dt = zp.get_data_type(DNNL_ARG_WEIGHTS);
VCHECK_SDPA_ATTR_TYPE(utils::one_of(zp_dt, s4, u4, u8, s8, s32),
vs_attr, "zero_points", "u4, s4, u8, s8, or s32");
VCHECK_SDPA_UNIMPL(IMPLICATION(engine->kind() == engine_kind::gpu,
!zp.has_host_scalars()),
VERBOSE_UNSUPPORTED_ZP_CFG);
}
}
if (attr) {
smask_t attr_mask = smask_t::dropout;
VCHECK_SDPA_UNIMPL(
attr->has_default_values(attr_mask), VERBOSE_UNSUPPORTED_ATTR);
if (!attr->dropout_.has_default_values()) {
CHECK(sdpa_dropout_desc_check(dst_desc, k_desc, attr));
}
}
return status::success;
}
static inline status_t sdpa_attr_check(const engine_t *engine,
const primitive_attr_t *attr, const memory_desc_t *dst_desc,
const memory_desc_t *k_desc) {
using smask_t = primitive_attr_t::skip_mask_t;
if (attr == nullptr) return status::success;
if (attr && attr->has_default_values()) { return status::success; }
if (attr) {
smask_t attr_mask = smask_t::dropout;
VCHECK_SDPA_UNIMPL(
attr->has_default_values(attr_mask), VERBOSE_UNSUPPORTED_ATTR);
if (!attr->dropout_.has_default_values()) {
CHECK(sdpa_dropout_desc_check(dst_desc, k_desc, attr));
}
}
return status::success;
}
static inline sdpa_desc_t create_sdpa_desc(const memory_desc_t *q_md,
const memory_desc_t *k_md, const memory_desc_t *v_md,
const memory_desc_t *dst_md, const memory_desc_t *attn_mask_md,
const memory_desc_t *scale_md, bool invert_scale, dim_t kv_head_number,
attn_mask_type_t attn_mask_type, alg_kind_t softmax_alg,
prop_kind_t prop, const primitive_attr_t *kq_attr,
const primitive_attr_t *vs_attr) {
auto sdpa_desc = sdpa_desc_t();
sdpa_desc.primitive_kind = primitive_kind::sdpa;
sdpa_desc.q_desc = *q_md;
sdpa_desc.k_desc = *k_md;
sdpa_desc.kq_acc_dt = data_type::f32;
sdpa_desc.vs_acc_dt = data_type::f32;
if (kq_attr) {
sdpa_desc.kq_scales = kq_attr->scales_.get(DNNL_ARG_WEIGHTS);
sdpa_desc.kq_zero_points = kq_attr->zero_points_.get(DNNL_ARG_WEIGHTS);
if (kq_attr->acc_mode_ == accumulation_mode::f16) {
sdpa_desc.kq_acc_dt = data_type::f16;
}
}
if (vs_attr) {
sdpa_desc.vs_scales = vs_attr->scales_.get(DNNL_ARG_WEIGHTS);
sdpa_desc.vs_zero_points = vs_attr->zero_points_.get(DNNL_ARG_WEIGHTS);
if (vs_attr->acc_mode_ == accumulation_mode::f16) {
sdpa_desc.vs_acc_dt = data_type::f16;
}
}
sdpa_desc.v_desc = *v_md;
sdpa_desc.dst_desc = *dst_md;
if (attn_mask_md) sdpa_desc.attn_mask_desc = *attn_mask_md;
sdpa_desc.scale_desc = *scale_md;
sdpa_desc.invert_scale = invert_scale;
sdpa_desc.kv_head_number = kv_head_number;
sdpa_desc.mask_type = attn_mask_type;
sdpa_desc.softmax_alg = softmax_alg;
sdpa_desc.prop_kind = prop;
return sdpa_desc;
}
static inline sdpa_desc_t create_sdpa_desc(const memory_desc_t *q_md,
const memory_desc_t *k_md, const memory_desc_t *v_md,
const memory_desc_t *dst_md, const memory_desc_t *attn_mask_md,
const memory_desc_t *scale_md, const memory_desc_t *diff_q_md,
const memory_desc_t *diff_k_md, const memory_desc_t *diff_v_md,
const memory_desc_t *diff_dst_md, const memory_desc_t *dS_md,
bool invert_scale, dim_t kv_head_number,
attn_mask_type_t attn_mask_type, alg_kind_t softmax_alg) {
auto sdpa_desc = sdpa_desc_t();
sdpa_desc.primitive_kind = primitive_kind::sdpa;
sdpa_desc.q_desc = *q_md;
sdpa_desc.k_desc = *k_md;
sdpa_desc.v_desc = *v_md;
sdpa_desc.dst_desc = *dst_md;
sdpa_desc.diff_dst_desc = *diff_dst_md;
if (dS_md) sdpa_desc.dS_desc = *dS_md;
sdpa_desc.diff_q_desc = *diff_q_md;
sdpa_desc.diff_k_desc = *diff_k_md;
sdpa_desc.diff_v_desc = *diff_v_md;
if (attn_mask_md) sdpa_desc.attn_mask_desc = *attn_mask_md;
sdpa_desc.scale_desc = *scale_md;
sdpa_desc.invert_scale = invert_scale;
sdpa_desc.kv_head_number = kv_head_number;
sdpa_desc.mask_type = attn_mask_type;
sdpa_desc.softmax_alg = softmax_alg;
sdpa_desc.prop_kind = prop_kind::backward;
return sdpa_desc;
}
static inline status_t create_sdpa_pd(
std::shared_ptr<primitive_desc_t> &sdpa_pd_, engine_t *engine,
const memory_desc_t *q_md, const memory_desc_t *k_md,
const memory_desc_t *v_md, const memory_desc_t *dst_md,
const memory_desc_t *attn_mask_md, const memory_desc_t *scale_md,
bool invert_scale, dim_t kv_head_number,
attn_mask_type_t attn_mask_type, alg_kind_t softmax_alg,
prop_kind_t prop, const primitive_attr_t *attr,
const primitive_attr_t *kq_attr = nullptr,
const primitive_attr_t *vs_attr = nullptr) {
CHECK(sdpa_attr_check(
q_md, k_md, v_md, dst_md, engine, attr, kq_attr, vs_attr));
CHECK(sdpa_desc_check(q_md, k_md, v_md, dst_md, attn_mask_md, engine, attr,
kq_attr, vs_attr));
auto sdpa_desc = create_sdpa_desc(q_md, k_md, v_md, dst_md, attn_mask_md,
scale_md, invert_scale, kv_head_number, attn_mask_type, softmax_alg,
prop, kq_attr, vs_attr);
primitive_attr_t sdpa_attr = attr ? *attr : default_attr();
primitive_desc_iterator_t it(
engine, (op_desc_t *)&sdpa_desc, &sdpa_attr, nullptr);
sdpa_pd_ = *(++it);
VCHECK_SDPA_COND(sdpa_pd_, "failed to create the SDPA primitive");
return status::success;
}
static inline status_t create_sdpa_pd(
std::shared_ptr<primitive_desc_t> &sdpa_pd_, engine_t *engine,
const memory_desc_t *q_md, const memory_desc_t *k_md,
const memory_desc_t *v_md, const memory_desc_t *dst_md,
const memory_desc_t *diff_q_md, const memory_desc_t *diff_k_md,
const memory_desc_t *diff_v_md, const memory_desc_t *diff_dst_md,
const memory_desc_t *dS_md, const memory_desc_t *attn_mask_md,
const memory_desc_t *scale_md, bool invert_scale, dim_t kv_head_number,
attn_mask_type_t attn_mask_type, alg_kind_t softmax_alg,
const primitive_attr_t *attr, const primitive_desc_t *hint_fwd_pd,
const primitive_attr_t *kq_attr = nullptr,
const primitive_attr_t *vs_attr = nullptr) {
CHECK(sdpa_attr_check(
q_md, k_md, v_md, dst_md, engine, attr, kq_attr, vs_attr));
CHECK(sdpa_desc_check(q_md, k_md, v_md, dst_md, attn_mask_md, engine, attr,
kq_attr, vs_attr));
auto sdpa_desc = create_sdpa_desc(q_md, k_md, v_md, dst_md, attn_mask_md,
scale_md, diff_q_md, diff_k_md, diff_v_md, diff_dst_md, dS_md,
invert_scale, kv_head_number, attn_mask_type, softmax_alg);
primitive_attr_t sdpa_attr = attr ? *attr : default_attr();
primitive_desc_iterator_t it(
engine, (op_desc_t *)&sdpa_desc, &sdpa_attr, hint_fwd_pd);
sdpa_pd_ = *(++it);
VCHECK_SDPA_COND(sdpa_pd_, "failed to create the backward SDPA primitive");
return status::success;
}
} }
#endif