通过 TCP 将模型推理请求转发到远端硬件加速后端(SpacemiT ONNXRuntime / RKNN / ONNX Runtime)的轻量级推理代理。
- 跨平台客户端:Python / C++ 客户端均兼容 x86_64 / ARM / RISC-V
- 异构推理后端:支持 RK3566/3588 RKNN NPU、SpacemiT K3 EP、通用 ONNX Runtime
- 轻量级协议:msgpack + 自定义二进制帧,单连接全双工
- 模型缓存去重:客户端自动计算 MD5,服务端缓存已上传模型,避免重复传输(启动时扫描
.cache/,运行时动态维护) - 零依赖部署:Server 仅依赖 msgpack-c + 对应 backend SDK,无需额外运行时
Client (Python / C++) ──TCP──▶ inferbridge-server (C++) ──▶ Backend
├── SpacemiT ORT (SpacemiT K3)
├── RKNN (RK3568/3588)
└── ORT (标准 ONNX Runtime)
| 组件 | 路径 | 说明 |
|---|---|---|
| Server | server/ |
C++17 TCP 服务,接收帧、调度后端推理 |
| SpacemiT ORT 后端 | server/src/backend/spacemit_ort_backend.cpp |
适配 SpacemiT K3 EP + ONNX Runtime |
| RKNN 后端 | server/src/backend/rknn_backend.cpp |
适配 RK3568/3588 NPU |
| ORT 后端 | server/src/backend/ort_backend.cpp |
标准 ONNX Runtime(CPU/CUDA) |
| Python 客户端 | client/python/ |
inferbridge 包,API 仿 onnxruntime |
| C++ 客户端 | client/cpp/ |
头文件库,include/inferbridge.h |
| 示例 | examples/ |
Python 和 C++ 使用示例 |
所有通信使用自定义二进制帧封装 msgpack payload。
┌─────────┬────────┬──────────────┐
│ Magic │ Type │ Length │
│ 4 byte │ 1 byte │ 4 byte BE │
├─────────┴────────┴──────────────┤
│ Msgpack Payload (N bytes) │
└──────────────────────────────────┘
| 字节偏移 | 长度 | 内容 |
|---|---|---|
| 0–3 | 4 | Magic: IBRG (0x49 0x42 0x52 0x47) |
| 4 | 1 | MsgType(见下表) |
| 5–8 | 4 | payload 长度(大端 uint32) |
| 9+ | N | msgpack 编码的 payload |
| 类型 | 值 | 方向 | 主要 payload 字段 |
|---|---|---|---|
CACHE_LIST_REQ |
0x07 | C→S | {} (空 map) |
CACHE_LIST_RESP |
0x08 | S→C | md5s (string[]) |
LOAD_MODEL_REQ |
0x01 | C→S | md5 (string), model_data (bin, 可选), backend (string), session_options (map) |
LOAD_MODEL_RESP |
0x02 | S→C | inputs (TensorMeta[]), outputs (TensorMeta[]) |
INFER_REQ |
0x03 | C→S | inputs (name→ndarray map) |
INFER_RESP |
0x04 | S→C | outputs (ndarray[]) |
UNLOAD_REQ |
0x05 | C→S | {} (空 map) |
UNLOAD_RESP |
0x06 | S→C | {} (空 map) |
ERROR_RESP |
0xFF | S→C | message (string) |
模型缓存去重流程:
- 客户端连接后先发送
CACHE_LIST_REQ,服务端返回已缓存的模型 MD5 集合 - 客户端计算本地模型文件的 MD5
- 若 MD5 已在缓存中 →
LOAD_MODEL_REQ只发md5字段(无model_data) - 否则 → 发送完整
model_data二进制,服务端计算 MD5 后存入.cache/<md5>.onnx并加入缓存集合
多次加载同一模型时,第二次起会自动跳过文件传输。
编码为 map,字段:
{
"__ndarray__": true,
"dtype": "float32", # numpy dtype 字符串
"shape": [1, 3, 640, 640],
"data": b'\x00\x01...' # msgpack bin
}Server(编译时)
- CMake 3.16+
- g++ / clang(C++17)
Python 客户端
pip install msgpack numpy
pip install ./client/pythonbash scripts/build_server.sh --spacemit-ort ${path-to-spacemit-ort} --build-dir build/server/spacemit-ort# download aarch64 gcc
wget https://armkeil.blob.core.windows.net/developer/files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xf arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz# download rknn sdk first
# https://console.zbox.filez.com/l/I00fc3
# https://github.com/airockchip/rknn-toolkit2
bash scripts/build_server.sh --rknn ${path-to-rknn-sdk} --build-dir build/server/rknn# 1. 下载 ONNX Runtime(或使用已有版本)
wget https://github.com/microsoft/onnxruntime/releases/download/v1.27.0/onnxruntime-linux-x64-1.27.0.tgz
tar xf onnxruntime-linux-x64-1.27.0.tgz
# 2. 编译
./scripts/build_server.sh --ort ${path-to-ort} --build-dir build/server/ort
# 方式 1:使用脚本(默认 0.0.0.0:50051)
bash scripts/run_server.sh
# 方式 2:直接运行二进制
./build/inferbridge-server --host 0.0.0.0 --port 50051
# 启动日志示例:
# [inferbridge] cache: 3 model(s) pre-loaded from .cache/
# [inferbridge] listening on 0.0.0.0:50051服务端会在首次启动时自动扫描 .cache/ 目录下已有的 *.onnx 模型(文件名即 MD5),后续客户端上传的模型也会按 MD5 缓存到该目录。
import inferbridge as ib
import numpy as np
sess = ib.InferenceSession(
"/path/to/model.onnx",
providers=["InferBridge"],
provider_options=[{
"host": "localhost",
"port": 50051,
"session_options": {
"backend": "ort",
"intra_op_num_threads": "4", # 0=auto
"inter_op_num_threads": "1", # 0=auto
}
}]
)
inp = sess.get_inputs()[0]
dummy = np.zeros([1, 3, 224, 224], dtype=np.float32)
outputs = sess.run(None, {inp.name: dummy})
print(outputs[0].shape, outputs[0].dtype)import inferbridge as ib
import numpy as np
# SpacemiT ORT backend
sess = ib.InferenceSession(
"/path/to/resnet50.onnx",
providers=["InferBridge"],
provider_options=[{
"host": "192.168.1.100", # K3 板 IP
"port": 50051,
"session_options": {
"backend": "spacemit_ort",
"intra_op_num_threads": "1",
"USE_SPACEMIT_EP": "1",
"SPACEMIT_EP_INTRA_THREAD_NUM": "4",
}
}]
)
# 首次加载会上传模型,再次加载同一模型会自动跳过传输
inp = sess.get_inputs()[0]
dummy = np.zeros([1, 3, 224, 224], dtype=np.float32)
outputs = sess.run(None, {inp.name: dummy})
print(outputs[0].shape, outputs[0].dtype)#include "inferbridge.h"
inferbridge::ProviderOptions opts;
opts.host = "192.168.1.100";
opts.port = 50051;
opts.session_options["backend"] = "spacemit_ort";
opts.session_options["intra_op_num_threads"] = "1";
opts.session_options["USE_SPACEMIT_EP"] = "1";
inferbridge::InferenceSession sess("/path/to/model.onnx", {"InferBridge"}, {opts});
auto outputs = sess.run({}, {{inp.name, tensor}});通过 session_options 传入,键值均为字符串。
| 字段 | 说明 | 示例 |
|---|---|---|
target_platform |
目标平台 | rk3566 / rk3568 / rk3588 |
core_mask |
NPU 核心掩码(位运算) | "7" (三核全开) / "1" (单核) |
| 字段 | 说明 | 示例 |
|---|---|---|
backend |
后端名称(固定) | "ort" |
intra_op_num_threads |
算子内并行线程数 | "4" (0=auto) |
inter_op_num_threads |
算子间并行线程数 | "1" (0=auto) |
| 字段 | 说明 | 示例 |
|---|---|---|
backend |
后端名称(固定) | "spacemit_ort" |
intra_op_num_threads |
算子内并行线程数 | "1" |
USE_SPACEMIT_EP |
启用 SpacemiT EP | "1" |
SPACEMIT_EP_INTRA_THREAD_NUM |
EP 内部线程数 | "4" |
| 路径 | 说明 |
|---|---|
examples/python/ort_infer.py |
标准 ONNX Runtime 推理示例(支持 --shape 回退) |
examples/python/spacemit_ort_infer.py |
SpacemiT K3 专用示例 |
examples/python/rknn_infer.py |
RKNN NPU 推理示例(RK3566/3568/3588,已在 RK3588 实测) |
examples/python/yolo_infer.py |
YOLO 检测示例(含前后处理) |
examples/cpp/yolo_infer.cpp |
C++ YOLO 端到端示例 |
运行示例:
# Python(第二次加载会跳过模型传输)
cd examples/python
python ort_infer.py /share/models/resnet50.onnx --host 10.3.91.75 --shape 1,3,224,224
# C++
cd examples/cpp
cmake -B build
cmake --build build
./build/yolo_infer /path/to/yolo.onnx /path/to/image.jpg原因:服务端返回的输出 dtype 未被识别(通常因 ONNX Runtime 版本不匹配或量化模型 type info 损坏)
解决:
- 检查服务端日志中的
output dtype=unknown(N),记下类型 IDN - 在
server/src/backend/spacemit_ort_backend.cpp的ort_type_to_str中补充该类型映射 - 重新编译部署
原因:量化模型的 input shape 在 graph 中未声明,客户端构造了 rank-0 标量
解决:
- 使用
--shape 1,3,224,224显式指定输入形状(见examples/python/ort_infer.py) - 或用
onnx.shape_inference工具修复模型的 value_info
正常行为:客户端断开时会打印该日志,但在最新版本中已改为仅在真正错误时打印,正常断开只显示 client disconnected fd=N
检查:
- 服务端启动日志中是否有
cache: N model(s) pre-loaded .cache/目录是否存在且可写- 两次加载的模型文件 MD5 是否完全一致(可用
md5sum验证)
[inferbridge] cache: 2 model(s) pre-loaded from .cache/
[inferbridge] listening on 0.0.0.0:50051
[inferbridge] client connected fd=9
[inferbridge] load_model fd=9 backend=spacemit_ort size=26053357
[inferbridge] input name=input_0 dtype=float32 shape=[1,3,224,224]
[inferbridge] output name=output_0 dtype=float32 shape=[1,1000]
[inferbridge] model loaded -> .cache/b658383597ba8589d25ea8dd3df7e93d.onnx
[inferbridge] infer fd=9 input[0] name=input_0 dtype=float32 shape=[1,3,224,224]
[inferbridge] infer fd=9 done, 1 output(s)
[inferbridge] model unloaded fd=9
[inferbridge] client disconnected fd=9
[inferbridge] client connected fd=10
[inferbridge] load_model fd=10 backend=spacemit_ort md5=b658383597ba8589d25ea8dd3df7e93d (cached)
[inferbridge] input name=input_0 dtype=float32 shape=[1,3,224,224]
[inferbridge] output name=output_0 dtype=float32 shape=[1,1000]
[inferbridge] model loaded -> .cache/b658383597ba8589d25ea8dd3df7e93d.onnx
[inferbridge] infer fd=10 input[0] name=input_0 dtype=float32 shape=[1,3,224,224]
[inferbridge] infer fd=10 done, 1 output(s)
注意第二次加载没有 size=...(未传输模型数据),直接显示 (cached)。
- 在
server/src/backend/创建xxx_backend.{h,cpp} - 继承
BackendBase并实现load()/infer()/unload() - 在
backend/backend.cpp的create_backend()中注册 - 更新
server/CMakeLists.txt添加编译选项-DWITH_XXX=ON
若需新增消息类型(如批量推理、异步通知):
- 在
protocol.h中添加MsgType枚举值 - 在
server.cpp的handle_client()中实现处理逻辑 - 同步更新客户端
session.py/session.cpp中的常量定义
MIT License. See LICENSE for details.