From ad69aac491fc423fc71484a8c633ef444eec3105 Mon Sep 17 00:00:00 2001 From: Hariharan Seshadri Date: Wed, 7 Sep 2022 11:58:15 -0700 Subject: [PATCH] Introduce ordered quantization ops for the CUDA EP [1/n] (#12582) Initial core small set for the ordered quantization ops for cuda EP. --- cmake/onnxruntime_providers.cmake | 2 +- docs/ContribOperators.md | 160 ++++++++++++++ docs/OperatorKernels.md | 3 + .../contrib_ops/cuda/cuda_contrib_kernels.cc | 6 + .../qordered_ops/qordered_common.cuh | 29 +++ .../qordered_ops/qordered_layer_norm.cc | 100 +++++++++ .../qordered_ops/qordered_layer_norm.h | 29 +++ .../qordered_ops/qordered_layer_norm_impl.cu | 120 ++++++++++ .../qordered_ops/qordered_layer_norm_impl.h | 21 ++ .../qordered_ops/qordered_matmul.cc | 131 +++++++++++ .../qordered_ops/qordered_matmul.h | 28 +++ .../qordered_ops/qordered_matmul_utils.cc | 209 ++++++++++++++++++ .../qordered_ops/qordered_matmul_utils.h | 60 +++++ .../qordered_ops/qordered_unary_ops.cc | 43 ++++ .../qordered_ops/qordered_unary_ops.h | 23 ++ .../qordered_ops/qordered_unary_ops_impl.cu | 79 +++++++ .../qordered_ops/qordered_unary_ops_impl.h | 25 +++ onnxruntime/core/graph/contrib_ops/ms_opset.h | 6 + .../graph/contrib_ops/quantization_defs.cc | 84 +++++++ .../providers/cuda/cuda_execution_provider.cc | 2 + .../providers/cuda/cuda_execution_provider.h | 24 +- onnxruntime/core/providers/cuda/cuda_kernel.h | 4 + onnxruntime/core/providers/cuda/cuda_pch.h | 1 + .../test/contrib_ops/qordered_gelu_op_test.cc | 47 ++++ .../qordered_layer_norm_op_test.cc | 112 ++++++++++ .../contrib_ops/qordered_matmul_op_test.cc | 209 ++++++++++++++++++ .../test/contrib_ops/qordered_test_utils.h | 65 ++++++ tools/ci_build/amd_hipify.py | 13 ++ 28 files changed, 1626 insertions(+), 9 deletions(-) create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_common.cuh create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.cc create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.h create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.cu create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.h create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.cc create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.h create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.cc create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.h create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.cc create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.h create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.cu create mode 100644 onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.h create mode 100644 onnxruntime/test/contrib_ops/qordered_gelu_op_test.cc create mode 100644 onnxruntime/test/contrib_ops/qordered_layer_norm_op_test.cc create mode 100644 onnxruntime/test/contrib_ops/qordered_matmul_op_test.cc create mode 100644 onnxruntime/test/contrib_ops/qordered_test_utils.h diff --git a/cmake/onnxruntime_providers.cmake b/cmake/onnxruntime_providers.cmake index 6e387d1710..a6b991af21 100644 --- a/cmake/onnxruntime_providers.cmake +++ b/cmake/onnxruntime_providers.cmake @@ -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() diff --git a/docs/ContribOperators.md b/docs/ContribOperators.md index e35bc53033..e1a8d23a0a 100644 --- a/docs/ContribOperators.md +++ b/docs/ContribOperators.md @@ -56,6 +56,9 @@ Do not modify directly.* * com.microsoft.QLinearReduceMean * com.microsoft.QLinearSigmoid * com.microsoft.QLinearSoftmax + * com.microsoft.QOrderedGelu + * com.microsoft.QOrderedLayerNormalization + * com.microsoft.QOrderedMatMul * com.microsoft.QuantizeLinear * com.microsoft.Range * com.microsoft.ReduceSumInteger @@ -2862,6 +2865,163 @@ This version of the operator has been available since version 1 of the 'com.micr +### **com.microsoft.QOrderedGelu** + + Ordered Quantize Gelu. + +#### Version + +This version of the operator has been available since version 1 of the 'com.microsoft' operator set. + +#### Attributes + +
+
order_X : int
+
cublasLt order of input X. Default is ROW MAJOR.
+
order_Y : int
+
cublasLt order of matrix Y, must be same as order_X. Default is ROW MAJOR.
+
+ +#### Inputs + +
+
X : Q
+
N-dimensional input A
+
scale_X : S
+
scale of the input A
+
scale_Y : S
+
scale of the output Y
+
+ +#### Outputs + +
+
Y : Q
+
Output of the Gelu
+
+ +#### Type Constraints + +
+
Q : tensor(int8)
+
Constrain input and output types to int8 tensors.
+
S : tensor(float)
+
Constrain scales to float32
+
+ + +### **com.microsoft.QOrderedLayerNormalization** + + QOrderedLayerNormalization + +#### Version + +This version of the operator has been available since version 1 of the 'com.microsoft' operator set. + +#### Attributes + +
+
axis : int
+
The first normalization dimension: normalization will be performed along dimensions axis : rank(inputs).
+
epsilon : float
+
The epsilon value to use to avoid division by zero.
+
order_X : int
+
cublasLt order of input X. Default is ROW MAJOR.
+
order_Y : int
+
cublasLt order of matrix Y, must be same as order_X. Default is ROW MAJOR.
+
+ +#### Inputs + +
+
X : Q
+
Input data tensor from the previous layer.
+
scale_X : S
+
scale of the quantized X
+
scale : F
+
Scale tensor, i.e., gamma vector.
+
B (optional) : F
+
Bias tensor.
+
scale_Y : S
+
scale of the quantized X
+
+ +#### Outputs + +
+
Y : Q
+
Output data tensor.
+
+ +#### Type Constraints + +
+
F : tensor(float16), tensor(float)
+
Constrain input gamma and bias could be float16/float tensors. float may get better precision, float16 runs faster.
+
S : tensor(float)
+
quantization scale must be float tensors.
+
Q : tensor(int8)
+
quantization tensor must be int8 tensors.
+
+ + +### **com.microsoft.QOrderedMatMul** + + TODO + +#### Version + +This version of the operator has been available since version 1 of the 'com.microsoft' operator set. + +#### Attributes + +
+
order_A : int
+
cublasLt order of matrix A. Default is ROW MAJOR.
+
order_B : int
+
cublasLt order of matrix B. Default is ROW MAJOR.
+
order_Y : int
+
cublasLt order of matrix Y and optional matrix C. Default is ROW MAJOR.
+
+ +#### Inputs (5 - 8) + +
+
A : Q
+
3-dimensional matrix A
+
scale_A : S
+
scale of the input A
+
B : Q
+
2-dimensional matrix B
+
scale_B : S
+
scale of the input B
+
scale_Y : S
+
scale of the output Y
+
bias (optional) : S
+
1d bias
+
C (optional) : Q
+
3d or 2d matrix C. if 2d expand to 3d first. Shape[0] should be 1 or same as A.shape[0]
+
scale_C (optional) : S
+
scale of the input A
+
+ +#### Outputs + +
+
Y : Q
+
Matrix multiply results from A * B
+
+ +#### Type Constraints + +
+
Q : tensor(int8)
+
Constrain input and output types to int8 tensors.
+
S : tensor(float)
+
Constrain bias and scales to float32
+
+ + ### **com.microsoft.QuantizeLinear** The linear quantization operator. It consumes a full precision data, a scale, a zero point to compute the low precision / quantized tensor. diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md index 0b5ae058a3..370b52ab38 100644 --- a/docs/OperatorKernels.md +++ b/docs/OperatorKernels.md @@ -780,6 +780,9 @@ Do not modify directly.* |LongformerAttention|*in* input:**T**
*in* weight:**T**
*in* bias:**T**
*in* mask:**T**
*in* global_weight:**T**
*in* global_bias:**T**
*in* global:**G**
*out* output:**T**|1+|**T** = tensor(float), tensor(float16)| |NGramRepeatBlock|*in* input_ids:**Tid**
*in* scores:**T**
*out* scores_out:**T**|1+|**T** = tensor(float)
**Tid** = tensor(int64)| |QAttention|*in* input:**T1**
*in* weight:**T2**
*in* bias:**T3**
*in* input_scale:**T3**
*in* weight_scale:**T3**
*in* mask_index:**T4**
*in* input_zero_point:**T1**
*in* weight_zero_point:**T2**
*in* past:**T3**
*out* output:**T3**
*out* present:**T3**|1+|**T1** = tensor(int8)
**T2** = tensor(int8)
**T3** = tensor(float), tensor(float16)
**T4** = tensor(int32)| +|QOrderedGelu|*in* X:**Q**
*in* scale_X:**S**
*in* scale_Y:**S**
*out* Y:**Q**|1+|**Q** = tensor(int8)
**S** = tensor(float)| +|QOrderedLayerNormalization|*in* X:**Q**
*in* scale_X:**S**
*in* scale:**F**
*in* B:**F**
*in* scale_Y:**S**
*out* Y:**Q**|1+|**F** = tensor(float), tensor(float16)
**Q** = tensor(int8)
**S** = tensor(float)| +|QOrderedMatMul|*in* A:**Q**
*in* scale_A:**S**
*in* B:**Q**
*in* scale_B:**S**
*in* scale_Y:**S**
*in* bias:**S**
*in* C:**Q**
*in* scale_C:**S**
*out* Y:**Q**|1+|**Q** = tensor(int8)
**S** = tensor(float)| |QuantizeLinear|*in* x:**T1**
*in* y_scale:**T1**
*in* y_zero_point:**T2**
*out* y:**T2**|1+|**T1** = tensor(float16)
**T2** = tensor(int8), tensor(uint8)| |Rfft|*in* X:**T**
*out* Y:**T**|1+|**T** = tensor(double), tensor(float), tensor(float16)| |SkipLayerNormalization|*in* input:**T**
*in* skip:**T**
*in* gamma:**T**
*in* beta:**T**
*in* bias:**T**
*out* output:**T**
*out* mean:**U**
*out* inv_std_var:**U**|1+|**T** = tensor(float), tensor(float16)| diff --git a/onnxruntime/contrib_ops/cuda/cuda_contrib_kernels.cc b/onnxruntime/contrib_ops/cuda/cuda_contrib_kernels.cc index 7062398dda..d9e9e6dc55 100644 --- a/onnxruntime/contrib_ops/cuda/cuda_contrib_kernels.cc +++ b/onnxruntime/contrib_ops/cuda/cuda_contrib_kernels.cc @@ -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, BuildKernelCreateInfo, BuildKernelCreateInfo, + BuildKernelCreateInfo, + BuildKernelCreateInfo, + BuildKernelCreateInfo, #ifdef ENABLE_ATEN BuildKernelCreateInfo, diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_common.cuh b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_common.cuh new file mode 100644 index 0000000000..ad3647e2a5 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_common.cuh @@ -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 +__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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.cc b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.cc new file mode 100644 index 0000000000..aba3d4759a --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.cc @@ -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()) + .TypeConstraint("S", DataTypeImpl::GetTensorType()) + .TypeConstraint("F", BuildKernelDefConstraints()) + .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("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::MappedType CudaQ; + + // Inputs + const Tensor* X = ctx->Input(0); + const auto* X_data = reinterpret_cast(X->Data()); + 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(2); + const void* scale_data = scale->DataRaw(); + + const Tensor* bias = ctx->Input(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(x_shape.GetDims()[0]); + unsigned int rows = gsl::narrow(x_shape.GetDims()[1]); + unsigned int cols = gsl::narrow(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(Y->MutableData()); + + const float* scale_x = ctx->Input(1)->Data(); + const float* scale_y = ctx->Input(4)->Data(); + + if (scale->IsDataType()) { + QOrderedLayerNorm(Stream(), GetDeviceProp(), static_cast(order_X_), + X_data, *scale_x, Y_data, *scale_y, static_cast(scale_data), + static_cast(bias_data), + static_cast(epsilon_), batch, rows, cols); + } else { + QOrderedLayerNorm(Stream(), GetDeviceProp(), static_cast(order_X_), + X_data, *scale_x, Y_data, *scale_y, static_cast(scale_data), + static_cast(bias_data), + static_cast(epsilon_), batch, rows, cols); + } + + return Status::OK(); +} + +} // namespace cuda +} // namespace contrib +} // namespace onnxruntime diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.h new file mode 100644 index 0000000000..f66a0597ca --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm.h @@ -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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.cu b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.cu new file mode 100644 index 0000000000..8ba13961f5 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.cu @@ -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(__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(input_1.x) * static_cast(input_2.x) + + static_cast(input_1.y) * static_cast(input_2.y) + + static_cast(input_1.z) * static_cast(input_2.z) + + static_cast(input_1.w) * static_cast(input_2.w); +} + +template +__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(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(src + c)); + sum += (static_cast(ch4.x) + static_cast(ch4.y) + + static_cast(ch4.z) + static_cast(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(sum); + square_sum = WarpReduceSum(square_sum); + + const float mean = __double2float_rn(src_scale * (double)sum / cols); + + const float rvar = rsqrtf(src_scale * src_scale * __double2float_rn(static_cast(square_sum) - (static_cast(sum) * static_cast(sum) / static_cast(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(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(dst + c) = QuantizeFloat4Char4(f4, dst_rscale); + } +} + +template +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(rows + QORDER_LAYERNORM_ROWS_PER_BLOCK - 1) / QORDER_LAYERNORM_ROWS_PER_BLOCK, + static_cast(batch), 1); + + QOrderedLayerNormRowKernel<<>>(src, src_scale, + dst, dst_scale, gamma, beta, + epsilon, rows, cols); +} + +template 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 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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.h new file mode 100644 index 0000000000..1ad8b46a68 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_layer_norm_impl.h @@ -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 +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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.cc b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.cc new file mode 100644 index 0000000000..34198b7b9f --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.cc @@ -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 + +namespace onnxruntime { +namespace contrib { +namespace cuda { + +using namespace onnxruntime::cuda; + +ONNX_OPERATOR_KERNEL_EX( + QOrderedMatMul, + kMSDomain, + 1, + kCudaExecutionProvider, + (*KernelDefBuilder::Create()) + .TypeConstraint("Q", DataTypeImpl::GetTensorType()) + .TypeConstraint("S", DataTypeImpl::GetTensorType()) + .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())); + + 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(0); + const Tensor& tensor_B = *context->Input(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(1)->Data(); + const float* scale_B = context->Input(3)->Data(); + const float* scale_Y = context->Input(4)->Data(); + ORT_ENFORCE(*scale_Y > 0.0f && *scale_A > 0.0f && *scale_B > 0.0f); + + const Tensor* tensor_bias = context->Input(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(); + + 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(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(7); + ORT_ENFORCE(tensor_scale_C != nullptr); + scale_C = tensor_scale_C->Data(); + + C = tensor_C->Data(); + } + + 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(batch_A), rows_A, cols_B, cols_A, + &alpha, tensor_A.Data(), tensor_B.Data(), + static_cast(batch_B), bias, + &beta, C, static_cast(batch_C), + tensor_Y->MutableData(), static_cast(order_B_))); + + return Status::OK(); +} + +} // namespace cuda +} // namespace contrib +} // namespace onnxruntime diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.h new file mode 100644 index 0000000000..829cd06625 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul.h @@ -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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.cc b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.cc new file mode 100644 index 0000000000..12021c99e6 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.cc @@ -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(weight_order) << "-" << static_cast(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(m), static_cast(n), static_cast(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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.h new file mode 100644 index 0000000000..143cdb19dd --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_matmul_utils.h @@ -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 +#include + +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 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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.cc b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.cc new file mode 100644 index 0000000000..8cb710d0cb --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.cc @@ -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()) + .TypeConstraint("S", DataTypeImpl::GetTensorType()) + .InputMemoryType(OrtMemTypeCPUInput, 1) // scale_input + .InputMemoryType(OrtMemTypeCPUInput, 2), // scale_output + QOrderedGelu); + +Status QOrderedGelu::ComputeInternal(OpKernelContext* context) const { + const Tensor& input = *context->Input(0); + + const float* scale_input = context->Input(1)->Data(); + const float* scale_output = context->Input(2)->Data(); + + const auto& shape = input.Shape(); + + Tensor* output = context->Output(0, shape); + + QOrderedUnarySharedMemory_Gelu(Stream(), input.Data(), scale_input, output->MutableData(), + scale_output, static_cast(shape.Size())); + + return Status::OK(); +} + +} // namespace cuda +} // namespace contrib +} // namespace onnxruntime diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.h new file mode 100644 index 0000000000..e32e9eb38d --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops.h @@ -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 diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.cu b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.cu new file mode 100644 index 0000000000..95dfc26036 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.cu @@ -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 +__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(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(__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(output_data + id) = i4; + id += kNumElementsPerBlockLine; + } + } +} + +template +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(CeilDiv(count, kNumElementsPerBlock)); + + QOrderedUnaryElementWiseSharedMemoryKernel<<>>( + input_data, *input_scale, output_data, inverse_output_scale, func, static_cast(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( + stream, input_data, input_scale, output_data, output_scale, QOrderedUnaryOpGelu(), count); +} + +} // namespace cuda +} // namespace contrib +} // namespace onnxruntime diff --git a/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.h b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.h new file mode 100644 index 0000000000..8c1ad09021 --- /dev/null +++ b/onnxruntime/contrib_ops/cuda/quantization/qordered_ops/qordered_unary_ops_impl.h @@ -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 diff --git a/onnxruntime/core/graph/contrib_ops/ms_opset.h b/onnxruntime/core/graph/contrib_ops/ms_opset.h index c6850eb8e3..23a54d325e 100644 --- a/onnxruntime/core/graph/contrib_ops/ms_opset.h +++ b/onnxruntime/core/graph/contrib_ops/ms_opset.h @@ -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()); fn(GetOpSchema()); fn(GetOpSchema()); + fn(GetOpSchema()); + fn(GetOpSchema()); + fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); diff --git a/onnxruntime/core/graph/contrib_ops/quantization_defs.cc b/onnxruntime/core/graph/contrib_ops/quantization_defs.cc index 34fb5a16ad..ccc3dd3b93 100644 --- a/onnxruntime/core/graph/contrib_ops/quantization_defs.cc +++ b/onnxruntime/core/graph/contrib_ops/quantization_defs.cc @@ -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(1)) + .Attr("order_B", "cublasLt order of matrix B. Default is ROW MAJOR.", + AttributeProto::INT, static_cast(1)) + .Attr("order_Y", + "cublasLt order of matrix Y and optional matrix C. Default is ROW MAJOR.", + AttributeProto::INT, static_cast(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(-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(1)) + .Attr("order_Y", + "cublasLt order of matrix Y, must be same as order_X. " + "Default is ROW MAJOR.", + AttributeProto::INT, static_cast(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(1)) + .Attr("order_Y", + "cublasLt order of matrix Y, must be same as order_X. " + "Default is ROW MAJOR.", + AttributeProto::INT, static_cast(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 diff --git a/onnxruntime/core/providers/cuda/cuda_execution_provider.cc b/onnxruntime/core/providers/cuda/cuda_execution_provider.cc index f18865498e..6a0c3283b0 100755 --- a/onnxruntime/core/providers/cuda/cuda_execution_provider.cc +++ b/onnxruntime/core/providers/cuda/cuda_execution_provider.cc @@ -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_))); } diff --git a/onnxruntime/core/providers/cuda/cuda_execution_provider.h b/onnxruntime/core/providers/cuda/cuda_execution_provider.h index cb126f203b..806be894f6 100644 --- a/onnxruntime/core/providers/cuda/cuda_execution_provider.h +++ b/onnxruntime/core/providers/cuda/cuda_execution_provider.h @@ -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>; diff --git a/onnxruntime/core/providers/cuda/cuda_kernel.h b/onnxruntime/core/providers/cuda/cuda_kernel.h index 7ea2a07ae0..2ce72d7112 100644 --- a/onnxruntime/core/providers/cuda/cuda_kernel.h +++ b/onnxruntime/core/providers/cuda/cuda_kernel.h @@ -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(); } diff --git a/onnxruntime/core/providers/cuda/cuda_pch.h b/onnxruntime/core/providers/cuda/cuda_pch.h index a671b91fb1..bdb0e78d77 100644 --- a/onnxruntime/core/providers/cuda/cuda_pch.h +++ b/onnxruntime/core/providers/cuda/cuda_pch.h @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef ORT_USE_NCCL #include diff --git a/onnxruntime/test/contrib_ops/qordered_gelu_op_test.cc b/onnxruntime/test/contrib_ops/qordered_gelu_op_test.cc new file mode 100644 index 0000000000..15e36233e0 --- /dev/null +++ b/onnxruntime/test/contrib_ops/qordered_gelu_op_test.cc @@ -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 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()); + std::vector vec_x = GenData(shape, 1.0f); + std::vector vec_y(N); + for (int64_t i = 0; i < N; i++) { + float x = scale_x * static_cast(vec_x[i]); + float r = (x * (0.5f * (1.0f + std::erff(x / sqrt_of_2)))) / scale_y; + int8_t q = static_cast(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(order)); + test_qorder.AddAttribute("order_Y", static_cast(order)); + test_qorder.AddInput("X", shape, vec_x); + test_qorder.AddInput("scale_X", {}, {scale_x}); + test_qorder.AddInput("scale_Y", {}, {scale_y}); + test_qorder.AddOutput("Y", shape, vec_y, false, 0.0f, 0.0f /* abs error */); + + std::vector> 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 diff --git a/onnxruntime/test/contrib_ops/qordered_layer_norm_op_test.cc b/onnxruntime/test/contrib_ops/qordered_layer_norm_op_test.cc new file mode 100644 index 0000000000..824f4324dd --- /dev/null +++ b/onnxruntime/test/contrib_ops/qordered_layer_norm_op_test.cc @@ -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 // MLFloat16 or float +static void RunQOrdered_LayerNorm_RowMajor(std::vector const& shape, int axis, float epsilon, + const std::vector& vec_x, float scale_x, + const std::vector& gamma, const std::vector* beta, + float scale_y, const std::vector& vec_y) { + std::vector 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("X", shape, vec_x); + test_qorder.AddInput("scale_X", {}, {scale_x}); + test_qorder.AddInput("scale", bias_shape, gamma); + if (beta) { + test_qorder.AddInput("B", bias_shape, *beta); + } else { + test_qorder.AddOptionalInputEdge(); + } + test_qorder.AddInput("scale_Y", {}, {scale_y}); + test_qorder.AddOutput("Y", shape, vec_y, false, 0.0f, 0.0f /* abs error */); + + std::vector> 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 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 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 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 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 diff --git a/onnxruntime/test/contrib_ops/qordered_matmul_op_test.cc b/onnxruntime/test/contrib_ops/qordered_matmul_op_test.cc new file mode 100644 index 0000000000..a6fa1c7e53 --- /dev/null +++ b/onnxruntime/test/contrib_ops/qordered_matmul_op_test.cc @@ -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 const& shape_A, + std::vector const& shape_B, + std::vector 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()); + std::vector vec_A = GenData(shape_A, 1.0f); + std::vector vec_B = GenData(shape_B, 1.0f); + std::vector 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 bias_shape = {cols_Y}; + std::vector bias; + if (add_bias) { // make bias not too big + float bias_scale = MLFloat16(scale_Y / 27.0f).ToFloat(); + bias = GenData(bias_shape, bias_scale); + } + std::vector vec_C; + std::vector shape_C = {broadcast_c_batch ? 1 : batch_Y, rows_Y, cols_Y}; + if (scale_C != 0.0f) { + vec_C = GenData(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(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> execution_providers; + execution_providers.push_back(DefaultCudaExecutionProvider()); + + OpTester test_qorder("QOrderedMatMul", 1, onnxruntime::kMSDomain); + test_qorder.AddAttribute("order_B", static_cast(weight_order)); + test_qorder.AddAttribute("order_A", static_cast(ORDER_ROW)); + test_qorder.AddAttribute("order_Y", static_cast(ORDER_ROW)); + test_qorder.AddInput("A", shape_A, vec_A); + test_qorder.AddInput("scale_A", {}, {scale_A}); + test_qorder.AddInput("B", shape_B, vec_B); + test_qorder.AddInput("scale_B", {}, {scale_B}); + test_qorder.AddInput("scale_Y", {}, {scale_Y}); + if (add_bias) { + test_qorder.AddInput("bias", bias_shape, bias); + } else { + test_qorder.AddOptionalInputEdge(); + } + + if (scale_C != 0.0f) { + test_qorder.AddInput("C", shape_C, vec_C); + test_qorder.AddInput("scale_C", {}, {scale_C}); + } + test_qorder.AddOutput("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 shape_A = {16, 32}; + std::vector shape_B = {32, 64}; + std::vector 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 shape_A = {16, 32}; + std::vector shape_B = {32, 64}; + std::vector 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 shape_A = {16, 32}; + std::vector shape_B = {32, 64}; + std::vector 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 shape_A = {16, 32}; + std::vector shape_B = {32, 64}; + std::vector 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 shape_A = {3, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 shape_A = {2, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 shape_A = {2, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 shape_A = {2, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 shape_A = {2, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 shape_A = {2, 16, 32}; + std::vector shape_B = {1, 32, 64}; + std::vector 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 diff --git a/onnxruntime/test/contrib_ops/qordered_test_utils.h b/onnxruntime/test/contrib_ops/qordered_test_utils.h new file mode 100644 index 0000000000..f60a48611f --- /dev/null +++ b/onnxruntime/test/contrib_ops/qordered_test_utils.h @@ -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 +#include + +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 +inline std::vector GenData(std::vector const& shape, float scale) { + int64_t n = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + + scale = std::is_same::value ? 1.0f : scale; // using scale = 1.0f to generate int8_t data, + std::vector r(n); + RandomValueGenerator random{}; + std::vector tmp = random.Uniform(shape, -128, 127); + for (int64_t i = 0; i < n; i++) { + r[i] = static_cast(tmp[i] * scale); + } + return r; +} + +inline void BatchRowColFromShape(std::vector 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()); +} + +} // namespace test +} // namespace onnxruntime diff --git a/tools/ci_build/amd_hipify.py b/tools/ci_build/amd_hipify.py index 1310e15629..3302ee1693 100644 --- a/tools/ci_build/amd_hipify.py +++ b/tools/ci_build/amd_hipify.py @@ -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",