Skip to content

Commit 8198949

Browse files
committed
new learn path:Unleashing SME2 Performance - Profile ONNX models with KleidiAI-Optimized ONNX Runtime
1 parent bc83181 commit 8198949

15 files changed

Lines changed: 376 additions & 0 deletions
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
title: Build ONNX Runtime with KleidiAI and SME2 for Android
3+
weight: 4
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Build ONNX Runtime and benchmark application with KleidiAI and SME2 support for Android
10+
11+
To run this on an Android device, you must cross-compile ORT using the Android NDK.
12+
Prerequisites
13+
- Android NDK: Version r26b or newer (r27+ recommended for latest SME2 toolchain support).
14+
- CMake & Ninja: Ensure these are in your system PATH.
15+
16+
### Build Command
17+
Run the following from the root of the ONNX Runtime repository:
18+
```bash
19+
./build.sh --android --android_sdk_path $ANDROID_NDK_HOME --android_ndk_path $ANDROID_NDK_HOME --android_abi arm64-v8a --android_api 27 --config RelWithDebInfo --build_shared_lib --cmake_extra_defines onnxruntime_USE_KLEIDIAI=ON --cmake_generator Ninja --parallel
20+
```
21+
22+
Note: The flag “onnxruntime_USE_KLEIDIAI=ON” triggers the inclusion of Arm KleidiAI kernels into the MLAS library.
23+
24+
## Profiling Performance with onnxruntime_perf_test
25+
Once the build is complete, you will find the libonnxruntime.so shared library and onnxruntime_perf_test binary in your build directory. The onnxruntime_perf_test is essential for measuring latency and identifying bottlenecks.
26+
### Step 1: Push files to Android Device
27+
```bash
28+
adb push <build_dir>/Android/RelWithDebInfo/onnxruntime_perf_test /data/local/tmp/
29+
adb push <build_dir>/Android/RelWithDebInfo/libonnxruntime.so /data/local/tmp/
30+
adb push your_model.onnx /data/local/tmp/
31+
```
32+
### Step 2: Run the Performance Test
33+
The perf_test tool allows you to simulate inference and gather statistics. For example,
34+
```bash
35+
# Execute on the device
36+
adb shell "/data/local/tmp/onnxruntime_perf_test -e cpu -m times -r 20 -s -Z -x 1 /data/local/tmp/your_model.onnx"
37+
```
38+
The command example set the arguments of the application as,
39+
- “-e cpu” specifies the provider as cpu provider
40+
- “-m times” specifies the test mode as “times”
41+
- “-r 20” specifies the repeated times as 20
42+
- “-Z” disallows thread from spinning during runs to reduce cpu usage
43+
- “-s” shows statistics result
44+
- “-x 1” sets the number of threads used to parallelize the execution within nodes as 1
45+
46+
You can try other arguments setting if you would like to.
47+
48+
### Step 3: Deep Dive into Operator Profiling
49+
To see exactly how many milliseconds are spent on each operator, use the profiling flag -p.
50+
```bash
51+
adb shell "/data/local/tmp/onnxruntime_perf_test -p profile.json -e cpu -m times -r 5 -s -Z -x 1 /data/local/tmp/your_model.onnx"
52+
adb pull /data/local/tmp/profile.json
53+
```
54+
The argument “-p” enables performance profiling during the benchmark run. When you provide this flag followed by a filename, ONNX Runtime will generate a JSON file containing a detailed trace of the model execution.
55+
You can view the results by opening [prefetto tool]( https://ui.perfetto.dev/), and loading the generated JSON file. This allows you to see a visual timeline of which operations took the most time.
56+
You also can convert the JSON file to a CSV sheet by creating a python script.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: Integration of KleidiAI to ORT MLAS
3+
weight: 3
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Integration of KleidiAI to ONNX runtime MLAS
10+
ONNX runtime is built with KleidiAI support:
11+
1. Detection: At runtime, MLAS checks the CPU capabilities for SME2 support.
12+
2. Dispatch: If SME2 is detected, MLAS overrides its default kernels. For example, a Gemm (General Matrix Multiplication) operation that would normally use standard vector instructions (such as NEON) is dispatched to a KleidiAI SME2 micro-kernel.
13+
14+
Currently, KleidiAI in MLAS provides ArmKleidiAI::MlasConv, ArmKleidiAI::MlasGemmBatch and ArmKleidiAI::MlasDynamicQGemmBatch kernels.
15+
16+
### The ArmKleidiAI::MlasConv kernel
17+
Usually, 2D fp32 convolution operators with batch_size=1 and multiple filters (filter kernel is equal or greater than (3,3)) are dispatched to the ArmKleidiAI::MlasConv kernel.
18+
19+
For example, the figure below shows a (7,7) Conv node.
20+
21+
![Diagram illustrating an example of 7x7 Conv alt-text#center](images/conv_nodes_7x7.jpg "An example of (7,7) Conv node")
22+
23+
ArmKleidiAI::MlasConv kernel makes use of KleidiAI’s indirect matrix multiplication (imatmul) micro kernel to accelerate the convolution.
24+
25+
The function calls are shown as below.
26+
```text
27+
onnxruntime::InferenceSession::Run
28+
|--onnxruntime::utils::ExecuteGraph
29+
| |--onnxruntime::utils::ExecuteGraphImp
30+
| | |--onnxruntime::ExecuteThePlan
31+
| | | |--onnxruntime::concurrency::ThreadPool::Schedule
32+
| | | | |--onnxruntime::RunSince
33+
| | | | | |--onnxruntime::LaunchKernelStep::Execute
34+
| | | | | | |--onnxruntime::ExecuteKernel
35+
| | | | | | | |--onnxruntime::Conv<float>::Compute
36+
| | | | | | | | |--MlasConv
37+
| | | | | | | | | |--ArmKleidiAI::MlasConv
38+
| | | | | | | | | | |--ConvolveSme
39+
| | | | | | | | | | | |--MlasTrySimpleParallel
40+
| | | | | | | | | | | | |--kai_run_lhs_imatmul_pack_x32p2vlx1_x32p_sme
41+
| | | | | | | | | | | | | |--kai_kernel_lhs_imatmul_pack_x32p2vlx1_x32p_sme
42+
| | | | | | | | | | | | |--kai_run_rhs_imatmul_pack_kxn_x32p2vlx1b_x32_x32_sme
43+
| | | | | | | | | | | | | |--kai_kernel_rhs_imatmul_pack_kxn_x32p2vlx1b_x32_x32_sme
44+
| | | | | | | | | | | | |--kai_run_imatmul_clamp_f32_f32p2vlx1_f32p2vlx1b_2vlx2vl_sme2_mopa
45+
| | | | | | | | | | | | | |--kai_kernel_imatmul_clamp_f32_f32p2vlx1_f32p2vlx1b_2vlx2vl_sme2_mopa
46+
```
47+
48+
### The ArmKleidiAI::MlasGemmBatch kernel
49+
It performs a batched fp32 matrix multiplication (GEMM or GemV) operation using KleidiAI matmul micro kernels. fp32 Conv operators with (1,1) filter kernels also use this kernel.
50+
51+
For example, the figure below shows a (1,1) Conv node.
52+
53+
![Diagram illustrating an example of 1x1 Conv alt-text#center](images/conv_nodes_1x1.jpg "An example of (1,1) FusedConv node")
54+
55+
The function calls of fp32 Conv operators with (1,1) filter kernels are shown below.
56+
57+
```text
58+
onnxruntime::InferenceSession::Run
59+
|--onnxruntime::utils::ExecuteGraph
60+
| |--onnxruntime::utils::ExecuteGraphImp
61+
| | |--onnxruntime::ExecuteThePlan
62+
| | | |--onnxruntime::concurrency::ThreadPool::Schedule
63+
| | | | |--onnxruntime::RunSince
64+
| | | | | |--onnxruntime::LaunchKernelStep::Execute
65+
| | | | | | |--onnxruntime::ExecuteKernel
66+
| | | | | | | |--onnxruntime::Conv<float>::Compute
67+
| | | | | | | | |--MlasConv
68+
| | | | | | | | | |--MlasGemmBatch
69+
| | | | | | | | | | |--ArmKleidiAI::MlasGemmBatch
70+
| | | | | | | | | | | |--MlasTrySimpleParallel
71+
| | | | | | | | | | | | |--kai_run_lhs_pack_f32p2vlx1_f32_sme
72+
| | | | | | | | | | | | | |--kai_kernel_lhs_pack_f32p2vlx1_f32_sme
73+
| | | | | | | | | | | | |--ArmKleidiAI::MlasGemmPackB
74+
| | | | | | | | | | | | | |--kai_run_rhs_pack_kxn_f32p2vlx1biasf32_f32_f32_sme
75+
| | | | | | | | | | | | | | |--kai_kernel_rhs_pack_kxn_f32p2vlx1biasf32_f32_f32_sme
76+
| | | | | | | | | | | | |--kai_run_matmul_clamp_f32_f32p2vlx1_f32p2vlx1biasf32_sme2_mopa
77+
| | | | | | | | | | | | | |--kai_kernel_matmul_clamp_f32_f32p2vlx1_f32p2vlx1biasf32_sme2_mopa
78+
```
79+
80+
For example, the figure below shows a Gemm node.
81+
82+
![Diagram illustrating an example of Gemm node alt-text#center](images/Gemm_node.jpg "An example of Gemm node")
83+
84+
The function calls of fp32 Gemm operators are shown below.
85+
```text
86+
onnxruntime::InferenceSession::Run
87+
|--onnxruntime::utils::ExecuteGraph
88+
| |--onnxruntime::utils::ExecuteGraphImp
89+
| | |--onnxruntime::ExecuteThePlan
90+
| | | |--onnxruntime::concurrency::ThreadPool::Schedule
91+
| | | | |--onnxruntime::RunSince
92+
| | | | | |--onnxruntime::LaunchKernelStep::Execute
93+
| | | | | | |--onnxruntime::ExecuteKernel
94+
| | | | | | | |--onnxruntime::Gemm<float>::Compute
95+
| | | | | | | | |--MlasGemm
96+
| | | | | | | | | |--MlasGemmBatch
97+
| | | | | | | | | | |--ArmKleidiAI::MlasGemmBatch
98+
| | | | | | | | | | | |--MlasTrySimpleParallel
99+
| | | | | | | | | | | | |--kai_run_matmul_clamp_f32_f32p2vlx1_f32p2vlx1biasf32_sme2_mopa
100+
| | | | | | | | | | | | | |--kai_kernel_matmul_clamp_f32_f32p2vlx1_f32p2vlx1biasf32_sme2_mopa
101+
```
102+
103+
### The ArmKleidiAI::MlasDynamicQGemmBatch kernel
104+
This kernel is for Matmul with float output of dynamic quantized A and symmetric quantized B.
105+
It uses KleidiAI *kai_kernel_matmul_clamp_f32_qai8dxp1vlx4_qsi8cxp4vlx4_1vlx4vl_sme2_mopa* micro kernel.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: ONNX runtime overview
3+
weight: 2
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## ONNX runtime overview
10+
With the rise of on-device AI, squeezing performance from CPUs has become critical. Arm’s Scalable Matrix Extension 2 (SME2) represents a leap forward, offering significant speedups for matrix-heavy workloads like Transformers and CNNs.
11+
This learning path will walk you through the technical steps to integrate KleidiAI—Arm's specialized micro-kernel library with SME2 support—into ONNX Runtime (ORT) and profile its performance using onnxruntime_perf_test on Android devices.
12+
13+
### Understanding the ONNX Runtime Software Stack
14+
Firstly, let us look at the internal architecture of the ONNX Runtime.
15+
![Diagram illustrating ONNX runtime components alt-text#center](images/ort_overview.jpg "The ONNX runtime overview")
16+
17+
#### 1. In-Memory Graph
18+
When loading an ONNX model, ORT parses the protobuf file and creates an In-Memory Graph. This is a live representation of the model’s structure, consisting of:
19+
- Nodes: Representing operations (e.g., MatMul, Conv, Add).
20+
- Edges: Representing the flow of data (tensors) between those operations.
21+
22+
During this stage, ORT performs Graph Optimizations like constant folding and node fusion.
23+
#### 2. Graph Partitioner
24+
The Graph Partitioner decides which part of the model runs on which hardware. It analyzes the computational graph and matches nodes to the registered Execution Providers.
25+
It clusters adjacent nodes assigned to the same EP into "Subgraphs".
26+
#### 3. Graph Runner
27+
Once the graph is partitioned, the Graph Runner is responsible for the actual execution of the operators in the correct order. It manages the flow of data (Tensors) between nodes.
28+
In ORT, parallelism is split into two distinct levels to maximize hardware utilization: Intra-op (inside an operator/node, splitting a single heavy operation/node into smaller chunks) and Inter-op (between different operators, running multiple independent operators at the same time).
29+
30+
#### 4. Execution Provider (EP)
31+
An Execution Provider is the abstraction layer that interfaces with specific hardware or libraries.
32+
Each EP provides a set of "Kernels" (optimized math functions) for specific operators.
33+
Examples:
34+
- CPU: Default CPU, Intel DNNL, XNNPACK etc.
35+
- GPU: NVIDIA CUDA/TensorRT, AMD MIGraphX, DirectML etc.
36+
- Others: NPU, Qualcomm QNN etc.
37+
38+
If a specialized EP doesn't support a specific operator, ORT automatically falls back to the CPU provider.
39+
40+
Default CPU provider uses Microsoft Linear Algebra Subprogram (MLAS). MLAS is a minimal version of BLAS library which implements an optimized version of linear algebra operations such as general matrix multiply (GEMM) in low-level languages with various processor support. For aarch64, MLAS already utilizes dotprod, i8mm, fp16, bf16 vector instructions for acceleration.
41+
42+
The KleidiAI-optimized MLAS can delegate high-performance matrix operations to KleidiAI micro kernels. KleidiAI provides micro-kernels specifically tuned for SME2, allowing ORT to instantly leverage the latest hardware features.
43+
44+
This learning path focuses on Arm CPU Execution Provider.
45+
46+
47+
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: Profiling – Use Resnet50v2 fp32 model as an example
3+
weight: 5
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Profile an ONNX model – Use Resnet50v2 as an example
10+
The Resnet50v2 fp32 ONNX model can be downloaded from Hugging Face or Modescope.
11+
12+
The Android device that we used is a VIVO X300 phone with MTK D9500 processor, which has Arm C1-Ultra, C1-Premium and C1-Pro CPU cores with SME2 support on it. We chose a C1-Pro CPU core running at 2.0GHz to run the onnxruntime_perf_test benchmark application. You can use any other Android device with SME2 support.
13+
14+
To compare the performance of running Resnet50v2 on ORT with SME2 and without SME2 support, we built two versions of ORT, one with SME2 support (set *onnxruntime_USE_KLEIDIAI=ON* when building ORT), the other without SME2 support(*onnxruntime_USE_KLEIDIAI=OFF* when building ORT).
15+
16+
Run following command on the device,
17+
```bash
18+
taskset 1 ./onnxruntime_perf_test -e cpu -r 5 -m times -s -Z -x 1 ./resnet50v2.onnx -p "resnet50v2.onnx_1xC1-Pro_profile
19+
```
20+
21+
The *taskset 1* in the command sets the CPU affinity of *onnxruntime_perf_test* benchmark to CPU core 0, which is a C1-Pro CPU core.
22+
*-x 1* in the command sets the number of threads used to parallelize the execution within nodes as 1 (single thread).
23+
24+
Here is output from running onnxruntime_perf_test with ORT with SME2 support as below.
25+
```text
26+
Setting intra_op_num_threads to 1
27+
Disabling intra-op thread spinning between runs
28+
Session creation time cost: 0.217932 s
29+
First inference time cost: 196 ms
30+
Total inference time cost: 0.49481 s
31+
Total inference requests: 5
32+
Average inference time cost total: 98.961997 ms
33+
Total inference run time: 0.494854 s
34+
Number of inferences per second: 10.104
35+
Avg CPU usage: 11 %
36+
Peak working set size: 271122432 bytes
37+
Avg CPU usage:11
38+
Peak working set size:271122432
39+
Runs:5
40+
Min Latency: 0.0958204 s
41+
Max Latency: 0.101519 s
42+
P50 Latency: 0.0995086 s
43+
P90 Latency: 0.101519 s
44+
P95 Latency: 0.101519 s
45+
P99 Latency: 0.101519 s
46+
P999 Latency: 0.101519 s
47+
```
48+
49+
Here is output from running onnxruntime_perf_test with ORT without SME2 support as below.
50+
```text
51+
Setting intra_op_num_threads to 1
52+
Disabling intra-op thread spinning between runs
53+
Session creation time cost: 0.227282 s
54+
First inference time cost: 343 ms
55+
Total inference time cost: 1.69691 s
56+
Total inference requests: 5
57+
Average inference time cost total: 339.381120 ms
58+
Total inference run time: 1.69697 s
59+
Number of inferences per second: 2.94642
60+
Avg CPU usage: 11 %
61+
Peak working set size: 241426432 bytes
62+
Avg CPU usage:11
63+
Peak working set size:241426432
64+
Runs:5
65+
Min Latency: 0.333323 s
66+
Max Latency: 0.34682 s
67+
P50 Latency: 0.336476 s
68+
P90 Latency: 0.34682 s
69+
P95 Latency: 0.34682 s
70+
P99 Latency: 0.34682 s
71+
P999 Latency: 0.34682 s
72+
```
73+
### Performance Indicators
74+
| Metric | Non-KleidiAI | KleidiAI (with SME2) | Speed Up |
75+
|---------|----------------|----------------|-------------------------------------------------|
76+
| Latency per inference (ms) | 339 | 99 | >3.4 |
77+
78+
We can use [prefetto tool](https://ui.perfetto.dev/), to view the two JSON profile files.
79+
80+
The figure below is a screenshot of the view of the Non-KleidiAI version of JSON profile file.
81+
The selected part(one model_run/SequentialExecutor) in the figure includes information of one inference execution.
82+
83+
![Figure showing profile file of Non-KleidiAI version alt-text#center](images/resnet50v2_no_sme_prefetto.png "prefetto view of Non-KleidiAI version of ORT")
84+
85+
The figure below is a screenshot of the view of the KleidiAI(with SME2) version of JSON profile file.
86+
The selected part (one model_run/SequentialExecutor) in the figure includes information of one inference execution.
87+
![Figure showing profile file of KleidiAI with SME2 version alt-text#center](images/resnet50v2_sme_prefetto.png "prefetto view of KleidiAI with SME2 version of ORT")
88+
89+
We also convert the two JSON profile files to CSV sheets, then we combine the individual operator execution time of the Non-KleidiAI and KleidiAI(with SME2) version to a single chart.
90+
![Figure showing operator time of both versions of ORT alt-text#center](images/resnet50v2_with_sme_without_sme_2.png "Operator execution time comparison")
91+
92+
It shows that ORT with KleidiAI (with SME2) kernels uplifts the performance significantly, especially for convolution operators.
93+
94+
If we use Arm Streamline tools and PMU counters for further investigation, in the timeline view of Streamline, we can see SME2 floating point Outer Product and Accumulate (MOPA) instruction is used intensively during the inference.
95+
96+
![Figure showing SME2 instructions and cycles alt-text#center](images/resnet50v2_sme_onnx_streamline_1xgelas_annotation.png "SME2 instructions and cycles shown in Streamline")
97+
98+
Then we combine the function call view of ORT without KleidiAI and with KleidiAI(with SME2) from Streamline to a single figure,
99+
100+
![Figure showing function call percentage of both versions of ORT alt-text#center](images/function_call_compare.png "Function call percentage of both versions of ORT in Streamline ")
101+
102+
It shows that KleidiAI kernels provide a significant performance uplift for convolution operators compared to the default MLSA kernels (*MlasSgemmKernelAdd* and *MlasSgemmKernelZero*).
103+
104+
## Summary
105+
By integrating KleidiAI (SME2) into ONNX Runtime, you unlock the massive parallel processing power of Arm SME2. This turns the Arm CPU from a "fallback" into a high-performance AI engine capable of running LLMs and complex vision models locally on devices.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: Unleashing SME2 Performance - Profile ONNX models with KleidiAI-Optimized ONNX Runtime
3+
4+
minutes_to_complete: 40
5+
6+
who_is_this_for: This is an advanced topic for software developers, performance engineers, and AI practitioners
7+
8+
learning_objectives:
9+
- Build ONNX runtime library with KleidiAI and SME2 support
10+
- Profile performance of ONNX models
11+
- Learn how KleidiAI and SME2 accelerates ONNX operators
12+
13+
prerequisites:
14+
- Knowledge of KleidiAI and SME2
15+
- An Android device with Arm SME2 support
16+
17+
author: Zenon Zhilong Xiu
18+
19+
### Tags
20+
skilllevels: Advanced
21+
subjects: ML
22+
armips:
23+
- Arm C1 CPU
24+
- Arm SME2 unit
25+
tools_software_languages:
26+
- C++
27+
- ONNX runtime
28+
operatingsystems:
29+
- Android
30+
- Linux
31+
32+
33+
34+
further_reading:
35+
- resource:
36+
title: part 1 Arm Scalable Matrix Extension Introduction
37+
link: https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/arm-scalable-matrix-extension-introduction
38+
type: blog
39+
- resource:
40+
title: part 2 Arm Scalable Matrix Extension Instructions
41+
link: https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/arm-scalable-matrix-extension-introduction-p2
42+
type: blog
43+
- resource:
44+
title: part4 Arm SME2 Introduction
45+
link: https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/part4-arm-sme2-introduction
46+
type: blog
47+
48+
49+
50+
### FIXED, DO NOT MODIFY
51+
# ================================================================================
52+
weight: 1 # _index.md always has weight of 1 to order correctly
53+
layout: "learningpathall" # All files under learning paths have this same wrapper
54+
learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content.
55+
---
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# ================================================================================
3+
# FIXED, DO NOT MODIFY THIS FILE
4+
# ================================================================================
5+
weight: 21 # The weight controls the order of the pages. _index.md always has weight 1.
6+
title: "Next Steps" # Always the same, html page title.
7+
layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing.
8+
---
18 KB
Loading
21.4 KB
Loading
20.7 KB
Loading
831 KB
Loading

0 commit comments

Comments
 (0)