edgefirst-ara2 0.1.3__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.
- edgefirst_ara2-0.1.3/Cargo.toml +41 -0
- edgefirst_ara2-0.1.3/PKG-INFO +329 -0
- edgefirst_ara2-0.1.3/README.md +300 -0
- edgefirst_ara2-0.1.3/crates/ara2/Cargo.toml +35 -0
- edgefirst_ara2-0.1.3/crates/ara2/README.md +165 -0
- edgefirst_ara2-0.1.3/crates/ara2/benches/model_benchmark.rs +66 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/dvm_metadata.rs +484 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/endpoint.rs +229 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/error.rs +124 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/lib.rs +49 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/model.rs +705 -0
- edgefirst_ara2-0.1.3/crates/ara2/src/session.rs +278 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/Cargo.lock +2266 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/Cargo.toml +30 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/README.md +300 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/edgefirst_ara2.pyi +716 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/endpoint.rs +55 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/error.rs +66 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/lib.rs +66 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/metadata.rs +393 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/model.rs +466 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/session.rs +108 -0
- edgefirst_ara2-0.1.3/crates/ara2-py/src/types.rs +316 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/Cargo.toml +15 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/README.md +27 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/dv_status_codes.h +187 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/dvapi.h +1014 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/src/ffi.rs +2528 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/src/lib.rs +9 -0
- edgefirst_ara2-0.1.3/crates/ara2-sys/update.sh +3 -0
- edgefirst_ara2-0.1.3/pyproject.toml +44 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
resolver = "3"
|
|
3
|
+
members = ["crates/*"]
|
|
4
|
+
|
|
5
|
+
[workspace.package]
|
|
6
|
+
description = "Rust client library for the Kinara ARA-2 neural network accelerator on NXP i.MX platforms"
|
|
7
|
+
authors = ["Au-Zone Technologies <support@au-zone.com>"]
|
|
8
|
+
homepage = "https://edgefirst.ai"
|
|
9
|
+
repository = "https://github.com/EdgeFirstAI/ara2-rs"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
version = "0.1.3"
|
|
13
|
+
edition = "2024"
|
|
14
|
+
|
|
15
|
+
[workspace.dependencies]
|
|
16
|
+
ara2 = { version = "0.1.3", path = "crates/ara2" }
|
|
17
|
+
ara2-sys = { version = "0.1.3", path = "crates/ara2-sys" }
|
|
18
|
+
criterion = "0.7.0"
|
|
19
|
+
edgefirst-hal = "0.13.0"
|
|
20
|
+
image = { version = "0.25.8", default-features = false, features = [
|
|
21
|
+
"jpeg",
|
|
22
|
+
"png",
|
|
23
|
+
"rayon",
|
|
24
|
+
] }
|
|
25
|
+
libloading = "0.8.9"
|
|
26
|
+
log = { version = "0.4.28", features = ["release_max_level_debug"] }
|
|
27
|
+
ndarray = { version = "0.16.1", features = ["rayon"] }
|
|
28
|
+
numpy = { version = "0.24", default-features = false }
|
|
29
|
+
pyo3 = "0.24"
|
|
30
|
+
pyo3-build-config = "0.24"
|
|
31
|
+
|
|
32
|
+
[profile.release]
|
|
33
|
+
opt-level = 3
|
|
34
|
+
lto = true
|
|
35
|
+
strip = true
|
|
36
|
+
debug = false
|
|
37
|
+
|
|
38
|
+
[profile.profiling]
|
|
39
|
+
inherits = "release"
|
|
40
|
+
strip = false
|
|
41
|
+
debug = true
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edgefirst-ara2
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Rust
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Requires-Dist: numpy>=1.24
|
|
15
|
+
Requires-Dist: edgefirst-hal ; extra == 'hal'
|
|
16
|
+
Provides-Extra: hal
|
|
17
|
+
Summary: Python bindings for the ARA-2 neural accelerator client library
|
|
18
|
+
Keywords: ara2,neural-network,accelerator,inference,edgefirst
|
|
19
|
+
Home-Page: https://edgefirst.ai
|
|
20
|
+
Author-email: Au-Zone Technologies <support@au-zone.com>
|
|
21
|
+
License: Apache-2.0
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
24
|
+
Project-URL: Documentation, https://github.com/EdgeFirstAI/ara2-rs#readme
|
|
25
|
+
Project-URL: Homepage, https://edgefirst.ai
|
|
26
|
+
Project-URL: Issues, https://github.com/EdgeFirstAI/ara2-rs/issues
|
|
27
|
+
Project-URL: Repository, https://github.com/EdgeFirstAI/ara2-rs
|
|
28
|
+
|
|
29
|
+
# Python Bindings for ARA-2
|
|
30
|
+
|
|
31
|
+
Python bindings for the ARA-2 neural network accelerator client library,
|
|
32
|
+
providing efficient NPU inference from Python via a proxy service running
|
|
33
|
+
on NXP i.MX platforms with Kinara ARA-2 hardware.
|
|
34
|
+
|
|
35
|
+
Published to PyPI as [`edgefirst-ara2`](https://pypi.org/project/edgefirst-ara2/).
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Python Application ──(UNIX/TCP socket)──▶ ara2-proxy ──(PCIe)──▶ ARA-2 NPU
|
|
41
|
+
│ (system service) (Kinara hardware)
|
|
42
|
+
│
|
|
43
|
+
edgefirst-hal ──(DMA-BUF fd)──▶ GPU preprocessing (zero-copy)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Your Python code connects to the `ara2-proxy` system service (not directly
|
|
47
|
+
to the hardware). The proxy manages device access and must be running before
|
|
48
|
+
your application starts.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### From PyPI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install edgefirst-ara2
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For zero-copy preprocessing with edgefirst-hal:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install edgefirst-ara2[hal]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Prerequisites for Development
|
|
65
|
+
|
|
66
|
+
- Python 3.11 or higher
|
|
67
|
+
- Rust stable toolchain (edition 2024)
|
|
68
|
+
- maturin (`pip install maturin`)
|
|
69
|
+
- ARA-2 client library (`libaraclient.so.1`)
|
|
70
|
+
|
|
71
|
+
### Development Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd crates/ara2-py
|
|
75
|
+
maturin develop --release --features abi3
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import edgefirst_ara2
|
|
82
|
+
|
|
83
|
+
# Connect to ARA-2 proxy
|
|
84
|
+
session = edgefirst_ara2.Session.create_via_unix_socket("/var/run/ara2.sock")
|
|
85
|
+
|
|
86
|
+
# Get version information
|
|
87
|
+
versions = session.versions()
|
|
88
|
+
print(f"Proxy version: {versions['proxy']}")
|
|
89
|
+
|
|
90
|
+
# List endpoints
|
|
91
|
+
endpoints = session.list_endpoints()
|
|
92
|
+
print(f"Found {len(endpoints)} endpoints")
|
|
93
|
+
|
|
94
|
+
# Check endpoint status
|
|
95
|
+
for endpoint in endpoints:
|
|
96
|
+
state = endpoint.check_status()
|
|
97
|
+
stats = endpoint.dram_statistics()
|
|
98
|
+
print(f"State: {state}, Free DRAM: {stats.free_size / stats.dram_size * 100:.1f}%")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Inference with numpy
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import numpy as np
|
|
105
|
+
import edgefirst_ara2
|
|
106
|
+
|
|
107
|
+
session = edgefirst_ara2.Session.create_via_unix_socket("/var/run/ara2.sock")
|
|
108
|
+
endpoints = session.list_endpoints()
|
|
109
|
+
model = endpoints[0].load_model("model.dvm")
|
|
110
|
+
|
|
111
|
+
# Allocate tensors and run inference
|
|
112
|
+
model.allocate_tensors()
|
|
113
|
+
input_data = np.zeros(model.input_size(0), dtype=np.uint8)
|
|
114
|
+
model.set_input_tensor(0, input_data)
|
|
115
|
+
timing = model.run()
|
|
116
|
+
|
|
117
|
+
print(f"Inference: {timing.run_time_us} us")
|
|
118
|
+
output = model.get_output_tensor(0)
|
|
119
|
+
dequantized = model.dequantize(0)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Zero-Copy DMA-BUF Pipeline
|
|
123
|
+
|
|
124
|
+
For maximum throughput, use DMA-BUF tensors with
|
|
125
|
+
[edgefirst-hal](https://pypi.org/project/edgefirst-hal/) for GPU-accelerated
|
|
126
|
+
preprocessing. This eliminates CPU memory copies between preprocessing and
|
|
127
|
+
inference:
|
|
128
|
+
|
|
129
|
+
| Path | CPU copies | Flow |
|
|
130
|
+
|------|-----------|------|
|
|
131
|
+
| Standard (numpy) | 2 | numpy → shared memory → NPU |
|
|
132
|
+
| DMA-BUF | 0 | GPU writes directly to NPU input buffer |
|
|
133
|
+
|
|
134
|
+
**How it works:** `allocate_tensors("dma")` allocates the model's input tensor
|
|
135
|
+
in a DMA-BUF — a Linux kernel buffer accessible by multiple hardware devices.
|
|
136
|
+
`input_tensor_fd(0)` returns a file descriptor to that buffer. You pass this
|
|
137
|
+
FD to `edgefirst_hal.import_image()`, which maps it as a GPU image surface.
|
|
138
|
+
The GPU writes the preprocessed frame directly into the NPU's input buffer —
|
|
139
|
+
no CPU copies involved.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import os
|
|
143
|
+
import edgefirst_ara2 as ara2
|
|
144
|
+
import edgefirst_hal as hal
|
|
145
|
+
|
|
146
|
+
session = ara2.Session.create_via_unix_socket(ara2.DEFAULT_SOCKET)
|
|
147
|
+
endpoint = session.list_endpoints()[0]
|
|
148
|
+
|
|
149
|
+
with endpoint.load_model("yolov8s.dvm") as model:
|
|
150
|
+
model.allocate_tensors("dma") # Must use "dma" for tensor FD access
|
|
151
|
+
|
|
152
|
+
# Get DMA-BUF FD for the model's input tensor
|
|
153
|
+
input_fd = model.input_tensor_fd(0)
|
|
154
|
+
c, h, w = model.input_shape(0)
|
|
155
|
+
try:
|
|
156
|
+
# Import as PlanarRgb (CHW layout) to match ARA-2 tensor format
|
|
157
|
+
dst = hal.import_image(input_fd, w, h, hal.PixelFormat.PlanarRgb)
|
|
158
|
+
finally:
|
|
159
|
+
os.close(input_fd) # FD duplicated by import_image; close original
|
|
160
|
+
|
|
161
|
+
# GPU-accelerated convert: camera frame -> model input (zero CPU copies)
|
|
162
|
+
processor = hal.ImageProcessor()
|
|
163
|
+
src = hal.load_image("image.jpg", format=hal.PixelFormat.Rgba, mem=hal.TensorMemory.DMA)
|
|
164
|
+
processor.convert(src, dst)
|
|
165
|
+
|
|
166
|
+
# Run inference — NPU reads from the same DMA-BUF
|
|
167
|
+
timing = model.run()
|
|
168
|
+
print(f"Inference: {timing.run_time_us} us")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Performance
|
|
172
|
+
|
|
173
|
+
Benchmarked on NXP i.MX 8M Plus + ARA-2 with YOLOv8n (640x640).
|
|
174
|
+
The Python API adds minimal overhead over native Rust thanks to DMA-BUF
|
|
175
|
+
zero-copy — GPU and NPU operate on the same physical memory buffers.
|
|
176
|
+
|
|
177
|
+
| Stage | Rust | Python | Overhead |
|
|
178
|
+
|-------|------|--------|----------|
|
|
179
|
+
| GPU preprocess (RGBA → CHW) | 6.35 ms | 6.37 ms | +0.02 ms |
|
|
180
|
+
| NPU inference (wall clock) | 8.95 ms | 9.13 ms | +0.18 ms |
|
|
181
|
+
| NPU execution | 3.33 ms | 3.33 ms | — |
|
|
182
|
+
| DMA input upload | 2.21 ms | 2.20 ms | — |
|
|
183
|
+
| DMA output download | 1.96 ms | 1.96 ms | — |
|
|
184
|
+
| Postprocess (decode + NMS) | 1.41 ms | 2.53 ms | +1.12 ms |
|
|
185
|
+
| **Total pipeline** | **16.71 ms** | **18.03 ms** | **+1.32 ms** |
|
|
186
|
+
| **Throughput** | **59.9 FPS** | **55.5 FPS** | |
|
|
187
|
+
|
|
188
|
+
> Steady-state mean over 20 iterations. Python overhead is in postprocessing
|
|
189
|
+
> (numpy array marshalling). GPU preprocessing and NPU inference are identical.
|
|
190
|
+
|
|
191
|
+
Run the benchmark yourself:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
python examples/yolov8.py model.dvm image.jpg --benchmark 20
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## DVM Metadata
|
|
198
|
+
|
|
199
|
+
Read model metadata without loading onto the NPU:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import edgefirst_ara2
|
|
203
|
+
|
|
204
|
+
metadata = edgefirst_ara2.read_metadata("model.dvm")
|
|
205
|
+
if metadata:
|
|
206
|
+
print(f"Task: {metadata.task}")
|
|
207
|
+
print(f"Classes: {metadata.classes}")
|
|
208
|
+
if metadata.compilation and metadata.compilation.ppa:
|
|
209
|
+
print(f"IPS: {metadata.compilation.ppa.ips}")
|
|
210
|
+
|
|
211
|
+
labels = edgefirst_ara2.read_labels("model.dvm")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## API Reference
|
|
215
|
+
|
|
216
|
+
### Session
|
|
217
|
+
|
|
218
|
+
Connection to the ARA-2 proxy service.
|
|
219
|
+
|
|
220
|
+
**Static Methods:**
|
|
221
|
+
- `create_via_unix_socket(socket_path: str) -> Session`
|
|
222
|
+
- `create_via_tcp_ipv4_socket(ip: str, port: int) -> Session`
|
|
223
|
+
|
|
224
|
+
**Methods:**
|
|
225
|
+
- `versions() -> dict[str, str]` - Get component versions
|
|
226
|
+
- `list_endpoints() -> list[Endpoint]` - List available endpoints
|
|
227
|
+
|
|
228
|
+
**Properties:**
|
|
229
|
+
- `socket_type: str` - "unix" or "tcp"
|
|
230
|
+
|
|
231
|
+
### Endpoint
|
|
232
|
+
|
|
233
|
+
Represents an ARA-2 accelerator device.
|
|
234
|
+
|
|
235
|
+
**Methods:**
|
|
236
|
+
- `check_status() -> State` - Get device state
|
|
237
|
+
- `dram_statistics() -> DramStatistics` - Get memory usage
|
|
238
|
+
- `load_model(model_path: str) -> Model` - Load a .dvm model
|
|
239
|
+
|
|
240
|
+
### Model
|
|
241
|
+
|
|
242
|
+
Loaded neural network model.
|
|
243
|
+
|
|
244
|
+
**Lifecycle:**
|
|
245
|
+
- `allocate_tensors(memory: str | None = None)` - Allocate tensors ("dma", "shm", "mem", or None)
|
|
246
|
+
- `set_timeout_ms(timeout_ms: int)` - Set inference timeout
|
|
247
|
+
- `run() -> ModelTiming` - Execute inference
|
|
248
|
+
|
|
249
|
+
**Tensor I/O (numpy):**
|
|
250
|
+
- `set_input_tensor(index: int, data: np.ndarray)` - Copy data into input
|
|
251
|
+
- `get_output_tensor(index: int) -> np.ndarray` - Copy output data out
|
|
252
|
+
- `dequantize(index: int) -> np.ndarray` - Dequantize output to float32
|
|
253
|
+
|
|
254
|
+
**DMA-BUF Zero-Copy:**
|
|
255
|
+
- `input_tensor_fd(index: int) -> int` - Get input tensor FD
|
|
256
|
+
- `output_tensor_fd(index: int) -> int` - Get output tensor FD
|
|
257
|
+
- `input_tensor_memory(index: int) -> str` - Input memory type
|
|
258
|
+
- `output_tensor_memory(index: int) -> str` - Output memory type
|
|
259
|
+
|
|
260
|
+
**Introspection:**
|
|
261
|
+
- `n_inputs: int`, `n_outputs: int` - Tensor counts
|
|
262
|
+
- `input_shape(i) -> (C, H, W)`, `output_shape(i) -> (C, H, W)`
|
|
263
|
+
- `input_size(i) -> int`, `output_size(i) -> int` - Size in bytes
|
|
264
|
+
- `input_bpp(i) -> int`, `output_bpp(i) -> int` - Bytes per element
|
|
265
|
+
- `input_info(i) -> InputTensorInfo`, `output_info(i) -> OutputTensorInfo`
|
|
266
|
+
- `input_quants(i) -> InputQuantization`, `output_quants(i) -> OutputQuantization`
|
|
267
|
+
|
|
268
|
+
### Metadata Functions
|
|
269
|
+
|
|
270
|
+
- `read_metadata(path: str) -> DvmMetadata | None`
|
|
271
|
+
- `read_labels(path: str) -> list[str]`
|
|
272
|
+
- `has_metadata(path: str) -> bool`
|
|
273
|
+
|
|
274
|
+
### Supporting Types
|
|
275
|
+
|
|
276
|
+
- **State** (enum): Init, Idle, Active, ActiveSlow, ActiveBoosted, ThermalInactive, ThermalUnknown, Inactive, Fault
|
|
277
|
+
- **ModelOutputType** (enum): Classification, Detection, SemanticSegmentation, Raw
|
|
278
|
+
- **DramStatistics**: dram_size, free_size, model_occupancy_size, ...
|
|
279
|
+
- **ModelTiming**: run_time_us, input_time_us, output_time_us
|
|
280
|
+
- **InputQuantization**: qn, scale, mean, is_signed
|
|
281
|
+
- **OutputQuantization**: qn, scale, offset, is_signed
|
|
282
|
+
|
|
283
|
+
### Exceptions
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
Ara2Error (RuntimeError)
|
|
287
|
+
+-- LibraryError - libaraclient.so loading failures
|
|
288
|
+
+-- HardwareError - NPU faults, endpoint errors
|
|
289
|
+
+-- ProxyError - Proxy connection failures
|
|
290
|
+
+-- ModelError - Model load/inference failures
|
|
291
|
+
+-- TensorError - Tensor allocation, DMA-BUF errors
|
|
292
|
+
+-- MetadataError - DVM metadata parsing errors
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Building Wheels
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
cd crates/ara2-py
|
|
299
|
+
maturin build --release --features abi3
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Wheels are created in `target/wheels/`.
|
|
303
|
+
|
|
304
|
+
## Stable ABI
|
|
305
|
+
|
|
306
|
+
The bindings use PyO3's stable ABI (`abi3-py311`):
|
|
307
|
+
- A single wheel works across Python 3.11, 3.12, 3.13, and future versions
|
|
308
|
+
- Minimum supported Python version is 3.11
|
|
309
|
+
|
|
310
|
+
## Troubleshooting
|
|
311
|
+
|
|
312
|
+
### "libaraclient.so.1 not found"
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
export LD_LIBRARY_PATH=/path/to/ara2/lib:$LD_LIBRARY_PATH
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Verify Installation
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
python -c "import edgefirst_ara2; print(edgefirst_ara2.__version__)"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
Licensed under the Apache License 2.0.
|
|
327
|
+
|
|
328
|
+
Copyright 2025 Au-Zone Technologies. All Rights Reserved.
|
|
329
|
+
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Python Bindings for ARA-2
|
|
2
|
+
|
|
3
|
+
Python bindings for the ARA-2 neural network accelerator client library,
|
|
4
|
+
providing efficient NPU inference from Python via a proxy service running
|
|
5
|
+
on NXP i.MX platforms with Kinara ARA-2 hardware.
|
|
6
|
+
|
|
7
|
+
Published to PyPI as [`edgefirst-ara2`](https://pypi.org/project/edgefirst-ara2/).
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Python Application ──(UNIX/TCP socket)──▶ ara2-proxy ──(PCIe)──▶ ARA-2 NPU
|
|
13
|
+
│ (system service) (Kinara hardware)
|
|
14
|
+
│
|
|
15
|
+
edgefirst-hal ──(DMA-BUF fd)──▶ GPU preprocessing (zero-copy)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Your Python code connects to the `ara2-proxy` system service (not directly
|
|
19
|
+
to the hardware). The proxy manages device access and must be running before
|
|
20
|
+
your application starts.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### From PyPI
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install edgefirst-ara2
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For zero-copy preprocessing with edgefirst-hal:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install edgefirst-ara2[hal]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Prerequisites for Development
|
|
37
|
+
|
|
38
|
+
- Python 3.11 or higher
|
|
39
|
+
- Rust stable toolchain (edition 2024)
|
|
40
|
+
- maturin (`pip install maturin`)
|
|
41
|
+
- ARA-2 client library (`libaraclient.so.1`)
|
|
42
|
+
|
|
43
|
+
### Development Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd crates/ara2-py
|
|
47
|
+
maturin develop --release --features abi3
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import edgefirst_ara2
|
|
54
|
+
|
|
55
|
+
# Connect to ARA-2 proxy
|
|
56
|
+
session = edgefirst_ara2.Session.create_via_unix_socket("/var/run/ara2.sock")
|
|
57
|
+
|
|
58
|
+
# Get version information
|
|
59
|
+
versions = session.versions()
|
|
60
|
+
print(f"Proxy version: {versions['proxy']}")
|
|
61
|
+
|
|
62
|
+
# List endpoints
|
|
63
|
+
endpoints = session.list_endpoints()
|
|
64
|
+
print(f"Found {len(endpoints)} endpoints")
|
|
65
|
+
|
|
66
|
+
# Check endpoint status
|
|
67
|
+
for endpoint in endpoints:
|
|
68
|
+
state = endpoint.check_status()
|
|
69
|
+
stats = endpoint.dram_statistics()
|
|
70
|
+
print(f"State: {state}, Free DRAM: {stats.free_size / stats.dram_size * 100:.1f}%")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Inference with numpy
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import numpy as np
|
|
77
|
+
import edgefirst_ara2
|
|
78
|
+
|
|
79
|
+
session = edgefirst_ara2.Session.create_via_unix_socket("/var/run/ara2.sock")
|
|
80
|
+
endpoints = session.list_endpoints()
|
|
81
|
+
model = endpoints[0].load_model("model.dvm")
|
|
82
|
+
|
|
83
|
+
# Allocate tensors and run inference
|
|
84
|
+
model.allocate_tensors()
|
|
85
|
+
input_data = np.zeros(model.input_size(0), dtype=np.uint8)
|
|
86
|
+
model.set_input_tensor(0, input_data)
|
|
87
|
+
timing = model.run()
|
|
88
|
+
|
|
89
|
+
print(f"Inference: {timing.run_time_us} us")
|
|
90
|
+
output = model.get_output_tensor(0)
|
|
91
|
+
dequantized = model.dequantize(0)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Zero-Copy DMA-BUF Pipeline
|
|
95
|
+
|
|
96
|
+
For maximum throughput, use DMA-BUF tensors with
|
|
97
|
+
[edgefirst-hal](https://pypi.org/project/edgefirst-hal/) for GPU-accelerated
|
|
98
|
+
preprocessing. This eliminates CPU memory copies between preprocessing and
|
|
99
|
+
inference:
|
|
100
|
+
|
|
101
|
+
| Path | CPU copies | Flow |
|
|
102
|
+
|------|-----------|------|
|
|
103
|
+
| Standard (numpy) | 2 | numpy → shared memory → NPU |
|
|
104
|
+
| DMA-BUF | 0 | GPU writes directly to NPU input buffer |
|
|
105
|
+
|
|
106
|
+
**How it works:** `allocate_tensors("dma")` allocates the model's input tensor
|
|
107
|
+
in a DMA-BUF — a Linux kernel buffer accessible by multiple hardware devices.
|
|
108
|
+
`input_tensor_fd(0)` returns a file descriptor to that buffer. You pass this
|
|
109
|
+
FD to `edgefirst_hal.import_image()`, which maps it as a GPU image surface.
|
|
110
|
+
The GPU writes the preprocessed frame directly into the NPU's input buffer —
|
|
111
|
+
no CPU copies involved.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import os
|
|
115
|
+
import edgefirst_ara2 as ara2
|
|
116
|
+
import edgefirst_hal as hal
|
|
117
|
+
|
|
118
|
+
session = ara2.Session.create_via_unix_socket(ara2.DEFAULT_SOCKET)
|
|
119
|
+
endpoint = session.list_endpoints()[0]
|
|
120
|
+
|
|
121
|
+
with endpoint.load_model("yolov8s.dvm") as model:
|
|
122
|
+
model.allocate_tensors("dma") # Must use "dma" for tensor FD access
|
|
123
|
+
|
|
124
|
+
# Get DMA-BUF FD for the model's input tensor
|
|
125
|
+
input_fd = model.input_tensor_fd(0)
|
|
126
|
+
c, h, w = model.input_shape(0)
|
|
127
|
+
try:
|
|
128
|
+
# Import as PlanarRgb (CHW layout) to match ARA-2 tensor format
|
|
129
|
+
dst = hal.import_image(input_fd, w, h, hal.PixelFormat.PlanarRgb)
|
|
130
|
+
finally:
|
|
131
|
+
os.close(input_fd) # FD duplicated by import_image; close original
|
|
132
|
+
|
|
133
|
+
# GPU-accelerated convert: camera frame -> model input (zero CPU copies)
|
|
134
|
+
processor = hal.ImageProcessor()
|
|
135
|
+
src = hal.load_image("image.jpg", format=hal.PixelFormat.Rgba, mem=hal.TensorMemory.DMA)
|
|
136
|
+
processor.convert(src, dst)
|
|
137
|
+
|
|
138
|
+
# Run inference — NPU reads from the same DMA-BUF
|
|
139
|
+
timing = model.run()
|
|
140
|
+
print(f"Inference: {timing.run_time_us} us")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Performance
|
|
144
|
+
|
|
145
|
+
Benchmarked on NXP i.MX 8M Plus + ARA-2 with YOLOv8n (640x640).
|
|
146
|
+
The Python API adds minimal overhead over native Rust thanks to DMA-BUF
|
|
147
|
+
zero-copy — GPU and NPU operate on the same physical memory buffers.
|
|
148
|
+
|
|
149
|
+
| Stage | Rust | Python | Overhead |
|
|
150
|
+
|-------|------|--------|----------|
|
|
151
|
+
| GPU preprocess (RGBA → CHW) | 6.35 ms | 6.37 ms | +0.02 ms |
|
|
152
|
+
| NPU inference (wall clock) | 8.95 ms | 9.13 ms | +0.18 ms |
|
|
153
|
+
| NPU execution | 3.33 ms | 3.33 ms | — |
|
|
154
|
+
| DMA input upload | 2.21 ms | 2.20 ms | — |
|
|
155
|
+
| DMA output download | 1.96 ms | 1.96 ms | — |
|
|
156
|
+
| Postprocess (decode + NMS) | 1.41 ms | 2.53 ms | +1.12 ms |
|
|
157
|
+
| **Total pipeline** | **16.71 ms** | **18.03 ms** | **+1.32 ms** |
|
|
158
|
+
| **Throughput** | **59.9 FPS** | **55.5 FPS** | |
|
|
159
|
+
|
|
160
|
+
> Steady-state mean over 20 iterations. Python overhead is in postprocessing
|
|
161
|
+
> (numpy array marshalling). GPU preprocessing and NPU inference are identical.
|
|
162
|
+
|
|
163
|
+
Run the benchmark yourself:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python examples/yolov8.py model.dvm image.jpg --benchmark 20
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## DVM Metadata
|
|
170
|
+
|
|
171
|
+
Read model metadata without loading onto the NPU:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import edgefirst_ara2
|
|
175
|
+
|
|
176
|
+
metadata = edgefirst_ara2.read_metadata("model.dvm")
|
|
177
|
+
if metadata:
|
|
178
|
+
print(f"Task: {metadata.task}")
|
|
179
|
+
print(f"Classes: {metadata.classes}")
|
|
180
|
+
if metadata.compilation and metadata.compilation.ppa:
|
|
181
|
+
print(f"IPS: {metadata.compilation.ppa.ips}")
|
|
182
|
+
|
|
183
|
+
labels = edgefirst_ara2.read_labels("model.dvm")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## API Reference
|
|
187
|
+
|
|
188
|
+
### Session
|
|
189
|
+
|
|
190
|
+
Connection to the ARA-2 proxy service.
|
|
191
|
+
|
|
192
|
+
**Static Methods:**
|
|
193
|
+
- `create_via_unix_socket(socket_path: str) -> Session`
|
|
194
|
+
- `create_via_tcp_ipv4_socket(ip: str, port: int) -> Session`
|
|
195
|
+
|
|
196
|
+
**Methods:**
|
|
197
|
+
- `versions() -> dict[str, str]` - Get component versions
|
|
198
|
+
- `list_endpoints() -> list[Endpoint]` - List available endpoints
|
|
199
|
+
|
|
200
|
+
**Properties:**
|
|
201
|
+
- `socket_type: str` - "unix" or "tcp"
|
|
202
|
+
|
|
203
|
+
### Endpoint
|
|
204
|
+
|
|
205
|
+
Represents an ARA-2 accelerator device.
|
|
206
|
+
|
|
207
|
+
**Methods:**
|
|
208
|
+
- `check_status() -> State` - Get device state
|
|
209
|
+
- `dram_statistics() -> DramStatistics` - Get memory usage
|
|
210
|
+
- `load_model(model_path: str) -> Model` - Load a .dvm model
|
|
211
|
+
|
|
212
|
+
### Model
|
|
213
|
+
|
|
214
|
+
Loaded neural network model.
|
|
215
|
+
|
|
216
|
+
**Lifecycle:**
|
|
217
|
+
- `allocate_tensors(memory: str | None = None)` - Allocate tensors ("dma", "shm", "mem", or None)
|
|
218
|
+
- `set_timeout_ms(timeout_ms: int)` - Set inference timeout
|
|
219
|
+
- `run() -> ModelTiming` - Execute inference
|
|
220
|
+
|
|
221
|
+
**Tensor I/O (numpy):**
|
|
222
|
+
- `set_input_tensor(index: int, data: np.ndarray)` - Copy data into input
|
|
223
|
+
- `get_output_tensor(index: int) -> np.ndarray` - Copy output data out
|
|
224
|
+
- `dequantize(index: int) -> np.ndarray` - Dequantize output to float32
|
|
225
|
+
|
|
226
|
+
**DMA-BUF Zero-Copy:**
|
|
227
|
+
- `input_tensor_fd(index: int) -> int` - Get input tensor FD
|
|
228
|
+
- `output_tensor_fd(index: int) -> int` - Get output tensor FD
|
|
229
|
+
- `input_tensor_memory(index: int) -> str` - Input memory type
|
|
230
|
+
- `output_tensor_memory(index: int) -> str` - Output memory type
|
|
231
|
+
|
|
232
|
+
**Introspection:**
|
|
233
|
+
- `n_inputs: int`, `n_outputs: int` - Tensor counts
|
|
234
|
+
- `input_shape(i) -> (C, H, W)`, `output_shape(i) -> (C, H, W)`
|
|
235
|
+
- `input_size(i) -> int`, `output_size(i) -> int` - Size in bytes
|
|
236
|
+
- `input_bpp(i) -> int`, `output_bpp(i) -> int` - Bytes per element
|
|
237
|
+
- `input_info(i) -> InputTensorInfo`, `output_info(i) -> OutputTensorInfo`
|
|
238
|
+
- `input_quants(i) -> InputQuantization`, `output_quants(i) -> OutputQuantization`
|
|
239
|
+
|
|
240
|
+
### Metadata Functions
|
|
241
|
+
|
|
242
|
+
- `read_metadata(path: str) -> DvmMetadata | None`
|
|
243
|
+
- `read_labels(path: str) -> list[str]`
|
|
244
|
+
- `has_metadata(path: str) -> bool`
|
|
245
|
+
|
|
246
|
+
### Supporting Types
|
|
247
|
+
|
|
248
|
+
- **State** (enum): Init, Idle, Active, ActiveSlow, ActiveBoosted, ThermalInactive, ThermalUnknown, Inactive, Fault
|
|
249
|
+
- **ModelOutputType** (enum): Classification, Detection, SemanticSegmentation, Raw
|
|
250
|
+
- **DramStatistics**: dram_size, free_size, model_occupancy_size, ...
|
|
251
|
+
- **ModelTiming**: run_time_us, input_time_us, output_time_us
|
|
252
|
+
- **InputQuantization**: qn, scale, mean, is_signed
|
|
253
|
+
- **OutputQuantization**: qn, scale, offset, is_signed
|
|
254
|
+
|
|
255
|
+
### Exceptions
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
Ara2Error (RuntimeError)
|
|
259
|
+
+-- LibraryError - libaraclient.so loading failures
|
|
260
|
+
+-- HardwareError - NPU faults, endpoint errors
|
|
261
|
+
+-- ProxyError - Proxy connection failures
|
|
262
|
+
+-- ModelError - Model load/inference failures
|
|
263
|
+
+-- TensorError - Tensor allocation, DMA-BUF errors
|
|
264
|
+
+-- MetadataError - DVM metadata parsing errors
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Building Wheels
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd crates/ara2-py
|
|
271
|
+
maturin build --release --features abi3
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Wheels are created in `target/wheels/`.
|
|
275
|
+
|
|
276
|
+
## Stable ABI
|
|
277
|
+
|
|
278
|
+
The bindings use PyO3's stable ABI (`abi3-py311`):
|
|
279
|
+
- A single wheel works across Python 3.11, 3.12, 3.13, and future versions
|
|
280
|
+
- Minimum supported Python version is 3.11
|
|
281
|
+
|
|
282
|
+
## Troubleshooting
|
|
283
|
+
|
|
284
|
+
### "libaraclient.so.1 not found"
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
export LD_LIBRARY_PATH=/path/to/ara2/lib:$LD_LIBRARY_PATH
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Verify Installation
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
python -c "import edgefirst_ara2; print(edgefirst_ara2.__version__)"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
Licensed under the Apache License 2.0.
|
|
299
|
+
|
|
300
|
+
Copyright 2025 Au-Zone Technologies. All Rights Reserved.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ara2"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
authors.workspace = true
|
|
6
|
+
description.workspace = true
|
|
7
|
+
homepage.workspace = true
|
|
8
|
+
repository.workspace = true
|
|
9
|
+
license.workspace = true
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["neural-network", "inference", "accelerator", "ara2", "edgefirst"]
|
|
12
|
+
categories = ["hardware-support", "science", "embedded"]
|
|
13
|
+
exclude = ["target/", "*.dvm", "*.onnx"]
|
|
14
|
+
|
|
15
|
+
[features]
|
|
16
|
+
default = ["hal"]
|
|
17
|
+
hal = ["edgefirst-hal", "image"]
|
|
18
|
+
|
|
19
|
+
[dependencies]
|
|
20
|
+
ara2-sys.workspace = true
|
|
21
|
+
edgefirst-hal = { workspace = true, optional = true }
|
|
22
|
+
image = { workspace = true, optional = true }
|
|
23
|
+
libloading.workspace = true
|
|
24
|
+
log.workspace = true
|
|
25
|
+
ndarray.workspace = true
|
|
26
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
27
|
+
serde_json = "1.0"
|
|
28
|
+
zip = { version = "2.2", default-features = false }
|
|
29
|
+
|
|
30
|
+
[dev-dependencies]
|
|
31
|
+
criterion.workspace = true
|
|
32
|
+
|
|
33
|
+
[[bench]]
|
|
34
|
+
name = "model_benchmark"
|
|
35
|
+
harness = false
|