2026-08-04 22:04:18 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""推理后端工厂:按配置选择后端实现(可插拔适配层)。
|
|
|
|
|
|
|
|
|
|
|
|
PRD 5.6「配置点:资源配额、推理后端选择、灰度发布策略」——
|
|
|
|
|
|
`backend: gpu|npu` 即推理后端选择;业务代码只调用 `build_backend()`
|
|
|
|
|
|
返回的接口对象,不感知具体硬件。
|
|
|
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
from inference_backend.base import InferenceBackend
|
|
|
|
|
|
from inference_backend.gpu_backend import NvidiaGpuBackend
|
|
|
|
|
|
from inference_backend.npu_backend import AscendNpuBackend
|
|
|
|
|
|
|
|
|
|
|
|
#: 可插拔后端注册表:配置名 -> 实现类。新增硬件只需注册新类。
|
|
|
|
|
|
BACKEND_REGISTRY = {
|
|
|
|
|
|
"gpu": NvidiaGpuBackend, # NVIDIA 5090(Triton/ONNX)
|
|
|
|
|
|
"npu": AscendNpuBackend, # 华为昇腾(ACL/CANN)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_backend(config: dict) -> InferenceBackend:
|
|
|
|
|
|
"""依据配置构建推理后端实例(切换后端仅改配置,业务代码零改动)。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config: 后端配置字典(见 config/backends.template.yaml),
|
|
|
|
|
|
至少包含 ``backend`` 键(gpu | npu)。
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
实现了 :class:`InferenceBackend` 接口的后端实例。
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
ValueError: 配置缺失或指定了未注册的后端。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not isinstance(config, dict) or not config.get("backend"):
|
|
|
|
|
|
raise ValueError("推理后端配置缺失:需要 backend: gpu|npu")
|
|
|
|
|
|
name = str(config["backend"]).lower()
|
|
|
|
|
|
if name not in BACKEND_REGISTRY:
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"未注册的推理后端: {name!r},可用: {sorted(BACKEND_REGISTRY)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
cls = BACKEND_REGISTRY[name]
|
|
|
|
|
|
inf = config.get("inference", {}) or {}
|
2026-08-05 04:13:23 +08:00
|
|
|
|
kwargs = dict(
|
2026-08-04 22:04:18 +08:00
|
|
|
|
endpoint=inf.get("endpoint", ""),
|
|
|
|
|
|
model=inf.get("model", "iaop-default"),
|
|
|
|
|
|
timeout_seconds=float(inf.get("timeout_seconds", 10)),
|
|
|
|
|
|
)
|
2026-08-05 04:13:23 +08:00
|
|
|
|
# 空值/缺省不覆盖后端类默认(如昇腾 npu 默认 mindie / ascend-910b / CANN 8.0),
|
|
|
|
|
|
# 保证「切换后端仅改 backend 字段」时硬件相关参数落到正确的默认值。
|
|
|
|
|
|
for key in ("runtime", "device", "cann_version"):
|
|
|
|
|
|
value = inf.get(key)
|
|
|
|
|
|
if value:
|
|
|
|
|
|
kwargs[key] = value
|
|
|
|
|
|
return cls(**kwargs)
|
2026-08-04 22:04:18 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_backend_config(path: str) -> dict:
|
|
|
|
|
|
"""从 YAML 配置资产加载后端配置(模板可覆盖资产)。"""
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
|
|
|
|
return yaml.safe_load(fh) or {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def default_config_path() -> str:
|
|
|
|
|
|
"""返回本模块模板配置资产的默认路径。"""
|
|
|
|
|
|
return os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
|
|
|
|
"config", "backends.template.yaml")
|