torch-prisma 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.
- torch_prisma-0.1.0/LICENSE +21 -0
- torch_prisma-0.1.0/PKG-INFO +72 -0
- torch_prisma-0.1.0/README.md +52 -0
- torch_prisma-0.1.0/docs/ascend.md +53 -0
- torch_prisma-0.1.0/docs/benchmarks.md +73 -0
- torch_prisma-0.1.0/docs/index.md +41 -0
- torch_prisma-0.1.0/docs/measurement.md +87 -0
- torch_prisma-0.1.0/pyproject.toml +75 -0
- torch_prisma-0.1.0/tests/test_accelerators.py +145 -0
- torch_prisma-0.1.0/tests/test_measurement.py +162 -0
- torch_prisma-0.1.0/torch_prisma/__init__.py +20 -0
- torch_prisma-0.1.0/torch_prisma/complexity.py +303 -0
- torch_prisma-0.1.0/torch_prisma/execution.py +19 -0
- torch_prisma-0.1.0/torch_prisma/formulas.py +677 -0
- torch_prisma-0.1.0/torch_prisma/latency.py +246 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZaberKo
|
|
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,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch-prisma
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Measure PyTorch inference latency, MACs, FLOPs, and parameters
|
|
5
|
+
Keywords: pytorch,inference,benchmarking,latency,model-complexity,macs,flops,cuda,ascend
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 6 - Mature
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Topic :: System :: Benchmark
|
|
14
|
+
Requires-Dist: torch>=2.6
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Project-URL: Documentation, https://github.com/ZaberKo/torch-prisma/blob/main/docs/index.md
|
|
17
|
+
Project-URL: Issues, https://github.com/ZaberKo/torch-prisma/issues
|
|
18
|
+
Project-URL: Repository, https://github.com/ZaberKo/torch-prisma
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Torch Prisma
|
|
22
|
+
|
|
23
|
+
Measure PyTorch inference latency, parameters, MACs, and FLOPs on CPU, CUDA, and Huawei Ascend.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Requires Python 3.10+ and PyTorch 2.6+. Install in a virtual environment.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv pip install torch-prisma
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Latency uses `torch.compile` by default. Set `compile=False` for eager timing.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import torch
|
|
39
|
+
from torch import nn
|
|
40
|
+
|
|
41
|
+
from torch_prisma import calculate_model_complexity, measure_module_latency
|
|
42
|
+
|
|
43
|
+
# For inference, set the model to evaluation mode and place it and the inputs on the device.
|
|
44
|
+
device = "cpu"
|
|
45
|
+
model = nn.Linear(8, 4).eval().to(device)
|
|
46
|
+
x = torch.randn(2, 8, device=device)
|
|
47
|
+
|
|
48
|
+
complexity = calculate_model_complexity(model, x)
|
|
49
|
+
latency = measure_module_latency(model, x, device=device)
|
|
50
|
+
|
|
51
|
+
print(complexity.params, complexity.macs, complexity.flops)
|
|
52
|
+
# Operations with incomplete counts, grouped by MACs and FLOPs.
|
|
53
|
+
print(complexity.uncovered_ops)
|
|
54
|
+
print(latency.median_ms)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
[Measurement API](https://github.com/ZaberKo/torch-prisma/blob/main/docs/measurement.md) · [Latency benchmarks](https://github.com/ZaberKo/torch-prisma/blob/main/docs/benchmarks.md) · [Huawei Ascend](https://github.com/ZaberKo/torch-prisma/blob/main/docs/ascend.md)
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv sync
|
|
63
|
+
uv run pre-commit install
|
|
64
|
+
uv run pytest
|
|
65
|
+
uv build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Preview the documentation with `uv run --group docs zensical serve`.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
Torch Prisma is distributed under the [MIT License](https://github.com/ZaberKo/torch-prisma/blob/main/LICENSE).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Torch Prisma
|
|
2
|
+
|
|
3
|
+
Measure PyTorch inference latency, parameters, MACs, and FLOPs on CPU, CUDA, and Huawei Ascend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Requires Python 3.10+ and PyTorch 2.6+. Install in a virtual environment.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv pip install torch-prisma
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Latency uses `torch.compile` by default. Set `compile=False` for eager timing.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import torch
|
|
19
|
+
from torch import nn
|
|
20
|
+
|
|
21
|
+
from torch_prisma import calculate_model_complexity, measure_module_latency
|
|
22
|
+
|
|
23
|
+
# For inference, set the model to evaluation mode and place it and the inputs on the device.
|
|
24
|
+
device = "cpu"
|
|
25
|
+
model = nn.Linear(8, 4).eval().to(device)
|
|
26
|
+
x = torch.randn(2, 8, device=device)
|
|
27
|
+
|
|
28
|
+
complexity = calculate_model_complexity(model, x)
|
|
29
|
+
latency = measure_module_latency(model, x, device=device)
|
|
30
|
+
|
|
31
|
+
print(complexity.params, complexity.macs, complexity.flops)
|
|
32
|
+
# Operations with incomplete counts, grouped by MACs and FLOPs.
|
|
33
|
+
print(complexity.uncovered_ops)
|
|
34
|
+
print(latency.median_ms)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
[Measurement API](https://github.com/ZaberKo/torch-prisma/blob/main/docs/measurement.md) · [Latency benchmarks](https://github.com/ZaberKo/torch-prisma/blob/main/docs/benchmarks.md) · [Huawei Ascend](https://github.com/ZaberKo/torch-prisma/blob/main/docs/ascend.md)
|
|
38
|
+
|
|
39
|
+
## Development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv sync
|
|
43
|
+
uv run pre-commit install
|
|
44
|
+
uv run pytest
|
|
45
|
+
uv build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Preview the documentation with `uv run --group docs zensical serve`.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
Torch Prisma is distributed under the [MIT License](https://github.com/ZaberKo/torch-prisma/blob/main/LICENSE).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Huawei Ascend
|
|
2
|
+
|
|
3
|
+
## Runtime setup
|
|
4
|
+
|
|
5
|
+
Install the Ascend driver, CANN, and compatible PyTorch and TorchNPU versions. Initialize CANN before starting Python. The default compiled timing backend, TorchAir, is bundled in supported TorchNPU distributions. [Ascend compatibility matrix](https://github.com/Ascend/pytorch/blob/master/COMPATIBILITY.en.md) · [TorchAir](https://github.com/Ascend/torchair)
|
|
6
|
+
|
|
7
|
+
## Measuring latency
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import torch
|
|
11
|
+
import torch_npu
|
|
12
|
+
from torch import nn
|
|
13
|
+
|
|
14
|
+
from torch_prisma import measure_module_latency
|
|
15
|
+
|
|
16
|
+
device = torch.device("npu:0")
|
|
17
|
+
model = nn.Linear(128, 64, device=device).eval()
|
|
18
|
+
inputs = torch.randn(8, 128, device=device)
|
|
19
|
+
|
|
20
|
+
print(measure_module_latency(model, inputs, device).median_ms)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
TorchAir is the default compiler backend on Ascend. If it raises a compatibility error, use `compile=False` for eager timing.
|
|
24
|
+
|
|
25
|
+
To configure TorchAir, create a `CompilerConfig` and pass the resulting backend to the measurement function. This example enables graph dumps.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import torchair
|
|
29
|
+
from torchair.configs.compiler_config import CompilerConfig
|
|
30
|
+
|
|
31
|
+
config = CompilerConfig()
|
|
32
|
+
config.debug.graph_dump.type = "pbtxt"
|
|
33
|
+
backend = torchair.get_npu_backend(compiler_config=config)
|
|
34
|
+
latency = measure_module_latency(model, inputs, device, compile_kwargs={"backend": backend})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See the [TorchAir documentation](https://github.com/Ascend/torchair) for configuration options.
|
|
38
|
+
|
|
39
|
+
## Triton Ascend
|
|
40
|
+
|
|
41
|
+
To use [Triton Ascend](https://github.com/triton-lang/triton-ascend) through PyTorch Inductor, install a compatible Triton Ascend, CANN, PyTorch, and TorchNPU combination. With `compile=True` (the default), select the Inductor backend explicitly:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
latency = measure_module_latency(model, inputs, device, compile_kwargs={"backend": "inductor"})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
TorchNPU uses Triton as its default Inductor compilation mode. If `TORCHINDUCTOR_NPU_BACKEND` has been changed, set it to `default` before the first compilation to select Triton. See the [TorchNPU backend documentation](https://github.com/Ascend/pytorch/blob/master/docs/zh/api/environment_variable/inductor/TORCHINDUCTOR_NPU_BACKEND.md).
|
|
48
|
+
|
|
49
|
+
For a callable that launches a Triton kernel directly, use eager timing:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
latency = measure_module_latency(triton_callable, (x, y, output), device, compile=False)
|
|
53
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Latency benchmarks
|
|
2
|
+
|
|
3
|
+
These measurements examine variation across independent process starts. Each process calls `measure_module_latency()` once. Each supported combination has 16 results: eight on each of two physical cards. The two cards were tested concurrently, with case order shuffled. Results retain all timed samples, including outliers.
|
|
4
|
+
|
|
5
|
+
Each test uses one accelerator, one PyTorch intra-op thread, randomly initialized TorchVision weights, and a fixed FP32 input of shape `(1, 3, 224, 224)`. The model is in evaluation mode, and weights and input use a fixed seed. The warmup and sample counts are shown below. Compiled measurements also use three preparation forwards. Inductor's disk cache was reused between processes separately for each card.
|
|
6
|
+
|
|
7
|
+
The latency columns are the averages of the 16 returned `mean_ms` and `median_ms` values. CV means *coefficient of variation*: the sample standard deviation divided by the average, expressed as a percentage. Each CV pair reports the variation of the returned mean and median, respectively. Combined CV uses all 16 processes; card CV uses the eight processes on that card. The Ascend test used cards 6 and 7; the 3080 Ti test used cards 0 and 1.
|
|
8
|
+
|
|
9
|
+
## Ascend 910B2
|
|
10
|
+
|
|
11
|
+
The Ascend environment used PyTorch 2.10.0, TorchVision 0.25.0, TorchNPU 2.10.0.post7, Triton Ascend 3.6.0, CANN 9.2.0-beta.2, and driver 26.1.1. Inductor used Triton mode and four compiler threads. TorchAir is the default Ascend compiler backend.
|
|
12
|
+
|
|
13
|
+
| Model | Execution | Warmup / samples | Mean (ms) | Median (ms) | Combined CV | Card 6 CV | Card 7 CV |
|
|
14
|
+
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
|
15
|
+
| MobileNetV3-Small | Eager | 5 / 20 | 6.895 | 6.804 | 4.00% / 3.12% | 2.92% / 2.51% | 5.04% / 3.72% |
|
|
16
|
+
| MobileNetV3-Small | Eager | 20 / 100 | 6.612 | 6.559 | 4.65% / 4.40% | 4.64% / 4.75% | 4.96% / 4.20% |
|
|
17
|
+
| MobileNetV3-Small | Eager | 50 / 20 | 6.365 | 6.343 | 3.86% / 3.49% | 3.72% / 3.00% | 3.74% / 3.77% |
|
|
18
|
+
| MobileNetV3-Small | TorchAir | 5 / 20 | 1.724 | 1.704 | 3.66% / 3.77% | 3.89% / 4.05% | 3.55% / 3.62% |
|
|
19
|
+
| MobileNetV3-Small | TorchAir | 20 / 100 | 1.744 | 1.739 | 4.28% / 4.29% | 4.28% / 4.30% | 4.47% / 4.50% |
|
|
20
|
+
| MobileNetV3-Small | TorchAir | 50 / 20 | 1.748 | 1.723 | 4.46% / 4.37% | 4.87% / 4.51% | 4.19% / 4.42% |
|
|
21
|
+
| MobileNetV3-Small | Triton Inductor | 5 / 20 | 7.488 | 7.477 | 4.85% / 4.98% | 5.60% / 5.77% | 3.37% / 3.52% |
|
|
22
|
+
| MobileNetV3-Small | Triton Inductor | 20 / 100 | 7.400 | 7.374 | 3.23% / 3.12% | 2.28% / 2.15% | 3.81% / 3.76% |
|
|
23
|
+
| MobileNetV3-Small | Triton Inductor | 50 / 20 | 7.303 | 7.293 | 3.91% / 3.88% | 4.09% / 3.95% | 4.01% / 4.07% |
|
|
24
|
+
| ResNet-50 | Eager | 5 / 20 | 7.481 | 7.451 | 6.15% / 6.15% | 7.04% / 7.14% | 5.60% / 5.47% |
|
|
25
|
+
| ResNet-50 | Eager | 20 / 100 | 7.235 | 7.196 | 4.38% / 4.27% | 3.69% / 3.71% | 5.13% / 4.97% |
|
|
26
|
+
| ResNet-50 | Eager | 50 / 20 | 7.181 | 7.162 | 3.21% / 3.25% | 3.07% / 3.28% | 3.38% / 3.31% |
|
|
27
|
+
| ResNet-50 | TorchAir | 5 / 20 | 2.378 | 2.361 | 4.05% / 4.07% | 3.98% / 4.02% | 4.38% / 4.40% |
|
|
28
|
+
| ResNet-50 | TorchAir | 20 / 100 | 2.375 | 2.371 | 3.77% / 3.76% | 4.26% / 4.25% | 0.37% / 0.36% |
|
|
29
|
+
| ResNet-50 | TorchAir | 50 / 20 | 2.359 | 2.338 | 4.03% / 4.12% | 4.24% / 4.33% | 4.09% / 4.20% |
|
|
30
|
+
| ResNet-50 | Triton Inductor | 5 / 20 | 12.365 | 12.324 | 0.29% / 0.28% | 0.20% / 0.22% | 0.34% / 0.29% |
|
|
31
|
+
| ResNet-50 | Triton Inductor | 20 / 100 | 12.329 | 12.321 | 0.35% / 0.35% | 0.19% / 0.19% | 0.25% / 0.25% |
|
|
32
|
+
| ResNet-50 | Triton Inductor | 50 / 20 | 12.376 | 12.321 | 0.32% / 0.35% | 0.13% / 0.24% | 0.18% / 0.21% |
|
|
33
|
+
| ViT-B/16 | Eager | 5 / 20 | 8.810 | 8.799 | 4.79% / 4.49% | 3.05% / 2.64% | 6.19% / 5.90% |
|
|
34
|
+
| ViT-B/16 | Eager | 20 / 100 | 8.344 | 8.311 | 6.71% / 6.14% | 9.26% / 8.45% | 3.28% / 3.08% |
|
|
35
|
+
| ViT-B/16 | Eager | 50 / 20 | 8.278 | 8.253 | 4.42% / 4.39% | 5.82% / 5.72% | 2.72% / 2.69% |
|
|
36
|
+
| ViT-B/16 | Triton Inductor | 5 / 20 | 10.945 | 10.899 | 2.51% / 2.43% | 2.02% / 1.94% | 2.98% / 2.88% |
|
|
37
|
+
| ViT-B/16 | Triton Inductor | 20 / 100 | 10.578 | 10.553 | 3.05% / 3.15% | 2.44% / 2.44% | 3.48% / 3.66% |
|
|
38
|
+
| ViT-B/16 | Triton Inductor | 50 / 20 | 10.672 | 10.643 | 5.36% / 5.21% | 5.91% / 5.74% | 5.01% / 4.89% |
|
|
39
|
+
|
|
40
|
+
ViT-B/16 with TorchAir failed during compilation preparation on both cards with `only supported softmax_layout=TND`; no latency statistics are available for that combination.
|
|
41
|
+
|
|
42
|
+
## GeForce RTX 3080 Ti
|
|
43
|
+
|
|
44
|
+
The 3080 Ti environment used PyTorch 2.15.0, TorchVision 0.29.2, CUDA runtime 13.2, Triton 3.9.0, and NVIDIA driver 595.71.05. Inductor used four compiler threads. Software versions differ between devices, so these results are not a controlled cross-device performance comparison.
|
|
45
|
+
|
|
46
|
+
| Model | Execution | Warmup / samples | Mean (ms) | Median (ms) | Combined CV | Card 0 CV | Card 1 CV |
|
|
47
|
+
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
|
48
|
+
| MobileNetV3-Small | Eager | 5 / 20 | 4.302 | 4.267 | 11.07% / 10.99% | 13.12% / 12.99% | 2.31% / 1.34% |
|
|
49
|
+
| MobileNetV3-Small | Eager | 20 / 100 | 4.399 | 4.383 | 12.16% / 12.36% | 11.37% / 11.69% | 13.54% / 13.61% |
|
|
50
|
+
| MobileNetV3-Small | Eager | 50 / 20 | 4.149 | 4.127 | 6.94% / 7.10% | 2.00% / 1.86% | 9.47% / 9.66% |
|
|
51
|
+
| MobileNetV3-Small | Inductor | 5 / 20 | 2.214 | 2.209 | 14.75% / 14.60% | 15.17% / 14.82% | 15.14% / 15.15% |
|
|
52
|
+
| MobileNetV3-Small | Inductor | 20 / 100 | 2.115 | 2.114 | 11.96% / 13.02% | 14.83% / 15.83% | 0.67% / 0.81% |
|
|
53
|
+
| MobileNetV3-Small | Inductor | 50 / 20 | 2.046 | 2.038 | 8.14% / 8.22% | 1.54% / 1.30% | 11.18% / 11.33% |
|
|
54
|
+
| ResNet-50 | Eager | 5 / 20 | 5.197 | 5.106 | 10.02% / 10.16% | 8.87% / 9.16% | 11.21% / 11.41% |
|
|
55
|
+
| ResNet-50 | Eager | 20 / 100 | 4.884 | 4.840 | 1.46% / 1.42% | 1.36% / 1.36% | 1.57% / 1.38% |
|
|
56
|
+
| ResNet-50 | Eager | 50 / 20 | 5.006 | 4.981 | 8.10% / 8.31% | 8.33% / 8.61% | 8.44% / 8.59% |
|
|
57
|
+
| ResNet-50 | Inductor | 5 / 20 | 2.656 | 2.612 | 15.85% / 13.01% | 10.20% / 10.24% | 18.49% / 14.66% |
|
|
58
|
+
| ResNet-50 | Inductor | 20 / 100 | 2.674 | 2.658 | 13.58% / 13.51% | 10.56% / 10.48% | 14.70% / 14.58% |
|
|
59
|
+
| ResNet-50 | Inductor | 50 / 20 | 2.613 | 2.590 | 15.67% / 14.44% | 10.95% / 11.09% | 19.15% / 17.22% |
|
|
60
|
+
| ViT-B/16 | Eager | 5 / 20 | 4.522 | 4.505 | 7.30% / 7.23% | 6.22% / 5.73% | 8.07% / 8.25% |
|
|
61
|
+
| ViT-B/16 | Eager | 20 / 100 | 4.224 | 4.151 | 7.89% / 8.41% | 2.35% / 1.45% | 10.07% / 10.50% |
|
|
62
|
+
| ViT-B/16 | Eager | 50 / 20 | 4.284 | 4.249 | 8.91% / 9.10% | 10.22% / 10.27% | 7.66% / 8.15% |
|
|
63
|
+
| ViT-B/16 | Inductor | 5 / 20 | 4.655 | 4.670 | 1.43% / 0.17% | 1.79% / 0.23% | 0.09% / 0.07% |
|
|
64
|
+
| ViT-B/16 | Inductor | 20 / 100 | 4.315 | 4.240 | 1.62% / 0.98% | 1.60% / 0.18% | 1.04% / 0.34% |
|
|
65
|
+
| ViT-B/16 | Inductor | 50 / 20 | 4.282 | 4.240 | 1.11% / 0.82% | 1.51% / 0.46% | 0.38% / 0.29% |
|
|
66
|
+
|
|
67
|
+
## Interpreting the defaults
|
|
68
|
+
|
|
69
|
+
The default is five warmup forwards and 20 timed forwards. On Ascend, the combined mean and median CVs at 5/20 ranged from 0.28% to 6.15% across the measured combinations. Increasing warmup or sample counts did not consistently reduce variation across all models and backends.
|
|
70
|
+
|
|
71
|
+
On the 3080 Ti, the combined median CV at 5/20 ranged from 0.17% to 14.60%. For ViT-B/16 with Inductor, the returned median averaged 4.670 ms at 5/20 and 4.240 ms at 50/20, even though the 5/20 median CV was only 0.17%. A low CV therefore does not by itself establish that the measurement has reached a representative latency. Increasing warmup to 50 did not reduce CV for every measured combination.
|
|
72
|
+
|
|
73
|
+
These results do not establish either 5/20 or 50/20 as a reliable setting for every workload or device. Compare settings for the workload and environment being measured, and inspect the samples when the mean and median differ substantially.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Torch Prisma
|
|
2
|
+
|
|
3
|
+
Torch Prisma measures PyTorch inference latency, parameters, multiply-accumulates (MACs), and floating-point operations (FLOPs) on CPU, CUDA, and Huawei Ascend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Requires Python 3.10+ and PyTorch 2.6+. Install in a virtual environment.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv pip install torch-prisma
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Latency uses `torch.compile` by default. Set `compile=False` for eager timing.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import torch
|
|
19
|
+
from torch import nn
|
|
20
|
+
|
|
21
|
+
from torch_prisma import calculate_model_complexity, measure_module_latency
|
|
22
|
+
|
|
23
|
+
# For inference, set the model to evaluation mode and place it and the inputs on the device.
|
|
24
|
+
device = "cpu"
|
|
25
|
+
model = nn.Linear(8, 4).eval().to(device)
|
|
26
|
+
inputs = torch.randn(2, 8, device=device)
|
|
27
|
+
|
|
28
|
+
complexity = calculate_model_complexity(model, inputs)
|
|
29
|
+
latency = measure_module_latency(model, inputs, device=device)
|
|
30
|
+
|
|
31
|
+
print(complexity.params, complexity.macs, complexity.flops)
|
|
32
|
+
# Operations with incomplete counts, grouped by MACs and FLOPs.
|
|
33
|
+
print(complexity.uncovered_ops)
|
|
34
|
+
print(latency.median_ms)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Guides
|
|
38
|
+
|
|
39
|
+
- [Measurement API and conventions](measurement.md)
|
|
40
|
+
- [Latency benchmarks](benchmarks.md)
|
|
41
|
+
- [Huawei Ascend](ascend.md)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Measurement API
|
|
2
|
+
|
|
3
|
+
Import the measurement functions from `torch_prisma`. Set the model's training or evaluation mode and place the model and inputs on their execution devices before measuring. Forward side effects persist.
|
|
4
|
+
|
|
5
|
+
## `measure_module_latency`
|
|
6
|
+
|
|
7
|
+
Measures one forward per sample for the entire batch in inference mode. CPU uses a wall clock. CUDA and Ascend use events on the selected device's current stream.
|
|
8
|
+
|
|
9
|
+
**Parameters**
|
|
10
|
+
|
|
11
|
+
- `target` — Prepared module or callable.
|
|
12
|
+
- `input_args` — Positional input or tuple/list of positional inputs. Use `(items,)` to pass a list as one argument.
|
|
13
|
+
- `device` — Timing device.
|
|
14
|
+
- `input_kwargs` — Optional keyword arguments.
|
|
15
|
+
- `repetitions` — Number of timed forwards. Defaults to 20.
|
|
16
|
+
- `warmup` — Number of untimed forwards after compilation preparation. Defaults to 5.
|
|
17
|
+
- `compile` — Enable `torch.compile`. Defaults to `True`.
|
|
18
|
+
- `compile_kwargs` — Keyword arguments passed to `torch.compile` when enabled. On Ascend, pass `{"backend": "inductor"}` to use Triton through Inductor. See the [Ascend guide](ascend.md#triton-ascend).
|
|
19
|
+
|
|
20
|
+
**Returns**
|
|
21
|
+
|
|
22
|
+
A `LatencyStats` result in milliseconds. Its `samples_ms` preserve sampling order. The remaining fields are `mean_ms`, `median_ms`, `std_ms` (population standard deviation), `min_ms`, `max_ms`, `p90_ms`, and `p99_ms`. Percentiles select the sample at index `min(int(n * q), n - 1)` after sorting, with `q` equal to 0.90 or 0.99. Use `LatencyStats.from_samples()` to summarize external timings and `to_dict()` to serialize the result.
|
|
23
|
+
|
|
24
|
+
**Raises**
|
|
25
|
+
|
|
26
|
+
- `ValueError` for invalid timing options or an unsupported timing device.
|
|
27
|
+
- `RuntimeError` when the accelerator or default Ascend backend is unavailable, or compilation is required after preparation.
|
|
28
|
+
|
|
29
|
+
Compilation runs three untimed forwards before warmup. Set `compile=False` for eager timing. See [Ascend setup](ascend.md) and [latency benchmarks](benchmarks.md).
|
|
30
|
+
|
|
31
|
+
## `calculate_model_complexity`
|
|
32
|
+
|
|
33
|
+
Counts operations for one eager forward without gradients. Counting requests math scaled dot-product attention and disables the multihead attention fast path.
|
|
34
|
+
|
|
35
|
+
**Parameters**
|
|
36
|
+
|
|
37
|
+
- `target` — Prepared module or callable.
|
|
38
|
+
- `input_args` — Positional input or tuple/list of positional inputs.
|
|
39
|
+
- `input_kwargs` — Optional keyword arguments.
|
|
40
|
+
|
|
41
|
+
**Returns**
|
|
42
|
+
|
|
43
|
+
A `ModelComplexity` result for the supplied batch. `params` counts unique registered parameter elements. `macs` and `flops` are known operation subtotals. `uncovered_ops` lists visible operations with incomplete counts, grouped by MACs and FLOPs. A plain callable reports zero parameters. Use `to_dict()` to serialize the result.
|
|
44
|
+
|
|
45
|
+
Each real floating-point dense MAC contributes two FLOPs. Supported arithmetic outside matrix products adds to FLOPs. Comparisons, ReLU, forward max pooling, indexing, and data movement contribute zero FLOPs. Integer matrix work contributes MACs only. Complex matrix work contributes MACs and a FLOP coverage gap.
|
|
46
|
+
|
|
47
|
+
## `count_parameters`
|
|
48
|
+
|
|
49
|
+
Counts the elements in unique registered parameter objects without executing a forward.
|
|
50
|
+
|
|
51
|
+
**Parameters**
|
|
52
|
+
|
|
53
|
+
- `model` — Module whose parameters are counted.
|
|
54
|
+
|
|
55
|
+
**Returns**
|
|
56
|
+
|
|
57
|
+
The parameter count as an integer. Distinct parameters sharing storage count separately.
|
|
58
|
+
|
|
59
|
+
## `count_operations`
|
|
60
|
+
|
|
61
|
+
Counts operations for a prepared zero-argument eager callable.
|
|
62
|
+
|
|
63
|
+
**Parameters**
|
|
64
|
+
|
|
65
|
+
- `forward` — Callable that executes the forward to count.
|
|
66
|
+
|
|
67
|
+
**Returns**
|
|
68
|
+
|
|
69
|
+
An `OperationCounts` result containing `macs`, `flops`, and `uncovered_ops`.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from torch_prisma import count_operations
|
|
73
|
+
|
|
74
|
+
operations = count_operations(lambda: model(inputs))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Nominal FLOP formulas
|
|
78
|
+
|
|
79
|
+
Each transcendental operation counts as one FLOP.
|
|
80
|
+
|
|
81
|
+
- Addition, subtraction, multiplication, and division contribute one FLOP per output element. A nonunit add/subtract scale contributes an additional multiplication.
|
|
82
|
+
- Sum over a nonempty group of `K` elements contributes `K - 1` additions. Mean adds one division.
|
|
83
|
+
- Softmax over a nonempty group of `K` elements contributes `4K - 1` FLOPs.
|
|
84
|
+
- LayerNorm and GroupNorm contribute `5K + 2` FLOPs per group of `K` elements, plus `K` for each affine weight or bias.
|
|
85
|
+
- BatchNorm with stored statistics contributes two FLOPs per channel and two per input element, plus one per element for each affine weight or bias.
|
|
86
|
+
- Matrix product bias and scaling contribute an addition per output element when the bias term is active, plus each applicable nonunit scalar multiplication.
|
|
87
|
+
- Convolution bias contributes one additional FLOP per output element.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.9.11,<0.10.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "torch-prisma"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Measure PyTorch inference latency, MACs, FLOPs, and parameters"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["torch>=2.6"]
|
|
14
|
+
keywords = [
|
|
15
|
+
"pytorch",
|
|
16
|
+
"inference",
|
|
17
|
+
"benchmarking",
|
|
18
|
+
"latency",
|
|
19
|
+
"model-complexity",
|
|
20
|
+
"macs",
|
|
21
|
+
"flops",
|
|
22
|
+
"cuda",
|
|
23
|
+
"ascend",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 6 - Mature",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Topic :: System :: Benchmark",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Repository = "https://github.com/ZaberKo/torch-prisma"
|
|
36
|
+
Documentation = "https://github.com/ZaberKo/torch-prisma/blob/main/docs/index.md"
|
|
37
|
+
Issues = "https://github.com/ZaberKo/torch-prisma/issues"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pre-commit; python_version >= '3.11'",
|
|
42
|
+
"pytest>=8",
|
|
43
|
+
"ruff",
|
|
44
|
+
]
|
|
45
|
+
docs = ["zensical"]
|
|
46
|
+
|
|
47
|
+
[tool.uv.dependency-groups]
|
|
48
|
+
docs = { requires-python = ">=3.11" }
|
|
49
|
+
|
|
50
|
+
[tool.uv.build-backend]
|
|
51
|
+
module-root = ""
|
|
52
|
+
source-include = ["docs/**", "tests/**"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
minversion = "8.0"
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
markers = ["cuda: requires a CUDA runner", "ascend: requires an Ascend and TorchAir runner"]
|
|
58
|
+
addopts = ["-ra", "--strict-config", "--strict-markers", "--import-mode=importlib"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
target-version = "py310"
|
|
62
|
+
line-length = 100
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "C4", "RUF", "D"]
|
|
66
|
+
ignore = ["E501"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint.pydocstyle]
|
|
69
|
+
convention = "google"
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.per-file-ignores]
|
|
72
|
+
"tests/**" = ["D"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.format]
|
|
75
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import torch
|
|
8
|
+
from torch import nn
|
|
9
|
+
|
|
10
|
+
import torch_prisma.latency as latency
|
|
11
|
+
from torch_prisma import calculate_model_complexity, measure_module_latency
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_ascend_compiler_and_event_wiring(monkeypatch):
|
|
15
|
+
device = SimpleNamespace(type="npu")
|
|
16
|
+
stream = object()
|
|
17
|
+
operations = []
|
|
18
|
+
forward_limits = []
|
|
19
|
+
config = torch._dynamo.config
|
|
20
|
+
limit_name = "recompile_limit" if hasattr(config, "recompile_limit") else "cache_size_limit"
|
|
21
|
+
accumulated_limit_name = f"accumulated_{limit_name}"
|
|
22
|
+
original_limits = getattr(config, limit_name), getattr(config, accumulated_limit_name)
|
|
23
|
+
|
|
24
|
+
class Event:
|
|
25
|
+
def __init__(self, *, enable_timing):
|
|
26
|
+
assert enable_timing
|
|
27
|
+
|
|
28
|
+
def record(self, selected_stream):
|
|
29
|
+
assert selected_stream is stream
|
|
30
|
+
operations.append("record")
|
|
31
|
+
|
|
32
|
+
def elapsed_time(self, end):
|
|
33
|
+
assert isinstance(end, Event)
|
|
34
|
+
return 0.25
|
|
35
|
+
|
|
36
|
+
def current_stream(selected):
|
|
37
|
+
assert selected is device
|
|
38
|
+
return stream
|
|
39
|
+
|
|
40
|
+
def synchronize(selected):
|
|
41
|
+
assert selected is device
|
|
42
|
+
operations.append("sync")
|
|
43
|
+
|
|
44
|
+
runtime = SimpleNamespace(Event=Event, current_stream=current_stream, synchronize=synchronize)
|
|
45
|
+
|
|
46
|
+
def timing_device(requested):
|
|
47
|
+
assert requested == "npu:0"
|
|
48
|
+
return device, runtime
|
|
49
|
+
|
|
50
|
+
def compile_fn(fn, *, backend, fullgraph):
|
|
51
|
+
assert backend == "torchair-test" and fullgraph
|
|
52
|
+
return fn
|
|
53
|
+
|
|
54
|
+
def forward(x):
|
|
55
|
+
operations.append("forward")
|
|
56
|
+
forward_limits.append(
|
|
57
|
+
(getattr(config, limit_name), getattr(config, accumulated_limit_name))
|
|
58
|
+
)
|
|
59
|
+
return x
|
|
60
|
+
|
|
61
|
+
backend_factory = Mock(return_value="torchair-test")
|
|
62
|
+
monkeypatch.setitem(sys.modules, "torchair", SimpleNamespace(get_npu_backend=backend_factory))
|
|
63
|
+
monkeypatch.setattr(latency, "_timing_device", timing_device)
|
|
64
|
+
monkeypatch.setattr(torch, "compile", compile_fn)
|
|
65
|
+
latency._default_npu_backend.cache_clear()
|
|
66
|
+
try:
|
|
67
|
+
assert latency._compile_options(device, {"backend": "custom"}) == {"backend": "custom"}
|
|
68
|
+
backend_factory.assert_not_called()
|
|
69
|
+
result = measure_module_latency(
|
|
70
|
+
forward,
|
|
71
|
+
torch.ones(1),
|
|
72
|
+
"npu:0",
|
|
73
|
+
repetitions=2,
|
|
74
|
+
warmup=1,
|
|
75
|
+
compile_kwargs={"fullgraph": True},
|
|
76
|
+
)
|
|
77
|
+
assert latency._compile_options(device, None) == {"backend": "torchair-test"}
|
|
78
|
+
backend_factory.assert_called_once_with()
|
|
79
|
+
finally:
|
|
80
|
+
latency._default_npu_backend.cache_clear()
|
|
81
|
+
assert result.samples_ms == (0.25, 0.25)
|
|
82
|
+
assert operations == (
|
|
83
|
+
["forward", "forward", "forward", "sync", "forward"]
|
|
84
|
+
+ ["record"] * 4
|
|
85
|
+
+ ["sync", "record", "forward", "record", "record", "forward", "record", "sync"]
|
|
86
|
+
)
|
|
87
|
+
raised_limits = (2**31 - 1, 2**31 - 1)
|
|
88
|
+
assert forward_limits == [raised_limits] * 3 + [original_limits] * 3
|
|
89
|
+
assert (getattr(config, limit_name), getattr(config, accumulated_limit_name)) == original_limits
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.cuda
|
|
93
|
+
@pytest.mark.skipif(os.environ.get("TORCH_PRISMA_TEST_DEVICE") != "cuda", reason="Requires CUDA")
|
|
94
|
+
def test_cuda_measurements():
|
|
95
|
+
assert torch.cuda.is_available()
|
|
96
|
+
device = torch.device("cuda", torch.cuda.current_device())
|
|
97
|
+
model = nn.Linear(4, 3, device=device).eval()
|
|
98
|
+
x = torch.ones(2, 4, device=device)
|
|
99
|
+
stats = measure_module_latency(model, x, device, warmup=1, repetitions=3, compile=False)
|
|
100
|
+
assert len(stats.samples_ms) == 3
|
|
101
|
+
assert stats.median_ms > 0
|
|
102
|
+
complexity = calculate_model_complexity(model, x)
|
|
103
|
+
assert (complexity.params, complexity.macs, complexity.flops) == (15, 24, 54)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.ascend
|
|
107
|
+
@pytest.mark.skipif(
|
|
108
|
+
os.environ.get("TORCH_PRISMA_TEST_DEVICE") != "ascend", reason="Requires Ascend"
|
|
109
|
+
)
|
|
110
|
+
def test_ascend_measurements():
|
|
111
|
+
__import__("torch_npu")
|
|
112
|
+
assert torch.npu.is_available()
|
|
113
|
+
device = torch.device("npu", torch.npu.current_device())
|
|
114
|
+
model = nn.Linear(4, 3, device=device).eval()
|
|
115
|
+
x = torch.ones(2, 4, device=device)
|
|
116
|
+
for compile in (False, True):
|
|
117
|
+
stats = measure_module_latency(model, x, device, warmup=2, repetitions=3, compile=compile)
|
|
118
|
+
assert len(stats.samples_ms) == 3
|
|
119
|
+
assert stats.median_ms > 0
|
|
120
|
+
|
|
121
|
+
complexity = calculate_model_complexity(model, x)
|
|
122
|
+
assert complexity.params == 15
|
|
123
|
+
assert complexity.macs == 24 or complexity.uncovered_ops["macs"]
|
|
124
|
+
assert complexity.flops == 54 or complexity.uncovered_ops["flops"]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.ascend
|
|
128
|
+
@pytest.mark.skipif(
|
|
129
|
+
os.environ.get("TORCH_PRISMA_TEST_ASCEND_INDUCTOR") != "1",
|
|
130
|
+
reason="Requires Ascend with Triton Inductor",
|
|
131
|
+
)
|
|
132
|
+
def test_ascend_triton_inductor_measurement():
|
|
133
|
+
__import__("torch_npu")
|
|
134
|
+
device = torch.device("npu", torch.npu.current_device())
|
|
135
|
+
x = torch.randn(8192, device=device)
|
|
136
|
+
|
|
137
|
+
def forward(x):
|
|
138
|
+
return torch.sin(x) + x * x
|
|
139
|
+
|
|
140
|
+
stats = measure_module_latency(
|
|
141
|
+
forward, x, device, warmup=2, repetitions=3, compile_kwargs={"backend": "inductor"}
|
|
142
|
+
)
|
|
143
|
+
assert len(stats.samples_ms) == 3
|
|
144
|
+
assert stats.median_ms > 0
|
|
145
|
+
torch.testing.assert_close(torch.compile(forward, backend="inductor")(x), forward(x))
|