Add resnet model files
Browse files- README.md +185 -0
- prepare_model.py +425 -0
- resnet50-v1.onnx.link +1 -0
- resnet50-v1_config.yaml +40 -0
- resnet50.onnx.link +1 -0
- resnet50_config.yaml +40 -0
README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
tags:
|
| 4 |
+
- vision
|
| 5 |
+
- image-classification
|
| 6 |
+
datasets:
|
| 7 |
+
- imagenet-1k
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
<div align="center">
|
| 11 |
+
|
| 12 |
+
# ResNet for TI EdgeAI
|
| 13 |
+
|
| 14 |
+
### Deep Residual Network for Image Classification
|
| 15 |
+
|
| 16 |
+
[](https://opensource.org/licenses/Apache-2.0)
|
| 17 |
+
[](https://onnx.ai/)
|
| 18 |
+
[](https://github.com/TexasInstruments/edgeai)
|
| 19 |
+
[](http://www.image-net.org/)
|
| 20 |
+
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Overview
|
| 26 |
+
|
| 27 |
+
**ResNet-50** is a 50-layer deep convolutional neural network optimized for **Texas Instruments MPU devices**. This folder provides two production-ready ONNX variants that deliver industry-leading accuracy on ImageNet classification while maintaining efficient computation suitable for edge deployment.
|
| 28 |
+
|
| 29 |
+
This folder contains two distinct architectural generations of ResNet-50:
|
| 30 |
+
|
| 31 |
+
- **resNet50** — ResNet-50 **v1.5**, the modern de-facto standard. Used by PyTorch (`torchvision`), TensorFlow, and most current frameworks. The stride-2 downsampling in each bottleneck block is applied in the **3×3 convolution** rather than the 1×1, which improves accuracy with no added parameters. This is the version most practitioners encounter today.
|
| 32 |
+
|
| 33 |
+
- **resnet50-v1** — ResNet-50 **v1**, the original architecture from He et al. (2016) as published in the ONNX Model Zoo (opset 7). Stride-2 is applied in the **1×1 convolution**. Useful when strict reproducibility with the original paper or ONNX Model Zoo benchmarks is required.
|
| 34 |
+
|
| 35 |
+
The two variants differ only in where the stride-2 downsampling is placed inside each bottleneck block: moving the stride to the 3×3 conv (v1.5) preserves more spatial information before downsampling, which accounts for the ~1.2% accuracy gain over v1 at zero extra cost in parameters or FLOPs.
|
| 36 |
+
|
| 37 |
+
> **Which should I use?** For new projects, prefer **resNet50 (v1.5)** — it is more accurate and is the implementation underlying most pre-trained weights available today. Use **resnet50-v1** when you need exact compatibility with the original ONNX Model Zoo model or are comparing against v1 benchmarks.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Model Variants
|
| 42 |
+
|
| 43 |
+
| Model | Architecture | Params | Top-1 Accuracy | Validated Devices | Config |
|
| 44 |
+
|-------|--------------|--------|-----------------|--------------------|--------|
|
| 45 |
+
| `resNet50` | ResNet-50 v1.5 (stride-2 in 3×3 conv) | ~25.6M | 76.15% | TDA4VH, TDA4VL, TDA4AEN | [resnet50_config.yaml](resnet50_config.yaml) |
|
| 46 |
+
| `resnet50-v1` | ResNet-50 v1, original (stride-2 in 1×1 conv) | ~25.6M | 74.93% | TDA4VH, TDA4VL, TDA4AEN | [resnet50-v1_config.yaml](resnet50-v1_config.yaml) |
|
| 47 |
+
|
| 48 |
+
**Recommended for edge deployment:** `resNet50` (v1.5) — highest accuracy with the same compute cost (4.1 GigaMACs) as the original v1.
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Quick Start
|
| 53 |
+
|
| 54 |
+
### Prerequisites
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
pip install onnx>=1.22.0 onnxruntime>=1.23.2
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Export the Model
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
# Download and prepare the default model (resNet50, v1.5)
|
| 64 |
+
python prepare_model.py
|
| 65 |
+
|
| 66 |
+
# Download and prepare a specific variant via its .link file
|
| 67 |
+
python prepare_model.py --link-file resnet50-v1.onnx.link
|
| 68 |
+
|
| 69 |
+
# Skip download and only fix shapes on an already-downloaded model
|
| 70 |
+
python prepare_model.py --link-file resnet50.onnx.link --skip-download
|
| 71 |
+
|
| 72 |
+
# Use a custom input resolution
|
| 73 |
+
python prepare_model.py --link-file resnet50.onnx.link --height 256 --width 256
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
The script automatically:
|
| 77 |
+
- Parses the `.link` file to get the download URL and output filename
|
| 78 |
+
- Downloads the ONNX model from HuggingFace (unless `--skip-download` is set)
|
| 79 |
+
- Fixes dynamic input shapes to a static shape (default `[1, 3, 224, 224]`)
|
| 80 |
+
- Runs ONNX shape inference and optional `onnx-simplifier` optimization
|
| 81 |
+
- Validates the resulting model and confirms all shapes are fixed
|
| 82 |
+
|
| 83 |
+
### Compile and Infer uing edgeai-tidlrunner
|
| 84 |
+
|
| 85 |
+
> **Note:** Run the commands below from inside the `tidlrunner` directory (the cloned [edgeai-tidlrunner](https://github.com/TexasInstruments/edgeai-tidlrunner) repository), with `--config_path` pointing to this model's config file.
|
| 86 |
+
|
| 87 |
+
**Compile using edgeai-tidlrunner - on PC**
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
cd /path/to/edgeai-tidlrunner
|
| 91 |
+
tidlrunner-cli compile --target_device J784S4 \
|
| 92 |
+
--config_path /path/to/resnet50_config.yaml
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
**Run Inference Benchmark - on device**
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
cd /path/to/edgeai-tidlrunner
|
| 99 |
+
tidlrunner-cli infer --target_device J784S4 \
|
| 100 |
+
--config_path /path/to/resnet50_config.yaml
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
### Compile and Infer using edgeai-tidl-tools (Advanced):
|
| 104 |
+
|
| 105 |
+
Follow the instructions at https://github.com/TexasInstruments/edgeai-tidl-tools
|
| 106 |
+
|
| 107 |
+
### Deploy using edgeai-tidl-tools:
|
| 108 |
+
|
| 109 |
+
Deplyment can be done using **[edgeai-tidl-tools](https://github.com/TexasInstruments/edgeai-tidl-tools)**. For ONNX models, onnxruntime-tidl with TIDL acceleration can be used. Consult the documentation of edgeai-tidl-tools for more details.
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Citation
|
| 114 |
+
|
| 115 |
+
If you use this model, please cite:
|
| 116 |
+
|
| 117 |
+
```bibtex
|
| 118 |
+
@inproceedings{he2016deep,
|
| 119 |
+
title={Deep residual learning for image recognition},
|
| 120 |
+
author={He, Kaiming and Zhang, Xiangyu and Ren, Shaoqing and Sun, Jian},
|
| 121 |
+
booktitle={Proceedings of the IEEE conference on computer vision
|
| 122 |
+
and pattern recognition},
|
| 123 |
+
pages={770--778},
|
| 124 |
+
year={2016}
|
| 125 |
+
}
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## 🔗 Resources
|
| 131 |
+
|
| 132 |
+
| Resource | Link |
|
| 133 |
+
|----------|------|
|
| 134 |
+
| **Paper** | [arXiv:1512.03385](https://arxiv.org/abs/1512.03385) |
|
| 135 |
+
| **Source (resNet50)** | [onnx-community/resnet-50-ONNX](https://huggingface.co/onnx-community/resnet-50-ONNX) |
|
| 136 |
+
| **Source (resnet50-v1)** | [onnxmodelzoo/resnet50-v1-7](https://huggingface.co/onnxmodelzoo/resnet50-v1-7) |
|
| 137 |
+
| **edgeai-tidl-tools** | [GitHub](https://github.com/TexasInstruments/edgeai-tidl-tools) |
|
| 138 |
+
| **edgeai-tidlrunner** | [GitHub](https://github.com/TexasInstruments/edgeai-tidlrunner) |
|
| 139 |
+
| **EdgeAI SDK** | [Documentation](https://github.com/TexasInstruments/edgeai/blob/main/edgeai-mpu/readme_sdk.md) |
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Related Models
|
| 144 |
+
|
| 145 |
+
<table>
|
| 146 |
+
<tr>
|
| 147 |
+
<td align="center">
|
| 148 |
+
|
| 149 |
+
**MobileNetV3**
|
| 150 |
+
Mobile-optimized CNN
|
| 151 |
+
Lighter alternative
|
| 152 |
+
|
| 153 |
+
</td>
|
| 154 |
+
<td align="center">
|
| 155 |
+
|
| 156 |
+
**ConvNeXt**
|
| 157 |
+
Modern CNN successor
|
| 158 |
+
Higher accuracy
|
| 159 |
+
|
| 160 |
+
</td>
|
| 161 |
+
<td align="center">
|
| 162 |
+
|
| 163 |
+
**DINO (ResNet-50)**
|
| 164 |
+
Self-supervised ResNet
|
| 165 |
+
No-label pre-training
|
| 166 |
+
|
| 167 |
+
</td>
|
| 168 |
+
<td align="center">
|
| 169 |
+
|
| 170 |
+
**ViT**
|
| 171 |
+
Vision Transformer
|
| 172 |
+
Attention-based backbone
|
| 173 |
+
|
| 174 |
+
</td>
|
| 175 |
+
</tr>
|
| 176 |
+
</table>
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
<div align="center">
|
| 181 |
+
|
| 182 |
+
**Maintained by:** Texas Instruments EdgeAI Team
|
| 183 |
+
**Last Updated:** August 2026
|
| 184 |
+
|
| 185 |
+
</div>
|
prepare_model.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Unified script to download ONNX models and fix their shapes for hardware deployment.
|
| 4 |
+
|
| 5 |
+
Reads .link files containing download URLs and automatically:
|
| 6 |
+
1. Downloads the model if not present
|
| 7 |
+
2. Fixes dynamic shapes to static shapes
|
| 8 |
+
3. Validates the result
|
| 9 |
+
|
| 10 |
+
Link file format:
|
| 11 |
+
<download_url> -o <output_filename>
|
| 12 |
+
|
| 13 |
+
Example:
|
| 14 |
+
https://huggingface.co/.../model.onnx -o resnet50.onnx
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import sys
|
| 18 |
+
import subprocess
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _ensure_dependencies():
|
| 23 |
+
required = {"onnx": "onnx", "onnxsim": "onnx-simplifier"}
|
| 24 |
+
for module, package in required.items():
|
| 25 |
+
try:
|
| 26 |
+
__import__(module)
|
| 27 |
+
except ImportError:
|
| 28 |
+
print(f"Installing missing dependency: {package}")
|
| 29 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
_ensure_dependencies()
|
| 33 |
+
|
| 34 |
+
import onnx
|
| 35 |
+
from onnx import shape_inference
|
| 36 |
+
import argparse
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def parse_link_file(link_file_path):
|
| 40 |
+
"""
|
| 41 |
+
Parse .link file to extract download URL and output filename.
|
| 42 |
+
|
| 43 |
+
Expected format: <URL> -o <filename>
|
| 44 |
+
|
| 45 |
+
Returns:
|
| 46 |
+
tuple: (download_url, output_filename) or (None, None) if parsing fails
|
| 47 |
+
"""
|
| 48 |
+
try:
|
| 49 |
+
with open(link_file_path, 'r') as f:
|
| 50 |
+
content = f.read().strip()
|
| 51 |
+
|
| 52 |
+
# Find the line with the URL and -o flag
|
| 53 |
+
for line in content.split('\n'):
|
| 54 |
+
line = line.strip()
|
| 55 |
+
if not line or line.startswith('#'):
|
| 56 |
+
continue
|
| 57 |
+
|
| 58 |
+
# Look for pattern: URL -o filename
|
| 59 |
+
if '-o' in line:
|
| 60 |
+
parts = line.split('-o')
|
| 61 |
+
if len(parts) == 2:
|
| 62 |
+
url = parts[0].strip()
|
| 63 |
+
filename = parts[1].strip()
|
| 64 |
+
|
| 65 |
+
# Convert HuggingFace blob URLs to resolve URLs for direct download
|
| 66 |
+
if 'huggingface.co' in url and '/blob/' in url:
|
| 67 |
+
url = url.replace('/blob/', '/resolve/')
|
| 68 |
+
|
| 69 |
+
return url, filename
|
| 70 |
+
|
| 71 |
+
print(f"Error: Could not parse link file format. Expected: <URL> -o <filename>")
|
| 72 |
+
return None, None
|
| 73 |
+
|
| 74 |
+
except Exception as e:
|
| 75 |
+
print(f"Error reading link file: {e}")
|
| 76 |
+
return None, None
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def download_model(url, output_path, force=False):
|
| 80 |
+
"""
|
| 81 |
+
Download model from URL using curl.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
url: Download URL
|
| 85 |
+
output_path: Path to save downloaded model
|
| 86 |
+
force: Force re-download even if file exists
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
bool: True if successful, False otherwise
|
| 90 |
+
"""
|
| 91 |
+
if output_path.exists() and not force:
|
| 92 |
+
print(f"Model already exists: {output_path}")
|
| 93 |
+
file_size = output_path.stat().st_size
|
| 94 |
+
print(f"File size: {file_size:,} bytes ({file_size / 1024 / 1024:.2f} MB)")
|
| 95 |
+
return True
|
| 96 |
+
|
| 97 |
+
print(f"\n📥 Downloading Model:")
|
| 98 |
+
print("-" * 80)
|
| 99 |
+
print(f"URL: {url}")
|
| 100 |
+
print(f"Output: {output_path}")
|
| 101 |
+
print()
|
| 102 |
+
|
| 103 |
+
try:
|
| 104 |
+
# Use curl to download with progress bar
|
| 105 |
+
result = subprocess.run(
|
| 106 |
+
['curl', '-L', url, '-o', str(output_path), '--progress-bar'],
|
| 107 |
+
check=True,
|
| 108 |
+
capture_output=False
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
if output_path.exists():
|
| 112 |
+
file_size = output_path.stat().st_size
|
| 113 |
+
print(f"\n✓ Download completed successfully!")
|
| 114 |
+
print(f"✓ File size: {file_size:,} bytes ({file_size / 1024 / 1024:.2f} MB)")
|
| 115 |
+
return True
|
| 116 |
+
else:
|
| 117 |
+
print(f"\n✗ Download failed: output file not created")
|
| 118 |
+
return False
|
| 119 |
+
|
| 120 |
+
except subprocess.CalledProcessError as e:
|
| 121 |
+
print(f"\n✗ Download failed: {e}")
|
| 122 |
+
return False
|
| 123 |
+
except FileNotFoundError:
|
| 124 |
+
print(f"\n✗ curl not found. Please install curl.")
|
| 125 |
+
return False
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def fix_model_shape(model_path, output_path, batch_size=1, channels=3, height=224, width=224, use_simplifier=True):
|
| 129 |
+
"""
|
| 130 |
+
Convert dynamic ONNX model input shape to fixed shape in all layers.
|
| 131 |
+
|
| 132 |
+
Uses ONNX shape inference to propagate fixed shapes through all intermediate layers.
|
| 133 |
+
Optionally uses onnxsim for additional simplification and optimization.
|
| 134 |
+
"""
|
| 135 |
+
print(f"\n🔧 Fixing Model Shapes:")
|
| 136 |
+
print("=" * 80)
|
| 137 |
+
print(f"Input model: {model_path}")
|
| 138 |
+
print(f"Output model: {output_path}")
|
| 139 |
+
|
| 140 |
+
# Load model
|
| 141 |
+
print(f"\nLoading model...")
|
| 142 |
+
model = onnx.load(str(model_path))
|
| 143 |
+
|
| 144 |
+
# Get the first input (skip initializers)
|
| 145 |
+
graph = model.graph
|
| 146 |
+
input_tensor = None
|
| 147 |
+
for inp in graph.input:
|
| 148 |
+
if any(init.name == inp.name for init in graph.initializer):
|
| 149 |
+
continue
|
| 150 |
+
input_tensor = inp
|
| 151 |
+
break
|
| 152 |
+
|
| 153 |
+
if input_tensor is None:
|
| 154 |
+
print("✗ Error: No input tensor found!")
|
| 155 |
+
return False
|
| 156 |
+
|
| 157 |
+
# Print original shape
|
| 158 |
+
print(f"\n📋 Original Shape:")
|
| 159 |
+
print("-" * 80)
|
| 160 |
+
print(f"Input name: {input_tensor.name}")
|
| 161 |
+
original_shape = []
|
| 162 |
+
for dim in input_tensor.type.tensor_type.shape.dim:
|
| 163 |
+
if dim.dim_value:
|
| 164 |
+
original_shape.append(str(dim.dim_value))
|
| 165 |
+
elif dim.dim_param:
|
| 166 |
+
original_shape.append(f"'{dim.dim_param}'")
|
| 167 |
+
else:
|
| 168 |
+
original_shape.append("?")
|
| 169 |
+
print(f"Shape: [{', '.join(original_shape)}]")
|
| 170 |
+
|
| 171 |
+
# Modify input shape to fixed dimensions
|
| 172 |
+
print(f"\n🔧 Setting Fixed Shape:")
|
| 173 |
+
print("-" * 80)
|
| 174 |
+
new_shape = [batch_size, channels, height, width]
|
| 175 |
+
print(f"New shape: {new_shape}")
|
| 176 |
+
print(f"Format: [batch_size, channels, height, width]")
|
| 177 |
+
|
| 178 |
+
# Clear existing dimensions and add new fixed dimensions
|
| 179 |
+
input_tensor.type.tensor_type.shape.ClearField('dim')
|
| 180 |
+
for dim_value in new_shape:
|
| 181 |
+
dim = input_tensor.type.tensor_type.shape.dim.add()
|
| 182 |
+
dim.dim_value = dim_value
|
| 183 |
+
|
| 184 |
+
# Run shape inference
|
| 185 |
+
print(f"\n🔄 Running Shape Inference:")
|
| 186 |
+
print("-" * 80)
|
| 187 |
+
try:
|
| 188 |
+
model = shape_inference.infer_shapes(model)
|
| 189 |
+
value_info_count = len(model.graph.value_info)
|
| 190 |
+
print(f"✓ Propagated shapes through {value_info_count} intermediate tensors")
|
| 191 |
+
except Exception as e:
|
| 192 |
+
print(f"⚠ Warning: Shape inference issue: {e}")
|
| 193 |
+
print(" Continuing with partial inference...")
|
| 194 |
+
|
| 195 |
+
# Validate model
|
| 196 |
+
print(f"\n✅ Validating Model:")
|
| 197 |
+
print("-" * 80)
|
| 198 |
+
try:
|
| 199 |
+
onnx.checker.check_model(model)
|
| 200 |
+
print("✓ Model validation passed")
|
| 201 |
+
except Exception as e:
|
| 202 |
+
print(f"✗ Model validation failed: {e}")
|
| 203 |
+
return False
|
| 204 |
+
|
| 205 |
+
# Optional: Use onnx-simplifier
|
| 206 |
+
if use_simplifier:
|
| 207 |
+
print(f"\n🚀 Running ONNX Simplifier:")
|
| 208 |
+
print("-" * 80)
|
| 209 |
+
try:
|
| 210 |
+
import onnxsim
|
| 211 |
+
model_simplified, check = onnxsim.simplify(
|
| 212 |
+
model,
|
| 213 |
+
check_n=3,
|
| 214 |
+
perform_optimization=True,
|
| 215 |
+
skip_fuse_bn=False,
|
| 216 |
+
overwrite_input_shapes={input_tensor.name: new_shape}
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
if check:
|
| 220 |
+
print("✓ Model simplified and optimized")
|
| 221 |
+
model = model_simplified
|
| 222 |
+
|
| 223 |
+
# Report node reduction if any
|
| 224 |
+
original_nodes = len(graph.node)
|
| 225 |
+
simplified_nodes = len(model.graph.node)
|
| 226 |
+
if simplified_nodes < original_nodes:
|
| 227 |
+
print(f"✓ Reduced nodes: {original_nodes} → {simplified_nodes}")
|
| 228 |
+
else:
|
| 229 |
+
print("⚠ Simplification validation failed, using non-simplified version")
|
| 230 |
+
|
| 231 |
+
except ImportError:
|
| 232 |
+
print("⚠ onnx-simplifier not installed, skipping")
|
| 233 |
+
print(" Install with: pip install onnx-simplifier")
|
| 234 |
+
except Exception as e:
|
| 235 |
+
print(f"⚠ Simplification failed: {e}")
|
| 236 |
+
print(" Continuing with non-simplified model")
|
| 237 |
+
|
| 238 |
+
# Save the modified model
|
| 239 |
+
print(f"\n💾 Saving Fixed Model:")
|
| 240 |
+
print("-" * 80)
|
| 241 |
+
onnx.save(model, str(output_path))
|
| 242 |
+
output_size = output_path.stat().st_size
|
| 243 |
+
print(f"✓ Saved to: {output_path}")
|
| 244 |
+
print(f"✓ File size: {output_size:,} bytes ({output_size / 1024 / 1024:.2f} MB)")
|
| 245 |
+
|
| 246 |
+
# Final verification
|
| 247 |
+
print(f"\n🔍 Final Verification:")
|
| 248 |
+
print("-" * 80)
|
| 249 |
+
try:
|
| 250 |
+
verified_model = onnx.load(str(output_path))
|
| 251 |
+
onnx.checker.check_model(verified_model)
|
| 252 |
+
|
| 253 |
+
# Check input shape
|
| 254 |
+
verified_graph = verified_model.graph
|
| 255 |
+
for inp in verified_graph.input:
|
| 256 |
+
if any(init.name == inp.name for init in verified_graph.initializer):
|
| 257 |
+
continue
|
| 258 |
+
shape = [dim.dim_value for dim in inp.type.tensor_type.shape.dim]
|
| 259 |
+
all_fixed = all(isinstance(s, int) and s > 0 for s in shape)
|
| 260 |
+
if all_fixed:
|
| 261 |
+
print(f"✓ Input '{inp.name}': {shape}")
|
| 262 |
+
else:
|
| 263 |
+
print(f"⚠ Input '{inp.name}' has dynamic dimensions")
|
| 264 |
+
|
| 265 |
+
# Check intermediate tensors
|
| 266 |
+
if verified_graph.value_info:
|
| 267 |
+
fixed_count = sum(
|
| 268 |
+
1 for vi in verified_graph.value_info
|
| 269 |
+
if all(dim.dim_value > 0 for dim in vi.type.tensor_type.shape.dim)
|
| 270 |
+
)
|
| 271 |
+
total_count = len(verified_graph.value_info)
|
| 272 |
+
print(f"✓ Fixed shapes: {fixed_count}/{total_count} intermediate tensors")
|
| 273 |
+
|
| 274 |
+
print(f"\n✨ Success! Fixed model ready for deployment")
|
| 275 |
+
return True
|
| 276 |
+
|
| 277 |
+
except Exception as e:
|
| 278 |
+
print(f"✗ Final verification failed: {e}")
|
| 279 |
+
return False
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def main():
|
| 283 |
+
parser = argparse.ArgumentParser(
|
| 284 |
+
description='Download ONNX model and fix shapes for hardware deployment',
|
| 285 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 286 |
+
epilog="""
|
| 287 |
+
Examples:
|
| 288 |
+
# Use default .link file and settings
|
| 289 |
+
%(prog)s
|
| 290 |
+
|
| 291 |
+
# Specify custom .link file
|
| 292 |
+
%(prog)s --link-file model.onnx.link
|
| 293 |
+
|
| 294 |
+
# Custom shape dimensions
|
| 295 |
+
%(prog)s --batch-size 4 --height 256 --width 256
|
| 296 |
+
|
| 297 |
+
# Force re-download
|
| 298 |
+
%(prog)s --force-download
|
| 299 |
+
|
| 300 |
+
# Skip model simplification (faster)
|
| 301 |
+
%(prog)s --no-simplifier
|
| 302 |
+
|
| 303 |
+
# Skip download (fix existing model only)
|
| 304 |
+
%(prog)s --skip-download
|
| 305 |
+
|
| 306 |
+
# Keep intermediate downloaded file
|
| 307 |
+
%(prog)s --keep-intermediate
|
| 308 |
+
"""
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
parser.add_argument('--link-file', type=str, default='resnet50.onnx.link',
|
| 312 |
+
help='Link file containing download URL (default: resnet50.onnx.link)')
|
| 313 |
+
parser.add_argument('--batch-size', type=int, default=1,
|
| 314 |
+
help='Fixed batch size (default: 1)')
|
| 315 |
+
parser.add_argument('--channels', type=int, default=3,
|
| 316 |
+
help='Number of channels (default: 3)')
|
| 317 |
+
parser.add_argument('--height', type=int, default=224,
|
| 318 |
+
help='Image height (default: 224)')
|
| 319 |
+
parser.add_argument('--width', type=int, default=224,
|
| 320 |
+
help='Image width (default: 224)')
|
| 321 |
+
parser.add_argument('--force-download', action='store_true',
|
| 322 |
+
help='Force re-download even if model exists')
|
| 323 |
+
parser.add_argument('--skip-download', action='store_true',
|
| 324 |
+
help='Skip download, only fix existing model')
|
| 325 |
+
parser.add_argument('--no-simplifier', action='store_true',
|
| 326 |
+
help='Skip onnx-simplifier optimization')
|
| 327 |
+
parser.add_argument('--keep-intermediate', action='store_true',
|
| 328 |
+
help='Keep intermediate downloaded file (not cleaned up)')
|
| 329 |
+
|
| 330 |
+
args = parser.parse_args()
|
| 331 |
+
|
| 332 |
+
# Resolve paths
|
| 333 |
+
script_dir = Path(__file__).parent
|
| 334 |
+
link_file = script_dir / args.link_file
|
| 335 |
+
|
| 336 |
+
if not link_file.exists():
|
| 337 |
+
print(f"Error: Link file not found: {link_file}")
|
| 338 |
+
print(f"Expected format in link file: <URL> -o <filename>")
|
| 339 |
+
sys.exit(1)
|
| 340 |
+
|
| 341 |
+
# Parse link file
|
| 342 |
+
print("📄 Parsing Link File:")
|
| 343 |
+
print("=" * 80)
|
| 344 |
+
print(f"Link file: {link_file}")
|
| 345 |
+
|
| 346 |
+
download_url, model_filename = parse_link_file(link_file)
|
| 347 |
+
|
| 348 |
+
if download_url is None or model_filename is None:
|
| 349 |
+
sys.exit(1)
|
| 350 |
+
|
| 351 |
+
print(f"Download URL: {download_url}")
|
| 352 |
+
print(f"Model name (from .link file): {model_filename}")
|
| 353 |
+
|
| 354 |
+
# The final output uses the name from .link file
|
| 355 |
+
final_output = script_dir / model_filename
|
| 356 |
+
|
| 357 |
+
# Use temporary name for intermediate download (will be cleaned up)
|
| 358 |
+
temp_download = script_dir / f".tmp_{model_filename}"
|
| 359 |
+
|
| 360 |
+
print(f"Final output model: {final_output.name}")
|
| 361 |
+
|
| 362 |
+
# Determine which file to use as source for shape fixing
|
| 363 |
+
if args.skip_download:
|
| 364 |
+
# User wants to fix existing model, use it directly if it exists
|
| 365 |
+
if not final_output.exists():
|
| 366 |
+
print(f"\n✗ Error: Model file not found: {final_output}")
|
| 367 |
+
print(f" Run without --skip-download to download it first.")
|
| 368 |
+
sys.exit(1)
|
| 369 |
+
model_path = final_output
|
| 370 |
+
print(f"Using existing model: {model_path.name}")
|
| 371 |
+
else:
|
| 372 |
+
# Download to temporary location
|
| 373 |
+
model_path = temp_download
|
| 374 |
+
|
| 375 |
+
# Step 1: Download model (unless skipped)
|
| 376 |
+
if not args.skip_download:
|
| 377 |
+
success = download_model(download_url, model_path, force=args.force_download)
|
| 378 |
+
if not success:
|
| 379 |
+
print("\n✗ Download failed, aborting.")
|
| 380 |
+
# Clean up temporary file if download failed
|
| 381 |
+
if model_path.exists():
|
| 382 |
+
model_path.unlink()
|
| 383 |
+
sys.exit(1)
|
| 384 |
+
|
| 385 |
+
# Step 2: Fix shapes (output directly to final location)
|
| 386 |
+
success = fix_model_shape(
|
| 387 |
+
model_path,
|
| 388 |
+
final_output,
|
| 389 |
+
batch_size=args.batch_size,
|
| 390 |
+
channels=args.channels,
|
| 391 |
+
height=args.height,
|
| 392 |
+
width=args.width,
|
| 393 |
+
use_simplifier=not args.no_simplifier
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
if success:
|
| 397 |
+
# Step 3: Clean up intermediate file (unless --keep-intermediate)
|
| 398 |
+
if not args.skip_download and model_path != final_output:
|
| 399 |
+
if args.keep_intermediate:
|
| 400 |
+
print(f"\n📦 Keeping intermediate file: {model_path.name}")
|
| 401 |
+
else:
|
| 402 |
+
print(f"\n🗑️ Cleaning up intermediate file...")
|
| 403 |
+
try:
|
| 404 |
+
model_path.unlink()
|
| 405 |
+
print(f"✓ Removed: {model_path.name}")
|
| 406 |
+
except Exception as e:
|
| 407 |
+
print(f"⚠ Could not remove intermediate file: {e}")
|
| 408 |
+
|
| 409 |
+
print("\n" + "=" * 80)
|
| 410 |
+
print("✅ COMPLETE!")
|
| 411 |
+
print("=" * 80)
|
| 412 |
+
print(f"Final model: {final_output.name}")
|
| 413 |
+
print(f"Location: {script_dir}")
|
| 414 |
+
print(f"Input shape: [{args.batch_size}, {args.channels}, {args.height}, {args.width}]")
|
| 415 |
+
sys.exit(0)
|
| 416 |
+
else:
|
| 417 |
+
print("\n✗ Shape fixing failed")
|
| 418 |
+
# Clean up temporary file on failure
|
| 419 |
+
if not args.skip_download and model_path.exists() and model_path != final_output:
|
| 420 |
+
model_path.unlink()
|
| 421 |
+
sys.exit(1)
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
if __name__ == "__main__":
|
| 425 |
+
main()
|
resnet50-v1.onnx.link
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
https://huggingface.co/onnxmodelzoo/resnet50-v1-7/blob/main/resnet50-v1-7.onnx -o resnet50-v1.onnx
|
resnet50-v1_config.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
task_type: classification
|
| 2 |
+
#dataset_category: imagenet
|
| 3 |
+
#calibration_dataset: imagenet
|
| 4 |
+
#input_dataset: imagenet
|
| 5 |
+
dataloader:
|
| 6 |
+
name: image_classification_dataloader
|
| 7 |
+
path: ./data/datasets/imagenetv2c/val
|
| 8 |
+
postprocess: {}
|
| 9 |
+
preprocess:
|
| 10 |
+
resize: 256
|
| 11 |
+
crop: 224
|
| 12 |
+
data_layout: NCHW
|
| 13 |
+
reverse_channels: false
|
| 14 |
+
backend: pil
|
| 15 |
+
interpolation: null
|
| 16 |
+
resize_with_pad: false
|
| 17 |
+
pad_color: 0
|
| 18 |
+
session:
|
| 19 |
+
session_name: onnxrt
|
| 20 |
+
target_device: null
|
| 21 |
+
input_optimization: false
|
| 22 |
+
input_data_layout: NCHW
|
| 23 |
+
input_mean:
|
| 24 |
+
- 123.675
|
| 25 |
+
- 116.28
|
| 26 |
+
- 103.53
|
| 27 |
+
input_scale:
|
| 28 |
+
- 0.017125
|
| 29 |
+
- 0.017507
|
| 30 |
+
- 0.017429
|
| 31 |
+
model_path: resnet50-v1.onnx
|
| 32 |
+
model_id: cl-mh6001
|
| 33 |
+
input_details: null
|
| 34 |
+
output_details: null
|
| 35 |
+
num_inputs: 1
|
| 36 |
+
model_info:
|
| 37 |
+
metric_reference:
|
| 38 |
+
accuracy_top1%: 74.93
|
| 39 |
+
compact_name: resnet50-v1
|
| 40 |
+
shortlisted: true
|
resnet50.onnx.link
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
https://huggingface.co/onnx-community/resnet-50-ONNX/blob/main/onnx/model.onnx -o resnet50.onnx
|
resnet50_config.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
task_type: classification
|
| 2 |
+
#dataset_category: imagenet
|
| 3 |
+
#calibration_dataset: imagenet
|
| 4 |
+
#input_dataset: imagenet
|
| 5 |
+
dataloader:
|
| 6 |
+
name: image_classification_dataloader
|
| 7 |
+
path: ./data/datasets/imagenetv2c/val
|
| 8 |
+
postprocess: {}
|
| 9 |
+
preprocess:
|
| 10 |
+
resize: 256
|
| 11 |
+
crop: 224
|
| 12 |
+
data_layout: NCHW
|
| 13 |
+
reverse_channels: false
|
| 14 |
+
backend: pil
|
| 15 |
+
interpolation: null
|
| 16 |
+
resize_with_pad: false
|
| 17 |
+
pad_color: 0
|
| 18 |
+
session:
|
| 19 |
+
session_name: onnxrt
|
| 20 |
+
target_device: null
|
| 21 |
+
input_optimization: false
|
| 22 |
+
input_data_layout: NCHW
|
| 23 |
+
input_mean:
|
| 24 |
+
- 123.675
|
| 25 |
+
- 116.28
|
| 26 |
+
- 103.53
|
| 27 |
+
input_scale:
|
| 28 |
+
- 0.017125
|
| 29 |
+
- 0.017507
|
| 30 |
+
- 0.017429
|
| 31 |
+
model_path: resnet50.onnx
|
| 32 |
+
model_id: cl-mh6000
|
| 33 |
+
input_details: null
|
| 34 |
+
output_details: null
|
| 35 |
+
num_inputs: 1
|
| 36 |
+
model_info:
|
| 37 |
+
metric_reference:
|
| 38 |
+
accuracy_top1%: 76.15
|
| 39 |
+
compact_name: resNet50
|
| 40 |
+
shortlisted: true
|