No space left on device (28) llamafactory微调训练的时候 报错,需要调节 dataloader_num_workers

跑到 50 个小时节点突然崩溃,这确实非常搞人心态。结合你上一条提到的 /dev/shm (Error 28: No space left on device) 报错,以及你这份长达 50 小时的训练配置文件,问题的原因已经非常清晰了:这是一个典型的 PyTorch 长周期训练下的共享内存(Shared Memory)泄漏/碎片化问题。

在你的配置中,最核心的"定时炸弹"是这部分:

yaml 复制代码
# 【30核CPU高性能配置】数据加载优化
dataloader_num_workers: 6
dataloader_prefetch_factor: 2

为什么 50 小时后会崩?

在双卡(DDP模式)下,设置 dataloader_num_workers: 6 意味着你一共拉起了 12 个子进程 在后台不断地读取数据。每个进程还会预取 2 个 batch (prefetch_factor: 2)。

虽然系统一开始 /dev/shm 空间很充足,但在长达 50 小时的密集 I/O 吞吐中,PyTorch 的 Multiprocessing 机制有时无法完美地回收每一个 Tensor 的共享内存句柄(特别是在遇到数据尺寸波动、或者底层网络/磁盘极短延时的时候)。这会导致共享内存产生碎片或者轻微泄漏 。随着时间推移(通常在几十个小时后),/dev/shm 最终被慢慢撑爆,导致 Error 28 瞬间崩溃,随后系统回收内存,你再去查时就又变成了 118G 可用。


针对配置文件的修改建议

要彻底解决这个长周期运行崩溃的问题,建议直接在你的 YAML 配置文件中做以下调整:

方案一:追求 100% 稳定(强烈推荐)

直接关闭 DataLoader 的多进程加载。虽然理论上会增加一点点 CPU 读取数据的时间,但在大模型训练中,瓶颈往往在 GPU 算力而不是 CPU 读数据。关掉它,你的 /dev/shm 占用将几乎为 0,跑一个月都不会崩。

yaml 复制代码
# 将 6 改为 0,强制主进程读取数据
dataloader_num_workers: 0 

# 设置为 0 时,需要注释掉 prefetch_factor,否则可能会报错
# dataloader_prefetch_factor: 2 
方案二:保留多进程,但降低风险(折中方案)

如果你确实发现设为 0 后 GPU 利用率下降严重(GPU 在等 CPU 喂数据),你可以大幅调小进程数,让共享内存的压力呈指数级下降。

yaml 复制代码
# 双卡环境下,单卡分配 1~2 个 worker 已经足够喂饱 GPU
dataloader_num_workers: 2

# 保持较低的预取数量
dataloader_prefetch_factor: 2

配置文件中的其他潜在风险排查

除了 DataLoader,你的配置整体写得非常标准和专业(尤其是针对海光 DCU 的 pure_bf16use_cache: false 优化),但还有两个小细节需要注意:

1. 超大数据集的内存瞬间爆炸(OOM)风险

yaml 复制代码
dataset: binding_part_004-005
preprocessing_num_workers: 6
max_samples: 1800000

你允许的最大样本数高达 180 万条 。在 LLaMA-Factory 等框架中,非流式(非 streaming)加载时,preprocessing_num_workers: 6 会将这 180 万条数据分给 6 个进程同时进行 Tokenize(分词)。这在预处理阶段极其吃系统 RAM(118G 内存可能在几分钟内被瞬间吃光导致节点直接被超算系统 Kill)。

  • 建议: 如果你发现训练一开始 就崩了,尝试将 preprocessing_num_workers 降到 2 或者开启 streaming: true。如果已经能正常跑起来,说明 118G 内存扛住了这部分压力,可以不改。

2. Checkpoint 恢复问题

yaml 复制代码
# resume_from_checkpoint: /root/.../checkpoint-6500

你注释里的分析非常准确:"PyTorch 强制开启了 weights_only=True,但 checkpoint 里的 rng_state.pth 包含 numpy 对象,加载失败"。这是 PyTorch 2.0+ 之后的一个安全更新限制。

  • 你目前的做法是对的: 使用 adapter_name_or_path 来挂载上一个阶段的 LoRA 权重,以纯 SFT 的形式继续跑,而不是用 resume_from_checkpoint 恢复整个训练器状态(包括优化器和步数)。

总结:

去修改你的 dataloader_num_workers,这是 50 小时魔咒的罪魁祸首。把它降到 02,然后再提交一次超算任务吧。祝炼丹顺利!

是否无法保存训练结果

由于你的程序已经报了 RuntimeError: No space left on device非常遗憾,当前正在运行的这个进程确实无法再保存了。

为什么无法"抢救"?

  1. 文件句柄已失效: 报错 No space left on device 说明 PyTorch 在尝试创建用于保存模型的临时文件或共享内存对象时失败了。一旦这种底层的 I/O 错误触发,Python 进程通常会进入一种"僵死"状态,无法再执行后续的 model.save_pretrained() 指令。
  2. 分布式(DDP)死锁: 你使用的是多卡训练(2卡),在这种模式下,保存模型需要所有进程进行同步(Barrier)。其中一个进程因为内存空间报错崩溃,会导致其他进程无限期等待,最终整个任务都会卡死或被超算系统杀掉。
  3. 内存回收: 即使你现在清理了磁盘空间,已经崩溃的 PyTorch 运行栈也无法自动"恢复"并继续写文件。

你现在应该怎么办?(止损步骤)

虽然这 50 小时的进度可能无法通过正常途径保存,但为了不浪费接下来的时间,请按以下步骤操作:

1. 手动清理"幽灵"占用

报错虽然说没空间,但你查 df -h 又是满的。这通常是因为崩溃进程产生的僵尸进程 依然挂载了共享内存。

在终端执行:

bash 复制代码
# 杀掉所有残留的 python 进程
ps -ef | grep python | awk '{print $2}' | xargs kill -9
2. 修改配置并重启(重点)

在重启任务前,请务必修改你的 YAML 配置文件,避免再次掉进同一个坑:

  • 修改 dataloader_num_workers: 0 (最保险,彻底解决 No space 问题)。
  • 修改 preprocessing_num_workers: 2(防止启动时内存溢出)。
3. 利用 adapter_name_or_path 续跑

好在你的配置文件里已经写了:

yaml 复制代码
adapter_name_or_path: /root/private_data/output/binding_sft_short_part_004-005_lora64_epoch1/checkpoint-2500

虽然这次奔溃没能保存到最新的 checkpoint(比如原本该存 3000 步的),但你依然可以从 checkpoint-2500(上一个成功保存的点)继续开始。

建议: 检查一下你的 output_dir 目录下,看看有没有比 checkpoint-2500 更晚的文件夹(比如 checkpoint-3000)。如果有且文件夹完整(里面有 adapter_model.binadapter_model.safetensors),就把配置文件里的路径更新到最新的那个,这样损失最小。

流式数据读取方式

是的,如果改用流式读取(Streaming Mode),确实可以完美避开由于内存溢出(OOM)导致的进程崩溃问题。

对于你目前这种 180万条数据 的大规模 SFT 任务,流式读取是最稳妥的方案。

为什么流式读取能解决问题?

普通的读取方式(非流式)就像是**"先把整桶水倒进碗里再喝"**:

  • 程序会尝试把 180 万条数据全部加载到 RAM 内存中,进行分词(Tokenize)并拼接,然后再打乱(Shuffle)。
  • 这就是为什么你之前需要 preprocessing_num_workers: 6,因为数据量太大了,预处理非常慢。但在内存只有 118G 的情况下,这 6 个进程很容易把内存"撑爆"。

流式读取(Streaming)就像是**"接水管喝水"**:

  • 程序一次只从硬盘读取一小部分数据(比如几十条),处理完直接喂给 GPU,然后再读下一部分。
  • 内存占用极低: 无论你的数据集是 180 万条还是 1.8 亿条,内存占用几乎是恒定的,维持在几百 MB 到几 GB 之间。
  • 即刻启动: 不需要等待漫长的"预处理"进度条,任务一运行,显卡几乎立刻就能开始计算。

如果改用流式读取,你的配置需要做如下调整:

yaml 复制代码
# ============================================
# 数据集配置(流式模式修改版)
# ============================================
streaming: true                # 开启流式读取

# 在流式模式下,预处理必须是单进程的,因为它是随用随读
preprocessing_num_workers: 1   

# 【核心重点】流式模式下,DataLoader 必须设为 0
# 这能彻底解决你遇到的 "No space left on device" (共享内存溢出) 问题
dataloader_num_workers: 0

# 最大样本数依然有效,它会读够 180 万条后自动停止
max_samples: 1800000

# 注意:流式模式下无法进行全局 Shuffle(打乱顺序)
# 它只能在缓存窗口内进行局部打乱。如果你对数据顺序敏感,建议在存 JSON 之前手动打乱一次

流式读取的"代价"与注意事项

虽然流式读取解决了内存和崩溃问题,但有两点你需要知道:

  1. 无法显示总进度条: 因为程序不知道"水管"里到底还有多少水,所以你看到的进度条通常不会显示百分比,只会显示已经跑了多少步(Steps)。
  2. 不支持全局打乱(Global Shuffle): 普通模式可以随机打乱 180 万条数据的顺序;流式模式只能在缓冲区(比如 1000 条)里打乱。对于你的 Binding 任务(分子-蛋白对),如果数据本身是按类别堆叠的,建议你在生成 JSON 文件时先用 Python 脚本随机打乱(Shuffle)一下,确保训练的随机性。

总结建议

如果你现在的任务因为内存或共享内存频繁崩溃,改用流式模式是绝对正确的选择。

建议最终配置组合:

  • streaming: true
  • preprocessing_num_workers: 1
  • dataloader_num_workers: 0
  • overwrite_cache: false

这样配置后,你的 30 核 CPU 和 118G 内存会跑得非常轻松,再也不会出现 50 小时后的"幽灵崩溃"了。

在工业界和学术界的大模型微调中,是否使用 streaming: true 并不是单纯看数据量(40万还是100万),而是取决于"内存瓶颈"和"训练效率"的博弈。

以下是实际开发中的真实逻辑:

1. 为什么很多人即使有 40万+ 数据也不一定用 Streaming?

如果服务器的 系统内存 (RAM) 足够大(比如 512GB 或 1TB),开发者通常更倾向于禁用 Streaming。原因如下:

  • 全局打乱(Global Shuffle): 普通模式可以将 40万条数据彻底洗牌,让模型每一轮看到的顺序都不同,这有助于模型收敛得更好。
  • 速度优势: 普通模式在训练开始前一次性分好词(Tokenize)并缓存到硬盘。训练开始后,GPU 几乎是零延迟地从硬盘读取已经处理好的二进制文件。
  • 断点恢复方便: 非流式模式下,Trainer 能精确知道现在跑到了总进度的百分之多少。

2. 什么情况下 Streaming 是"强制要求"?

当满足以下任一条件时,大家都会毫不犹豫开启 streaming: true

  • 内存(RAM)不足: 像你现在的情况,118GB 内存处理 180万条复杂的生物/化学数据。1.8M 数据预处理后在内存中展开可能超过 200GB,这种情况下不开启流式,系统必崩。
  • 超大规模数据集: 比如你在做几千万条数据的预训练(Pre-training),这种数据量没有任何服务器能一次性加载进内存。
  • 想要立刻看到训练效果: 普通模式预处理 180万条数据可能要卡在进度条半小时甚至一小时;流式模式点下运行,30秒内显卡就开始跑了。

3. "别人"是怎么做的?(行业规范)

在处理类似你这种 1.8M (180万) 规模的数据时,业界通常有两种主流做法:

方案 A:预处理成 Arrow/JSONL 缓存(最常用)

他们会先写一个离线脚本,把 180万条数据预先分好词,保存为 Arrow 格式或 Dataset 磁盘缓存。

  • 优点: 训练时不需要 preprocessing_num_workers,直接加载。
  • 缺点: 占硬盘空间大,且第一次转换很费时间。
方案 B:流式读取 + 预打乱(最聪明)

如果选择 streaming: true,为了弥补"无法全局打乱"的缺陷,大家会这样做:

  1. 在把数据存进 dataset_004.json 之前,先在本地用 Python 把 JSON 里的 List 彻底随机打乱
  2. 开启 streaming: true
  3. 这样即便流式读取是顺序的,但数据本质上已经是乱序的了。

针对你的建议

对于你现在的 1.8M 数据 + 118G 内存 + 海光 DCU 环境,我的建议是:

强烈建议你开启 streaming: true

理由:

  1. 容错率极高: 你之前 50 小时崩溃的根本原因是多进程争抢共享内存。流式模式下 num_workers: 0,内存占用极低且极其稳定,不会再出现"No space left on device"。
  2. 启动快: 你不需要再等那 180 万条数据的预处理进度条了。
  3. 海光 DPU/DCU 兼容性: 异构计算环境(如海光、华为昇腾)有时对多进程内存管理比较敏感,流式读取这种"单进程、低负荷"的模式是最不容易出错的。

操作提醒:

开启流式后,请把 save_steps 改小一点(比如 200),因为流式模式下如果中途停了,找回进度的操作比普通模式稍微麻烦一点,频繁保存 Checkpoint 是最好的保命手段。

总结

现在的这个进程已经没救了,建议直接手动杀掉任务。

超算训练中,**"断点续训能力"**比"单次任务时长"更重要。建议你以后将 save_steps 设置得稍微小一点(比如每 200 或 300 步保存一次),这样即使遇到这种不可控的硬件/系统错误,你损失的进度也就一两个小时。

这个错误非常直接:max_samples 无法与 streaming 模式同时使用。

在 LLaMA-Factory(以及底层的 Hugging Face 数据处理逻辑)中,当你开启了 streaming: true(流式加载),系统会以迭代器的形式读取数据,这通常用于处理超大规模、内存放不下的数据集。而 max_samples 参数是用来在预加载阶段截断数据集的,这两者在目前的逻辑中是互斥的。

解决方案

你需要修改你的配置文件:
/root/private_data/training_configs/binding_part_004-005_shuguang_alpaca_my_deepseed2_lora64_epoch1.yaml

根据你的需求,有两种改法:


方法 A:关闭流式加载(推荐,如果数据集不是特别大)

如果你想限制训练样本数量(例如只练 1000 条看看效果),请关闭 streaming

yaml 复制代码
# 在 YAML 文件中修改
streaming: false
max_samples: 1000 # 或者你设置的其他数值
方法 B:保持流式加载,移除样本限制

如果你确定数据集很大,必须用流式加载,那么你不能 使用 max_samples

yaml 复制代码
# 在 YAML 文件中修改
streaming: true
# max_samples: 1000  <-- 注释掉或删除这一行

注意:在流式模式下,如果你想限制训练量,通常是通过控制 max_steps(最大步数)来实现的,而不是样本数。


为什么会报错?

简单来说,max_samples 需要程序先知道数据集的"总长度"才能进行截断,而 streaming 模式的设计初衷就是"我不知道有多长,我边读边算"。两者在逻辑链条上打架了。

建议: 如果你的 GPU 内存和系统内存足够处理这个数据集,直接用 方法 A(关闭 streaming)会省心很多,因为这样你还能看到训练进度条的百分比(流式模式下通常看不到总进度)。

bash 复制代码
# streaming: true 情况下 val_size需要设置具体数字
# val_size: 0.01
# val_size: 1000
相关推荐
流年似水~1 小时前
iOS 开发进阶之路:从能跑到能维护
人工智能·程序人生·ios·语言模型
QuestLab1 小时前
【第23期】2026年4月26日 AI日报
人工智能
AIminminHu2 小时前
((AI篇)OpenGL渲染与几何内核那点事-(二-1-(10):从“搜个大概”到“读懂图纸”:一个 CAD 开发者眼中的 RAG 进化简史)
人工智能·agent·opengl·智能体
SmartBrain2 小时前
AI技术演进与实战路径洞察
人工智能·架构·aigc
冰西瓜6002 小时前
深度学习的数学原理(三十一)—— Transformer前馈网络FFN(为什么要先升维再降维)
人工智能·深度学习·transformer
szxinmai主板定制专家2 小时前
基于ZYNQ MPSOC多通道声音振动采集方案,替代NI9234和B&K
arm开发·人工智能·嵌入式硬件·fpga开发
ZGi.ai2 小时前
ZGI四层能力架构:一个企业AI底座的设计逻辑
人工智能·架构
AI 赋能2 小时前
深入探讨OpenAI ChatGPT 4o图像API的运用与操作
人工智能·chatgpt
格林威2 小时前
面阵相机 vs 线阵相机:堡盟与海康相机选型差异全解析 附Python实战演示
开发语言·人工智能·python·数码相机·计算机视觉·视觉检测·工业相机