HY-2012's picture
DeepFilterNet3 AXERA 量化部署包: axmodels (GRU 状态携带) + Python SDK + C++ 可执行文件
26f00f8 verified
Raw History Blame Contribute Delete
4.84 kB
"""3 个 axmodel 的 axengine 会话封装 (enc/erb_dec/df_dec 流水线)。"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import numpy as np
T = 99 # 静态时间帧 (1s @ 48kHz 流式 STFT)
@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 _AxSession:
"""单个 axmodel 的 axengine 会话 (参考 AXERA 官方推理工程封装)。"""
def __init__(self, model_path: str | Path):
import axengine
self.path = Path(model_path)
if not self.path.is_file():
raise FileNotFoundError(self.path)
self._session = axengine.InferenceSession(str(self.path))
self.inputs = [_tensor_info(v) for v in self._session.get_inputs()]
self.outputs = [_tensor_info(v) for v in self._session.get_outputs()]
self.input_by_name = {v.name: v for v in self.inputs}
def run(self, feed: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
missing = [v.name for v in self.inputs if v.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(v) for name, v 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(v) for meta, v in zip(self.outputs, values)}
class DfNet3Ax:
"""DeepFilterNet3 三子图 NPU 流水线 (与 dsp.pipeline 配合)。
enc: feat_erb(1,1,T,32)+feat_spec(1,2,T,96) -> e0..e3, emb, c0, lsnr
erb_dec: emb,e3,e2,e1,e0 -> m(1,1,T,32)
df_dec: emb,c0 -> coefs(1,T,96,10)
"""
def __init__(self, model_dir: str | Path):
model_dir = Path(model_dir)
self.enc = _AxSession(model_dir / "enc.axmodel")
self.erb_dec = _AxSession(model_dir / "erb_dec.axmodel")
self.df_dec = _AxSession(model_dir / "df_dec.axmodel")
# 状态携带模式检测: axmodel 是否有 h0 输入
self.stateful = all("h0" in s.input_by_name for s in (self.enc, self.df_dec))
def run_enc(self, feat_erb: np.ndarray, feat_spec: np.ndarray,
h: np.ndarray | None = None) -> tuple[dict[str, np.ndarray], np.ndarray | None]:
feed = {"feat_erb": feat_erb, "feat_spec": feat_spec}
if self.stateful:
if h is None:
h = np.zeros((1, 1, 256), np.float32)
feed["h0"] = h
out = self.enc.run(feed)
return out, np.asarray(out["h_out"])
return self.enc.run(feed), None
def run_erb_dec(self, emb, e3, e2, e1, e0) -> np.ndarray:
return self.erb_dec.run({"emb": emb, "e3": e3, "e2": e2, "e1": e1, "e0": e0})["m"]
def run_df_dec(self, emb, c0, h: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray | None]:
feed = {"emb": emb, "c0": c0}
if self.stateful:
if h is None:
h = np.zeros((2, 1, 256), np.float32)
feed["h0"] = h
out = self.df_dec.run(feed)
return np.asarray(out["coefs"]), np.asarray(out["h_out"])
return self.df_dec.run(feed)["coefs"], None