1. 背景

这个软件涉及多个组织、多个人的开发成果,去年在项目A现场,炎热又湿润的海边气候,和J伙伴抢救过一次,这回又开始了。

她支持在1块Nvidia显卡上做视频解码、图像处理和D3D9绘制,去年v1.0版本在项目A的Windows7 + 2块显卡环境下,每个进程对应1个显卡时工作正常。

去年下半年,伙伴为项目B加入了几个新功能发布v1.1,通过了项目B的Windows 10单显卡环境测试和现场验证。现在给项目A升级后发现有1个进程失败,日志中显示CUDA错误:

1
CUDA_ERROR_INVALID_DEVICE: invalid device ordinal

软件各模块的大概关系:

  1. APP调用foo。
  2. foo库通过ffmpeg收流解复用,cuvid(基于CUDA)解码。
  3. foo库通过CUDA Driver分配显存。
  4. foo库通过bar处理解码后图像。
  5. foo库把最终图像渲染到对应显示器。

如下图:

框架

2. 过程

第1天,2022/08/19,周五

  1. 中午接到编写其他文档的任务,因为要处理CUDA问题,跟关联方协调到下周三给出。
  2. 既然每次都发生,不难解决,只需不断缩小范围,直到定位到具体位置,攻克拿下。
  3. Google搜索结果集中在给CUDA Runtime或Driver的API,收到了错误的显卡索引。比对成功和失败进程的不同参数,发现使用0显卡成功,1显卡失败。通过在APP、FOO的各个API调用前后加入日志,排除参数传错。同时借此重新熟悉该软件的组成、代码关系,以及编码、测试的各个环节。

第2天,2022/08/20,周六

  1. 去除编译时数千个字符集警告,降为9个。
  2. 由于是FFMPEG提示CUDA_ERROR_INVALID_DEVICE: invalid device ordinal,比对v4.1.4和v4.4中cuviddec.c和hwcontext_cuda.c,区别主要是支持AV1和AV_CUDA_USE_PRIMARY_CONTEXT参数,在FOO中调整参数试验如旧。
  3. 物我两忘,D伙伴提醒水壶渗水跳闸了,中午听王德峰聊《六祖坛经》,风和幡还有你,都动了,心动说的乖巧是把人和物之间的信息传递割开,给予一个洞察自我存在的机缘。
  4. FOO库的自测软件经过多次加入临时代码,已经不好使用,所以一直在APP中验证。到下午还没有进展时,为缩小范围,修改自测软件跑起来。
  5. 伙伴给自测软件留下的逐个API测试功能非常赞,借此发现不调用BAR库API就不会导致FFMPEG提示错误。
  6. 自测时发现BAR库输出cuda id is zero,而期望是1,看上去有戏。

第3天,2022/08/22,周一

  1. BAR库作者W伙伴告知各个使用CUDA的地方有个CUDA Context概念,同时其某个关键API中要求传入指定显卡的索引,经过比对,发现果然是调用时没有如实传入正确参数。修改后即正常。

3. 答案

BAR库的新版API发生变更,支持指定显卡索引和其他参数,但FOO集成时没有相应修改,使得BAR内部认为工作在显卡0,所以出现显卡0上进程正常,显卡1上进程失败。

也就是说问题发生在BAR库,“损坏”了CUDA环境,导致FFMPEG在BAR损坏之后访问CUDA Driver时错误,FFMPEG是受害者,正因为第一故障点没有留下记录,导致许多精力放在了分析FFMPEG中cuda解码,偏离了方向。

4. 重新出发

应该早些想到该软件频发问题的故障点,是和关键的BAR库有关,着重分析对其调用的时序、参数,以及其内部的机理。

5. 改善点

  1. BAR库头文件中的API支持参数默认值。由于C语言不支持参数默认值,故作为DLL或SO这个层面的库,应避免使用,更显著地要求调用者验证API原型。
  2. 赶进度,没有明确要求v1.1的必要测试用例。这是我的问题,流程上的问题对品质的损害往往大于编码,因为编码着眼于局部,流程缺失的影响范围更大,挽救成本也更高。