1. 训练中断那一刻:Bus Error到底在报什么
1.1 先分清Segmentation Fault和Bus Error不是一回事
先说说我自己是怎么撞上这个坑的。当时我在一台双卡服务器上训练YOLOv8,数据集是自采的工业零部件图片,单张分辨率不高,但总量有十几万张,训练脚本用的是ultralytics标准写法,model.train(data=xxx.yaml, epochs=300, batch=64, workers=16) 这样的配置。跑起来的前几百step一切正常,loss也在稳步下降,结果到了某个epoch之后进程毫无征兆地消失,终端只留下一句:
code复制Bus error (core dumped)
我的第一反应是硬件问题。因为"Bus Error"这个名字听起来就像主板总线、内存条、PCIe链路出了故障,我甚至让人去机房重新插拔了内存。折腾一晚上之后才发现,这个问题和显卡、内存条半毛钱关系都没有,真正的瓶颈在Linux的共享内存,也就是 /dev/shm 这个挂载点。
很多人容易把SegmentFault和Bus Error混为一谈。Segmentation Fault(段错误)是进程访问了虚拟地址空间里非法区域,比如空指针、越界数组,本质是代码逻辑错误导致的内存访问违规。Bus Error则不一样,它是进程试图访问一块"物理上无法完成读写"的地址,比如映射了一个文件之后,文件被截断了,或者底层共享内存块已经被回收,后续访存指令就会触发SIGBUS信号。在x86平台上Bus Error平时很少见,所以一旦遇到,绝大多数人的排查方向都会像我一样跑偏。
1.2 YOLO训练场景里Bus Error的高发位置在哪里
我后来把YOLO训练中遇到Bus Error的场景归纳成三类,这几个位置基本覆盖了90%的病例:
- 数据集规模大、ultralytics自动缓存标签或缓存图像时
- DataLoader开了多worker,batch size又调得比较大
- 在Docker容器里跑训练,且没有给容器单独设置共享内存大小
这三种情况表面上看起来不一样,根因却是同一个:/dev/shm这个共享内存区域被写满了。数据加载的worker进程在尝试继续写入或读取共享数据块的时候,因为底层内存无法扩展或已经被回收,直接触发了SIGBUS信号,进程崩溃。
要解释清楚这个问题,就得从PyTorch的DataLoader工作机制说起。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从DataLoader到/dev/shm:共享内存是怎么被耗空的
2.1 PyTorch的DataLoader为什么依赖共享内存
PyTorch的DataLoader在单进程模式(num_workers=0)下,数据加载在主进程里同步完成,不涉及进程间通信。但只要设置num_workers>0,主进程和worker进程之间就要传递批量数据。如果通过管道传递,免不了反复序列化和拷贝,效率很低。
所以PyTorch采用了一种更高效的方式:每个worker把准备好的batch数据序列化后写入一块共享内存,然后通过IPC消息通知主进程"数据准备好了",主进程直接读取这块共享内存,整个batch的数据就传过去了。这个共享内存,在Linux上默认落在 /dev/shm 这个tmpfs挂载点上。
这里关键的一点是:num_workers越大,同一时刻并行准备数据的wor
