#include "gpu/intel/reorder/jit/tiler.hpp"
#include "gpu/intel/jit/utils/range.hpp"
namespace dnnl {
namespace impl {
namespace gpu {
namespace intel {
namespace reorder {
namespace jit {
enum class message_kind_t {
block,
scattered,
};
dim_t max_strided_bytes(const dsl::hw_t &hw, const dsl::type_t &src_type,
const dsl::type_t &dst_type) {
const bool use_smaller_buffer
= utils::one_of(true, src_type.is_fp8(), dst_type.is_fp8())
|| (src_type.is_x32() && (dst_type.is_bf16() || dst_type.is_f16()))
|| (src_type.is_f16() && dst_type.is_bf16());
const int buf_regs = use_smaller_buffer ? 38 : 58;
return buf_regs * hw.grf_size();
}
dim_t max_packed_bytes(const dsl::hw_t &hw) {
return 32 * hw.grf_size();
}
dim_t count_block_messages(
const dsl::hw_t &hw, dim_t inner_bytes, dim_t iterations) {
const auto max_block_owords = hw.grf_size() / 2;
const auto oword_size = 16;
const auto owords_per_grf = hw.grf_size() / oword_size;
dim_t block_owords = max_block_owords / 2;
auto inner_owords = inner_bytes / oword_size;
dim_t messages = inner_owords / max_block_owords;
inner_owords -= messages * max_block_owords;
const dim_t lower_bound = iterations == 1 ? 1 : owords_per_grf;
for (; block_owords >= lower_bound; block_owords >>= 1) {
if (inner_owords >= block_owords) {
inner_owords -= block_owords;
messages++;
}
}
gpu_assert(inner_owords == 0);
return messages * iterations;
}
dim_t count_scattered_messages(const dsl::hw_t &hw, dim_t inner_bytes,
dim_t iterations, int item_size) {
constexpr int scattered_message_penalty = 4;
const int message_items = hw.grf_size() / 2;
auto inner_items = (iterations * inner_bytes) / item_size;
auto messages = utils::div_up(inner_items, message_items);
return messages * scattered_message_penalty;
}
struct message_info_t {
message_info_t() = default;
message_info_t(message_kind_t kind, dim_t inner_bytes, dim_t iterations,
int item_size)
: kind(kind)
, inner_bytes(inner_bytes)
, iterations(iterations)
, item_size(item_size) {}
message_kind_t kind = message_kind_t::block;
dim_t inner_bytes = 0;
dim_t iterations = 0;
int item_size = 16;
dim_t latency(const dsl::hw_t &hw) const {
if (inner_bytes == 0 || iterations == 0) return 0;
return kind == message_kind_t::block
? count_block_messages(hw, inner_bytes, iterations)
: count_scattered_messages(
hw, inner_bytes, iterations, item_size);
}
};
message_info_t estimate_message_info(
const dsl::hw_t &hw, const layout_t &layout, const tile_t &tile) {
const auto grf_size = hw.grf_size();
bool can_use_block_messages = true;
tile_t outer = tile;
dim_t inner_elems = 1;
int item_size = 16;
for (auto &blk : layout.blocks()) {
auto block = blk.size;
auto dim = blk.idx;
if (block == 1) continue;
if (outer[dim] < block) {
if (block % outer[dim] == 0) {
inner_elems *= outer[dim];
outer[dim] = 1;
}
break;
}
can_use_block_messages &= (outer[dim] % block == 0);
inner_elems *= block;
outer[dim] = utils::div_up(outer[dim], block);
}
auto inner_bytes = utils::div_up(
layout.type().with_elems(8).size() * inner_elems, 8);
auto iterations = outer.elems();
can_use_block_messages &= (inner_bytes % 16 == 0);
can_use_block_messages &= (iterations == 1 || inner_bytes % grf_size == 0);
if (inner_bytes == 0 || iterations == 0) return {};
auto message_kind = can_use_block_messages ? message_kind_t::block
: message_kind_t::scattered;
if (!can_use_block_messages)
for (item_size = 8; item_size > 1; item_size >>= 1) {
if (inner_bytes % item_size == 0) break;
}
return {message_kind, inner_bytes, iterations, item_size};
}
std::vector<tile_t> tiles(const dsl::hw_t &hw, layout_t a, layout_t b) {
using tile_pair_t = std::array<tile_t, 2>;
std::vector<dim_t> dims(a.ndims());
for (dim_idx_t i = 0; i < a.ndims(); ++i)
dims[i] = std::max(a.elems(i), b.elems(i));
auto pad_layout = [&](layout_t &l) {
std::vector<layout_block_t> padded_blocks;
for (auto &b : l.blocks()) {
if (l.is_outermost(b)) {
dim_t inner = l.elems(b.idx) / b.size;
padded_blocks.emplace_back(b.idx,
ir_utils::safe_divide(dims[b.idx], inner), b.stride);
} else {
padded_blocks.emplace_back(b);
}
}
l = l.with(padded_blocks, false);
};
pad_layout(a);
pad_layout(b);
gpu_assert(a.tile() == b.tile());
auto can_be_mapped = [](const layout_t &l, const tile_t &t) {
std::vector<dim_t> rem_dims = t.values();
for (auto &b : l.blocks()) {
auto &rem_dim = rem_dims[b.idx];
if (rem_dim >= b.size) {
if (rem_dim % b.size != 0) return false;
rem_dim /= b.size;
continue;
}
if (b.size % rem_dim != 0) return false;
rem_dim = 1;
}
for (auto d : rem_dims)
gpu_assert(d == 1);
return true;
};
auto add_pseudo_dimension = [](const layout_t &l) {
auto layout_size = size_bytes(l);
return [=](const tile_t &t) {
auto dims = t.values();
dims.push_back(layout_size);
return tile_t(dims);
};
};
auto mappable_tiles = [&](const tile_t &t) {
return can_be_mapped(a, t) && can_be_mapped(b, t);
};
auto merge_tiles = [](const tile_pair_t &p) {
auto ndims = p[0].size() - 1;
std::vector<dim_t> dims(ndims);
for (dim_idx_t i = 0; i < ndims; ++i)
dims[i] = std::max(p[0][i], p[1][i]);
return tile_t(dims);
};
auto take_smaller = [](const tile_t &a, const tile_t &b) {
return a.elems() < b.elems();
};
const auto eu_count = hw.eu_count();
auto cmp = [&](const tile_t &l, const tile_t &r) {
auto l_threads_reqd = a.elems() / l.elems();
auto r_threads_reqd = a.elems() / r.elems();
auto l_eu_util = utils::div_up(l_threads_reqd, eu_count);
auto r_eu_util = utils::div_up(r_threads_reqd, eu_count);
auto l_a_msg = estimate_message_info(hw, a, l);
auto l_b_msg = estimate_message_info(hw, b, l);
auto r_a_msg = estimate_message_info(hw, a, r);
auto r_b_msg = estimate_message_info(hw, b, r);
auto l_msg_load = l_a_msg.latency(hw) + l_b_msg.latency(hw);
auto r_msg_load = r_a_msg.latency(hw) + r_b_msg.latency(hw);
if (l_eu_util * l_msg_load != r_eu_util * r_msg_load)
return (l_eu_util * l_msg_load < r_eu_util * r_msg_load);
if (l.elems() * r_msg_load != r.elems() * l_msg_load)
return (l.elems() * r_msg_load > r.elems() * l_msg_load);
return l.elems() > r.elems();
};
std::vector<tile_t> candidate_tiles;
auto a_tiles = inner_tiles(a.blocks(), a.ndims()) | filter(mappable_tiles)
| transform(add_pseudo_dimension(a));
auto b_tiles = inner_tiles(b.blocks(), b.ndims()) | filter(mappable_tiles)
| transform(add_pseudo_dimension(b));
auto tiles = merge(a_tiles, b_tiles, take_smaller) | transform(merge_tiles);
const int elem_size = std::max(a.type().size(), b.type().size());
const dim_t max_layout_size = max_strided_bytes(hw, a.type(), b.type());
const dim_t max_elems = max_packed_bytes(hw) / elem_size;
auto get_grf_layout_size = [&](const tile_t &tile) {
auto elems = tile.elems();
dim_t grf_layout_size = 0;
for (const auto &l : {a, b}) {
auto info = estimate_message_info(hw, l, tile);
int elem_size = std::max(info.item_size, 4);
int elem_packing = info.item_size / l.type().size();
auto layout_size = elem_size * elems / elem_packing;
if (layout_size > grf_layout_size) grf_layout_size = layout_size;
}
return grf_layout_size;
};
for (auto tile : tiles) {
if (tile.elems() > max_elems) break;
if (get_grf_layout_size(tile) > max_layout_size) continue;
if (candidate_tiles.empty() || tile != candidate_tiles.back())
candidate_tiles.push_back(std::move(tile));
}
gpu_assert(!candidate_tiles.empty());
size_t best_idx = 0;
for (size_t i = 0; i < candidate_tiles.size(); ++i)
if (cmp(candidate_tiles[i], candidate_tiles[best_idx])) best_idx = i;
candidate_tiles.resize(best_idx + 1);
auto best = candidate_tiles.back();
candidate_tiles.erase(
std::remove_if(candidate_tiles.begin(), candidate_tiles.end(),
[&](const tile_t &t) { return !best.is_divisible(t); }),
candidate_tiles.end());
candidate_tiles.shrink_to_fit();
return candidate_tiles;
}
} } } } } }