#ifndef GPU_INTEL_COMPUTE_DISPATCH_REUSABLE_HPP
#define GPU_INTEL_COMPUTE_DISPATCH_REUSABLE_HPP
#include <string>
#include <vector>
#include <unordered_map>
#include "common/c_types_map.hpp"
#include "common/memory_desc_wrapper.hpp"
#include "common/serialization.hpp"
#include "gpu/intel/block_structure.hpp"
#include "gpu/intel/compute/block_manipulation.hpp"
#include "gpu/intel/compute/dispatch.hpp"
#include "gpu/intel/compute/kernel_ctx.hpp"
#include "gpu/intel/compute/types_interop.hpp"
#include "gpu/intel/compute/utils.hpp"
#include "gpu/intel/engine.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace compute {
#define MAX_REGISTERED_BUFFERS 4
#define MAX_DIM_NAME_LENGTH 15
#define MAX_BUFFER_NAME_LENGTH 7
static_assert(MAX_REGISTERED_BUFFERS * (MAX_BUFFER_NAME_LENGTH + 1) % 4 == 0,
"Padding will be introduced due to registered buffers.");
enum class gws_op_t : uint32_t {
ZERO,
SOLO,
FIRST,
MOD,
SOLO_BLOCK,
FIRST_BLOCK,
MOD_BLOCK,
UNDEF,
};
inline std::string to_string(gws_op_t op) {
switch (op) {
#define CASE(x) \
case gws_op_t::x: return #x;
CASE(ZERO);
CASE(SOLO);
CASE(FIRST);
CASE(MOD);
CASE(SOLO_BLOCK);
CASE(FIRST_BLOCK);
CASE(MOD_BLOCK);
CASE(UNDEF);
#undef CASE
}
return "invalid";
#undef CASE
}
struct gws_indexing_term_t {
struct compile_params_t {
compile_params_t() = default;
compile_params_t(gws_op_t op, size_t gws_idx)
: op(op), gws_idx(gws_idx) {};
bool operator==(const compile_params_t &other) const {
return op == other.op && gws_idx == other.gws_idx;
}
std::string str() const {
stringstream_t ss;
ss << "<compile_params_t op=" << to_string(op)
<< ", gws_idx=" << gws_idx << ">";
return ss.str();
}
gws_op_t op;
uint8_t padding[4] = {0, 0, 0, 0};
uint64_t gws_idx;
};
struct runtime_params_t {
runtime_params_t() = default;
runtime_params_t(dim_t size, stride_t stride, dim_t block)
: size(size), stride(stride), block(block) {};
bool operator==(const runtime_params_t &other) const {
return size == other.size && stride == other.stride
&& block == other.block;
}
dim_t size;
stride_t stride;
dim_t block;
};
gws_indexing_term_t() = default;
bool operator==(const gws_indexing_term_t &other) const {
return compile_params_ == other.compile_params_
&& runtime_params_ == other.runtime_params_;
}
gws_indexing_term_t(gws_op_t op, size_t gws_idx, dim_t size,
stride_t stride, dim_t block)
: compile_params_(op, gws_idx), runtime_params_(size, stride, block) {};
std::string str() const {
stringstream_t ss;
ss << "<gws_indexing_term_t op=" << to_string(compile_params_.op)
<< ", gws_idx=" << compile_params_.gws_idx
<< ", size=" << runtime_params_.size
<< ", stride=" << runtime_params_.stride
<< ", block=" << runtime_params_.block << ">";
return ss.str();
}
const compile_params_t &compile_params() const { return compile_params_; }
const runtime_params_t &runtime_params() const { return runtime_params_; }
compile_params_t compile_params_;
runtime_params_t runtime_params_;
};
struct gws_term_list_t {
size_t append(const gws_indexing_term_t &term) {
size_t idx = add_term(term);
gpu_assert(idx <= MAX_INDEXING_TERMS);
return idx;
}
const gws_indexing_term_t &operator[](size_t idx) const {
return terms[idx];
}
size_t size() const { return terms.size(); }
std::vector<gws_indexing_term_t> terms;
std::string str() const {
ostringstream_t ss;
for (size_t i = 0; i < terms.size(); i++) {
ss << terms[i].str() << std::endl;
}
return ss.str();
}
private:
size_t add_term(const gws_indexing_term_t &term) {
for (size_t i = 0; i < terms.size(); i++) {
const gws_indexing_term_t &existing = terms[i];
if (term == existing) return i;
}
size_t ret = terms.size();
terms.emplace_back(term);
return ret;
}
};
struct subgroup_data_t {
public:
subgroup_data_t() = default;
subgroup_data_t(size_t buffer_idx, size_t size)
: use_subgroup(true), buffer_idx_(buffer_idx), size_(size) {}
#if __cplusplus >= 202002L
bool operator==(const subgroup_data_t &) const = default;
#endif
bool used() const { return use_subgroup; }
size_t buffer_idx() const {
gpu_assert(use_subgroup);
return buffer_idx_;
}
size_t size() const {
gpu_assert(use_subgroup);
return size_;
}
protected:
bool use_subgroup = false;
int8_t padding[7] = {0};
uint64_t buffer_idx_ = 0;
uint64_t size_ = 0;
};
struct dispatch_compile_params_t {
dispatch_compile_params_t() = default;
#if __cplusplus >= 202002L
bool operator==(const dispatch_compile_params_t &) const = default;
#endif
void def_kernel_macros(
kernel_ctx_t &kernel_ctx, const char *suffix = "DEFAULT") const;
std::string str() const {
ostringstream_t ss;
ss << "dispatch_compile_params_t<num_terms=" << num_terms;
ss << ": [";
for (size_t i = 0; i < static_cast<size_t>(num_terms); i++) {
ss << terms[i].str() << ", ";
}
ss << "], num_buffers=" << num_buffers;
ss << ": [";
for (size_t i = 0; i < num_buffers; i++) {
ss << buffer_names[i] << " - [";
for (size_t j = 0; j < buffer_num_terms[i]; j++) {
ss << buffer_term_index[i][j] << "/";
}
ss << "], ";
}
ss << "]>";
return ss.str();
}
subgroup_data_t subgroup;
int32_t num_terms = 0;
bool use_int32_offset = false;
bool require_stateless_addressing = true;
uint8_t padding[2] = {0};
gws_indexing_term_t::compile_params_t terms[MAX_INDEXING_TERMS]
= {{gws_op_t::SOLO, 0}};
uint64_t num_buffers = 0;
char buffer_names[MAX_REGISTERED_BUFFERS][MAX_BUFFER_NAME_LENGTH + 1]
= {{'\0'}};
uint64_t buffer_term_index[MAX_REGISTERED_BUFFERS][MAX_INDEXING_TERMS]
= {{0}};
uint64_t buffer_num_terms[MAX_REGISTERED_BUFFERS] = {0};
data_type_t buffer_types[MAX_REGISTERED_BUFFERS] = {data_type::undef};
};
DNNL_ASSERT_TRIVIALLY_SERIALIZABLE(dispatch_compile_params_t);
class dispatch_runtime_params_t {
public:
dispatch_runtime_params_t() = default;
dispatch_runtime_params_t(
const nd_range_t &nd_range, const gws_term_list_t &terms)
: nd_range(nd_range), num_terms(terms.size()) {
for (size_t i = 0; i < num_terms; i++) {
const gws_indexing_term_t::runtime_params_t ¶ms
= terms[i].runtime_params();
rt_params.sizes[i] = params.size;
rt_params.strides[i] = static_cast<int64_t>(params.stride);
rt_params.blocks[i] = params.block;
}
for (size_t i = num_terms; i < MAX_INDEXING_TERMS; i++) {
rt_params.sizes[i] = 1;
rt_params.strides[i] = 1;
rt_params.blocks[i] = 1;
}
}
dispatch_gws_rt_params_t get() const { return rt_params; }
std::string str() const {
stringstream_t ss;
ss << "<dispatch_runtime_params_t (size/stride/block): ";
for (size_t i = 0; i < num_terms; i++) {
ss << rt_params.sizes[i] << "/" << rt_params.strides[i] << "/"
<< rt_params.blocks[i] << ", ";
}
ss << ">";
return ss.str();
}
nd_range_t nd_range;
private:
size_t num_terms = 0;
dispatch_gws_rt_params_t rt_params;
};
struct named_dim_t {
public:
named_dim_t(const char *name, size_t idx) : name(name), idx(idx) {};
const char *name;
size_t idx;
};
class gws_bin_mapping_t;
struct lws_strategy_t {
lws_strategy_t(const engine_t *engine, const gpu_primitive_attr_t *gpu_attr)
: engine(engine), gpu_attr(gpu_attr) {};
virtual ~lws_strategy_t() = default;
virtual range_t create_lws(
range_t &gws, const gws_bin_mapping_t &mapper) const
= 0;
virtual bool is_included(const mapped_block_t &blocks) const = 0;
size_t get_max_wg_size() const {
bool large_grf_mode = gpu_attr && gpu_attr->threads_per_eu() == 4;
return engine->device_info()->max_wg_size(large_grf_mode);
}
protected:
const engine_t *engine;
const gpu_primitive_attr_t *gpu_attr;
};
struct default_lws_strategy_t : public lws_strategy_t {
default_lws_strategy_t(
const engine_t *engine, const gpu_primitive_attr_t *gpu_attr)
: lws_strategy_t(engine, gpu_attr) {};
range_t create_lws(
range_t &gws, const gws_bin_mapping_t &mapper) const override {
range_t lws
= get_optimal_lws(gws, -1, engine->device_info()->gpu_arch());
return lws;
}
bool is_included(const mapped_block_t &blocks) const override {
return false;
}
};
struct dim_id_hash_t {
size_t operator()(const dim_idx_t &id) const noexcept { return id; }
};
constexpr dim_idx_t dim_not_found = std::numeric_limits<dim_idx_t>::max();
struct named_buffer_t : public memory_desc_t {
named_buffer_t(const char *name, const memory_desc_t &md,
const std::vector<dim_idx_t> &dims)
: memory_desc_t(md), name(name), dim_ids(dims) {
gpu_assert(this->name.size() <= MAX_BUFFER_NAME_LENGTH);
gpu_assert(format_kind == format_kind::blocked);
gpu_assert(static_cast<size_t>(md.ndims) <= dim_ids.size());
}
named_buffer_t(const char *name) : name(name) {
format_kind = format_kind::blocked;
}
named_buffer_t(const char *name, const named_buffer_t &buf)
: memory_desc_t(buf), name(name), dim_ids(buf.get_dim_ids()) {};
dim_t nelems(bool with_padding = false) const {
return memory_desc_wrapper(static_cast<memory_desc_t>(*this))
.nelems(with_padding);
}
uint64_t size(int index = 0, bool include_additional_size = true,
bool include_offset0 = false) const {
return memory_desc_wrapper(static_cast<memory_desc_t>(*this))
.size(index, include_additional_size, include_offset0);
}
const std::string &get_name() const { return name; }
const std::vector<dim_idx_t> &get_dim_ids() const { return dim_ids; }
void remove_dim(dim_idx_t dim, bool update_strides = true) {
size_t dim_idx = get_dim_idx(dim);
if (dim_idx == dim_not_found) return;
remove_blocking(dim);
auto &blk = format_desc.blocking;
dim_t dim_stride = blk.strides[dim_idx];
dim_t dim_size = padded_dims[dim_idx];
for (size_t i = 0; i < static_cast<size_t>(ndims); i++) {
if (update_strides && blk.strides[i] > dim_stride) {
blk.strides[i] /= dim_size;
}
if (i > dim_idx) {
blk.strides[i - 1] = blk.strides[i];
dims[i - 1] = dims[i];
padded_dims[i - 1] = padded_dims[i];
}
}
for (size_t blk_idx = 0; blk_idx < static_cast<size_t>(blk.inner_nblks);
blk_idx++) {
if (static_cast<size_t>(blk.inner_idxs[blk_idx]) > dim_idx)
blk.inner_idxs[blk_idx]--;
}
dim_ids.erase(dim_ids.begin() + static_cast<dim_t>(dim_idx));
ndims--;
}
void append_block(dim_idx_t dim, dim_t size) {
auto &blk = format_desc.blocking;
size_t dim_idx = get_dim_idx(dim);
if (dim_idx == dim_not_found) {
assert(ndims < DNNL_MAX_NDIMS - 1);
dims[ndims] = 1;
padded_dims[ndims] = 1;
blk.strides[ndims] = 1;
dim_idx = static_cast<size_t>(ndims++);
dim_ids.emplace_back(dim);
}
if (blk.inner_nblks > 0) {
blk.inner_idxs[blk.inner_nblks] = static_cast<dim_t>(dim_idx);
blk.inner_blks[blk.inner_nblks++] = size;
}
for (size_t i = 0; i < static_cast<size_t>(ndims); i++) {
if (i == dim_idx) continue;
blk.strides[i] *= size;
}
dims[dim_idx] *= size;
padded_dims[dim_idx] *= size;
}
dim_idx_t get_dim_idx(dim_idx_t dim) const {
for (dim_idx_t i = 0; i < into<dim_idx_t>(dim_ids.size()); i++) {
if (dim_ids[i] == dim) { return i; }
}
return dim_not_found;
}
block_layout_t layout() const {
block_layout_t layout(*this);
for (auto &block : layout) {
block.dim_idx = get_dim_ids()[static_cast<size_t>(block.dim_idx)];
}
return layout;
}
private:
std::string name;
std::vector<dim_idx_t> dim_ids;
void remove_blocking(dim_idx_t dim) {
auto &blk = format_desc.blocking;
dim_idx_t dim_idx = get_dim_idx(dim);
if (dim_idx == dim_not_found) return;
std::vector<block_t> blocks;
dim_t stride = 1;
for (int i = blk.inner_nblks - 1; i >= 0; i--) {
if (blk.inner_idxs[i] == dim_idx)
blocks.emplace_back(dim_idx, blk.inner_blks[i], stride);
stride *= blk.inner_blks[i];
}
size_t num_remaining_blocks = 0;
for (size_t i = 0; i < static_cast<size_t>(blk.inner_nblks); i++) {
if (static_cast<size_t>(blk.inner_idxs[i]) == dim_idx) continue;
blk.inner_idxs[num_remaining_blocks] = blk.inner_idxs[i];
blk.inner_blks[num_remaining_blocks++] = blk.inner_blks[i];
}
blk.inner_nblks = static_cast<int>(num_remaining_blocks);
dim_t outer_stride = blk.strides[dim_idx];
for (size_t i = 0; i < static_cast<size_t>(ndims); i++) {
if (blk.strides[i] > outer_stride) continue;
for (const auto &block : blocks) {
if (stride_t(blk.strides[i]) >= block.stride)
blk.strides[i] /= block.block;
}
}
}
};
class reusable_dispatch_t {
public:
reusable_dispatch_t() = default;
reusable_dispatch_t(const std::vector<named_buffer_t> &buffers,
const gws_term_list_t &term_list,
const compute::nd_range_t &nd_range, subgroup_data_t subgroup,
const std::vector<std::vector<size_t>> &buffer_term_map) {
assert(buffers.size() == buffer_term_map.size());
compile_params.num_terms = into<int>(term_list.terms.size());
for (size_t i = 0; i < term_list.terms.size(); i++) {
compile_params.terms[i] = term_list.terms[i].compile_params();
}
dim_t max_buffer_size = 0;
compile_params.num_buffers = buffers.size();
for (size_t buf_idx = 0; buf_idx < buffers.size(); buf_idx++) {
const named_buffer_t &buffer = buffers[buf_idx];
const auto &buf_name = buffer.get_name();
for (size_t i = 0; i < buf_name.size(); i++) {
compile_params.buffer_names[buf_idx][i] = buf_name[i];
}
const std::vector<size_t> &buf_terms = buffer_term_map[buf_idx];
compile_params.buffer_num_terms[buf_idx] = buf_terms.size();
for (size_t j = 0; j < buf_terms.size(); j++) {
compile_params.buffer_term_index[buf_idx][j] = buf_terms[j];
}
compile_params.buffer_types[buf_idx] = buffer.data_type;
max_buffer_size = std::max(max_buffer_size, buffer.nelems(true));
}
compile_params.use_int32_offset = max_buffer_size <= INT32_MAX;
compile_params.subgroup = subgroup;
runtime_params = dispatch_runtime_params_t(nd_range, term_list);
}
const dispatch_compile_params_t &get_compile_params() const {
return compile_params;
}
const dispatch_runtime_params_t &get_runtime_params() const {
return runtime_params;
}
private:
dispatch_compile_params_t compile_params;
dispatch_runtime_params_t runtime_params;
};
class gws_bin_mapping_t {
public:
gws_bin_mapping_t(subgroup_data_t sg) : sg(sg) {}
void add(const block_bin_t &bin) {
if (sg.used()) {
if (!bin.is_broadcasted(sg.buffer_idx())) {
block_t block = bin.combined_block(sg.buffer_idx());
if (block.stride == stride_t(1)) {
std::vector<block_bin_t> displaced = map[0];
clear_(0);
add_(bin, 0);
for (const block_bin_t &old_bin : displaced) {
add(old_bin);
}
return;
}
}
}
for (size_t i = 0; i < map.size(); i++) {
if (map[i].empty()) {
add_(bin, i);
return;
}
}
const mapped_block_t &first_new_block = bin.get_blocks().front();
for (size_t i = 0; i < map.size(); i++) {
block_bin_t &last = map[i].back();
const mapped_block_t &last_old_block = last.get_blocks().back();
if (last_old_block.can_merge(first_new_block, false)) {
add_(bin, i);
return;
}
}
add_(bin, gws_.ndims() - 1);
}
nd_range_t nd_range(const lws_strategy_t &lws_strategy) {
range_t lws = lws_strategy.create_lws(gws_, *this);
return compute::nd_range_t(gws_, lws);
}
const range_t &gws() const { return gws_; }
const std::vector<block_bin_t> &get_bins(size_t idx) const {
return map[idx];
}
std::vector<gws_indexing_term_t> condense_terms(size_t buffer_idx) const;
private:
void add_(const block_bin_t &bin, size_t gws_dim) {
map[gws_dim].emplace_back(bin);
gws_[gws_dim] *= bin.size();
}
void clear_(size_t gws_idx) {
map[gws_idx].clear();
gws_[gws_idx] = 1;
}
subgroup_data_t sg;
std::array<std::vector<block_bin_t>, range_t::max_ndims> map;
range_t gws_ = range_t::one();
};
class reusable_dispatch_config_t {
public:
reusable_dispatch_config_t(
const engine_t *engine, std::vector<dim_idx_t> dims)
: dispatched_dims(std::move(dims)), engine(engine) {};
status_t generate(
reusable_dispatch_t &dispatch, const lws_strategy_t &lws_strategy);
status_t register_buffer(const named_buffer_t &buffer);
status_t define_dim_index(
const char *dim_name, dim_idx_t dim_id, dim_t size);
status_t use_subgroup(const std::string &buf_name, size_t size);
private:
std::vector<named_buffer_t> buffers;
std::vector<dim_idx_t> dispatched_dims;
std::unordered_map<dim_idx_t, dim_t, dim_id_hash_t> dim_sizes;
subgroup_data_t subgroup;
const engine_t *engine;
};
} } } } }
#endif