diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md index 0668cb4a93..3c739d89bf 100644 --- a/docs/OperatorKernels.md +++ b/docs/OperatorKernels.md @@ -1296,6 +1296,7 @@ Do not modify directly.* |GroupNorm|*in* X:**T**
*in* gamma:**M**
*in* beta:**M**
*out* Y:**T**|1+|**M** = tensor(float), tensor(float16)
**T** = tensor(float), tensor(float16)| |GroupQueryAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* seqlens_k:**M**
*in* total_sequence_length:**M**
*in* cos_cache:**T**
*in* sin_cache:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**M** = tensor(int32)
**T** = tensor(float), tensor(float16)| |MatMulIntegerToFloat|*in* A:**T1**
*in* B:**T2**
*in* a_scale:**T3**
*in* b_scale:**T3**
*in* a_zero_point:**T1**
*in* b_zero_point:**T2**
*in* bias:**T3**
*out* Y:**T3**|1+|**T1** = tensor(int8), tensor(uint8)
**T2** = tensor(int8), tensor(uint8)
**T3** = tensor(float), tensor(float16)| +|MatMulNBits|*in* A:**T1**
*in* B:**T2**
*in* scales:**T1**
*in* zero_points:**T3**
*in* g_idx:**T4**
*out* Y:**T1**|1+|**T1** = tensor(float), tensor(float16)
**T2** = tensor(uint8)| |MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* relative_position_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**M** = tensor(int32)
**T** = tensor(float), tensor(float16)| |NhwcConv|*in* X:**T**
*in* W:**T**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(float), tensor(float16)| |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), tensor(uint8)
**T2** = tensor(int8), tensor(uint8)
**T3** = tensor(float), tensor(float16)
**T4** = tensor(int32)| diff --git a/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/DmlOperatorMatMulNBits.cpp b/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/DmlOperatorMatMulNBits.cpp new file mode 100644 index 0000000000..0a9b1cc4db --- /dev/null +++ b/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/DmlOperatorMatMulNBits.cpp @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "precomp.h" + +namespace Dml +{ +class DmlOperatorMatMulNBits : public DmlOperator +{ +public: + DmlOperatorMatMulNBits(const MLOperatorKernelCreationContext& kernelInfo) + : DmlOperator(kernelInfo) + { + ML_CHECK_VALID_ARGUMENT(kernelInfo.GetInputCount() >= 3 && kernelInfo.GetInputCount() <= 4); + ML_CHECK_VALID_ARGUMENT(kernelInfo.GetOutputCount() == 1); + DmlOperator::Initialize(kernelInfo); + + const bool hasZeroPoint = kernelInfo.IsInputValid(3); + const uint32_t bRowCount = gsl::narrow_cast(kernelInfo.GetAttribute(AttrName::UppercaseN)); + const uint32_t bColCount = gsl::narrow_cast(kernelInfo.GetAttribute(AttrName::UppercaseK)); + const auto bitCount = kernelInfo.GetAttribute(AttrName::Bits); + + MLOperatorTensorDataType mlDataType = kernelInfo.GetInputEdgeDescription(0).tensorDataType; + + ML_CHECK_VALID_ARGUMENT(bitCount == 4 || bitCount == 8); + const DML_TENSOR_DATA_TYPE quantizedDataType = bitCount == 4 ? DML_TENSOR_DATA_TYPE_UINT4 : DML_TENSOR_DATA_TYPE_UINT8; + + std::vector aShape = kernelInfo.GetTensorShapeDescription().GetInputTensorShape(0); + ML_CHECK_VALID_ARGUMENT(aShape.size() >= 2); + + // The quantized input to MatMulNBits always comes as uint8, but the real shape is provided through the N and K attributes + std::vector bBroadcastedShape = aShape; + bBroadcastedShape[bBroadcastedShape.size() - 2] = bRowCount; + bBroadcastedShape[bBroadcastedShape.size() - 1] = bColCount; + + // The B tensor always has a batch size and channel of 1 since it's a weight, but DML requires it to have the same channels + // and channel count as the A tensor so we need to broadcast it + std::vector bShape(aShape.size(), 1); + bShape[bShape.size() - 2] = bRowCount; + bShape[bShape.size() - 1] = bColCount; + + std::vector outputShape = kernelInfo.GetTensorShapeDescription().GetOutputTensorShape(0); + + std::vector scaleShape = bShape; + + uint32_t scaleElementCount = ComputeElementCountFromDimensions(m_inputTensorDescs[2].GetSizes()); + scaleShape[scaleShape.size() - 1] = scaleElementCount / bRowCount; + + std::vector scaleBroadcastedShape = bBroadcastedShape; + scaleBroadcastedShape.back() = scaleShape.back(); + + // The quantized input and zero point to MatMulNBits always comes as uint8, but DML will expect the real data type (int4 or int8) + m_inputTensorDescs[0] = TensorDesc::ConstructDefaultTensorDesc(mlDataType, aShape); + m_inputTensorDescs[1] = TensorDesc::ConstructBroadcastedTensorDesc(GetMlDataTypeFromDmlDataType(quantizedDataType), bBroadcastedShape, bShape); + m_inputTensorDescs[2] = TensorDesc::ConstructBroadcastedTensorDesc(mlDataType, scaleBroadcastedShape, scaleShape); + + if (hasZeroPoint) + { + m_inputTensorDescs[3] = TensorDesc::ConstructBroadcastedTensorDesc(GetMlDataTypeFromDmlDataType(quantizedDataType), scaleBroadcastedShape, scaleShape); + + // Zero points are stored in uint8 data in ORT, which means that it will not be tightly packed if the last dimension is an odd number. To account for that, + // we round the strides of the next multiple of 2. + auto lastDimension = m_inputTensorDescs[3].GetSizes().back(); + + if (lastDimension % 2 != 0) + { + std::vector strides(m_inputTensorDescs[3].GetDimensionCount()); + strides[strides.size() - 1] = 1; + strides[strides.size() - 2] = lastDimension + 1; + m_inputTensorDescs[3].SetStrides(strides); + } + } + + auto dequantizedInputTensorDesc = TensorDesc::ConstructDefaultTensorDesc(mlDataType, bBroadcastedShape); + auto dequantizedInputDmlTensorDesc = dequantizedInputTensorDesc.GetDmlDesc(); + + // Initialize the output description while overriding the shape + m_outputTensorDescs[0] = TensorDesc::ConstructDefaultTensorDesc(mlDataType, outputShape); + + std::vector inputDescs = GetDmlInputDescs(); + std::vector outputDescs = GetDmlOutputDescs(); + + std::vector quantizationTensors; + quantizationTensors.push_back(inputDescs[2]); + + if (hasZeroPoint) + { + quantizationTensors.push_back(inputDescs[3]); + } + + DML_DEQUANTIZE_OPERATOR_DESC dequantizeDesc = {}; + dequantizeDesc.InputTensor = &inputDescs[1]; + dequantizeDesc.QuantizationType = hasZeroPoint ? DML_QUANTIZATION_TYPE_SCALE_ZERO_POINT : DML_QUANTIZATION_TYPE_SCALE; + dequantizeDesc.QuantizationTensorCount = gsl::narrow_cast(quantizationTensors.size()); + dequantizeDesc.QuantizationTensors = quantizationTensors.data(); + dequantizeDesc.OutputTensor = &dequantizedInputDmlTensorDesc; + DML_OPERATOR_DESC dequantizeOpDesc = { DML_OPERATOR_DEQUANTIZE, &dequantizeDesc }; + + DML_GEMM_OPERATOR_DESC gemmDesc = {}; + gemmDesc.ATensor = &inputDescs[0]; + gemmDesc.BTensor = &dequantizedInputDmlTensorDesc; + gemmDesc.CTensor = nullptr; + gemmDesc.OutputTensor = &outputDescs[0]; + gemmDesc.TransA = DML_MATRIX_TRANSFORM_NONE; + gemmDesc.TransB = DML_MATRIX_TRANSFORM_TRANSPOSE; + gemmDesc.Alpha = 1.0f; + gemmDesc.Beta = 0.0f; + DML_OPERATOR_DESC gemmOpDesc = { DML_OPERATOR_GEMM, &gemmDesc }; + + // Construct the graph + std::vector inputEdges; + std::vector intermediateEdges; + std::vector outputEdges; + + std::vector opDescs = { + &dequantizeOpDesc, + &gemmOpDesc, + }; + + DML_INPUT_GRAPH_EDGE_DESC secondInputToDequantizeEdge = {}; + secondInputToDequantizeEdge.GraphInputIndex = 1; + secondInputToDequantizeEdge.ToNodeIndex = 0; + secondInputToDequantizeEdge.ToNodeInputIndex = 0; + inputEdges.push_back(secondInputToDequantizeEdge); + + DML_INPUT_GRAPH_EDGE_DESC scaleToDequantizeEdge = {}; + scaleToDequantizeEdge.GraphInputIndex = 2; + scaleToDequantizeEdge.ToNodeIndex = 0; + scaleToDequantizeEdge.ToNodeInputIndex = 1; + inputEdges.push_back(scaleToDequantizeEdge); + + if (hasZeroPoint) + { + DML_INPUT_GRAPH_EDGE_DESC zeroPointToDequantizeEdge = {}; + zeroPointToDequantizeEdge.GraphInputIndex = 3; + zeroPointToDequantizeEdge.ToNodeIndex = 0; + zeroPointToDequantizeEdge.ToNodeInputIndex = 2; + inputEdges.push_back(zeroPointToDequantizeEdge); + } + + DML_INPUT_GRAPH_EDGE_DESC firstInputToGemmEdge = {}; + firstInputToGemmEdge.GraphInputIndex = 0; + firstInputToGemmEdge.ToNodeIndex = 1; + firstInputToGemmEdge.ToNodeInputIndex = 0; + inputEdges.push_back(firstInputToGemmEdge); + + DML_INTERMEDIATE_GRAPH_EDGE_DESC dequantizeToGemmEdge = {}; + dequantizeToGemmEdge.FromNodeIndex = 0; + dequantizeToGemmEdge.FromNodeOutputIndex = 0; + dequantizeToGemmEdge.ToNodeIndex = 1; + dequantizeToGemmEdge.ToNodeInputIndex = 1; + intermediateEdges.push_back(dequantizeToGemmEdge); + + DML_OUTPUT_GRAPH_EDGE_DESC gemmToOutputEdge = {}; + gemmToOutputEdge.FromNodeIndex = 1; + gemmToOutputEdge.FromNodeOutputIndex = 0; + gemmToOutputEdge.GraphOutputIndex = 0; + outputEdges.push_back(gemmToOutputEdge); + + MLOperatorGraphDesc operatorGraphDesc = {}; + operatorGraphDesc.inputEdgeCount = gsl::narrow_cast(inputEdges.size()); + operatorGraphDesc.inputEdges = inputEdges.data(); + operatorGraphDesc.intermediateEdgeCount = gsl::narrow_cast(intermediateEdges.size()); + operatorGraphDesc.intermediateEdges = intermediateEdges.data(); + operatorGraphDesc.outputEdgeCount = gsl::narrow_cast(outputEdges.size()); + operatorGraphDesc.outputEdges = outputEdges.data(); + operatorGraphDesc.nodeCount = gsl::narrow_cast(opDescs.size()); + operatorGraphDesc.nodes = opDescs.data(); + + SetDmlOperatorGraphDesc(std::move(operatorGraphDesc), kernelInfo); + } +}; + +void CALLBACK QueryMatMulNBits(IMLOperatorSupportQueryContextPrivate* context, /*out*/ bool* isSupported) +{ + *isSupported = false; + + MLOperatorAttributes attributes(context); + const auto bitCount = attributes.GetAttribute(AttrName::Bits); + if (bitCount != 4 && bitCount != 8) + { + return; + } + + // DML only supports perfect blocks at the moment, which is what most quantization tools produce anyways since they pad + // as part of the quantization process + const uint32_t bColCount = gsl::narrow_cast(attributes.GetAttribute(AttrName::UppercaseK)); + const auto blockSize = attributes.GetAttribute(AttrName::MatMulNBitsBlockSize); + ML_CHECK_VALID_ARGUMENT(blockSize > 0); + + if (bColCount % blockSize != 0) + { + return; + } + + *isSupported = true; +} + +DML_OP_DEFINE_CREATION_FUNCTION(MatMulNBits, DmlOperatorMatMulNBits); +} // namespace Dml diff --git a/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/OperatorRegistration.cpp b/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/OperatorRegistration.cpp index 0091210f43..a2338a6da9 100644 --- a/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/OperatorRegistration.cpp +++ b/onnxruntime/core/providers/dml/DmlExecutionProvider/src/Operators/OperatorRegistration.cpp @@ -530,6 +530,7 @@ DML_OP_EXTERN_CREATION_FUNCTION(BitwiseOr); DML_OP_EXTERN_CREATION_FUNCTION(BitwiseXor); DML_OP_EXTERN_CREATION_FUNCTION(BitwiseNot); DML_OP_EXTERN_CREATION_FUNCTION(RotaryEmbedding); +DML_OP_EXTERN_CREATION_FUNCTION(MatMulNBits); DML_OP_EXTERN_QUERY_FUNCTION(MaxPool); DML_OP_EXTERN_QUERY_FUNCTION(Slice); @@ -544,6 +545,7 @@ DML_OP_EXTERN_QUERY_FUNCTION(SkipLayerNormalization); DML_OP_EXTERN_QUERY_FUNCTION(QLinearSigmoid); DML_OP_EXTERN_QUERY_FUNCTION(QAttention); DML_OP_EXTERN_QUERY_FUNCTION(Attention); +DML_OP_EXTERN_QUERY_FUNCTION(MatMulNBits); constexpr static std::array typeNameListDefault = {"T"}; constexpr static std::array typeNameListDefaultV = {"V"}; @@ -632,6 +634,7 @@ constexpr static std::array supportedTypeListAttent constexpr static std::array supportedTypeListRotaryEmbedding = {SupportedTensorDataTypes::Float16to32, SupportedTensorDataTypes::Int64}; constexpr static std::array supportedTypeListGroupNorm = {SupportedTensorDataTypes::Float16to32, SupportedTensorDataTypes::Float16to32}; constexpr static std::array supportedTypeListNonZero = {SupportedTensorDataTypes::Float16to32 | SupportedTensorDataTypes::Ints8Bit | SupportedTensorDataTypes::Ints16Bit | SupportedTensorDataTypes::Ints32Bit | SupportedTensorDataTypes::Bool}; +constexpr static std::array supportedTypeListMatMulNBits = {SupportedTensorDataTypes::Float16to32, SupportedTensorDataTypes::UInt8}; constexpr static std::array supportedTypeListQLinearMatMul = { SupportedTensorDataTypes::Ints8Bit, @@ -1140,6 +1143,7 @@ constexpr static OperatorRegistrationInformation operatorRegistrationInformation {REG_INFO_MS( 1, BiasAdd, typeNameListDefault, supportedTypeListFloat16to32, DmlGraphSupport::Supported)}, {REG_INFO_MS( 1, QuickGelu, typeNameListDefault, supportedTypeListFloat16to32, DmlGraphSupport::Supported)}, {REG_INFO_MS( 1, GroupNorm, typeNameListGroupNorm, supportedTypeListGroupNorm, DmlGraphSupport::Supported)}, + {REG_INFO_MS( 1, MatMulNBits, typeNameListTwo, supportedTypeListMatMulNBits, DmlGraphSupport::Supported, requiredConstantCpuInputs(), std::nullopt, QueryMatMulNBits)}, // Operators that need to alias an input with an output {REG_INFO_MS_ALIAS(1, GroupQueryAttention, Aliases(std::make_pair(3, 1), std::make_pair(4, 2)), typeNameListAttention, supportedTypeListAttention, DmlGraphSupport::Supported, requiredConstantCpuInputs(6))}, diff --git a/onnxruntime/core/providers/dml/OperatorAuthorHelper/Attributes.h b/onnxruntime/core/providers/dml/OperatorAuthorHelper/Attributes.h index 5d5806865a..0c5739554b 100644 --- a/onnxruntime/core/providers/dml/OperatorAuthorHelper/Attributes.h +++ b/onnxruntime/core/providers/dml/OperatorAuthorHelper/Attributes.h @@ -126,6 +126,10 @@ namespace AttrName static constexpr const char* GraphFusedActivation = "activation"; static constexpr const char* GraphFusedAxis = "activation_axis"; static constexpr const char* Interleaved = "interleaved"; + static constexpr const char* Bits = "bits"; + static constexpr const char* UppercaseN = "N"; + static constexpr const char* UppercaseK = "K"; + static constexpr const char* MatMulNBitsBlockSize = "block_size"; } // namespace AttrName diff --git a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp index 7637c12086..e308f76d4b 100644 --- a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp +++ b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp @@ -2919,4 +2919,29 @@ namespace OperatorHelper return { EdgeShapes(std::move(outputShape)) }; } + std::vector MatMulNBitsHelper::GetOutputShapes(const MLShapeInferenceContext& shapeInfo) const + { + auto inputShape = shapeInfo.GetInputTensorShape(0); + onnxruntime::TensorShape aShape(std::vector(inputShape.begin(), inputShape.end())); + onnxruntime::TensorShape bShape({m_bRowCount, m_bColCount}); + + onnxruntime::MatMulComputeHelper helper; + + // The B tensor is always transposed + ML_CHECK_VALID_ARGUMENT(helper.Compute(aShape, bShape, false, true).IsOK()); + const auto outputShape = helper.OutputShape().GetDims(); + + std::vector uint32OutputShape; + uint32OutputShape.reserve(outputShape.size()); + std::transform(outputShape.begin(), outputShape.end(), std::back_inserter(uint32OutputShape), [](int64_t dimSize){ return static_cast(dimSize); }); + + return { EdgeShapes(uint32OutputShape) }; + } + + void MatMulNBitsHelper::Initialize(const IKernelInformationAdapter& kernelInformation) + { + m_bRowCount = kernelInformation.GetAttributes().GetAttribute(AttrName::UppercaseN); + m_bColCount = kernelInformation.GetAttributes().GetAttribute(AttrName::UppercaseK); + } + } // namespace OperatorHelper diff --git a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.h b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.h index 0ad6ff6e3e..06bed80a7c 100644 --- a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.h +++ b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.h @@ -1606,6 +1606,24 @@ public: std::vector GetOutputShapes(const MLShapeInferenceContext& shapeInfo) const; }; +class MatMulNBitsHelper +{ +public: + template + MatMulNBitsHelper(const Info_t& info, const Shape_t& shapeInfo) + { + Initialize(KernelInformationAdapter(info)); + } + + std::vector GetOutputShapes(const MLShapeInferenceContext& shapeInfo) const; + +private: + void Initialize(const IKernelInformationAdapter& kernelInformation); + + int64_t m_bRowCount; + int64_t m_bColCount; +}; + using ShapeInferenceHelper_Conv = ConvHelper; using ShapeInferenceHelper_NhwcConv = NhwcConvHelper; using ShapeInferenceHelper_ConvTranspose = ConvTransposeHelper; @@ -1859,5 +1877,6 @@ using ShapeInferenceHelper_DmlFusedSum = GetBroadcastedOutputShapeHelper; using ShapeInferenceHelper_Shape = ShapeHelper; using ShapeInferenceHelper_Size = SizeHelper; using ShapeInferenceHelper_BiasSplitGelu = BiasSplitGeluHelper; +using ShapeInferenceHelper_MatMulNBits = MatMulNBitsHelper; } // namespace OperatorHelper diff --git a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorVersions.h b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorVersions.h index 1c84e3bada..896f18c507 100644 --- a/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorVersions.h +++ b/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorVersions.h @@ -467,6 +467,7 @@ namespace OperatorHelper static const int sc_sinceVer_RotaryEmbedding = 1; static const int sc_sinceVer_QLinearAveragePool = 1; static const int sc_sinceVer_QLinearGlobalAveragePool = 1; + static const int sc_sinceVer_MatMulNBits = 1; static const int sc_sinceVer_DynamicQuantizeMatMul = 1; } // namespace MsftOperatorSet1 diff --git a/onnxruntime/test/contrib_ops/matmul_4bits_test.cc b/onnxruntime/test/contrib_ops/matmul_4bits_test.cc index d294fd4e2b..353455631a 100644 --- a/onnxruntime/test/contrib_ops/matmul_4bits_test.cc +++ b/onnxruntime/test/contrib_ops/matmul_4bits_test.cc @@ -156,7 +156,15 @@ void RunTest(int64_t M, int64_t N, int64_t K, int64_t block_size, int64_t accura test.SetOutputAbsErr("Y", fp16_abs_error); std::vector> execution_providers; + +#ifdef USE_CUDA execution_providers.push_back(DefaultCudaExecutionProvider()); +#endif + +#ifdef USE_DML + execution_providers.push_back(DefaultDmlExecutionProvider()); +#endif + test.Run(OpTester::ExpectResult::kExpectSuccess, "", {}, nullptr, &execution_providers); } else { test.AddInput("A", {M, K}, input0_vals, false); @@ -205,7 +213,7 @@ TEST(MatMulNBits, Float32) { for (auto N : {1, 2, 32, 288}) { for (auto K : {16, 32, 64, 128, 256, 1024, 93, 1234}) { for (auto block_size : {16, 32, 64, 128}) { -#ifdef ORT_NEURAL_SPEED +#if defined(ORT_NEURAL_SPEED) || defined(USE_DML) for (auto accuracy_level : {0, 1, 4}) { RunTest(M, N, K, block_size, accuracy_level, false, false); RunTest(M, N, K, block_size, accuracy_level, true, false); @@ -224,15 +232,25 @@ TEST(MatMulNBits, Float32) { } } -#if defined(USE_CUDA) +#if defined(USE_CUDA) || defined(USE_DML) TEST(MatMulNBits, Float16) { +#ifdef USE_CUDA + auto has_gidx_options = {true, false}; +#else + auto has_gidx_options = {false}; +#endif + for (auto M : {1, 2, 100}) { for (auto N : {1, 2, 32, 288}) { for (auto K : {16, 32, 64, 128, 256, 1024, 93, 1234}) { for (auto block_size : {16, 32, 64, 128}) { - for (auto has_gidx : {true, false}) { + for (auto has_gidx : has_gidx_options) { +#ifdef USE_DML + RunTest(M, N, K, block_size, 0, false, true, has_gidx, true, 0.04f); +#else RunTest(M, N, K, block_size, 0, false, true, has_gidx); RunTest(M, N, K, block_size, 0, true, true, has_gidx, false); +#endif } } } @@ -241,11 +259,21 @@ TEST(MatMulNBits, Float16) { } TEST(MatMulNBits, Float16Large) { +#ifdef USE_DML + // For some reason, the A10 machine that runs these tests during CI has a much bigger error than all retail + // machines we tested on. All consumer-grade machines from Nvidia/AMD/Intel seem to pass these tests with an + // absolute error of 0.08, but the A10 has errors going as high as 0.22. Ultimately, given the large number + // of elements in this test, ULPs should probably be used instead of absolute/relative tolerances. + float abs_error = 0.3f; +#else + float abs_error = 0.05f; +#endif + for (auto block_size : {16, 32, 64, 128}) { for (auto symmetric : {false, true}) { - RunTest(1, 4096, 4096, block_size, 0, symmetric, true, false, true, 0.05f); - RunTest(1, 4096, 11008, block_size, 0, symmetric, true, false, true, 0.05f); - RunTest(1, 11008, 4096, block_size, 0, symmetric, true, false, true, 0.05f); + RunTest(1, 4096, 4096, block_size, 0, symmetric, true, false, true, abs_error); + RunTest(1, 4096, 11008, block_size, 0, symmetric, true, false, true, abs_error); + RunTest(1, 11008, 4096, block_size, 0, symmetric, true, false, true, abs_error); } } }