机器人刚启动时避障正常,跑了十几分钟后 GPU 内存不断上涨,最后要么丢帧,要么地图更新直接停住。另一个看似相反的现象是,内存没有上涨,旧障碍却一直留在地图里。两种问题都和“三维记忆”有关,但一个是空间没有回收,一个是清除策略没有生效。
体素地图不是一张静态图片。相机每次观测都会更新一批体素块,位姿、分辨率、最大观测距离、动态障碍物清除和 ESDF 计算都会影响存储量。把地图当成无限大的缓存,最终一定会遇到显存或尾延迟问题。
先确认到底是哪块内存涨了
看到 GPU OOM 时,不要只记一条“显存不足”。应该同时记录 TensorRT、体素地图、CUDA stream、相机缓冲和系统共享内存。地图不断变大,和某个节点每帧泄漏,排查路径完全不同。
可以先留下运行时状态:
1 | tegrastats |
命令中的话题名要按实际配置替换。tegrastats 看到的是整机资源,不能直接告诉你某个体素层占了多少;还需要结合节点参数、profile 或运行时日志记录活跃块数量、更新频率和清除次数。
如果地图显存和推理显存互相争抢,先用CUDA 内存与 Stream 的对照实验拆出分配、复制、kernel 和同步,再判断是地图生命周期失控,还是每帧缓冲没有复用。
体素尺寸和范围先决定一个数量级
如果把一个边长为 L 的立方空间按边长 r 的体素离散,理论体素数量近似为:
1 | N_voxels ≈ (L / r)^3 |
真实实现通常按块分配,并且只为观测到的区域分配内存,所以这个公式不能当作精确显存结果。它却能提醒你,体素边长从 5 cm 改成 2.5 cm,空间数量级可能增加八倍,而不是“细节增加一倍”。
对移动机器人来说,远处区域通常不需要和近距离抓取区使用同样分辨率。可以采用局部窗口、最大观测距离或分层地图,把计算预算留给规划真正会用到的空间。参数改动后要同时观察地图更新 P99 和规划器拿到的地图年龄。
旧障碍为什么不会自己消失
深度相机看见一个箱子时,障碍体素会被标记。箱子被搬走以后,地图只有在后续观测射线经过原位置、清除策略允许更新、并且时间戳和位姿都有效时,才有机会把它改成自由空间。没有新观测时,系统无法凭空证明那里已经空了。
清除逻辑至少要回答三个问题:空闲观测保存多久,动态障碍是否允许衰减,地图暂停更新时导航是否继续使用旧数据。对于安全相关的障碍,宁可让规划器知道“地图过期”并降速,也不要把旧地图当成当前事实。
ESDF 更新和感知更新不是一回事
体素占据更新成功,不代表 ESDF 已经使用了最新数据。距离场通常有自己的更新周期、工作区和队列。若规划器消费时间为 t_plan,地图最新观测时间为 t_map,还应记录:
1 | map_age = t_plan - t_map |
当 map_age 很小而 esdf_age 很大时,继续提高相机帧率没有意义,瓶颈在距离场更新或 GPU 资源竞争。控制接口应能收到地图年龄和有效标志,超过截止期就降低速度或暂停规划。
map_age 与感知结果年龄可以共用一套记录口径,具体字段见视觉伺服的端到端延迟预算。两者都要从源数据时间算到实际消费时刻,不能用节点发布频率代替。
长时间运行要做三组测试
第一组让机器人原地观察,确认静态场景下显存和活跃块数量是否稳定。第二组让机器人在有限区域来回走,观察地图窗口是否回收,动态物体离开后多久消失。第三组加入录包、推理和导航负载,测温度变化、显存峰值、地图更新 P99 和规划等待。
| 测试 | 主要观察 | 不能直接推出的结论 |
|---|---|---|
| 原地静态 | 基线显存、更新周期 | 不能证明长距离行走不会增长 |
| 有限区域移动 | 活跃块回收、动态清除 | 不能证明未知环境覆盖充分 |
| 全链路负载 | 资源争抢、P99、地图年龄 | 不能把仿真结果当成真机结果 |
每次测试都保存体素参数、相机分辨率、位姿来源、GPU 型号和功耗模式。只记“跑了半小时没崩”无法复现问题。
参考资料
证据边界:体素数量公式只是数量级估算,不能替代 nvblox、驱动和目标 GPU 的实际 profile。本文没有给出某个体素尺寸、地图范围或 Jetson 型号的显存上限。发布前应补上运行参数、长时间日志和地图年龄记录。