#include "graph/backend/dnnl/executables/sdpa.hpp"
#include "common/sdpa_test_iface.hpp"
namespace dnnl {
namespace impl {
namespace graph {
namespace dnnl_impl {
sdpa_executable_t::sdpa_executable_t(std::shared_ptr<op_t> &op,
const dnnl::engine &p_engine, pd_cache_t &pd_cache,
const fpmath_t &fpmath, bool use_block_layout)
: with_scale_(op->get_attr<bool>(op_attr::with_scale))
, is_training_(op->get_attr<bool>(op_attr::is_training))
, mask_type_(static_cast<attn_mask_type_t>(
op->get_attr<int64_t>(op_attr::mask_type)))
, with_dropout_(op->get_attr<bool>(op_attr::with_dropout)) {
auto md_q = make_dnnl_memory_desc(op->get_input_logical_tensor(0));
auto md_k = make_dnnl_memory_desc(op->get_input_logical_tensor(1));
auto md_v = make_dnnl_memory_desc(op->get_input_logical_tensor(2));
auto md_dst = make_dnnl_memory_desc(op->get_output_logical_tensor(0));
auto md_scale = dnnl::memory::desc();
size_t idx = 3;
if (with_scale_)
md_scale = make_dnnl_memory_desc(op->get_input_logical_tensor(idx++));
dnnl::memory::desc md_mask;
with_explicit_mask_ = mask_type_ == attn_mask_type::buffer;
if (with_explicit_mask_)
md_mask = make_dnnl_memory_desc(op->get_input_logical_tensor(idx++));
dnnl::primitive_attr attr, qk_attr, vs_attr;
attr.set_scratchpad_mode(dnnl::scratchpad_mode::user);
attr.set_fpmath_mode(static_cast<dnnl::fpmath_mode>(fpmath.mode_));
if (with_dropout_) {
dnnl::memory::desc dropout_mask_desc;
attr.set_dropout(dropout_mask_desc, dnnl::memory::data_type::s64,
true, true);
}
is_invert_scale_ = op->has_attr(op_attr::is_invert_scale)
? op->get_attr<bool>(op_attr::is_invert_scale)
: false;
if (op->has_attr(op_attr::fusion_info)) {
const auto &sdpa_fusion_info
= op->get_attr<fusion_info_t>(op_attr::fusion_info);
qk_attr = make_dnnl_sdpa_primitive_attr(
op, sdpa_fusion_info, attr_type_t::QK);
vs_attr = make_dnnl_sdpa_primitive_attr(
op, sdpa_fusion_info, attr_type_t::VS);
}
qk_attr.set_accumulation_mode(str2accumulation_mode(
op->get_attr<std::string>(op_attr::qk_acc_mode)));
vs_attr.set_accumulation_mode(str2accumulation_mode(
op->get_attr<std::string>(op_attr::vs_acc_mode)));
dim_t kv_head_number = op->get_input_logical_tensor(1).dims[1];
const std::string &softmax_mode = op->get_attr<std::string>(op_attr::mode);
const alg_kind_t softmax_alg = softmax_mode == "inf_as_zero"
? alg_kind::softmax_accurate_inf_as_zero
: alg_kind::softmax_accurate;
const auto prop
= is_training_ ? dnnl_forward_training : dnnl_forward_inference;
dnnl_primitive_desc_t pd = nullptr;
auto ret = sdpa_primitive_desc_create(&pd, p_engine.get(), md_q.get(),
md_k.get(), md_v.get(), md_dst.get(), md_mask.get(), md_scale.get(),
is_invert_scale_, kv_head_number, mask_type_,
static_cast<dnnl_alg_kind_t>(softmax_alg), prop, attr.get(),
qk_attr.get(), vs_attr.get());
if (pd && ret == dnnl_success) {
pd_.reset(pd);
} else {
is_initialized_ = false;
return;
}
dnnl_primitive_t prim = nullptr;
ret = dnnl_primitive_create(&prim, pd_.get());
if (prim && ret == dnnl_success) {
prim_.reset(prim);
is_initialized_ = true;
} else {
is_initialized_ = false;
}
}
void sdpa_executable_t::execute(const stream &stream,
const std::unordered_map<int, memory> &args) const {
UNUSED(stream);
UNUSED(args);
assert(!"sdpa_executable_t::execute() is not implemented on cpu");
}
#ifdef DNNL_WITH_SYCL
std::optional<::sycl::event> sdpa_executable_t::execute_sycl(
const stream &stream, const std::unordered_map<int, memory> &args,
const std::vector<::sycl::event> &deps) const {
std::vector<dnnl_exec_arg_t> c_args;
c_args.reserve(args.size());
for (const auto &a : args)
c_args.push_back({a.first, a.second.get()});
sycl::event return_event;
auto ret = dnnl_sycl_interop_primitive_execute(prim_.get(), stream.get(),
c_args.size(), c_args.data(), &deps, &return_event);
dnnl::error::wrap_c_api(
ret, "could not execute sdpa primitive with sycl runtime");
return return_event;
}
#endif
#if DNNL_GPU_RUNTIME == DNNL_RUNTIME_OCL
cl_event sdpa_executable_t::execute_ocl(const stream &stream,
const std::unordered_map<int, memory> &args,
const std::vector<cl_event> &deps) const {
std::vector<dnnl_exec_arg_t> c_args;
c_args.reserve(args.size());
for (const auto &a : args)
c_args.push_back({a.first, a.second.get()});
const cl_event *c_deps = deps.empty() ? nullptr : deps.data();
cl_event return_event = nullptr;
auto ret = dnnl_ocl_interop_primitive_execute(prim_.get(), stream.get(),
static_cast<int>(c_args.size()), c_args.data(), c_deps,
static_cast<int>(deps.size()), &return_event);
dnnl::error::wrap_c_api(
ret, "could not execute sdpa primitive with ocl runtime");
return return_event;
}
#endif
arg_indices_t sdpa_executable_t::get_arg_indices(const op_t *op) {
arg_indices_t args;
size_t idx = 0;
args.insert({DNNL_ARG_QUERIES, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_KEYS, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_VALUES, {indices_t::type_t::input, idx++}});
if (op->get_attr<bool>(op_attr::with_scale)) {
args.insert({DNNL_ARG_SCALE, {indices_t::type_t::input, idx++}});
}
if (op->get_attr<int64_t>(op_attr::mask_type)
== static_cast<int64_t>(attn_mask_type::buffer)) {
args.insert({DNNL_ARG_ATTN_MASK, {indices_t::type_t::input, idx++}});
}
if (op->get_attr<bool>(op_attr::with_dropout)) {
args.insert({DNNL_ARG_ATTR_DROPOUT_SEED,
{indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_ATTR_DROPOUT_OFFSET,
{indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_ATTR_DROPOUT_PROBABILITY,
{indices_t::type_t::input, idx++}});
}
const auto &sdpa_fusion_info = op->has_attr(op_attr::fusion_info)
? op->get_attr<fusion_info_t>(op_attr::fusion_info)
: fusion_info_t();
if (sdpa_fusion_info.with_runtime_scales(true, DNNL_ARG_KEYS)) {
args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_KEYS,
{indices_t::type_t::input, idx++}});
}
if (sdpa_fusion_info.with_runtime_zero_points(true, DNNL_ARG_KEYS)) {
args.insert({DNNL_ARG_ATTR_ZERO_POINTS | DNNL_ARG_KEYS,
{indices_t::type_t::input, idx++}});
}
if (sdpa_fusion_info.with_runtime_scales(true, DNNL_ARG_VALUES)) {
args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_VALUES,
{indices_t::type_t::input, idx++}});
}
if (sdpa_fusion_info.with_runtime_zero_points(true, DNNL_ARG_VALUES)) {
args.insert({DNNL_ARG_ATTR_ZERO_POINTS | DNNL_ARG_VALUES,
{indices_t::type_t::input, idx++}});
}
args.insert({DNNL_ARG_DST, {indices_t::type_t::output, 0}});
args.insert({DNNL_ARG_SCRATCHPAD, {indices_t::type_t::output, 1}});
if (op->get_attr<bool>(op_attr::is_training)) {
args.insert({DNNL_ARG_WORKSPACE, {indices_t::type_t::output, 2}});
}
return args;
}
sdpa_bwd_executable_t::sdpa_bwd_executable_t(std::shared_ptr<op_t> &op,
const dnnl::engine &p_engine, pd_cache_t &pd_cache,
const fpmath_t &fpmath, bool use_block_layout)
: with_scale_(op->get_attr<bool>(op_attr::with_scale))
, mask_type_(static_cast<attn_mask_type_t>(
op->get_attr<int64_t>(op_attr::mask_type)))
, is_invert_scale_(op->has_attr(op_attr::is_invert_scale)
? op->get_attr<bool>(op_attr::is_invert_scale)
: false)
, with_explicit_mask_(mask_type_ == attn_mask_type::buffer)
, with_dropout_(op->get_attr<bool>(op_attr::with_dropout)) {
auto md_q = make_dnnl_memory_desc(op->get_input_logical_tensor(0));
auto md_k = make_dnnl_memory_desc(op->get_input_logical_tensor(1));
auto md_v = make_dnnl_memory_desc(op->get_input_logical_tensor(2));
auto md_dst = make_dnnl_memory_desc(op->get_input_logical_tensor(3));
auto md_diff_dst = make_dnnl_memory_desc(op->get_input_logical_tensor(5));
auto md_diff_q = make_dnnl_memory_desc(op->get_output_logical_tensor(0));
auto md_diff_k = make_dnnl_memory_desc(op->get_output_logical_tensor(1));
auto md_diff_v = make_dnnl_memory_desc(op->get_output_logical_tensor(2));
dnnl::memory::desc md_scale, md_attn_mask, md_dS;
size_t idx = 6;
if (with_scale_) {
md_scale = make_dnnl_memory_desc(op->get_input_logical_tensor(idx++));
}
if (with_explicit_mask_) {
md_attn_mask
= make_dnnl_memory_desc(op->get_input_logical_tensor(idx++));
if (op->num_outputs() > 4) {
md_dS = make_dnnl_memory_desc(op->get_output_logical_tensor(4));
}
}
const auto &sdpa_fusion_info = op->has_attr(op_attr::fusion_info)
? op->get_attr<fusion_info_t>(op_attr::fusion_info)
: fusion_info_t();
dnnl::primitive_attr attr, qk_attr, vs_attr;
if (op->has_attr(op_attr::fusion_info)) {
qk_attr = make_dnnl_sdpa_primitive_attr(
op, sdpa_fusion_info, attr_type_t::QK);
vs_attr = make_dnnl_sdpa_primitive_attr(
op, sdpa_fusion_info, attr_type_t::VS);
}
qk_attr.set_accumulation_mode(str2accumulation_mode(
op->get_attr<std::string>(op_attr::qk_acc_mode)));
vs_attr.set_accumulation_mode(str2accumulation_mode(
op->get_attr<std::string>(op_attr::vs_acc_mode)));
attr.set_scratchpad_mode(dnnl::scratchpad_mode::user);
attr.set_fpmath_mode(static_cast<dnnl::fpmath_mode>(fpmath.mode_));
if (with_dropout_) {
dnnl::memory::desc dropout_mask_desc;
attr.set_dropout(dropout_mask_desc, dnnl::memory::data_type::s64,
true, true);
}
dim_t kv_head_number = op->get_input_logical_tensor(1).dims[1];
const alg_kind_t softmax_alg = alg_kind::softmax_accurate_inf_as_zero;
dnnl_primitive_desc_t hint_pd = nullptr;
auto ret = sdpa_primitive_desc_create(&hint_pd, p_engine.get(), md_q.get(),
md_k.get(), md_v.get(), md_dst.get(), md_attn_mask.get(),
md_scale.get(), is_invert_scale_, kv_head_number, mask_type_,
static_cast<dnnl_alg_kind_t>(softmax_alg), dnnl_forward_training,
attr.get(), qk_attr.get(), vs_attr.get());
if (hint_pd && ret == dnnl_success) {
hint_pd_.reset(hint_pd);
} else {
is_initialized_ = false;
return;
}
dnnl_primitive_desc_t pd = nullptr;
ret = sdpa_primitive_desc_create(&pd, p_engine.get(), md_q.get(),
md_k.get(), md_v.get(), md_dst.get(), md_attn_mask.get(),
md_scale.get(), md_diff_q.get(), md_diff_k.get(), md_diff_v.get(),
md_diff_dst.get(), md_dS.get(), is_invert_scale_, kv_head_number,
mask_type_, static_cast<dnnl_alg_kind_t>(softmax_alg), attr.get(),
hint_pd_.get());
if (pd && ret == dnnl_success) {
pd_.reset(pd);
} else {
is_initialized_ = false;
return;
}
dnnl_primitive_t prim = nullptr;
ret = dnnl_primitive_create(&prim, pd_.get());
if (prim && ret == dnnl_success) {
prim_.reset(prim);
is_initialized_ = true;
} else {
is_initialized_ = false;
}
}
void sdpa_bwd_executable_t::execute(const stream &stream,
const std::unordered_map<int, memory> &args) const {
UNUSED(stream);
UNUSED(args);
assert(!"sdpa_bwd_executable_t::execute() is not implemented on cpu");
}
#ifdef DNNL_WITH_SYCL
std::optional<::sycl::event> sdpa_bwd_executable_t::execute_sycl(
const stream &stream, const std::unordered_map<int, memory> &args,
const std::vector<::sycl::event> &deps) const {
std::vector<dnnl_exec_arg_t> c_args;
c_args.reserve(args.size());
for (const auto &a : args)
c_args.push_back({a.first, a.second.get()});
sycl::event return_event;
auto ret = dnnl_sycl_interop_primitive_execute(prim_.get(), stream.get(),
c_args.size(), c_args.data(), &deps, &return_event);
dnnl::error::wrap_c_api(
ret, "could not execute sdpa backward with sycl runtime");
return return_event;
}
#endif
#if DNNL_GPU_RUNTIME == DNNL_RUNTIME_OCL
cl_event sdpa_bwd_executable_t::execute_ocl(const stream &stream,
const std::unordered_map<int, memory> &args,
const std::vector<cl_event> &deps) const {
std::vector<dnnl_exec_arg_t> c_args;
c_args.reserve(args.size());
for (const auto &a : args)
c_args.push_back({a.first, a.second.get()});
const cl_event *c_deps = deps.empty() ? nullptr : deps.data();
cl_event return_event = nullptr;
auto ret = dnnl_ocl_interop_primitive_execute(prim_.get(), stream.get(),
static_cast<int>(c_args.size()), c_args.data(), c_deps,
static_cast<int>(deps.size()), &return_event);
dnnl::error::wrap_c_api(
ret, "could not execute sdpa backward with ocl runtime");
return return_event;
}
#endif
arg_indices_t sdpa_bwd_executable_t::get_arg_indices(const op_t *op) {
arg_indices_t args;
size_t idx = 0;
args.insert({DNNL_ARG_QUERIES, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_KEYS, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_VALUES, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_DST, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_WORKSPACE, {indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_DIFF_DST, {indices_t::type_t::input, idx++}});
if (op->get_attr<bool>(op_attr::with_scale)) {
args.insert({DNNL_ARG_SCALE, {indices_t::type_t::input, idx++}});
}
if (op->get_attr<int64_t>(op_attr::mask_type)
== static_cast<int64_t>(attn_mask_type::buffer)) {
args.insert({DNNL_ARG_ATTN_MASK, {indices_t::type_t::input, idx++}});
}
if (op->get_attr<bool>(op_attr::with_dropout)) {
args.insert({DNNL_ARG_ATTR_DROPOUT_SEED,
{indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_ATTR_DROPOUT_OFFSET,
{indices_t::type_t::input, idx++}});
args.insert({DNNL_ARG_ATTR_DROPOUT_PROBABILITY,
{indices_t::type_t::input, idx++}});
}
args.insert({DNNL_ARG_DIFF_QUERIES, {indices_t::type_t::output, 0}});
args.insert({DNNL_ARG_DIFF_KEYS, {indices_t::type_t::output, 1}});
args.insert({DNNL_ARG_DIFF_VALUES, {indices_t::type_t::output, 2}});
args.insert({DNNL_ARG_SCRATCHPAD, {indices_t::type_t::output, 3}});
if (op->num_outputs() > 4) {
args.insert({DNNL_ARG_DS, {indices_t::type_t::output, 4}});
}
return args;
}
} } } }