diff --git a/VERSION_NUMBER b/VERSION_NUMBER
index a6c2798a482eb..49e0a31d4964d 100644
--- a/VERSION_NUMBER
+++ b/VERSION_NUMBER
@@ -1 +1 @@
-1.23.0
+1.23.1
diff --git a/cmake/onnxruntime_unittests.cmake b/cmake/onnxruntime_unittests.cmake
index 6aad71e40b2a8..b23365e99c2d7 100644
--- a/cmake/onnxruntime_unittests.cmake
+++ b/cmake/onnxruntime_unittests.cmake
@@ -1800,6 +1800,7 @@ endif()
if (WIN32 AND onnxruntime_BUILD_SHARED_LIB AND
NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten" AND
NOT onnxruntime_MINIMAL_BUILD)
+ # example_plugin_ep
file(GLOB onnxruntime_autoep_test_library_src "${TEST_SRC_DIR}/autoep/library/*.h"
"${TEST_SRC_DIR}/autoep/library/*.cc")
onnxruntime_add_shared_library_module(example_plugin_ep ${onnxruntime_autoep_test_library_src})
@@ -1822,6 +1823,9 @@ if (WIN32 AND onnxruntime_BUILD_SHARED_LIB AND
set_property(TARGET example_plugin_ep APPEND_STRING PROPERTY LINK_FLAGS
${ONNXRUNTIME_AUTOEP_LIB_LINK_FLAG})
+ set_target_properties(example_plugin_ep PROPERTIES FOLDER "ONNXRuntimeTest")
+ source_group(TREE ${TEST_SRC_DIR} FILES ${onnxruntime_autoep_test_library_src})
+
# test library
file(GLOB onnxruntime_autoep_test_SRC "${ONNXRUNTIME_AUTOEP_TEST_SRC_DIR}/*.h"
"${ONNXRUNTIME_AUTOEP_TEST_SRC_DIR}/*.cc")
diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md
index 660c63d056335..c7844b4120d97 100644
--- a/docs/OperatorKernels.md
+++ b/docs/OperatorKernels.md
@@ -253,6 +253,8 @@ Do not modify directly.*
|||[9, 12]|**T** = tensor(float)|
|||[1, 8]|**T** = tensor(float)|
|MelWeightMatrix|*in* num_mel_bins:**T1**
*in* dft_length:**T1**
*in* sample_rate:**T1**
*in* lower_edge_hertz:**T2**
*in* upper_edge_hertz:**T2**
*out* output:**T3**|17+|**T1** = tensor(int32), tensor(int64)
**T2** = tensor(float)
**T3** = tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
+|MemcpyFromHost|*in* X:**T**
*out* Y:**T**|1+|**T** = seq(tensor(bfloat16)), seq(tensor(bool)), seq(tensor(double)), seq(tensor(float)), seq(tensor(float16)), seq(tensor(float8e4m3fn)), seq(tensor(float8e4m3fnuz)), seq(tensor(float8e5m2)), seq(tensor(float8e5m2fnuz)), seq(tensor(int16)), seq(tensor(int32)), seq(tensor(int64)), seq(tensor(int8)), seq(tensor(uint16)), seq(tensor(uint32)), seq(tensor(uint64)), seq(tensor(uint8)), tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float8e4m3fn), tensor(float8e4m3fnuz), tensor(float8e5m2), tensor(float8e5m2fnuz), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
+|MemcpyToHost|*in* X:**T**
*out* Y:**T**|1+|**T** = seq(tensor(bfloat16)), seq(tensor(bool)), seq(tensor(double)), seq(tensor(float)), seq(tensor(float16)), seq(tensor(float8e4m3fn)), seq(tensor(float8e4m3fnuz)), seq(tensor(float8e5m2)), seq(tensor(float8e5m2fnuz)), seq(tensor(int16)), seq(tensor(int32)), seq(tensor(int64)), seq(tensor(int8)), seq(tensor(uint16)), seq(tensor(uint32)), seq(tensor(uint64)), seq(tensor(uint8)), tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float8e4m3fn), tensor(float8e4m3fnuz), tensor(float8e5m2), tensor(float8e5m2fnuz), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|Min|*in* data_0:**T**
*out* min:**T**|13+|**T** = tensor(double), tensor(float), tensor(float16), tensor(int32), tensor(int64), tensor(uint32), tensor(uint64)|
|||12|**T** = tensor(double), tensor(float), tensor(float16), tensor(int32), tensor(int64), tensor(uint32), tensor(uint64)|
|||[8, 11]|**T** = tensor(double), tensor(float)|
@@ -547,6 +549,7 @@ Do not modify directly.*
|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)|
|MatMulNBits|*in* A:**T1**
*in* B:**T2**
*in* scales:**T1**
*in* zero_points:**T3**
*in* g_idx:**T4**
*in* bias:**T1**
*out* Y:**T1**|1+|**T1** = tensor(float), tensor(float16)
**T2** = tensor(uint8)
**T3** = tensor(float), tensor(float16), tensor(uint8)
**T4** = tensor(int32)|
|MaxpoolWithMask|*in* X:**T**
*in* M:**tensor(int32)**
*out* Y:**T**|1+|**T** = tensor(float)|
+|MoE|*in* input:**T**
*in* router_probs:**T**
*in* fc1_experts_weights:**T**
*in* fc1_experts_bias:**T**
*in* fc2_experts_weights:**T**
*in* fc2_experts_bias:**T**
*in* fc3_experts_weights:**T**
*in* fc3_experts_bias:**T**
*out* output:**T**|1+|**T** = tensor(float)|
|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* cache_indirection:**M**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**T** = tensor(float)|
|MurmurHash3|*in* X:**T1**
*out* Y:**T2**|1+|**T1** = tensor(double), tensor(float), tensor(int32), tensor(int64), tensor(string), tensor(uint32), tensor(uint64)
**T2** = tensor(int32), tensor(uint32)|
|NGramRepeatBlock|*in* input_ids:**Tid**
*in* scores:**T**
*out* scores_out:**T**|1+|**T** = tensor(float)
**Tid** = tensor(int64)|
diff --git a/docs/python/README.rst b/docs/python/README.rst
index fdef200c1d0de..c23c194ed8132 100644
--- a/docs/python/README.rst
+++ b/docs/python/README.rst
@@ -8,6 +8,11 @@ For more information on ONNX Runtime, please see `aka.ms/onnxruntime >;
/** \brief Wrapper around ::OrtSyncStream
*
*/
-struct SyncStream : detail::Base {
- explicit SyncStream(std::nullptr_t) {} ///< Create an empty SyncStream object, must be assigned a valid one to be used
- explicit SyncStream(OrtSyncStream* p) : Base{p} {} ///< Take ownership of a pointer created by C API
- void* GetHandle() const; ///< Wraps SyncStream_GetHandle
+
+namespace detail {
+template
+struct SyncStreamImpl : Base {
+ using B = Base;
+ using B::B;
+ // For some reason this is not a const method on the stream
+ void* GetHandle(); ///< Wraps SyncStream_GetHandle
};
+} // namespace detail
+
+struct SyncStream : detail::SyncStreamImpl {
+ ///< Create an empty SyncStream object, must be assigned a valid one to be used
+ explicit SyncStream(std::nullptr_t) {}
+ ///< Take ownership of a pointer created by C API
+ explicit SyncStream(OrtSyncStream* p) : SyncStreamImpl{p} {}
+};
+
+using UnownedSyncStream = detail::SyncStreamImpl>;
namespace detail {
template
diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h
index 59979189eed0f..cb6448ad12a81 100644
--- a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h
+++ b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h
@@ -669,9 +669,12 @@ inline void KeyValuePairs::Remove(const char* key) {
GetApi().RemoveKeyValuePair(this->p_, key);
}
-inline void* SyncStream::GetHandle() const {
+namespace detail {
+template
+inline void* SyncStreamImpl::GetHandle() {
return GetApi().SyncStream_GetHandle(this->p_);
}
+} // namespace detail
namespace detail {
template
@@ -1582,11 +1585,13 @@ inline std::vector ConstSessionImpl::GetMemoryInfoForInputs(
auto num_inputs = GetInputCount();
std::vector mem_infos;
- mem_infos.resize(num_inputs);
+ if (num_inputs > 0) {
+ mem_infos.resize(num_inputs);
- ThrowOnError(GetApi().SessionGetMemoryInfoForInputs(this->p_,
- reinterpret_cast(mem_infos.data()),
- num_inputs));
+ ThrowOnError(GetApi().SessionGetMemoryInfoForInputs(this->p_,
+ reinterpret_cast(mem_infos.data()),
+ num_inputs));
+ }
return mem_infos;
}
@@ -1598,11 +1603,13 @@ inline std::vector ConstSessionImpl::GetMemoryInfoForOutputs
auto num_outputs = GetOutputCount();
std::vector mem_infos;
- mem_infos.resize(num_outputs);
+ if (num_outputs > 0) {
+ mem_infos.resize(num_outputs);
- ThrowOnError(GetApi().SessionGetMemoryInfoForOutputs(this->p_,
- reinterpret_cast(mem_infos.data()),
- num_outputs));
+ ThrowOnError(GetApi().SessionGetMemoryInfoForOutputs(this->p_,
+ reinterpret_cast(mem_infos.data()),
+ num_outputs));
+ }
return mem_infos;
}
@@ -1631,12 +1638,12 @@ template
inline std::vector ConstSessionImpl::GetEpDeviceForInputs() const {
auto num_inputs = GetInputCount();
std::vector input_devices;
- input_devices.resize(num_inputs);
-
- ThrowOnError(GetApi().SessionGetEpDeviceForInputs(this->p_,
- reinterpret_cast(input_devices.data()),
- num_inputs));
-
+ if (num_inputs > 0) {
+ input_devices.resize(num_inputs);
+ ThrowOnError(GetApi().SessionGetEpDeviceForInputs(this->p_,
+ reinterpret_cast(input_devices.data()),
+ num_inputs));
+ }
return input_devices;
}
diff --git a/js/common/lib/version.ts b/js/common/lib/version.ts
index 994eb6f4300c1..f00f4ec8dee50 100644
--- a/js/common/lib/version.ts
+++ b/js/common/lib/version.ts
@@ -4,4 +4,4 @@
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
-export const version = '1.23.0';
+export const version = '1.23.1';
diff --git a/js/common/package-lock.json b/js/common/package-lock.json
index 706f8b46a3ad4..9ef468a229788 100644
--- a/js/common/package-lock.json
+++ b/js/common/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"devDependencies": {
"typedoc": "^0.25.7"
diff --git a/js/common/package.json b/js/common/package.json
index a0eff9095e6d7..200aff42f8fca 100644
--- a/js/common/package.json
+++ b/js/common/package.json
@@ -2,7 +2,7 @@
"license": "MIT",
"type": "module",
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"repository": {
"url": "https://github.com/Microsoft/onnxruntime.git",
"type": "git"
diff --git a/js/node/lib/version.ts b/js/node/lib/version.ts
index 994eb6f4300c1..f00f4ec8dee50 100644
--- a/js/node/lib/version.ts
+++ b/js/node/lib/version.ts
@@ -4,4 +4,4 @@
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
-export const version = '1.23.0';
+export const version = '1.23.1';
diff --git a/js/node/package-lock.json b/js/node/package-lock.json
index bd7e6cc1966c7..0a65eab39df70 100644
--- a/js/node/package-lock.json
+++ b/js/node/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "onnxruntime-node",
- "version": "1.23.0",
+ "version": "1.23.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "onnxruntime-node",
- "version": "1.23.0",
+ "version": "1.23.1",
"hasInstallScript": true,
"license": "MIT",
"os": [
@@ -30,7 +30,7 @@
},
"../common": {
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"devDependencies": {
"typedoc": "^0.25.7"
diff --git a/js/node/package.json b/js/node/package.json
index 5520a48aa124a..1f29f2354b0d7 100644
--- a/js/node/package.json
+++ b/js/node/package.json
@@ -11,7 +11,7 @@
6
]
},
- "version": "1.23.0",
+ "version": "1.23.1",
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^3.0.0",
diff --git a/js/node/script/install-metadata-versions.js b/js/node/script/install-metadata-versions.js
index 3147f90904e7a..23df0b7ac96ed 100644
--- a/js/node/script/install-metadata-versions.js
+++ b/js/node/script/install-metadata-versions.js
@@ -4,4 +4,4 @@
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
-module.exports = { nuget: [{ feed: 'nuget', version: '1.23.0' }] };
+module.exports = { nuget: [{ feed: 'nuget', version: '1.23.1' }] };
diff --git a/js/react_native/lib/version.ts b/js/react_native/lib/version.ts
index 994eb6f4300c1..f00f4ec8dee50 100644
--- a/js/react_native/lib/version.ts
+++ b/js/react_native/lib/version.ts
@@ -4,4 +4,4 @@
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
-export const version = '1.23.0';
+export const version = '1.23.1';
diff --git a/js/react_native/package-lock.json b/js/react_native/package-lock.json
index ec2147b2cc4ba..f681b9166da98 100644
--- a/js/react_native/package-lock.json
+++ b/js/react_native/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "onnxruntime-react-native",
- "version": "1.23.0",
+ "version": "1.23.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "onnxruntime-react-native",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
@@ -31,7 +31,7 @@
},
"../common": {
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"devDependencies": {
"typedoc": "^0.25.7"
diff --git a/js/react_native/package.json b/js/react_native/package.json
index 7a5ee35bdb25a..a88f5cf267aed 100644
--- a/js/react_native/package.json
+++ b/js/react_native/package.json
@@ -37,7 +37,7 @@
"registry": "https://registry.npmjs.org/"
},
"source": "lib/index",
- "version": "1.23.0",
+ "version": "1.23.1",
"main": "dist/commonjs/index",
"homepage": "https://github.com/microsoft/onnxruntime/blob/main/js/react_native/README.md",
"files": [
diff --git a/js/web/lib/version.ts b/js/web/lib/version.ts
index 994eb6f4300c1..f00f4ec8dee50 100644
--- a/js/web/lib/version.ts
+++ b/js/web/lib/version.ts
@@ -4,4 +4,4 @@
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
-export const version = '1.23.0';
+export const version = '1.23.1';
diff --git a/js/web/package-lock.json b/js/web/package-lock.json
index eabb198e97177..74776abb25bd5 100644
--- a/js/web/package-lock.json
+++ b/js/web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "onnxruntime-web",
- "version": "1.23.0",
+ "version": "1.23.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "onnxruntime-web",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"dependencies": {
"flatbuffers": "^25.1.24",
@@ -50,7 +50,7 @@
},
"../common": {
"name": "onnxruntime-common",
- "version": "1.23.0",
+ "version": "1.23.1",
"license": "MIT",
"devDependencies": {
"typedoc": "^0.25.7"
diff --git a/js/web/package.json b/js/web/package.json
index 425aa88035424..db20202b4f24e 100644
--- a/js/web/package.json
+++ b/js/web/package.json
@@ -7,7 +7,7 @@
"type": "git"
},
"author": "fs-eire",
- "version": "1.23.0",
+ "version": "1.23.1",
"jsdelivr": "dist/ort.min.js",
"dependencies": {
"flatbuffers": "^25.1.24",
diff --git a/onnxruntime/__init__.py b/onnxruntime/__init__.py
index 550502cf3bc48..ac25159802092 100644
--- a/onnxruntime/__init__.py
+++ b/onnxruntime/__init__.py
@@ -8,7 +8,7 @@
or the `Github project `_.
"""
-__version__ = "1.23.0"
+__version__ = "1.23.1"
__author__ = "Microsoft"
# we need to do device version validation (for example to check Cuda version for an onnxruntime-training package).
@@ -31,14 +31,17 @@
OrtAllocatorType, # noqa: F401
OrtArenaCfg, # noqa: F401
OrtCompileApiFlags, # noqa: F401
+ OrtDeviceMemoryType, # noqa: F401
OrtEpDevice, # noqa: F401
OrtExecutionProviderDevicePolicy, # noqa: F401
OrtExternalInitializerInfo, # noqa: F401
OrtHardwareDevice, # noqa: F401
OrtHardwareDeviceType, # noqa: F401
OrtMemoryInfo, # noqa: F401
+ OrtMemoryInfoDeviceType, # noqa: F401
OrtMemType, # noqa: F401
OrtSparseFormat, # noqa: F401
+ OrtSyncStream, # noqa: F401
RunOptions, # noqa: F401
SessionIOBinding, # noqa: F401
SessionOptions, # noqa: F401
@@ -78,6 +81,7 @@
OrtDevice, # noqa: F401
OrtValue, # noqa: F401
SparseTensor, # noqa: F401
+ copy_tensors, # noqa: F401
)
# TODO: thiagofc: Temporary experimental namespace for new PyTorch front-end
diff --git a/onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc b/onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc
index 34410a5f42630..d959d11e3fd43 100644
--- a/onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc
+++ b/onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc
@@ -108,6 +108,7 @@ class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1,
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, QGemm);
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, MLFloat16, QMoE);
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, QMoE);
+class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, MoE);
// ******** End: Quantization ******************* //
#ifdef MLAS_F16VEC_INTRINSICS_SUPPORTED
@@ -275,6 +276,7 @@ Status RegisterQuantizationKernels(KernelRegistry& kernel_registry) {
BuildKernelCreateInfo,
BuildKernelCreateInfo,
BuildKernelCreateInfo,
+ BuildKernelCreateInfo,
};
for (auto& function_table_entry : function_table) {
diff --git a/onnxruntime/contrib_ops/cpu/moe/moe_base_cpu.h b/onnxruntime/contrib_ops/cpu/moe/moe_base_cpu.h
index eae96c186d471..84580b310f6b3 100644
--- a/onnxruntime/contrib_ops/cpu/moe/moe_base_cpu.h
+++ b/onnxruntime/contrib_ops/cpu/moe/moe_base_cpu.h
@@ -6,7 +6,7 @@
#include "core/common/common.h"
#include "core/framework/tensor_shape.h"
#include "core/framework/op_kernel.h"
-#include "contrib_ops/cpu/moe/moe_helper.h"
+#include "moe_helper.h"
#include
namespace onnxruntime {
diff --git a/onnxruntime/contrib_ops/cpu/moe/moe_cpu.cc b/onnxruntime/contrib_ops/cpu/moe/moe_cpu.cc
new file mode 100644
index 0000000000000..73be099181aa9
--- /dev/null
+++ b/onnxruntime/contrib_ops/cpu/moe/moe_cpu.cc
@@ -0,0 +1,605 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#include "contrib_ops/cpu/moe/moe_cpu.h"
+#include "contrib_ops/cpu/moe/moe_utils.h"
+#include "contrib_ops/cpu/moe/moe_helper.h"
+#include "core/framework/op_kernel.h"
+#include "core/providers/common.h"
+#include "core/providers/cpu/math/gemm_helper.h"
+#include "core/util/math_cpuonly.h"
+#include "core/mlas/inc/mlas.h"
+#include "core/framework/float16.h"
+#include "core/framework/allocator.h"
+#include "core/platform/threadpool.h"
+
+#include
+#include
+#include
+
+namespace onnxruntime {
+namespace contrib {
+
+template
+MoE::MoE(const OpKernelInfo& op_kernel_info) : OpKernel(op_kernel_info), MoEBaseCPU(op_kernel_info) {
+ if (activation_type_ == ActivationType::SwiGLU && swiglu_fusion_ != 1) {
+ ORT_THROW("CPU MoE only supports interleaved SwiGLU format. Please set swiglu_fusion=1.");
+ }
+}
+
+template
+Status MoE::Compute(OpKernelContext* context) const {
+ const Tensor* input = context->Input(0);
+ const Tensor* router_probs = context->Input(1);
+ const Tensor* fc1_experts_weights = context->Input(2);
+ const Tensor* fc1_experts_bias = context->Input(3);
+ const Tensor* fc2_experts_weights = context->Input(4);
+ const Tensor* fc2_experts_bias = context->Input(5);
+ const Tensor* fc3_experts_weights = context->Input(6);
+ const Tensor* fc3_experts_bias = context->Input(7);
+
+ // FC3 not supported
+ if (fc3_experts_weights != nullptr || fc3_experts_bias != nullptr) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED,
+ "FC3 is not implemented for CPU MoE.");
+ }
+
+ MoEParameters moe_params;
+ ORT_RETURN_IF_ERROR(moe_helper::CheckInputs(
+ moe_params, input, router_probs,
+ fc1_experts_weights, fc1_experts_bias, nullptr,
+ fc2_experts_weights, fc2_experts_bias, nullptr,
+ fc3_experts_weights, fc3_experts_bias, nullptr,
+ 1,
+ activation_type_ == ActivationType::SwiGLU));
+
+ Tensor* output = context->Output(0, input->Shape());
+
+ return ComputeMoE(context, input, router_probs, fc1_experts_weights, fc1_experts_bias,
+ fc2_experts_weights, fc2_experts_bias, output);
+}
+
+template
+Status MoE::ComputeMoE(const OpKernelContext* context,
+ const Tensor* input,
+ const Tensor* router_probs,
+ const Tensor* fc1_experts_weights,
+ const Tensor* fc1_experts_bias,
+ const Tensor* fc2_experts_weights,
+ const Tensor* fc2_experts_bias,
+ Tensor* output) const {
+ const auto& input_shape = input->Shape();
+ const auto& router_shape = router_probs->Shape();
+ const auto& fc2_shape = fc2_experts_weights->Shape();
+
+ const int64_t num_tokens = input_shape.Size() / input_shape[input_shape.NumDimensions() - 1];
+ const int64_t hidden_size = input_shape[input_shape.NumDimensions() - 1];
+ const int64_t num_experts = router_shape[1];
+ const int64_t inter_size = (fc2_shape[1] * fc2_shape[2]) / hidden_size;
+ const bool is_swiglu = activation_type_ == ActivationType::SwiGLU;
+ const int64_t fc1_output_size = is_swiglu ? (inter_size * 2) : inter_size;
+
+ const T* input_data = input->Data();
+ const T* router_data = router_probs->Data();
+ const T* fc1_weights_data = fc1_experts_weights->Data();
+ const T* fc1_bias_data = fc1_experts_bias ? fc1_experts_bias->Data() : nullptr;
+ const T* fc2_weights_data = fc2_experts_weights->Data();
+ const T* fc2_bias_data = fc2_experts_bias ? fc2_experts_bias->Data() : nullptr;
+ T* output_data = output->MutableData();
+
+ AllocatorPtr allocator;
+ ORT_RETURN_IF_ERROR(context->GetTempSpaceAllocator(&allocator));
+
+ const T* input_data_to_use = input_data;
+ IAllocatorUniquePtr input_data_copy_ptr;
+ if (normalize_routing_weights_) {
+ input_data_copy_ptr = IAllocator::MakeUniquePtr(allocator, static_cast(num_tokens * hidden_size));
+ T* input_data_copy = input_data_copy_ptr.get();
+ std::copy(input_data, input_data + (num_tokens * hidden_size), input_data_copy);
+ input_data_to_use = input_data_copy;
+ }
+
+ std::fill_n(output_data, output->Shape().Size(), T{});
+
+ IAllocatorUniquePtr router_logits_float_buffer;
+ const float* router_logits_float = nullptr;
+ if constexpr (std::is_same_v) {
+ router_logits_float_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(num_tokens * num_experts));
+ router_logits_float = router_logits_float_buffer.get();
+ MlasConvertHalfToFloatBuffer(reinterpret_cast(router_data), const_cast(router_logits_float), static_cast(num_tokens * num_experts));
+ } else {
+ router_logits_float = reinterpret_cast(router_data);
+ }
+
+ auto route_expert_ptr = IAllocator::MakeUniquePtr(allocator, static_cast(num_tokens * k_));
+ int* route_expert = route_expert_ptr.get();
+ auto route_scale_ptr = IAllocator::MakeUniquePtr(allocator, static_cast(num_tokens * k_));
+ float* route_scale = route_scale_ptr.get();
+
+ auto* tp = context->GetOperatorThreadPool();
+ int num_routing_threads = 1;
+ if (tp != nullptr && num_tokens >= 1024) {
+ int max_threads = concurrency::ThreadPool::DegreeOfParallelism(tp);
+ num_routing_threads = std::min(static_cast(num_tokens / 512), max_threads);
+ num_routing_threads = std::max(1, num_routing_threads);
+ }
+
+ std::vector>> thread_local_expert_token_maps(num_routing_threads);
+ for (auto& map : thread_local_expert_token_maps) {
+ map.resize(static_cast(num_experts));
+ for (auto& expert_map : map) {
+ expert_map.reserve(static_cast(std::max(static_cast(1), num_tokens / num_experts / num_routing_threads * 2)));
+ }
+ }
+
+ concurrency::ThreadPool::TrySimpleParallelFor(tp, num_routing_threads, [&](std::ptrdiff_t thread_id) {
+ auto work = concurrency::ThreadPool::PartitionWork(static_cast(thread_id), num_routing_threads, static_cast(num_tokens));
+ auto& local_expert_token_map = thread_local_expert_token_maps[thread_id];
+
+ std::vector> sorted_logits(static_cast(num_experts));
+ std::vector full_softmax(static_cast(num_experts));
+
+ for (int64_t i = work.start; i < work.end; ++i) {
+ const float* logits = router_logits_float + i * num_experts;
+
+ float max_logit = logits[0];
+ for (int64_t j = 1; j < num_experts; ++j) {
+ max_logit = std::max(max_logit, logits[j]);
+ }
+
+ float sum_exp = 0.0f;
+ for (int64_t j = 0; j < num_experts; ++j) {
+ full_softmax[static_cast(j)] = std::exp(logits[j] - max_logit);
+ sum_exp += full_softmax[static_cast(j)];
+ }
+
+ const float inv_sum_exp = 1.0f / sum_exp;
+ for (int64_t j = 0; j < num_experts; ++j) {
+ full_softmax[static_cast(j)] *= inv_sum_exp;
+ sorted_logits[static_cast(j)] = {full_softmax[static_cast(j)], j};
+ }
+
+ std::partial_sort(sorted_logits.begin(), sorted_logits.begin() + static_cast(k_), sorted_logits.end(), std::greater<>());
+
+ if (normalize_routing_weights_) {
+ float top_k_sum = 0.0f;
+ for (int64_t j = 0; j < k_; ++j) {
+ top_k_sum += sorted_logits[static_cast(j)].first;
+ }
+ const float inv_top_k_sum = 1.0f / top_k_sum;
+
+ for (int64_t j = 0; j < k_; ++j) {
+ int64_t expert_idx = sorted_logits[static_cast(j)].second;
+ int64_t route_idx = i * k_ + j;
+ float normalized_weight = sorted_logits[static_cast(j)].first * inv_top_k_sum;
+
+ route_expert[route_idx] = static_cast(expert_idx);
+ route_scale[route_idx] = normalized_weight;
+ if (normalized_weight > 0.0f) {
+ local_expert_token_map[static_cast(expert_idx)].push_back(route_idx);
+ }
+ }
+ } else {
+ for (int64_t j = 0; j < k_; ++j) {
+ int64_t expert_idx = sorted_logits[static_cast(j)].second;
+ int64_t route_idx = i * k_ + j;
+ float weight = sorted_logits[static_cast(j)].first;
+
+ route_expert[route_idx] = static_cast(expert_idx);
+ route_scale[route_idx] = weight;
+ if (weight > 0.0f) {
+ local_expert_token_map[static_cast(expert_idx)].push_back(route_idx);
+ }
+ }
+ }
+ }
+ });
+
+ std::vector> expert_token_map(static_cast(num_experts));
+
+ for (int64_t expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
+ size_t total_tokens_for_expert = 0;
+ for (int t = 0; t < num_routing_threads; ++t) {
+ total_tokens_for_expert += thread_local_expert_token_maps[t][static_cast(expert_idx)].size();
+ }
+ expert_token_map[static_cast(expert_idx)].reserve(total_tokens_for_expert);
+ }
+
+ for (int t = 0; t < num_routing_threads; ++t) {
+ for (int64_t expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
+ auto& local_tokens = thread_local_expert_token_maps[t][static_cast(expert_idx)];
+ if (!local_tokens.empty()) {
+ auto& expert_map = expert_token_map[static_cast(expert_idx)];
+ expert_map.insert(expert_map.end(),
+ std::make_move_iterator(local_tokens.begin()),
+ std::make_move_iterator(local_tokens.end()));
+ }
+ }
+ }
+
+ for (int64_t expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
+ auto& expert_map = expert_token_map[static_cast(expert_idx)];
+ if (!expert_map.empty()) {
+ std::sort(expert_map.begin(), expert_map.end());
+ }
+ }
+
+ IAllocatorUniquePtr input_float_buffer;
+ const float* input_float;
+ if constexpr (std::is_same_v) {
+ input_float_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(num_tokens * hidden_size));
+ input_float = input_float_buffer.get();
+ MlasConvertHalfToFloatBuffer(reinterpret_cast(input_data_to_use), const_cast(input_float), static_cast(num_tokens * hidden_size));
+ } else {
+ input_float = reinterpret_cast(input_data_to_use);
+ }
+
+ int num_expert_threads = 1;
+ if (tp != nullptr) {
+ int total_active_experts = 0;
+ for (int64_t expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
+ if (!expert_token_map[static_cast(expert_idx)].empty()) {
+ total_active_experts++;
+ }
+ }
+
+ if (total_active_experts > 0) {
+ int max_threads = concurrency::ThreadPool::DegreeOfParallelism(tp);
+ num_expert_threads = std::min(total_active_experts, max_threads);
+ num_expert_threads = std::min(num_expert_threads, 8);
+ }
+ }
+
+ // Calculate maximum possible tokens per expert for buffer sizing
+ int64_t max_tokens_per_expert = 0;
+ for (int64_t expert_idx = 0; expert_idx < num_experts; ++expert_idx) {
+ const auto& routes = expert_token_map[static_cast(expert_idx)];
+ max_tokens_per_expert = std::max(max_tokens_per_expert, static_cast(routes.size()));
+ }
+
+ // Thread-local buffer pool for expert processing
+ struct ThreadLocalBuffers {
+ IAllocatorUniquePtr A1_buffer;
+ IAllocatorUniquePtr batch_weights_buffer;
+ IAllocatorUniquePtr token_ids_buffer;
+ IAllocatorUniquePtr A1_t_buffer;
+ IAllocatorUniquePtr C2_buffer;
+ // Additional buffers for ProcessExpertBatch to avoid repeated allocations
+ IAllocatorUniquePtr fc1_output_buffer;
+ IAllocatorUniquePtr activation_output_buffer;
+ int64_t current_capacity = 0;
+ int64_t current_fc1_capacity = 0;
+ int64_t current_activation_capacity = 0;
+
+ void EnsureCapacity(AllocatorPtr& allocator, int64_t required_tokens, int64_t hidden_size,
+ int64_t fc1_output_size, int64_t inter_size) {
+ if (required_tokens > current_capacity) {
+ // Use high watermark approach - allocate more than needed for future reuse
+ int64_t new_capacity = std::max(required_tokens * 2, current_capacity + 512);
+
+ A1_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_capacity * hidden_size));
+ batch_weights_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_capacity));
+ token_ids_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_capacity));
+ A1_t_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_capacity * hidden_size));
+ C2_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_capacity * hidden_size));
+
+ current_capacity = new_capacity;
+ }
+
+ // Ensure ProcessExpertBatch buffers have sufficient capacity
+ int64_t required_fc1_capacity = required_tokens * fc1_output_size;
+ int64_t required_activation_capacity = required_tokens * inter_size;
+
+ if (required_fc1_capacity > current_fc1_capacity) {
+ int64_t new_fc1_capacity = std::max(required_fc1_capacity * 2, current_fc1_capacity + (512 * fc1_output_size));
+ fc1_output_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_fc1_capacity));
+ current_fc1_capacity = new_fc1_capacity;
+ }
+
+ if (required_activation_capacity > current_activation_capacity) {
+ int64_t new_activation_capacity = std::max(required_activation_capacity * 2, current_activation_capacity + (512 * inter_size));
+ activation_output_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(new_activation_capacity));
+ current_activation_capacity = new_activation_capacity;
+ }
+ }
+ };
+
+ // Pre-allocate thread-local buffer pools
+ std::vector thread_buffers(num_expert_threads);
+ for (int i = 0; i < num_expert_threads; ++i) {
+ thread_buffers[i].EnsureCapacity(allocator, max_tokens_per_expert, hidden_size, fc1_output_size, inter_size);
+ }
+
+ const size_t output_buffer_size = static_cast(output->Shape().Size());
+ auto thread_local_outputs_ptr = IAllocator::MakeUniquePtr(allocator, static_cast(num_expert_threads) * output_buffer_size);
+ float* thread_local_outputs = thread_local_outputs_ptr.get();
+
+ // Initialize thread-local outputs with vectorized operation
+ std::fill_n(thread_local_outputs, static_cast(num_expert_threads) * output_buffer_size, 0.0f);
+
+ // Optimized expert processing with thread-local buffer reuse
+ concurrency::ThreadPool::TrySimpleParallelFor(tp, num_expert_threads, [&](std::ptrdiff_t thread_id_pd) {
+ int thread_id = static_cast(thread_id_pd);
+ auto work = concurrency::ThreadPool::PartitionWork(thread_id, num_expert_threads, static_cast(num_experts));
+
+ float* local_output = thread_local_outputs + static_cast(thread_id) * output_buffer_size;
+ ThreadLocalBuffers& buffers = thread_buffers[thread_id];
+
+ for (int64_t expert_idx = work.start; expert_idx < work.end; ++expert_idx) {
+ const auto& routes = expert_token_map[static_cast(expert_idx)];
+ if (routes.empty()) continue;
+
+ const int64_t num_expert_tokens = static_cast(routes.size());
+
+ // Ensure thread-local buffers have sufficient capacity
+ buffers.EnsureCapacity(allocator, num_expert_tokens, hidden_size, fc1_output_size, inter_size);
+
+ // Use pre-allocated buffers from thread-local pool
+ float* A1 = buffers.A1_buffer.get();
+ float* batch_weights = buffers.batch_weights_buffer.get();
+ int64_t* token_ids = buffers.token_ids_buffer.get();
+ T* A1_t = buffers.A1_t_buffer.get();
+ T* C2 = buffers.C2_buffer.get();
+ T* fc1_output = buffers.fc1_output_buffer.get();
+ T* activation_output = buffers.activation_output_buffer.get();
+
+ // Optimized data gathering with better memory access patterns
+ for (int64_t r = 0; r < num_expert_tokens; ++r) {
+ int64_t route_idx = routes[static_cast(r)];
+ int64_t token = route_idx / k_;
+
+ token_ids[r] = token;
+ batch_weights[r] = route_scale[route_idx];
+
+ // Use SIMD-friendly copy for better performance
+ const float* src = input_float + token * hidden_size;
+ float* dst = A1 + static_cast(r) * static_cast(hidden_size);
+ std::copy(src, src + hidden_size, dst);
+ }
+
+ const T* fc1_expert_weights = fc1_weights_data + expert_idx * fc1_output_size * hidden_size;
+ const T* fc1_expert_bias = fc1_bias_data ? fc1_bias_data + expert_idx * fc1_output_size : nullptr;
+ const T* fc2_expert_weights = fc2_weights_data + expert_idx * hidden_size * inter_size;
+ const T* fc2_expert_bias = fc2_bias_data ? fc2_bias_data + expert_idx * hidden_size : nullptr;
+
+ // Convert input to T only when needed for computation
+ for (size_t i = 0; i < static_cast(num_expert_tokens * hidden_size); ++i) {
+ A1_t[i] = static_cast(A1[i]);
+ }
+
+ ORT_IGNORE_RETURN_VALUE(ProcessExpertBatch(A1_t, token_ids, batch_weights,
+ num_expert_tokens, expert_idx,
+ fc1_expert_weights, fc1_expert_bias,
+ fc2_expert_weights, fc2_expert_bias,
+ C2, hidden_size, inter_size,
+ fc1_output, activation_output));
+
+ // Optimized output accumulation with vectorized operations
+ for (int64_t r = 0; r < num_expert_tokens; ++r) {
+ int64_t token = token_ids[r];
+ const T* expert_output_t = C2 + static_cast(r) * static_cast(hidden_size);
+ float w = batch_weights[r];
+ float* dest = local_output + static_cast(token) * static_cast(hidden_size);
+
+ // Use explicit loop for better vectorization opportunities
+ for (int64_t j = 0; j < hidden_size; ++j) {
+ dest[j] += w * static_cast(expert_output_t[j]);
+ }
+ }
+ }
+ });
+
+ auto accumulate = [&](float* buffer) {
+ std::fill_n(buffer, output_buffer_size, 0.0f);
+
+ for (size_t j = 0; j < output_buffer_size; ++j) {
+ double sum = 0.0;
+ double c = 0.0;
+
+ for (int i = 0; i < num_expert_threads; ++i) {
+ const size_t thread_offset = static_cast(i) * output_buffer_size;
+ double y = static_cast(thread_local_outputs[thread_offset + j]) - c;
+ double t = sum + y;
+ c = (t - sum) - y;
+ sum = t;
+ }
+ buffer[j] = static_cast(sum);
+ }
+ };
+
+ if constexpr (std::is_same_v) {
+ auto final_output_float_ptr = IAllocator::MakeUniquePtr(allocator, output_buffer_size);
+ float* final_output_float = final_output_float_ptr.get();
+ accumulate(final_output_float);
+
+ MlasConvertFloatToHalfBuffer(final_output_float,
+ reinterpret_cast(output->MutableData()),
+ static_cast(output_buffer_size));
+ } else {
+ auto final_output_float_ptr = IAllocator::MakeUniquePtr(allocator, output_buffer_size);
+ float* final_output_float = final_output_float_ptr.get();
+ accumulate(final_output_float);
+
+ float* out_ptr = reinterpret_cast(output->MutableData());
+ memcpy(out_ptr, final_output_float, output_buffer_size * sizeof(float));
+ }
+ return Status::OK();
+}
+template
+Status MoE::ProcessExpertBatch(const T* input_tokens,
+ const int64_t* token_expert_ids,
+ const float* token_weights,
+ int64_t batch_size,
+ int64_t expert_id,
+ const T* fc1_weights,
+ const T* fc1_bias,
+ const T* fc2_weights,
+ const T* fc2_bias,
+ T* output_buffer,
+ int64_t hidden_size,
+ int64_t inter_size,
+ T* fc1_output_buffer,
+ T* activation_output_buffer) const {
+ const bool is_swiglu = activation_type_ == ActivationType::SwiGLU;
+ const int64_t fc1_output_size = is_swiglu ? (inter_size * 2) : inter_size;
+
+ constexpr int64_t stack_threshold = 1024;
+ const bool use_stack = (batch_size * fc1_output_size) <= stack_threshold;
+
+ std::vector fc1_output_vec;
+ std::vector activation_output_vec;
+ T* fc1_output;
+ T* activation_output;
+
+ if (use_stack) {
+ fc1_output_vec.resize(static_cast(batch_size * fc1_output_size));
+ activation_output_vec.resize(static_cast(batch_size * inter_size));
+ fc1_output = fc1_output_vec.data();
+ activation_output = activation_output_vec.data();
+ } else {
+ fc1_output_vec.resize(static_cast(batch_size * fc1_output_size));
+ activation_output_vec.resize(static_cast(batch_size * inter_size));
+ fc1_output = fc1_output_vec.data();
+ activation_output = activation_output_vec.data();
+ }
+
+ ORT_RETURN_IF_ERROR(ComputeGEMM(input_tokens, fc1_weights, fc1_output,
+ batch_size, hidden_size, fc1_output_size, true));
+
+ if (fc1_bias) {
+ for (int64_t batch = 0; batch < batch_size; ++batch) {
+ T* batch_output = fc1_output + batch * fc1_output_size;
+ // Explicit loop for better vectorization
+ for (int64_t i = 0; i < fc1_output_size; ++i) {
+ batch_output[i] = static_cast(static_cast(batch_output[i]) +
+ static_cast(fc1_bias[i]));
+ }
+ }
+ }
+
+ if (is_swiglu) {
+ for (int64_t batch = 0; batch < batch_size; ++batch) {
+ ApplySwiGLUVectorized(fc1_output + batch * fc1_output_size,
+ activation_output + batch * inter_size,
+ inter_size);
+ }
+ } else {
+ ApplyActivationVectorized(fc1_output, batch_size * fc1_output_size);
+ std::copy(fc1_output, fc1_output + (batch_size * fc1_output_size), activation_output);
+ }
+
+ ORT_RETURN_IF_ERROR(ComputeGEMM(activation_output, fc2_weights, output_buffer,
+ batch_size, inter_size, hidden_size, true));
+
+ if (fc2_bias) {
+ for (int64_t batch = 0; batch < batch_size; ++batch) {
+ T* batch_output = output_buffer + batch * hidden_size;
+ for (int64_t i = 0; i < hidden_size; ++i) {
+ batch_output[i] = static_cast(static_cast(batch_output[i]) +
+ static_cast(fc2_bias[i]));
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
+template <>
+Status MoE::ComputeGEMM(const float* A, const float* B, float* C,
+ int64_t M, int64_t K, int64_t N, bool transpose_B) const {
+ MLAS_SGEMM_DATA_PARAMS params;
+ params.A = A;
+ params.lda = static_cast(K);
+ params.alpha = 1.0f;
+ params.beta = 0.0f;
+ params.C = C;
+ params.ldc = static_cast(N);
+ params.B = B;
+
+ if (transpose_B) {
+ params.ldb = static_cast(K);
+ MlasGemm(CblasNoTrans, CblasTrans, static_cast(M), static_cast(N), static_cast(K), params, nullptr);
+ } else {
+ params.ldb = static_cast(N);
+ MlasGemm(CblasNoTrans, CblasNoTrans, static_cast(M), static_cast(N), static_cast(K), params, nullptr);
+ }
+
+ return Status::OK();
+}
+
+template <>
+Status MoE::ComputeGEMM(const MLFloat16* A, const MLFloat16* B, MLFloat16* C,
+ int64_t M, int64_t K, int64_t N, bool transpose_B) const {
+ MLAS_HALF_GEMM_DATA_PARAMS params;
+ params.A = A;
+ params.lda = static_cast(K);
+ params.C = C;
+ params.ldc = static_cast(N);
+ params.AIsfp32 = false;
+ params.BIsfp32 = false;
+ params.B = B;
+
+ if (transpose_B) {
+ params.ldb = static_cast(K);
+ } else {
+ params.ldb = static_cast(N);
+ }
+
+ MlasHalfGemmBatch(static_cast(M), static_cast(N), static_cast(K), 1, ¶ms, nullptr);
+ return Status::OK();
+}
+
+template
+void MoE::ApplyActivationVectorized(T* data, int64_t size) const {
+ for (int64_t i = 0; i < size; ++i) {
+ float val = static_cast(data[i]);
+ data[i] = static_cast(ApplyActivation(val, activation_type_));
+ }
+}
+
+template
+void MoE::ApplySwiGLUVectorized(const T* input, T* output, int64_t size) const {
+ for (int64_t i = 0; i < size; ++i) {
+ float gate = static_cast(input[2 * i]);
+ float linear = static_cast(input[2 * i + 1]);
+
+ gate = std::min(gate, swiglu_limit_);
+ linear = std::clamp(linear, -swiglu_limit_, swiglu_limit_);
+
+ float sigmoid_arg = activation_alpha_ * gate;
+ float sigmoid_out;
+ if (sigmoid_arg > 0) {
+ float exp_neg = std::exp(-sigmoid_arg);
+ sigmoid_out = 1.0f / (1.0f + exp_neg);
+ } else {
+ float exp_pos = std::exp(sigmoid_arg);
+ sigmoid_out = exp_pos / (1.0f + exp_pos);
+ }
+
+ float swish_out = gate * sigmoid_out;
+ output[i] = static_cast(swish_out * (linear + activation_beta_));
+ }
+}
+
+template <>
+void MoE::ApplySwiGLUVectorized(const float* input, float* output, int64_t size) const {
+ ApplySwiGLUActivation(input, output, size, true,
+ activation_alpha_, activation_beta_, swiglu_limit_);
+}
+
+template class MoE;
+template class MoE;
+
+#define REGISTER_KERNEL_TYPED(type) \
+ ONNX_OPERATOR_TYPED_KERNEL_EX( \
+ MoE, kMSDomain, 1, type, kCpuExecutionProvider, \
+ (*KernelDefBuilder::Create()) \
+ .MayInplace(0, 0) \
+ .TypeConstraint("T", DataTypeImpl::GetTensorType()), \
+ MoE);
+
+REGISTER_KERNEL_TYPED(float)
+REGISTER_KERNEL_TYPED(MLFloat16)
+
+} // namespace contrib
+} // namespace onnxruntime
diff --git a/onnxruntime/contrib_ops/cpu/moe/moe_cpu.h b/onnxruntime/contrib_ops/cpu/moe/moe_cpu.h
new file mode 100644
index 0000000000000..60d8217015b5b
--- /dev/null
+++ b/onnxruntime/contrib_ops/cpu/moe/moe_cpu.h
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "core/common/common.h"
+#include "core/framework/op_kernel.h"
+#include "contrib_ops/cpu/moe/moe_base_cpu.h"
+
+namespace onnxruntime {
+namespace contrib {
+
+template
+class MoE final : public OpKernel, public MoEBaseCPU {
+ public:
+ explicit MoE(const OpKernelInfo& op_kernel_info);
+ Status Compute(OpKernelContext* context) const override;
+
+ private:
+ Status ComputeMoE(const OpKernelContext* context,
+ const Tensor* input,
+ const Tensor* router_probs,
+ const Tensor* fc1_experts_weights,
+ const Tensor* fc1_experts_bias,
+ const Tensor* fc2_experts_weights,
+ const Tensor* fc2_experts_bias,
+ Tensor* output) const;
+
+ Status ProcessExpertBatch(const T* input_tokens,
+ const int64_t* token_expert_ids,
+ const float* token_weights,
+ int64_t num_tokens,
+ int64_t expert_id,
+ const T* fc1_weights,
+ const T* fc1_bias,
+ const T* fc2_weights,
+ const T* fc2_bias,
+ T* output_buffer,
+ int64_t hidden_size,
+ int64_t inter_size,
+ T* fc1_output_buffer,
+ T* activation_output_buffer) const;
+
+ Status ComputeGEMM(const T* A, const T* B, T* C,
+ int64_t M, int64_t K, int64_t N,
+ bool transpose_B = false) const;
+
+ void ApplyActivationVectorized(T* data, int64_t size) const;
+ void ApplySwiGLUVectorized(const T* input, T* output, int64_t size) const;
+};
+
+} // namespace contrib
+} // namespace onnxruntime
diff --git a/onnxruntime/contrib_ops/cpu/moe/moe_helper.h b/onnxruntime/contrib_ops/cpu/moe/moe_helper.h
index e494719464d20..39249f842e632 100644
--- a/onnxruntime/contrib_ops/cpu/moe/moe_helper.h
+++ b/onnxruntime/contrib_ops/cpu/moe/moe_helper.h
@@ -49,7 +49,8 @@ Status CheckInputs(MoEParameters& parameters,
const Tensor* fc3_experts_bias, // optional
const Tensor* fc3_experts_scales, // required for qMoE; NULL for MOE
const int64_t pack_size, // number of weights packed together (like 2 for uint4 packed to uint8)
- const bool is_fused_swiglu) {
+ const bool is_fused_swiglu,
+ const int64_t block_size = 0) { // block size for block-wise quantization
// Check dimensions of input to avoid input_dims index out of range. CHECK_TENSOR_SHAPE will verify each tensor later.
ASSERT_TENSOR_2D_OR_3D(input);
ASSERT_TENSOR_3D(fc1_experts_weights);
@@ -90,9 +91,63 @@ Status CheckInputs(MoEParameters& parameters,
CHECK_TENSOR_SHAPE(fc2_experts_bias, num_experts, hidden_size);
CHECK_TENSOR_SHAPE(fc3_experts_bias, num_experts, inter_size);
- CHECK_TENSOR_SHAPE(fc1_experts_scales, num_experts, fc1_inter_size);
- CHECK_TENSOR_SHAPE(fc2_experts_scales, num_experts, hidden_size);
- CHECK_TENSOR_SHAPE(fc3_experts_scales, num_experts, inter_size);
+ // Validate scale tensors: Handle both row-wise and block-wise quantization flexibly
+ // First, detect the actual quantization method from the tensor shapes
+ bool is_row_wise_quantization = true;
+ if (fc1_experts_scales != nullptr) {
+ const auto& fc1_scales_dims = fc1_experts_scales->Shape().GetDims();
+ if (fc1_scales_dims.size() == 3 && fc1_scales_dims[2] > 1) {
+ is_row_wise_quantization = false;
+ }
+ }
+
+ if (block_size > 0 && !is_row_wise_quantization) {
+ // Block-wise quantization: 3D scale tensors
+ // For block-wise quantization, we calculate the number of blocks using ceiling division
+ // to handle cases where the dimension is not perfectly divisible by block_size
+ const int64_t fc1_blocks_per_row = (hidden_size + block_size - 1) / block_size;
+ const int64_t fc2_blocks_per_row = (inter_size + block_size - 1) / block_size;
+ const int64_t fc3_blocks_per_row = (hidden_size + block_size - 1) / block_size;
+
+ CHECK_TENSOR_SHAPE(fc1_experts_scales, num_experts, fc1_inter_size, fc1_blocks_per_row);
+ CHECK_TENSOR_SHAPE(fc2_experts_scales, num_experts, hidden_size, fc2_blocks_per_row);
+ CHECK_TENSOR_SHAPE(fc3_experts_scales, num_experts, inter_size, fc3_blocks_per_row);
+ } else {
+ // Row-wise quantization: 2D scale tensors or 3D with last dimension = 1
+ // Handle both {num_experts, features} and {num_experts, features, 1} shapes
+ if (fc1_experts_scales != nullptr) {
+ const auto& fc1_scales_dims = fc1_experts_scales->Shape().GetDims();
+ if (fc1_scales_dims.size() == 2) {
+ CHECK_TENSOR_SHAPE(fc1_experts_scales, num_experts, fc1_inter_size);
+ } else if (fc1_scales_dims.size() == 3) {
+ CHECK_TENSOR_SHAPE(fc1_experts_scales, num_experts, fc1_inter_size, 1);
+ } else {
+ ORT_THROW("fc1_experts_scales must be 2D or 3D tensor");
+ }
+ }
+
+ if (fc2_experts_scales != nullptr) {
+ const auto& fc2_scales_dims = fc2_experts_scales->Shape().GetDims();
+ if (fc2_scales_dims.size() == 2) {
+ CHECK_TENSOR_SHAPE(fc2_experts_scales, num_experts, hidden_size);
+ } else if (fc2_scales_dims.size() == 3) {
+ CHECK_TENSOR_SHAPE(fc2_experts_scales, num_experts, hidden_size, 1);
+ } else {
+ ORT_THROW("fc2_experts_scales must be 2D or 3D tensor");
+ }
+ }
+
+ if (fc3_experts_scales != nullptr) {
+ const auto& fc3_scales_dims = fc3_experts_scales->Shape().GetDims();
+ if (fc3_scales_dims.size() == 2) {
+ CHECK_TENSOR_SHAPE(fc3_experts_scales, num_experts, inter_size);
+ } else if (fc3_scales_dims.size() == 3) {
+ CHECK_TENSOR_SHAPE(fc3_experts_scales, num_experts, inter_size, 1);
+ } else {
+ ORT_THROW("fc3_experts_scales must be 2D or 3D tensor");
+ }
+ }
+ }
if (fc3_experts_weights == nullptr) {
ORT_ENFORCE(fc3_experts_bias == nullptr && fc3_experts_scales == nullptr);
diff --git a/onnxruntime/contrib_ops/cpu/moe/moe_quantization_cpu.cc b/onnxruntime/contrib_ops/cpu/moe/moe_quantization_cpu.cc
index 5c6c3b919b572..f3a3cd5e1d627 100644
--- a/onnxruntime/contrib_ops/cpu/moe/moe_quantization_cpu.cc
+++ b/onnxruntime/contrib_ops/cpu/moe/moe_quantization_cpu.cc
@@ -2,12 +2,16 @@
// Licensed under the MIT License.
#include "contrib_ops/cpu/moe/moe_quantization_cpu.h"
-
#include "core/framework/allocator.h"
#include "core/framework/float16.h"
#include "core/mlas/inc/mlas.h"
+#include "core/mlas/inc/mlas_q4.h"
#include "core/platform/threadpool.h"
#include "core/providers/cpu/math/gemm_helper.h"
+#include "core/providers/cpu/activation/activations.h"
+#include "core/common/safeint.h"
+#include "core/framework/tensor_type_and_shape.h"
+#include "core/util/math.h"
#include "contrib_ops/cpu/moe/moe_utils.h"
#include "contrib_ops/cpu/moe/moe_helper.h"
@@ -17,44 +21,326 @@
#include
#include
+namespace {
+inline int64_t GetOptimalBlockSize(int64_t total_elements, int num_threads) {
+ if (total_elements <= 0 || num_threads <= 0) return 64;
+ const int64_t l1_cache_elements = 8192; // ~32KB / 4 bytes per float
+ const int64_t divisor = std::max(1, num_threads > 1 ? 4 : 2);
+ const int64_t base_block_size = l1_cache_elements / divisor;
+ const int64_t max_block = std::max(int64_t{32}, total_elements / std::max(int64_t{1}, int64_t{4}));
+ return std::clamp(base_block_size, int64_t{32}, std::min(int64_t{512}, max_block));
+}
+
+inline int64_t GetUnrollFactor(int64_t vector_size) {
+ if (vector_size <= 0) return 2;
+ if (vector_size >= 512) return 16;
+ if (vector_size >= 128) return 8;
+ if (vector_size >= 32) return 4;
+ return 2;
+}
+
+inline bool ShouldUseMemcpy(int64_t size) {
+ return size >= 64;
+}
+
+inline int64_t GetDequantBlockSize(int64_t features, int64_t total_work) {
+ if (features <= 0 || total_work <= 0) return 16;
+ const int64_t target_block_size = std::max(int64_t{16}, features / std::max(int64_t{1}, int64_t{8}));
+ const int64_t work_based_size = std::max(int64_t{16}, total_work / std::max(int64_t{1}, int64_t{4}));
+ return std::min(target_block_size, work_based_size);
+}
+
+bool CanUseMlasQ4Dequant(int64_t num_bits, int64_t block_size) {
+ if (num_bits != 4) {
+ return false;
+ }
+
+ return true;
+}
+
+bool CanUseMlasQ4Gemm(int64_t expert_weight_bits, int64_t block_size,
+ int64_t rows, int64_t cols, MLAS_BLK_QUANT_TYPE& out_qtype) {
+ if (expert_weight_bits != 4) {
+ return false;
+ }
+
+ if (block_size == 64) {
+ out_qtype = BlkQ4Sym64;
+ } else if (block_size == 128) {
+ out_qtype = BlkQ4Sym128;
+ } else if (block_size == 0) {
+ out_qtype = BlkQ4Sym;
+ } else {
+ return false;
+ }
+
+ size_t expected_size = MlasQ4GemmPackBSize(out_qtype, static_cast(cols), static_cast(rows));
+ return expected_size > 0;
+}
+
+} // namespace
+
namespace onnxruntime {
namespace contrib {
-// Helper function to dequantize weights. Supports 4-bit and 8-bit symmetric quantization.
-// The source quantized weights are stored as a row-major representation of the transposed
-// logical weight matrix (W^T). This function dequantizes it into a float row-major W^T matrix.
template
-void DequantizeBlock(const uint8_t* quantized_data,
- const TScale* scales,
- int64_t /*block_size*/,
- int64_t num_bits,
- int64_t rows,
- int64_t cols,
- float* dequantized_data) {
+void DequantizeBlockWithMlas(const uint8_t* quantized_data,
+ const TScale* scales,
+ int64_t block_size,
+ int64_t num_bits,
+ int64_t rows,
+ int64_t cols,
+ float* dequantized_data,
+ MLAS_THREADPOOL* thread_pool);
+
+template
+Status ConvertToMlasQ4Format(const uint8_t* quantized_data,
+ const TScale* scales,
+ int64_t block_size,
+ int64_t num_bits,
+ int64_t rows,
+ int64_t cols,
+ MLAS_BLK_QUANT_TYPE qtype,
+ AllocatorPtr allocator,
+ IAllocatorUniquePtr& mlas_packed_buffer) {
+ if (num_bits != 4) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Only 4-bit quantization supported for MLAS Q4 format conversion");
+ }
+
+ auto temp_float_buffer = IAllocator::MakeUniquePtr(allocator, static_cast(rows * cols));
+ float* temp_float = temp_float_buffer.get();
+
+ DequantizeBlockWithMlas(quantized_data, scales, block_size, num_bits, rows, cols, temp_float, nullptr);
+
+ size_t packed_size = MlasQ4GemmPackBSize(qtype, static_cast(cols), static_cast(rows));
+ if (packed_size == 0) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "MLAS Q4 packing not supported for this configuration");
+ }
+
+ mlas_packed_buffer = IAllocator::MakeUniquePtr(allocator, packed_size);
+ MlasQ4GemmPackB(qtype, mlas_packed_buffer.get(), temp_float, static_cast(cols), static_cast(rows), static_cast(cols));
+
+ return Status::OK();
+}
+
+Status DirectQ4Gemm(const float* A,
+ const uint8_t* mlas_packed_B,
+ const float* bias,
+ float* C,
+ int64_t M,
+ int64_t N,
+ int64_t K,
+ MLAS_BLK_QUANT_TYPE qtype,
+ MLAS_THREADPOOL* thread_pool) {
+ MLAS_Q4_GEMM_DATA_PARAMS params;
+ params.A = A;
+ params.lda = static_cast(K);
+ params.B = mlas_packed_B;
+ params.Bias = bias;
+ params.C = C;
+ params.ldc = static_cast(N);
+ params.OutputProcessor = nullptr;
+
+ MlasQ4GemmBatch(qtype, static_cast(M), static_cast(N), static_cast(K), 1, ¶ms, thread_pool);
+ return Status::OK();
+}
+
+template
+void DequantizeBlockWithMlas(const uint8_t* quantized_data,
+ const TScale* scales,
+ int64_t block_size,
+ int64_t num_bits,
+ int64_t rows,
+ int64_t cols,
+ float* dequantized_data,
+ MLAS_THREADPOOL* thread_pool) {
+ ORT_UNUSED_PARAMETER(thread_pool);
const float zero_point = num_bits == 8 ? 128.0f : 8.0f;
- if (num_bits == 8) {
- for (int64_t r = 0; r < rows; ++r) {
- const float scale = static_cast(scales[r]);
- for (int64_t c = 0; c < cols; ++c) {
- // Symmetric quantization: dequantized_value = scale * (quantized_value - zero_point)
- dequantized_data[r * cols + c] = scale * (static_cast(quantized_data[r * cols + c]) - zero_point);
+ const int64_t blocks_per_row = (block_size > 0) ? ((cols + block_size - 1) / block_size) : 1;
+
+ if (CanUseMlasQ4Dequant(num_bits, block_size)) {
+ const int64_t packed_cols = (cols + 1) / 2;
+
+ if (block_size == 0) {
+ for (int64_t r = 0; r < rows; ++r) {
+ const uint8_t* row_data = quantized_data + r * packed_cols;
+ float* row_output = dequantized_data + r * cols;
+ const float scale = static_cast(scales[r]);
+
+ int64_t c = 0;
+ for (; c + 8 <= cols; c += 8) {
+ const uint8_t packed_val0 = row_data[(c + 0) / 2];
+ const uint8_t packed_val1 = row_data[(c + 2) / 2];
+ const uint8_t packed_val2 = row_data[(c + 4) / 2];
+ const uint8_t packed_val3 = row_data[(c + 6) / 2];
+
+ row_output[c + 0] = scale * (static_cast(packed_val0 & 0x0F) - zero_point);
+ row_output[c + 1] = scale * (static_cast(packed_val0 >> 4) - zero_point);
+ row_output[c + 2] = scale * (static_cast(packed_val1 & 0x0F) - zero_point);
+ row_output[c + 3] = scale * (static_cast(packed_val1 >> 4) - zero_point);
+ row_output[c + 4] = scale * (static_cast(packed_val2 & 0x0F) - zero_point);
+ row_output[c + 5] = scale * (static_cast(packed_val2 >> 4) - zero_point);
+ row_output[c + 6] = scale * (static_cast(packed_val3 & 0x0F) - zero_point);
+ row_output[c + 7] = scale * (static_cast(packed_val3 >> 4) - zero_point);
+ }
+
+ for (; c < cols; c += 2) {
+ const uint8_t packed_val = row_data[c / 2];
+ const uint8_t val0 = packed_val & 0x0F;
+ const uint8_t val1 = packed_val >> 4;
+
+ row_output[c] = scale * (static_cast(val0) - zero_point);
+ if (c + 1 < cols) {
+ row_output[c + 1] = scale * (static_cast(val1) - zero_point);
+ }
+ }
}
+ return;
+ } else {
+ for (int64_t r = 0; r < rows; ++r) {
+ const uint8_t* row_data = quantized_data + r * packed_cols;
+ float* row_output = dequantized_data + r * cols;
+
+ for (int64_t block_start = 0; block_start < cols; block_start += block_size) {
+ const int64_t block_end = std::min(block_start + block_size, cols);
+ const int64_t block_idx = std::min(block_start / block_size, blocks_per_row - 1);
+ const int64_t scale_idx = r * blocks_per_row + block_idx;
+ const float scale = static_cast(scales[scale_idx]);
+
+ int64_t c = block_start;
+ for (; c + 4 <= block_end; c += 4) {
+ const uint8_t packed_val0 = row_data[(c + 0) / 2];
+ const uint8_t packed_val1 = row_data[(c + 2) / 2];
+
+ row_output[c + 0] = scale * (static_cast(packed_val0 & 0x0F) - zero_point);
+ row_output[c + 1] = scale * (static_cast