上周四晚上,我又经历了一次通宵排查。
我们的App有一个AI滤镜功能,用手机的NPU来做AI推理,速度快、功耗低。这个功能上线已经有一段时间了,大部分手机上都运行正常。但那天晚上,客服突然收到大量用户反馈,说在新款AI手机上,一用AI滤镜功能就闪退。
我当时正在吃晚饭,看到群里的消息,饭也顾不上吃了,赶紧回到电脑前开始排查。没想到,这一排就是一夜。
这篇文章,我想记录一下这次NPU相关Bug的排查经历。从发现问题到定位原因,从尝试各种方法到最终解决,聊聊排查过程中的思路、踩过的坑、学到的经验。如果你也在做移动端AI开发,或者对NPU推理感兴趣,希望这篇文章能给你一些参考。
问题初现
先说说问题是怎么发现的。
那天晚上七点多,客服群里开始有用户反馈,说我们的App在新款AI手机上使用AI滤镜功能时会闪退。一开始只有一两个用户,我们以为是个别情况。但不到一个小时,反馈就增加到了几十个,而且都是同几款新发布的AI手机。
我意识到这不是个别问题,而是一个批量的兼容性问题。我赶紧拉了一个临时群,把客户端、测试、运维都拉了进来,开始排查。
首先,我让测试同学找了一台同款手机来复现。测试同学在测试机上安装了我们的App,打开AI滤镜功能,果然,一应用滤镜就闪退了,而且是必现。
我让测试同学抓取了崩溃日志。日志显示,崩溃发生在NPU推理的native层,错误信息是:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)
backtrace:
#00 pc 0000000000123456 /vendor/lib64/libnpu_runtime.so崩溃发生在厂商的NPU运行时库里,是一个段错误。这说明,我们的App在调用NPU推理的时候,触发了厂商NPU库的bug,导致了崩溃。
但问题是,同样的代码,在其他手机上运行正常,为什么在这几款新手机上就崩溃了呢?
第一次尝试:怀疑模型问题
我的第一反应是,是不是我们的AI模型有问题?
我们的AI滤镜功能,用的是一个经过量化的轻量级神经网络模型。这个模型在很多手机上都测试过,包括不同品牌、不同芯片的手机,都没有问题。
但这几款新手机用的是最新的NPU芯片,可能对模型的要求不一样。比如,新的NPU可能不支持某些算子,或者对量化的格式有特殊要求。
我让测试同学在这几款手机上跑了一下我们的模型兼容性测试工具。结果显示,模型中的所有算子都是这几款手机的NPU支持的,量化格式也没有问题。
这就奇怪了。既然模型和算子都支持,为什么会崩溃呢?
我又怀疑是不是模型的输入尺寸有问题。我们的模型输入是256x256的RGB图像,这是很标准的输入尺寸,应该不会有问题。但我还是让测试同学试了一下不同的输入尺寸,结果不管什么尺寸,都会崩溃。
看来,不是模型的问题。
第二次尝试:怀疑NPU驱动版本
既然模型没问题,那会不会是NPU驱动的问题?
这几款新手机刚发布不久,NPU的驱动可能还不够稳定,有一些bug。我们的App可能触发了驱动中的某个边界条件,导致了崩溃。
我查了一下这几款手机的NPU驱动版本,发现它们用的是最新版本的驱动,比我们之前测试过的版本都新。
我怀疑是新驱动引入了bug。我联系了厂商的技术支持,把崩溃日志和复现步骤发给了他们。厂商的技术支持说,他们会跟进,但需要时间,可能要几天才能有结果。
几天?我们等不了几天。线上有大量用户在反馈,必须尽快解决。
我决定不等厂商的回复,自己继续排查。
第三次尝试:降级NPU SDK
我们的App用的是厂商提供的NPU SDK来做推理。我想,会不会是SDK版本太新了,和新手机的驱动不兼容?
我们用的是最新版本的NPU SDK。我决定试一下降级到旧版本的SDK,看看能不能解决问题。
我下载了几个旧版本的SDK,分别编译了测试包,让测试同学在崩溃手机上测试。
测试结果让我很意外:不管用哪个版本的SDK,都会崩溃。甚至用一年前的旧版本SDK,也会崩溃。
这说明,不是SDK版本的问题。问题出在更底层的地方。
这时候,已经是晚上十一点了。我有点焦虑,但还是继续排查。
第四次尝试:对比正常手机和崩溃手机
既然直接找原因找不到,我决定换一个思路:对比正常手机和崩溃手机的差异。
我让测试同学在一台正常手机和一台崩溃手机上,分别运行我们的AI滤镜功能,同时抓取详细的NPU推理日志。
对比两份日志,我发现了一个关键的差异:
在正常手机上,NPU推理的时候,会先把模型编译成NPU可执行的格式,然后执行推理。整个过程很顺利。
但在崩溃手机上,模型编译的过程和正常手机不一样。正常手机编译模型的时候,会输出很多编译信息,而崩溃手机编译模型的时候,输出的信息很少,而且在编译完成之后,执行推理的时候就崩溃了。
我仔细对比了编译日志,发现了一个细节:在崩溃手机上,模型编译的时候,有一个算子的编译方式和正常手机不一样。
具体来说,我们的模型中有一个卷积算子,在正常手机上,NPU会用一种优化的方式来执行这个卷积。但在崩溃手机上,NPU用了另一种方式来执行这个卷积,而且这种方式可能有问题。
为什么同样的算子,在不同手机上的编译方式不一样呢?
我查了一下,这几款新手机的NPU用了新的架构,和之前的NPU架构不一样。新架构对某些算子的优化方式不同,可能在某些情况下会触发bug。
找到了这个差异之后,我开始想办法解决。
第五次尝试:禁用特定算子的NPU加速
既然问题出在某个算子的NPU编译上,那我能不能禁用这个算子的NPU加速,让它在CPU上执行呢?
NPU SDK通常都有一个机制,可以指定哪些算子在NPU上执行,哪些算子在CPU上执行。我查了一下SDK的文档,发现确实有这样的配置。
我把那个有问题的卷积算子配置为在CPU上执行,其他算子还是在NPU上执行。然后编译了一个测试包,让测试同学在崩溃手机上测试。
测试结果:不再崩溃了!AI滤镜功能可以正常使用了!
我终于松了一口气。问题找到了,就是那个卷积算子在新架构NPU上的编译有问题,导致了崩溃。禁用这个算子的NPU加速,让它在CPU上执行,就能解决问题。
但这只是一个临时方案。禁用NPU加速之后,这个算子在CPU上执行,推理速度会变慢,功耗也会增加。虽然用户感觉不太明显,但毕竟不是最优解。
我需要找到一个更好的解决方案。
第六次尝试:调整模型结构
既然问题出在那个卷积算子上,那我能不能调整模型结构,避开这个有问题的算子呢?
我仔细分析了一下那个卷积算子。它是一个1x1的卷积,用来做通道数的调整。这种卷积在神经网络中很常见,一般不会有问题。
但我发现,这个1x1卷积的输入通道数和输出通道数都是32,而且用的是某种特定的激活函数。可能是这种组合在新架构NPU上触发了bug。
我尝试了几种调整方案:
第一,把1x1卷积改成3x3卷积。改完之后测试,还是崩溃。看来不是卷积核大小的问题。
第二,去掉激活函数,把激活函数移到后面。改完之后测试,不再崩溃了!看来是1x1卷积和某种激活函数的组合,在新架构NPU上有问题。
第三,把激活函数换成另一种。改完之后测试,也不再崩溃了。看来问题确实出在激活函数上。
找到了根本原因之后,我选择了第二种方案:把激活函数移到卷积后面,作为一个单独的算子。这样既不影响模型的精度,又能避开NPU的bug。
我重新训练了模型,确保精度没有下降,然后编译了测试包,让测试同学在崩溃手机上测试。
测试结果:不再崩溃,推理速度和之前一样快,功耗也正常。完美!
这时候,已经是凌晨四点了。我终于找到了一个完美的解决方案。
第七次尝试:灰度发布和验证
找到解决方案之后,我没有马上全量发布,而是先做了灰度发布。
我把修复后的版本发布给了一小部分用户,包括那些之前反馈崩溃的用户。然后密切关注崩溃率和用户反馈。
灰度发布了两个小时,崩溃率降到了零,也没有用户再反馈AI滤镜功能有问题。而且,AI滤镜的使用时长和成功率,和正常手机上的表现一致。
确认没有问题之后,我才全量发布了修复版本。
发布之后,我又观察了几个小时,确认崩溃率保持在零,用户反馈也正常。这时候,天已经亮了,早上七点多。
我终于可以放心地去睡觉了。
排查过程中的经验
这次排查,我学到了很多经验。
第一,新硬件带来新的兼容性问题。AI手机的NPU是一个新的硬件,不同厂商、不同型号的NPU,架构和驱动都不一样。做移动端AI开发,一定要关注新硬件的兼容性,提前做好适配和测试。不要以为在旧手机上能跑,在新手机上就一定能跑。
第二,NPU推理的调试很困难。NPU推理发生在native层,而且是厂商的闭源库,出了问题很难调试。崩溃日志往往只有一个地址,没有详细的错误信息。这时候,需要用对比的方法,对比正常手机和崩溃手机的差异,一步步缩小范围,找到问题所在。
第三,要有降级方案。NPU推理可能会因为各种原因失败,比如模型不兼容、驱动有bug、硬件不支持等。在设计的时候,一定要有降级方案,比如NPU推理失败的时候,自动降级到CPU或者GPU推理。这样,即使NPU出了问题,功能也能正常使用,只是速度慢一点。
第四,和厂商保持良好的沟通。NPU的问题,最终还是需要厂商来解决。和厂商的技术支持保持良好的沟通,能帮助你更快地定位和解决问题。虽然这次我没有等厂商的回复就自己解决了,但厂商的技术支持还是给了我一些有用的信息。
第五,灰度发布很重要。修复了bug之后,不要马上全量发布,先做灰度发布,观察一段时间,确认没有问题再全量。这样即使修复方案有问题,影响的范围也有限。
第六,模型设计要考虑兼容性。在设计AI模型的时候,要考虑到不同硬件的兼容性。尽量用标准的、广泛支持的算子,避免使用太冷门或者太复杂的算子。这样,模型在不同硬件上的兼容性会更好。
这些经验,我会记在心里,以后遇到类似的问题就能更快地解决。
后续工作
问题虽然解决了,但还有一些后续工作要做。
第一,把这个问题反馈给厂商。我把详细的问题分析和复现步骤发给了厂商的技术支持,让他们修复NPU驱动中的bug。这样,其他开发者也不会遇到同样的问题。
第二,完善兼容性测试流程。我们要建立一个更完善的NPU兼容性测试流程,包括不同品牌、不同型号、不同驱动版本的手机。每次发布新版本之前,都要在这些手机上做完整的兼容性测试。
第三,优化降级机制。我们的降级机制还不够完善,目前只在NPU初始化失败的时候才降级。以后要优化降级机制,在推理失败、超时、结果异常的时候,也能自动降级。
第四,关注NPU技术的发展。NPU技术发展很快,新的架构、新的特性不断出现。我们要持续关注NPU技术的发展,及时适配新的硬件和特性,让我们的AI功能在更多手机上都能流畅运行。
这些后续工作,我会在接下来的几周里逐步完成。
写在最后
这一夜的排查,虽然很累,但很有价值。
它让我认识到,移动端AI开发,不只是把模型跑起来那么简单。硬件的多样性、驱动的不稳定性、算子的兼容性,都是需要面对的挑战。作为开发者,我们要做好充分的准备,应对各种可能的问题。
它也让我认识到,排查问题的能力,是开发者最重要的能力之一。面对一个陌生的问题,如何从现象出发,一步步分析,缩小范围,最终定位到根本原因,这需要经验、耐心和系统性的思维。
最后,我想说,线上Bug不可怕,可怕的是出了问题之后不重视、不反思。每一个线上Bug,都是一次学习的机会。从Bug中学习,从问题中成长,这才是一个成熟开发者应该有的态度。
希望这篇文章能给你一些启发。如果你也遇到过类似的NPU兼容性问题,或者有其他移动端AI开发的经验,欢迎在评论区交流。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录