新年第一篇博客,分享一个组内硬件同学踩过的大坑。这可能是我见过的最神奇的Bug,因为它诡异地体现出量子力学中不确定性原理的特性:不可能同时令Bug复现并且观察到这个Bug所导致的异常效果。
首先介绍一下整个事情的技术背景。我们组的主要工作内容是为FPGA编写数值计算逻辑,用于计算密集型业务的性能加速(硬件方向),然后为这些硬件功能编写相应的软件驱动、API并且将其接入业务方模型(软件方向)。
从软件的角度来讲,用FPGA做计算加速大致是这样的一个过程:硬件通过PCI-E接口连接到主机(Host端),将自身的命令寄存器映射到Host端的一小段连续物理内存上面,这样当Host端向这段内存地址读写数据时,就相当于在发送指令给FPGA,然后硬件根据指令完成相应的计算工作,最后再结果拷贝回Host,从而完成运算。以矩阵乘法为例,那么这个过程就是:
- 矩阵数据原本存储于Host端的DDR内存中,通过DMA(Direct Memory Access)传输到FPGA板卡上的DDR内存中;
- Host端向相应的内存地址(也就是FPGA的命令寄存器)写数据(其实就是在执行mov),从而向FPGA发送计算指令;
- FPGA的硬件逻辑开始执行计算,当然这个过程可能要持续一段时间;
- 计算完成后,FPGA发起一个硬件中断,告知Host端已经计算完成;
- Host端将结果从FPGA上DMA拷贝到自己的DDR内存中,从而完成计算。
显然,上面第三步的计算耗时是有上限的,如果Host端发现FPGA迟迟不返回中断来通知自己计算已经完成,那么Host端就会认为硬件出现了故障,从而引发一个超时错误,这也是硬件方向的同学们最害怕出现的一种情况。
某一天,在对硬件逻辑进行压力测试的时候,发现在多线程压力足够大(大于三个线程以100%CPU占用率同时不停向FPGA发送指令)的情况下,出现了非常稳定的指令执行超时现象,但是单线程的情况下却一切正常。经过硬件同学的一周奋战,把所有新增逻辑都排查了一遍,终于发现原来其实是先前一直运行正常的一个老硬件逻辑模块中实际上存在一个资源竞争的Bug。
细节大致是这样的,先前已经提到过,在发起计算前,我们先要把计算所需的数据通过DMA传输到FPGA上面。而包括DMA在内的所有指令,在执行的时候都需要依赖FPGA上的内存管理单元(MMU),它被一种类似于硬件逻辑上的“锁”机制所保护着,只有获得这把锁的硬件指令逻辑才能继续执行下去。然而,由于DMA指令执行得非常快,在那个老模块中,不等到获取这把锁,硬件就错误地向Host端发起了中断,告知驱动程序DMA已经完成,可以发送下一条指令了。所以这里就是引发Bug的地方,事实上此时硬件逻辑还不能够保证当前DMA传输彻底完成,这个指令是有可能会被接下来的计算指令抢占的。那么等到Host端的计算指令发过来,计算时用的还是尚未更新的数据,自然就产生了数据错误。
这个硬件Bug类比到软件工程上面,其实就是典型的多线程资源竞争错误。然而FPGA特殊的地方就在于,它所执行的计算都是基于硬件逻辑,物理上如果下一条指令的电信号想要抢在当前指令的电信号传播完成之前完成,两条指令之间的时间间隔必须极短,因此这样的指令抢占必须要在非常大的压力下才会出现。然而我们进行压力测试的时候,显然只能在CPU上执行同样的计算逻辑,把结果相对比检查其正确性。但CPU算得一定是比FPGA要慢(不然我们还折腾啥),压测时巨大的数据量又不可能存起来慢慢检查(并且存储也耗时啊),所以只要我们想检查计算结果的正确性,那么就不可能给FPGA带来足够的压力来引发Bug,算出来的结果就完全正确;只有不去观察它(也就是不检查正确性),才有可能引发计算错误。
看起来这样的Bug似乎永远不可能被发现,但最后我们又是如何发现它的呢?上文其实已经提到了,我们并没有发现计算错误而是发现了超时现象,是由于新增的硬件逻辑在数据错误的情况下会执行太多次(比如让硬件执行100次for循环但数据错了,它实际在试图执行1000000次),因为算不完而超时,最终排查到了这里。至于说为什么单线程情况下并不会超时,这个涉及到驱动的一些实现细节,此处就不再赘述了。总之,如果不是因为新增的feature,这个Bug恐怕还会继续默默隐藏在我们的硬件逻辑中不知多久。
还有同学可能会问,如果在业务方在真实使用的过程中触发了这样的数据计算错误,难道跑在上层的应用程序不会有所感知,直接体现出错误么?这其实是因为,这套计算加速系统应用于深度神经网络的训练,而由于DL众所周知的“炼丹”特性,它的鲁棒性足以抵抗这样的随机性错误而不会导致训练误差扩散,甚至有可能这样引入的硬件随机性某种程度上还改善了训练效果(因为这样的小bug可能会起到某种regularization的作用,防止了过拟合),所以……
以上就是这个神奇Bug的全部细节。总结起来就是,最难调试的Bug往往与真实世界的物理特性相联系,从而引入了无穷的不确定性,并且又难以复现甚至难以察觉。