【错误记录】llamafactory-train的时候-跑到50%左右-OOM

两张V100*32G
训练LLaMA3-8B-Chat

从硬件设施上来看训这个8B应该是绰绰有余不至于OOM
在这里插入图片描述

原来的运行命令:

#!/bin/bash
CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.run --nproc_per_node=2 src/train.py\
    --stage sft \
    --do_train True \
    --model_name_or_path {模型路径} \
    --preprocessing_num_workers 16 \
    --finetuning_type lora \
    --template llama3 \
    --flash_attn auto \
    --dataset_dir data \
    --dataset {数据集} \
    --cutoff_len 100000 \
    --learning_rate 5e-05 \
    --num_train_epochs 3.0 \
    --max_samples 100000 \
    --per_device_train_batch_size 1 \
    --gradient_accumulation_steps 4 \
    --gradient_checkpointing true   \
    --lr_scheduler_type cosine \
    --max_grad_norm 1.0 \
    --logging_steps 5 \
    --save_steps 100 \
    --warmup_steps 0 \
    --packing False \
    --report_to all \
    --output_dir {输出路径} \
    --fp16 True \
    --plot_loss True \
    --ddp_timeout 180000000 \
    --optim adamw_torch \
    --lora_rank 8 \
    --lora_alpha 16 \
    --lora_dropout 0 \
    --lora_target all 

修改后:

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 \
...
--cutoff_len 4096 \
...
--gradient_accumulation_steps 8 \
...

先说问题原因:

1. 主要问题是--cutoff_len 100000 \引起的

  • 由于我的pormpt和input字段都很长,所以直击把llama factory里的截断长度拉到最大了(应该是13万多),这个导致了内存溢出,实际上改成4096就可以运行了。

2. 在来一套组合拳,就可以确保不会内存溢出

  • PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128
  • –gradient_accumulation_steps 8
#!/bin/bash
PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 \
CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.run --nproc_per_node=2 src/train.py\
    --stage sft \
    --do_train True \
    --model_name_or_path {模型路径} \
    --preprocessing_num_workers 16 \
    --finetuning_type lora \
    --template llama3 \
    --flash_attn auto \
    --dataset_dir data \
    --dataset {数据集} \
    --cutoff_len 4096 \
    --learning_rate 5e-05 \
    --num_train_epochs 3.0 \
    --max_samples 100000 \
    --per_device_train_batch_size 1 \
    --gradient_accumulation_steps 8 \
    --gradient_checkpointing true   \
    --lr_scheduler_type cosine \
    --max_grad_norm 1.0 \
    --logging_steps 5 \
    --save_steps 100 \
    --warmup_steps 0 \
    --packing False \
    --report_to all \
    --output_dir {输出路径} \
    --fp16 True \
    --plot_loss True \
    --ddp_timeout 180000000 \
    --optim adamw_torch \
    --lora_rank 8 \
    --lora_alpha 16 \
    --lora_dropout 0 \
    --lora_target all 

为什么修改起作用

我在用llama factory微调模型,为什么–cutoff_len 100000会导致OOM,截断长度不是对每条数据而言吗?我的每条数据是远远小于100000的?

内存分配策略:即使你的单条数据远小于100000,但在训练过程中,PyTorch会动态地为模型分配内存。如果--cutoff_len设置得过高,可能会导致PyTorch尝试为更大的序列分配内存,从而超出GPU的显存容量。

为了解决这个问题,你可以尝试:

  • 降低--cutoff_len的值:将其设置为更接近你数据实际需要的最大长度,减少不必要的内存分配。

{批量大小和梯度累积:--per_device_train_batch_size--gradient_accumulation_steps 参数共同决定了实际的批量大小。}
你说的这句话我很认同,但是我不明白这两参数的作用,以及他们的合适大小,和大小关系,我设置的是
–per_device_train_batch_size 1
–gradient_accumulation_steps 4

1. 参数解释

  • --per_device_train_batch_size
    每个设备(如每块GPU)在一次前向传播中处理的样本数,称为每设备批量大小。
    如果你有多个设备,总批量大小还需要乘以设备数量。

  • --gradient_accumulation_steps
    梯度累积步数,即在更新模型参数前,累积的前向和后向传播的次数。
    这样,即使你的显存不足以直接处理一个大批量,你也可以通过累积多个小批量的梯度,来模拟更大的批量大小。


2. 实际批量大小的计算

实际总批量大小可以用公式表示为:
[
\text{实际批量大小} = \text{每设备批量大小} \times \text{设备数量} \times \text{梯度累积步数}
]

根据你的设置:

  • 每设备批量大小:1
  • 梯度累积步数:4
  • 假设你只有一个设备:设备数量为 1

因此:
[
\text{实际批量大小} = 1 \times 1 \times 4 = 4
]

这意味着,在每次参数更新时,你的模型实际上使用的是一个大小为 4 的总批量。


3. 参数的作用

  • --per_device_train_batch_size 控制单次前向传播的内存使用量。如果显存不足,可以设置较小值。
  • --gradient_accumulation_steps 用于在显存受限的情况下实现较大的有效批量大小。通过梯度累积,模型会在 gradient_accumulation_steps 次前向传播后才更新参数。

4. 如何选择合适的大小

选择这些参数时,需要考虑以下几点:

  1. 显存限制

    • 如果显存较小,建议降低 --per_device_train_batch_size,同时增大 --gradient_accumulation_steps
    • 例如:显存不足以支持批量大小为 16,可以设置 --per_device_train_batch_size 2--gradient_accumulation_steps 8
  2. 总批量大小的效果

    • 理论上,总批量大小越大,模型训练的稳定性越高,适合较大的学习率。
    • 太小的批量可能导致训练不稳定。
  3. 设备数量的影响

    • 如果你有多块 GPU,总批量大小会随着 GPU 数量增加。比如,4 块 GPU 的设置中,--per_device_train_batch_size 4--gradient_accumulation_steps 2,总批量大小为:
      [
4 \times 4 \times 2 = 32
]

5. 推荐设置

以下是几个常见情况的建议:

  1. 显存紧张

    • 减小 --per_device_train_batch_size
    • 增大 --gradient_accumulation_steps
  2. 显存充裕

    • 尽量增大 --per_device_train_batch_size,减少梯度累积次数,提高训练速度。
  3. 实际测试

    • 确保总批量大小适合模型和任务。常见的总批量大小在 16128 之间,具体取决于模型规模和数据量。

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128,这条命令起什么作用?以及为什么在OOM的时候有效

1. 命令的作用

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 是 PyTorch 提供的一种控制 GPU 内存分配策略的环境变量配置。它主要用于调节内存分配器在分配显存时的最大分块大小

  • max_split_size_mb
    指定内存分配器在分配显存时的单个分块的最大大小(单位是 MB)。默认值是相对较大的块(例如 2GB 左右,具体取决于显存和场景)。
    当这个值被设置得较小(例如 128MB),内存分配器会将较大的内存需求拆分成多个小块,从而减少大块内存分配失败的风险。

2. 为什么在 OOM(Out of Memory) 时有效

当显存不足时,OOM 的主要原因是内存碎片化问题:

  • PyTorch 在显存管理上采用了内存池机制来提升性能,即提前申请大块显存池供程序使用。
  • 如果显存已经分配了一些块,但由于碎片化导致没有足够大的连续内存块满足分配需求,就会报 OOM 错误,即使总剩余显存量足够。

通过设置 max_split_size_mb

  1. 将大的内存请求拆分为多个小块,降低对连续大内存块的需求。
  2. 通过这种方式,有助于更好地利用显存碎片,从而减少 OOM 发生。

3. 应用场景

  1. 大模型训练时

    • 如果显存紧张或大模型的激活函数占用大量显存,设置 max_split_size_mb 可以有效减少 OOM。
  2. 显存碎片化严重

    • 在多次模型训练或推理任务中,由于显存频繁分配和释放,可能导致碎片化。
  3. 动态计算图

    • 在使用诸如 torch.nn.DataParallel 或动态计算图时,大块显存可能分配失败,此配置可以帮助更高效地分配小块显存。

4. 使用建议

  1. 推荐值

    • max_split_size_mb:128 是常见的推荐值,但也可以根据实际需求调整,例如 64MB256MB
    • 如果内存需求较小,可以选择更小的值;对于显存较大的场景,可以稍微调大。
  2. 如何设置

    • 在运行程序前,设置环境变量:
      export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128
      
    • 或者在代码中动态设置:
      import os
      os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128'
      
  3. 调试 OOM 问题

    • 如果设置此值仍无法解决 OOM,可以尝试进一步减少 batch_size 或启用 torch.cuda.empty_cache() 在关键点释放显存。

5. 总结

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 的核心作用是优化显存分配,通过限制单块内存分配的最大大小来减少碎片化和大块分配失败。对于显存紧张或碎片化严重的场景,这是一种简单而有效的优化方法。

也就是我的分块大小最大是128MB是吗?这样就不会出现大块内存占用只剩下小块?

是的,你理解得非常准确!设置 PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 后,分块大小的上限变成了 128MB,从而优化显存的利用效率,减少 OOM 的风险。以下是进一步解释:


1. 为什么分块大小设置为 128MB 有帮助?

显存的 OOM 问题通常源于内存碎片化,即虽然显存总量足够,但由于内存被分割成了不连续的小块,大的内存分配请求(如 512MB 或 1GB)无法找到足够大的连续区域。

通过限制分块大小:

  • 大内存请求会被拆分成多个小块(如 128MB 的小块),这样即使显存存在碎片,也更容易分配成功。
  • 减少“剩下许多小块但无法利用”的情况,因为拆分后的小块能够拼接到碎片中更高效地利用。

2. 分块最大为 128MB的实际效果

  • 每次分配显存时,如果需求超过 128MB,大内存分配会拆成多个 128MB 块。例如:
    • 如果需要分配 512MB,则会拆成 4 个 128MB 块。
  • 显存中的碎片被更高效地利用:
    • 假设显存碎片分别为 200MB、150MB 和 300MB,如果不限制分块大小,分配一个 512MB 内存可能失败(OOM)。
    • 但如果拆分成 128MB 小块,则内存分配可以成功(150 + 150 + 150 + 62 = 512MB)。

3. 有了限制后还会 OOM 吗?

限制分块大小后,仍可能出现以下情况导致 OOM:

  1. 显存总量确实不足:即使通过拆分,也没有足够的小块总量。
  2. 梯度累积或激活占用过多:显存中需要同时保存前向、后向计算的中间结果(尤其是多层 Transformer 模型),如果这些结果太多或太大,显存仍然会耗尽。

解决方法

  • 进一步减少 --per_device_train_batch_size
  • 启用显存优化选项,例如 PyTorch 的 torch.utils.checkpoint 或模型并行。
  • 考虑混合精度训练(使用 torch.cuda.amptransformers 提供的 fp16 支持)。

4. 设置分块大小的注意事项

  • 分块大小不能太小
    太小的分块会导致分配和释放显存的频率增加,从而降低性能。128MB 是一个合理的折中值。

  • 显存足够时可以调大
    如果显存充裕,例如使用 32GB 显存的 GPU,可以尝试将 max_split_size_mb 设置为更大的值(如 256MB 或 512MB),以减少分块分配的开销。


5. 总结

通过设置 max_split_size_mb:128,PyTorch 内存分配器可以避免因为找不到大块连续内存而导致的 OOM,大大提高了显存碎片化情况下的利用率。这种方法对于显存紧张或碎片化严重的场景非常有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值