[GPU] Gemm tiled opt add dynamic padding support (#21640)

This commit is contained in:
Sergey Shlyapnikov authored and GitHub committed 2023-12-14 09:38:16 +04:00
1 parent 926a0591ff
commit a04d59d779
4 files changed
+221 -12

No files matched your search

@@ -117,7 +117,7 @@ KERNEL(gemm_tiled_opt)(
// Start pointers offsets
#if !TRANSPOSE_INPUT0
const __global INPUT0_TYPE* a_ptr = input0 + batch_offset_input0 + tile_m_offset * K;
const __global INPUT0_TYPE* a_ptr = input0 + batch_offset_input0 + tile_m_offset * K_PADDED_IN0;
#else // !TRANSPOSE_INPUT0
const __global INPUT0_TYPE* a_ptr = input0 + batch_offset_input0 + tile_m_offset;
#endif // !TRANSPOSE_INPUT0
@@ -153,7 +153,13 @@ KERNEL(gemm_tiled_opt)(
// Loading B tile
unroll_for (uint b_load_id = 0; b_load_id < TILE_K; b_load_id++) {
#if IS_DYNAMIC
#if HAS_DYNAMIC_N_PADDING
// In case of dynamic padding we can't guarantee memory access alignment for
// block reads (4 bytes), so use scattered read
b_tile[b_load_id] = b_raw_global_id > N - 1 ? 0 : b_ptr[sglid];
#else
b_tile[b_load_id] = TILE_N_NOT_DIVISIBLE ? (b_raw_global_id > N - 1 ? 0 : b_ptr[sglid]) : BLOCK_READ_B(b_ptr, 0);
#endif
#else // IS_DYNAMIC
#if TILE_N_NOT_DIVISIBLE
b_tile[b_load_id] = b_raw_global_id > N - 1 ? 0 : b_ptr[sglid];
@@ -162,7 +168,7 @@ KERNEL(gemm_tiled_opt)(
#endif // TILE_N_NOT_DIVISIBLE
#endif // IS_DYNAMIC
#if !TRANSPOSE_INPUT1
b_ptr += N;
b_ptr += N_PADDED;
#else // !TRANSPOSE_INPUT1
b_ptr += K;
#endif // !TRANSPOSE_INPUT1
@@ -203,7 +209,13 @@ KERNEL(gemm_tiled_opt)(
unroll_for (uint dot_id = 0; dot_id < tile_m_iterations; dot_id++) {
#if !TRANSPOSE_INPUT0
#if IS_DYNAMIC
A_FLOATN a_read = TILE_K_NOT_DIVISIBLE ? a_ptr[dot_id * K + sglid] : BLOCK_READ_A(a_ptr, dot_id * K);
#if HAS_DYNAMIC_K_PADDING
// In case of dynamic padding we can't guarantee memory access alignment for
// block reads (4 bytes), so use scattered read
A_FLOATN a_read = a_ptr[dot_id * K_PADDED_IN0 + sglid];
#else
A_FLOATN a_read = TILE_K_NOT_DIVISIBLE ? a_ptr[dot_id * K_PADDED_IN0 + sglid] : BLOCK_READ_A(a_ptr, dot_id * K);
#endif
#else // IS_DYNAMIC
#if TILE_K_NOT_DIVISIBLE
A_FLOATN a_read = a_ptr[dot_id * K + sglid];
@@ -273,13 +285,17 @@ KERNEL(gemm_tiled_opt)(
if (TILE_K_NOT_DIVISIBLE) {
// Loading leftovers of the matrix B
unroll_for (uint b_load_id = 0; b_load_id < TILE_K_LEFTOVER; b_load_id++) {
#if HAS_DYNAMIC_N_PADDING
b_tile[b_load_id] = b_raw_global_id > N - 1 ? 0 : b_ptr[sglid];
#else
b_tile[b_load_id] = TILE_N_NOT_DIVISIBLE ? (b_raw_global_id > N - 1 ? 0 : b_ptr[sglid]) : BLOCK_READ_B(b_ptr, 0);
b_ptr += N;
#endif
b_ptr += N_PADDED;
} // Loading leftovers of the matrix B end
// Loading leftovers of the matrix A and tile C calculation
unroll_for (uint dot_id = 0; dot_id < tile_m_iterations; dot_id++) {
INPUT0_TYPE a_read = a_ptr[dot_id * K + sglid];
INPUT0_TYPE a_read = a_ptr[dot_id * K_PADDED_IN0 + sglid];
unroll_for (uint simd_id = 0; simd_id < TILE_K_LEFTOVER; simd_id++) {
c_tile[dot_id] = mad((INPUT0_TYPE)(sub_group_broadcast(a_read, simd_id)), b_tile[simd_id], c_tile[dot_id]);
@@ -197,10 +197,14 @@ std::string toCodeString(const Tensor::Dim& dim, size_t offset, bool padded, boo
pad_str = " + " + std::to_string(dim.pad.Total());
}
}
if (dim.is_dynamic || pad_is_dynamic) {
if (dim.is_dynamic) {
snprintf(buf, sizeof(buf), "(shape_info[%zu] %s)", offset, pad_str.c_str());
} else {
snprintf(buf, sizeof(buf), "%zu", dim.v + (padded ? dim.pad.Total() : 0));
if (pad_is_dynamic) {
snprintf(buf, sizeof(buf), "(%zu %s)", dim.v, pad_str.c_str()); // Static dim, dynamic padding
} else {
snprintf(buf, sizeof(buf), "%zu", dim.v + (padded ? dim.pad.Total() : 0)); // Static dim, static padding
}
}
return buf;
}
@@ -24,6 +24,7 @@ ParamsKey GemmKernelTiledOpt::GetSupportedKey() const {
k.EnableOutputLayout(DataLayout::bfwzyx);
k.EnableTensorOffset();
k.EnableTensorPitches();
k.EnableBatching();
k.EnableDifferentTypes();
k.EnableDynamicShapesSupport();
@@ -64,7 +65,7 @@ GemmKernelTiledOpt::GemmTuningData GemmKernelTiledOpt::SetTuningParams(const gem
GemmKernelTiledOpt::GemmTuningData tuning_data;
if (!params.is_shape_agnostic) {
if (!params.is_shape_agnostic) {
auto m_size = output.Y().v;
auto n_size = output.X().v;
auto k_size = params.transpose_input0 ? params.inputs[0].Y().v : params.inputs[0].X().v;
@@ -112,11 +113,17 @@ JitConstants GemmKernelTiledOpt::GetJitConstants(const gemm_params& params) cons
if (params.has_dynamic_tensors()) {
DimensionAccessHelper dims0(params.inputs[0]);
DimensionAccessHelper dims1(params.inputs[1]);
DimensionAccessHelper dims0_padded(params.inputs[0], true);
DimensionAccessHelper dims1_padded(params.inputs[1], true);
// Note: Actually currently this kernel is not being selected if it is shape agnostic impl && transposed inputs
// Because we cannot get the original rank
auto m_size = params.transpose_input0 ? dims0.x() : dims0.y();
auto n_size = params.transpose_input1 ? dims1.y() : dims1.x();
auto n_padded_size = params.transpose_input1 ? "(" + dims1_padded.y() + ")"
: "(" + dims1_padded.x() + ")";
auto k_size = params.transpose_input0 ? dims0.y() : dims0.x();
auto k_padded_size_in0 = params.transpose_input0 ? "(" + dims0_padded.y() + ")"
: "(" + dims0_padded.x() + ")";
const std::string leftover_m = "(" + m_size + "%" + std::to_string(tuning_data.tile_m_size) + ")";
const std::string leftover_n = "(" + n_size + "%" + std::to_string(tuning_data.tile_n_size) + ")";
const std::string leftover_k = "(" + k_size + "%" + std::to_string(tuning_data.tile_k_size) + ")";
@@ -129,6 +136,8 @@ JitConstants GemmKernelTiledOpt::GetJitConstants(const gemm_params& params) cons
MakeJitConstant("M", m_size),
MakeJitConstant("K", k_size),
MakeJitConstant("N", n_size),
MakeJitConstant("K_PADDED_IN0", k_padded_size_in0),
MakeJitConstant("N_PADDED", n_padded_size),
MakeJitConstant("SIMD_WIDTH", tuning_data.simd_size),
MakeJitConstant("TILE_M", tuning_data.tile_m_size),
MakeJitConstant("TILE_K", tuning_data.tile_k_size),
@@ -141,6 +150,15 @@ JitConstants GemmKernelTiledOpt::GetJitConstants(const gemm_params& params) cons
MakeJitConstant("TILE_K_LEFTOVER", leftover_k),
MakeJitConstant("TILE_N_LEFTOVER", leftover_n),
});
bool has_dynamic_k_padding = params.transpose_input0 ? params.inputs[0].Y().pad.is_dynamic
: params.inputs[0].X().pad.is_dynamic;
bool has_dynamic_n_padding = params.transpose_input1 ? params.inputs[1].Y().pad.is_dynamic
: params.inputs[1].X().pad.is_dynamic;
if (has_dynamic_k_padding)
jit.AddConstant(MakeJitConstant("HAS_DYNAMIC_K_PADDING", 1));
if (has_dynamic_n_padding)
jit.AddConstant(MakeJitConstant("HAS_DYNAMIC_N_PADDING", 1));
} else {
auto m_size = output.Y().v;
auto n_size = output.X().v;
@@ -153,6 +171,8 @@ JitConstants GemmKernelTiledOpt::GetJitConstants(const gemm_params& params) cons
MakeJitConstant("M", m_size),
MakeJitConstant("K", k_size),
MakeJitConstant("N", n_size),
MakeJitConstant("K_PADDED_IN0", k_size),
MakeJitConstant("N_PADDED", n_size),
MakeJitConstant("SIMD_WIDTH", tuning_data.simd_size),
MakeJitConstant("TILE_M", tuning_data.tile_m_size),
MakeJitConstant("TILE_K", tuning_data.tile_k_size),
@@ -235,10 +255,24 @@ bool GemmKernelTiledOpt::Validate(const Params& params, const optional_params& o
return false;
const auto& gmm_params = static_cast<const gemm_params&>(params);
for (auto input : gmm_params.inputs) {
// Only supports outer padding as first element offset
if (input.X().pad.Total() != 0 || input.Y().pad.Total() != 0 || input.Z().pad.Total() != 0 ||
input.Feature().pad.Total() != 0)
if (gmm_params.outputs[0].PitchesDifferFromLogicalDims())
return false;
for (size_t input_idx = 0; input_idx < gmm_params.inputs.size(); ++input_idx) {
auto& input = gmm_params.inputs[input_idx];
// Supports outer padding as first element offset and dynamic padding for Batch, Feature, X, Y dimensions for first and second inputs
// in case of shape agnostic kernel
bool proper_pad_f = input.Feature().pad.is_dynamic ? false : input.Feature().pad.Total() == 0;
bool proper_pad_x = input.X().pad.is_dynamic ? false : input.X().pad.Total() == 0;
bool proper_pad_y = input.Y().pad.is_dynamic ? false : input.Y().pad.Total() == 0;
if (gmm_params.is_shape_agnostic && input_idx < 2) {
proper_pad_f |= input.Feature().pad.is_dynamic;
proper_pad_x |= input.X().pad.is_dynamic;
proper_pad_y |= input.Y().pad.is_dynamic;
}
if (!proper_pad_x || !proper_pad_y || input.Z().pad.Total() != 0 || !proper_pad_f)
return false;
}
@@ -359,6 +359,157 @@ public:
}
}
void test_dynamic_padding(bool is_caching_test) {
tests::random_generator rg;
rg.set_seed(GET_SUITE_NAME);
auto& engine = get_test_engine();
const unsigned long BATCH_SIZE = 31;
const unsigned long M_SIZE = 11;
const unsigned long K_SIZE = 37;
const unsigned long N_SIZE = 49;
auto fill_mem = [&](cldnn::memory_ptr mem, std::vector<ov::float16>& data) {
cldnn::mem_lock<ov::float16> mem_ptr(mem, get_test_stream());
auto&& l = mem->get_layout();
auto data_idx = 0;
for (cldnn::tensor::value_type b = 0; b < l.batch(); ++b) {
for (cldnn::tensor::value_type f = 0; f < l.feature(); ++f) {
for (cldnn::tensor::value_type y = 0; y < l.spatial(1); ++y) {
for (cldnn::tensor::value_type x = 0; x < l.spatial(0); ++x) {
auto tensor_coord = cldnn::tensor{{b, f, x, y}, 0};
auto buffer_idx = l.get_linear_offset(tensor_coord);
mem_ptr[buffer_idx] = data[data_idx++];
}
}
}
}
};
const auto align_size_m = 13;
const auto align_size_k = 16;
const auto align_size_n = 15;
const auto align_size_b1 = 3;
const auto align_size_b2 = 19;
const auto aligned_batch1_size = align_to(1ul, align_size_b1);
auto padding_size_batch1 = static_cast<int>(aligned_batch1_size - 1);
const auto aligned_batch2_size = align_to(BATCH_SIZE, align_size_b2);
auto padding_size_batch2 = static_cast<int>(aligned_batch2_size - BATCH_SIZE);
const auto aligned_m_size = align_to(M_SIZE, align_size_m);
auto padding_size_m = static_cast<int>(aligned_m_size - M_SIZE);
const auto aligned_k_size = align_to(K_SIZE, align_size_k);
auto padding_size_k = static_cast<int>(aligned_k_size - K_SIZE);
const auto aligned_n_size = align_to(N_SIZE, align_size_n);
auto padding_size_n = static_cast<int>(aligned_n_size - N_SIZE);
ov::Shape in1_shape = { 1, BATCH_SIZE, M_SIZE, K_SIZE };
ov::Shape in2_shape = { 1, BATCH_SIZE, K_SIZE, N_SIZE };
ov::Shape in1_shape_aligned = { aligned_batch1_size, aligned_batch2_size, aligned_m_size, aligned_k_size };
ov::Shape in2_shape_aligned = { aligned_batch1_size, aligned_batch2_size, aligned_k_size, aligned_n_size };
// Use dynamic padding for all BFYX dimensions
tensor dyn_pad_dims_input({1, 1, 1, 1}, 0);
auto in1_layout = layout{ {-1, -1, -1, -1}, data_types::f16, format::bfyx, padding({0, 0, 0, 0}, {0, 0, 0, 0}, 0.0f, dyn_pad_dims_input)};
auto in2_layout = layout{ {-1, -1, -1, -1}, data_types::f16, format::bfyx, padding({0, 0, 0, 0}, {0, 0, 0, 0}, 0.0f, dyn_pad_dims_input)};
auto aligned_input1_mem = engine.allocate_memory({ov::PartialShape(in1_shape_aligned), data_types::f16, format::bfyx});
auto aligned_input2_mem = engine.allocate_memory({ov::PartialShape(in2_shape_aligned), data_types::f16, format::bfyx});
auto input1_mem = engine.reinterpret_buffer(*aligned_input1_mem, layout{ov::PartialShape(in1_shape),
data_types::f16,
format::bfyx,
padding({padding_size_batch1, 0, 0, 0},
{0, padding_size_batch2, padding_size_k, padding_size_m}, 0.0f, dyn_pad_dims_input)});
auto input2_mem = engine.reinterpret_buffer(*aligned_input2_mem, layout{ov::PartialShape(in2_shape),
data_types::f16,
format::bfyx,
padding({0, padding_size_batch2, 0, 0},
{padding_size_batch1, 0, padding_size_n, padding_size_k}, 0.0f, dyn_pad_dims_input)});
auto input_1_data = rg.generate_random_1d<ov::float16>(ov::shape_size(in1_shape), -2, 2);
auto input_2_data = rg.generate_random_1d<ov::float16>(ov::shape_size(in2_shape), -2, 2);
fill_mem(input1_mem, input_1_data);
fill_mem(input2_mem, input_2_data);
auto get_ref_results = [&]() {
ov::Shape in1_shape = { 1, BATCH_SIZE, M_SIZE, K_SIZE };
ov::Shape in2_shape = { 1, BATCH_SIZE, K_SIZE, N_SIZE };
auto in1_layout = layout{ {-1, -1, -1, -1}, data_types::f16, format::bfyx};
auto in2_layout = layout{ {-1, -1, -1, -1}, data_types::f16, format::bfyx};
auto input1_mem = engine.allocate_memory(layout{ov::PartialShape(in1_shape), data_types::f16, format::bfyx});
auto input2_mem = engine.allocate_memory(layout{ov::PartialShape(in2_shape), data_types::f16, format::bfyx});
fill_mem(input1_mem, input_1_data);
fill_mem(input2_mem, input_2_data);
topology topology;
topology.add(input_layout("input1", in1_layout),
input_layout("input2", in2_layout),
gemm("gemm_ref", { input_info("input1"), input_info("input2") }, data_types::f16, false, false, 1.0f, 0.0f, 4, 4)
);
auto config = get_test_default_config(engine);
config.set_property(ov::intel_gpu::optimize_data(true));
config.set_property(ov::intel_gpu::allow_new_shape_infer(true));
network network(engine, topology, config);
network.set_input_data("input1", input1_mem);
network.set_input_data("input2", input2_mem);
auto outputs = network.execute();
OPENVINO_ASSERT(outputs.size() == 1);
OPENVINO_ASSERT(outputs.begin()->first == "gemm_ref");
auto inst = network.get_primitive("gemm_ref");
auto output_mem = outputs.at("gemm_ref").get_memory();
auto output_layout = outputs.at("gemm_ref").get_layout();
return engine.reinterpret_buffer(*output_mem, output_layout);
};
topology topology;
topology.add(input_layout("input1", in1_layout),
input_layout("input2", in2_layout),
gemm("gemm", { input_info("input1"), input_info("input2") }, data_types::f16, false, false, 1.0f, 0.0f, 4, 4)
);
ExecutionConfig config = get_test_default_config(engine);
config.set_property(ov::intel_gpu::optimize_data(true));
config.set_property(ov::intel_gpu::allow_new_shape_infer(true));
network::ptr network = get_network(engine, topology, config, get_test_stream_ptr(), is_caching_test);
network->set_input_data("input1", input1_mem);
network->set_input_data("input2", input2_mem);
auto inst = network->get_primitive("gemm");
auto impl = inst->get_impl();
ASSERT_TRUE(impl != nullptr);
ASSERT_TRUE(impl->is_dynamic());
auto outputs = network->execute();
auto output_mem = outputs.at("gemm").get_memory();
auto output_layout = outputs.at("gemm").get_layout();
auto res = engine.reinterpret_buffer(*output_mem, output_layout);
auto ref_res = get_ref_results();
mem_lock<ov::float16> res_lock(res, get_test_stream());
mem_lock<ov::float16> res_ref_lock(ref_res, get_test_stream());
for (size_t i = 0; i < res->count(); i++) {
ASSERT_EQ(res_lock[i], res_ref_lock[i]) << i;
}
}
void test_dynamic_multi_inference_same_shape(bool is_caching_test) {
auto& engine = get_test_engine();
@@ -549,6 +700,10 @@ TEST_F(gemm_gpu_tests, dynamic) {
this->test_dynamic(false);
}
TEST_F(gemm_gpu_tests, dynamic_padding) {
this->test_dynamic_padding(false);
}
TEST_F(gemm_gpu_tests, dynamic_multi_inference_same_shape) {
this->test_dynamic_multi_inference_same_shape(false);
}