跑到 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_bf16 和 use_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 小时魔咒的罪魁祸首。把它降到 0 或 2,然后再提交一次超算任务吧。祝炼丹顺利!
是否无法保存训练结果
由于你的程序已经报了 RuntimeError: No space left on device,非常遗憾,当前正在运行的这个进程确实无法再保存了。
为什么无法"抢救"?
- 文件句柄已失效: 报错
No space left on device说明 PyTorch 在尝试创建用于保存模型的临时文件或共享内存对象时失败了。一旦这种底层的 I/O 错误触发,Python 进程通常会进入一种"僵死"状态,无法再执行后续的model.save_pretrained()指令。 - 分布式(DDP)死锁: 你使用的是多卡训练(2卡),在这种模式下,保存模型需要所有进程进行同步(Barrier)。其中一个进程因为内存空间报错崩溃,会导致其他进程无限期等待,最终整个任务都会卡死或被超算系统杀掉。
- 内存回收: 即使你现在清理了磁盘空间,已经崩溃的 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.bin 或 adapter_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 之前手动打乱一次
流式读取的"代价"与注意事项
虽然流式读取解决了内存和崩溃问题,但有两点你需要知道:
- 无法显示总进度条: 因为程序不知道"水管"里到底还有多少水,所以你看到的进度条通常不会显示百分比,只会显示已经跑了多少步(Steps)。
- 不支持全局打乱(Global Shuffle): 普通模式可以随机打乱 180 万条数据的顺序;流式模式只能在缓冲区(比如 1000 条)里打乱。对于你的 Binding 任务(分子-蛋白对),如果数据本身是按类别堆叠的,建议你在生成 JSON 文件时先用 Python 脚本随机打乱(Shuffle)一下,确保训练的随机性。
总结建议
如果你现在的任务因为内存或共享内存频繁崩溃,改用流式模式是绝对正确的选择。
建议最终配置组合:
streaming: truepreprocessing_num_workers: 1dataloader_num_workers: 0overwrite_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,为了弥补"无法全局打乱"的缺陷,大家会这样做:
- 在把数据存进
dataset_004.json之前,先在本地用 Python 把 JSON 里的 List 彻底随机打乱。 - 开启
streaming: true。 - 这样即便流式读取是顺序的,但数据本质上已经是乱序的了。
针对你的建议
对于你现在的 1.8M 数据 + 118G 内存 + 海光 DCU 环境,我的建议是:
强烈建议你开启 streaming: true。
理由:
- 容错率极高: 你之前 50 小时崩溃的根本原因是多进程争抢共享内存。流式模式下
num_workers: 0,内存占用极低且极其稳定,不会再出现"No space left on device"。 - 启动快: 你不需要再等那 180 万条数据的预处理进度条了。
- 海光 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