Introduce ordered quantization ops for the CUDA EP [1/n] (#12582)

Initial core small set for the ordered quantization ops for cuda EP.
This commit is contained in:
Hariharan Seshadri 2022-09-07 11:58:15 -07:00 committed by GitHub
parent 69f7cc6494
commit ad69aac491
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1626 additions and 9 deletions

View file

@ -464,7 +464,7 @@ if (onnxruntime_USE_CUDA)
endif()
add_dependencies(onnxruntime_providers_cuda onnxruntime_providers_shared ${onnxruntime_EXTERNAL_DEPENDENCIES})
target_link_libraries(onnxruntime_providers_cuda PRIVATE cublas cudnn curand cufft ${ABSEIL_LIBS} ${ONNXRUNTIME_PROVIDERS_SHARED})
target_link_libraries(onnxruntime_providers_cuda PRIVATE cublasLt cublas cudnn curand cufft ${ABSEIL_LIBS} ${ONNXRUNTIME_PROVIDERS_SHARED})
if(onnxruntime_CUDNN_HOME)
target_include_directories(onnxruntime_providers_cuda PRIVATE ${onnxruntime_CUDNN_HOME}/include)
endif()

View file

@ -56,6 +56,9 @@ Do not modify directly.*
* <a href="#com.microsoft.QLinearReduceMean">com.microsoft.QLinearReduceMean</a>
* <a href="#com.microsoft.QLinearSigmoid">com.microsoft.QLinearSigmoid</a>
* <a href="#com.microsoft.QLinearSoftmax">com.microsoft.QLinearSoftmax</a>
* <a href="#com.microsoft.QOrderedGelu">com.microsoft.QOrderedGelu</a>
* <a href="#com.microsoft.QOrderedLayerNormalization">com.microsoft.QOrderedLayerNormalization</a>
* <a href="#com.microsoft.QOrderedMatMul">com.microsoft.QOrderedMatMul</a>
* <a href="#com.microsoft.QuantizeLinear">com.microsoft.QuantizeLinear</a>
* <a href="#com.microsoft.Range">com.microsoft.Range</a>
* <a href="#com.microsoft.ReduceSumInteger">com.microsoft.ReduceSumInteger</a>
@ -2862,6 +2865,163 @@ This version of the operator has been available since version 1 of the 'com.micr
</dl>
### <a name="com.microsoft.QOrderedGelu"></a><a name="com.microsoft.qorderedgelu">**com.microsoft.QOrderedGelu**</a>
Ordered Quantize Gelu.
#### Version
This version of the operator has been available since version 1 of the 'com.microsoft' operator set.
#### Attributes
<dl>
<dt><tt>order_X</tt> : int</dt>
<dd>cublasLt order of input X. Default is ROW MAJOR.</dd>
<dt><tt>order_Y</tt> : int</dt>
<dd>cublasLt order of matrix Y, must be same as order_X. Default is ROW MAJOR.</dd>
</dl>
#### Inputs
<dl>
<dt><tt>X</tt> : Q</dt>
<dd>N-dimensional input A</dd>
<dt><tt>scale_X</tt> : S</dt>
<dd>scale of the input A</dd>
<dt><tt>scale_Y</tt> : S</dt>
<dd>scale of the output Y</dd>
</dl>
#### Outputs
<dl>
<dt><tt>Y</tt> : Q</dt>
<dd>Output of the Gelu</dd>
</dl>
#### Type Constraints
<dl>
<dt><tt>Q</tt> : tensor(int8)</dt>
<dd>Constrain input and output types to int8 tensors.</dd>
<dt><tt>S</tt> : tensor(float)</dt>
<dd>Constrain scales to float32</dd>
</dl>
### <a name="com.microsoft.QOrderedLayerNormalization"></a><a name="com.microsoft.qorderedlayernormalization">**com.microsoft.QOrderedLayerNormalization**</a>
QOrderedLayerNormalization
#### Version
This version of the operator has been available since version 1 of the 'com.microsoft' operator set.
#### Attributes
<dl>
<dt><tt>axis</tt> : int</dt>
<dd>The first normalization dimension: normalization will be performed along dimensions axis : rank(inputs).</dd>
<dt><tt>epsilon</tt> : float</dt>
<dd>The epsilon value to use to avoid division by zero.</dd>
<dt><tt>order_X</tt> : int</dt>
<dd>cublasLt order of input X. Default is ROW MAJOR.</dd>
<dt><tt>order_Y</tt> : int</dt>
<dd>cublasLt order of matrix Y, must be same as order_X. Default is ROW MAJOR.</dd>
</dl>
#### Inputs
<dl>
<dt><tt>X</tt> : Q</dt>
<dd>Input data tensor from the previous layer.</dd>
<dt><tt>scale_X</tt> : S</dt>
<dd>scale of the quantized X</dd>
<dt><tt>scale</tt> : F</dt>
<dd>Scale tensor, i.e., gamma vector.</dd>
<dt><tt>B</tt> (optional) : F</dt>
<dd>Bias tensor.</dd>
<dt><tt>scale_Y</tt> : S</dt>
<dd>scale of the quantized X</dd>
</dl>
#### Outputs
<dl>
<dt><tt>Y</tt> : Q</dt>
<dd>Output data tensor.</dd>
</dl>
#### Type Constraints
<dl>
<dt><tt>F</tt> : tensor(float16), tensor(float)</dt>
<dd>Constrain input gamma and bias could be float16/float tensors. float may get better precision, float16 runs faster.</dd>
<dt><tt>S</tt> : tensor(float)</dt>
<dd>quantization scale must be float tensors.</dd>
<dt><tt>Q</tt> : tensor(int8)</dt>
<dd>quantization tensor must be int8 tensors.</dd>
</dl>
### <a name="com.microsoft.QOrderedMatMul"></a><a name="com.microsoft.qorderedmatmul">**com.microsoft.QOrderedMatMul**</a>
TODO
#### Version
This version of the operator has been available since version 1 of the 'com.microsoft' operator set.
#### Attributes
<dl>
<dt><tt>order_A</tt> : int</dt>
<dd>cublasLt order of matrix A. Default is ROW MAJOR.</dd>
<dt><tt>order_B</tt> : int</dt>
<dd>cublasLt order of matrix B. Default is ROW MAJOR.</dd>
<dt><tt>order_Y</tt> : int</dt>
<dd>cublasLt order of matrix Y and optional matrix C. Default is ROW MAJOR.</dd>
</dl>
#### Inputs (5 - 8)
<dl>
<dt><tt>A</tt> : Q</dt>
<dd>3-dimensional matrix A</dd>
<dt><tt>scale_A</tt> : S</dt>
<dd>scale of the input A</dd>
<dt><tt>B</tt> : Q</dt>
<dd>2-dimensional matrix B</dd>
<dt><tt>scale_B</tt> : S</dt>
<dd>scale of the input B</dd>
<dt><tt>scale_Y</tt> : S</dt>
<dd>scale of the output Y</dd>
<dt><tt>bias</tt> (optional) : S</dt>
<dd>1d bias</dd>
<dt><tt>C</tt> (optional) : Q</dt>
<dd>3d or 2d matrix C. if 2d expand to 3d first. Shape[0] should be 1 or same as A.shape[0] </dd>
<dt><tt>scale_C</tt> (optional) : S</dt>
<dd>scale of the input A</dd>
</dl>
#### Outputs
<dl>
<dt><tt>Y</tt> : Q</dt>
<dd>Matrix multiply results from A * B</dd>
</dl>
#### Type Constraints
<dl>
<dt><tt>Q</tt> : tensor(int8)</dt>
<dd>Constrain input and output types to int8 tensors.</dd>
<dt><tt>S</tt> : tensor(float)</dt>
<dd>Constrain bias and scales to float32</dd>
</dl>
### <a name="com.microsoft.QuantizeLinear"></a><a name="com.microsoft.quantizelinear">**com.microsoft.QuantizeLinear**</a>
The linear quantization operator. It consumes a full precision data, a scale, a zero point to compute the low precision / quantized tensor.

View file

@ -780,6 +780,9 @@ Do not modify directly.*
|LongformerAttention|*in* input:**T**<br> *in* weight:**T**<br> *in* bias:**T**<br> *in* mask:**T**<br> *in* global_weight:**T**<br> *in* global_bias:**T**<br> *in* global:**G**<br> *out* output:**T**|1+|**T** = tensor(float), tensor(float16)|
|NGramRepeatBlock|*in* input_ids:**Tid**<br> *in* scores:**T**<br> *out* scores_out:**T**|1+|**T** = tensor(float)<br/> **Tid** = tensor(int64)|
|QAttention|*in* input:**T1**<br> *in* weight:**T2**<br> *in* bias:**T3**<br> *in* input_scale:**T3**<br> *in* weight_scale:**T3**<br> *in* mask_index:**T4**<br> *in* input_zero_point:**T1**<br> *in* weight_zero_point:**T2**<br> *in* past:**T3**<br> *out* output:**T3**<br> *out* present:**T3**|1+|**T1** = tensor(int8)<br/> **T2** = tensor(int8)<br/> **T3** = tensor(float), tensor(float16)<br/> **T4** = tensor(int32)|
|QOrderedGelu|*in* X:**Q**<br> *in* scale_X:**S**<br> *in* scale_Y:**S**<br> *out* Y:**Q**|1+|**Q** = tensor(int8)<br/> **S** = tensor(float)|
|QOrderedLayerNormalization|*in* X:**Q**<br> *in* scale_X:**S**<br> *in* scale:**F**<br> *in* B:**F**<br> *in* scale_Y:**S**<br> *out* Y:**Q**|1+|**F** = tensor(float), tensor(float16)<br/> **Q** = tensor(int8)<br/> **S** = tensor(float)|
|QOrderedMatMul|*in* A:**Q**<br> *in* scale_A:**S**<br> *in* B:**Q**<br> *in* scale_B:**S**<br> *in* scale_Y:**S**<br> *in* bias:**S**<br> *in* C:**Q**<br> *in* scale_C:**S**<br> *out* Y:**Q**|1+|**Q** = tensor(int8)<br/> **S** = tensor(float)|
|QuantizeLinear|*in* x:**T1**<br> *in* y_scale:**T1**<br> *in* y_zero_point:**T2**<br> *out* y:**T2**|1+|**T1** = tensor(float16)<br/> **T2** = tensor(int8), tensor(uint8)|
|Rfft|*in* X:**T**<br> *out* Y:**T**|1+|**T** = tensor(double), tensor(float), tensor(float16)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**|1+|**T** = tensor(float), tensor(float16)|

View file

@ -97,6 +97,9 @@ class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, BFloat16, TransposeMatMul); // backward compatibility
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, BFloat16, FusedMatMul);
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, BFloat16_float_BFloat16, LayerNormalization);
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedMatMul);
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedLayerNormalization);
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedGelu);
#ifdef ENABLE_ATEN
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kPytorchAtenDomain, 1, ATen);
@ -201,6 +204,9 @@ Status RegisterCudaContribKernels(KernelRegistry& kernel_registry) {
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, BFloat16, FusedMatMul)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, BFloat16_float_BFloat16, LayerNormalization)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, float, FusedConv)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedMatMul)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedLayerNormalization)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kMSDomain, 1, QOrderedGelu)>,
#ifdef ENABLE_ATEN
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kPytorchAtenDomain, 1, ATen)>,

View file

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/cuda/cu_inc/common.cuh"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
__device__ inline float ToFloat(const __half h) { return __half2float(h); }
__device__ inline float ToFloat(const float f) { return f; }
template <typename T>
__inline__ __device__ T
WarpReduceSum(T val) {
val += __shfl_xor_sync(0xFFFFFFFF, val, 1);
val += __shfl_xor_sync(0xFFFFFFFF, val, 2);
val += __shfl_xor_sync(0xFFFFFFFF, val, 4);
val += __shfl_xor_sync(0xFFFFFFFF, val, 8);
val += __shfl_xor_sync(0xFFFFFFFF, val, 16);
return val;
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_layer_norm.h"
#include "qordered_layer_norm_impl.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
ONNX_OPERATOR_KERNEL_EX(
QOrderedLayerNormalization,
kMSDomain,
1,
kCudaExecutionProvider,
(*KernelDefBuilder::Create())
.TypeConstraint("Q", DataTypeImpl::GetTensorType<int8_t>())
.TypeConstraint("S", DataTypeImpl::GetTensorType<float>())
.TypeConstraint("F", BuildKernelDefConstraints<float, MLFloat16>())
.InputMemoryType(OrtMemTypeCPUInput, 1) // scale_X
.InputMemoryType(OrtMemTypeCPUInput, 4), // scale_Y
QOrderedLayerNormalization);
QOrderedLayerNormalization::QOrderedLayerNormalization(const OpKernelInfo& op_kernel_info)
: CudaKernel(op_kernel_info) {
ORT_ENFORCE(op_kernel_info.GetAttr("axis", &axis_).IsOK());
float tmp_epsilon;
ORT_ENFORCE(op_kernel_info.GetAttr<float>("epsilon", &tmp_epsilon).IsOK());
epsilon_ = tmp_epsilon;
ORT_ENFORCE(op_kernel_info.GetAttr("order_X", &order_X_).IsOK());
ORT_ENFORCE(op_kernel_info.GetAttr("order_Y", &order_Y_).IsOK());
ORT_ENFORCE(order_X_ == 1, "QOrderedLayerNormlalization: Only Row major data ordering is currently supported");
ORT_ENFORCE(order_X_ == order_Y_, "QOrderedLayerNormlalization: Input ordering should match the output ordering");
}
Status QOrderedLayerNormalization::ComputeInternal(OpKernelContext* ctx) const {
typedef typename ToCudaType<int8_t>::MappedType CudaQ;
// Inputs
const Tensor* X = ctx->Input<Tensor>(0);
const auto* X_data = reinterpret_cast<const CudaQ*>(X->Data<int8_t>());
const TensorShape& x_shape = X->Shape();
ORT_ENFORCE(x_shape.GetDims().size() == 3,
"QOrderedLayerNormlalization: Input shape must be {batch, rows, cols}");
const Tensor* scale = ctx->Input<Tensor>(2);
const void* scale_data = scale->DataRaw();
const Tensor* bias = ctx->Input<Tensor>(3);
const void* bias_data = (nullptr == bias) ? nullptr : bias->DataRaw();
const int64_t axis = HandleNegativeAxis(axis_, x_shape.NumDimensions());
ORT_ENFORCE(axis == 2,
"QOrderedLayerNormlalization: Implementation only "
"supports on normalizing on innermost axis");
unsigned int batch = gsl::narrow<unsigned int>(x_shape.GetDims()[0]);
unsigned int rows = gsl::narrow<unsigned int>(x_shape.GetDims()[1]);
unsigned int cols = gsl::narrow<unsigned int>(x_shape.GetDims()[2]);
if (cols & 0x03) {
return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED,
"QOrderedLayerNormlalization: Cols MUST be a multiple of 4");
}
// Outputs
Tensor* Y = ctx->Output(0, x_shape);
if (x_shape.Size() == 0) {
return Status::OK();
}
auto* Y_data = reinterpret_cast<CudaQ*>(Y->MutableData<int8_t>());
const float* scale_x = ctx->Input<Tensor>(1)->Data<float>();
const float* scale_y = ctx->Input<Tensor>(4)->Data<float>();
if (scale->IsDataType<MLFloat16>()) {
QOrderedLayerNorm(Stream(), GetDeviceProp(), static_cast<cublasLtOrder_t>(order_X_),
X_data, *scale_x, Y_data, *scale_y, static_cast<const __half*>(scale_data),
static_cast<const __half*>(bias_data),
static_cast<float>(epsilon_), batch, rows, cols);
} else {
QOrderedLayerNorm(Stream(), GetDeviceProp(), static_cast<cublasLtOrder_t>(order_X_),
X_data, *scale_x, Y_data, *scale_y, static_cast<const float*>(scale_data),
static_cast<const float*>(bias_data),
static_cast<float>(epsilon_), batch, rows, cols);
}
return Status::OK();
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_kernel.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
class QOrderedLayerNormalization final : public CudaKernel {
public:
explicit QOrderedLayerNormalization(const OpKernelInfo& op_kernel_info);
Status ComputeInternal(OpKernelContext* ctx) const override;
private:
double epsilon_;
int64_t axis_;
int64_t order_X_;
int64_t order_Y_;
};
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/cuda/cu_inc/common.cuh"
#include "qordered_common.cuh"
#include "qordered_layer_norm_impl.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
constexpr static unsigned QORDER_LAYERNORM_ROWS_PER_BLOCK = 8;
__device__ inline int8_t QuantizeFloatS8(const float val, const float inverse_scale) {
float dqval = fmaxf(fminf(127.0f, val * inverse_scale), -128.0f);
return static_cast<int8_t>(__float2int_rn(dqval));
}
// TODO: Const Ref ?
__device__ inline char4 QuantizeFloat4Char4(const float4 val4, const float rscale) {
return char4{QuantizeFloatS8(val4.x, rscale), QuantizeFloatS8(val4.y, rscale),
QuantizeFloatS8(val4.z, rscale), QuantizeFloatS8(val4.w, rscale)};
}
// TODO: Const Ref ?
__device__ inline int32_t Dp4a_Defined(const char4 input_1, const char4 input_2) {
return static_cast<int8_t>(input_1.x) * static_cast<int8_t>(input_2.x) +
static_cast<int8_t>(input_1.y) * static_cast<int8_t>(input_2.y) +
static_cast<int8_t>(input_1.z) * static_cast<int8_t>(input_2.z) +
static_cast<int8_t>(input_1.w) * static_cast<int8_t>(input_2.w);
}
template <typename T>
__global__ void QOrderedLayerNormRowKernel(const int8_t* __restrict__ src, const float src_scale,
int8_t* __restrict__ dst, const float dst_scale,
const T* __restrict__ gamma, const T* __restrict__ beta, const float epsilon,
const unsigned rows, const unsigned cols) {
int32_t sum = 0;
int32_t square_sum = 0;
unsigned r = blockIdx.x * QORDER_LAYERNORM_ROWS_PER_BLOCK + threadIdx.y;
if (rows <= r) {
return;
}
const size_t batch_row_index = static_cast<size_t>(blockIdx.y) * (rows * cols) + r * cols;
src += batch_row_index;
dst += batch_row_index;
for (unsigned c = threadIdx.x << 2; c < cols; c += 128) {
char4 ch4 = __ldg(reinterpret_cast<const char4*>(src + c));
sum += (static_cast<short>(ch4.x) + static_cast<short>(ch4.y) +
static_cast<short>(ch4.z) + static_cast<short>(ch4.w));
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 610
square_sum = __dp4a(ch4, ch4, square_sum);
#else
square_sum = Dp4a_Defined(ch4, ch4);
#endif
}
sum = WarpReduceSum<int32_t>(sum);
square_sum = WarpReduceSum<int32_t>(square_sum);
const float mean = __double2float_rn(src_scale * (double)sum / cols);
const float rvar = rsqrtf(src_scale * src_scale * __double2float_rn(static_cast<double>(square_sum) - (static_cast<double>(sum) * static_cast<double>(sum) / static_cast<double>(cols))) / cols + epsilon);
const float dst_rscale = 1.0f / dst_scale;
float4 f4;
for (unsigned c = threadIdx.x << 2; c < cols; c += 128) {
char4 ch4 = __ldg(reinterpret_cast<const char4*>(src + c));
f4.x = (src_scale * ch4.x - mean) * rvar * ToFloat(gamma[c]);
f4.y = (src_scale * ch4.y - mean) * rvar * ToFloat(gamma[c + 1]);
f4.z = (src_scale * ch4.z - mean) * rvar * ToFloat(gamma[c + 2]);
f4.w = (src_scale * ch4.w - mean) * rvar * ToFloat(gamma[c + 3]);
if (beta) {
f4.x += ToFloat(beta[c]);
f4.y += ToFloat(beta[c + 1]);
f4.z += ToFloat(beta[c + 2]);
f4.w += ToFloat(beta[c + 3]);
}
*reinterpret_cast<char4*>(dst + c) = QuantizeFloat4Char4(f4, dst_rscale);
}
}
template <typename T>
void QOrderedLayerNorm(cudaStream_t stream, const cudaDeviceProp& /*device_prop*/, cublasLtOrder_t order,
const int8_t* src, const float src_scale, int8_t* dst, const float dst_scale,
const T* gamma, const T* beta, const float epsilon,
const unsigned batch, const unsigned rows, const unsigned cols) {
// The implementation only supports Row major tensor data ordering for now
ORT_ENFORCE(order == CUBLASLT_ORDER_ROW);
dim3 threads(32, QORDER_LAYERNORM_ROWS_PER_BLOCK, 1);
dim3 blocks(static_cast<unsigned>(rows + QORDER_LAYERNORM_ROWS_PER_BLOCK - 1) / QORDER_LAYERNORM_ROWS_PER_BLOCK,
static_cast<unsigned>(batch), 1);
QOrderedLayerNormRowKernel<T><<<blocks, threads, 0, stream>>>(src, src_scale,
dst, dst_scale, gamma, beta,
epsilon, rows, cols);
}
template void QOrderedLayerNorm<float>(cudaStream_t stream, const cudaDeviceProp& /*device_prop*/, cublasLtOrder_t order,
const int8_t* src, const float src_scale, int8_t* dst, const float dst_scale,
const float* gamma, const float* beta, const float epsilon,
const unsigned batch, const unsigned rows, const unsigned cols);
template void QOrderedLayerNorm<__half>(cudaStream_t stream, const cudaDeviceProp& /*device_prop*/, cublasLtOrder_t order,
const int8_t* src, const float src_scale, int8_t* dst, const float dst_scale,
const __half* gamma, const __half* beta, const float epsilon,
const unsigned batch, const unsigned rows, const unsigned cols);
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_common.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
template <typename T>
void QOrderedLayerNorm(
cudaStream_t stream, const cudaDeviceProp& device_prop, cublasLtOrder_t order,
const int8_t* src, const float src_scale, int8_t* dst, const float dst_scale,
const T* gamma, const T* beta, const float epsilon,
unsigned batch, unsigned rows, unsigned cols);
}
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_matmul.h"
#include "qordered_matmul_utils.h"
#include <functional>
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
ONNX_OPERATOR_KERNEL_EX(
QOrderedMatMul,
kMSDomain,
1,
kCudaExecutionProvider,
(*KernelDefBuilder::Create())
.TypeConstraint("Q", DataTypeImpl::GetTensorType<int8_t>())
.TypeConstraint("S", DataTypeImpl::GetTensorType<float>())
.InputMemoryType(OrtMemTypeCPUInput, 1) // scale_A
.InputMemoryType(OrtMemTypeCPUInput, 3) // scale_B
.InputMemoryType(OrtMemTypeCPUInput, 4) // scale_Y
.InputMemoryType(OrtMemTypeCPUInput, 7), // scale_C
QOrderedMatMul);
static Status ParseRowMajorTensorMetadata(const Tensor& input_tensor, int64_t& rows,
int64_t& cols, int64_t& batch_count, int64_t& element_count) {
const auto& dims = input_tensor.Shape().GetDims();
cols = dims.back();
rows = (dims.size() <= 1 ? 1LL : dims[dims.size() - 2]);
batch_count = (dims.size() <= 2
? 1LL
: std::accumulate(dims.begin(), dims.begin() + (dims.size() - 2),
1LL, std::multiplies<int64_t>()));
element_count = cols * rows * batch_count;
return Status::OK();
}
QOrderedMatMul::QOrderedMatMul(const OpKernelInfo& info) : CudaKernel(info) {
ORT_ENFORCE(info.GetAttr("order_A", &order_A_).IsOK());
ORT_ENFORCE(info.GetAttr("order_B", &order_B_).IsOK());
ORT_ENFORCE(info.GetAttr("order_Y", &order_Y_).IsOK());
ORT_ENFORCE(order_B_ == CUBLASLT_ORDER_COL &&
order_A_ == CUBLASLT_ORDER_ROW &&
order_Y_ == CUBLASLT_ORDER_ROW,
"QOrderedMatMul: Input 1 and output's data ordering should be ROW_MAJOR and "
"Input 2's data ordering should be COL_MAJOR");
}
Status QOrderedMatMul::QOrderedMatMul::ComputeInternal(OpKernelContext* context) const {
int64_t rows_A = 0, cols_A = 0, batch_A = 1, elements_A = 0;
int64_t rows_B = 0, cols_B = 0, batch_B = 1, elements_B = 0;
int64_t rows_C = 0, cols_C = 0, batch_C = 1, elements_C = 0;
const Tensor& tensor_A = *context->Input<Tensor>(0);
const Tensor& tensor_B = *context->Input<Tensor>(2);
// Support General case only. No broadcasting, is handled now.
ORT_ENFORCE(tensor_A.Shape().NumDimensions() == 2 || tensor_A.Shape().NumDimensions() == 3);
ORT_ENFORCE(tensor_B.Shape().NumDimensions() == 2 || tensor_B.Shape().NumDimensions() == 3);
ORT_RETURN_IF_ERROR(ParseRowMajorTensorMetadata(tensor_A, rows_A, cols_A, batch_A, elements_A));
ORT_RETURN_IF_ERROR(ParseRowMajorTensorMetadata(tensor_B, rows_B, cols_B, batch_B, elements_B));
const float* scale_A = context->Input<Tensor>(1)->Data<float>();
const float* scale_B = context->Input<Tensor>(3)->Data<float>();
const float* scale_Y = context->Input<Tensor>(4)->Data<float>();
ORT_ENFORCE(*scale_Y > 0.0f && *scale_A > 0.0f && *scale_B > 0.0f);
const Tensor* tensor_bias = context->Input<Tensor>(5);
ORT_ENFORCE(tensor_bias == nullptr ||
(tensor_bias->Shape().NumDimensions() == 1 && tensor_bias->Shape()[0] == cols_B));
const float* bias = (tensor_bias == nullptr) ? nullptr : tensor_bias->Data<float>();
ORT_ENFORCE(batch_A == batch_B || batch_B == 1, "Batch count for matrix A and matrix B does not match");
ORT_ENFORCE(cols_A == rows_B, "MatMul shape mis-match");
TensorShape output_shape(tensor_A.Shape());
output_shape[output_shape.NumDimensions() - 1] = cols_B;
const float zero = 0.0f;
const float* scale_C = &zero;
const int8_t* C = nullptr;
const Tensor* tensor_C = context->Input<Tensor>(6);
if (tensor_C != nullptr) {
ORT_ENFORCE(tensor_C->Shape().NumDimensions() == 2 || tensor_C->Shape().NumDimensions() == 3);
ORT_RETURN_IF_ERROR(ParseRowMajorTensorMetadata(*tensor_C, rows_C, cols_C, batch_C, elements_C));
ORT_ENFORCE(batch_C == batch_A || batch_C == 1);
ORT_ENFORCE(rows_C == rows_A && cols_C == cols_B);
const Tensor* tensor_scale_C = context->Input<Tensor>(7);
ORT_ENFORCE(tensor_scale_C != nullptr);
scale_C = tensor_scale_C->Data<float>();
C = tensor_C->Data<int8_t>();
}
Tensor* tensor_Y = context->Output(0, output_shape);
cublasLtHandle_t cublasLt = CublasLtHandle();
cudaStream_t stream = Stream();
auto& device_prop = GetDeviceProp();
const float alpha = *scale_A * *scale_B / *scale_Y;
const float beta = *scale_C / *scale_Y;
ORT_RETURN_IF_ERROR(QOrdered_MatMul(cublasLt, stream, device_prop,
static_cast<int32_t>(batch_A), rows_A, cols_B, cols_A,
&alpha, tensor_A.Data<int8_t>(), tensor_B.Data<int8_t>(),
static_cast<int32_t>(batch_B), bias,
&beta, C, static_cast<int32_t>(batch_C),
tensor_Y->MutableData<int8_t>(), static_cast<cublasLtOrder_t>(order_B_)));
return Status::OK();
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_kernel.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
class QOrderedMatMul final : public CudaKernel {
public:
explicit QOrderedMatMul(const OpKernelInfo& info);
Status ComputeInternal(OpKernelContext* context) const override;
private:
int64_t order_A_;
int64_t order_B_;
int64_t order_Y_;
};
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,209 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_matmul_utils.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
int64_t CalcLeadingDimensionLt(int64_t rows, int64_t cols, cublasLtOrder_t order) {
switch (order) {
case CUBLASLT_ORDER_ROW:
return cols;
case CUBLASLT_ORDER_COL:
return rows;
// TODO: Support other CUBLASLT ordering
default:
return 0;
}
}
static Status cublasLtMatMulInt8SetupAlgo(cublasLtHandle_t cublasLt_handle, cublasLtMatmulAlgo_t& algo,
int algo_id, int swizzle,
int custom_option, int tile, int splitk_val,
int reduction_scheme, int stages) {
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoInit(cublasLt_handle, CUBLAS_COMPUTE_32I, CUDA_R_32F,
CUDA_R_8I, CUDA_R_8I, CUDA_R_8I, CUDA_R_8I, algo_id, &algo));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo,
CUBLASLT_ALGO_CONFIG_CUSTOM_OPTION,
&(custom_option), sizeof(custom_option)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo,
CUBLASLT_ALGO_CONFIG_TILE_ID,
&(tile), sizeof(tile)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo, CUBLASLT_ALGO_CONFIG_SPLITK_NUM,
&(splitk_val), sizeof(splitk_val)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo, CUBLASLT_ALGO_CONFIG_CTA_SWIZZLING,
&(swizzle), sizeof(swizzle)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo, CUBLASLT_ALGO_CONFIG_REDUCTION_SCHEME,
&(reduction_scheme), sizeof(int)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulAlgoConfigSetAttribute(&algo, CUBLASLT_ALGO_CONFIG_STAGES_ID,
&(stages), sizeof(stages)));
return Status::OK();
}
static inline std::string AlgoKey(const cudaDeviceProp& /*device_prop*/,
int batch_count, int m, int n, int k,
cublasLtOrder_t weight_order, cublasLtOrder_t input_output_order) {
std::stringstream ss;
ss << batch_count << "-" << m << "_" << n << "_" << k << "-"
<< static_cast<int>(weight_order) << "-" << static_cast<int>(input_output_order);
return ss.str();
}
CublasLtMMAlgoMap& CublasLtMMAlgoMap::Instance() {
static CublasLtMMAlgoMap instance;
return instance;
}
void CublasLtMMAlgoMap::GetAlgo(cublasLtHandle_t cublasLt_handle, cublasLtMatmulAlgo_t& algo,
const cudaDeviceProp& device_prop,
int batch_count, int m, int n, int k,
cublasLtOrder_t weight_order,
cublasLtOrder_t input_output_order) const {
ORT_ENFORCE(input_output_order == CUBLASLT_ORDER_ROW, "Input/Output should be ORDER_ROW");
ORT_ENFORCE(weight_order == CUBLASLT_ORDER_COL, "Weight should be ORDER_COL");
const std::string& key = AlgoKey(device_prop, batch_count, m, n, k, weight_order, input_output_order);
// TODO: Pre-load the best_algos_ from a config file which will be generated offline (or)
// Find the best algo dynamically in the warm-up run and cache it in `best_algos_`
auto algo_it = best_algos_.find(key);
if (algo_it != best_algos_.end() && algo_it->second.workspace_size == 0) {
const auto& algo_info = algo_it->second;
ORT_THROW_IF_ERROR(cublasLtMatMulInt8SetupAlgo(cublasLt_handle, algo, algo_info.algo_id,
algo_info.swizzle, algo_info.custom_option,
algo_info.tile, algo_info.splitk_val,
algo_info.reduction_scheme, algo_info.stages));
} else {
// Default algo
int algo_id = 21;
int stages = 0;
ORT_THROW_IF_ERROR(cublasLtMatMulInt8SetupAlgo(cublasLt_handle, algo, algo_id, 0, 0, 20, 0, 0, stages));
}
}
static Status CreateLtMatrixLayout(cublasLtMatrixLayout_t& layout_desc,
int const batch_count, int64_t const rows_after_op, int64_t const cols_after_op,
cudaDataType_t const data_type, cublasLtOrder_t const mat_order,
cublasOperation_t const mat_trans) {
if (mat_trans == CUBLAS_OP_T) {
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutCreate(&layout_desc, data_type, cols_after_op, rows_after_op,
CalcLeadingDimensionLt(cols_after_op, rows_after_op, mat_order)));
} else {
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutCreate(&layout_desc, data_type, rows_after_op, cols_after_op,
CalcLeadingDimensionLt(rows_after_op, cols_after_op, mat_order)));
}
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutSetAttribute(layout_desc,
CUBLASLT_MATRIX_LAYOUT_ORDER,
&mat_order, sizeof(mat_order)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutSetAttribute(layout_desc,
CUBLASLT_MATRIX_LAYOUT_BATCH_COUNT,
&batch_count, sizeof(batch_count)));
if (batch_count > 1) {
int64_t batch_stride = rows_after_op * cols_after_op;
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutSetAttribute(layout_desc, CUBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET,
&batch_stride, sizeof(batch_stride)));
}
return Status::OK();
}
Status QOrdered_MatMul(cublasLtHandle_t cublasLt_handle, cudaStream_t stream,
const cudaDeviceProp& device_prop,
int32_t batch_count, int64_t m, int64_t n, int64_t k,
const float* alpha, const int8_t* A, const int8_t* B, int32_t batch_B,
const float* bias, const float* beta,
const int8_t* C, int32_t batch_C,
int8_t* D, cublasLtOrder_t weight_order) {
ORT_ENFORCE(weight_order == CUBLASLT_ORDER_COL, "Weight should be ORDER_COL");
const cublasOperation_t transpose_op = CUBLAS_OP_T;
cublasLtMatmulDesc_t matmul_desc = nullptr;
auto clean_matmul_desc = gsl::finally([&matmul_desc]() {if (matmul_desc) cublasLtMatmulDescDestroy(matmul_desc); });
cublasLtMatrixLayout_t desc_A = nullptr;
auto clean_desc_A = gsl::finally([&desc_A]() {if (desc_A) cublasLtMatrixLayoutDestroy(desc_A); });
cublasLtMatrixLayout_t desc_B = nullptr;
auto clean_desc_B = gsl::finally([&desc_B]() {if (desc_B) cublasLtMatrixLayoutDestroy(desc_B); });
cublasLtMatrixLayout_t desc_C = nullptr;
auto clean_desc_C = gsl::finally([&desc_C]() {if (desc_C) cublasLtMatrixLayoutDestroy(desc_C); });
cublasLtMatrixLayout_t desc_D = nullptr;
auto clean_desc_D = gsl::finally([&desc_D]() {if (desc_D) cublasLtMatrixLayoutDestroy(desc_D); });
const float beta_zero = 0.0f;
beta = (C == nullptr ? &beta_zero : beta);
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulDescCreate(&matmul_desc, CUBLAS_COMPUTE_32I, CUDA_R_32F));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulDescSetAttribute(matmul_desc,
CUBLASLT_MATMUL_DESC_TRANSA,
&transpose_op, sizeof(transpose_op)));
if (bias != nullptr) {
cublasLtEpilogue_t epilogue_bias = CUBLASLT_EPILOGUE_BIAS;
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulDescSetAttribute(matmul_desc,
CUBLASLT_MATMUL_DESC_EPILOGUE,
&epilogue_bias, sizeof(epilogue_bias)));
CUBLAS_RETURN_IF_ERROR(cublasLtMatmulDescSetAttribute(matmul_desc,
CUBLASLT_MATMUL_DESC_BIAS_POINTER,
&bias, sizeof(bias)));
}
ORT_RETURN_IF_ERROR(CreateLtMatrixLayout(desc_A, batch_count, k, m, CUDA_R_8I,
CUBLASLT_ORDER_COL, CUBLAS_OP_N)); // for A'
ORT_RETURN_IF_ERROR(CreateLtMatrixLayout(desc_B, batch_B, k, n, CUDA_R_8I,
CUBLASLT_ORDER_COL, CUBLAS_OP_N)); // For B
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutSetAttribute(desc_B, CUBLASLT_MATRIX_LAYOUT_BATCH_COUNT,
&batch_count, sizeof(batch_count)));
ORT_RETURN_IF_ERROR(CreateLtMatrixLayout(desc_D, batch_count, n, m,
CUDA_R_8I, CUBLASLT_ORDER_COL,
CUBLAS_OP_N)); // For D'
if (C != nullptr) {
ORT_RETURN_IF_ERROR(CreateLtMatrixLayout(desc_C, batch_C, n, m, CUDA_R_8I,
CUBLASLT_ORDER_COL, CUBLAS_OP_N)); // For C'
CUBLAS_RETURN_IF_ERROR(cublasLtMatrixLayoutSetAttribute(desc_C, CUBLASLT_MATRIX_LAYOUT_BATCH_COUNT,
&batch_count, sizeof(batch_count)));
}
cublasLtMatmulAlgo_t algo;
CublasLtMMAlgoMap::Instance().GetAlgo(cublasLt_handle, algo, device_prop, batch_count,
static_cast<int>(m), static_cast<int>(n), static_cast<int>(k),
weight_order, CUBLASLT_ORDER_ROW);
CUBLAS_RETURN_IF_ERROR(cublasLtMatmul(cublasLt_handle, matmul_desc, alpha,
B, desc_B,
A, desc_A,
beta,
C == nullptr ? D : C, C == nullptr ? desc_D : desc_C,
D, desc_D,
&algo, nullptr, 0, // algo, workspace, workspace_size
stream));
return Status::OK();
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_common.h"
#include <string>
#include <unordered_set>
namespace onnxruntime {
namespace contrib {
namespace cuda {
class CublasLtMMAlgoMap {
public:
static CublasLtMMAlgoMap& Instance();
void GetAlgo(cublasLtHandle_t cublasLt_handle, cublasLtMatmulAlgo_t& algo,
const cudaDeviceProp& device_prop,
int batch_count, int m, int n, int k,
cublasLtOrder_t weight_order,
cublasLtOrder_t input_output_order = CUBLASLT_ORDER_ROW) const;
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(CublasLtMMAlgoMap);
private:
CublasLtMMAlgoMap() = default;
~CublasLtMMAlgoMap() = default;
struct CublasLtMatmulAlgoInfo {
int algo_id, custom_option, tile, splitk_val,
swizzle, reduction_scheme, workspace_size, stages;
float exec_time;
};
std::unordered_map<std::string, CublasLtMatmulAlgoInfo> best_algos_;
};
Status QOrdered_MatMul(cublasLtHandle_t cublasLt_handle, cudaStream_t stream,
const cudaDeviceProp& device_prop,
int32_t batch_count, int64_t m, int64_t n, int64_t k,
const float* alpha, const int8_t* A, const int8_t* B,
int32_t batch_B,
const float* bias, const float* beta,
const int8_t* C, int32_t batch_C,
int8_t* D, cublasLtOrder_t weight_order);
Status QOrdered_MatMul(cublasLtHandle_t cublasLt_handle, cudaStream_t stream,
const cudaDeviceProp& device_prop,
int32_t batch_count, int64_t m, int64_t n, int64_t k,
const float* alpha, const int8_t* A, const int8_t* B,
const float* bias, int8_t* D, cublasLtOrder_t order_weight);
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_unary_ops.h"
#include "qordered_unary_ops_impl.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
ONNX_OPERATOR_KERNEL_EX(
QOrderedGelu,
kMSDomain,
1,
kCudaExecutionProvider,
(*KernelDefBuilder::Create())
.TypeConstraint("Q", DataTypeImpl::GetTensorType<int8_t>())
.TypeConstraint("S", DataTypeImpl::GetTensorType<float>())
.InputMemoryType(OrtMemTypeCPUInput, 1) // scale_input
.InputMemoryType(OrtMemTypeCPUInput, 2), // scale_output
QOrderedGelu);
Status QOrderedGelu::ComputeInternal(OpKernelContext* context) const {
const Tensor& input = *context->Input<Tensor>(0);
const float* scale_input = context->Input<Tensor>(1)->Data<float>();
const float* scale_output = context->Input<Tensor>(2)->Data<float>();
const auto& shape = input.Shape();
Tensor* output = context->Output(0, shape);
QOrderedUnarySharedMemory_Gelu(Stream(), input.Data<int8_t>(), scale_input, output->MutableData<int8_t>(),
scale_output, static_cast<size_t>(shape.Size()));
return Status::OK();
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_kernel.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
class QOrderedGelu final : public CudaKernel {
public:
explicit QOrderedGelu(const OpKernelInfo& info) : CudaKernel(info) {}
Status ComputeInternal(OpKernelContext* context) const override;
};
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_unary_ops_impl.h"
#include "core/providers/cuda/cu_inc/common.cuh"
namespace onnxruntime {
namespace contrib {
namespace cuda {
using namespace onnxruntime::cuda;
constexpr int kNumLinePerThread = 4;
constexpr int kNumThreadsPerBlock = 256;
constexpr int kNumElementsPerBlockLine = sizeof(char4) * kNumThreadsPerBlock;
constexpr int kNumElementsPerBlock = sizeof(char4) * kNumLinePerThread * kNumThreadsPerBlock;
template <typename FuncT>
__global__ void QOrderedUnaryElementWiseSharedMemoryKernel(
const int8_t* input_data, float input_scale, int8_t* output_data,
float inverse_output_scale, const FuncT& functor, CUDA_LONG N) {
__shared__ char table[256];
const int calc_id = static_cast<int>(threadIdx.x) - 128;
float gelu_value = inverse_output_scale * functor(input_scale * calc_id);
gelu_value = fmaxf(-128.0f, fmin(127.0f, gelu_value));
table[threadIdx.x] = static_cast<char>(__float2int_rn(gelu_value));
__syncthreads();
CUDA_LONG id = kNumElementsPerBlock * blockIdx.x + threadIdx.x * (CUDA_LONG)sizeof(char4);
#pragma unroll
for (int line = 0; line < kNumLinePerThread; line++) {
if (id < N) {
char4 i4 = *(const char4*)(input_data + id);
i4.x = table[128 + i4.x];
i4.y = table[128 + i4.y];
i4.z = table[128 + i4.z];
i4.w = table[128 + i4.w];
*reinterpret_cast<char4*>(output_data + id) = i4;
id += kNumElementsPerBlockLine;
}
}
}
template <typename FuncT>
void QOrderedUnaryElementWiseSharedMemoryImpl(
cudaStream_t stream,
const int8_t* input_data,
const float* input_scale,
int8_t* output_data,
const float* output_scale,
const FuncT& func,
size_t count) {
if (count > 0) {
float inverse_output_scale = 1.0f / *output_scale;
int blocksPerGrid = static_cast<int>(CeilDiv(count, kNumElementsPerBlock));
QOrderedUnaryElementWiseSharedMemoryKernel<FuncT><<<blocksPerGrid, kNumThreadsPerBlock, 0, stream>>>(
input_data, *input_scale, output_data, inverse_output_scale, func, static_cast<CUDA_LONG>(count));
}
}
struct QOrderedUnaryOpGelu {
static constexpr float sqrt2 = 1.4142135623730950488016887242097f;
__device__ __inline__ float operator()(const float& x) const {
return x * 0.5f * (1.0f + erff(x / sqrt2));
}
};
QORDERED_UNARY_OP_SHARED_MEMORY_DECLARATION(Gelu) {
QOrderedUnaryElementWiseSharedMemoryImpl<QOrderedUnaryOpGelu>(
stream, input_data, input_scale, output_data, output_scale, QOrderedUnaryOpGelu(), count);
}
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/providers/cuda/cuda_common.h"
namespace onnxruntime {
namespace contrib {
namespace cuda {
#define QORDERED_UNARY_OP_SHARED_MEMORY_DECLARATION(name) \
void QOrderedUnarySharedMemory_##name( \
cudaStream_t stream, \
const int8_t* input_data, \
const float* input_scale, \
int8_t* output_data, \
const float* output_scale, \
size_t count)
QORDERED_UNARY_OP_SHARED_MEMORY_DECLARATION(Gelu);
} // namespace cuda
} // namespace contrib
} // namespace onnxruntime

View file

@ -32,6 +32,9 @@ class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QLinearSigmoid);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QLinearSoftmax);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QuantizeLinear);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, ReduceSumInteger);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedMatMul);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedGelu);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedLayerNormalization);
//Others
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, Attention);
@ -102,6 +105,9 @@ class OpSet_Microsoft_ver1 {
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QLinearSoftmax)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QuantizeLinear)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, ReduceSumInteger)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedMatMul)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedGelu)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, QOrderedLayerNormalization)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, Attention)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Microsoft, 1, BeamSearch)>());

View file

@ -1131,5 +1131,89 @@ If mask is provided, mask index (that is position of first 0 in mask, or number
.TypeConstraint("T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain input and output types to int8 tensors.")
.TypeConstraint("T", {"tensor(float)"}, "Constrain input and output types to float32 tensors.")
.TypeAndShapeInferenceFunction(EmbedLayerNormalizationShapeInference));
constexpr const char* QOrderedMatMul_ver1_doc = R"DOC(TODO)DOC";
ONNX_MS_OPERATOR_SET_SCHEMA(QOrderedMatMul, 1,
OpSchema()
.SetDoc(QOrderedMatMul_ver1_doc)
.Attr("order_A", "cublasLt order of matrix A. Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Attr("order_B", "cublasLt order of matrix B. Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Attr("order_Y",
"cublasLt order of matrix Y and optional matrix C. Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Input(0, "A", "3-dimensional matrix A", "Q")
.Input(1, "scale_A", "scale of the input A", "S")
.Input(2, "B", "2-dimensional matrix B", "Q")
.Input(3, "scale_B", "scale of the input B", "S")
.Input(4, "scale_Y", "scale of the output Y", "S")
.Input(5, "bias", "1d bias", "S", OpSchema::Optional)
.Input(6, "C",
"3d or 2d matrix C. if 2d expand to 3d first. "
"Shape[0] should be 1 or same as A.shape[0] ",
"Q", OpSchema::Optional)
.Input(7, "scale_C", "scale of the input A", "S", OpSchema::Optional)
.Output(0, "Y", "Matrix multiply results from A * B", "Q")
.TypeConstraint("Q", {"tensor(int8)"},
"Constrain input and output types to int8 tensors.")
.TypeConstraint("S", {"tensor(float)"}, "Constrain bias and scales to float32")
.TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) {
propagateElemTypeFromInputToOutput(ctx, 0, 0);
ONNX_NAMESPACE::matmulShapeInference(ctx, 0, 2);
}));
ONNX_MS_OPERATOR_SET_SCHEMA(QOrderedLayerNormalization, 1,
OpSchema()
.SetDoc("QOrderedLayerNormalization")
.Attr("axis",
"The first normalization dimension: normalization "
"will be performed along dimensions axis "
": rank(inputs).",
AttributeProto::INT,
static_cast<int64_t>(-1))
.Attr("epsilon", "The epsilon value to use to avoid division by zero.",
AttributeProto::FLOAT, 1e-5f)
.Attr("order_X", "cublasLt order of input X. Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Attr("order_Y",
"cublasLt order of matrix Y, must be same as order_X. "
"Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.AllowUncheckedAttributes()
.Input(0, "X", "Input data tensor from the previous layer.", "Q")
.Input(1, "scale_X", "scale of the quantized X", "S")
.Input(2, "scale", "Scale tensor, i.e., gamma vector.", "F")
.Input(3, "B", "Bias tensor.", "F", OpSchema::Optional)
.Input(4, "scale_Y", "scale of the quantized X", "S")
.Output(0, "Y", "Output data tensor.", "Q")
.TypeConstraint("F", {"tensor(float16)", "tensor(float)"},
"Constrain input gamma and bias could be float16/float tensors. "
"float may get better precision, float16 runs faster.")
.TypeConstraint("S", {"tensor(float)"}, "quantization scale must be float tensors.")
.TypeConstraint("Q", {"tensor(int8)"}, "quantization tensor must be int8 tensors.")
.TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) {
propagateShapeAndTypeFromFirstInput(ctx);
propagateElemTypeFromInputToOutput(ctx, 0, 0);
}));
ONNX_MS_OPERATOR_SET_SCHEMA(QOrderedGelu, 1,
OpSchema()
.SetDoc(R"DOC(Ordered Quantize Gelu.)DOC")
.Attr("order_X", "cublasLt order of input X. Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Attr("order_Y",
"cublasLt order of matrix Y, must be same as order_X. "
"Default is ROW MAJOR.",
AttributeProto::INT, static_cast<int64_t>(1))
.Input(0, "X", "N-dimensional input A", "Q")
.Input(1, "scale_X", "scale of the input A", "S")
.Input(2, "scale_Y", "scale of the output Y", "S")
.Output(0, "Y", "Output of the Gelu", "Q")
.TypeConstraint("Q", {"tensor(int8)"},
"Constrain input and output types to int8 tensors.")
.TypeConstraint("S", {"tensor(float)"}, "Constrain scales to float32")
.TypeAndShapeInferenceFunction(ONNX_NAMESPACE::propagateShapeAndTypeFromFirstInput));
} // namespace contrib
} // namespace onnxruntime

View file

@ -149,6 +149,7 @@ CUDAExecutionProvider::PerThreadContext::PerThreadContext(OrtDevice::DeviceId de
stream_ = stream;
CUBLAS_CALL_THROW(cublasCreate(&cublas_handle_));
CUBLAS_CALL_THROW(cublasLtCreate(&cublas_lt_handle_));
CUBLAS_CALL_THROW(cublasSetStream(cublas_handle_, stream));
CUDNN_CALL_THROW(cudnnCreate(&cudnn_handle_));
@ -164,6 +165,7 @@ CUDAExecutionProvider::PerThreadContext::PerThreadContext(OrtDevice::DeviceId de
CUDAExecutionProvider::PerThreadContext::~PerThreadContext() {
ORT_IGNORE_RETURN_VALUE(CUBLAS_CALL(cublasDestroy(cublas_handle_)));
ORT_IGNORE_RETURN_VALUE(CUBLAS_CALL(cublasLtDestroy(cublas_lt_handle_)));
ORT_IGNORE_RETURN_VALUE(CUDNN_CALL(cudnnDestroy(cudnn_handle_)));
}

View file

@ -45,6 +45,10 @@ class CUDAExecutionProvider : public IExecutionProvider {
return GetPerThreadContext().CublasHandle();
}
cublasLtHandle_t PerThreadCublasLtHandle() {
return GetPerThreadContext().CublasLtHandle();
}
cudnnHandle_t PerThreadCudnnHandle() {
return GetPerThreadContext().CudnnHandle();
}
@ -130,6 +134,10 @@ class CUDAExecutionProvider : public IExecutionProvider {
return cudnn_handle_;
}
cublasLtHandle_t CublasLtHandle() const {
return cublas_lt_handle_;
}
cudaEvent_t& GetCurrentDeferredReleaseEvent() {
return current_deferred_release_event_;
}
@ -166,18 +174,19 @@ class CUDAExecutionProvider : public IExecutionProvider {
}
#if defined(CUDA_VERSION) && CUDA_VERSION >= 10000
bool IsGraphCaptureAllowed() const;
void CaptureBegin();
void CaptureEnd();
bool IsGraphCaptured() const;
Status ReplayGraph();
void IncrementRegularRunCountBeforeGraphCapture();
bool IsGraphCaptureAllowed() const;
void CaptureBegin();
void CaptureEnd();
bool IsGraphCaptured() const;
Status ReplayGraph();
void IncrementRegularRunCountBeforeGraphCapture();
#endif
private:
cudaStream_t stream_ = nullptr;
cublasHandle_t cublas_handle_ = nullptr;
cudnnHandle_t cudnn_handle_ = nullptr;
cublasLtHandle_t cublas_lt_handle_ = nullptr;
// deferred release for temporary CPU pinned memory used in cudaMemcpyAsync
// note that cudaEvent will be assigned at OnRunEnd() when PerThreadContext destory
@ -197,10 +206,9 @@ class CUDAExecutionProvider : public IExecutionProvider {
CUDAGraph cuda_graph_;
bool is_graph_captured_ = false;
int regular_run_count_before_graph_capture_ = 0;
const int min_num_runs_before_cuda_graph_capture_ = 1; // required min regular runs before graph capture for the necessary memory allocations.
const int min_num_runs_before_cuda_graph_capture_ = 1; // required min regular runs before graph capture for the necessary memory allocations.
#endif
};
using PerThreadContextMap = std::unordered_map<const CUDAExecutionProvider*, std::weak_ptr<PerThreadContext>>;

View file

@ -135,6 +135,10 @@ class CudaKernel : public OpKernel {
return provider_->PerThreadCublasHandle();
}
inline cublasLtHandle_t CublasLtHandle() const {
return provider_->PerThreadCublasLtHandle();
}
inline cudnnHandle_t CudnnHandle() const {
return provider_->PerThreadCudnnHandle();
}

View file

@ -15,6 +15,7 @@
#include <curand.h>
#include <cudnn.h>
#include <cufft.h>
#include <cublasLt.h>
#ifdef ORT_USE_NCCL
#include <nccl.h>

View file

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_test_utils.h"
namespace onnxruntime {
namespace test {
// Only the CUDA EP supports ordered quantized ops for now
#ifdef USE_CUDA
static void
RunQOrdered_Gelu_Test(std::vector<int64_t> const& shape, float scale_x,
float scale_y, OrderCublasLt order) {
static const float sqrt_of_2 = std::sqrt(2.0f);
int64_t N = std::accumulate(shape.begin(), shape.end(), int64_t{1LL}, std::multiplies<int64_t>());
std::vector<int8_t> vec_x = GenData<int8_t>(shape, 1.0f);
std::vector<int8_t> vec_y(N);
for (int64_t i = 0; i < N; i++) {
float x = scale_x * static_cast<float>(vec_x[i]);
float r = (x * (0.5f * (1.0f + std::erff(x / sqrt_of_2)))) / scale_y;
int8_t q = static_cast<int8_t>(std::nearbyintf(std::min(127.0f, std::max(-128.0f, r))));
vec_y[i] = q;
}
OpTester test_qorder("QOrderedGelu", 1, onnxruntime::kMSDomain);
test_qorder.AddAttribute("order_X", static_cast<int64_t>(order));
test_qorder.AddAttribute("order_Y", static_cast<int64_t>(order));
test_qorder.AddInput<int8_t>("X", shape, vec_x);
test_qorder.AddInput<float>("scale_X", {}, {scale_x});
test_qorder.AddInput<float>("scale_Y", {}, {scale_y});
test_qorder.AddOutput<int8_t>("Y", shape, vec_y, false, 0.0f, 0.0f /* abs error */);
std::vector<std::unique_ptr<IExecutionProvider>> execution_providers;
execution_providers.push_back(DefaultCudaExecutionProvider());
test_qorder.Run(OpTester::ExpectResult::kExpectSuccess, "", {}, nullptr, &execution_providers);
}
TEST(QOrderedTest, Gelu_3x11x12) {
RunQOrdered_Gelu_Test({3, 11, 12}, 1.0f / 32.0f, 1.0f / 128.0f, ORDER_ROW);
}
#endif
} // namespace test
} // namespace onnxruntime

View file

@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_test_utils.h"
namespace onnxruntime {
namespace test {
// Only the CUDA EP supports ordered quantized ops for now
#ifdef USE_CUDA
template <typename T> // MLFloat16 or float
static void RunQOrdered_LayerNorm_RowMajor(std::vector<int64_t> const& shape, int axis, float epsilon,
const std::vector<int8_t>& vec_x, float scale_x,
const std::vector<T>& gamma, const std::vector<T>* beta,
float scale_y, const std::vector<int8_t>& vec_y) {
std::vector<int64_t> bias_shape = {shape.back()};
OpTester test_qorder("QOrderedLayerNormalization", 1, onnxruntime::kMSDomain);
test_qorder.AddAttribute("axis", (int64_t)axis);
test_qorder.AddAttribute("epsilon", epsilon);
test_qorder.AddInput<int8_t>("X", shape, vec_x);
test_qorder.AddInput<float>("scale_X", {}, {scale_x});
test_qorder.AddInput<T>("scale", bias_shape, gamma);
if (beta) {
test_qorder.AddInput<T>("B", bias_shape, *beta);
} else {
test_qorder.AddOptionalInputEdge<T>();
}
test_qorder.AddInput<float>("scale_Y", {}, {scale_y});
test_qorder.AddOutput<int8_t>("Y", shape, vec_y, false, 0.0f, 0.0f /* abs error */);
std::vector<std::unique_ptr<IExecutionProvider>> execution_providers;
execution_providers.push_back(DefaultCudaExecutionProvider());
test_qorder.Run(OpTester::ExpectResult::kExpectSuccess, "", {}, nullptr, &execution_providers);
}
TEST(QOrderedTest, LayerNormalization_RowMajor) {
float scale_x = 1.0;
int64_t batch = 2;
int64_t sequence = 2;
int64_t hidden = 32;
std::vector<int8_t> vec_x = {
-103, -24, -11, 125, 103, -117, 44, -109, 27, 122, 113,
0, -7, 26, 110, -34, 28, -61, -107, -35, -54, 69,
-3, -58, -119, 61, -19, -115, 35, 76, 16, 54,
-103, -24, -11, 125, 103, -117, 44, -109, 27, 122, 113,
0, -7, 26, 110, -34, 28, -61, -107, -35, -54, 69,
-3, -58, -119, 61, -19, -115, 35, 76, 16, 54,
-103, -24, -11, 125, 103, -117, 44, -109, 27, 122, 113,
0, -7, 26, 110, -34, 28, -61, -107, -35, -54, 69,
-3, -58, -119, 61, -19, -115, 35, 76, 16, 54,
-103, -24, -11, 125, 103, -117, 44, -109, 27, 122, 113,
0, -7, 26, 110, -34, 28, -61, -107, -35, -54, 69,
-3, -58, -119, 61, -19, -115, 35, 76, 16, 54};
std::vector<float> gamma_fp32 = {
0.9536133f, 0.9160156f, 1.3310547f, 1.0068359f, 0.8095703f,
1.0126953f, 0.8876953f, 1.4316406f, 1.0947266f, 1.0498047f,
0.7373047f, 1.0615234f, 1.015625f, 1.0751953f, 1.0068359f,
1.0908203f, 1.2011719f, 1.1962891f, 0.91796875f, 1.0947266f,
1.3183594f, 1.0185547f, 1.0791016f, 1.0273438f, 0.8364258f,
0.94873047f, 1.0292969f, 1.09375f, 1.0371094f, 1.1240234f,
1.4384766f, 1.0068359f};
auto gamma_fp16 = ToFloat16(gamma_fp32);
std::vector<float> beta_fp32 = {
0.01411438f, 0.18273926f, -0.12414551f, 0.09887695f, -0.1114502f,
-0.08227539f, -0.04598999f, -0.11322021f, 0.12731934f, -0.06591797f,
-0.00662994f, 0.04962158f, -0.04281616f, 0.07476807f, 0.23010254f,
0.1036377f, 0.10852051f, 0.10919189f, -0.02905273f, -0.0512085f,
-0.1194458f, 0.02661133f, 0.05789185f, -0.05239868f, 0.17907715f,
-0.01765442f, -0.12255859f, -0.09729004f, 0.06591797f, 0.02258301f,
-0.01844788f, -0.11999512f};
auto beta_fp16 = ToFloat16(beta_fp32);
std::vector<int8_t> vec_y = {
-84, -8, -22, 114, 64, -108, 30, -128, 33, 105, 71,
2, -10, 28, 109, -26, 35, -57, -87, -37, -70, 61,
0, -56, -75, 48, -26, -115, 35, 74, 17, 38,
-84, -8, -22, 114, 64, -108, 30, -128, 33, 105, 71,
2, -10, 28, 109, -26, 35, -57, -87, -37, -70, 61,
0, -56, -75, 48, -26, -115, 35, 74, 17, 38,
-84, -8, -22, 114, 64, -108, 30, -128, 33, 105, 71,
2, -10, 28, 109, -26, 35, -57, -87, -37, -70, 61,
0, -56, -75, 48, -26, -115, 35, 74, 17, 38,
-84, -8, -22, 114, 64, -108, 30, -128, 33, 105, 71,
2, -10, 28, 109, -26, 35, -57, -87, -37, -70, 61,
0, -56, -75, 48, -26, -115, 35, 74, 17, 38};
float scale_y = 1.0 / 64.0f;
RunQOrdered_LayerNorm_RowMajor({batch, sequence, hidden}, -1, 0.00001f, vec_x, scale_x,
gamma_fp16, &beta_fp16, scale_y, vec_y);
RunQOrdered_LayerNorm_RowMajor({batch, sequence, hidden}, -1, 0.00001f, vec_x, scale_x,
gamma_fp32, &beta_fp32, scale_y, vec_y);
}
#endif
} // namespace test
} // namespace onnxruntime

View file

@ -0,0 +1,209 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "qordered_test_utils.h"
namespace onnxruntime {
namespace test {
// Only the CUDA EP supports ordered quantized ops for now
#ifdef USE_CUDA
static void RunQOrdered_MatMul_Test(
std::vector<int64_t> const& shape_A,
std::vector<int64_t> const& shape_B,
std::vector<int64_t> const& shape_Y,
OrderCublasLt weight_order,
float scale_A, float scale_B, float scale_C, float scale_Y,
bool add_bias = false, bool broadcast_c_batch = false) {
// Needs Turing or higher architecture
if (NeedSkipIfCudaArchLowerThan(750)) {
return;
}
scale_A = MLFloat16(scale_A).ToFloat();
scale_B = MLFloat16(scale_B).ToFloat();
scale_C = MLFloat16(scale_C).ToFloat();
scale_Y = MLFloat16(scale_Y).ToFloat();
int64_t num_elements_Y = std::accumulate(shape_Y.begin(), shape_Y.end(), int64_t{1LL}, std::multiplies<int64_t>());
std::vector<int8_t> vec_A = GenData<int8_t>(shape_A, 1.0f);
std::vector<int8_t> vec_B = GenData<int8_t>(shape_B, 1.0f);
std::vector<int8_t> vec_Y(num_elements_Y);
int64_t cols_A = 0, rows_A = 0, batch_A = 0;
BatchRowColFromShape(shape_A, batch_A, rows_A, cols_A);
OrderedIndex indexerA(ORDER_ROW, rows_A, cols_A);
int64_t cols_B = 0, rows_B = 0, batch_B = 0;
BatchRowColFromShape(shape_B, batch_B, rows_B, cols_B);
OrderedIndex indexerB(ORDER_ROW, cols_B, rows_B); // B need Transpose
int64_t cols_Y = 0, rows_Y = 0, batch_Y = 0;
BatchRowColFromShape(shape_Y, batch_Y, rows_Y, cols_Y);
OrderedIndex indexerY(ORDER_ROW, rows_Y, cols_Y);
std::vector<int64_t> bias_shape = {cols_Y};
std::vector<float> bias;
if (add_bias) { // make bias not too big
float bias_scale = MLFloat16(scale_Y / 27.0f).ToFloat();
bias = GenData<float>(bias_shape, bias_scale);
}
std::vector<int8_t> vec_C;
std::vector<int64_t> shape_C = {broadcast_c_batch ? 1 : batch_Y, rows_Y, cols_Y};
if (scale_C != 0.0f) {
vec_C = GenData<int8_t>(shape_C, 1.0f);
}
// TODO: broadcasting higher dims
float alpha = scale_A * scale_B / scale_Y;
float beta = scale_C / scale_Y;
int8_t const* A = vec_A.data();
int8_t const* B = vec_B.data();
int8_t* Y = vec_Y.data();
int8_t* C = vec_C.data();
for (int64_t b = 0; b < batch_Y; b++) {
for (int64_t m = 0; m < rows_A; m++) {
for (int64_t n = 0; n < cols_B; n++) {
int32_t isum = 0;
for (int64_t k = 0; k < cols_A; k++) {
auto posA = indexerA(m, k);
auto posB = indexerB(n, k); // Transpose B
isum += (A[posA] * B[posB]);
}
float sum = alpha * isum;
if (add_bias) {
sum += bias[n];
}
auto posY = indexerY(m, n);
if (scale_C != 0.0f) {
sum += beta * C[posY];
}
Y[posY] = static_cast<int8_t>(std::nearbyintf(std::min(127.0f, std::max(-128.0f, sum))));
}
}
A += (batch_A <= 1 ? int64_t{0} : (rows_A * cols_A));
B += (batch_B <= 1 ? int64_t{0} : (rows_B * cols_B));
Y += (batch_Y <= 1 ? int64_t{0} : (rows_Y * cols_Y));
C += (shape_C[0] == 1 ? int64_t{0} : (rows_Y * cols_Y));
}
std::vector<std::unique_ptr<IExecutionProvider>> execution_providers;
execution_providers.push_back(DefaultCudaExecutionProvider());
OpTester test_qorder("QOrderedMatMul", 1, onnxruntime::kMSDomain);
test_qorder.AddAttribute("order_B", static_cast<int64_t>(weight_order));
test_qorder.AddAttribute("order_A", static_cast<int64_t>(ORDER_ROW));
test_qorder.AddAttribute("order_Y", static_cast<int64_t>(ORDER_ROW));
test_qorder.AddInput<int8_t>("A", shape_A, vec_A);
test_qorder.AddInput<float>("scale_A", {}, {scale_A});
test_qorder.AddInput<int8_t>("B", shape_B, vec_B);
test_qorder.AddInput<float>("scale_B", {}, {scale_B});
test_qorder.AddInput<float>("scale_Y", {}, {scale_Y});
if (add_bias) {
test_qorder.AddInput<float>("bias", bias_shape, bias);
} else {
test_qorder.AddOptionalInputEdge<float>();
}
if (scale_C != 0.0f) {
test_qorder.AddInput<int8_t>("C", shape_C, vec_C);
test_qorder.AddInput<float>("scale_C", {}, {scale_C});
}
test_qorder.AddOutput<int8_t>("Y", shape_Y, vec_Y, false, 0.0f, 0.0f /* abs error */);
test_qorder.Run(OpTester::ExpectResult::kExpectSuccess, "", {}, nullptr, &execution_providers);
}
TEST(QOrderedTest, MatMul_COL_16x64x32) {
std::vector<int64_t> shape_A = {16, 32};
std::vector<int64_t> shape_B = {32, 64};
std::vector<int64_t> shape_Y = {16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
false /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_bias_COL_16x64x32) {
std::vector<int64_t> shape_A = {16, 32};
std::vector<int64_t> shape_B = {32, 64};
std::vector<int64_t> shape_Y = {16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
true /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_addC_COL_16x64x32) {
std::vector<int64_t> shape_A = {16, 32};
std::vector<int64_t> shape_B = {32, 64};
std::vector<int64_t> shape_Y = {16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 4.0f /*scaleC*/, 2.0f,
false /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_bias_addC_COL_16x64x32) {
std::vector<int64_t> shape_A = {16, 32};
std::vector<int64_t> shape_B = {32, 64};
std::vector<int64_t> shape_Y = {16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 4.0f /*scaleC*/, 2.0f,
true /* add bias */, true /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_COL_16x64x32_b3_1) {
std::vector<int64_t> shape_A = {3, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {3, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
false /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_bias_COL_16x64x32_b2_1) {
std::vector<int64_t> shape_A = {2, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {2, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
true /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_addC_COL_16x64x32_b2_1) {
std::vector<int64_t> shape_A = {2, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {2, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 4.0f /*scaleC*/, 2.0f,
false /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_addC_broadcastC_COL_16x64x32_b2_1) {
std::vector<int64_t> shape_A = {2, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {2, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
false /* add bias */, true /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_addC_bias_COL_16x64x32_b2_1) {
std::vector<int64_t> shape_A = {2, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {2, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
true /* add bias */, false /* broadcast batch c */);
}
TEST(QOrderedTest, MatMul_bias_addC_broadcastC_COL_16x64x32_b2_1) {
std::vector<int64_t> shape_A = {2, 16, 32};
std::vector<int64_t> shape_B = {1, 32, 64};
std::vector<int64_t> shape_Y = {2, 16, 64};
RunQOrdered_MatMul_Test(shape_A, shape_B, shape_Y, ORDER_COL,
1.0f / 32.0f, 1.0f / 32.0f, 0.0f /*scaleC*/, 2.0f,
true /* add bias */, true /* broadcast batch c */);
}
#endif
} // namespace test
} // namespace onnxruntime

View file

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "test/common/tensor_op_test_utils.h"
#include "test/common/cuda_op_test_utils.h"
#include "test/providers/provider_test_utils.h"
#include <vector>
#include <functional>
namespace onnxruntime {
namespace test {
enum OrderCublasLt {
ORDER_COL = 0,
ORDER_ROW = 1,
};
class OrderedIndex {
OrderCublasLt order_;
int64_t rows_;
int64_t cols_;
public:
OrderedIndex(OrderCublasLt order, int64_t rows, int64_t cols)
: order_(order), rows_(rows), cols_(cols) {}
int64_t operator()(int64_t r, int64_t c) {
switch (order_) {
case ORDER_ROW:
return r * cols_ + c;
case ORDER_COL:
return c * rows_ + r;
default:
return 0;
}
}
};
template <typename T>
inline std::vector<T> GenData(std::vector<int64_t> const& shape, float scale) {
int64_t n = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies<int64_t>());
scale = std::is_same<T, int8_t>::value ? 1.0f : scale; // using scale = 1.0f to generate int8_t data,
std::vector<T> r(n);
RandomValueGenerator random{};
std::vector<int> tmp = random.Uniform<int32_t>(shape, -128, 127);
for (int64_t i = 0; i < n; i++) {
r[i] = static_cast<T>(tmp[i] * scale);
}
return r;
}
inline void BatchRowColFromShape(std::vector<int64_t> const& shape, int64_t& batch, int64_t& rows, int64_t& cols) {
cols = shape.back();
rows = (shape.size() > 1 ? shape[shape.size() - 2] : 1LL);
batch = (shape.size() <= 2)
? 1LL
: std::accumulate(shape.data(), shape.data() + (shape.size() - 2), 1LL, std::multiplies<int64_t>());
}
} // namespace test
} // namespace onnxruntime

View file

@ -47,6 +47,19 @@ contrib_ops_excluded_files = [
"quantization/attention_quantization_impl.cu",
"quantization/attention_quantization_impl.cuh",
"quantization/quantize_dequantize_linear.cc",
"quantization/qordered_ops/qordered_common.cuh",
"quantization/qordered_ops/qordered_layer_norm.h",
"quantization/qordered_ops/qordered_layer_norm.cc",
"quantization/qordered_ops/qordered_layer_norm_impl.h",
"quantization/qordered_ops/qordered_layer_norm_impl.cu",
"quantization/qordered_ops/qordered_matmul.h",
"quantization/qordered_ops/qordered_matmul.cc",
"quantization/qordered_ops/qordered_matmul_utils.h",
"quantization/qordered_ops/qordered_matmul_utils.cc",
"quantization/qordered_ops/qordered_unary_ops.h",
"quantization/qordered_ops/qordered_unary_ops.cc",
"quantization/qordered_ops/qordered_unary_ops_impl.h",
"quantization/qordered_ops/qordered_unary_ops_impl.cu",
"tensor/crop.cc",
"tensor/crop.h",
"tensor/crop_impl.cu",