Adding Transpose3d and Transpose4d special case kernels for Rocm (#5837)

* add transpose3d; seeing memory fault on rocm3.7

* cleaned up code; commit to switch machines

* tested working on gcr-openpai-35; 168 ex/sec

* remove debug HCC_ENABLE_PRINTF
This commit is contained in:
Suffian Khan 2020-11-18 17:47:19 -08:00 committed by GitHub
parent b495ae8103
commit 7196d4206f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 282 additions and 7 deletions

View file

@ -103,6 +103,53 @@ Status Transpose::DoTranspose(const Transpose& kernel,
TensorPitches original_input_strides(input_dims);
TensorPitches original_output_strides(output_dims);
// flatten the adjacent dimensions which are contiguous
// for example: permutations[0, 2, 3, 1] -> [0, 2, 1], permutations[0, 3, 1, 2] -> [0, 2, 1]
auto new_rank = rank;
std::vector<size_t> new_permutations(permutations);
std::vector<int64_t> new_input_dims(input_dims);
std::vector<int64_t> new_output_dims(output_dims);
for (auto i = rank - 1; i > 0; i--) {
auto curr = new_permutations[i];
auto prev = new_permutations[i - 1];
if (prev + 1 == curr) {
// all dims bigger than curr need to be reduced by 1 due to the merging.
for (auto j = 0; j < new_rank; j++) {
if (new_permutations[j] > curr) {
new_permutations[j] -= 1;
}
}
for (auto j = i+1; j < new_rank; j++) {
new_permutations[j-1] = new_permutations[j];
}
// update input dims
new_input_dims[prev] *= new_input_dims[curr];
new_input_dims[curr] = 1;
for (auto j = static_cast<int32_t>(curr+1); j < new_rank; j++) {
new_input_dims[j-1] = new_input_dims[j];
}
new_input_dims[new_rank-1] = 1;
// update output dims
new_output_dims[i-1] *= new_output_dims[i];
new_output_dims[i] = 1;
for (auto j = i+1; j < new_rank; j++) {
new_output_dims[j-1] = new_output_dims[j];
}
new_output_dims[new_rank-1] = 1;
new_rank--;
}
}
new_permutations.resize(new_rank);
new_input_dims.resize(new_rank);
new_output_dims.resize(new_rank);
TensorPitches new_input_strides(new_input_dims);
TensorPitches new_output_strides(new_output_dims);
// TArray<int64_t> input_strides(rank);
// for (auto i = 0; i < rank; i++) {
// input_strides[i] = original_input_strides[permutations[i]];
@ -113,13 +160,32 @@ Status Transpose::DoTranspose(const Transpose& kernel,
// output_strides[i] = fast_divmod(gsl::narrow_cast<int>(original_output_strides[i]));
// }
RocmAsyncBuffer<int64_t> input_strides(&kernel, rank);
for (auto i = 0; i < rank; i++) {
input_strides.CpuPtr()[i] = original_input_strides[permutations[i]];
size_t element_size = input.DataType()->Size();
std::vector<int64_t> input_shape(new_rank);
std::vector<int64_t> tmp_input_strides(new_rank);
std::vector<int64_t> tmp_output_strides(new_rank);
for (auto i = 0; i < new_rank; i++) {
input_shape[i] = new_input_dims[i];
tmp_input_strides[i] = new_input_strides[i];
tmp_output_strides[i] = new_output_strides[new_permutations[i]];
}
RocmAsyncBuffer<fast_divmod> output_strides(&kernel, rank);
ORT_ENFORCE(CalculateFdmStrides(output_strides.CpuSpan(), output_dims));
if (CanDoTranspose3D(new_rank, new_input_dims, new_permutations)) {
return Transpose3DImpl(kernel, element_size, input_shape, tmp_input_strides,
input.DataRaw(), output.MutableDataRaw(), output.Shape().Size());
} else if (CanDoTranspose4D(kernel.GetDeviceProp(), element_size, new_rank, new_input_dims, new_permutations)) {
return Transpose4DImpl(kernel, element_size, input_shape, tmp_input_strides, input.DataRaw(),
tmp_output_strides, output.MutableDataRaw(), output.Shape().Size());
}
RocmAsyncBuffer<int64_t> input_strides(&kernel, new_rank);
for (auto i = 0; i < new_rank; i++) {
input_strides.CpuPtr()[i] = new_input_strides[new_permutations[i]];
}
RocmAsyncBuffer<fast_divmod> output_strides(&kernel, new_rank);
ORT_ENFORCE(CalculateFdmStrides(output_strides.CpuSpan(), new_output_dims));
// TODO: use output shape in reverse order for uint24 math
// for (auto i = 0; i < rank; i++) {
@ -131,8 +197,7 @@ Status Transpose::DoTranspose(const Transpose& kernel,
ORT_RETURN_IF_ERROR(input_strides.CopyToGpu());
ORT_RETURN_IF_ERROR(output_strides.CopyToGpu());
size_t element_size = input.DataType()->Size();
auto status = TransposeImpl(element_size, rank, input_strides.GpuPtr(), input.DataRaw(),
auto status = TransposeImpl(element_size, new_rank, input_strides.GpuPtr(), input.DataRaw(),
output_strides.GpuPtr(), output.MutableDataRaw(), output.Shape().Size());
return status;

View file

@ -7,6 +7,199 @@
namespace onnxruntime {
namespace rocm {
// todo: should it be different for AMD HW?
constexpr int TILE_DIM = 16;
template <typename T>
/* __global__ void Transpose3DKernel(const TArray<int64_t> input_shape,
const TArray<int64_t> input_strides,
const T* input_data, T* output_data) { */
__global__ void Transpose3DKernel(const int64_t* input_shape,
const int64_t* input_strides,
const T* __restrict__ input_data, T* __restrict__ output_data) {
__shared__ T tile[TILE_DIM * (TILE_DIM + 1)];
int x = blockIdx.x * TILE_DIM + threadIdx.x;
int y = blockIdx.y * TILE_DIM + threadIdx.y;
tile[threadIdx.y * TILE_DIM + threadIdx.x] = input_data[blockIdx.z * input_strides[0] + y * input_shape[2] + x];
__syncthreads();
x = blockIdx.y * TILE_DIM + threadIdx.x;
y = blockIdx.x * TILE_DIM + threadIdx.y;
output_data[blockIdx.z * input_strides[0] + y * input_shape[1] + x] = tile[threadIdx.x * TILE_DIM + threadIdx.y];
}
bool CanDoTranspose3D(int32_t rank,
const std::vector<int64_t>& input_dims,
const std::vector<size_t>& permutations) {
if (rank == 3 &&
// permutation is done in the last two dimensions.
permutations[rank - 2] == (rank - 1) && permutations[rank - 1] == (rank - 2) &&
// the last two dimensions are aligned with TILE_DIM.
input_dims[rank - 2] % TILE_DIM == 0 && input_dims[rank - 1] % TILE_DIM == 0) {
return true;
}
return false;
}
/* Status Transpose3DImpl(size_t element_size,
const TArray<int64_t>& input_shape, const TArray<int64_t>& input_strides,
const void* input_data, void* output_data, int64_t N) { */
Status Transpose3DImpl(const Transpose& kernel, size_t element_size,
const std::vector<int64_t>& input_shape, const std::vector<int64_t>& input_strides,
const void* input_data, void* output_data, int64_t N) {
dim3 block_size(TILE_DIM, TILE_DIM);
dim3 grid_size(input_shape[2] / TILE_DIM, input_shape[1] / TILE_DIM, input_shape[0]);
RocmKernel::RocmAsyncBuffer<int64_t> input_shape_buffer(&kernel, input_shape.size());
RocmKernel::RocmAsyncBuffer<int64_t> input_strides_buffer(&kernel, input_strides.size());
for (int i = 0; i < input_shape.size(); i++) {
input_shape_buffer.CpuPtr()[i] = input_shape[i];
input_strides_buffer.CpuPtr()[i] = input_strides[i];
}
ORT_RETURN_IF_ERROR(input_shape_buffer.CopyToGpu());
ORT_RETURN_IF_ERROR(input_strides_buffer.CopyToGpu());
switch (element_size) {
case sizeof(int8_t):
hipLaunchKernelGGL(Transpose3DKernel<int8_t>, grid_size, block_size, 0, 0,
input_shape_buffer.GpuPtr(), input_strides_buffer.GpuPtr(),
reinterpret_cast<const ToHipType<int8_t>::MappedType*>(input_data),
reinterpret_cast<ToHipType<int8_t>::MappedType*>(output_data));
break;
case sizeof(int16_t):
hipLaunchKernelGGL(Transpose3DKernel<int16_t>, grid_size, block_size, 0, 0,
input_shape_buffer.GpuPtr(), input_strides_buffer.GpuPtr(),
reinterpret_cast<const ToHipType<int16_t>::MappedType*>(input_data),
reinterpret_cast<ToHipType<int16_t>::MappedType*>(output_data));
break;
case sizeof(int32_t):
hipLaunchKernelGGL(Transpose3DKernel<int32_t>, grid_size, block_size, 0, 0,
input_shape_buffer.GpuPtr(), input_strides_buffer.GpuPtr(),
reinterpret_cast<const ToHipType<int32_t>::MappedType*>(input_data),
reinterpret_cast<ToHipType<int32_t>::MappedType*>(output_data));
break;
case sizeof(int64_t):
hipLaunchKernelGGL(Transpose3DKernel<int64_t>, grid_size, block_size, 0, 0,
input_shape_buffer.GpuPtr(), input_strides_buffer.GpuPtr(),
reinterpret_cast<const ToHipType<int64_t>::MappedType*>(input_data),
reinterpret_cast<ToHipType<int64_t>::MappedType*>(output_data));
break;
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Type not supported for transpose on CUDA. Element size was ",
element_size);
}
return Status::OK();
}
template <int element_size>
// __global__ void Transpose4DKernel(const TArray<int64_t> input_strides, const void* input_data,
// const TArray<int64_t> output_strides, void* output_data,
// HIP_LONG N) {
__global__ void Transpose4DKernel(const int64_t* input_strides, const void* input_data,
const int64_t* output_strides, void* output_data,
HIP_LONG N) {
// output coordinates will be: blockIdx.y, blockIdx.x, threadIdx.y, threadIdx.x
HIP_LONG input_index = (blockIdx.y * input_strides[0] +
blockIdx.x * input_strides[1] +
threadIdx.y * input_strides[2]) /
(4 * sizeof(int) / element_size) +
threadIdx.x * input_strides[3];
HIP_LONG output_index = (blockIdx.y * output_strides[0] +
blockIdx.x * output_strides[1] +
threadIdx.y * output_strides[2]) /
(4 * sizeof(int) / element_size) +
threadIdx.x * output_strides[3];
const int4* v_input = reinterpret_cast<const int4*>(input_data);
int4* v_output = reinterpret_cast<int4*>(output_data);
if (input_index < N && output_index < N) {
v_output[output_index] = v_input[input_index];
}
}
bool CanDoTranspose4D(const hipDeviceProp_t& prop,
size_t element_size,
int32_t rank,
const std::vector<int64_t>& input_dims,
const std::vector<size_t>& permutations) {
if (rank == 4 &&
// the permutations is not on the last dimension.
permutations[rank - 1] == (rank - 1)) {
// The block size will be set based on the last two dimensions of 4D tensor.
// the number threads per block will be calculated as below.
int num_elements_per_thread = 4 * sizeof(int) / element_size; // int4 is used in the kernel to access data.
int64_t num_elements_in_last_two_dimensions = input_dims[rank - 2] * input_dims[rank - 1];
int64_t num_threads_per_block = num_elements_in_last_two_dimensions / num_elements_per_thread;
if (((num_elements_in_last_two_dimensions & (num_elements_per_thread - 1)) == 0) &&
num_threads_per_block <= prop.maxThreadsPerBlock &&
num_threads_per_block >= prop.warpSize &&
// num_threads_per_block must be aligned with warp size: 32
((num_threads_per_block & (prop.warpSize - 1)) == 0)) {
return true;
}
}
return false;
}
// Status Transpose4DImpl(size_t element_size, const TArray<int64_t>& input_shape, const TArray<int64_t>& input_strides, const void* input_data,
// const TArray<int64_t>& output_strides, void* output_data, int64_t N) {
Status Transpose4DImpl(const Transpose& kernel, size_t element_size, const std::vector<int64_t>& input_shape, const std::vector<int64_t>& input_strides, const void* input_data,
const std::vector<int64_t>& output_strides, void* output_data, int64_t N) {
int num_elements_per_thread = 4 * sizeof(int) / element_size; // int4 is used in the kernel to access data.
dim3 block_size(input_shape[3] / num_elements_per_thread, input_shape[2]);
dim3 grid_size(input_shape[1], input_shape[0]);
RocmKernel::RocmAsyncBuffer<int64_t> input_strides_buffer(&kernel, input_strides.size());
RocmKernel::RocmAsyncBuffer<int64_t> output_strides_buffer(&kernel, output_strides.size());
for (int i = 0; i < input_strides.size(); i++) {
input_strides_buffer.CpuPtr()[i] = input_strides[i];
output_strides_buffer.CpuPtr()[i] = output_strides[i];
}
ORT_RETURN_IF_ERROR(input_strides_buffer.CopyToGpu());
ORT_RETURN_IF_ERROR(output_strides_buffer.CopyToGpu());
switch (element_size) {
case sizeof(int8_t):
hipLaunchKernelGGL(Transpose4DKernel<sizeof(int8_t)>, grid_size, block_size, 0, 0,
input_strides_buffer.GpuPtr(), input_data,
output_strides_buffer.GpuPtr(), output_data, N / num_elements_per_thread);
break;
case sizeof(int16_t):
hipLaunchKernelGGL(Transpose4DKernel<sizeof(int16_t)>, grid_size, block_size, 0, 0,
input_strides_buffer.GpuPtr(), input_data,
output_strides_buffer.GpuPtr(), output_data, N / num_elements_per_thread);
break;
case sizeof(int32_t):
hipLaunchKernelGGL(Transpose4DKernel<sizeof(int32_t)>, grid_size, block_size, 0, 0,
input_strides_buffer.GpuPtr(), input_data,
output_strides_buffer.GpuPtr(), output_data, N / num_elements_per_thread);
break;
case sizeof(int64_t):
hipLaunchKernelGGL(Transpose4DKernel<sizeof(int64_t)>, grid_size, block_size, 0, 0,
input_strides_buffer.GpuPtr(), input_data,
output_strides_buffer.GpuPtr(), output_data, N / num_elements_per_thread);
break;
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Type not supported for transpose on CUDA. Element size was ",
element_size);
}
return Status::OK();
}
template <typename T>
// __global__ void _TransposeKernel(int32_t shape_rank, const TArray<int64_t> input_strides,
// const T* __restrict__ input_data, const TArray<fast_divmod> output_strides, T* __restrict__ output_data, HIP_LONG N) {

View file

@ -4,10 +4,27 @@
#pragma once
#include <stdint.h>
#include "core/providers/rocm/shared_inc/rocm_utils.h"
#include "core/providers/rocm/tensor/transpose.h"
namespace onnxruntime {
namespace rocm {
bool CanDoTranspose3D(int32_t rank, const std::vector<int64_t>& input_dims, const std::vector<size_t>& permutations);
// Status Transpose3DImpl(size_t element_size, const TArray<int64_t>& input_shape, const TArray<int64_t>& input_strides, const void* input_data,
// void* output_data, int64_t N);
Status Transpose3DImpl(const Transpose& kernel, size_t element_size, const std::vector<int64_t>& input_shape, const std::vector<int64_t>& input_strides, const void* input_data,
void* output_data, int64_t N);
bool CanDoTranspose4D(const hipDeviceProp_t& prop,
size_t element_size,
int32_t rank,
const std::vector<int64_t>& input_dims,
const std::vector<size_t>& permutations);
Status Transpose4DImpl(const Transpose& kernel, size_t element_size, const std::vector<int64_t>& input_shape, const std::vector<int64_t>& input_strides, const void* input_data,
const std::vector<int64_t>& output_strides, void* output_data, int64_t N);
// Status TransposeImpl(size_t element_size, int32_t shape_rank, const TArray<int64_t>& input_strides,
// const void* input_data, const TArray<fast_divmod>& fdm_output_strides, void* output_data, int64_t N);
Status TransposeImpl(size_t element_size, int32_t shape_rank, const int64_t* input_strides,