上周三晚上,我经历了入职以来最漫长的一夜。

那天晚上九点多,我正准备下班,运维群里突然弹出一条消息:线上有用户反馈,我们的桌面客户端在新款AI PC上启动就崩溃,而且是必现。

我心里咯噔一下。AI PC是今年的新趋势,各大厂商都在推带NPU的AI PC,我们的产品也在做AI PC的适配。但适配工作还在测试阶段,怎么线上就出问题了?

我赶紧回到工位,开始排查。没想到,这一排就是一夜。

这篇文章,我想记录一下这次线上Bug排查的经历。从发现问题到定位原因,从尝试各种方法到最终解决,聊聊排查过程中的思路、踩过的坑、学到的经验。如果你也做桌面客户端开发,或者对AI PC的兼容性问题感兴趣,希望这篇文章能给你一些参考。

问题初现

先说说问题是怎么发现的。

那天晚上,客服收到了好几个用户的反馈,说我们的桌面客户端在新买的AI PC上安装之后,一启动就崩溃,完全用不了。这些用户的电脑都是最近买的,品牌不同,但都是带NPU的AI PC,操作系统是Windows 11。

客服把问题转给了我们技术团队。我当时正好在值班,就接手了这个问题。

我先看了一下用户的反馈信息。用户说,客户端安装成功了,但双击图标之后,闪一下就没了,进程也消失了。没有任何错误提示,也没有崩溃弹窗,就是静默退出。

我第一反应是,是不是我们的客户端和AI PC上的某个软件冲突了?比如杀毒软件、系统优化工具之类的。但用户说,他们的电脑是新买的,只装了我们的客户端和一些常用软件,没有装什么特殊的东西。

我又想,是不是我们的客户端在Windows 11上有兼容性问题?但我们的客户端在Windows 11上已经运行很久了,大部分用户都是Windows 11,没有出现过这个问题。

那问题到底出在哪呢?

我决定先复现问题。但我们办公室的电脑都是普通PC,没有AI PC,没法直接复现。我只能先看日志,分析问题。

第一次尝试:看日志

我先让用户把客户端的日志文件发过来。

我们的客户端有日志系统,会记录启动过程中的关键信息。用户把日志发过来之后,我打开一看,发现日志只记录到了初始化阶段,然后就断了。没有任何错误信息,也没有异常堆栈,就是突然停止了。

这说明,客户端在初始化的某个阶段崩溃了,而且崩溃得很彻底,连日志都来不及写。

这种情况,一般是底层的问题,比如内存访问错误、DLL加载失败、硬件指令不支持等。

我又让用户把Windows的事件查看器里的应用程序错误日志发过来。Windows的事件查看器会记录应用程序崩溃的信息,包括异常代码、故障模块等。

用户把事件日志发过来之后,我看到了关键信息:

故障模块名称: nvinfer.dll
异常代码: 0xc000001d

异常代码0xc000001d,这是STATUSILLEGALINSTRUCTION,也就是非法指令异常。意思是,CPU执行了一条它不支持的指令。

故障模块是nvinfer.dll,这是NVIDIA TensorRT的动态链接库。我们的客户端用了TensorRT来做AI推理,在AI PC上会调用NPU来加速推理。

问题找到了:我们的客户端在加载TensorRT的时候,执行了一条CPU不支持的指令,导致崩溃。

但为什么会执行不支持的指令呢?我们的TensorRT库是编译好的,应该支持主流的CPU指令集啊。

我查了一下,我们用的TensorRT版本是8.6,这个版本要求CPU支持AVX2指令集。AVX2是2013年推出的指令集,现在的CPU基本都支持。AI PC的CPU都是最新的,不可能不支持AVX2啊。

那问题到底出在哪呢?

第二次尝试:分析崩溃地址

我决定深入分析一下崩溃的具体位置。

我让用户用WinDbg抓取了一个崩溃转储文件(dump)。WinDbg是Windows的调试工具,可以在程序崩溃的时候抓取内存转储,方便事后分析。

用户把dump文件发过来之后,我用WinDbg打开,分析崩溃的位置。

崩溃的指令地址在nvinfer.dll的某个偏移处。我用反汇编看了一下,崩溃的指令是:

vpdpbusd ymm0, ymm1, ymm2

这条指令,我一看就明白了。vpdpbusd是AVX-512 VNNI指令集里的指令,用于深度学习的矩阵运算。AVX-512 VNNI是比较新的指令集,只有较新的Intel CPU才支持。

但AI PC的CPU不都是最新的吗?为什么会不支持AVX-512 VNNI呢?

我查了一下用户的CPU型号。用户的AI PC用的是某厂商的最新款处理器,这款处理器虽然是最新的,但它是能效核(E-core)架构,不支持AVX-512指令集。

原来如此!AI PC为了追求续航,用了能效核架构的处理器,这种处理器不支持AVX-512指令集。而我们的TensorRT库在编译的时候,开启了AVX-512 VNNI的优化,在AI PC上运行的时候,就会执行不支持的指令,导致崩溃。

问题定位到了:我们的TensorRT库编译时开启了AVX-512优化,在不支持AVX-512的AI PC上运行时崩溃。

但怎么解决呢?

第三次尝试:重新编译TensorRT

第一个想到的解决方案是,重新编译TensorRT,关闭AVX-512优化。

但TensorRT是NVIDIA的闭源库,我们没有源代码,没法重新编译。我们只能用NVIDIA提供的预编译版本。

我去NVIDIA的官网看了一下,TensorRT 8.6的预编译版本有两个:一个是支持AVX2的通用版本,一个是支持AVX-512的优化版本。我们之前为了追求性能,用了AVX-512的优化版本。

那解决方案就很简单了:把TensorRT换成AVX2的通用版本就行了。

我赶紧下载了AVX2版本的TensorRT,替换了客户端里的库,然后打包了一个测试版本,发给用户测试。

用户测试之后,反馈说:客户端能启动了,不再崩溃了。

我松了一口气,以为问题解决了。但没想到,新的问题又来了。

用户说,客户端虽然能启动了,但AI功能用不了,一调用AI功能就报错。错误信息是:"TensorRT engine creation failed"。

我一看,头又大了。能启动但AI功能用不了,说明TensorRT能加载了,但创建推理引擎的时候失败了。

为什么会失败呢?

第四次尝试:分析AI功能失败的原因

我又让用户把AI功能的详细日志发过来。

日志显示,在创建TensorRT推理引擎的时候,报错了。错误信息是:

[TensorRT] ERROR: Could not find any implementation for node Conv_0.
[TensorRT] ERROR: Builder failed while profiling allowed tactics.

这个错误的意思是,TensorRT在构建推理引擎的时候,找不到合适的卷积实现,构建失败了。

为什么会找不到合适的卷积实现呢?

我分析了一下,原因可能是这样的:我们的AI模型是在支持AVX-512的机器上导出的,导出的时候,TensorRT把模型优化成了依赖AVX-512指令的引擎。在不支持AVX-512的机器上,这个引擎就没法运行了。

也就是说,问题不只是TensorRT库的问题,还有模型引擎的问题。我们的模型引擎是在特定的机器上构建的,换了机器就可能不兼容。

那解决方案就是,在AI PC上重新构建模型引擎。但我们的客户端是分发出去的,不可能让每个用户都自己构建引擎。

我想了一下,有几个可能的解决方案:

第一,在客户端里集成多种架构的模型引擎,启动的时候根据CPU支持的指令集选择合适的引擎。但这样会增加客户端的体积,而且维护成本高。

第二,在客户端首次启动的时候,自动检测CPU支持的指令集,然后动态构建模型引擎。但构建引擎需要时间,用户体验不好,而且可能会失败。

第三,用更通用的方式导出模型,不依赖特定的CPU指令集。比如用ONNX Runtime,而不是TensorRT。ONNX Runtime有更通用的CPU后端,不依赖AVX-512。

我觉得第三个方案比较靠谱。ONNX Runtime是微软开源的推理框架,支持多种硬件后端,包括CPU、GPU、NPU等。它的CPU后端比较通用,不依赖特定的指令集,兼容性更好。

但我们的客户端已经深度集成了TensorRT,换成ONNX Runtime工作量很大,而且性能可能会下降。

这时候,已经是凌晨两点了。我有点疲惫,但问题还没解决。

第五次尝试:找到折中方案

我冷静下来,重新思考这个问题。

问题的核心是:我们的客户端在不支持AVX-512的AI PC上,TensorRT库和模型引擎都不兼容。

有没有一个折中方案,既能解决兼容性问题,又不用大改架构呢?

我想到了一个方案:在客户端启动的时候,检测CPU是否支持AVX-512。如果支持,就用TensorRT的AVX-512版本和优化后的引擎;如果不支持,就用TensorRT的AVX2版本,并且在运行时动态构建引擎。

动态构建引擎虽然需要时间,但只需要在首次启动的时候构建一次,之后就可以缓存起来。而且,我们可以在后台异步构建,不影响用户使用其他功能。

这个方案的好处是:

  • 不需要大改架构,还是用TensorRT
  • 兼容性好,支持各种CPU
  • 性能有保障,支持AVX-512的机器还是用优化后的引擎
  • 用户体验好,首次构建在后台进行,不阻塞主流程

我觉得这个方案可行,就开始实施。

首先,我写了一个CPU指令集检测的函数,用CPUID指令检测CPU是否支持AVX-512。

然后,我把两个版本的TensorRT库都打包进客户端,启动的时候根据检测结果加载对应的库。

接着,我修改了模型引擎的加载逻辑。如果检测到CPU不支持AVX-512,就不加载预构建的引擎,而是在后台线程中动态构建引擎,构建完成后缓存到本地。

最后,我加了一个降级机制。如果动态构建引擎失败,就禁用AI功能,提示用户当前设备不支持AI功能,而不是让客户端崩溃。

改完之后,我打包了一个测试版本,发给用户测试。

这时候,已经是凌晨四点了。我盯着屏幕,等着用户的反馈。

问题解决

过了大约半小时,用户回复了。

用户说,客户端能正常启动了,AI功能也能用了。首次启动的时候,AI功能显示"正在初始化",过了大约两分钟,就可以用了。之后再启动,AI功能就是立即可用的。

我终于松了一口气。问题解决了!

我又让用户测试了一下AI功能的性能。用户说,AI功能的响应速度还可以,虽然比在普通PC上慢一点,但完全能用。

我又检查了一下日志,确认没有其他错误。一切正常。

这时候,天已经亮了。我看了一下时间,早上六点。从晚上九点到早上六点,我排查了整整一夜。

虽然很累,但问题解决了,心里还是很有成就感的。

排查过程中的经验

这次排查,我学到了很多经验。

第一,新硬件带来新的兼容性问题。AI PC是今年的新趋势,带NPU的AI PC和传统PC在硬件架构上有很大不同。特别是能效核架构的处理器,可能不支持一些高级的CPU指令集。做桌面客户端开发,一定要关注新硬件的兼容性,提前做好适配和测试。

第二,不要假设所有CPU都支持高级指令集。我们之前想当然地认为,现在的CPU都支持AVX-512,所以用了AVX-512优化的库。但实际上,很多低功耗处理器、能效核处理器都不支持AVX-512。在发布软件的时候,一定要做好CPU指令集的检测和降级,不能假设所有CPU都支持高级指令集。

第三,闭源库的兼容性问题要提前考虑。TensorRT是闭源库,我们没法修改它的代码,只能用NVIDIA提供的预编译版本。这就导致,当预编译版本有兼容性问题的时候,我们很难解决。在选择第三方库的时候,要考虑它的兼容性和可维护性,尽量选择开源的、可定制的库。

第四,模型引擎的可移植性很重要。我们的AI模型引擎是在特定机器上构建的,换了机器就可能不兼容。在设计AI推理架构的时候,要考虑模型引擎的可移植性,尽量用更通用的格式,或者在运行时动态构建引擎。

第五,做好降级机制。当AI功能不可用的时候,不要让整个客户端崩溃,而是要优雅地降级,禁用AI功能,提示用户。这样至少能保证客户端的基本功能可用,用户体验更好。

第六,日志和调试工具很重要。这次排查,如果没有详细的日志和WinDbg的崩溃转储,我很难定位到问题。在开发桌面客户端的时候,一定要做好日志系统,集成崩溃转储功能,这样出了问题才能快速定位。

第七,排查问题要有系统性。这次排查,我从日志到事件查看器,到WinDbg分析,到CPU指令集检测,一步步深入,最终定位到问题。排查问题不能靠猜,要有系统性,从现象到本质,一步步分析。

这些经验,我会记在心里,以后遇到类似的问题就能更快地解决。

后续工作

问题虽然解决了,但还有一些后续工作要做。

第一,完善AI PC的适配。这次的问题只是解决了崩溃和AI功能不可用的问题,但AI PC的NPU加速还没有用上。后续要做NPU的适配,让我们的AI功能在AI PC上能调用NPU加速,性能更好。

第二,建立兼容性测试流程。我们要建立一个完善的兼容性测试流程,包括不同品牌、不同配置、不同CPU的AI PC,确保我们的客户端在各种硬件上都能正常运行。

第三,优化动态构建引擎的体验。首次启动动态构建引擎需要两分钟,用户体验还不够好。后续要优化构建速度,或者在安装的时候就预构建好引擎,减少用户等待时间。

第四,考虑切换到更通用的推理框架。长期来看,我们可能需要考虑切换到更通用的推理框架,比如ONNX Runtime,它的兼容性更好,支持多种硬件后端。虽然切换的工作量很大,但从长远来看是值得的。

这些后续工作,我会在接下来的几周里逐步完成。

写在最后

这一夜的排查,虽然很累,但很有价值。

它让我认识到,新硬件带来的兼容性问题,是桌面客户端开发必须面对的挑战。AI PC的普及,会带来更多新的硬件特性,也会带来更多新的兼容性问题。作为开发者,我们要提前做好准备,关注新硬件的发展,做好适配和测试。

它也让我认识到,软件开发没有银弹。我们为了追求性能,用了AVX-512优化的库,结果在新硬件上出了兼容性问题。性能和兼容性,永远是一对矛盾,需要在两者之间找到平衡。

它还让我认识到,排查问题的能力,是开发者最重要的能力之一。面对一个陌生的问题,如何从现象出发,一步步分析,最终定位到根本原因,这需要经验、耐心和系统性的思维。这次排查,让我在这方面又有了进步。

最后,我想说,线上Bug不可怕,可怕的是出了问题之后不重视、不反思。每一个线上Bug,都是一次学习的机会。从Bug中学习,从问题中成长,这才是一个成熟开发者应该有的态度。

希望这篇文章能给你一些启发。如果你也遇到过类似的兼容性问题,或者有其他排查线上Bug的经验,欢迎在评论区交流。