Skip to main content
Robotics
Rami Mouro·2026 年 7 月 29 日·← 全部文章

在 Qualcomm Dragonwing IQ-9075 EVK 上一步一步地搭建一个视觉-语言-动作模型,直到它在开发板的 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 上下文二进制文件,并通过 AI Hub Models 提供,任何人无需账户即可下载。qualcomm-qcs9075 — 即 IQ-9075 EVK — 是 Pi0.5 的 default_device,因此获取模型只是一次普通的 HTTPS 下载:
预期输出 — 四个 .bin 文件和一个元数据文件,共 2.9 GB:
Pi0.5 不是一个模型 — 它是四个。 这决定了后面的一切: 量化是混合的:backbone 使用 w4a16,vision encoder 和 action expert 使用 w8a16。
该 bundle 记录了 SDK build=v2.45.0.260326154327。它可以在 apt 提供的 QAIRT 2.46.0 上原样运行,因此你不需要精确匹配 SDK 版本。

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

在编写任何代码之前,先用随 qairt-tools 提供的 qnn-net-run 证明硬件正常工作。 vision encoder 接收一张 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,然后是经过 action expert 的十次去噪步骤。值得了解的常量: 除了排序陷阱之外,还有两个重命名要注意:token_emb 输出 prefix_emb,但 backbone 称其为 hidden_state,而 prefix_att_2d 变成了 prefix_att_2d_masks。suffix_sin/suffix_cos 到达 action expert 时变为 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,然后转换为计算图的 dtype。对于 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_emb 的 lang_tokens 所需要的。我们在这里安装它是为了获取头文件和了解 API,然后在步骤 8 中从源码构建 — 那一步无论如何都是必需的。
将这个文件写为 $WORK/minimal_npu.cpp。这是完整的程序:
构建并运行它 — 一行 g++,无需 CMake:
预期输出:
三个 API 调用就把一个 30 亿参数模型的视觉编码器放到了 NPU 上。没有会话设置,没有委托注册,没有图构建器,也不需要编写设备管理。
QNN_HTP_BURST=1 将 HTP 锁定在其 TURBO 性能级别。没有它,NPU 会以默认的 DCVS 设置运行,每个阶段都会明显变慢。它在上下文创建时被读取,因此必须在进程启动之前就位于环境中。

6. 串联四个模型

这是你需要自己编写的部分,而且大部分是簿记工作。它的形态如下:
两个能节省实际时间的实现说明:
  • 除 x_t 和 time_step 之外的一切在十个去噪步骤中都是常量。 将约 34 MB 的 KV 缓存打包进 expert 的输入缓冲区一次,之后每步只重写 6404 字节。
  • expert 直接返回 x_{t+dt}。 它在内部应用了 Euler 更新,因此主机侧不需要 x += dt * v。
按照你在步骤 4 中转储的计算图顺序拼接张量来构建每个阶段的扁平输入缓冲区 — 这是 inference_execute() 强制执行的契约,如果总大小不对它会告诉你:

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

当四个上下文都在一个进程中构建后,你会得到:
这四个二进制文件包含 2875 MiB 的权重。这是 DSP 地址空间,而不是主机 RAM — 即使系统内存还有 34 GB 空闲、以 root 身份运行,它也会失败,而每个二进制文件单独加载都完全正常。在设备 0 上测量: 它也不是一个干净的容量限制:反复的映射/取消映射会使地址空间碎片化,因此相同的总量会因进程之前做了什么而成功或失败。
权重映射失败的上下文仍然报告成功。 日志中会出现 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 调用替换为:
在 QnnInference 和 QrbInferenceManager 上都添加一个 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 中崩溃。你的覆盖层的 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%。我们反复且一致地测量了这一点;我们没有确认的原因,也不会去猜测。 这种不对称决定了放置方式。backbone 和 action_expert 合计约占约 1030 ms 计算中的约 888 ms — 所以它们属于快的设备,两个便宜的组件放到慢的设备上。按成本平衡,而不是按大小: 注意第一行和最后一行在每个设备上放置的字节数相同。它们之间的 212 ms 纯粹来自哪个设备执行昂贵的工作。 最终状态,在 3 次预热后测量 12 次迭代: 上下文换页现在恰好为零,整个流水线比它起步时的单 NPU 路径快 4 倍 — 从 4421 ms 降至 1111 ms。
这块开发板上的延迟与历史相关,所以要在刚重启的板子上做基准测试。 在一块空闲但已运行 25 小时、经历了数千次上下文映射/取消映射周期的开发板上重新运行同一基准测试,得到的是 1152 ms 而不是 1111 — 每个阶段都更慢,而 token_emb(最大的权重映射,1006 MiB)慢了 29%。这与消除竞争应有的效果恰恰相反。可能的机制是上面描述的地址空间碎片化,以性能下降而不是映射失败的形式表现出来。我们尚未确认 — 实验方法是重启后立即重新运行 — 因此请将此处任何单个数字视为 1111–1152 ms 范围的下限,并在给出你自己的数字时同时注明开发板的运行时间。

与 AI Hub 发布的数据相比如何

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

9. 证明它在数值上正确

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

用 ROS 2 节点封装它

一旦链条工作正常,ROS 层就平淡无奇 — 这正是重点: 两个值得借鉴的决策。推理耗时约 1.1 s,对于执行器回调来说太长了,所以在专用的工作线程上运行它。以及丢弃在推理中途到达的帧,而不是把它们排队 — 一个基于陈旧观测行动的 VLA 比一个以更低频率行动的 VLA 更糟糕。 在每个块旁边发布逐阶段延迟分解。这没有任何成本,而且意味着你之后做出的任何性能声明都不会脱离实时测量。

输出到底好不好?

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

它真的能完成任务吗?

上一节有一个漏洞:接下来发生什么是由人类的动作决定的,所以策略从未为自己的动作负责。一个略有偏差的策略和一个好的策略得分大致相同,因为两者的错误都从不累积。 闭环消除了人类。模拟器渲染机械臂能看到的画面,策略做出决定,模拟器执行那个决定,任务自身的目标谓词判断是否成功。 结果证明这可以完全在开发板上运行 — 这出乎我们的意料,因为我们刚刚确认 Gazebo 无法在这里渲染。区别在于需求中的一个词: Mesa 的软件光栅化器提供 4.5 compatibility 配置文件。Ogre2 要求 core 于是崩溃;MuJoCo 的经典渲染器则很满意。所以不需要笔记本电脑,不需要跨主机桥接,不需要跨机器的 DDS:

渲染是昂贵的那一半,所以只在重规划时渲染

在那个软件光栅化器上,一次双摄像头 256×256 观测耗费 340 ms。一个物理步耗费 31 ms。NPU 在 1126 ms 内产生五十个动作。 如果每个模拟步都渲染,数字就会不可避免地反转 — 模拟器的成本是 3B VLA 的 3.2 倍: 所以在重规划时渲染。这不是捷径 — 这正是 50 步动作块带给你的东西,因为策略只在规划时才需要观测。它将一个 220 步的 episode 从 84.8 s 变为 15.3 s,并将瓶颈移回到你所期望的 NPU 上。

NPU 块生产与模拟器步消耗的对比,按测量成本绘制。一个块以 1126 ms 换来 50 个动作;每个模拟步都渲染会让模拟器成为策略的 3.2 倍,而只在重规划边界渲染则将其压缩到 18.7 s,让 NPU 重新掌控局面。重规划范围是调节旋钮:H=1 每个 episode 耗费 426 s,H=50 耗费 18 s。

这个旋钮两端都有成本,而只有一端被测量了。长的范围便宜,但会基于更陈旧的观测行动,因为机械臂会在一个由更旧图像制定的计划中执行得更深。我们尚未测量这从哪里开始损害准确性,所以 --replan-horizon 10 是一个可用的选择,而不是调优后的选择。

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

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

结果

全部十个 LIBERO-10 任务 — 长时程任务套件 — 每个任务十个初始状态: 这十个初始状态是两个独立的五个一组 — LIBERO 的索引 0–4 和 20–24 — 有意作为独立的扫描运行。单独一组无法告诉你碰巧选中的状态是否是简单的,而这个套件中难度差异很大: 两个区间舒适地重叠,所以两组是一致的,合并它们是合理的。十个任务中有一半以 10/10 解决。唯一真正困难的任务是 “put the yellow and white mug in the microwave and close it”,为 4/10 — 唯一在两组中得分都低的任务。“Put both moka pots on the stove” 先是 2/5 后是 5/5,这有力地提醒我们五个 episode 的样本能说明的东西何其有限。

一个闭环 episode:'pick up the book and place it in the back compartment of the caddy',在 252 个模拟步和 26 个块内解决。视频下方的条带是 50 动作块 — 有阴影的单元格是下次重规划前将被执行的十个,琥珀色单元格是此刻正在应用的动作,带轮廓的其余部分被丢弃。逐阶段 NPU 延迟和加载的后端烧录在每一帧上。

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

坦诚的局限性

  • 任务成功率来自精简的协议。 完整的 LIBERO 评估是 4 个套件 × 10 个任务 × 50 个 episode,这在一块开发板上无法承受。这里的任何成功率都注明了其 episode 数量和置信区间,并且绝不作为套件级基准数字呈现。
  • 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 配置文件即可满足,这就是闭环演示完全在开发板上运行、不涉及笔记本电脑的原因。渲染仍是软件渲染:不存在硬件路径,因为 /dev/dri/renderD128 是显示控制器而不是 GPU,因此 Mesa 的 freedreno 无法绑定它,而 zink 被两个 Vulkan ICD 都拒绝。
  • 动作语义是特定于具体机器人形态的。 已发布的导出针对 LIBERO 的 7-DoF Franka Panda。其输出对不同的机械臂不是有效的关节命令。
  • 归一化统计数据是外部的。 原始关节值会产生无意义的提示词;你需要策略训练时使用的统计数据。
  • 没有功耗或热测量,也没有持续负载浸泡测试。所有数字都来自热应力未饱和的开发板上的短时运行。
  • 从未用实时摄像头测量过。 输入是合成的或从磁盘重放的。
  • NPU 是独占的。 两个各自映射完整 bundle 的进程会争夺同一 CDSP 预算,双双变慢。测量时确保 NPU 上没有其他任务。
  • 我们遵循的每设备约 ~1600 MiB 上限是经验性且保守的,不是文档记载的限制。

这对平台意味着什么

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

相关内容