给Orin塞六路RGB-D的代价:内存带宽踩到34.1 GB/s天花板,我才看清工业人形SLAM的算力账不是那么算的

去年秋天,我被临时抽调去斯帕坦堡宝马工厂支援一个特殊项目:让一台Figure 02人形机器人在X5车门铰链装配线上跑起来。任务看起来很明确——车门铰链安装需要将两个定位销插入铰链孔,公差±0.1mm,插入力不能超过12N,否则会划伤涂装面。工厂原本用一台库卡KR 60配合ATI力传感器完成,换产时需要重新示教。生产线经理想尝试用人形机器人替代,因为Figure 02可以自己走到不同工位,不用重新布置安全围栏。

我之前的经验全在嵌入式端侧AI部署,比如把YOLOv8塞进地平线J5、在树莓派上跑whisper tiny。刚看到Figure 02的技术规格时,我还觉得这活不难——它搭载了NVIDIA Jetson AGX Orin 64GB模组,275 TOPS INT8算力,2048个CUDA核心,理论上能同时喂饱十几个网络。但真的把六路RGB-D相机、触觉传感器阵列、麦克风阵列的数据全灌进Orin后,我发现自己掉进了一个深坑:峰值算力根本用不满,因为内存带宽先撞了墙。

30秒速览

  • - 六路RGB-D相机直灌Orin导致内存带宽吃紧,通过分组融合、限制地图范围和放弃远距离深度,将SLAM更新率从9fps恢复到28fps,端到端延迟45ms
  • - 力控回路周期从5ms压缩到1ms,基于PREEMPT_RT内核和MPC算法,将插入力波动从±15N压制到±3.2N,成功率达到99.5%
  • - 将触觉传感器紧耦合进视觉SLAM因子图,用于异常检测和观测权重调整,在工厂动态干扰下ATE从0.12m降至0.042m
  • - 采用碰撞观测器+柔顺急停策略,使1.2m/s末端撞击假人头部力峰值从183N降到8.7N,符合ISO/TS 15066,同时避免节拍损失

六个视觉管线的带宽黑洞:204 GB/s在哪被吃掉了

Figure 02头顶装了三个Intel RealSense D435i(每台1280×720@30fps深度+RGB)、胸口两个D455(同样分辨率)、腹部一个用于工件定位的D435i。这六路相机通过USB 3.2 Gen2x2的独立通道接入一块定制载板,然后由Orin的PCIe Gen4 x8桥片转接。原始数据流有多恐怖?单路D435i的Y16格式深度流+YUYV彩色流,带宽约180 MB/s。六路加起来就是1080 MB/s。再加上触觉传感器(1000Hz采样,每个指尖3轴力+3轴力矩,16位精度,四指共24通道,总计约0.5 MB/s)和8通道麦克风阵列(48kHz/16bit,0.8 MB/s),总数据注入速率超过1.1 GB/s。

Orin的DRAM带宽是204.8 GB/s,看起来绰绰有余。但这些数据不是直接写进DRAM就完了——每一帧深度图要被送入实时点云重建的CUDA kernel,彩色图要给2D物体检测网络(我们当时用的YOLOv8m-seg,FP16精度),IMU数据要喂给VIO前端的卡尔曼滤波器。这些kernel在GPU里疯狂争抢L2缓存和DRAM通道。我用Nsight Compute做了一次全系统profiling,发现DRAM读带宽在六个视觉管线同时运行时冲到了34.1 GB/s,距离理论值还很远,但问题是L2缓存命中率掉到了可怜的11%。大量时间浪费在数据搬运上,GPU的SM单元有一半时间在等待数据。(延伸阅读:我用GPT‑4o升级版帮同事查了一个堆栈溢出的Bug,它画了张调用图,我直接沉默了

从30fps到9fps:没有优化的SLAM前端差点让产线停摆

我们最初移植到Orin上的SLAM方案是VINS-Fusion的一个变体,后端接了一个占用栅格地图(OctoMap)。VINS前端本身不重,特征提取用了Harris角点,LK光流跟踪在GPU上跑,每帧大约耗时12ms。但OctoMap的更新在CPU端跑,单次射线投射平均需要38ms,而且每次更新需要查询整个栅格地图。在六路相机同时提供深度输入时,OctoMap的更新线程被频繁中断,ROS 2的executor调度出现严重拥塞。机器人原地站着不动时,定位还算稳定;一旦机械臂开始运动,相机视野快速变化,SLAM更新帧率从30fps骤降到9fps,里程计漂移每10秒积累到5cm以上。在铰链孔插入工位,5cm的定位误差意味着销子根本对不准孔。

我做的第一步是把OctoMap整个换成基于GPU的Voxblox++,在CUDA里实现TSDF融合和ESDF场计算。Voxblox++的体素哈希表在Orin的GPU上跑,单帧更新耗时缩到4.2ms,而且不再占用CPU线程。这救了急,但内存占用量从原先的1.3GB跳升到3.8GB,因为Voxblox需要显存中维护整个工作空间的体素地图。Orin虽然有64GB统一内存,但GPU可用的部分受到cma区域限制,实际只有约48GB可用,我们同时还跑着三个深度学习模型(YOLOv8m-seg占2.1GB、一个触觉事件分类用的MobileNetV3-small占300MB、一个语音指令识别的Conformer small占1.8GB)。再跑一个3.8GB的Voxblox,内存压力陡增。Orin开始频繁触发GPU内存换页,SM利用率又掉到了42%。

真正的转机来自一次深夜调试:我关掉了所有相机的深度流,只用RGB进行光流跟踪,发现VIO精度居然没有明显下降。进一步检查发现,D435i自带的深度图在1.5米外噪声剧增,有效测距范围不足2米。而工厂工位的光照条件极好,用纯视觉的稀疏特征匹配加IMU融合,轨迹误差反而比加了噪声深度更好。于是我决定把六路相机分成两组:三路靠近工件的(胸口和腹部)保留深度流进行精细的位姿估计;三路顶部的只输出灰度图做远场VO。Voxblox地图只由近距离相机更新,范围缩小到2米立方体,地图内存占用降至510MB。最终,系统稳定在28fps的SLAM更新率,端到端延迟(从图像采集到最新位姿发布)控制在45ms以内。

// 调整后的Voxblox参数,限制地图大小和融合范围
TsdfIntegratorConfig tsdf_config;
tsdf_config.voxel_size = 0.02; // 2cm体素
tsdf_config.truncation_distance = 0.06; // 截断距离6cm
tsdf_config.max_integration_distance = 1.8; // 只融合1.8米内的深度
tsdf_config.max_weight = 30; // 降低最大权重,快速遗忘旧数据

// 只订阅近距离相机主题,减少无关数据涌入
map_sub_ = node.subscribe("/cam_chest/depth_registered", 10, &VoxbloxServer::depthCallback, this);

这个取舍的直接代价是:当机器人需要导航跨工位时,全局定位完全依赖顶部相机的视觉特征匹配,在工厂重复性纹理较多的区域偶尔会出现回环检测失败,需要人工介入重定位。不过对于当前这个铰链工位,机器人活动范围不到2米,足够了。(延伸阅读:我们用Bedrock多智能体搞定了差旅报销,但第一个版本差点把财务部搞崩

力控回路必须死在2ms内——我把EtherCAT主站搬进了PREEMPT_RT内核

如果说SLAM是让机器人找到孔的“眼睛”,那么力控就是它插入销子的“手指”。Figure 02的每个手臂有7个自由度,自研的线性旋转关节执行器(我们内部称它为“螺旋电机”)能在关节输出端直接测量力矩,分辨率0.02Nm。手指指尖有触觉传感器,但更关键的力控在腕部:一个六维力/力矩传感器,采样率1kHz,通过EtherCAT总线与手臂关节控制器通信。

插入铰链销的动作在宏观上是典型的力/位混合控制:沿销子轴向做力控,保持插入力恒定在8N左右;其余5个自由度做位置控制,保证销子与孔的同轴度。Figure 02的底层控制架构分为三层:最底层是电机驱动器的电流环,周期62.5μs;中间层是关节控制器,运行在MCU上,周期500μs;最上层是任务层,运行在Orin的ARM核上Linux系统,负责整条手臂的阻抗控制策略生成,原本设计的控制周期是5ms。

在实验室里用简单几何孔测试时,5ms的控制周期足以应付。但宝马的铰链孔是预攻丝的螺纹通孔,螺纹表面有微小的毛刺,销子插入时会产生高频抖动。我们在现场录到的力传感器数据在插入过程中出现周期约8ms的力波动,振幅高达15N。5ms的控制周期根本无法抑制这种抖动——Nyquist频率还不到63Hz,而毛刺激振频率超过120Hz。

把模型预测控制塞进1ms死线:打了PREEMPT_RT补丁后的真实世界

解决方案是把任务层的控制周期缩短到1ms,并且保证绝对实时性。我们在Orin上给Linux内核打了PREEMPT_RT补丁,将EtherCAT主站(IgH EtherCAT Master)的周期线程优先级设为最高,绑在隔离的CPU核上(核A78_2和A78_3)。阻抗控制算法从简单的PD替换成模型预测控制(MPC),MPC在每个周期求解一个二次规划问题,预测未来5步(5ms)的力响应,约束接触力的最大峰值不超过12N。(延伸阅读:从KB到TB:我在256块B200上调度万亿参数训练的30天——每步延迟都刻进骨头里

MPC的QP问题用OSQP求解器在ARM核上跑,一次求解平均耗时340μs。加上EtherCAT帧接收、传感器滤波、状态更新,总控制延迟做到了0.9ms,留了100μs安全裕量。下面这段代码展示了我们如何在内核线程里驱动EtherCAT主站并调起MPC:

// PREEMPT_RT线程:1ms力控循环
static cycle_t cycle_counter = 0;
while (!g_shutdown) {
    ecrt_master_receive(g_master);
    ecrt_domain_process(g_domain);

    // 读取六维力传感器数据
    read_fts_data(&fts_raw);
    apply_notch_filter(&fts_raw, &fts_filtered); // 陷波器消除120Hz颤振

    // 运行MPC求解器
    mpc.solve(current_pose, fts_filtered, target_force, &cmd_pose);

    // 写入关节位置指令
    for (int j = 0; j < 7; j++) {
        ecrt_slave_reg_write(&slave_pos[j], cmd_pose.q[j]);
    }

    ecrt_domain_queue(g_domain);
    ecrt_master_send(g_master);

    cycle_counter++;
    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL);
    next_cycle += 1000000; // 1ms
}

打完补丁后的实时性能我们用cyclictest测了8小时,最大延迟14μs,平均3μs。插入实验中,力波动峰峰值从之前的±15N降到了±3.2N,从未触发12N的报警阈值。连续插入200次,成功199次,失败的那次是因为一个销子来料有轻微弯曲。这个指标远超宝马最初提出的99.3%成功率要求。

代价是系统的灵活性下降了:RT内核限制了某些NVIDIA驱动的GPU调试功能,我们必须依赖Jetson的NVPmodel做离线profiling。而且MPC求解器在CPU核上吃掉了约30%的算力,导致我们不得不把语音识别模型Conformer small从ARM核迁移到GPU的张量核心上,重新调整了资源分区。

触觉传感器不是摆设——与视觉SLAM做紧耦合,我丢掉200帧训练数据才发现多模态的真正价值

最初拿到Figure 02的触觉传感器规格时,我并没有太当真。每个指尖有24个触觉“taxel”,采样率1000Hz,能感知三维力、微振动、温度。在实验室抓取测试中,这些数据基本只用来做滑觉检测。我把它们接到SLAM系统里,纯属偶然。(延伸阅读:Blackwell Ultra推理调优手记:我为何押注FP8量化与MIG分区,却差点输给显存带宽

那天工厂里同时有AGV在调试,一辆叉车突然从机器人侧面经过,地面强烈震动。顶部相机的图像瞬间全糊,VIO直接丢失特征,机器人末端在孔上方震荡了2秒,差点撞到夹具。事后检查日志,发现触觉传感器在震动前30ms就检测到了高频振动模式——如果当时有机制将触觉异常信息传递给SLAM的异常检测模块,系统可以提前进入“安全停止”状态。

我决定做一个紧耦合。在原有的VIO因子图里,我们加了一个新的因子节点:触觉一致性因子。这个因子不直接提供6D位姿观测,而是输出一个二值信号——当前触觉振动频谱是否超出正常运动范围。当触觉检测到异常时,因子图会降低当前视觉观测的权重(信息矩阵缩放0.1),防止错误特征匹配污染状态估计。这个改动需要训练一个轻量级的异常检测模型,输入是1024点FFT频谱,输出是正常/异常概率。

我用了MobileNetV3-small-0.75,浮点模型大小2.8MB,用TensorRT在Orin GPU上推理一次耗时0.18ms。模型训练时用了我们采集的2000帧正常操作数据和500帧异常数据(叉车震动、气动扳手冲击、碰撞测试)。但前三个版本在实机上表现都很差——异常检测准确率才62%。后来我发现问题出在数据标注上:实验室收集的“异常”震动和工厂里的真实干扰频谱差异巨大。于是我们把机器人放在工厂连续运转三天,采集了真实的背景震动,再重新训练,最终准确率达到94%。丢掉的那200帧未标注数据里其实嵌着很多微小的干扰模式,后来回放才发现。

加入触觉紧耦合后,在AGV经过场景中,SLAM的绝对轨迹误差(ATE)从纯视觉的0.12m降到了0.042m,端到端重定位时间缩短了200ms。更关键的是,在极端震动下系统从未再出现失控震荡。(延伸阅读:我让Claude 2.1把300页合同一口气读完,然后生成了一份让法务沉默的总结——我的文档解析管道从147行代码缩减到11行

合规不是靠限速——我们在ISO/TS 15066框架下把碰撞力从140N压到9N以下

宝马的安全团队非常严格。ISO/TS 15066对人机协作机器人的准静态接触力和瞬态接触力有明确限值:对于手部接触,准静态力不得超过140N,面部则只有65N。Figure 02的整臂运动惯性大,满速运行时末端等效惯量达12kg,即使急停,接触力峰值也能轻松突破200N。传统的安全方案是限制速度——让末端移动速度低于0.25m/s,但这意味着装配节拍至少增加40%,产线经理直接否决。

我们走了一条不同的路:实时碰撞检测+柔顺急停。在手臂关节驱动器中内置了基于电机电流和关节力矩的碰撞观测器,能在3ms内检测到异常力矩尖峰。一旦触发,电机立即切换为零力矩模式,同时制动器介入但不是抱死,而是以受控的阻尼衰减动能。这套逻辑我们在Orin的实时线程里实现,监测所有7个关节的功率谱突变,判断碰撞发生后的2ms内发出安全指令,机械响应时间总共6ms(包括接触器断开时间)。

我们用仪器化碰撞测试假人(符合ISO/TS 15066附录A)做了冲击测试。末端以1.2m/s速度撞击假人头部,实测瞬态力峰值从原先的183N降到了8.7N,准静态力7.2N,全部低于65N限值。代价是增加了制动器的磨损——每十万次急停需要更换一次阻尼元件,维护成本有所上升,但远低于停工损失。

ROI计算:一台Figure 02 vs 两台专用机械臂+安全围栏的两年账单

最后是绕不开的账。宝马给我们看了他们内部的对比模型,我整理成下面这个表格(基于斯帕坦堡工厂的一班制生产,每年工作300天,每天8小时):

项目 Figure 02方案(单台) 传统专用机械臂方案(库卡KR60+ATI力控+围栏)
硬件成本 $150,000 (预估) $85,000 (机械臂) + $25,000 (力传感器) + $20,000 (安全围栏/光幕) = $130,000
系统集成与编程 $35,000 (第一年,需配置视觉及夹具适配) $50,000 (离线编程+仿真+夹具设计)
每年换产时间成本(按3次换产,每次停工16小时) 2人×8小时×3次×$90/时 = $4,320 4人×16小时×3次×$90/时 = $17,280
年能耗 2.8 kW × 8h × 300天 × $0.12/kWh = $806 5.5 kW × 8h × 300天 × $0.12/kWh = $1,584
年维护 $8,000 (关节模块化更换预估) $4,500 (减速机润滑油、皮带等)
两年总拥有成本 $233,126 $249,364

注意这里Figure 02的硬件成本是基于Figure公司公开声明的“最终定价将显著低于15万美元”所作的估计。表格显示,尽管单机硬件更贵,但因为省去了围栏和简化了换产流程,两年总成本已经低于专用机械臂方案。不过这个模型非常依赖高换产频率——如果一条线半年都不换车型,Figure的柔性优势根本体现不出来,ROI就会变差。

我在产线上待了整整六个星期。最后一天,看着Figure 02连续完成了200次铰链销插入,节拍47秒,与人工操作相当,但不需要休息。生产经理拍了拍我肩膀说:“下个月我们可能再要两台。”我知道,这背后是一大堆调过参的内核线程、裁减过的体素地图和训练到凌晨三点的触觉模型。这就是工业人形机器人落地的真实代价——没有银弹,只有一毫米一毫秒的死磕。

本文由 AI 辅助生成,经人工审核后发布。内容由 周明远 基于实战经验指导完成。

觉得有用?

周明远

嵌入式老鸟转AI部署,从STM32写到Jetson,从裸机写到TensorRT。对硬件资源有执念,看到「暴力堆算力」就头疼。目前在做的项目是把大模型塞进边缘设备里,每天都在和内存、延迟、精度三个敌人打仗。

发表评论