ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CARLA 0.9.13 长时运行崩溃排查:显存泄漏与同步模式优化实战

CARLA 0.9.13 长时运行崩溃排查:显存泄漏与同步模式优化实战 1. 长时运行崩溃的现场还原与问题定位思路CARLA 0.9.13 在 Ubuntu 20.04 上跑仿真短则十几分钟、长则一两个小时后进程突然挂掉终端只留下一行冷冰冰的Segmentation fault (core dumped)这是很多做自动驾驶仿真、感知算法验证、强化学习训练的朋友都遇到过的事。我自己在做一个多传感器融合的闭环测试项目时就反复被这个问题折磨了将近两周——每次跑到关键场景就崩日志里什么有效信息都没有重跑一遍又要等一个多小时效率低到让人抓狂。这篇文章就把我踩过的坑、用过的排查手段、以及最终把崩溃频率从“每小时必崩”压到“连续跑十几个小时不崩”的整套方法完整地分享出来。先说清楚这个问题的本质Segmentation fault是进程访问了不属于自己的内存地址操作系统直接把进程杀掉。它本身不是原因只是一个结果。CARLA 是一个基于 Unreal Engine 4 的大型 C 仿真器同时对外提供 Python API中间还夹着 Boost、libpng、protobuf、以及 GPU 驱动和 CUDA 这一整条链路。任何一环出问题最终都可能表现为段错误。所以定位的核心思路不是“找一个万能修复”而是分层排查、逐步缩小范围把“CARLA 崩了”这个模糊现象拆解成“是渲染线程崩了”“是 Python 客户端崩了”“是显存耗尽被驱动杀了”还是“是某个传感器回调里的野指针”。适合读这篇内容的人包括正在用 CARLA 做算法验证的研究生、做仿真测试的工程同学、以及刚把 CARLA 装起来准备跑长时任务的新手。哪怕你只是刚在 Ubuntu 20.04 上装好 CARLA 0.9.13还没遇到崩溃我也建议你把排查思路和参数配置先过一遍因为长时运行崩溃几乎是绕不开的一道坎提前知道怎么处理能省下大量重复劳动。在展开之前先给一个我实测下来最有效的总体判断CARLA 长时运行崩溃八成以上和显存泄漏、渲染线程资源未释放、以及 Python 客户端与服务器之间的同步模式配置有关真正属于 CARLA 自身代码 bug 的比例反而不高。所以排查顺序应该是先看显存和内存曲线再看同步模式与传感器频率最后才去怀疑引擎本身。2. 环境准备与崩溃复现的最小化配置2.1 先把运行环境钉死避免变量干扰排查崩溃最忌讳的就是环境一团乱。我见过太多人一边怀疑 CARLA 有 bug一边系统里装着三四个版本的 CUDA、驱动是半年前手动编译的、Python 环境里 pip 和 conda 混用。这种情况下你根本没法判断问题出在哪。所以第一步把环境固定下来并且记录下来。我用的这套配置是经过验证比较稳的组件版本说明操作系统Ubuntu 20.04.6 LTS内核 5.15 系列GPUNVIDIA RTX 3060 12GB显存越大越不容易崩显卡驱动525.xx与 CUDA 11.7 匹配CUDA11.7CARLA 0.9.13 官方推荐Python3.8官方 wheel 对应版本CARLA0.9.13预编译包这里有个关键点CARLA 0.9.13 的预编译包对 CUDA 版本是有要求的如果你系统里的 CUDA 版本和它编译时用的不一致运行时可能不会立刻报错但会在长时间运行后因为某些 kernel 调用异常而崩溃。所以务必用nvcc --version和nvidia-smi确认版本并且和官方文档对齐。2.2 用最小脚本复现崩溃而不是一上来就跑完整项目很多人一遇到崩溃就直接拿自己那个几千行的训练脚本去跑结果崩了也不知道是哪一步的问题。正确的做法是先写一个最小复现脚本只做一件事让 CARLA 持续运行并不断产生数据看它多久崩。我用的最小复现脚本大概是这样import carla import time client carla.Client(localhost, 2000) client.set_timeout(10.0) world client.get_world() # 开启同步模式固定步长 settings world.get_settings() settings.synchronous_mode True settings.fixed_delta_seconds 0.05 world.apply_settings(settings) # 生成一辆车和一个相机 bp_lib world.get_blueprint_library() vehicle_bp bp_lib.filter(vehicle.*)[0] spawn_point world.get_map().get_spawn_points()[0] vehicle world.spawn_actor(vehicle_bp, spawn_point) camera_bp bp_lib.find(sensor.camera.rgb) camera_bp.set_attribute(image_size_x, 800) camera_bp.set_attribute(image_size_y, 600) camera world.spawn_actor(camera_bp, carla.Transform(), attach_tovehicle) frame_count 0 def on_image(image): global frame_count frame_count 1 camera.listen(on_image) try: while True: world.tick() if frame_count % 100 0: print(fprocessed {frame_count} frames) except KeyboardInterrupt: pass finally: camera.destroy() vehicle.destroy() settings.synchronous_mode False world.apply_settings(settings)这个脚本跑起来之后你就能观察它到底能撑多久。我第一次跑的时候大概 40 分钟就崩了而且是在world.tick()那一行报的段错误。这就把范围缩小到了“服务器端 tick 过程中出问题”而不是 Python 客户端的问题。提示复现脚本一定要开同步模式。异步模式下 CARLA 的 tick 由服务器自己控制问题更难复现也更难定位。2.3 打开 core dump让崩溃留下证据光看到Segmentation fault是没用的必须拿到 core 文件才能分析。Ubuntu 20.04 默认可能没开 core dump需要手动配置ulimit -c unlimited sudo sysctl -w kernel.core_pattern/tmp/core-%e-%p-%t然后重新跑复现脚本崩溃后去/tmp下找core-carla*文件。有了 core 文件就可以用 gdb 看崩溃时的调用栈gdb /path/to/CarlaUE4/Binaries/Linux/CarlaUE4-Linux-Shipping /tmp/core-carla-xxxx (gdb) bt这一步非常关键。我当时的调用栈指向了渲染线程里的一个纹理资源释放函数这就直接说明了问题方向——不是逻辑 bug是资源管理问题。3. 核心崩溃原因逐层拆解与针对性缓解3.1 显存泄漏长时运行崩溃的头号嫌疑CARLA 在长时运行中最典型的问题就是显存持续增长。原因是多方面的传感器每帧产生的图像数据如果没有被及时释放会在 GPU 上堆积渲染线程的中间纹理在某些情况下不会立即回收Python 客户端如果持有大量图像引用也会间接导致服务器端资源无法释放。判断方法很简单开一个终端持续监控显存watch -n 2 nvidia-smi如果显存占用随着运行时间线性上升那基本可以确定是泄漏。我实测的时候显存从初始的 2GB 左右每小时涨 1.5GB 到 2GB12GB 的卡跑到六七个小时就满了然后驱动直接杀进程表现就是段错误。缓解手段有几个我按有效性排序第一降低传感器频率和分辨率。很多人为了数据质量把相机设成 1920x1080、30Hz还同时开好几个传感器。这在短时测试里没问题但长时运行就是灾难。我后来把相机降到 800x600、10Hz显存增长明显放缓。第二在 Python 回调里及时释放图像数据。CARLA 的carla.Image对象持有底层数据如果你在回调里把它存进列表或者队列就会一直占着内存。正确做法是处理完立刻丢弃def on_image(image): # 只取需要的数据处理完不要保留 image 对象 array np.frombuffer(image.raw_data, dtypenp.uint8) # 做你的处理 del array # 不要 append 到全局列表第三定期重启传感器。这是一个比较“土”但很有效的办法每隔一段时间比如每 30 分钟销毁并重建传感器强制释放资源。虽然会丢几帧数据但能显著延长稳定运行时间。3.2 同步模式配置不当导致的死锁与崩溃CARLA 的同步模式是把双刃剑。用好了仿真稳定、可复现用不好就会出现 tick 卡死、客户端和服务器步调不一致最终在某个 tick 上崩溃。核心问题在于同步模式下服务器会等所有客户端 tick 完才继续。如果你有多个客户端或者某个客户端的回调处理太慢服务器就会一直等等到超时或者内部状态错乱就崩了。我踩过的一个坑是在相机回调里做了一次比较重的图像处理跑了一个小模型导致回调耗时超过了fixed_delta_seconds。短时间没事跑久了服务器端的帧队列就乱了最后段错误。解决办法是把重处理从回调里挪出来用队列解耦import queue import threading image_queue queue.Queue(maxsize10) def on_image(image): if image_queue.full(): image_queue.get() # 丢弃旧帧防止堆积 image_queue.put(image) def process_thread(): while True: image image_queue.get() # 在这里做重处理 # ... threading.Thread(targetprocess_thread, daemonTrue).start()这样回调本身只做入队耗时极短服务器不会被拖慢。同时队列设了上限防止内存无限增长。另外fixed_delta_seconds不要设得太小。有人为了“高精度仿真”设成 0.01也就是 100Hz这对服务器压力极大。一般 0.0520Hz就够用了除非你有特殊需求。3.3 渲染线程与 GPU 驱动层面的问题如果 core dump 的调用栈指向渲染相关函数那问题可能在渲染线程和 GPU 驱动的交互上。CARLA 用的是 UE4 的渲染管线在 Linux 下通过 Vulkan 或 OpenGL 与驱动通信。驱动版本不匹配、或者驱动本身有 bug都会导致长时运行后崩溃。我遇到过一次驱动是 470 版本跑久了必崩换成 525 之后就稳定了。所以驱动版本一定要用官方验证过的不要随便用最新版或者太老的版本。还有一个容易被忽略的点CARLA 的渲染质量设置。在CarlaUE4.sh启动时可以加参数降低渲染负载./CarlaUE4.sh -quality-levelLow -benchmark -fps20-quality-levelLow会关闭很多高级渲染特性显存占用和 GPU 负载都会明显下降。对于不需要高画质的感知算法测试这个设置非常划算。我实测下来Low 画质下显存增长几乎停滞连续跑 10 小时以上没问题。3.4 Python 客户端自身的内存问题有时候崩溃的不是 CARLA 服务器而是你的 Python 客户端。Python 的垃圾回收不是实时的如果你在循环里不断创建大对象比如 numpy 数组内存会持续增长最终 OOM 被杀表现也可能是段错误。排查方法是单独监控 Python 进程的内存ps aux | grep python # 找到你的进程 PID watch -n 2 cat /proc/PID/status | grep VmRSS如果 Python 进程内存一直涨那就是客户端的问题。解决办法是显式触发垃圾回收或者复用缓冲区import gc frame_count 0 while True: world.tick() frame_count 1 if frame_count % 1000 0: gc.collect()复用缓冲区是指预先分配好 numpy 数组每次把图像数据拷进去而不是每次新建buffer np.zeros((600, 800, 4), dtypenp.uint8) def on_image(image): buffer[:] np.frombuffer(image.raw_data, dtypenp.uint8).reshape(600, 800, 4)这样能大幅减少内存分配和回收的压力。4. 完整实操流程与稳定性验证方案4.1 从零搭建一个可长时运行的 CARLA 测试环境把前面所有点串起来我给一套完整的实操流程。这套流程是我现在做长时测试的标准动作你可以直接抄。第一步启动 CARLA 服务器用低画质、固定帧率cd /path/to/CARLA_0.9.13 ./CarlaUE4.sh -quality-levelLow -benchmark -fps20 -carla-rpc-port2000-benchmark会关闭一些交互特性让运行更稳定。-fps20限制服务器帧率避免它跑太快导致客户端跟不上。第二步客户端连接并配置同步模式client carla.Client(localhost, 2000) client.set_timeout(20.0) world client.get_world() settings world.get_settings() settings.synchronous_mode True settings.fixed_delta_seconds 0.05 settings.no_rendering_mode False # 需要图像就设 False world.apply_settings(settings)第三步传感器配置要克制。我一般只开必要的传感器分辨率不超过 800x600频率不超过 20Hzcamera_bp bp_lib.find(sensor.camera.rgb) camera_bp.set_attribute(image_size_x, 800) camera_bp.set_attribute(image_size_y, 600) camera_bp.set_attribute(sensor_tick, 0.1) # 10Hz第四步用队列解耦数据处理回调只入队。第五步加一个监控线程定期打印显存和内存import subprocess def monitor(): while True: result subprocess.run([nvidia-smi, --query-gpumemory.used, --formatcsv,noheader], capture_outputTrue, textTrue) print(fGPU memory used: {result.stdout.strip()}) time.sleep(60)第六步加一个定期重启传感器的逻辑每 30 分钟重建一次。4.2 稳定性验证怎么判断真的修好了修完之后不能只跑一次就说好了要有验证方案。我的做法是连续跑 12 小时记录显存和内存曲线每 30 分钟打印一次帧数和资源占用如果 12 小时不崩且显存增长在 500MB 以内就算通过我最终调优后的结果是显存从 2.1GB 涨到 2.6GB 左右就稳定了不再线性增长连续跑了 14 小时没崩。这个结果对于大多数仿真任务已经够用了。验证的时候要注意不要用交互式操作去干扰。有些人一边跑一边在 CARLA 窗口里拖视角、切场景这些操作本身就会增加崩溃概率。长时测试就让它安安静静跑。4.3 参数选择的计算依据这里补充一下几个关键参数为什么这么选。fixed_delta_seconds 0.05对应 20Hz。为什么不是 0.110Hz因为 10Hz 下车辆运动看起来会卡顿对于需要连续轨迹的算法不够平滑。为什么不是 0.0250Hz因为 50Hz 下服务器每帧的计算压力翻倍长时运行更容易崩。20Hz 是一个平衡点。相机 800x600 而不是 1920x1080是因为显存占用和像素数成正比。1920x1080 是 800x600 的 4.3 倍显存压力也差不多是这个倍数。对于大多数感知算法验证800x600 的清晰度已经足够。传感器频率 10Hz 而不是 20Hz是因为图像数据量太大20Hz 下每秒产生的数据是 10Hz 的两倍队列和显存压力都翻倍。除非你的算法需要高频图像否则 10Hz 够用。5. 常见问题速查与避坑经验5.1 崩溃问题速查表现象可能原因排查方法解决手段跑几十分钟后段错误显存泄漏nvidia-smi 看显存曲线降分辨率、降频率、定期重启传感器tick 卡死后崩溃同步模式死锁看回调耗时队列解耦、减小 fixed_delta_seconds 压力调用栈指向渲染函数驱动或渲染问题gdb 看 bt换驱动版本、开 Low 画质Python 进程内存涨客户端内存泄漏看 VmRSSgc.collect、复用缓冲区一启动就崩环境不匹配看启动日志对齐 CUDA、驱动、Python 版本多客户端时崩同步模式冲突检查客户端数量只用一个客户端 tick5.2 我踩过的几个坑第一个坑以为崩溃是 CARLA 的 bug去 GitHub 提 issue结果发现是自己回调写太重。这个教训让我养成了先看自己代码的习惯。CARLA 本身在长时运行上确实有一些资源管理问题但大部分崩溃都能通过合理配置避免。第二个坑用异步模式跑长时任务。异步模式下服务器自己控制节奏客户端只是被动接收看起来简单但一旦客户端处理不过来数据就会堆积最后崩。同步模式虽然要自己 tick但可控性强得多。第三个坑忽略了 Python 的 GIL 影响。在回调里做多线程处理时GIL 会导致实际并行度很低回调耗时比预期长。后来我改成单线程处理加队列反而更稳。第四个坑显存监控只看总量不看增长趋势。一开始我看到显存 8GB 觉得还有余量没在意结果它一直在涨涨到 12GB 就崩了。后来我改成看增长速率只要发现持续增长就立刻处理。5.3 几个实用的避坑技巧技巧一用no_rendering_mode做纯逻辑测试。如果你的任务不需要图像只是测试车辆控制或路径规划可以开no_rendering_mode True这样完全不渲染显存占用极低几乎不会崩。我做一些控制算法验证时就用这个模式连续跑几天都没事。技巧二把长时任务拆成多个短时任务。与其让一个进程跑 12 小时不如拆成 12 个 1 小时的任务每个任务结束后重启 CARLA。虽然麻烦一点但稳定性大幅提升而且单个任务崩了不影响其他任务。技巧三日志要记全。崩溃时如果只有一行段错误什么都查不了。我现在的做法是客户端和服务器都开详细日志客户端记录每一帧的帧号和时间戳服务器端把 UE4 的日志也保留下来。这样崩溃时能知道是跑到第几帧、哪个场景崩的。技巧四定期保存状态。长时任务一定要有 checkpoint 机制每隔一段时间保存一次仿真状态和算法状态。这样即使崩了也能从最近的 checkpoint 恢复不用从头再来。6. 长时运行稳定性的进一步优化方向6.1 用容器化隔离环境如果你需要在多台机器上跑或者经常重装系统建议用 Docker 把整个环境打包。CARLA 官方提供了 Docker 镜像你也可以自己基于 Ubuntu 20.04 构建。容器化的好处是环境完全一致不会出现“我这儿能跑你那儿崩”的情况。而且容器可以限制资源避免某个进程吃光显存影响其他任务。构建的时候注意把 GPU 支持配好需要nvidia-docker或者--gpus all参数。CARLA 在容器里跑的性能和裸机差不多我实测下来帧率损失在 5% 以内。6.2 监控与自动恢复对于真正需要无人值守的长时任务可以加一套监控和自动恢复机制。思路是用一个外部脚本监控 CARLA 进程和客户端进程如果发现进程消失或者显存异常就自动重启并恢复任务。#!/bin/bash while true; do if ! pgrep -f CarlaUE4 /dev/null; then echo CARLA died, restarting... ./CarlaUE4.sh -quality-levelLow -benchmark -fps20 sleep 30 python3 my_client.py --resume-from-checkpoint fi sleep 60 done这个脚本虽然简单但在实际长时任务里非常有用。我有个跑了三天的测试任务就是靠这个脚本自动恢复了两次最终完整跑完。6.3 关注 CARLA 版本更新CARLA 0.9.13 是 2022 年的版本后续版本在资源管理和稳定性上有不少改进。如果你的项目允许升级可以考虑用更新的版本。不过升级前一定要做兼容性测试因为 API 可能有变化。我个人的经验是0.9.13 在合理配置下已经能满足大部分需求不急着升级。最后分享一个我自己的体会CARLA 长时运行崩溃这个问题看起来吓人但只要你把显存、同步模式、回调处理这三块管好基本就能解决九成以上的崩溃。剩下的那些偶发的、难以复现的崩溃往往和具体的硬件、驱动组合有关换一套环境可能就没了。所以遇到崩溃不要慌按分层排查的思路一步步来总能找到原因。
返回列表