You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/learning-paths/servers-and-cloud-computing/tune-pytorch-cpu-perf-with-threads/Background.md
+32-11Lines changed: 32 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,18 +8,31 @@ layout: learningpathall
8
8
9
9
## Background Information
10
10
11
-
A well-known tradeoff in parallel programming is choosing the right granularity of work. When multiple threads are created to perform a task, the amount of actual computation must justify the cost of coordinating those threads. For example, if a computation is split across many threads, the overhead of creating those threads and synchronizing their results through shared memory can easily outweigh any performance gains if each thread does too little work. The same principle applies to generative AI systems.
11
+
A well-known challenge in parallel programming is choosing the right number of threads for a given amount of work. When multiple threads are created to perform a task, the actual computation must be large enough to justify the overhead of coordinating those threads.
12
12
13
-
When distribution work across multiple threads, the communication and synchronisation overhead results in more overall work required. This introduces a tradeoff between latency, time to execute a single request, and throughput, number of requests processed in a given time interval. PyTorch looks to automatically find the correct number of threads, however as we will demonstrate, in some cases you may wish to manually fine tune.
13
+
For example, if a computation is split across many threads, the costs of:
14
+
- creating the threads, and
15
+
- synchronizing their results through shared memory
16
+
17
+
can easily outweigh any performance gains from parallel execution. The same principle applies to generative AI workloads running on CPU.
18
+
19
+
When work is distributed across multiple threads, communication and synchronization overhead increases the total amount of work the system must perform. This creates a trade-off between:
20
+
21
+
-**Latency** – the time to process a single request, and
22
+
-**Throughput** – the number of requests processed per unit time.
23
+
24
+
PyTorch attempts to automatically choose an appropriate number of threads. However, as we will show, in some cases you may want to manually fine-tune this configuration to improve performance.
14
25
15
26
## Multi-threading with PyTorch on CPU
16
27
17
28
18
-
The diagram below is taken from the [PyTorch documentation](https://docs.pytorch.org/docs/2.8/notes/cpu_threading_torchscript_inference.html). When running inference, PyTorch uses a thread pool. In PyTorch, we have inter-op parallelism, which is spawning threads to run separate operations in a graph in parallel (e.g., 1 thread for a matmul and another thread for a softmax). Additionally there's intra-op parallelism can be used to spawn multiple threads to work on the same operation.
29
+
The diagram below is taken from the [PyTorch documentation](https://docs.pytorch.org/docs/2.8/notes/cpu_threading_torchscript_inference.html). When running inference, PyTorch uses an **Application Thread Pool**. In PyTorch, there are inter-op parallelism, which is spawning threads to run separate operations in a graph in parallel (e.g., 1 thread for a matmul and another thread for a softmax). Additionally there's intra-op parallelism can be used to spawn multiple threads to work on the same operation.
19
30
20
31

21
32
22
-
In PyTorch, the `torch.set_num_threads()`[API](https://docs.pytorch.org/docs/stable/generated/torch.set_num_threads.html) is used to set the maximum number of threads to spawn in the Application Thread Pool. As of PyTorch 2.8.0, the default number of threads is equal to the number of CPU cores (see [PyTorch CPU Threading Documentation](https://docs.pytorch.org/docs/2.8/notes/cpu_threading_torchscript_inference.html)). PyTorch looks to find the ideal number of threads as described with the following code snippet taken from the PyTorch source code, [ParallemOpenMP.h](https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/ParallelOpenMP.h).
33
+
In PyTorch, the `torch.set_num_threads()`[API](https://docs.pytorch.org/docs/stable/generated/torch.set_num_threads.html) is used to set the maximum number of threads to spawn in the Application Thread Pool.
34
+
35
+
As of PyTorch 2.8.0, the default number of threads is equal to the number of CPU cores (see [PyTorch CPU Threading Documentation](https://docs.pytorch.org/docs/2.8/notes/cpu_threading_torchscript_inference.html) for more detail). PyTorch looks to find the ideal number of threads as described with the following code snippet taken from the PyTorch source code, [ParallemOpenMP.h](https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/ParallelOpenMP.h).
In PyTorch builds that use OpenMP, the maximum size of the application’s thread pool can be configured once at runtime using the `OMP_NUM_THREADS` environment variable. The actual number of threads used will scale up to this limit depending on the workload and the `grain_size`.
35
50
36
-
In PyTorch build with OpenMP, we can set the application thread pool once at runtime using the `OMP_NUM_THREADS` environment variable. The basic demonstration below will show that the default argument on manycore systems may not be ideal for your workload. In the use case of running a language model with a low parameter count on a many-core CPU system, this default configuration may not be the most performant configuration.
51
+
The short example below illustrates that the default settings on many-core systems may not provide optimal performance for all workloads.
37
52
38
-
## Basic Example
53
+
## Basic PyTorch Example
39
54
40
-
Create a new file called `pytorch_omp_example.py` and paste in the Python script below. We are performing a matrix multiplication in eager mode on two 256-by-256 random matrices. This is clearly a very small operation but we will observe the default behaviour and print the parallel configuration using the `torch.__config__.parallel_info()` function.
55
+
Create a new file named `pytorch_omp_example.py` and paste in the Python script below. The script performs a matrix multiplication in eager mode on two 256×256 random matrices.
56
+
For this relatively small computation, we will:
57
+
58
+
- Observe the default performance of PyTorch’s parallelism
59
+
- Print the parallel configuration using `torch.__config__.parallel_info()`.
41
60
42
61
```python
43
62
import os
@@ -76,13 +95,15 @@ if __name__ == "__main__":
76
95
main()
77
96
```
78
97
79
-
80
-
Running the python script above you will observe the following output. As you can see the number of threads is set to core count of 96 and the time to execute is 2.24 ms.
98
+
Run the python script
81
99
82
100
```bash
83
101
python pytorch_omp_example.py
84
102
```
85
103
104
+
You will observe the an output similar to the follow. As you can see the number of threads is set to core count of 96 and the time to execute is 2.24 ms.
105
+
106
+
86
107
```output
87
108
PyTorch version: 2.10.0.dev20251124
88
109
OMP_NUM_THREADS in environment: None
@@ -103,7 +124,7 @@ Environment variables:
103
124
ATen parallel backend: OpenMP
104
125
```
105
126
106
-
Now when reduce the number of OpenMP threads using the `OMP_NUM_THREADS` value and observe a reduction is matrix multiply time to 0.64 ms.
127
+
Now reduce the number of OpenMP threads using the `OMP_NUM_THREADS` value and observe a reduction is matrix multiply time to 0.64 ms.
107
128
108
129
```bash
109
130
OMP_NUM_THREADS=16 python pytorch_omp_example.py
@@ -129,4 +150,4 @@ Environment variables:
129
150
ATen parallel backend: OpenMP
130
151
```
131
152
132
-
We will now move on from this trivial example to a LLM.
153
+
We will now move on from this trivial example to a much larger workload of a large language model (LLM).
Copy file name to clipboardExpand all lines: content/learning-paths/servers-and-cloud-computing/tune-pytorch-cpu-perf-with-threads/tune.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ layout: learningpathall
9
9
10
10
## Tuning thread size
11
11
12
-
We will run inference on Google's [Gemma-3-1B-it](https://huggingface.co/google/gemma-3-1b-it) and measure how inference performance changes as we vary the thread count for both the 270-million-parameter and 1-billion-parameter models. We will be running the `transformers_llm_text_gen.py` script which by default applies groupwise, layout-aware INT4 quantization of our model.
12
+
We will run inference on Google's [Gemma-3-1B-it](https://huggingface.co/google/gemma-3-1b-it) and measure how inference performance vaires with thread count for both the 270-million-parameter and 1-billion-parameter models. We will be running the `transformers_llm_text_gen.py` script which by default applies groupwise, layout-aware INT4 quantization of our model.
13
13
14
14
Create a file names `comparison-1b.sh` and paste in the following script.
15
15
@@ -34,7 +34,7 @@ for t in 2 4 8 16 32 64 96; do
34
34
done
35
35
```
36
36
37
-
Likewise a separate script for comparing the 270m model
37
+
Likewise create a separate script, `comparison-1b.sh` for comparing the 270m model
38
38
39
39
```bash
40
40
#!/usr/bin/env bash
@@ -57,7 +57,7 @@ for t in 2 4 8 16 32 64 96; do
57
57
done
58
58
```
59
59
60
-
Run both scripts from the directory containing the `transformers_llm_text_gen.py`script with the following commands. For clarity we are only printing the final statistics.
60
+
Run both scripts from the directory containing the `transformers_llm_text_gen.py`file using the commands below. For clarity, we print only the final statistics.
61
61
62
62
```bash
63
63
./comparison-1b.sh
@@ -70,7 +70,7 @@ Run both scripts from the directory containing the `transformers_llm_text_gen.py
You should see the following output, which shows the CPU utilization of each thread and illustrates how new, inter-op, and intra-op threads are spawned over time.
73
+
You should see output similar to the example below, showing the CPU utilization of each thread. This illustrates how new threads, both inter-op and intra-op, are created and used over time.
74
74
75
75
```output
76
76
PID TID PSR %CPU STAT COMMAND
@@ -100,7 +100,7 @@ Decode Tokens per second: 45.23
100
100
101
101
```
102
102
103
-
The graph above shows how prefill tokens per second change with the number of OpenMP threads for the 270M and 1B variants of Gemma-3. As expected, the smaller 270M model generally runs faster. Both models reach their optimal token generation rate at around 16–32 threads, though the 270M model exhibits a sharper performance drop-off beyond this range compared with the 1B variant.
103
+
The graph above shows how prefill tokens per second change with the number of OpenMP threads for the 270M and 1B variants of Gemma-3. As expected, the smaller 270M model runs faster. Both models reach their optimal token generation rate at around 16–32 threads, though the 270M model exhibits a sharper performance drop-off beyond this range compared with the 1B variant.
@@ -146,5 +146,5 @@ Decode Tokens per second: 107.37
146
146
147
147
### Summary
148
148
149
-
In this learning path, we explored how the number of OpenMP threads is a tunable parameter that can significantly impact the performance of a large language model. This is especially important when running such models on Arm systems with high core counts. You should also take the model’s parameter size into account. In practice, using a heuristic or trial-and-error approach is often the fastest way to determine the optimal thread count for a given system.
149
+
In this learning path, we explored how the number of OpenMP threads is a tunable parameter that can significantly impact the performance of a large language model. This is especially important when running such models on Arm systems with high core counts. You should also take the model’s parameter size into account. In practice, using a heuristic or trial-and-error approach is often the fastest way to determine the optimal thread count for a given model and system.
0 commit comments