深度学习工作程序故障案例的实证研究

2020-12-22 16:35 1126 59

深度学习在许多应用领域都取得了显著成就。为了更有效地训练和测试模型,企业开发人员在共享的多支撑平台上提交和运行他们的深度学习程序。然而,由于代码/脚本缺陷,一些程序在长时间的执行后失败,这降低了开发效率,浪费了 GPU、存储、网络 I/O 等昂贵的资源。

本文首次对深度学习工作的程序故障案例进行了全面的实证研究。从微软的一个深度学习平台收集了 4960 个真正的故障案例。我们手动检查他们的错误信息并将它们分类为 20 类。此外,我们在 400 个故障样本上确定了常见的根本原因和错误修复解决方案。为了更好地理解当前深度学习测试的 debug 实践,我们还进行了开发人员访谈。我们的主要发现有:(1)48.0%的故障发生在与平台的交互中,而不是代码逻辑的执行,这主要是由于本地和平台执行环境之间的差异;(2)深度学习特有故障(13.5%)主要是由不恰当的模型参数/架构和框架 API 的错误解析引起的(3)目前的调试实践在许多情况下对于故障定位是无效的,开发人员需要更多深度学习特定工具。基于我们的这些发现,我们进一步给出了研究主题和工具支持的相关建议,以促进未来的深度学习发展。

一、引言

近年来,深度学习(DL)迅速成为最成功的机器学习技术之一。随着计算能力和数据量的巨大增长,深度学习已经广泛应用于各个领域(如语音和图像识别、自然语言处理、强化学习游戏等 ),可能会产生许多重要但以前似乎遥不可及的结果。

为了帮助数据科学家训练和测试他们的深度学习模型,企业构建了专用平台,如微软 AzureMachineLearning[3]、AmazonSageMaker[1]和 GoogleCloudAI[2] 允许多个开发人员提交和执行他们的深度学习程序。这些平台是共享的,多租户的,并配备了大量的 CPU,GPU 或新的 AI 加速器为多种深度学习框架提供支持,框架有 TensorFlow(TF)[7]、PyTorch[30]、MXNet[10]和 CNTK[37]。

Philly 是微软的一个类似的深度学习平台,它使用的开源技术和典型的计算硬件构建。每天都有几十个研究团队和产品团队将成千上万的深度学习工作提交给 Philly。然而,我们发现,由于代码或脚本缺陷,这些作业中有相当一部分抛出了运行时异常,并且未能完成。这样的故障,特别是那些发生在长时间的执行后,导致了 GPU、存储和网络 I/O 等共享资源的高度浪费。例如,一个工作在 4 个 NVIDIA Telsa P100 GPU 上运行了 40 个小时,由于测试图像的路径配置错误,然后触发异常。此外,深度学习训练过程的随机性也可能导致意外的故障。因此,了解工作故障的类别和根源对于提高程序质量和节省宝贵资源至关重要。而且,它还可以为预防、检测、调试和修复与深度学习工作相关的程序缺陷提供指导。

虽然对机器学习/深度学习程序[22,38,40,49]的缺陷进行了一些实证研究,但对于运行在远程控制平台上的深度学习工作的程序故障几乎没有相关的工作。与深度学习程序故障最相关的工作是 Zhang 等人[49]和 Islam 等人[22]的工作。然而,他们的主题是从 GitHub 问题和堆栈溢出问题收集的,这些问题在许多方面与 Philly 的工作不同。Jeon 等人的另一项相关工作[23] 研究微软内部深度学习工作的行为。然而,它们的目的是了解集群 GPU 的利用率,而不是减少工作故障。

在本文中,我们对深度学习工作的程序故障和修复进行了第一次全面的实证研究。我们研究了微软数百名开发人员提交的 4960 个故障的工作。这些故障是由开发人员的代码/脚本缺陷引起的,我们排除了底层硬件或系统故障。在提交作业之前,程序可能已经在本地进行了测试,因此有些通过局部测试可以解决的故障可能不在我们的研究中。本研究的目的是对深度学习的故障提供一个系统和概括的理解,这可能在共享平台中实现故障减少和资源节约。具体来说,我们的研究旨在解决以下研究问题:

RQ1:哪些类型的深度学习工作故障更多? 为了回答这个问题,我们手动检查了 4960 个失败作业的故障消息,并将它们分类为 20 个类别。我们得到了许多发现。例如,48.0%的故障发生在与平台的交互中,而不是在代码逻辑的执行中,主要是由于本地和平台执行环境之间的差异。详细的结果将在第四节中报告。

RQ2:失败的深度学习工作的共同根源是什么? 为了回答这个问题,我们选择了 400 个失败作业的样本,并手动区分它们的根本原因:源代码或是相关脚本。对于一些复杂的问题,我们联系开发人员进行解释。我们还研究了开发人员如何修复错误。我们得到了许多发现。例如:深度学习特定故障(13.5%)主要是由于模型参数/结构不当和 API 误解所致。详细结果将在第 5 节中报告。

RQ3:什么是目前深度学习编程中测试和调试实践? 为了回答这个问题,我们对来自微软的 6 名有代表性的开发人员进行了深入的面对面访谈。我们发现当前的测试和调试实践在许多情况下都是低效的。详细结果将在第 6 节中报告。

在我们的实证研究的基础上,我们还总结了经验教训,并建议未来的工具支持深度学习测试和调试。例如,我们建议对 Philly 和深度学习框架扩展一些工具。

总之,本文做出了以下贡献:

(1)我们对深度学习工作的程序故障进行了第一次全面的研究。我们将 4960 个故障案例手动分类,并分析其中 400 个案例的根本原因。

(2)我们指出了我们的调查结果的影响,并提出了发展深度学习平台和框架的相关建议。

论文的其余部分组织如下。在第二节中,我们概述了新的深度学习编程范式还有 Philly 平台。第三节介绍了研究方法。第四节和第五节介绍了故障分类、根本原因和修复方法。第六节介绍了我们对当前测试和 debug 的用户研究以及在生产环境中的实践。第七节讨论了我们的研究的普适性和未来的故障减少相关的研究工作。我们在第八节中调查了相关工作,并在第 9 节中总结了全文。

深度学习工作程序故障案例的实证研究

二、背景

2.1 深度学习程序

深度学习(DL)是机器学习的一个子领域,它学习分层的数据表示,称为神经网络或模型。开发人员使用 TensorFlow、PyTorch、MXNet、CNTK 等框架编写深度学习程序,加上工具包库,如 NumPy[28],DLTK[31],Detectron[16]和 Fairseq[15]。Python 是最流行的编程语言,而在某些情况下也使用 C++或 Java。

典型的 DL 代码由三个逻辑连续的阶段组成:数据预处理、模型训练和验证以及模型评估。第一阶段通常有输入数据清洗和数据扩增(例如,随机裁剪输入图像以获得更多的训练数据)。接下来,开发人员使用计算原语(如神经网络层(例如 CNN)、激活函数(例如 ReLU)、损失函数优化器(例如 Adam[25])和变量初始化器。该模型由层的权重参数组成,并允许张量(多维数组值)的流动。训练实际上是通过迭代更新模型来找到最佳的权重参数,直到其学习性能(例如损失和精度)满足要求为止。验证集每隔几次训练迭代一次,为超参数的决策(例如学习速率、隐藏单元数)调整或早期停止提供及时的反馈。最后评估(即测试)阶段,开发人员量化评估最终模型性能。

2.2 Philly 中深度学习工作

Philly 是微软的一个 DL 平台,部署在多个物理集群上,配备了不同代的 GPU。每天都有来自诸多研究团队和产品团队的数千个工作执行在 Philly 中,包括机器翻译(例如 Transformer[43])、阅读理解(例如 BERT[13])、目标检测(例如 Mask RCNN[21])、游戏(例如 DQN[27])、广告(例如 Wide&Deep[11])等。

Philly 的作业提交和执行工作流程类似于 MicrosoftAzureMachineLearning、AmazonSageMaker 和 GoogleCloudAI。图 1 概述了 Philly 中工作的生命周期。开发人员首先将他们的代码/脚本和输入数据上传到分布式存储中。然后,它们指定作业配置,如所需进程/GPU、Docker[26]图像的数量,输入/输出文件夹和 Python 代码文件。Philly 提供标准深度学习工具链的 Docker 图像,以建立一个密封的工作执行环境。尽管如此,定制 Docker 图像还允许适配额外的软件需求(例如安装依赖的 Python 库)。可以以手动或自动的方式[17,35]提交具有相同程序但不同超参数的多个作业寻找最好的模型。作业最初在队列中等待调度。一旦选择了作业,Philly 就实例化 Docker 容器来执行启动 Shell 脚本,然后运行 Python 代码文件。如果抛出 Python 异常或 Shell 错误代码,则作业可能失败。为了增加容错,Philly 可以自动重新运行它[23]。每个运行实例称为 retry。最后,作业将进入三个终端状态之一:SUCCEEDED、KILLED(由开发人员主动终止)和 FAILED。

请注意,虽然 Philly 是由微软开发的,但它实际上在原则上类似于其他常用的工业 DL 平台。Philly 的硬件、系统体系结构和作业提交/执行机制[9,23,47]被广泛采用。此外,在 Philly 执行的大多数 DL 程序也使用编程范式(如 Python 和 TensorFlow)。

三、方法

3.1 主题

我们把 Philly 故障的深度学习工作作为我们的研究主题。这些工作是由微软的研究和产品团队提交的,并具有“FAILED”的最终状态。对于每一项失败的工作,我们都对相关信息做了整理。这些信息包括输入数据、源代码、作业脚本、执行日志和运行时统计信息。

我们研究中的故障表现为意料之外的运行时错误。我们在一项工作完成但结果与预期不同时没有研究其语义错误,因为我们没有测试 oracle。我们还排除了故障的工作机会 由于硬件故障或系统问题,因为它们超出了本研究的范围。由于开发人员在提交作业之前可能已经在本地测试了他们的程序,因此可能会出现一些通过局部测试解决所以不存在与我们研究中的故障。

为了研究故障分类,我们选择了 4960 个由开发人员代码/脚本缺陷引起的故障工作,时间范围为 2018 年 10 月的 3 周内。我们称之为完整样本集。由于 Philly 的重试机制,故障的工作可能有几个实例,我们只考虑了最后一个实例。为了进一步了解根本原因和修复,我们随机选择了 400 个故障的工作,并对它们进行了详细的分析。我们称之为小样本集。

3.2 故障分类

全样本集中的作业故障是根据其运行时错误类型手动分类的。对于每个故障的作业,我们首先通过搜索带有关键字的执行日志来定位故障消息,如 assert, wrong, error, exception, fault, fail, crash, unsuccessful 等。然后,我们手动检查了故障周围的所有相关日志消息,理解了上下文,过滤掉了错误定位导致工作终止的失败,并对其进行分类。例如,消息“cuda running time error(2):内存不足”的故障被归类为 GPU 输出内存不足,而不是 CPU 内存不足。我们还从执行的角度将所有故障类别分为 4 个主要维度:深度学习特性、执行环境、通用代码错误和数据。我们在小组会议上讨论了每一次故障的类别,直到达成共识。如果有差异,我们联系了工作提交人寻求帮助。

3.3 索引与修复

我们手动研究了小样本集中每个故障作业的源代码和脚本。对于数据相关故障,对相应的输入数据进行了额外的检查。然后分析了每次故障其发生阶段和根本原因。我们还通过匹配作业和提交者名称来确定作业的后一个“固定”版本,并通过比较来检查错误修复更改故障的和固定的版本。如果一个复杂的修复超出了我们的理解,我们联系了工作提交人进行澄清。要计算故障的运行时成本,我们只需计算它的所有 GPU 服务时间(即 GPU 数量 × 作业执行时间)。

3.4 有效性威胁

对内部有效性的威胁:由于工作的复杂性和大量的人工操作,在故障分类和根本原因分析中可能存在主观性。为了减少这种主观性,在做出决定之前,我们努力实现群体共识。对于工作不明确或困难的故障,我们也直接与开发人员沟通,寻求澄清。

对外部有效性的威胁:我们的研究对象都是从 Philly 收集的。虽然在 Philly 和其他的深度学习平台在软件栈、平台架构和作业管理方面有明显的相似之处,但是有可能有些发现可能不存在于其他平台或其他公司。为了减轻这种威胁,在这项研究中,我们尽量只涉及 Philly 和 Microsoft。在第 7 节中,我们讨论了可以推广到类似于 Philly 的其他平台的发现。

四、故障分类

在本节中,我们介绍了完整样本集中 4960 个故障的分类。表 1 总结了 20 个故障类别,图 2 显示了 4 个类别的故障分布。

4.1 深度学习相关

13.5%的故障是深度学习相关的,分为 5 类。TensorMismatch 是指张量的形状或名称与它的期望不匹配。通常有 3 情况:

(1)相互连接的模型层具有不兼容的张量需求

(2)将形状不匹配的输入数据输入到模型中

(3)将不匹配的模型文件恢复到构建的模型中

当工作集超过 GPU 可用时,GPU 退出内存,故障发生。它是这个维度中的顶级错误类型。由于 GPU 内存相对有限,开发人员需要非常仔细地对模型进行大小调整。当作业运行时,若作业在主存中运行(处理大量数据时),CPU 内存耗尽,故障发生。Loss NaN 表示计算的损失值变得未定义或不可表示(例如,0×log0)。

最后一类是框架 API 误用,这意味着框架 API 调用违反了固有的假设。例如,TensorFlowAPI“session.run()”假设所有变量都应该初始化正确。否则,它会抛出一个异常消息“FailedPreconditionError: Attempting to use uninitialized value”。

结论 1:13.5%的故障是深度学习相关的。在这 5 个类别中,GPU 内存不足是占比最大的,占该维度故障的 65.0%。

深度学习工作程序故障案例的实证研究

深度学习工作程序故障案例的实证研究

4.2 执行环境

执行环境相关故障发生在与平台的交互中,而不是在代码逻辑的执行中,占总故障的近一半(48.0%)。很明显,平台提供的执行环境与本地执行环境是不同的。例如,所需的 Python 模块(例如 Fairseq[15])或依赖的 DLL(例如 libcudnn.so)可能没有预先安装在 Docker 容器中,然后发生库未找到的故障。允许开发人员对执行环境进行修改。但是,如果他们没有足够的权限,则此操作故障并触发权限拒绝故障。例如,开发人员在所需的 Fairseq 库的启动脚本中执行“pip install Fairseq”。此命令不能成功,因为需要 root 权限。它需要识别您的“--user”选项,以便将库安装到它的主目录中。这个维度中的主要错误类别是路径未找到(39.4%),这意味着编译器通过了不存在的文件或目录。例如,开发人员在提交作业之前忘记更改代码中的本地文件路径。

结论 2:近一半(48.0%)的总故障发生在与平台的交互中,而不是在代码逻辑的执行中。在这三个类别中,路径未发现是主导的,它在这个维度中占比 82.1%。

4.3 代码错误

我们发现,36.5%的故障是常见的,类似于其他计算机程序。14.6%为 Illegal Argument(不符合要求的 Argument),其中近一半为、在启动脚本中将参数输入 Python 代码时导致的非法 Argument。还有其他类别,如 Key Not Found(使用不存在的密钥访问集合项)、Attribute Not Found(涉及不存在的 Python 类字段或函数)、空引用和语法错误(例如,不正确的 Python 缩进)。由于 Python 是一种动态编程语言,所以类型 Mismatch 故障都相当普遍(11.4%)。例如“TypeError: join() argument must be str or bytes, not ‘PosixPath’”

结论 3:一般代码错误维度中的故障占总故障的很大一部分(36.5%),非法 argument 和类型错配是其中占比最多的两类。

4.4 数据

此维度中的故障与无法处理的意外数据有关,可能是由于上传失败或对数据属性的错误解析。这两个类别之一是 Corrupt Data,这表明数据完整性受到损害。例如,可能被意外截断的 JSON 文件会丢失属性值对中的值部分。另一个是 Unexpected Encoding,这意味着一些数据字段无法正确编码或解码。我们注意到一个程序,它使用默认的 ASCII 编码来解码带有一些非 ASCII 字符的字符串。

结论 4:一些深度学习作业(2.0%)无法处理数据错误,如损坏数据和错误数据编码。

五、索因和修复

我们对小样本集(400 个故障)进行了根本原因和修复分析,因为它需要大量的人工来研究源代码/脚本。我们还联系了开发人员,必要时进行澄清。我们定义了 5 种常见的故障原因。。图 3 显示了 400 个故障的故障类别和根本原因的分布。在下面的小节中,我们将详细描述根本原因,并演示一些故障程序和相应修复过程的示例。

深度学习工作程序故障案例的实证研究

5.1 环境区别(DE)

虽然 Philly 通过标准 DL 工具链的 Docker 图像建立了一个密封的作业执行环境,但小样本集中的 140(35%)故障是由本地和平台之间的执行环境差异引起的,占 GPU 服务时间的 14.8。主要差异包括环境变量、输入/输出路径、依赖软件、Python 版本、框架和工具 工具包库等。另一个显著的区别是,在新凭证下的 Docker 容器中运行用于本地机器的 DL 作业,授予的权限很可能与这些权限不同。

这些故障大多在执行环境维度。路径未找到类别的修复是参数化文件/文件夹路径并从中获取它们外部参数或配置文件。此外,开发人员应尽早验证这些路径,因为以后的故障发生(例如模型评估阶段的故障)将会浪费很多资源。库未找到和部分权限被拒绝(将 Python 包安装到系统文件夹中)故障可以通过自定义 Docker 映像来解决,所有所需的软件都是预先输入的停滞了。采样语法错误失败是由于误用了早期版本的 Python,也可以这样解决。如果在作业 init 期间手动安装了相关软件初始化,开发人员应该向 pip 命令指定“--user”选项,并将 Python 包安装到他们的主文件夹中。剩下的认证失败的故障是由于操作外部文件和文件夹的权限不足。修复方法是预先设置正确的访问模式(例如,在启动 Shell 脚本中执行“chmod”)并在编码的早期保证其有效化。

结论 5:执行环境维度中的大多数故障是由本地和平台之间的环境差异造成的。许多差异(安装的软件、存储路径、授予的权限等。) 使 DL 程序容易出错。

含义:鼓励开发人员使用自定义 Docker 映像,并预先安装所有所需的软件,修改代码以获得更多的环境适应性,并尽早验证路径/任务。

5.2 不合适的模型参数/结构(IPS)

43 个(10.75%)小样本集中的深度学习特定故障都是由不适当的模型参数(例如学习速率和批次大小)或模型结构引起的。

5.2.1GPU 存储不足

这 39 个故障中的大多数是由于批量大小过大和/或模型过于复杂。图 4 显示了一个示例。更大的批量大小和更复杂的模型可以提高模型学习性能。然而,它们显著增加 GPU 内存消耗。目前,由于缺乏必要的工具支持,开发人员很大程度上依靠他们的领域知识来选择最优的模型配置。例如,在某些情况下,开发人员必须使用使内存有效的 1×1 核卷积[39]。 目前有一些正在进行的工作可以用来解决内存不足的问题。例如,GPU 内存消耗是静态估计的某些计算机视觉任务[34]。另一种很有前途的技术是 GPU 内存虚拟化,其中数据在必要时在主机和 GPU 之间[36,44]自动交换。TensorFlow 还具有基本的 GPU 内存交换机制(即“swapmemory”参数),它是在某些模型层(例如 tf.nn.dynamic中启用的 RNN[4])。交换应该有效地实现,否则数据延迟会减慢计算速度。

结论 6:GPU 退出内存故障主要是由过大的批处理大小和/或过复杂的模型引起的。

含义:考虑到可用的 GPU 内存和预期的学习性能,开发人员应该主动选择最优模型参数和结构。GPU 内存的评估和虚拟化是两种很有前途的技术。

5.2.2Tensor Mismatch&Loss NaN

虽然在小样本集中只有 4 个这样的故障,但它们非常具有 DL 的特异性,值得更多的调查。

两个 Tensor Mismatch 故障中的一个是由变量名不匹配引起的 保存的模型文件和构造的模型。与开发人员沟通后,发现其根本原因是 Pytorch 中单个 GPU 和多 GPU 之间的不同的变量命名规则。更特别地,储存的模型文件来自多 GPU 训练作业,而构造的模型用于单 GPU 推理。模型恢复无法匹配这些变量名。修复的方法是在构造的模型中使用匹配的变量名或切换到多 GPU 推理。另一次故障在图 5 中显示,其根本原因来自初始输入数据与构造模型之间的形状不匹配。修复方法是将数据排列到正确的形状。

Loss NaN 故障根源于深度学习算法的随机性。其中之一发生在培训和验证阶段开始后很长时间。我们联系了开发人员寻求帮助,很难发现根本原因。开发人员告诉我们,这项工作是微调一个预先训练的模型。然而,如果这种预先训练的模型有某些有问题的参数,梯度可能导致不良的训练情况,最终导致 NaN 的损失。一个快速的解决办法是尝试一个新的预先训练的模型从候选集。另一个可能的根本原因是学习率过大,其修复方法只是降低其价值。开发人员也提出 Loss NaN 有时是由于初始输入或中间结果中的特殊数据所致。把它们过滤掉可以解决问题。Loss NaN 故障是不确定的。因此,修复在很大程度上取决于领域知识。

深度学习工作程序故障案例的实证研究

5.3 API 错误解析(APIM)

19(4.75%)的故障是由于错误地理解了框架/图书馆 API 所做的复杂假设。图 6 显示了由不正确的 API 参数值触发的框架 API 误用故障。错误信息是“ValueError: Variable attweights/kernelweightsconv already exists, disallowed. Did you mean to set reuse=True or reuse= tf.AUTOREUSE in VarScope?”。因为 TensorFlow 变化“kernelweightsconv”(第 25 行)能够在几个 GPU 之间共享(第 16、19、20 行),其父范围(第 24 行)应该用显式设置为“TF”的参数“重用”来构造。autoreuse”。开发人员不知道这个假设,忘记设置“重用”参数。因此,使用了默认值,TensorFlow 运行时第二次无法创建变量。

一些故障是由内部 API 的演变引起的。软件升级后,DL 相关组件之间的接口发生了变化,从而破坏了内部兼容性。开发者可能会认为他们的程序仍然有效,因为这些变化对他们来说是看不见的。图 7 表示了一个失败的 Keras[12]作业,错误消息“TypeError: softmax() got an unexpected keyword argument ‘axis’ while using layers.Dense”。执行日志表明 Keras2.2.2 安装在 Tensor Flow1.3Docker 容器中。开发人员可能认为这是最新版本的 Keras 将与 TensorFlow1.3 兼容。然而,Keras2.2.2 升级了 Softmax 实现,并要求 TensorFlow1.5 或更高版本。修复方法是将 Keras 降级为兼容版本 TensorFlow1.3。此外,为了管理包之间的复杂依赖关系,DL 平台可以提供全局包管理器和依赖检查器。

结论 7:开发人员可能不完全理解框架/库 API 所做的复杂假设,这是由于 DL 相关软件的快速发展,导致了与框架相关的失败 API 误用,非法论证等。这种内部兼容性问题通常是程序看不见的。

含义:开发人员需要更深入地理解框架/库 API。一个定制的 Docker 图像可以帮助减轻这些故障的一部分。

深度学习工作程序故障案例的实证研究

5.4 特殊数据(ED)

在小样本集中,24(6%)故障是由异常数据引起的。图 8 展示了这样一个例子。开发人员希望获得 ROC(接收器操作特性)下的区域的 URE(即 AUC)。然而,在“标签”变量(第 12 行)中只存在一个类别,不能满足计算 AUC 的多类别要求。主动编写异常数据处理代码可以帮助避免这种类型的故障。例如,图 8 中的故障可以用“try-catch”来修复以捕获抛出的异常,从而避免在错误场景中计算 AUC。

DL 开发人员可以学习分布式数据并行编程实践:提交前的手动数据集清理、异常数据处理的防御编程和输入数据抽样进行本地测试[25]。需要更多的抽样工具,这可以帮助开发人员对具有代表性的数据进行抽样,以不仅确保分布,而且还需要一些角落案例来形成更好的本地测试。理想情况下,DL 平台还应该提供数据模式检查器,以自动化确保作业执行前数据的正确性,从而提高用户经验。

结论 8:特殊的数据处理问题对于深度学习编程很重要。DL 开发人员可以学习分布式数据并行编程实践来避免这个问题。

含义:特殊数据处理代码可以改进。DL 框架可以提供数据集 API 来处理这个问题。可以使用数据验证过程或工具来在训练之前采样和检查数据的有效性。

深度学习工作程序故障案例的实证研究

5.5 常见编程错误(CPE)

在小样本集中,近一半(174;43.5%)的故障是由常见的编程错误引起的。其中大多数(例如,第 4.3 节中的路径连接示例)易于理解和掌握,且可以通过引用代码中的错误消息和故障站点进行快速修复。然而,一些故障相当复杂,需要彻底检查代码逻辑。图 9 显示了一个 NLP 工作故障:Key Not Found。原始程序简化很多便于演示。在模型评估阶段,异常被抛出第 21 行;然而,在这里或在相同的情况下没有发现任何线索。在阅读了完整的源代码后,我们注意到第 1 行和第 6 行的两个提示,它们远离原始故障站点。事实证明,开发商错误地禁用了在程序入口[42]RC 失败的代码需要用到的波束搜索。修复方法是为“beam_width”变量添加一个检查。

根据我们的观察,DL 开发人员经常使用他们的代码和脚本中许多参数作控制实验。然而,有时缺少或存在不合适的 argument 可能违反代码中的隐式假设并导致不兼容的配置。失败结果包括非法争论和类型错配,根据我们的统计,这在 CPE 中占主导地位 如图 3 所示。一种可能的解决方案是执行正式的代码审查或高级静态代码分析,以更早地检测编程错误。

六、对当前测试和 debug 实践的用户研究

6.1 研究设计

为了找出 DL 作业失败的原因以及如何解决它们,我们需要更好地理解测试和调试 DL 作业的当前实践。更具体地说:

(1)测试实践。我们会的 我们想知道开发人员是否在与 Philly 相同的环境中本地测试他们的程序。如果没有,他们目前的测试环境是什么。我们还想知道挑战的发展 测试 DL 程序时,ERS 面临。

(2)调试做法。我们想知道开发人员目前是如何调试失败的工作的,他们是如何检测随机 nat 导致的非确定性错误的 对于 DL,他们希望从调试工具中获得什么支持,以及“捕获和复制”工具是否有用。

为了回答上述问题,我们对 6 名代表微软的开发者进行了深入的面对面访谈。所有受访者都是具有 0.5 至 5 年 DL 经验的算法工程师或研究人员。他们在与游戏,计算机视觉,自然语言处理,语音,图形和广告相关的不同产品和任务上工作。表 2 显示了受访者的人口统计数据及其在 DL 中的经验。请注意,虽然只有 6 名开发人员,这项研究的成本是巨大的,因为每次面谈是一对一的,持续 1-3 小时。

深度学习工作程序故障案例的实证研究

6.2 测试实践

由于 Philly 不提供本地模拟器,一个挑战是获得与 Philly 相当的测试环境(具有相同的硬件和 DL 相关软件)。我们首先研究开发人员如何从硬件、开发环境和输入数据三个方面进行 DL 测试。四名受访者(U1、U2、U5、U6)手头有几个 GPU。然而,这种 GPU 内存要小得多,在性能上要比 Philly 的要慢得多。考虑到硬件的限制,所有受访者都承认在本地测试中减少了批量大小、模型复杂度和 GPU 计数。关于这个开发环境,U3 说,由于缺乏强大的 GPU 等原因,他把 Philly 作为试验台。这可能解释了一些简单的 bug 的存在,这些 bug 可以在提交作业之前解决。其他人有自己的本地工作站。他们通过引用 Philly 提供的标准 DL 工具链的 Docker 图像来手动设置一个本机 PythonDL 环境。此外,他们模拟了 Philly 的环境变量,如输入/输出目录。关于输入数据检查,三个人(U1,U4,U6)将使用一个采样较小的数据集进行测试,因为原始输入数据的噪声较大。除了 U3,他们总是在本地机器上放置数据文件。

受访者还告诉我们关于 DL 测试中的测试空间的挑战。由于 DL 的随机性,有大量的超参数组合需要测试。例如,U2 用来向 Philly[35]提交一堆自动 ML 作业。然而,他们只能负担得起一个非常小的候选对象的测试。

此外,受访者描述了在不同 DL 阶段测试的挑战。他们通常更多地关注培训管道的正确性(例如,数据读取,模型构建和序列化)。四位受访者(U2-U5)对模型验证和评估阶段的关注不够。他们可能会在第一个模型检查点之后停止测试 保存成功。为了进一步研究不同 DL 阶段的故障,我们将 DL 作业的执行分为 4 个阶段,并分析了小样本集的故障发生情况,如图所示 乌尔 10。在“初始化”阶段发生的故障数量占总故障的 30.8%,它们只消耗了 GPU 总服务时间的 0.9%。在“模型评估”阶段发生的故障数量占总故障的 24,但它们消耗了 GPU 总服务时间的 81%。这些结果表明,在模型验证和评估中实际上发生的许多故障都可以被测试。

发现 9:由于深度学习的特点,目前的 DL 测试实践往往是不够的。面临三大挑战:

(1)无法比较的测试环境;

(2)测试空间很大;

(3)在不同 DL 阶段进行测试的必要性。

含义:鼓励开发人员测试更多的案例和所有 DL 阶段。平台的本地模拟器,估计 GPU 内存消耗和使用可以用于 DL 测试的测试数据生成器。

深度学习工作程序故障案例的实证研究

6.3 调试实践

我们也对开发人员如何解决故障感兴趣。六位受访者使用传统的代码编辑器(例如 VisualStudio 代码)和调试方法(例如日志记录、断点和单个用于调试 DL 程序的 step。所有这些都对 Philly 的失败作业采用了类似的调试实践:它们首先检查日志文件,理解问题上下文,并将源代码/脚本中的故障定位。对于明显的错误,它们只需将修复方法应用于代码(例如,校正对象类型)、数据(例如,用全新的数据集替换)和实验配置。对于复杂的漏洞,它们转向更多的调试方法(例如的调试方法(例如掉异常数据),并通过利用领域知识(例如,降低 Loss NaN 的学习速率)或框架文档(例如,图 6 中描述的示例)来解决它们。还有一些 DL 特定的工具来帮助调试,例如 TensorFlow 调试器[6]和 Tensor Board[5,45]。开发人员可以使用这些工具跟踪 Inf/NaN 值并定位生成它的操作符。仍然有一些故障难以在本地复制,因此开发人员必须通过一个尝试和错误的过程来调试它们,反复向 Philly 提交修改后的程序并观察结果。

对于 GPU OOM 来说,减少批处理大小通常是开发人员对这个问题的第一个解决方案,然后是随后的解决方案:降低网络结构的复杂性。它们还具有一些特定域的参数,如 NLP 中的最大序列长度和图形中的图像分辨率,这些参数都可以用于降低模型的复杂性。

对于 Loss NaN,开发人员通常试图通过打印中间结果和检查计算来复制它。这个过程通常没有任何调试工具支持,涉及许多耗时的手工工作。

U1、U2、U3 和 U4 提到他们想要一个工具来可视化变量值的变化,他们认为这将有助于调试和提高模型学习性能。全部受访者认为,提供一个重放机制是很好的,它可以在计算过程中捕获一些有用的数据,并在出现故障时重放它们。这将有助于找到与 DL 和异常数据的随机性质有关的错误。

结论 10:在许多情况下,当前的 DL 调试实践对于故障定位是无效的。由于本地和平台之间的差异,现有的调试工具可能无法很好地应对工作环境以及 DL 的随机性。

含义:开发人员需要更多的 DL 特定调试工具。一个很好的重放机制,用于保存中间结果并恢复它们,并在稍后重新运行,这可能有助于调试。内存使用评估工具可以补充当前的调试方法。

七、讨论

7.1 我们工作的普适性

虽然我们的研究完全是在微软进行的,但我们相信我们的失败类别是普遍存在的,我们的大部分结果可以推广到其他 DL 平台。主要原因是在 Philly 执行的大多数深度学习程序都使用常见的编程范式(例如 Python 语言、随机算法、框架和工具包库),并针对常见的应用程序 (例如图像/视频/语音识别和 NLP)。此外,Philly 的硬件、系统架构和作业提交/执行机制[9,23,47]也被广泛采用。

例如,调查结果 1 和 6 显示 GPU 出于内存故障。众所周知,具有复杂网络结构和大批量的 DL 模型可以提高模型的学习性能,但也显著增加内存消耗[13,18]。因此,我们认为发现 1 和 6 是一般的其他平台提供 GPU,甚至其他 AI 加速器,如 TPU。

另一个例子,调查结果 2 和 5 是与环境相关的故障,这对 Philly 的工作来说并不罕见,即使 Philly 通过标准 DL 工具链的 Docker 图像建立了一个密封的作业执行环境。潜在的原因是这样的密封环境不可能总是与开发人员使用的本地环境相同。我们相信这些发现也适用于其他平台,因为开发人员也有可能犯同样的错误。例如,GoogleCloudAI 的故障排除网页列出了几个类似的环境故障。

7.2 未来研究方向

基于我们的研究,我们提出以下未来的研究工作:

(1)平台改善

避免不必要的重试。自动重试是各种平台中常见的设计,主要用于从非确定性系统故障中恢复。开发人员也可以利用这种机制进行非确定性深度学习特定失败(例如,Loss NaN)。然而,如果一个失败决定性地植根于 DL,多次重新运行作业显然是不必要的,只会浪费资源。该平台可以设计更精细的启发式算法来推断是否一份失败的工作值得重试,避免不必要的工作。

本地模拟器。从调查结果 2 和 5 中,我们可以看到,对环境差异的不认识导致了近一半的失败。因此,为本地开发提供一个平台模拟器是可取的。本地模拟器可以表现为平台的单节点实现,该平台导出相同的环境 变量,访问分布式存储,并模拟一个或多个 GPU 设备。

(2)工具支持

GPU 内存消耗的估计。从内存故障中调试和修复 GPU 是相当有挑战性的。选择满足内存消耗要求的良好模型参数/结构,很大程度上取决于开发人员的领域知识或采用一种尝试和错误策略(即提交几个具有不同模型配置的作业)。可以开发一个估计器来基于当前数据分布、模型结构和批处理大小等特性推断 GPU 内存消耗。通过分析源代码和历史 GPU 消耗可以建立预测模型记忆。这些工具可以帮助开发人员更好地估计 DL 程序的内存使用情况,并减少内存不足发生的情况。

静态程序分析。静态程序分析是一种在运行程序的情况下检测代码缺陷的 UL 技术。它将有很大的潜力,积极主动地处理许多类型的工作故障,包括框架 API 误用,TensorMismatch 以及那些在一般代码错误等。跨功能调用执行全程序分析可以减少错误警报。

数据合成。根据结论 8,发展一种通过[24]扩展符号执行技术来测试数据处理逻辑鲁棒性的数据综合工具是有用的。该工具可以生成符合初始输入 di 的特殊数据集用于触发潜在的 bug。

(3)框架改善

自动 GPU 内存管理。虽然 DL 框架隐藏了 GPU 的大部分复杂性,但开发人员仍然需要手动 GPU 内存管理来进行数据放置和消耗控制。框架可以建立一个自动 GPU 内存管理机制,就像 OSES 对主存储器所做的那样。例如,GPU 上的冻结数据可以自动交换出来[36,44]以避免 GPU 内存耗尽。另一个例子是,该机制可以预取出即将进行的计算中使用的输入数据和模型分区。

重放机制。调试 LossNaN 等非确定性故障是有挑战性的,因为很难复制相同的失败执行。框架可以使用现有的重放技术[20]记录精确的执行顺序和与环境的交互。因此 当开发人员想要跟踪故障时,她可以确定性地重放到故障状态,这可以极大地促进故障诊断。

八、相关工作

机器学习/深度学习 bug。Thung 等人[40]和 Sun 等人。[38]对机器学习系统中的 bug 进行了实证分析。这样的系统缺陷也会导致工作失败;然而,我们的研究集中在程序/脚本错误上,这些错误是由 DL 应用程序开发人员编写的。Zhang 等人[49]对 GitHub 和堆栈溢出进行了研究,以研究 TensorFlow 程序的 bug。ISLAM 等人[22]将工作扩展到更多的 DL 框架。他们发现的一些 bug(例如,由于 API 进化而产生的 bug)与我们的观察一致。我们的研究分析了现实世界的工业具有多个 DL 框架的作业(例如,TensorFlow、PyTorch 和 CNTK)。我们发现许多不同类型的错误发生在基于云的 DL 平台上,这是开发人员通过本地测试不容易被发现的。

实证研究。对大数据平台和软件系统[25,46,48,50]的故障进行了一些实证研究。例如,肖等人[46]学习非类似 SQL 的 Map Reduce 程序中的错误。Li 等人[25]分析了分布式数据并行程序中的代码/数据缺陷。周等人[50]对服务质量进行了实证研究 一个生产大数据计算平台的起诉,它有各种类型的问题,包括硬件故障和用户错误。张[48]分析了大型软战中的断层分布系统。我们的研究更多地集中在生产计划失败的深度学习工作。

集群基础设施。许多研究[19,23,33,47]旨在提高 GPU 集群的深度利用率 通过研究独特的工作特点来学习。Jeon 等人[23]率先了解低 GPU 利用率的多租户集群。如此低的利用率来自帮派调度 [29]资源局部性和工作失败。这些作业故障分布在基础设施、DL 框架和用户代码之间。除了对失败症状进行分类外,我们的工作还研究了 DL 程序故障并对其根源进行了深入的分析。

测试。最近,在测试 DL 模型方面有大量的工作。例如,DeepXplore[32]提出了一个新的度量指标用于评估深度神经网络的覆盖范围。Deeptest[41]进一步引入了一个特定领域域的自动测试工具,通过对图像数据的真实转换来最大化神经元的覆盖。TF X[8]构建了一个机器学习管道,它简化了用户在部署中的工作,其中包括一组用于数据分析、转换和验证的系统组件。因此,平台可以显著减少培训失败,提高模型质量。我们的工作提供了对 DL 作业程序故障的系统分析,可以帮助社区设计更好、更实用的用于 DL 测试和调试工具包。

九、总结

与其他计算机程序一样,深度学习程序也可能无法执行。本文描述了第一个关于深度学习工作程序失败的实证研究。我们手动检查微软的 4960 个失败作业中收集的故障信息,并将它们分类为 20 个类别。此外,我们在 400 个故障样本上确定了常见的根本原因和错误修复解决方案。为了更好地了解当前深度学习程序的测试和调试实践,我们还与开发人员进行访谈。根据我们的发现,我们建议可能的研究主题和工具支持,可以促进深度学习培训和测试。我们相信我们的工作可以帮助理解 DL 的质量问题,为今后的深度学习发展提供有价值的指导方针。

致谢

本文由南京大学软件学院 2020 级硕士侯忠昊翻译转述。

推荐阅读
华为p40Pro深度评测:拍照、影像、后期处理 2020-06-03 16:11
PCB电路板有哪些常用材质,有什么区别和差异 2020-06-18 15:21
嵌入式ARM与STM32有什么区别,哪个更容易学? 2020-03-17 14:15
iPhone 11 Pro Max iFixit拆解,不具备反向无线充电、相机额外记忆体 2019-09-23 17:48
什么是运放差分放大电路,怎样计算、其原理和特点 2023-04-19 14:18