Profile GPT-2 inference with the Arm Performix Instruction Mix recipe
Introduction
Understand profiling with Arm Performix Instruction Mix
Set up and run a GPT-2 baseline
Find GPT-2 hotspots and profile with the Arm Performix Instruction Mix recipe
(Optional) Optimize matmul with vector intrinsics
Compare Neon and SVE with the Arm Performix Instruction Mix recipe
Accelerate execution with KleidiAI
Next Steps
Profile GPT-2 inference with the Arm Performix Instruction Mix recipe
Introduction
Understand profiling with Arm Performix Instruction Mix
Set up and run a GPT-2 baseline
Find GPT-2 hotspots and profile with the Arm Performix Instruction Mix recipe
(Optional) Optimize matmul with vector intrinsics
Compare Neon and SVE with the Arm Performix Instruction Mix recipe
Accelerate execution with KleidiAI
Next Steps
Find the code hotspot
Before you optimize, identify where the application spends most of its time. Use the Code Hotspots recipe to periodically sample the running application and build a profile of the functions that execute most often.
Open Arm Performix and select the Code Hotspots recipe. If this is your first run on the target, complete tool deployment as prompted.
Set the launch command to your baseline binary with the number of tokens (-n) set to 150. This value keeps startup overhead small compared to inference time, so the profile minimizes the time taken to load the model weights.
Copy and paste the following command as the Performix Workload. If your path is different, adjust it to point to gpt2:
/home/ubuntu/GPT-2-Example/build/gpt2 --model gpt2-medium "Once upon a time" -n 150
Code Hotspots recipe configuration for GPT-2 baseline
The results show that kernels::matmul_ref() is the hottest function. Double-click on the function to see which lines of source code are mostly attributed to the accumulate step of kernels::matmul_ref().
Hotspot results highlighting `matmul_ref`
This confirms that matrix multiplication is the highest-impact optimization target.
Assess compiler output
You can use online tools such as
Compiler Explorer
to conveniently see how this function is being compiled with the -O2 -g flags. The following example uses GCC 12.1.0. You can check your installed compiler version with the g++ --version command and select the corresponding version from the Compiler Explorer drop-down menu. The generated assembly might differ slightly across compiler versions.
With this view, you can spot missed vectorization opportunities. In an optimized build, you’d expect the accumulation step to use SIMD instructions, for example fmla v0.4s, v3.4s, v2.4s with use of the vector register (v0->v3). However, assembly inspection has limitations.
First, you need familiarity with SIMD mnemonics to recognize vectorized code. Second, this narrow snippet doesn’t show whether changing compiler flags introduces regressions in other parts of the codebase. Third, and most importantly, this static view doesn’t show which instructions in this function run most often on the CPU.
The Instruction Mix recipe helps fill this gap.
Configure the Instruction Mix recipe
Open Arm Performix and select the Instruction Mix recipe.
Set the launch command to your baseline binary with the same runtime arguments used for baseline testing:
/home/ubuntu/GPT-2-Example/build/gpt2 --model gpt2-medium "Once upon a time" -n 150
Note the number of counters.
Use the same model and prompt arguments as your baseline terminal run so the measurements are comparable.
Configure Arm Performix Instruction Mix recipe
Analyze static disassembly
After the run completes, review static disassembly first. This view is ordered by percentage contribution and provides a high-level profile of the application’s generated instruction stream. By reviewing static disassembly, you can identify broad characteristics, such as whether the code is branch-heavy, dominated by memory operations, or making effective use of SIMD instructions.
Use this static view to understand overall code generation patterns rather than to attribute performance to specific functions or source lines. Dynamic analysis is typically more relevant for optimization because it reflects the instructions that are actually executed at runtime.
Static disassembly instruction classification
Dynamic analysis
Then, inspect dynamic analysis bar chart to see where sampled runtime work is concentrated. Dynamic data is typically more useful for optimization because it reflects actual execution behavior for your input, runtime settings, and call frequencies.
Dynamic function sample distribution
Finally, in dynamic functions, you can break down operation types to individual functions. This is particularly useful when no single function dominates the profile, allowing you to inspect dynamic instruction patterns for specific functions.
What you’ve accomplished and what’s next
You’ve now used Instruction Mix to confirm that baseline runtime is dominated by scalar-heavy matmul execution.
Next, you can optionally learn to optimize matmul with vector intrinsics and use the Arm MCP Server with Performix. You can also skip to
Compare Neon and SVE with the Arm Performix Instruction Mix recipe
to compare updated Instruction Mix and throughput across scalar, Neon, SVE, and KleidiAI variants.