加速路由 API
Hardware-first capability router.
The SDK ships loose components (散件), not a pipeline; apps compose them. Most components have (or will have) two implementations: a hardware leg executed by a daemon (DSP color convert / resize, ai-runtime NMS, codec JPEG, camera-daemon overlay) and a numpy/cv2 software leg that always works. Today several hardware legs are not reachable because the service layer does not expose them yet — see docs/proposals/sdk-hardware-routing.md for the platform ask list.
This router makes that situation explicit and uniform:
one policy per app — prefer hardware, software-only, or hardware-only;
one route table — which leg serves each operation, and why the other leg is not used (“pending platform exposure”, “probe failed”, “runtime error”);
degradation tracking — every hardware→software fallback is recorded and surfaced through
health()(and an optionalon_degradationhook an app can forward to the event bus as a health event).
from neoruntime_ipc_sdk.accel import get_default_router
router = get_default_router()
small = router.run("resize_nv12", nv12, (1920, 1080), (640, 384))
if router.health()["ops"]["resize_nv12"]["backend"] == "software":
... # hardware path not available on this platform build
- class neoruntime_ipc_sdk.accel.AccelRouter(policy=RoutePolicy.PREFER_HARDWARE)[source]
Bases:
objectRoute-table dispatcher between hardware and software providers.
Operations are registered with
register();run()executes through the leg chosen by the policy, recording any fallback.get_default_router()returns a singleton pre-registered with the operations the SDK can serve today.- __init__(policy=RoutePolicy.PREFER_HARDWARE)[source]
- set_policy(policy)[source]
Switch the routing policy at runtime (also accepts its value name).
- register(op, software=None, hardware=None, note='')[source]
Map an operation name to its software/hardware providers.
Either leg may be
None: a missing hardware leg means the capability awaits platform exposure (state it innote), a missing software leg means there is no fallback.
- use_hardware(op, hardware)[source]
Attach (or replace) the hardware leg of an operation.
- add_probe(name, probe)[source]
Register a cheap availability probe (e.g. service reachable).
- property policy: RoutePolicy
The active policy (fixed at construction).
- route(op)[source]
Decide which backend serves
opunder the current policy.
- run(op, *args, **kwargs)[source]
Execute
opthrough the leg chosen byroute().
- note_degradation(op, reason)[source]
Record a fallback that happened outside
run().Frame.resizekeeps its own direct DSP fast path (its geometry contract is richer than the router op’s NV12 signature); when that attempt fails under PREFER_HARDWARE it reports here sohealth()and the fallback counters stay honest. Fireson_degradationexactly like arunfallback does.
- probe()[source]
Run all registered probes → name → available.
- health()[source]
Snapshot of routing state, per-op counters and recent fallbacks.
- class neoruntime_ipc_sdk.accel.DegradationRecord(op, reason, timestamp)[source]
Bases:
objectOne hardware→software fallback, kept for health reporting.
- op: str
- reason: str
- timestamp: float
- __init__(op, reason, timestamp)
- exception neoruntime_ipc_sdk.accel.HardwareUnavailable[source]
Bases:
RuntimeErrorA hardware provider could not run (service down, op not exposed).
- class neoruntime_ipc_sdk.accel.RouteDecision(op, backend, provider, reason)[source]
Bases:
objectWhy a given backend serves an operation right now.
- op: str
- backend: str
- provider: str
- reason: str
- __init__(op, backend, provider, reason)
- class neoruntime_ipc_sdk.accel.RoutePolicy(value)[source]
Bases:
EnumHow the router picks a leg for each operation.
PREFER_HARDWAREuses the hardware leg when it is registered and healthy, falling back to software otherwise.SOFTWARE_ONLYnever touches hardware — for benchmarks and reproducible tests.HARDWARE_ONLYraises instead of degrading — when the zero-CPU guarantee matters more than uptime.- PREFER_HARDWARE = 'prefer_hardware'
- SOFTWARE_ONLY = 'software_only'
- HARDWARE_ONLY = 'hardware_only'
- neoruntime_ipc_sdk.accel.get_default_router()[source]
Return the pre-registered router singleton.
The public convenience layer rides this singleton:
color.rgb_to_nv12()/color.nv12_to_rgb(),draw.draw_detections()(NV12 arrays) andFrame.to_jpeg_bytes()route throughrun;Frame.resize()keeps its direct DSP fast path but consultsroute/policyand reports its fallbacks vianote_degradationso health stays honest.Operations served today:
resize_nv12,rgb_to_nv12andnv12_to_rgb(DSP when reachable, numpy otherwise),encode_jpeg(camera-daemon EncodeImage when reachable, cv2/Pillow otherwise),draw_detections(DSP blend when reachable and the frame is NV12, draw.py raster otherwise),nms(software only — suppression already runs in the HEF’s integrated hardware NMS before the app sees boxes, and itsiou_threshold/max_boxesare compile-time there; the runtime-tunabledetection_thresholdlives inInferenceClient.update_postprocess_config(), honored for family-function postprocess models. See docs/proposals/sdk-hardware-routing.md S-2).OverlayClient.annotateis hardware-first already and needs no routing.
- neoruntime_ipc_sdk.accel.shared_dsp_call(method, *args, **kwargs)[source]
Run one DspClient method on the process-resident client.
The same resident machinery the router legs use, exposed for SDK fast paths that live outside the route table (
Frame.resize’s DSP arm). On any error the resident client is retired — the next call reconnects — and the original exception propagates so the caller keeps its own fallback policy. Retirement defers the close until no shared call is in flight and no live pool remains, so a failure in one thread cannot cut the transport under a concurrent call or kill a ref another thread is holding. The resident is never retired on success.
类与函数
AccelRouter
- class neoruntime_ipc_sdk.AccelRouter(policy=RoutePolicy.PREFER_HARDWARE)[source]
Route-table dispatcher between hardware and software providers.
Operations are registered with
register();run()executes through the leg chosen by the policy, recording any fallback.get_default_router()returns a singleton pre-registered with the operations the SDK can serve today.- __init__(policy=RoutePolicy.PREFER_HARDWARE)[source]
- on_degradation: Callable[[DegradationRecord], None] | None
- set_policy(policy)[source]
Switch the routing policy at runtime (also accepts its value name).
- register(op, software=None, hardware=None, note='')[source]
Map an operation name to its software/hardware providers.
Either leg may be
None: a missing hardware leg means the capability awaits platform exposure (state it innote), a missing software leg means there is no fallback.
- use_hardware(op, hardware)[source]
Attach (or replace) the hardware leg of an operation.
- add_probe(name, probe)[source]
Register a cheap availability probe (e.g. service reachable).
- property policy: RoutePolicy
The active policy (fixed at construction).
- route(op)[source]
Decide which backend serves
opunder the current policy.
- run(op, *args, **kwargs)[source]
Execute
opthrough the leg chosen byroute().
- note_degradation(op, reason)[source]
Record a fallback that happened outside
run().Frame.resizekeeps its own direct DSP fast path (its geometry contract is richer than the router op’s NV12 signature); when that attempt fails under PREFER_HARDWARE it reports here sohealth()and the fallback counters stay honest. Fireson_degradationexactly like arunfallback does.
- probe()[source]
Run all registered probes → name → available.
- health()[source]
Snapshot of routing state, per-op counters and recent fallbacks.
RoutePolicy
- class neoruntime_ipc_sdk.RoutePolicy(value)[source]
How the router picks a leg for each operation.
PREFER_HARDWAREuses the hardware leg when it is registered and healthy, falling back to software otherwise.SOFTWARE_ONLYnever touches hardware — for benchmarks and reproducible tests.HARDWARE_ONLYraises instead of degrading — when the zero-CPU guarantee matters more than uptime.- PREFER_HARDWARE = 'prefer_hardware'
- SOFTWARE_ONLY = 'software_only'
- HARDWARE_ONLY = 'hardware_only'
RouteDecision / DegradationRecord
get_default_router
- neoruntime_ipc_sdk.accel.get_default_router()[source]
Return the pre-registered router singleton.
The public convenience layer rides this singleton:
color.rgb_to_nv12()/color.nv12_to_rgb(),draw.draw_detections()(NV12 arrays) andFrame.to_jpeg_bytes()route throughrun;Frame.resize()keeps its direct DSP fast path but consultsroute/policyand reports its fallbacks vianote_degradationso health stays honest.Operations served today:
resize_nv12,rgb_to_nv12andnv12_to_rgb(DSP when reachable, numpy otherwise),encode_jpeg(camera-daemon EncodeImage when reachable, cv2/Pillow otherwise),draw_detections(DSP blend when reachable and the frame is NV12, draw.py raster otherwise),nms(software only — suppression already runs in the HEF’s integrated hardware NMS before the app sees boxes, and itsiou_threshold/max_boxesare compile-time there; the runtime-tunabledetection_thresholdlives inInferenceClient.update_postprocess_config(), honored for family-function postprocess models. See docs/proposals/sdk-hardware-routing.md S-2).OverlayClient.annotateis hardware-first already and needs no routing.
使用示例
默认路由器:硬件优先,自动降级
from neoruntime_ipc_sdk import get_default_router
router = get_default_router()
small = router.run("resize_nv12", nv12, (1920, 1080), (640, 384))
# DSP 不可用时自动回落 numpy/cv2,并记录一次降级
health = router.health()
print(health["ops"]["resize_nv12"]["backend"])
print(health["recent_degradations"])
颜色转换同样走硬件腿
from neoruntime_ipc_sdk import get_default_router
router = get_default_router()
nv12 = router.run("rgb_to_nv12", rgb) # DSP convert_hw
rgb2 = router.run("nv12_to_rgb", nv12, 1920, 1080) # 反方向同腿
# 路由器以 cpu_fallback=False 调用 DSP——真降级时只记一次,
# 软件腿恰好执行一遍(不会先在客户端内部悄悄跑一遍 CPU)。
# keep-fd 源(Frame/FrameHandle)直接透传给 *_hw:dma-buf 在
# DspClient 内部零拷贝导入,路由腿不再 ascontiguousarray 拷贝
#(dsp-offload P2)。数组源行为不变。
JPEG 编码:daemon 一发 RPC,CPU 兜底
router = get_default_router()
jpeg = router.run("encode_jpeg", rgb, quality=85)
# 硬件腿 = camera-daemon EncodeImage(DspClient.encode_jpeg_hw,
# DSP 核上 N 线程 libjpeg——hailo15 无专用 JPEG 编码块),
# 软件腿 = cv2/Pillow。daemon 未暴露该 RPC 时自动降级并记账,
# health()["ops"]["encode_jpeg"]["backend"] 如实反映当前后端。
检测标注:NV12 走 DSP blend,RGB 走软件光栅
router = get_default_router()
annotated = router.run("draw_detections", nv12, result)
# 硬件腿把标注渲染成最小 RGBA 画布(draw.render_overlay_rgba),
# 一次 DspClient.blend_hw 合成回 NV12——输入 NV12 返回 NV12。
# RGB 数组留在软件腿(draw_detections 光栅):把 RGB 折腾两次
# 色彩转换再合成,比省下的光栅还贵。空检测列表直接返回拷贝,
# 不碰 DSP。
降级事件转发到事件总线
from neoruntime_ipc_sdk import EventClient, get_default_router
bus = EventClient()
router = get_default_router()
def report(record):
bus.publish("app/health/degradation", {
"op": record.op,
"reason": record.reason,
})
router.on_degradation = report
只信硬件:失败即抛错
from neoruntime_ipc_sdk import AccelRouter, RoutePolicy
# HARDWARE_ONLY 路由器不降级——硬件失败直接抛 HardwareUnavailable,
# 适合零 CPU 占用比可用性更重要的调用点
strict = AccelRouter(policy=RoutePolicy.HARDWARE_ONLY)
strict.register("resize_nv12", hardware=my_dsp_resize)
能力探测
print(get_default_router().probe())
# {'cv2': True, 'dsp': False} ← dsp=False 表示服务当前不可达