File size: 4,275 Bytes
5e23710 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | # coding: utf-8
"""axengine / onnxruntime 推理会话封装。"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import numpy as np
@dataclass(frozen=True)
class TensorInfo:
name: str
shape: tuple[int, ...]
dtype: np.dtype
def _numpy_dtype(value: Any) -> np.dtype:
text = str(value).lower()
mapping = (
(("tensor(float)", "float32", "fp32", "f32"), np.float32),
(("tensor(float16)", "float16", "fp16", "f16"), np.float16),
(("tensor(int64)", "int64", "s64"), np.int64),
(("tensor(int32)", "int32", "s32"), np.int32),
(("tensor(uint16)", "uint16", "u16"), np.uint16),
(("tensor(uint8)", "uint8", "u8"), np.uint8),
)
for aliases, dtype in mapping:
if any(alias in text for alias in aliases):
return np.dtype(dtype)
raise ValueError(f"unsupported runtime tensor dtype: {value}")
def _tensor_info(value: Any) -> TensorInfo:
shape = getattr(value, "shape", None)
if shape is None:
shape = getattr(value, "dims", None)
if shape is None or any(dim is None for dim in shape):
raise ValueError(f"dynamic or missing tensor shape for {value.name}: {shape}")
dtype = getattr(value, "dtype", None)
if dtype is None:
dtype = getattr(value, "type", None)
return TensorInfo(
name=value.name,
shape=tuple(int(dim) for dim in shape),
dtype=_numpy_dtype(dtype),
)
class InferenceSession:
""".axmodel -> axengine;.onnx -> onnxruntime CPU。"""
def __init__(self, model_path: str | Path, backend: str | None = None):
self.path = Path(model_path)
if not self.path.is_file():
raise FileNotFoundError(self.path)
if backend is None:
backend = "axengine" if self.path.suffix == ".axmodel" else "onnx"
self.backend = backend
if backend == "axengine":
try:
import axengine
except ImportError as error:
raise RuntimeError(
"axengine is unavailable; run this backend on an AXERA board"
) from error
self._session = axengine.InferenceSession(str(self.path))
elif backend == "onnx":
try:
import onnxruntime as ort
except ImportError as error:
raise RuntimeError("onnxruntime is required for --backend onnx") from error
options = ort.SessionOptions()
options.inter_op_num_threads = 1
options.intra_op_num_threads = 1
self._session = ort.InferenceSession(
str(self.path),
sess_options=options,
providers=["CPUExecutionProvider"],
)
else:
raise ValueError(f"unsupported backend: {backend}")
self.inputs = [_tensor_info(value) for value in self._session.get_inputs()]
self.outputs = [_tensor_info(value) for value in self._session.get_outputs()]
self.input_by_name = {value.name: value for value in self.inputs}
def run(self, feed: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
missing = [value.name for value in self.inputs if value.name not in feed]
if missing:
raise KeyError(f"missing inputs for {self.path.name}: {missing}")
prepared = {
name: np.ascontiguousarray(np.asarray(feed[name], dtype=meta.dtype))
for name, meta in self.input_by_name.items()
}
values = self._session.run(None, prepared)
if isinstance(values, dict):
return {name: np.asarray(value) for name, value in values.items()}
if not isinstance(values, (list, tuple)):
values = [values]
if len(values) != len(self.outputs):
raise RuntimeError(
f"unexpected output count from {self.path.name}: "
f"{len(values)} != {len(self.outputs)}"
)
return {
meta.name: np.asarray(value)
for meta, value in zip(self.outputs, values)
}
def first_output(session: InferenceSession, outputs: dict[str, np.ndarray]) -> np.ndarray:
return outputs[session.outputs[0].name]
|