异度部落格

学习是一种生活态度。

0%

引言

QLoRA(Quantized Low-Rank Adapter)是一种高效的微调方法,是 LoRA 的量化版本(什么是 LoRA?)。该调优方法由华盛顿大学发表于论文《QLORA: Efficient Finetuning of Quantized LLMs》。通过降低内存使用,实现在单个 GPU 上对大型语言模型进行微调。它可以在单个 48GB GPU 上微调 650 亿个参数的模型,并且能够保持完整的 1 6 位微调任务性能。 7f7733856584f50e20ddf3fdf1711a9d.png

基础技术介绍

Block-wise k-bit Quantization

量化指的是将连续或高精度的数值转换为较低精度(比如较少的位数)的表示形式的过程。这通常用于减少模型的存储需求和加快其运算速度。例如,将一个 FP32 的 tensor 转成 Int8: \[X^{\text{Int8}} = \text{round}\left(\frac{127}{\text{absmax}(X^{\text{FP32}})} \cdot X^{\text{FP32}}\right)=round(c^{\text{FP32}})\] 其中,c 为量化常数。逆量化公式则为: \[\text{dequant}(c^{\text{FP32}}, X^{\text{Int8}}) = \frac{X^{\text{Int8}}}{c^{\text{FP32}}}\] 这种常规的量化方法的局限性也非常明显,当 tensor 中有一个非常大的数字(一般称为 outlier)时,将影响最终的量化结果。

因此,Block-wise k-bit Quantization 方法就是把 tensor 线性平展开,然后分割成 B 段,每段有自己的量化常数 c,独自量化。

Low-rank Adapters

LoRA,全称为“Low-Rank Adaptation”,尤其是在大型预训练语言模型(比如 GPT-3)的微调领域中。它是一种参数高效的模型微调技术,通过引入低秩的权重矩阵来修改预训练模型的自注意力机制。

在大型语言模型的背景下,LoRA 的关键优势是它能够在增加极少量参数的情况下,有效地适应新的任务或数据集。这一点尤其重要,因为全面重新训练这些大型模型需要大量的计算资源,而 LoRA 提供了一种更为节能的替代方案。

具体来说,LoRA 的工作原理是在自注意力模块的键(key)和值(value)矩阵中引入低秩矩阵。这些低秩矩阵与原始的键值对进行相乘,产生新的键值对,用于微调模型。因为这些矩阵的秩很低,所以新增加的参数数量较少,但这些参数的引入足以让模型学习到新任务的特定特征。

LoRA 的这种设计旨在保持预训练模型的大部分权重不变,同时仅对模型进行必要的小规模修改,从而快速适应新的任务。这样的方法在保持模型性能的同时,还能显著减少部署和运行大型模型所需的资源。

b0baa4e120421c06f465b976b92f3af9.png

PEFT

PEFT(Parameter- Efficient Finetune)是一种在自然语言处理(NLP)中常用的方法,特别是在大型预训练语言模型的微调阶段。PEFT 方法的关键思想是,在保持大部分预训练模型参数固定不变的同时,只微调一小部分参数。这些参数可以是添加到模型的新参数,也可以是模型内部的一小部分可训练参数。这样做的好处是显著减少了微调过程中的内存和计算需求,同时仍然保持了模型的表现力。

核心技术

4 bit Normal Quantization

论文中提出的 4-bit NormlFLoat 量化是对 Quantile Quantization(分位量化)进行了改进,并结合上诉 Block-wise Quantization,降低计算复杂度和误差。

以 4-bit 量化为例,参数将被映射到 16 个可能的值(从 0 到 15)中的某一个,类似于四舍五入的方式。然而,这种传统量化方法的一个主要缺点是,量化后的参数分布可能与原始分布相差甚远。比如,如果有一个极大的值,那么大多数参数可能都会被量化到 0,从而导致模型性能显著下降。

为了解决这一问题,作者采用了分位量化(Quantile Quantization)方法。那么,分位量化是什么呢?仍然以 4-bit 量化为例,这意味着有 16 个不同的值可以选择。在分位量化中,首先将输入参数按大小排序(Quantile),然后将它们等分成 16 份(Block-Wise),每份对应一个量化值。这种方法在量化过程中能够更好地保持参数的原始分布,从而在降低参数精度的同时,尽可能减少对模型性能的影响。

上述分位量化会额外引入明显的计算开销,因为每次有参数输入进来都需要对齐进行排序并等分。
作者发现预训练的参数基本上都服从均值为 0 的正态分布,可以将其缩放到[-1, 1]的范围中。同时可以将正态分布 N(0,1)划分为 2^k+1 份,并缩放到[-1, 1]的范围中。这样就能直接将参数映射到对应的分位,不用每次都对参数进行排序。 但是这样做饭也会有一个缺点,参数 0 量化后可能不在 0 的位置上了,就没法表达 0 的特殊意义了。为此作者还做了一点改进,即分别将负数和整数部分划分为 2^k-1 份,参数 0 还是放在 0 原本的位置上。 1d324b80c45e95df588cc34eca486fc0.png

Double Quantization

分块量化中每个 block 都会额外产生一个量化常数 c。以量化 32bit 参数、block 大小 64 为例,每个 block 会引入 32bit 的量化常数,对应每个参数会额外引入 32/64=0.5bit 的额外开销。
论文采用了双重量化方法,如下图所示: 967074c14c1ea352c40d440d2f704242.png 在第一次量化后,并不会直接储存量化常数 c1,而是按照 block 大小 256 对量化常数再量化到 8bit 上去储存,这个阶段会再引入一个量化常数 c2。最终保存的额外参数为 8/64 + 32/(64 · 256)=0.127bits,也就是每个参数减少 0.5-0.127=0.373bits

Paged Optimizers

Paged Optimizer 将优化器需要存储的数据(例如权重和梯度)分成多个小块或“页”。在每个训练步骤中,只加载当前需要的数据页到内存中,而不是一次性将所有数据加载进内存。这种方法允许在相对较小的内存中有效地处理大型模型,因为任何时候都只有部分数据被加载。

QLoRA 训练

所以 QLoRA 最终训练的表达为:

\[ Y^{\text{BF16}} = X^{\text{BF16}} \text{doubleDequant}\left(c_{1}^{\text{FP32}}, c_{2}^{\text{k-bit}}, W^{\text{NF4}}\right) + X^{\text{BF16}}{L_1}^{\text{BF16}}{L_2}^{\text{BF16}} \]

其中:

\[ \text{dequant}\left(c_1^{\text{FP32}},c_2^{\text{FP32}}, W^{\text{k-bit}}\right) = dequant(dequant(c_1^{FP32}, c_2^{k-bit}), W^{NF4})=W^{BF16} \]

参考资料

论文原文:Hidden Technical Debt in Machine Learning Systems

在文章的开篇,Google 工程师们就指出:虽然开发和部署机器学习系统相对快速并且成本低廉,但是持续的维护比较困难且成本高昂。这是由于:

首先,机器学习系统本质上也是一种软件系统,所以它具有其它软件系统中存在的技术债务。更复杂的是,机器学习系统又有很多机器学习相关的技术债务。而这些问题往往存在于系统级别 (system level),而不是代码级别 (code level)。

基于上面的判断,作者们给出了基于他们实践经验的技术债务总结。

复杂模型侵蚀边界 (Complex Models Erode Boundaries)

作者们指出的是:在传统的软件工程中,人们往往需要用封装、模块化等等手段来定义一些抽象边界,以达到改善软件系统的可维护性的目的。然而,在机器学习系统中,由于其依赖于大量的外部数据,这样的边界变得难以准确描述和定义了。

举个例子,如果一个机器学习系统中的模型使用了特征 x1, x2, ..., xn。当 x1 的分布情况变化时,原有模型的其他特征的权重也会发生改变,这就给系统的管理带来了很大的挑战。作者们把这种影响叫做 CACE 原则:

Changing Anything Changes Everything. (改变任何一个就改变了所有)

这个问题称做 Entanglement,作者们给出了两个方案来改善这一问题:隔离模型专注检测模型预测中的变化

数据依赖比代码依赖成本更高(Data Dependencies Cost More than Code Dependencies)

在传统的软件工程中,人们往往可以借助于静态分析(Static Analysis)来发现和分析代码依赖关系。然而在机器学习系统中数据的依赖关系要更难以分析。

第一个问题就是数据的依赖关系可能很不稳定(Unstable data dependencies)。一个简单的例子就是一个机器学习系统可能把另一个系统的输出作为一个输入。然后,这种输入是不稳定的,并且变化是难以觉察的。作者们给出了一个可行的方案是对输入进行版本控制。

另一个问题就是不必要的数据依赖(Underutilized data dependencies),比如随着系统的变化不再需要的软件包。作者们建议用 leave-one-feature-out 的方法,来定期的检查和去除非必要的软件包。

由于数据依赖方面静态分析(Static analysis of data dependencies)工具的缺失,作者们也建议开发相应的工具来帮助分析数据依赖。

反馈循环(Feedback Loops)

对于在线学习系统而言,一个非常重要的特性就是模型需要持续更新。一种常见的实践是建立一个反馈循环,用于持续更新模型。

机器学习系统的反模式(ML-System Anti-Patterns)

在一个实际的机器学习系统中,仅仅一小部分代码是用于学习和预测的。随着时间的推移,一个机器学习系统中会出现各种各样系统设计中的反模式。 f2bd64181b33c5bbf7f894d8042093e6.png

胶水代码(Glue Code)

作者们发现机器学习工程师们倾向于使用各种通用的机器学习库来编写代码,这就导致了系统中存在着大量的来自于庞大的通用机器学习库的代码。作者们的建议是把这些胶水代码用相同的 API 封装起来。

管道丛林(Piepline Jungles)

这里主要指的是数据准备(data preparation)阶段可能存在的问题。如果不加以注意,随着时间的推移,越来越多的输入信号加入到数据准备中,导致其变成一个混杂着各种爬虫、连接、取样等各种数据操作的丛林。有意思的是,作者们给出的解决方案是让工程师和科研工作者更多的合作,譬如在一个团队中,来帮助减少这种问题。

无用的实验代码路径(Dead Experimental Codepaths)

机器学习常常伴随着大量的试验,很容易导致在原有代码中产生各种条件分支。而后,随着条件分支越来越多,系统的技术债务也随之增加。作者们建议定期检查并删除这些无用的代码路径。

抽象债务(Abstraction Debt)

那么为什么会有这么多的技术债务问题呢?作者们的观点是我们目前还是缺乏用来描述机器学习系统的强抽象。无论是 MapReduce 还是 pramater-server 都达不到需要的抽象描述。

常见味道(Common Smells)

在软件工程中,有一个词语常常用来描述可能的代码结构问题,叫做坏的代码味道(bad code smell)。那么这里呢,作者们也列举了几个他们发现的可能的机器学习系统的坏的代码味道。

  • 基础数据类型(Plain-Old-Data Type Smell)
  • 多种语言(Multiple-Language Smell)
  • 原型(Prototype Smell)

配置债务(Configuration Debt)

由于机器学习系统和算法的复杂性,一个大型的机器学习系统中常常存在着大量的配置,譬如使用哪些特征、数据如何选取、具体算法的参数设置、预处理的方式等等。作者们强调了配置的可维护性和易读性。譬如,配置也应该经过代码评审并提交到代码库中。

处理外部环境的变化(Dealing with Changes in the External World)

一个机器学习系统常常要和外部世界交互。然而,一个不容忽视的问题就是外部环境常常存在着各种变化。这无疑给机器学习系统的维护带来了极大的挑战。比如,一个机器学习系统应该如何监测呢?我们知道一个模型上线后,随着时间的推移以及新的数据的注入,常常会发生概念偏移(Concept drift),模型的准确率可能会有较大的变化。所以持续的监控和预警也就是非常必要的了。

论文原文:A Berkeley View of Systems Challenges for AI

这又是篇不涉及过多技术的一篇论文,不过其中提到的 Challenges 确是目前 AI Systems 一直在努力解决的。

文中提到的几点关于 AI System 的 Challenge:

  • Design AI systems that learn continually by interacting with a dynamic environment, while making decisions that are timely, robust, and secure.
  • Design AI systems that enable personalized applica- tions and services yet do not compromise users’ privacy and security.
  • Design AI systems that can train on datasets owned by different organizations without compromising their confidentiality, and in the process provide AI capabilities that span the boundaries of potentially competing organization.
  • Develop domain-specific architectures and soſtware systems to address the performance needs offuture AI applications in the post-Moore’s Law era, including custom chips for AI work-loads, edge-cloud systems to efficiently process data at the edge, and techniques for abstracting and sampling data.

在论文中也提出了一些解决上述 Challenge 的研究方向: ca8b63b2530cd83225cd07e4ef8f8767.png

论文原文:MLSys: The New Frontier of Machine Learning Systems

这是一篇发表意义大于内容本身的一篇论文,它是一个全新的会议 MLSys 的开篇。所谓 MLSys,顾名思义就是 Machine Learning 和 System 的交叉领域。

在论文中,作者从两个维度阐述了 MLSys 涉及的核心问题: 维度一:

  • How should software systems be designed to support the full machine learning lifecycle, from program- ming interfaces and data preprocessing to output interpretation, debugging and monitoring?
  • How should hardware systems be designed for machine learning?
  • How should machine learning systems be designed to satisfy metrics beyond predictive accuracy, such as power and memory efficiency, accessibility, cost, latency, privacy, security, fairness, and interpretability?

维度二:

  • high-level systems for ML that support interfaces and workflows for ML development—the analogue of traditional work on programming languages and software engineering.
  • low-level systems for ML that involve hardware or software—and that often blur the lines between the two—to support training and execution of models, the analogue of traditional work on compilers and architecture.

这篇论文最流弊的地方就是它的作者署名(亮点自寻): b88bb01a94ff3754ea89c7abcfaf4fcc.png

术语解释

  • AD: Active Directory。
  • Service Session Key: 服务会话密钥。
  • Logon Session Key: 登录会话密钥。
  • KDC: Key Distribution Center。
  • KAS: Key Kerberos Authentication Service。它是 KDC 的一个服务。
  • TGS: Ticket Granting Service;它是 KDC 的一个服务。
  • Service Ticket: 服务票据,通过 TGS 获取,主要包括用户信息与 Service Session Key。
  • TGT: Ticket Granting Ticket。通过 KAS 获取,主要包括用户信息与 Logon Session Key。
  • Authenticator: 交互双方预先知晓的信息,通过它来对对方做认证。

KDC 整体结构图

f211a61aa6562d3e013345cfbfced73e.jpg

Kerberos 认证的流程

  1. 客户端通过 KDC 的 KAS 服务获取 TGT。
  2. 通过 TGT 获取 Service Ticket。
  3. 通过 Service Ticket 访问服务资源 。

Client 与 Server 之间的消息交互

  1. 客户端发送消息到 KDC 的 KAS 服务一获取 TGT f8fa180d291d0565ff69fe7366cb569e.jpg
  2. KAS 发送信息到 Client 9be76573f94944dd2a59ad3bd838afa7.jpg
  3. 客户端发送消息到 KDC(Key Distribution Center)的 TGS(Ticket Granting Service)服务一获取 ST(Service Ticket) a87493a61a11c90c157a13010298288f.jpg
  4. TGS 服务返回消息给客户端 32a73a40855355657a004489aa958883.jpg
  5. 客户端通过 ST 访问服务器资源 bdca1c145075a7b1438d7a92b3b2542d.jpg
  6. 客户端对服务器的认证 7c5e6d20ec15393f57d83abdcfb9b305.jpg

之前BASE 理论中提到,分布式系统对一致性做出了权衡。因此涌现出很多分布式协议。其中比较著名的是:两段式提交(Two Phase Commit)和三段式提交(Three Phase Commit)。

两段式提交

定义

每个参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报,决定各参与者是否要提交操作还是中止操作。

2pc

所谓的两个阶段分别是:

第一阶段:准备阶段/投票阶段

  1. 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
  2. 参与者节点执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入日志。
  3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。

第二阶段:提交阶段/执行阶段

当协调者节点从所有参与者节点获得的相应消息都为"同意"时:

  1. 协调者节点向所有参与者节点发出"正式提交"的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"完成"消息。
  4. 协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

  1. 协调者节点向所有参与者节点发出"回滚操作"的请求。
  2. 参与者节点利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"回滚完成"消息。
  4. 协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。

优缺点

  • 优点:原理简单,实现方便。
  • 缺点:同步阻塞,单点问题,数据不一致,容错性不好

三段式提交

定义

由于两段式提交存在诸多缺陷,因此研究者在两段式基础上进行改进,实现了三段式提交。与两阶段提交不同的是,三阶段提交有两个改动点:

  • 引入超时机制 - 同时在协调者和参与者中都引入超时机制。
  • 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。

3pc

所谓的三个阶段分别是:

第一阶段:CanCommit

  1. 协调者向参与者发送 CanCommit 请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  2. 参与者接到 CanCommit 请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回 Yes 响应,并进入预备状态;否则反馈 No。

第二阶段:PreCommit

  1. 协调者向所有参与者节点发出 preCommit 的请求,并进入 prepared 状态。
  2. 参与者受到 preCommit 请求后,会执行事务操作,对应 2PC 准备阶段中的 “执行事务”,也会 Undo 和 Redo 信息记录到事务日志中。
  3. 如果参与者成功执行了事务,就反馈 ACK 响应,同时等待指令:提交(commit) 或终止(abort)。

第三阶段:Do Commit

  1. 协调者接收到各参与者发送的 ACK 响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送 doCommit 请求。
  2. 参与者接收到 doCommit 请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  3. 事务提交完之后,向协调者发送 ACK 响应。
  4. 协调者接收到所有参与者的 ACK 响应之后,完成事务。

优缺点

  • 优点:相对于二阶段提交,三阶段提交主要解决的单点故障问题,并减少了阻塞的时间。
  • 缺点:三阶段提交也会导致数据一致性问题。由于网络原因,协调者发送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执行了 commit 操作。这样就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。

参考资料

  • https://en.wikipedia.org/wiki/Two-phase_commit_protocol
  • https://en.wikipedia.org/wiki/Three-phase_commit_protocol
  • https://juejin.im/post/5b26648e5188257494641b9f

BASE定理简介

BASE理论是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写,由eBay架构师Dan Pritchett提出来的。

BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结,是基于CAP定律逐步演化而来。其核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,才用适当的方式来使系统打到最终一致。

BASE理论的内容

基本可用(Basically Available)

所谓基本可用就是在出现不可预知的故障时,系统主体功能依然可用。一个比较典型的例子就是在电商促销时,为保护购物系统,部分消费有可能会被导到一个降级页面。

软状态(Soft State)

所谓软状态是指允许系统中的数据存在中间状态,并认为这种状态不影响系统的整体可用性。典型的例子如在分布式文件系统中,数据的写入往往是先写入一份,再异步生成多个副本(同步生成副本不属于这种情况)。

最终一致性(Eventually Consistent)

上面提到了软状态,但是系统不可以一直处于软状态,必须有一个期限。在期限过后,应当保证所有副本数据是的一致的,从而达到数据的最终一致性。而在实际工程实践中,最终一致性分为5种:

因果一致性(Causal consistency)

如果节点A在更新完某个数据后通知了节点B,那么节点B之后对该数据的访问和修改都是基于A更新后的值。于此同时,和节点A无因果关系的节点C的数据访问则没有这样的限制。

读己之所写(Read your writes)

节点A更新一个数据后,它自身总是能访问到自身更新过的最新值,而不会看到旧值。其实也算一种因果一致性。

会话一致性(Session consistency)

会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现 “读己之所写” 的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。

单调读一致性(Monotonic read consistency)

单调读一致性指的是:如果一个节点从系统中读取出一个数据项的某个值后,那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。

单调写一致性(Monotonic write consistency)

单调写一致性指的是:一个系统要能够保证来自同一个节点的写操作被顺序的执行。

在实际的实践中,这5种系统往往会结合使用,以构建一个具有最终一致性的分布式系统。事实上,最终一致性并不是只有那些大型分布式系统才涉及的特性,许多现代的关系型数据库都采用了最终一致性模型。在现代关系型数据库中,大多都会采用同步和异步方式来实现主备数据复制技术。在同步方式中,数据的复制过程通常是更新事务的一部分,因此在事务完成后,主备数据库的数据就会达到一致。而在异步方式中,备库的更新往往会存在延时,这取决于事务日志在主备数据库之间传输的时间长短,如果传输时间过长或者甚至在日志传输过程中出现异常导致无法及时将事务应用到备库上,那么很显然,从备库中读取的数据将是旧的,因此就出现了数据不一致的情况。当然,无论是采用多次重试还是人为数据订正,关系型数据库还是能够保证最终数据达到一致。

总结

总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统事务的ACID特性是相反的,它完全不同于ACID的强一致性模型,而是提出通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

参考资料

  • https://juejin.im/post/5b2663fcf265da59a401e6f8
  • https://xinklabi.iteye.com/blog/2341034

综述

这篇文章记录了 Spark 应用开发过程遇到的各种问题及解决方案。主要来自于个人开发的实践,也有部分解决方案来自互联网,如有不足之处欢迎批评指正。本人会持续更新转载请保留原文地址

问题描述及解决方案

java.lang.OutOfMemoryError: Java heap space 错误

这个是 Spark 应用常见错误。JVM 堆内存空间不足。解决方案如下:

  • 首先要判断是 Driver 或者 Executor 出现 OOM,通过--driver-memory 或者--executor-memory 进行调整。
  • 如果是 Spark SQL 或者 Spark Streaming 的程序,建议适当地提高 heap size。

java.lang.OutOfMemoryError: GC overhead limit exceeded 错误

这个也是 Spark 应用常见错误,由于 GC 时间过长导致的。解决方案如下:

  • 直接通过--driver-memory 或者--executor-memory 增加 heap size
  • 修改 GC policy。可以使用-XX:UseG1GC 或者-XX:UseParallelGC

编译 OK,运行时出 NoClassDefineError 错误

这个错误非常清晰,根本原因就是 jar 没有放入 classpath 之中。首先需要判断到底是 Driver 还是 Executor 缺少这个 jar 包。

  • 将 jar 包路径配置到 spark.driver.extraClassPath 或者 spark.executor.extraClassPath。
  • 将 jar 包路径通过 spark-submit 的--driver-class-path 或者--executor-class-path 指定。

其实,spark-submit 还有一个--packages 参数,这个参数让 Spark 通过 Maven 从本地或者远程的 repository 处获取 jar 包。这个参数看似非常方便,但实际使用的时候不是很实用。因为 Hadoop 和 Spark 集群应用一般都是部署在内网的,为了数据安全,一般情况都是无法访问外网的。

org.apache.spark.SparkException: Task not serializable

关于这个问题有单独的一篇文章进行分析,详见:Spark Troubleshooting - Task not serializable 问题分析

java.io.IOException: No space left on device 错误

具体 stack trace 如下:

stage 89.3 failed 4 times, most recent failure:
Lost task 38.4 in stage 89.3 (TID 30100, rhel4.cisco.com): java.io.IOException: No space left on device
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at org.apache.spark.storage.TimeTrackingOutputStream.write(TimeTrackingOutputStream.java:58)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:126)

这个错误是由于,Spark "scratch" space 不足,具体路径通过 spark.local.dir 参数设置,默认是/tmp。官方对于 scratch space 的解释是

Directory to use for "scratch" space in Spark, including map output files and RDDs that get stored on disk.
This should be on a fast, local disk in your system.
It can also be a comma-separated list of multiple directories on different disks.

spark.driver.maxResultSize 超出错误

数据拉回 Driver 端是有限制的,通过 spark.driver.maxResultSize 控制:

  • 默认是 1g
  • 可以设置为 0 或者 unlimited
  • 如果设置成 unlimited 就不会再遇到这个错误,取而代之的是 OOM。

java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE

具体 stack trace 如下:

java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:828) at
org.apache.spark.storage.DiskStore.getBytes(DiskStore.scala:123) at
org.apache.spark.storage.DiskStore.getBytes(DiskStore.scala:132) at
org.apache.spark.storage.BlockManager.doGetLocal(BlockManager.scala:51 7) at
org.apache.spark.storage.BlockManager.getLocal(BlockManager.scala:432) at
org.apache.spark.storage.BlockManager.get(BlockManager.scala:618) at
org.apache.spark.CacheManager.putInBlockManager(CacheManager.scala:146 ) at
org.apache.spark.CacheManager.getOrCompute(CacheManager.scala:70)

这个问题是由于 shuffle block 大于 2GB 导致的,这个是 Spark 实现上的一个问题。Spark 使用 ByteBuffer 作为 storing blocks。

val buf = ByteBuffer.allocate(length.toInt) * ByteBufferislimitedbyInteger.MAX_SIZE

这就是 2GB 的由来。

参考资料

  • http://spark.apache.org/docs/latest/configuration.html