#include "gpu/intel/rnn/utils.hpp"
#include "common/c_types_map.hpp"
#include "common/math_utils.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace rnn {
namespace utils {
using namespace intel::gpu_utils;
using namespace intel::compute;
using namespace prop_kind;
using namespace data_type;
bool is_ldigo(const memory_desc_wrapper &md) {
if (md.format_kind() != format_kind::blocked) return false;
auto blk = md.blocking_desc();
auto str = blk.strides;
auto dims = md.dims();
return md.ndims() == 5 && blk.inner_nblks == 0 && str[4] == 1
&& str[3] == dims[4] && str[1] == str[2] * dims[2]
&& str[0] == str[1] * dims[1];
}
bool is_ldgoi(const memory_desc_wrapper &md) {
if (md.format_kind() != format_kind::blocked) return false;
auto blk = md.blocking_desc();
auto str = blk.strides;
auto dims = md.dims();
return md.ndims() == 5 && blk.inner_nblks == 0 && str[2] == 1
&& str[3] == dims[4] * str[4] && str[1] == str[3] * dims[3]
&& str[0] == str[1] * dims[1];
}
void init_conf(conf_t &conf, const desc_t &rd,
const memory_desc_wrapper &src_layer_d,
const memory_desc_wrapper &src_iter_d,
const memory_desc_wrapper &weights_layer_d,
const memory_desc_wrapper &weights_iter_d,
const memory_desc_wrapper &dst_layer_d,
const memory_desc_wrapper &dst_iter_d,
const memory_desc_wrapper &diff_dst_layer_d,
const memory_desc_wrapper &bias_d, data_type_t acc_data_t,
const device_info_t &device_info) {
bool is_xe_hpc = device_info.gpu_arch() == gpu_arch_t::xe_hpc;
conf = utils::zero<decltype(conf)>();
conf.is_fwd = utils::one_of(rd.prop_kind, prop_kind::forward_training,
prop_kind::forward_inference);
conf.is_training = utils::one_of(
rd.prop_kind, prop_kind::forward_training, prop_kind::backward);
conf.is_lbr = rd.cell_kind == dnnl_lbr_gru;
conf.is_vanilla_gru = rd.cell_kind == dnnl_vanilla_gru;
conf.arch_ld = is_xe_hpc ? 128 : 64;
switch (rd.direction) {
case dnnl_unidirectional_left2right: conf.exec_dir = l2r; break;
case dnnl_unidirectional_right2left: conf.exec_dir = r2l; break;
case dnnl_bidirectional_concat: conf.exec_dir = bi_concat; break;
case dnnl_bidirectional_sum: conf.exec_dir = bi_sum; break;
default: break;
}
if (everyone_is(f32, src_layer_d.data_type(), dst_layer_d.data_type(),
weights_layer_d.data_type()))
conf.dt_conf = all_f32;
else if (everyone_is(bf16, src_layer_d.data_type(), dst_layer_d.data_type(),
weights_layer_d.data_type()))
conf.dt_conf = all_bf16;
else if (everyone_is(f16, src_layer_d.data_type(), dst_layer_d.data_type(),
weights_layer_d.data_type()))
conf.dt_conf = all_f16;
else if (dst_layer_d.data_type() == u8) {
if (IMPLICATION(src_iter_d.md_, src_iter_d.data_type() == u8))
conf.dt_conf = u8u8u8u8;
else
conf.dt_conf = f32u8f32u8;
} else {
if (IMPLICATION(src_iter_d.md_, src_iter_d.data_type() == u8))
conf.dt_conf = u8u8u8f32;
else
conf.dt_conf = f32u8f32f32;
}
conf.is_int8 = !one_of(conf.dt_conf, all_f32, all_f16, all_bf16);
conf.aux_data_type
= acc_data_t == data_type::f16 ? data_type::f16 : data_type::f32;
conf.diff_data_type = diff_dst_layer_d.data_type();
conf.acc_data_type = acc_data_t;
conf.acc_data_type_elsz = types::data_type_size(acc_data_t);
conf.wei_layer_type = weights_layer_d.data_type();
conf.wei_iter_type = weights_iter_d.data_type();
conf.n_layer = weights_layer_d.dims()[0];
conf.n_iter = src_layer_d.dims()[0];
conf.n_dir = weights_layer_d.dims()[1];
conf.n_gates = weights_layer_d.dims()[3];
conf.n_states = rd.cell_kind == dnnl_vanilla_lstm ? 2 : 1;
conf.n_bias = conf.n_gates + conf.is_lbr;
conf.mb = src_layer_d.dims()[1];
conf.sic = weights_iter_d.dims()[2];
conf.slc = weights_layer_d.dims()[2];
conf.dhc = weights_layer_d.dims()[4];
conf.dlc = dst_layer_d.dims()[2];
conf.wic = nstl::max(conf.slc, nstl::max(conf.sic, conf.dhc));
conf.gates_ld = conf.dhc * conf.n_gates;
conf.n_parts_weights_layer = 1;
conf.parts_weights_layer[0] = conf.n_gates;
conf.parts_weights_layer[1] = 0;
conf.n_parts_weights_iter = conf.is_vanilla_gru ? 2 : 1;
conf.parts_weights_iter[0] = conf.is_vanilla_gru ? 2 : conf.n_gates;
conf.parts_weights_iter[1] = conf.is_vanilla_gru ? 1 : 0;
conf.n_parts_bias = 1;
conf.parts_bias[0] = conf.n_bias;
conf.parts_bias[1] = 0;
bool is_gru = utils::one_of(
rd.cell_kind, alg_kind::vanilla_gru, alg_kind::lbr_gru);
auto dst_layer_is_trivial_stride = dst_layer_d.dims()[0] <= 1
|| dst_layer_d.dims()[1] <= 1
|| (dst_layer_d.blocking_desc().strides[0]
== (dst_layer_d.blocking_desc().strides[1] * conf.mb));
dim_t merge_scratch_size_estimate = conf.gates_ld * conf.mb * conf.n_iter;
bool is_small_scratch = merge_scratch_size_estimate < 256 * 1024 * 1024;
conf.merge_gemm_layer = dev_getenv("merge_gemm_layer",
is_small_scratch); conf.merge_gemm_iter = dev_getenv("merge_gemm_iter",
is_small_scratch && dst_layer_is_trivial_stride
&& !(conf.is_fwd || is_gru));
if (conf.is_fwd) {
bool can_fuse_gemm = !conf.is_int8
&& conf.wei_iter_type == conf.wei_layer_type && conf.is_fwd
&& utils::one_of(rd.cell_kind, alg_kind::vanilla_rnn,
alg_kind::vanilla_lstm, alg_kind::lbr_gru);
bool tail_dhc = conf.dhc % device_info.min_subgroup_size() != 0;
dim_t fuse_gemm_limit = [&]() {
const dim_t work_threshold = tail_dhc ? 512 : 1024;
return work_threshold * device_info.eu_count()
* device_info.max_subgroup_size(conf.acc_data_type);
}();
int min_k = nstl::min((int)conf.sic, (int)conf.slc);
int ideal_k = math::lcm((int)device_info.min_subgroup_size(), min_k);
if (rd.cell_kind == alg_kind::vanilla_lstm) {
min_k = (min_k <= 256) ? 160 : 256;
} else {
min_k = device_info.mayiuse_systolic() ? 64 : 256;
}
dim_t k_limit = tail_dhc ? 50 : nstl::min(min_k, ideal_k);
auto is_dense_dst_c = [](const memory_desc_wrapper &md) {
if (md.format_kind() == format_kind::any) return true;
if (md.format_kind() != format_kind::blocked) return false;
if (md.dims()[4] == 1) return true;
if (md.blocking_desc().strides[4] == 1) return true;
return false;
};
conf.cell_fusion.gemm_iter
= dev_getenv("fuse_gemm_iter",
!conf.merge_gemm_iter
&& conf.dhc * conf.sic * conf.mb
* conf.n_gates
< fuse_gemm_limit
&& conf.sic <= k_limit
&& is_dense_dst_c(weights_layer_d))
&& can_fuse_gemm;
conf.cell_fusion.gemm_layer
= dev_getenv("fuse_gemm_layer",
conf.cell_fusion.gemm_iter && !conf.merge_gemm_layer
&& conf.dhc * conf.slc * conf.mb
* conf.n_gates
< fuse_gemm_limit
&& conf.slc <= k_limit
&& is_dense_dst_c(weights_iter_d))
&& can_fuse_gemm;
gpu_assert(IMPLICATION(
conf.cell_fusion.gemm_layer, conf.cell_fusion.gemm_iter));
bool can_iter_loop = conf.cell_fusion.gemm_iter
&& (conf.merge_gemm_layer || conf.cell_fusion.gemm_layer);
const int loop_all = 0;
conf.iter_loop = dev_getenv("iter_loop", can_iter_loop ? loop_all : 1);
if (conf.iter_loop == loop_all) conf.iter_loop = conf.n_iter;
conf.dhc_loop = dev_getenv("dhc_loop", conf.iter_loop ? loop_all : 1);
if (conf.dhc_loop == loop_all) conf.dhc_loop = conf.dhc;
gpu_assert(IMPLICATION(conf.iter_loop, conf.dhc_loop == conf.dhc));
} else {
conf.iter_loop = 1;
conf.dhc_loop = 1;
}
conf.copy_bias = conf.is_int8;
conf.use_workspace = conf.is_training;
conf.src_data_type = src_layer_d.data_type();
conf.input_data_type = src_iter_d.data_type();
conf.bias_data_type = bias_d.data_type();
conf.dst_data_type = dst_layer_d.data_type();
conf.output_data_type = dst_iter_d.data_type();
if (conf.input_data_type == data_type::undef)
conf.input_data_type = conf.src_data_type;
if (conf.output_data_type == data_type::undef)
conf.output_data_type = conf.dst_data_type;
}
void init_test_mode(conf_t &conf, const primitive_attr_t &attr) {
conf.is_testmode = attr.rnn_tparams_.test_mode_;
conf.tm_ngates = attr.rnn_tparams_.ngates_;
conf.tm_cscale = attr.rnn_tparams_.cscale_;
}
void set_conf(conf_t &conf, const desc_t &rd,
const memory_desc_wrapper &src_layer_d,
const memory_desc_wrapper &diff_src_layer_d,
const memory_desc_wrapper &diff_dst_layer_d,
const memory_desc_wrapper &weights_layer_d,
const memory_desc_wrapper &weights_iter_d,
const memory_desc_wrapper &diff_weights_layer_d,
const memory_desc_wrapper &diff_weights_iter_d) {
const bool is_fwd = conf.is_fwd;
const bool is_bwd = !conf.is_fwd;
dim_t aux_elsz = into<dim_t>(types::data_type_size(conf.aux_data_type));
conf.ws_states_elsz = types::data_type_size(conf.src_data_type);
conf.scratch_gates_elsz = types::data_type_size(conf.acc_data_type);
conf.scratch_diff_gates_elsz
= is_bwd ? types::data_type_size(conf.src_data_type) : 0;
conf.states_ws_ld = get_good_ld(conf.arch_ld,
nstl::max(conf.slc, nstl::max(conf.sic, conf.dhc)),
conf.ws_states_elsz);
conf.gates_ws_ld = get_good_ld(conf.arch_ld, conf.gates_ld, aux_elsz);
conf.scratch_diff_states_ld = get_good_ld(conf.arch_ld,
nstl::max(conf.slc, nstl::max(conf.sic, conf.dhc)), sizeof(float),
utils::everyone_is(conf.slc, conf.sic, conf.dhc)
&& conf.n_layer * conf.n_dir * conf.n_iter * conf.mb
> 128 * 1024);
conf.scratch_gates_ld
= get_good_ld(conf.arch_ld, conf.gates_ld, conf.scratch_gates_elsz);
conf.scratch_diff_gates_ld = is_bwd
? get_good_ld(
conf.arch_ld, conf.gates_ld, conf.scratch_diff_gates_elsz)
: 0;
bool is_lstm = rd.cell_kind == dnnl_vanilla_lstm;
bool require_copy_src_layer = [&]() {
auto &strides = src_layer_d.strides();
auto &pdims = src_layer_d.padded_dims();
auto dt_size = types::data_type_size(src_layer_d.data_type());
if (pdims[0] > 1 && (strides[0] * dt_size) % 8) return true;
if (conf.merge_gemm_layer) {
constexpr int iter_dim = 0, mb_dim = 1;
if (pdims[iter_dim] > 1 && pdims[mb_dim] > 1
&& (strides[iter_dim] != strides[mb_dim] * conf.mb))
return true;
if (conf.exec_dir != utils::l2r) return true;
}
if (pdims[1] == 1) return true;
return false;
}();
bool prefer_copy_src_layer = [&]() {
auto &strides = src_layer_d.strides();
auto &pdims = src_layer_d.padded_dims();
auto dt_size = types::data_type_size(src_layer_d.data_type());
if (pdims[1] == 1 || strides[1] == conf.gates_ws_ld
|| (strides[1] % 64 == 0))
return false;
const dim_t data_reuse = conf.n_dir * (conf.is_training ? 2 : 1);
if (data_reuse < 2) return false;
if (src_layer_d.nelems(true) * dt_size >= 1024 * 1024 * 1024)
return false;
return true;
}();
bool copy_src_layer = dev_getenv("copy_src_layer", prefer_copy_src_layer)
|| require_copy_src_layer;
bool require_copy_diff_dst_layer = [&]() {
if (is_fwd) return false;
auto &strides = diff_dst_layer_d.strides();
auto &pdims = diff_dst_layer_d.padded_dims();
auto dt_size = types::data_type_size(diff_dst_layer_d.data_type());
if (pdims[0] > 1 && (strides[0] * dt_size) % 8) return true;
if (conf.merge_gemm_layer) {
constexpr int iter_dim = 0, mb_dim = 1;
if (pdims[iter_dim] > 1 && pdims[mb_dim] > 1
&& (strides[iter_dim] != strides[mb_dim] * conf.mb))
return true;
if (conf.exec_dir != utils::r2l) return true;
}
if (pdims[1] == 1) return true;
return false;
}();
bool copy_diff_dst_layer = dev_getenv("copy_diff_dst_layer", false)
|| require_copy_diff_dst_layer;
bool require_copy_diff_src_layer = [&]() {
if (is_fwd) return false;
if (conf.merge_gemm_iter || conf.merge_gemm_layer) return true;
if (conf.n_dir > 1) return true;
return false;
}();
bool copy_diff_src_layer = dev_getenv("copy_diff_src_layer", false)
|| require_copy_diff_src_layer;
conf.copy_src_layer = copy_src_layer;
conf.copy_diff_dst_layer = copy_diff_dst_layer;
conf.copy_diff_src_layer = copy_diff_src_layer;
conf.ws_states_cell_size
= conf.mb * conf.states_ws_ld * conf.ws_states_elsz;
conf.ws_states_size = (copy_src_layer ? conf.n_layer + 1 : conf.n_layer)
* conf.n_dir * (conf.n_iter + 1) * conf.ws_states_cell_size;
conf.ws_c_states_cell_size
= is_lstm ? conf.mb * conf.states_ws_ld * aux_elsz : 0;
conf.ws_c_states_size = is_lstm ? conf.n_layer * conf.n_dir
* (conf.n_iter + 1) * conf.ws_c_states_cell_size
: 0;
auto scratch_diff_n_states
= copy_diff_dst_layer || copy_diff_src_layer || conf.n_layer != 1
? conf.n_states + 1
: conf.n_states;
bool have_result_layer = copy_diff_src_layer || conf.n_layer > 1;
auto scratch_diff_n_layer
= conf.n_layer - 1 + copy_diff_dst_layer + have_result_layer;
auto scratch_diff_n_cells = is_bwd
? (scratch_diff_n_layer > 0
? scratch_diff_n_layer * (conf.n_iter + 1)
: 2)
* scratch_diff_n_states * conf.n_dir
: 0;
conf.scratch_diff_states_size = scratch_diff_n_cells * conf.mb
* conf.scratch_diff_states_ld * aux_elsz;
conf.ws_gates_cell_size = conf.mb * conf.gates_ws_ld * aux_elsz;
conf.ws_gates_size = conf.is_training
? (conf.n_layer * conf.n_dir * conf.n_iter
* conf.ws_gates_cell_size)
: 0;
bool supports_recompute_gates
= utils::one_of(rd.cell_kind, alg_kind::vanilla_lstm,
alg_kind::vanilla_rnn)
&& conf.is_training;
bool prefer_recompute_gates = conf.ws_gates_size >= 512 * 1024 * 1024;
conf.recompute_gates = dev_getenv("recompute_gates", prefer_recompute_gates)
&& supports_recompute_gates;
if (conf.recompute_gates) conf.ws_gates_size = 0;
conf.n_iter_scratch_gates
= (conf.merge_gemm_layer || conf.merge_gemm_iter) ? conf.n_iter : 1;
bool need_scratch_gates = is_fwd
|| (conf.recompute_gates
&& conf.scratch_gates_elsz != conf.scratch_diff_gates_elsz);
conf.scratch_gates_size = need_scratch_gates ? conf.n_iter_scratch_gates
* conf.mb * conf.scratch_gates_ld * conf.scratch_gates_elsz
: 0;
conf.scratch_diff_gates_size = is_bwd ? conf.n_iter_scratch_gates * conf.mb
* conf.scratch_diff_gates_ld * conf.scratch_diff_gates_elsz
: 0;
conf.scratch_dhG1_size = (rd.cell_kind == alg_kind::vanilla_gru && is_bwd)
? conf.mb * conf.scratch_diff_states_ld * sizeof(float)
: 0;
conf.ws_bias_size
= conf.n_layer * conf.n_dir * conf.n_bias * conf.dhc * aux_elsz;
conf.scratch_cell_size = [&]() {
if (conf.is_lbr && is_fwd) {
return conf.mb * conf.scratch_gates_ld * conf.scratch_gates_elsz;
} else if (conf.is_lbr && is_bwd) {
return conf.mb * conf.scratch_diff_gates_ld
* conf.scratch_diff_gates_elsz;
} else if (rd.cell_kind == alg_kind::vanilla_gru && is_bwd) {
return conf.mb * conf.states_ws_ld * conf.ws_states_elsz;
} else {
return static_cast<dim_t>(0);
}
}();
conf.ws_per_cell = conf.is_lbr * conf.mb * conf.dhc * aux_elsz;
conf.ws_grid_comp_size = conf.is_lbr * conf.is_training * conf.n_layer
* conf.n_dir * conf.n_iter * conf.ws_per_cell;
set_workspace_offsets(conf, conf.ws_gates_offset, conf.ws_states_offset,
conf.ws_c_state_offset, conf.ws_grid_comp_offset,
conf.ws_bias_offset);
}
dim_t get_good_ld(
dim_t arch_ld, dim_t dim, dim_t sizeof_dt, bool ignore_assoc) {
dim_t ld = rnd_up(dim, arch_ld / sizeof_dt);
return (ld % 256 == 0) && !ignore_assoc ? ld + arch_ld / sizeof_dt : ld;
}
dim_t set_workspace_offsets(const conf_t &conf, dim_t &ws_gates_offset,
dim_t &ws_states_offset, dim_t &ws_c_states_offset,
dim_t &ws_grid_comp_offset, dim_t &ws_bias_offset) {
const dim_t page_size = 4096;
dim_t current_offset = 0;
#define register_space(a) \
do { \
current_offset = utils::rnd_up(current_offset, page_size); \
CONCAT2(a, _offset) = current_offset; \
current_offset += conf.CONCAT2(a, _size); \
} while (false)
register_space(ws_gates);
register_space(ws_states);
register_space(ws_c_states);
register_space(ws_grid_comp);
ws_bias_offset = 0;
if (conf.copy_bias) { register_space(ws_bias); }
return current_offset;
}
dim_t get_workspace_size(const conf_t &conf) {
dim_t ws_gates_offset, ws_states_offset, ws_c_states_offset,
ws_grid_comp_offset, ws_bias_offset;
return set_workspace_offsets(conf, ws_gates_offset, ws_states_offset,
ws_c_states_offset, ws_grid_comp_offset, ws_bias_offset);
}
status_t set_good_strides(
dim_t ld_, memory_desc_t &weights_md, format_tag_t tag) {
auto &strides = weights_md.format_desc.blocking.strides;
auto dims = weights_md.dims;
using namespace format_tag;
if (tag == ldigo) {
strides[2] = utils::get_good_ld(
ld_, strides[2], types::data_type_size(weights_md.data_type));
strides[1] = dims[2] * strides[2];
strides[0] = dims[1] * strides[1];
} else if (tag == ldgoi) {
strides[4] = utils::get_good_ld(
ld_, strides[4], types::data_type_size(weights_md.data_type));
strides[3] = dims[4] * strides[4];
strides[1] = dims[3] * strides[3];
strides[0] = dims[1] * strides[1];
} else
return status::unimplemented;
return status::success;
}
status_t set_weights_desc(memory_desc_t &weights_md, const conf_t &conf) {
using namespace format_tag;
if (weights_md.format_kind == format_kind::any) {
CHECK(memory_desc_init_by_tag(weights_md, conf.is_fwd ? ldigo : ldgoi));
CHECK(set_good_strides(
conf.arch_ld, weights_md, conf.is_fwd ? ldigo : ldgoi));
if (conf.is_fwd && conf.is_int8) {
weights_md.extra.flags = memory_extra_flags::rnn_u8s8_compensation;
weights_md.extra.compensation_mask = 27; }
return status::success;
} else if (weights_md.format_kind != format_kind::blocked) {
return status::unimplemented;
} else if (conf.is_fwd && conf.is_int8) {
return status::unimplemented;
}
return status::success;
}
const memory_storage_t &get_storage(const memory_storage_t *storage) {
return storage ? *storage : memory_storage_t::empty_storage();
}
const memory_storage_t &get_storage(
const std::unique_ptr<memory_storage_t> &storage) {
return get_storage(storage.get());
}
} } } } } }