Skip to main content
机器人
Rami Mouro·2026 年 7 月 29 日·← 所有文章

在 Qualcomm Dragonwing IQ-9075 EVK 上一步步搭建一个视觉-语言-动作(VLA)模型,直到它在开发板的 Hexagon NPU 上生成机器人动作。本文完全自包含——你需要的每条命令和每行代码都在这一页上,且每一步都以可核对的输出结尾。 我们使用 Pi0.5,一个 30 亿参数的机器人基础模型:给它相机图像和一条自然英语指令,它就输出关节运动。大约十分钟就能完成第一次 NPU 推理。本页最后三分之一才是有趣的部分——为什么这个模型的四个组成部分最终会以那样的方式分布在硬件上。
本页的每个数字都是在运行 Ubuntu 24.04 Server 的真实 IQ-9075 EVK 上实测的。取自 Qualcomm AI Hub 官方公开性能分析的数字会明确标注,绝不与我们的数据混用。

你最终会得到什么

开始之前

下面每条命令都在开发板上运行,通过 SSH 或串口控制台。不需要 Qualcomm AI Hub 账户。没有云端编译任务。不需要浏览器。 设置一个后文都会引用的工作目录:

1. 安装 ROS 2 Jazzy 和 Qualcomm AI 运行时

先从 ROS 2 开始。以下命令来自软件设置页面:
使用 http:// 而不是 https:// 是有意为之。 packages.ros.org 是指向 ftp.osuosl.org 的 CNAME,后者的 TLS 证书只覆盖 *.osuosl.org。在没有 CDN 掩盖这一不匹配的网络中,https:// 会导致证书校验失败。软件包完整性由 GPG 签名保证,而这正是 apt 实际校验的内容——Qualcomm 官方设置页面出于同样的原因使用 http://
接下来是 Qualcomm AI Runtime(QAIRT),它包含与 Hexagon NPU 通信的 QNN 库:
ppa:ubuntu-qcom-iot/qcom-ppa 在 IQ-9075 出厂镜像中已存在,因此不需要添加。重复添加会产生致命错误 E: Conflicting values set for option Trusted。如果遇到这个问题,请列出 /etc/apt/sources.list.d/ 并删除重复项。
确认 NPU 可访问且 Hexagon 架构正确。IQ-9075 是 v73
预期输出:
注意有两个 CDSP 设备节点。先记住这一点——它稍后会成为整个故事的核心。
推理以普通用户身份即可运行;不需要 root。如果在 /dev/fastrpc-cdsp 上遇到权限错误,用 sudo usermod -aG fastrpc $(id -un) 把自己加入 fastrpc 组,然后注销再登录。

2. 无需 AI Hub 账户获取 Pi0.5

只有当你要把自己的模型带上 NPU 时,才需要 AI Hub 中按用户分配的云端编译任务。对于已发布的模型则不需要:Qualcomm 为其众多芯片预编译了 QNN 上下文二进制文件(context binary),并通过 AI Hub Models 提供,任何人无需账户即可下载。qualcomm-qcs9075——即 IQ-9075 EVK——正是 Pi0.5 的 default_device,所以获取模型只是一次普通的 HTTPS 下载:
预期输出——四个 .bin 文件和一个元数据文件,总计 2.9 GB:
Pi0.5 不是一个模型——而是四个。 这决定了后面的一切: 量化是混合的:backbone 为 w4a16,视觉编码器和动作专家为 w8a16。
该模型包记录了 SDK build=v2.45.0.260326154327。它可以在 apt 提供的 QAIRT 2.46.0 上原样运行,因此不需要精确匹配 SDK 版本。

3. 在 NPU 上运行模型的第一个部分

在写任何代码之前,先用 qairt-tools 附带的 qnn-net-run 证明硬件能正常工作。 视觉编码器接受一张 RGB 图像,224×224,通道优先,float32,归一化到 [-1, 1]。生成一张:
预期输出以以下内容结尾:
你就得到了一个真实的张量:
预期输出: 2097152 字节——正好是 1 × 256 × 2048 个 float32 值。

4. 弄清你实际需要连接什么

现在你有四个模型,却不知道它们如何连接。不要猜,也不要相信 metadata.json 的键顺序——直接问二进制文件本身。qnn-context-binary-utility 会转储上下文二进制文件声明的输入和输出,且顺序正是编译后的图所期望的
然后打印图的顺序:
预期输出(节选——action_expert 有 41 个输入):
请再读一遍 action_expert 的输入顺序。 它是 l0, l1, l10, l11 … l17, l2, l3 … l9——按字符串排序,而不是数字排序。产出这些缓存的 backbone 是按 l0, l1, l2 … l17 输出的。如果按生产者顺序连接,你就悄无声息地打乱了 18 层中的 12 层。没有任何报错。模型照常运行。输出的动作是垃圾。 这就是为什么要从二进制文件生成顺序,而不是手动敲出来。

流水线如何组合

一个动作块需要 15 次 NPU 调用:三次视觉推理(每个相机槽位一次)、一次 token 嵌入、一次 backbone prefill,然后是经过动作专家的十次去噪步骤。值得记住的常量: 除了顺序陷阱外还有两处重命名要注意:token_emb 输出 prefix_emb,但 backbone 称之为 hidden_stateprefix_att_2d 变成了 prefix_att_2d_maskssuffix_sin/suffix_cos 到达动作专家时叫 rope_emb_sin/rope_emb_cos

机器人的状态去了哪里

这四个图中任何地方都没有状态输入张量。Pi0.5 将本体感觉离散化为 256 个桶,并将其拼接进语言提示词。以下格式复现自上游 openpi 实现:
因此提示词——进而 token id——在每个控制步都会变化。你无法预计算一张分词后指令的查找表并在闭环中使用;分词器必须内联运行。 分词器是标准的 PaliGemma SentencePiece 模型。Hugging Face 上的 google/paligemma-3b-pt-224 是受限的(未接受许可证时返回 HTTP 401),但完全相同的文件可以从 Google 的 big_vision 存储桶免认证获取——openpi 也是从那里获取的:
预期输出: 8986bb4f423f07f8c7f70d0dbe3526fb2316056c17bae71b1ea975e77a168fc6 为某个任务生成 token id,并把状态向量折叠进去:
预期输出:
如果把这些输入喂给 qnn-net-run,你必须传入 --use_native_input_files。默认情况下它把每个输入文件当作 float32 解析再转换为图的数据类型。对于 int32 的 lang_tokens,这会把真实的 token id 变成零——即全是填充——模型随后就在悄无声息地对空内容做条件推理。症状很隐蔽:只有对应真实语言 token 的行的输出不同,其值等于填充嵌入。

5. 从你自己的代码运行模型

qnn-net-run 是一个测试工具;它读文件、写文件。要串联四个模型,你需要把它们放进同一个进程。完成这件事的 QRB ROS 软件包是 qrb_inference_manager,它的整个 API 只有三个调用。 安装它以及封装它的 ROS 节点:
apt 版本无法运行这个模型。 apt 中的 ros-jazzy-qrb-inference-manager 是 1.1.1;上游是 2.2.0。1.1.1 版本拒绝 int32 张量输入,而这恰恰是 token_emblang_tokens 所需要的。我们在这里安装它是为了获取头文件并了解 API,然后在第 8 步从源码构建——那一步无论如何都需要。
将以下文件写为 $WORK/minimal_npu.cpp。这就是完整的程序:
构建并运行它——一行 g++,不需要 CMake:
预期输出:
三个 API 调用就把一个 30 亿参数模型的视觉编码器放上了 NPU。没有会话设置,没有 delegate 注册,没有图构建器,也不用写任何设备管理代码。
QNN_HTP_BURST=1 将 HTP 锁定在 TURBO 性能级别。没有它,NPU 会以默认的 DCVS 设置运行,每个阶段都会明显变慢。它在上下文创建时读取,所以必须在进程启动前就存在于环境变量中。

6. 串联四个模型

这部分需要你自己编写,而且大多是记账式的工作。它的形态如下:
两条能实实在在省时间的实现笔记:
  • 在十次去噪步骤中,除 x_ttime_step 外一切都是常量。 把约 34 MB 的 KV 缓存一次性打包进专家的输入缓冲区,之后每步只重写 6404 字节。
  • 专家直接返回 x_{t+dt} 它在内部完成 Euler 更新,因此主机端不需要 x += dt * v
按第 4 步转储出的图顺序拼接张量,为每个阶段构建扁平输入缓冲区——这是 inference_execute() 强制执行的契约,如果总大小不对它会告诉你:

7. 撞上墙:一个 NPU 装不下 Pi0.5

当四个上下文都在一个进程中构建时,你会得到这个:
四个二进制文件共有 2875 MiB 权重。这是 DSP 地址空间,不是主机内存——在系统内存还有 34 GB 空闲、以 root 身份运行时它照样失败,而每个二进制文件单独加载都完全正常。在设备 0 上的实测: 它也不是一个干净的容量上限:反复的 map/unmap 会使地址空间碎片化,因此相同的总量会因进程之前做过什么而时而成功、时而失败。
权重映射失败的上下文仍然会报告成功。 日志里能看到 Initialize Qnn graph from binary file successfully,而它返回的句柄会产生长度错误的输出向量——这在很久之后才在你自己的代码中以内存错误的形式显现。要防御它:把你得到的输出张量数量与第 4 步中图声明的数量对比,任何不匹配都按硬错误处理。
明显的临时方案是在每次使用前后创建和销毁上下文。它能用,但很慢——权重加载占大头: 你三分之二的时间不是花在计算上。

8. 解锁第二个 NPU

还记得第 1 步的两个设备节点吗。问题是 QNN 是否允许你指定它们。直接问它——编写 $WORK/devices.cpp
预期输出:
两个可寻址的 HTP 设备,各自拥有独立的映射预算。 把四个上下文分布到两个设备上,它们就都能保持常驻——换页问题彻底消失。 有一个障碍:qrb_inference_manager 调用的是 deviceCreate(nullptr, nullptr, …),总是落在设备 0 上。选择设备意味着传给它一个单条目的 QnnDevice_PlatformInfo_t。从源码构建该库并加入这一功能:
无论如何都需要从源码构建——它带来了 int32 支持(token_emb)、多输入张量和 HTP burst 模式,这些都不在 apt 的 1.1.1 版本中。 qrb_ros_nn_inference/qrb_inference_manager/src/qnn_inference/qnn_inference.cpp 中找到 create_device(),将 deviceCreate 调用替换为:
QnnInferenceQrbInferenceManager 上都添加一个 const uint32_t device_id_ = 0; 成员和一个 device_id 构造函数参数,然后构建:
OutputTensor 在 apt 1.1.1 与上游之间存在 ABI 破坏。 上游添加了四个 DMA-buf 字段,改变了 sizeof(OutputTensor)。任何用 apt 头文件编译却链接到重新构建的库的代码,会以错误的步长读取 std::vector<OutputTensor> 并在 std::bad_array_new_length 中崩溃。你的 overlay 的 include 路径必须排在 /opt/ros/jazzy/include 之前,且依赖代码需要完整重新编译而不是仅重新链接。
现在用显式的设备 id 构造四个管理器中的每一个,并让四个都保持常驻。

我们为什么这样拆分

两个 NPU 都可用后,分配四个模型有多种方式。最直观的是按大小平衡——每个设备放约 1.4 GB。这并不是最优答案。

按实测延迟绘制的 Pi0.5 四个组件在 IQ-9075 两个 Hexagon NPU 上的布局。单 NPU 需付出 2258 ms 的权重换页(总计 3277 ms)。双 NPU 让所有组件保持常驻(1323 ms)。把开销大的阶段重新放到更快的 NPU 上——每设备字节数不变——达到 1111 ms。

这段动画呈现了静态图无法呈现的一点:四个阶段是顺序依赖的。 vision_encoder 喂给 token_emb,后者喂给 backbone,后者再喂给 action_expert。没有任何部分并发运行。把模型分布到两个 NPU 上并没有并行化任何东西——它只是让每个组件都能保持已映射状态,使整条链不再为权重换入换出付出代价。第一个也是最大的收益就来自这里。 从各组件的开销出发,按每个动作块实测: 这两个 NPU 的速度并不相同。 每个组件在设备 1 上都比设备 0 慢 25–30%。我们反复测量且结果一致;我们没有确认的原因,也不打算猜测。 这种不对称决定了布局。backboneaction_expert 合计约占 1030 ms 计算量中的约 888 ms——所以它们应放在快的设备上,两个开销小的组件放到慢的那个。按开销平衡,而不是按大小: 注意第一行和最后一行放到每个设备上的字节数完全相同。两者之间 212 ms 的差异纯粹来自哪个设备承担了开销大的工作。 最终状态,3 次预热后 12 次迭代的测量: 上下文换页现在恰好为零,整条流水线比它最初的单 NPU 路径快 4 倍——从 4421 ms 降到 1111 ms。
这块板上的延迟与历史状态相关,所以要在刚重启的板上做基准测试。 在一块已运行 25 小时、经历了数千次上下文 map/unmap 循环的空闲板上重跑同一基准,得到的是 1152 ms 而非 1111——每个阶段都更慢,且 token_emb(最大的权重映射,1006 MiB)慢了 29%。这与消除争用应有的效果恰好相反。可能的机制是上文描述的地址空间碎片化,以性能退化而非映射失败的形式出现。我们尚未确证——验证实验就是重启后立即重跑——因此请把这里的任何单一数字视为 1111–1152 ms 区间的下限,并在报告你自己的数字时同时注明板子的运行时长。

与 AI Hub 公布数字的比较

AI Hub 对每个组件的性能分析是孤立进行的。我们运行的是完整链路,阶段之间有主机端数据搬运,所以我们的数字必然更高。这是一个”同类与非同类”的对比,目的是定位开销所在,而不是宣称胜利: 约 223 ms 的差距主要来自在 CPU 上于各阶段之间搬运张量。仅 backbone 就输出 36 个 KV 张量、共约 34 MB,必须读出并重新打包进专家的输入缓冲区。这是显而易见的下一个优化目标,而 qrb_ros_transport 的 DMA-buf fd 传递就是实现机制——qrb_inference_manager 2.x 已经为此暴露了 inference_execute_dmabuf() 入口。

9. 证明数值正确性

在这里构建”快但错”的系统很容易——被打乱的 KV 缓存会产生自信、看似合理却毫无用处的动作。所以要对照你在第 3 步已经用过的参考实现进行校验。 从你的串联流水线中转储每个阶段的精确扁平输入缓冲区及其输出,用第 4 步得到的图顺序把输入拆回逐张量文件,通过 qnn-net-run 重放,然后做 diff:
然后将 ref/Result_0/<name>.raw 与流水线对每个张量的输出进行比较。我们的结果逐位一致——四个组件的全部 45 个输出张量,而且在组件被固定到不同 NPU 时依然一致:
四个独立信号证明是 NPU 而不是 CPU 在运行:AI Hub 报告四个组件的 NPU 层放置率均为 100%(3835/3835、2473/2473、1120/1120、34/34);加载的后端是 libQnnHtp.so;输出与 HTP 后端上的 qnn-net-run 逐位一致;所有组件常驻后上下文创建开销降为零。

封装为 ROS 2 节点

链路跑通之后,ROS 这一层平平无奇,而这正是重点: 有两个值得照抄的决定。推理耗时约 1.1 秒,对 executor 回调来说太长,所以在专用工作线程上运行它。并且丢弃推理期间到达的帧而不是排队——基于陈旧观测行动的 VLA,比以更低频率行动的 VLA 更糟。 随每个动作块一起发布各阶段延迟明细。这没有任何成本,并且意味着你以后做出的任何性能声明都不会与实时测量脱节。

输出到底好不好?

延迟和逐位一致性证明了流水线是正确的。它们没有说明这些动作是否有用。在没有机器人的情况下验证这一点,可以重放 LIBERO 数据集(Pi0.5 校准所用的数据)中的一个真实回合——把人类演示者看到的观测原样喂给 NPU,然后把预测的动作块与他们实际的操作进行比较。 状态和动作都使用均值/标准差归一化,统计量随数据集一起发布在 meta/stats.json 中。在 20 步 horizon 上对 15 个重规划步骤计分: MAE / 动作标准差是无量纲的数字:0.145 表示误差约为该数据集中动作自然离散程度的 15%。 预测数据集均值的得分按构造恰为 1.0,因此模型确实在跟踪演示者。夹爪——在 ±1 处实质上是二值的,也是唯一一个对错毫无歧义的维度——匹配到 0.022。
先发布状态再发布图像,否则提示词会悄悄变陈旧。 节点在每个相机都有新帧时立即触发;机器人状态属于触发条件,而没有状态时分词器构建的提示词与 Pi0.5 校准时的结构不同。由于 DDS 只保证单个话题内的顺序而不保证跨话题顺序,先发布图像会让推理在第 k 帧上运行时提示词仍编码着第 k−1 步的状态——没有报错也没有日志。先发布状态、再任务、再图像,使这张表的整体值从 0.149 降到 0.145,夹爪从 0.036 降到 0.022。
这是在单个回合上的开环 teacher-forced 比较,不是任务成功率。策略从未看到自己动作的后果。这正是下一节要解决的问题。

它真的能完成任务吗?

上一节有个漏洞:决定接下来发生什么的是人类的动作,所以策略从未为自己的动作负责。一个存在细微错误的策略与一个好策略得分相近,因为两者的错误都从不累积。 闭环把人类移出了回路。仿真器渲染机械臂能看到的画面,策略做决策,仿真器执行那个决策,任务自身的目标判定谓词说明它是否成功。 结果发现这一切完全可以在板上运行——这出乎我们的意料,因为我们刚刚确认 Gazebo 无法在这里渲染。差别在于需求中的一个词: Mesa 的软件光栅化器提供 4.5 compatibility profile。Ogre2 要求 core 于是崩溃;MuJoCo 的经典渲染器则很满意。所以不需要笔记本,不需要跨主机桥接,不需要跨机器 DDS:

渲染是开销大的那一半,所以只在重规划时渲染

在那个软件光栅化器上,一次双相机 256×256 观测耗费 340 ms。一次物理步进耗费 31 ms。NPU 在 1126 ms 内产出五十个动作。 如果每个仿真步都渲染,数字就会彻底倒挂——仿真器的开销是 3B VLA 的 3.2 倍 所以只在重规划时渲染。这不是取巧——这恰恰是 50 步动作块带给你的东西,因为策略只在规划时才需要观测。它把一个 220 步的回合从 84.8 s 缩短到 15.3 s,并把瓶颈移回你预期中的 NPU 上。

按实测开销绘制的 NPU 动作块生产与仿真器步进消耗对照。一个动作块以 1126 ms 换来 50 个动作;每个仿真步都渲染会让仿真器达到策略开销的 3.2 倍,而只在重规划边界渲染则把它压缩到 18.7 s,让 NPU 重新成为主导。重规划 horizon 是调节旋钮:H=1 每回合耗时 426 s,H=50 耗时 18 s。

这个旋钮两端都有代价,而只有一端被测量过。较长的 horizon 便宜,但会基于更陈旧的观测行动,因为机械臂会沿着由更早图像制定的计划执行得更远。我们尚未测量这从哪里开始损害准确率,所以 --replan-horizon 10 是一个可用的选择,而非调优后的结果。

四个会悄悄毁掉结果的约定

这些都不会抛出异常。搞错任何一个,闭环照样运行、照样渲染、照样报告一个成功率——为零的成功率。每一个都是通过与录制数据集比对而不是读文档确定的:
axis-angle 分支是个陷阱,而看似显然的修复恰恰是错的。 LIBERO 的初始姿态让夹爪竖直朝下,这使旋转角几乎恰好为 π——axis-angle 表示的不连续点。用四元数标量部分的符号来固定分支是自然的做法,但它会失败:一次 rollout 中途从 +3.14 翻转到 −3.14,跳变 2π,而描述的是同一个物理姿态。由于状态是以离散化分桶的形式拼进语言提示词的,这在策略看来就是手腕在两个控制步之间转了整整一圈。录制数据集从不发生环绕,所以正确的规则是相对于前一状态的连续性,而不是固定的符号测试。

结果

全部十个 LIBERO-10 任务——长程任务套件——每个任务十个初始状态: 这十个初始状态是两组独立的五个——LIBERO 的索引 0–4 和 20–24——有意作为两次独立扫描运行。单独一组无法告诉你恰好选中的状态是不是简单的,而这个套件中难度差异很大: 两个区间重叠得很充分,说明两组一致,将它们合并是合理的。十个任务中有一半以 10/10 解决。真正困难的任务是*“把黄白相间的马克杯放进微波炉并关上门”,得分 4/10——它是两组中唯一都得分很低的任务。“把两个摩卡壶都放到炉子上”*先是 2/5 后是 5/5,这有效地提醒我们五回合样本的分辨能力有多低。

一个闭环回合:'拿起书并把它放进置物架的后隔层',在 252 个仿真步和 26 个动作块内解决。视频下方的条带是 50 动作块——阴影格是下次重规划前将执行的十个动作,琥珀色格是此刻正在执行的动作,带轮廓的其余部分会被丢弃。每一帧都烧录了各阶段 NPU 延迟和加载的后端。

HUD 有明确的用途:backend libQnnHtp.so 出现在每一帧上,因此 NPU 运行的证据随影像本身传播,而不是在旁边被口头断言。动作条带的存在是为了让丢弃可见——每五十个预测动作中有四十个在下次重规划时被扔掉,这看起来很浪费,直到你意识到正是它让闭环得以运行。 录制这段视频并非免费:额外的帧每对耗费 340 ms,所以测试框架会打印警告,说明带 --save-frames 的运行不是计时测量。视频由对已保存帧的独立离线处理合成,因此合成过程绝不落在被测量的回合内。 有两个细节比标题数字更重要: 十四次失败每一次都恰好跑满 520 步。 没有一次发散、抖动或产生胡乱输出——它们只是在任务中途耗尽了步数预算。失败模式是”对上限来说太慢”,而不是”错误”,这使得步数上限成为结果的一部分而非无关设置。 空策略在同一测试框架上得分为 24 中的 0。 全零动作和均匀随机动作都在每个任务上失败,且目标判定谓词在重置时从不预先满足。没有这项检查,一个高成功率同样可能只是在测量一个坏掉的判定谓词——而且从外部看完全一样。
这是一个简化的评测协议。 LIBERO 的完整评测是 4 个套件 × 10 个任务 × 50 个初始状态。这里是一个套件、每任务十个初始状态,因此 86% 带有约 ±7 个百分点的区间,不是基准测试数字。两组结果一致提高了”采样状态并非病态”的置信度,但可用状态的百分之二十仍然只是一个样本。我们也不与已发表的 pi0 级结果做比较——那需要它们的重规划 horizon 和步数上限与我们一致,而这一点我们尚未核实。
每个回合都是一条 JSON 记录,包含套件、任务、初始状态索引、horizon、步数上限、库版本、每块的后端及延迟分布,因此这个数字是可审计的而非口头断言的。

诚实的局限性

  • 任务成功率基于简化协议。 完整的 LIBERO 评测是 4 个套件 × 10 个任务 × 50 个回合,在单块板上负担不起。此处的任何成功率都注明其回合数和置信区间,绝不作为套件级基准数字呈现。
  • Gazebo 无法在这块板上渲染,但 MuJoCo 可以。 gz-harmonic 在 arm64 上安装正常,但 Ogre2 需要桌面 OpenGL 3.3 core,而这里的 Adreno 驱动只暴露 OpenGL ES;Ogre v1 会中止,强制使用 Mesa llvmpipe 则在 Ogre2RenderEngine::LoadImpl 内段错误。LIBERO 的 MuJoCo 渲染器可由同一 llvmpipe 提供的 OpenGL 4.5 compatibility profile 满足,这就是闭环演示能完全在板上运行、无需笔记本参与的原因。渲染仍然是软件的:没有硬件路径,因为 /dev/dri/renderD128 是显示控制器而不是 GPU,所以 Mesa 的 freedreno 无法绑定它,而 zink 被两个 Vulkan ICD 都拒绝。
  • 动作语义与具体机器人形态绑定。 已发布的导出面向 LIBERO 的 7 自由度 Franka Panda。其输出对于另一种机械臂不是有效的关节指令。
  • 归一化统计量是外部的。 原始关节值会产生无意义的提示词;你需要策略训练时使用的统计量。
  • 没有功耗或热学测量,也没有持续负载的浸泡测试。所有数字来自热学上未受压的板上的短时运行。
  • 从未用实时相机测量过。 输入是合成的或从磁盘重放的。
  • NPU 是独占资源。 两个进程各自映射完整模型包会争用同一 CDSP 预算,双双变慢。测量时确保 NPU 上没有其他任务。
  • 我们采用的每设备约 ~1600 MiB 上限是经验性且保守的,不是文档记载的限制。

这对平台意味着什么

100 TOPS 使 30 亿参数的视觉-语言-动作模型变得可能。这次实践表明它同样是实用的——达到 4.3–4.5 倍实时——但前提是把两个 NPU 当作需要调度的资源,而不是一个加速器。朴素路径与知情路径之间的差距是 4 倍,而且朴素路径还会悄无声息地吞掉一个致命的映射错误。 剩余的提升空间在 CPU 而不是 NPU 上:每块 223 ms 的主机端张量搬运,零拷贝 DMA-buf 应能大幅消除它。

相关内容