目录
3.1 缩小batch_size,设置gradient_accumulation_steps=32:
3.2 设置 gradient_checkpointing=True
1 基础组件知识回顾
2 基于Transformers的NLP解决方案
3 显存优化策略,4G显存跑BERT-Large
模型训练之前:
好家伙,用这个哈工大的模型“hfl/chinese-macbert-large”直接爆内存了。
这是博主4090的显存:
果然,我这个废物的1650是跑不起来的
那么,如何让我们这个4g的也能跑起来呢?
3.1 缩小batch_size,设置gradient_accumulation_steps=32:
3.1.1 原因:
原因:
由于内存限制或其他原因,无法同时处理大批量的训练样本。那么就需要我们缩小batch_size,减少每个训练步骤中处理的样本数量。但这样这可能会导致梯度估计的不准确性,因为梯度是根据每个小批量样本计算得出的。梯度的不准确性可能会影响模型的训练效果。
为了缓解这个问题。这时,可以使用梯度累积策略(Gradient Accumulation)来解决这个问题。梯度累积策略将大批量的训练样本拆分成多个小批量,在每个小批量上计算梯度并累积,最后再根据累积的梯度来更新模型参数。
gradient_accumulation_steps
参数指定了梯度累积的步数,也就是每次更新模型参数之前累积的小批量数。例如,如果gradient_accumulation_steps
设置为32,那么模型会在累积了32个小批量的梯度之后才进行参数更新。这样做可以有效减小内存占用,并使得模型能够处理更大的批量大小,同时保持相对较小的内存需求。需要注意的是,梯度累积不会改变训练的效果,只是改变了参数更新的频率。在计算损失函数和梯度时,会考虑累积的小批量样本的平均效果。因此,梯度累积可以看作是一种近似方式,使得模型在每次参数更新时更接近于使用完整批量样本进行训练的效果。
3.1.2 效果:
你会发现显存占用从原来的15+掉到了7+,减小了近乎一半。
3.2 设置 gradient_checkpointing=True
3.2.1 原因:
gradient_checkpointing
是一个用于优化模型内存占用的技术。当你在训练大型模型或者使用较大的批量大小时,模型的内存需求可能会非常高。为了解决这个问题,可以启用梯度检查点(gradient checkpointing)。梯度检查点是一种技术,它允许在反向传播过程中临时释放一部分中间激活值的内存,从而减少内存占用。在正向传播过程中,模型会计算并保留所有中间激活值以供后续的反向传播使用。但是,在反向传播过程中,并不需要保留所有中间激活值,只需要保留一部分用于计算梯度的值即可。
启用
gradient_checkpointing=True
参数会在模型中使用梯度检查点技术。这样,当进行反向传播计算梯度时,只会保留必要的中间激活值,而释放其他不需要的中间激活值的内存。这可以显著减少模型的内存占用,特别是对于具有大量参数和复杂结构的模型。
3.2.2 效果:
可以看到这个确实也能减少一些内存开销,只是效果没有之前那个那样明显
3.3 设置optim="adafactor"
3.3.1 原因:
optim="adafactor"
表示你选择使用 Adafactor 优化器来训练模型。Adafactor 是一种自适应学习率优化器,它在处理稀疏梯度时表现出色,并且对学习率的自适应调整具有一定的稳定性。Adafactor 优化器在训练过程中会自动调整学习率,并根据梯度的稀疏性进行自适应。相比于传统的优化器(如 Adam、SGD 等),Adafactor 具有以下特点:
自适应学习率:Adafactor 会根据梯度的变化情况自动调整学习率,从而更好地适应不同参数的更新需求。
稀疏梯度支持:Adafactor 在处理稀疏梯度时表现出色,这对于处理具有大量参数的模型(如 Transformer)尤为重要。
参数矩阵自适应:Adafactor 会自适应地调整参数矩阵的学习率和正则化项,以适应不同参数的特性。
Adafactor 是一种较新的优化器,相对于传统的优化器来说,它可能在某些任务和模型上表现更好。然而,选择优化器时仍然需要根据具体任务和模型的特点进行评估和实验,以确定最合适的优化器。
注意,huggingface并不支持所有的优化器,但是对于adafactor还是支持的。
3.3.2 效果:
可以看到up主的电脑上显存以及降到了5.0g。
同时,我的4g显存电脑上终于也可以跑这个模型了 ,不过时间确实要很久:
3.4 Freeze Model冻结参数:
注意这次是在设置Trainer的代码块中修改,非参数设置模块
3.4.1 原因:
遍历模型
model
中 BERT 的参数,并将它们的requires_grad
属性设置为False
,从而冻结了这些参数。参数冻结是指在训练过程中保持参数的数值不变,不更新它们的梯度。通过将参数的
requires_grad
属性设置为False
,你告诉模型在训练过程中不需要计算和更新这些参数的梯度。这样做的目的通常是为了固定预训练模型的参数,以免在微调过程中不必要地改变它们的权重。参数冻结可以有几个用途:
- 加速训练:冻结一些参数可以减少计算量和内存需求,从而加快训练速度。
- 防止过拟合:在微调预训练模型时,冻结一些参数可以防止模型在小样本数据上过拟合。
- 保持预训练模型的特征提取能力:预训练模型通常在大规模数据上进行训练,学习了丰富的特征表示。通过冻结预训练模型的参数,可以保留这些特征提取能力,而不会在微调过程中过度调整它们。
3.4.2 效果:
可以看到不仅显存降低了很多,而且训练速度也得到了增强
3.5 缩小max_length:
3.5.1 原因:
缩小
max_length
意味着限制输入序列的最大长度。在自然语言处理任务中,输入序列的长度可能对模型的训练和推理过程产生影响。通过缩小max_length
,可以减少模型对长序列的处理需求,从而可能提高训练速度和节省内存。要缩小
max_length
,需要根据你的数据集和任务的特点进行调整。以下是一些可能的方法:
截断序列:对于超过
max_length
的输入序列,可以选择截断它们到指定的最大长度。这样做可能会导致信息的丢失,特别是对于长文本来说,但有时这是必要的权衡。选择合适的
max_length
值:根据数据集和任务的特点,选择一个适当的max_length
值。这需要考虑到输入序列的平均长度、文本的语义完整性以及模型的要求等因素。动态调整
max_length
:如果数据集中的序列长度差异很大,可以考虑动态调整max_length
。例如,可以根据每个具体样本的长度设置不同的max_length
值。需要注意的是,缩小
max_length
可能会导致信息的丢失或模型对长序列的处理能力下降。因此,在调整max_length
时,需要权衡模型性能和输入序列的完整性之间的平衡。确保选择一个合适的max_length
值,以满足任务需求并保持模型的有效性。
3.5.2 效果:
这个虽然在占用显存上的效果不太直观,但是其实时间还是缩小了不少。
4 最后的对比图:
up主的,这些策略还是非常有效的,时间换空间
当然还有其他的高效微调策略,敬请期待下个章节
最后的最后,我们在跑一个较大模型的时候,如果自由组合上述策略还不行(buff叠满了),那就只能寻求类似功能的小模型,做一个取舍