qnnx-backend 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qnnx_backend-0.1.0/LICENSE +21 -0
- qnnx_backend-0.1.0/PKG-INFO +309 -0
- qnnx_backend-0.1.0/README.md +288 -0
- qnnx_backend-0.1.0/pyproject.toml +75 -0
- qnnx_backend-0.1.0/setup.cfg +4 -0
- qnnx_backend-0.1.0/src/qnnx_backend/__init__.py +11 -0
- qnnx_backend-0.1.0/src/qnnx_backend/backend.py +13 -0
- qnnx_backend-0.1.0/src/qnnx_backend/compatibility.py +173 -0
- qnnx_backend-0.1.0/src/qnnx_backend/compiler.py +251 -0
- qnnx_backend-0.1.0/src/qnnx_backend/error.py +36 -0
- qnnx_backend-0.1.0/src/qnnx_backend/options.py +7 -0
- qnnx_backend-0.1.0/src/qnnx_backend/runtime/__init__.py +0 -0
- qnnx_backend-0.1.0/src/qnnx_backend/runtime/qnn.py +128 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/PKG-INFO +309 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/SOURCES.txt +30 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/dependency_links.txt +1 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/entry_points.txt +2 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/requires.txt +9 -0
- qnnx_backend-0.1.0/src/qnnx_backend.egg-info/top_level.txt +1 -0
- qnnx_backend-0.1.0/tests/test_auto_fallback_to_cpu.py +42 -0
- qnnx_backend-0.1.0/tests/test_auto_fallback_to_gpu.py +49 -0
- qnnx_backend-0.1.0/tests/test_backend.py +22 -0
- qnnx_backend-0.1.0/tests/test_compability.py +549 -0
- qnnx_backend-0.1.0/tests/test_compile_mlp_on_gpu.py +32 -0
- qnnx_backend-0.1.0/tests/test_compile_mlp_on_npu.py +32 -0
- qnnx_backend-0.1.0/tests/test_compile_relu_on_npu.py +32 -0
- qnnx_backend-0.1.0/tests/test_freeze_static_inputs.py +223 -0
- qnnx_backend-0.1.0/tests/test_is_available.py +38 -0
- qnnx_backend-0.1.0/tests/test_models.py +97 -0
- qnnx_backend-0.1.0/tests/test_operator_cases.py +1089 -0
- qnnx_backend-0.1.0/tests/test_qnn_runtime.py +21 -0
- qnnx_backend-0.1.0/tests/test_relu_on_npu.py +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Le Gall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qnnx-backend
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A PyTorch torch.compile backend for Qualcomm QNN.
|
|
5
|
+
Author: Thomas Le Gall
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/legall.thomas.ll/qnnx-backend
|
|
8
|
+
Project-URL: ORCID, https://orcid.org/0009-0007-4387-0435
|
|
9
|
+
Requires-Python: <3.14,>=3.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: torch<2.14,>=2.13
|
|
13
|
+
Requires-Dist: onnxruntime-qnn>=2.4
|
|
14
|
+
Requires-Dist: onnx
|
|
15
|
+
Requires-Dist: onnxscript
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: numpy; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# qnnx-backend
|
|
23
|
+
|
|
24
|
+
`qnnx-backend` is an experimental PyTorch `torch.compile` backend for Qualcomm QNN.
|
|
25
|
+
|
|
26
|
+
It enables PyTorch models to run through ONNX Runtime's QNN Execution Provider on supported Qualcomm NPU and GPU devices.
|
|
27
|
+
|
|
28
|
+
> [!WARNING]
|
|
29
|
+
> `qnnx-backend` is currently experimental and intended for inference only.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- PyTorch integration through `torch.compile(..., backend="qnn")`
|
|
34
|
+
- Qualcomm NPU/HTP acceleration
|
|
35
|
+
- Qualcomm GPU acceleration
|
|
36
|
+
- Automatic NPU → GPU → CPU fallback
|
|
37
|
+
- Explicit device selection
|
|
38
|
+
- Operator compatibility validation
|
|
39
|
+
- Static model parameter and buffer freezing
|
|
40
|
+
- QNN availability detection
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Windows ARM64
|
|
45
|
+
- Qualcomm Snapdragon platform with QNN support
|
|
46
|
+
- Python 3.13
|
|
47
|
+
|
|
48
|
+
### Tested configuration
|
|
49
|
+
|
|
50
|
+
- PyTorch 2.13
|
|
51
|
+
- ONNX Runtime QNN 2.4
|
|
52
|
+
- Snapdragon X Elite
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
`qnnx-backend` currently requires **Python 3.13 ARM64** on Windows ARM64.
|
|
57
|
+
|
|
58
|
+
> **Important:** make sure you are using a native ARM64 Python installation.
|
|
59
|
+
> A Python x64 installation will not use the required Windows ARM64 wheels.
|
|
60
|
+
|
|
61
|
+
Python.org provides a dedicated **Windows ARM64 installer** for Python 3.13.
|
|
62
|
+
|
|
63
|
+
You can verify your Python architecture with:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -c "import platform; print(platform.machine())"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The output should be:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
ARM64
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then install PyTorch:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m pip install torch==2.13.0+cpu --index-url https://download.pytorch.org/whl/cpu
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Finally, install `qnnx_backend`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m pip install qnnx-backend
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
### Basic usage
|
|
90
|
+
|
|
91
|
+
`qnnx_backend` integrates with PyTorch through `torch.compile`.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import torch
|
|
95
|
+
import qnnx_backend
|
|
96
|
+
|
|
97
|
+
model = MyModel().eval()
|
|
98
|
+
x = torch.randn(1, 32)
|
|
99
|
+
|
|
100
|
+
compiled_model = torch.compile(
|
|
101
|
+
model,
|
|
102
|
+
backend="qnn",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
with torch.inference_mode():
|
|
106
|
+
output = compiled_model(x)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
By default, `qnnx_backend` automatically selects an available QNN accelerator.
|
|
110
|
+
|
|
111
|
+
### Checking QNN availability
|
|
112
|
+
|
|
113
|
+
You can check whether at least one QNN accelerator is available:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
qnnx_backend.is_available()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
You can also check a specific device:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
qnnx_backend.is_available("npu")
|
|
123
|
+
qnnx_backend.is_available("gpu")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Device selection
|
|
127
|
+
|
|
128
|
+
The QNN backend supports the following device options:
|
|
129
|
+
|
|
130
|
+
- `"auto"` — automatically selects an available accelerator
|
|
131
|
+
- `"npu"` — uses the Qualcomm NPU/HTP backend
|
|
132
|
+
- `"gpu"` — uses the Qualcomm GPU backend
|
|
133
|
+
- `"cpu"` — uses the original PyTorch graph without QNN acceleration
|
|
134
|
+
|
|
135
|
+
The default is `"auto"`.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
compiled_model = torch.compile(
|
|
139
|
+
model,
|
|
140
|
+
backend="qnn",
|
|
141
|
+
options={
|
|
142
|
+
"device": "npu",
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Backend options
|
|
148
|
+
|
|
149
|
+
| Option | Values | Default | Description |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| `device` | `auto`, `npu`, `gpu`, `cpu` | `auto` | Selects the execution backend. |
|
|
152
|
+
| `strict` | `True`, `False` | `False` | Controls whether compilation failures can fall back. |
|
|
153
|
+
| `fp16` | `True`, `False` | `False` | Enables HTP FP16 precision for NPU execution. |
|
|
154
|
+
|
|
155
|
+
### Automatic fallback
|
|
156
|
+
|
|
157
|
+
With the default configuration, `qnnx_backend` falls back when QNN compilation is not possible.
|
|
158
|
+
|
|
159
|
+
In `"auto"` mode, devices are attempted in the following order:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
NPU -> GPU -> CPU
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For example:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
compiled_model = torch.compile(
|
|
169
|
+
model,
|
|
170
|
+
backend="qnn",
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
If the graph cannot be compiled for the NPU, the GPU is attempted. If neither QNN accelerator can execute the graph, PyTorch CPU execution is used.
|
|
175
|
+
|
|
176
|
+
### Strict mode
|
|
177
|
+
|
|
178
|
+
Use `strict=True` when execution on the selected QNN device is required:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
compiled_model = torch.compile(
|
|
182
|
+
model,
|
|
183
|
+
backend="qnn",
|
|
184
|
+
options={
|
|
185
|
+
"device": "npu",
|
|
186
|
+
"strict": True,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
In strict mode, a QNN compilation or session creation failure is propagated instead of falling back to CPU.
|
|
192
|
+
|
|
193
|
+
This is useful when testing whether a graph is actually supported by a specific QNN backend.
|
|
194
|
+
|
|
195
|
+
### NPU FP16 execution
|
|
196
|
+
|
|
197
|
+
For NPU execution, FP16 precision can be enabled with:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
compiled_model = torch.compile(
|
|
201
|
+
model,
|
|
202
|
+
backend="qnn",
|
|
203
|
+
options={
|
|
204
|
+
"device": "npu",
|
|
205
|
+
"fp16": True,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This option controls the QNN HTP FP16 precision setting.
|
|
211
|
+
|
|
212
|
+
## Inference
|
|
213
|
+
|
|
214
|
+
`qnnx_backend` currently targets inference workloads.
|
|
215
|
+
|
|
216
|
+
Models should be switched to evaluation mode before compilation:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
model.eval()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Inference should normally be performed using:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
with torch.inference_mode():
|
|
226
|
+
output = compiled_model(x)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Training and backward execution are not currently supported.
|
|
230
|
+
|
|
231
|
+
## Model parameters and buffers
|
|
232
|
+
|
|
233
|
+
Model parameters and buffers are captured when the QNN graph is compiled.
|
|
234
|
+
|
|
235
|
+
For example:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
compiled_model = torch.compile(
|
|
239
|
+
model.eval(),
|
|
240
|
+
backend="qnn",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
compiled_model(x)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
After compilation, modifying parameters or buffers of the original model does not update the existing QNN session.
|
|
247
|
+
|
|
248
|
+
Recompile the model after changing its parameters:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
compiled_model = torch.compile(
|
|
252
|
+
model.eval(),
|
|
253
|
+
backend="qnn",
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Static shapes
|
|
258
|
+
|
|
259
|
+
Version 0.1.0 currently targets static input shapes.
|
|
260
|
+
|
|
261
|
+
Compile the model using the input shapes that will be used during inference.
|
|
262
|
+
|
|
263
|
+
Dynamic-shape support is not currently provided.
|
|
264
|
+
|
|
265
|
+
## Operator support
|
|
266
|
+
|
|
267
|
+
`qnnx_backend` validates exported ONNX operators against the currently supported QNN backends.
|
|
268
|
+
|
|
269
|
+
Operator support may differ between NPU/HTP and GPU.
|
|
270
|
+
|
|
271
|
+
See the [operator compatibility list](docs/operators.md) for the current support matrix.
|
|
272
|
+
|
|
273
|
+
## Benchmarks
|
|
274
|
+
|
|
275
|
+
Benchmarks were performed on Windows ARM64 with a Snapdragon X Elite.
|
|
276
|
+
|
|
277
|
+
The results below show steady-state inference latency after warm-up.
|
|
278
|
+
|
|
279
|
+
### Small models
|
|
280
|
+
|
|
281
|
+
Steady-state inference latency after warm-up. Values are median latency in milliseconds (lower is better).
|
|
282
|
+
|
|
283
|
+
| Model | Batch | PyTorch CPU | QNN GPU | QNN NPU |
|
|
284
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
285
|
+
| Small MLP | 16 | 0.042 ms | 0.201 ms | 0.203 ms |
|
|
286
|
+
| Small CNN | 16 | 0.299 ms | 0.953 ms | **0.212 ms** |
|
|
287
|
+
| Small Transformer | 16 | 0.750 ms | 1.459 ms | **0.306 ms** |
|
|
288
|
+
|
|
289
|
+
### Medium models
|
|
290
|
+
|
|
291
|
+
Steady-state inference latency after warm-up. Values are median latency in milliseconds (lower is better).
|
|
292
|
+
|
|
293
|
+
| Model | Batch | PyTorch CPU | QNN GPU | QNN NPU |
|
|
294
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
295
|
+
| Medium MLP | 16 | 2.523 ms | 0.505 ms | **0.333 ms** |
|
|
296
|
+
| Medium CNN | 16 | 134.117 ms | 11.292 ms | **2.143 ms** |
|
|
297
|
+
| Medium Transformer | 16 | 47.343 ms | 9.176 ms | **2.036 ms** |
|
|
298
|
+
|
|
299
|
+
See [detailed benchmarks](docs/benchmarks.md) for batch-size scaling, first-call latency, median, p95, standard deviation, and benchmark methodology.
|
|
300
|
+
|
|
301
|
+
## Author
|
|
302
|
+
|
|
303
|
+
Developed by **Thomas Le Gall** — [ORCID](https://orcid.org/0009-0007-4387-0435) · [GitLab](https://gitlab.com/legall.thomas.ll).
|
|
304
|
+
|
|
305
|
+
## License
|
|
306
|
+
|
|
307
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
308
|
+
|
|
309
|
+
Copyright © 2026 Thomas Le Gall.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# qnnx-backend
|
|
2
|
+
|
|
3
|
+
`qnnx-backend` is an experimental PyTorch `torch.compile` backend for Qualcomm QNN.
|
|
4
|
+
|
|
5
|
+
It enables PyTorch models to run through ONNX Runtime's QNN Execution Provider on supported Qualcomm NPU and GPU devices.
|
|
6
|
+
|
|
7
|
+
> [!WARNING]
|
|
8
|
+
> `qnnx-backend` is currently experimental and intended for inference only.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- PyTorch integration through `torch.compile(..., backend="qnn")`
|
|
13
|
+
- Qualcomm NPU/HTP acceleration
|
|
14
|
+
- Qualcomm GPU acceleration
|
|
15
|
+
- Automatic NPU → GPU → CPU fallback
|
|
16
|
+
- Explicit device selection
|
|
17
|
+
- Operator compatibility validation
|
|
18
|
+
- Static model parameter and buffer freezing
|
|
19
|
+
- QNN availability detection
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Windows ARM64
|
|
24
|
+
- Qualcomm Snapdragon platform with QNN support
|
|
25
|
+
- Python 3.13
|
|
26
|
+
|
|
27
|
+
### Tested configuration
|
|
28
|
+
|
|
29
|
+
- PyTorch 2.13
|
|
30
|
+
- ONNX Runtime QNN 2.4
|
|
31
|
+
- Snapdragon X Elite
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
`qnnx-backend` currently requires **Python 3.13 ARM64** on Windows ARM64.
|
|
36
|
+
|
|
37
|
+
> **Important:** make sure you are using a native ARM64 Python installation.
|
|
38
|
+
> A Python x64 installation will not use the required Windows ARM64 wheels.
|
|
39
|
+
|
|
40
|
+
Python.org provides a dedicated **Windows ARM64 installer** for Python 3.13.
|
|
41
|
+
|
|
42
|
+
You can verify your Python architecture with:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -c "import platform; print(platform.machine())"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The output should be:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
ARM64
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then install PyTorch:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python -m pip install torch==2.13.0+cpu --index-url https://download.pytorch.org/whl/cpu
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Finally, install `qnnx_backend`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python -m pip install qnnx-backend
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic usage
|
|
69
|
+
|
|
70
|
+
`qnnx_backend` integrates with PyTorch through `torch.compile`.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import torch
|
|
74
|
+
import qnnx_backend
|
|
75
|
+
|
|
76
|
+
model = MyModel().eval()
|
|
77
|
+
x = torch.randn(1, 32)
|
|
78
|
+
|
|
79
|
+
compiled_model = torch.compile(
|
|
80
|
+
model,
|
|
81
|
+
backend="qnn",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
with torch.inference_mode():
|
|
85
|
+
output = compiled_model(x)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
By default, `qnnx_backend` automatically selects an available QNN accelerator.
|
|
89
|
+
|
|
90
|
+
### Checking QNN availability
|
|
91
|
+
|
|
92
|
+
You can check whether at least one QNN accelerator is available:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
qnnx_backend.is_available()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You can also check a specific device:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
qnnx_backend.is_available("npu")
|
|
102
|
+
qnnx_backend.is_available("gpu")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Device selection
|
|
106
|
+
|
|
107
|
+
The QNN backend supports the following device options:
|
|
108
|
+
|
|
109
|
+
- `"auto"` — automatically selects an available accelerator
|
|
110
|
+
- `"npu"` — uses the Qualcomm NPU/HTP backend
|
|
111
|
+
- `"gpu"` — uses the Qualcomm GPU backend
|
|
112
|
+
- `"cpu"` — uses the original PyTorch graph without QNN acceleration
|
|
113
|
+
|
|
114
|
+
The default is `"auto"`.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
compiled_model = torch.compile(
|
|
118
|
+
model,
|
|
119
|
+
backend="qnn",
|
|
120
|
+
options={
|
|
121
|
+
"device": "npu",
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Backend options
|
|
127
|
+
|
|
128
|
+
| Option | Values | Default | Description |
|
|
129
|
+
| --- | --- | --- | --- |
|
|
130
|
+
| `device` | `auto`, `npu`, `gpu`, `cpu` | `auto` | Selects the execution backend. |
|
|
131
|
+
| `strict` | `True`, `False` | `False` | Controls whether compilation failures can fall back. |
|
|
132
|
+
| `fp16` | `True`, `False` | `False` | Enables HTP FP16 precision for NPU execution. |
|
|
133
|
+
|
|
134
|
+
### Automatic fallback
|
|
135
|
+
|
|
136
|
+
With the default configuration, `qnnx_backend` falls back when QNN compilation is not possible.
|
|
137
|
+
|
|
138
|
+
In `"auto"` mode, devices are attempted in the following order:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
NPU -> GPU -> CPU
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For example:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
compiled_model = torch.compile(
|
|
148
|
+
model,
|
|
149
|
+
backend="qnn",
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If the graph cannot be compiled for the NPU, the GPU is attempted. If neither QNN accelerator can execute the graph, PyTorch CPU execution is used.
|
|
154
|
+
|
|
155
|
+
### Strict mode
|
|
156
|
+
|
|
157
|
+
Use `strict=True` when execution on the selected QNN device is required:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
compiled_model = torch.compile(
|
|
161
|
+
model,
|
|
162
|
+
backend="qnn",
|
|
163
|
+
options={
|
|
164
|
+
"device": "npu",
|
|
165
|
+
"strict": True,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
In strict mode, a QNN compilation or session creation failure is propagated instead of falling back to CPU.
|
|
171
|
+
|
|
172
|
+
This is useful when testing whether a graph is actually supported by a specific QNN backend.
|
|
173
|
+
|
|
174
|
+
### NPU FP16 execution
|
|
175
|
+
|
|
176
|
+
For NPU execution, FP16 precision can be enabled with:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
compiled_model = torch.compile(
|
|
180
|
+
model,
|
|
181
|
+
backend="qnn",
|
|
182
|
+
options={
|
|
183
|
+
"device": "npu",
|
|
184
|
+
"fp16": True,
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This option controls the QNN HTP FP16 precision setting.
|
|
190
|
+
|
|
191
|
+
## Inference
|
|
192
|
+
|
|
193
|
+
`qnnx_backend` currently targets inference workloads.
|
|
194
|
+
|
|
195
|
+
Models should be switched to evaluation mode before compilation:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
model.eval()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Inference should normally be performed using:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
with torch.inference_mode():
|
|
205
|
+
output = compiled_model(x)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Training and backward execution are not currently supported.
|
|
209
|
+
|
|
210
|
+
## Model parameters and buffers
|
|
211
|
+
|
|
212
|
+
Model parameters and buffers are captured when the QNN graph is compiled.
|
|
213
|
+
|
|
214
|
+
For example:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
compiled_model = torch.compile(
|
|
218
|
+
model.eval(),
|
|
219
|
+
backend="qnn",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
compiled_model(x)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
After compilation, modifying parameters or buffers of the original model does not update the existing QNN session.
|
|
226
|
+
|
|
227
|
+
Recompile the model after changing its parameters:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
compiled_model = torch.compile(
|
|
231
|
+
model.eval(),
|
|
232
|
+
backend="qnn",
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Static shapes
|
|
237
|
+
|
|
238
|
+
Version 0.1.0 currently targets static input shapes.
|
|
239
|
+
|
|
240
|
+
Compile the model using the input shapes that will be used during inference.
|
|
241
|
+
|
|
242
|
+
Dynamic-shape support is not currently provided.
|
|
243
|
+
|
|
244
|
+
## Operator support
|
|
245
|
+
|
|
246
|
+
`qnnx_backend` validates exported ONNX operators against the currently supported QNN backends.
|
|
247
|
+
|
|
248
|
+
Operator support may differ between NPU/HTP and GPU.
|
|
249
|
+
|
|
250
|
+
See the [operator compatibility list](docs/operators.md) for the current support matrix.
|
|
251
|
+
|
|
252
|
+
## Benchmarks
|
|
253
|
+
|
|
254
|
+
Benchmarks were performed on Windows ARM64 with a Snapdragon X Elite.
|
|
255
|
+
|
|
256
|
+
The results below show steady-state inference latency after warm-up.
|
|
257
|
+
|
|
258
|
+
### Small models
|
|
259
|
+
|
|
260
|
+
Steady-state inference latency after warm-up. Values are median latency in milliseconds (lower is better).
|
|
261
|
+
|
|
262
|
+
| Model | Batch | PyTorch CPU | QNN GPU | QNN NPU |
|
|
263
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
264
|
+
| Small MLP | 16 | 0.042 ms | 0.201 ms | 0.203 ms |
|
|
265
|
+
| Small CNN | 16 | 0.299 ms | 0.953 ms | **0.212 ms** |
|
|
266
|
+
| Small Transformer | 16 | 0.750 ms | 1.459 ms | **0.306 ms** |
|
|
267
|
+
|
|
268
|
+
### Medium models
|
|
269
|
+
|
|
270
|
+
Steady-state inference latency after warm-up. Values are median latency in milliseconds (lower is better).
|
|
271
|
+
|
|
272
|
+
| Model | Batch | PyTorch CPU | QNN GPU | QNN NPU |
|
|
273
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
274
|
+
| Medium MLP | 16 | 2.523 ms | 0.505 ms | **0.333 ms** |
|
|
275
|
+
| Medium CNN | 16 | 134.117 ms | 11.292 ms | **2.143 ms** |
|
|
276
|
+
| Medium Transformer | 16 | 47.343 ms | 9.176 ms | **2.036 ms** |
|
|
277
|
+
|
|
278
|
+
See [detailed benchmarks](docs/benchmarks.md) for batch-size scaling, first-call latency, median, p95, standard deviation, and benchmark methodology.
|
|
279
|
+
|
|
280
|
+
## Author
|
|
281
|
+
|
|
282
|
+
Developed by **Thomas Le Gall** — [ORCID](https://orcid.org/0009-0007-4387-0435) · [GitLab](https://gitlab.com/legall.thomas.ll).
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
287
|
+
|
|
288
|
+
Copyright © 2026 Thomas Le Gall.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=70"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qnnx-backend"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A PyTorch torch.compile backend for Qualcomm QNN."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Thomas Le Gall" }
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
requires-python = ">=3.13,<3.14"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"torch>=2.13,<2.14",
|
|
19
|
+
"onnxruntime-qnn>=2.4",
|
|
20
|
+
"onnx",
|
|
21
|
+
"onnxscript",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://gitlab.com/legall.thomas.ll/qnnx-backend"
|
|
26
|
+
ORCID = "https://orcid.org/0009-0007-4387-0435"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"numpy",
|
|
31
|
+
"pytest",
|
|
32
|
+
"pytest-cov",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.entry-points."torch_dynamo_backends"]
|
|
36
|
+
qnn = "qnnx_backend.backend:qnn_backend"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff]
|
|
42
|
+
line-length = 100
|
|
43
|
+
target-version = "py313"
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint]
|
|
46
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.format]
|
|
49
|
+
quote-style = "double"
|
|
50
|
+
indent-style = "space"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.envs.default]
|
|
53
|
+
detached = true
|
|
54
|
+
dependencies = [
|
|
55
|
+
"ruff",
|
|
56
|
+
"pyright"
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.hatch.envs.default.scripts]
|
|
60
|
+
lint = "ruff check ."
|
|
61
|
+
lint-fix = "ruff check . --fix"
|
|
62
|
+
format = "ruff format ."
|
|
63
|
+
format-check = "ruff format --check ."
|
|
64
|
+
typecheck = "pyright"
|
|
65
|
+
|
|
66
|
+
check = [
|
|
67
|
+
"ruff check .",
|
|
68
|
+
"ruff format --check .",
|
|
69
|
+
"pyright"
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
fix = [
|
|
73
|
+
"ruff format .",
|
|
74
|
+
"ruff check . --fix",
|
|
75
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from qnnx_backend.compatibility import QNNDevice
|
|
2
|
+
from qnnx_backend.runtime.qnn import QNNRuntime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_available(
|
|
6
|
+
device: QNNDevice | None = None,
|
|
7
|
+
) -> bool:
|
|
8
|
+
if device is not None:
|
|
9
|
+
return QNNRuntime.is_device_available(device)
|
|
10
|
+
|
|
11
|
+
return QNNRuntime.is_device_available("npu") or QNNRuntime.is_device_available("gpu")
|