Refactored Kleidiai executor and Support for INT4 groupwise asym Quantization - #37086
Refactored Kleidiai executor and Support for INT4 groupwise asym Quantization#37086hrushitfujitsu wants to merge 3 commits into
Conversation
|
@alvoron , could you please review? |
There was a problem hiding this comment.
Pull request overview
This PR extends the Intel CPU ARM KleidiAI fully-connected/matmul path by refactoring kernel dispatch into a lookup-key + switch and adding INT4 group-wise asymmetric (u4 weights + u4 zero-points) support, along with new functional tests.
Changes:
- Added u4 compressed-weight support and validation for asymmetric group-wise quantization (zero-points + scale layout checks).
- Refactored KleidiAI executor kernel selection into a lookup-key driven
switchand introduced asymmetric group-wise kernel tags. - Added new ARM functional test instantiation for KleidiAI asymmetric group-wise decompression.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/plugins/intel_cpu/tests/functional/custom/subgraph_tests/src/arm/matmul_weights_decompression.cpp |
Adds a new test instantiation covering u4 asymmetric group-wise decompression. |
src/plugins/intel_cpu/src/nodes/fullyconnected.cpp |
Expands supported compressed weight types to include u4; adds asymmetric zero-point validation; refactors attribute initialization. |
src/plugins/intel_cpu/src/nodes/executors/kleidiai/kleidiai_mm.hpp |
Introduces a kernel lookup-key enum and stores the lookup key in the executor. |
src/plugins/intel_cpu/src/nodes/executors/kleidiai/kleidiai_mm.cpp |
Refactors KleidiAI kernel dispatch into a switch on a computed key; adds asymmetric quantization detection. |
src/plugins/intel_cpu/src/nodes/executors/kleidiai/kleidiai_common.hpp |
Adds asymmetric group-wise INT4 kernel interfaces and packing/execution specializations. |
src/plugins/intel_cpu/src/nodes/executors/fullyconnected_implementations.cpp |
Adds a KleidiAI FC type mapping and expands KleidiAI supported weight precisions to include u4. |
| bool MatMulKleidiAIExecutor::isGroupQuantizationEnabled(const MemoryArgs& memory) { | ||
| auto scales = memory.at(ARG_WEI | ARG_ATTR_SCALES)->getDesc().getShape().getStaticDims(); | ||
| if (scales.size() == 1) { | ||
| return false; | ||
| } | ||
| OPENVINO_ASSERT(scales.size() > 1, | ||
| "Scales tensor to have at least 2 dimensions. Got ", | ||
| scales.size(), | ||
| " dimension(s)."); | ||
| // std::cout << "Scales[0,1,2] value: " << scales[0] << ", " << scales[1] << ", " << scales[2] << std::endl; | ||
| return (scales[1] > 1); | ||
| } |
| const bool hasWeightZeroPoints = | ||
| op->get_input_size() > WEIGHT_ZERO_POINTS && | ||
| op->input(WEIGHT_ZERO_POINTS).get_element_type() != ov::element::dynamic; | ||
|
|
||
| if (hasWeightZeroPoints) { | ||
| const auto weightsType = op->input(WEIGHTS).get_element_type(); | ||
| const auto zeroPointsType = op->input(WEIGHT_ZERO_POINTS).get_element_type(); | ||
| // unsigned INT4 weights with unsigned INT4 per-group zero-points. | ||
| if (weightsType != ov::element::u4 || zeroPointsType != ov::element::u4 || isNotGroupWise) { |
| void FullyConnected::initSupportedPrimitiveDescriptors() { | ||
| attrs.sparseWeights = useSparseWeightsDecompression(getParentEdgeAt(WEIGHTS)->getParent(), | ||
| getOriginalInputPrecisionAtPort(DATA), | ||
| context->getConfig().fcSparseWeiDecompressionRate); | ||
| attrs.dynamicQuantizationGroupSize = context->getConfig().fcDynamicQuantizationGroupSize; | ||
| attrs.modelType = context->getConfig().modelType; | ||
|
|
||
| attrs.dqScales = getDQScales(); | ||
|
|
||
| attrs.postOps = getPostOps(fusedWith); | ||
|
|
| #define FLOAT_MAX std::numeric_limits<float>::max() | ||
| #define FLOAT_MIN (-std::numeric_limits<float>::max()) | ||
| // Headers for INT4 group asymmetric KAI kernels | ||
| #include "kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qai4c32p/kai_matmul_clamp_f32_qsi8d32p4x4_qai4c32p4x4_8x4_neon_dotprod.h" | ||
| #include "kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qai4c32p/kai_matmul_clamp_f32_qsi8d32p4x8_qai4c32p4x8_8x4_neon_i8mm.h" | ||
| #include "kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qai4c32p/kai_matmul_clamp_f32_qsi8d32p_qai4c32p_interface.h" | ||
| #include "kai/ukernels/matmul/pack/kai_lhs_quant_pack_qsi8d32pscalef32_f32_neon.h" | ||
| #include "kai/ukernels/matmul/pack/kai_rhs_pack_nxk_qai4c32p_qau4c32s0s1_f32_f32_f32_neon.h" | ||
|
|
||
| namespace ov::intel_cpu::kai_common { | ||
| enum class KAIKernelTag : std::uint8_t { |
| static const TypeMapping aclLowpFCTypeMapping { | ||
| // {src, wei, bia, dst} pt<src, wei, bias, dst> | ||
| {{_u8, _i8, _i32 | _dynamic, _u8}, {bypass(), bypass(), bypass(), bypass()}}, | ||
| {{_i8, _i8, _i32 | _dynamic, _i8}, {bypass(), bypass(), bypass(), bypass()}}, | ||
| {{_u8 | _i8, _i8, _any, _f32}, {bypass(), bypass(), use<3>(), bypass()}} | ||
| }; |
|
|
||
| static const TypeMapping aclLowpFCTypeMapping { | ||
| // {src, wei, bia, dst} pt<src, wei, bias, dst> | ||
| {{_u8, _i8, _i32 | _dynamic, _u8}, {bypass(), bypass(), bypass(), bypass()}}, |
There was a problem hiding this comment.
Is it required to stop processing these cases to ComputeLibrary executor?
There was a problem hiding this comment.
Restored the original cases
| attrs.dynamicQuantizationGroupSize = context->getConfig().fcDynamicQuantizationGroupSize; | ||
| attrs.modelType = context->getConfig().modelType; | ||
|
|
||
| attrs.dqScales = getDQScales(); |
There was a problem hiding this comment.
This attribute is used by ACL executor. Is it required to stop initializing it?
alvoron
left a comment
There was a problem hiding this comment.
Please fix clang-format issues.
Copyright check failure could be ignored.
I also triggered Jenkins CI.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/plugins/intel_cpu/tests/functional/custom/subgraph_tests/src/arm/matmul_weights_decompression.cpp:99
- The second test case uses
32lufor the group size but the comment saysgroup_size = 16, which makes the test intent unclear and can mislead future debugging.
{{{}, {{1, 7, 256}}}, {256, 128}, 32lu}, // group_size = 32
{ {{-1, -1, -1}, {{10, 40, 64}, {11, 40, 64}}}, // data_shape
{64, 128}, // weights_shape
32lu}, // group_size = 16
};
| const bool hasWeightZeroPoints = | ||
| op->get_input_size() > WEIGHT_ZERO_POINTS && | ||
| op->input(WEIGHT_ZERO_POINTS).get_element_type() != ov::element::dynamic; | ||
|
|
||
| if (hasWeightZeroPoints) { | ||
| const auto weightsType = op->input(WEIGHTS).get_element_type(); | ||
| const auto zeroPointsType = op->input(WEIGHT_ZERO_POINTS).get_element_type(); | ||
| // KleidiAI supports asymmetric INT4 only for group-wise u4. | ||
| if (weightsType != ov::element::u4 || | ||
| zeroPointsType != ov::element::u4 || | ||
| !isGroupWise) { | ||
| return false; | ||
| } | ||
|
|
||
| const auto zeroPointsShape = | ||
| op->input(WEIGHT_ZERO_POINTS).get_shape(); | ||
|
|
||
| if (zeroPointsShape != scalesShape) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
src/plugins/intel_cpu/src/nodes/fullyconnected.cpp:213
- [BLOCKER] Adding
u4to the compressed-weight types also admitsu4graphs with no zero-point. This path does not reject them, so the executor classifies them as symmetric INT4 and routes them through the signed-i4 packers, which reinterpret values 8–15 as negative and silently changes inference results. Rejectu4unless a valid asymmetric zero-point is present, or add a separate unsigned symmetric kernel.
if (hasWeightZeroPoints) {
src/plugins/intel_cpu/tests/functional/custom/subgraph_tests/src/arm/matmul_weights_decompression.cpp:107
- [HIGH] These tests do not exercise either new asymmetric KleidiAI kernel.
ConvertMatMulToFCinserts a constant weights Transpose (convert_matmul_to_fc.cpp:240-242), thenFuseFCAndTransposeOnWeightselides it and setsweightsNonTransposed=true(graph_optimizer.cpp:915-932); the newsupports()check rejects every u4 configuration in that state.check_results()only checks compressed weight precision, so a fallback executor can make this suite pass. Add a test that builds the supported NxK FullyConnected layout without the elided Transpose and assert thatfullyconnected_kleidiaiwas selected.
::testing::Values(true),
src/plugins/intel_cpu/tests/functional/custom/subgraph_tests/src/arm/matmul_weights_decompression.cpp:101
- [HIGH] This suite name is not matched by the existing no-DOTPROD skip regex (
skip_tests_config.cpp:689-691only matchessmoke_MatMulCompressedWeights_Kleidiai). On an AArch64 CPU without DOTPROD, compressed conversion is rejected while this test still expects u4 weights to remain fused, so it fails instead of being skipped. Rename the prefix to fall under the established skip pattern (or update that pattern).
INSTANTIATE_TEST_SUITE_P(smoke_MatMulCompressedWeightsGrp_Kleidiai_asym,
| const auto zpPrec = rhsZeroPointsMem->getDescPtr()->getPrecision(); | ||
| OPENVINO_ASSERT(zpPrec == ov::element::u4 || zpPrec == ov::element::f32, | ||
| "INT4 asymmetric group KleidiAI kernel expects u4 or f32 " | ||
| "zero-points. Got ", | ||
| zpPrec, |
|
@hrushitfujitsu |
|
@hrushitfujitsu fyi: the fix has been merged into master |
12e4c35 to
8b2759f
Compare
|
Hi @alvoron, rebasing done |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
src/plugins/intel_cpu/src/nodes/fullyconnected.cpp:216
- [BLOCKER]
u4is accepted even when the zero-point input is empty. That configuration is classified as symmetric inkleidiai_mm.cppand dispatched through the signed-I4 packers, which reinterpret values 8–15 as negative and can silently produce incorrect inference results. Require a non-empty zero point whenever the weights areu4(or add a distinct unsigned-symmetric kernel) before accepting the compressed operation.
if (hasWeightZeroPoints) {
const auto weightsType = op->input(WEIGHTS).get_element_type();
const auto zeroPointsType = op->input(WEIGHT_ZERO_POINTS).get_element_type();
// KleidiAI supports asymmetric INT4 only for group-wise u4.
if (weightsType != ov::element::u4 || zeroPointsType != ov::element::u4 || !isGroupWise) {
src/plugins/intel_cpu/src/nodes/executors/kleidiai/kleidiai_common.hpp:1235
- [HIGH] This constructor advertises
f32zero-points as supported, butpackData()always obtains the buffer asuint8_t*and decodes packed nibbles. Passing the allowedf32form therefore reads float object bytes as U4 values and produces incorrect results. Either restrict this assertion tou4or add a precision-aware float path; the same issue must be corrected in the I8MM specialization at line 1443.
const auto zpPrec = rhsZeroPointsMem->getDescPtr()->getPrecision();
OPENVINO_ASSERT(zpPrec == ov::element::u4 || zpPrec == ov::element::f32,
"INT4 asymmetric group KleidiAI kernel expects u4 or f32 "
"zero-points. Got ",
zpPrec,
src/plugins/intel_cpu/tests/functional/custom/subgraph_tests/src/arm/matmul_weights_decompression.cpp:107
- [HIGH] This test sets
transpose_weights=true, which makesFuseFCAndTransposeOnWeightssetweightsNonTransposed=true(graph_optimizer.cpp:930). The updatedsupports()explicitly rejects everyu4configuration with that flag (kleidiai_mm.cpp:67), so these cases fall back and never exercise the new asymmetric KleidiAI kernels. Use the supported non-transposed test layout here; test the intentional fallback separately if needed.
::testing::Values(true),
src/plugins/intel_cpu/src/nodes/executors/kleidiai/kleidiai_common.hpp:1404
- [MEDIUM] The I8MM specialization duplicates nearly the entire DOTPROD asymmetric implementation, including validation, weight/zero-point conversion, packing, allocation sizing, and execution loops. This already makes defects such as the unsupported
f32decoding easy to fix in only one path. Extract the shared asymmetric group implementation and parameterize only the kernel interface and tag.
template <>
class uKernel<KAIKernelTag::I4_NEON_IMM_GROUP_ASYM> : public uKernelBase {
Details:
AI Assistance:
This work was contributed by @hrushitfujitsu and @abhijain1204fujitsu