已开启
[Bug]: 数据不够时训练静默退出 #366
pangxz创建于 16 天前
16 天前 添加了label:bug
fandengdong
16 天前 评论:
16 天前 评论:
我也遇到这个问题,下面补充一些细节:
补充详细复现过程与根因分析(该问题已在两种规模独立复现:单节点 8 卡、4 节点 32 卡)
一、问题现象
训练进行到接近数据可支撑的极限步数时,整个训练无限挂起:无任何报错、无进程退出、NPU 利用率归零。挂起点在 epoch 内数据集迭代器耗尽(StopIteration)之后。
二、复现环境与配置
- 硬件:Ascend910 × 8(单机)/ × 32(4 机),CANN 9.0.0,torch + torch_npu
- 配置:
examples/demo_trainer/train.yaml(官方 8 卡 smoke 配置)max_seq_len: 4096,micro_batch_size: 1,GBS=8(单机)/GBS=32(4 机)dataloader: DynamicBatchDataLoader(TextTokenBatcher按 token 预算打包)
- 数据:本地 jsonl(wikitext-2-raw-v1-train.jsonl),36,718 行,全量 tokenize 后 2,517,232 tokens(平均 68.6 token/行,中位数仅 10,空行占 35%)
- 数据消费口径:每 rank 每步 =
micro_batch_size × max_seq_len= 1 × 4096 = 4096 tokens - 数据可支撑步数 = 2,517,232 ÷ (world_size × 4096):
- 单节点 8 卡 → 76.8 步(配置 100 步,实测 MFU 恰好打印到第 76 步后卡死,与预估一致)
- 4 节点 32 卡 → 19.2 步(配置 25 步,第 20 步前后耗尽并挂死)
- 触发条件:只要
train_iters超过数据可支撑步数必然触发,无需特殊构造;单节点 8 卡即可复现,不要求多机
三、复现命令
最小复现(单节点 8 卡):
# 模型:任意本地 Qwen3-30B-A3B Hugging Face 目录(MODEL 换成你的路径)
# 数据:任意本地 jsonl 小数据集,total_tokens < train_iters × GBS × 4096 即可
# 以下示例使用 2.52M tokens 的数据跑 100 步(数据只够 76.8 步)
torchrun --standalone --nproc_per_node=8 \
scripts/train_lm.py examples/demo_trainer/train.yaml \
--model.pretrained_model_name_or_path=${MODEL} \
--dataset.model_assets.tokenizer.pretrained_model_name_or_path=${MODEL} \
--dataset.data_path=dataset/qwen3-30b-a3b/data/raw/wikitext-2-raw-v1-train.jsonl \
--dataset.data_config.hf_dataset_name=null \
--training.train_iters=100
4 节点 32 卡版(各节点执行,$RANK 为节点序号 0~3):
torchrun --nnodes=4 --nproc_per_node=8 --node_rank=${RANK} \
--master_addr=${MASTER_ADDR} --master_port=29581 \
scripts/train_lm.py examples/demo_trainer/train.yaml \
--model.pretrained_model_name_or_path=${MODEL} \
--dataset.model_assets.tokenizer.pretrained_model_name_or_path=${MODEL} \
--dataset.data_path=dataset/qwen3-30b-a3b/data/raw/wikitext-2-raw-v1-train.jsonl \
--dataset.data_config.hf_dataset_name=null \
--training.global_batch_size=32 \
--training.train_iters=25
四、现象时间线(单节点 100 步实例)
- step 1~76 完全正常:loss / 进度条正常,所有 rank 每步经 world barrier 同步
- 第 77 次迭代:部分 rank 迭代器先耗尽 →
StopIteration被捕获(text_trainer.py:308)→ 日志epoch:0 Dataloader finished with drop_last True(text_trainer.py:311) - 已断流的 rank 走完 epoch 收尾 → 进入
destroy_distributed()→ 阻塞在torch.distributed.barrier()(base.py:776) - 未断流的 rank 仍在执行第 77 步 forward
- world barrier 永远等不齐 → 全体挂死。
HCCL_EXEC_TIMEOUT(1800s)也不生效,因为卡在 c10d barrier 而非 HCCL 集合通信
五、根因分析
- 数据按 token 预算打包(非按行/按条数),各 rank 独立分片迭代同一数据文件;本数据行长极度不均(中位数 10 vs 平均 68.6、35% 空行)→ 各 rank 分片可产出的 micro-batch 数不同(实测 32 路分片 min/max 差 ≥2,8 路差 1)→
StopIteration在 rank 间天然不同时发生 - trainer 对
StopIteration是单 rank 局部处理(捕获后 break 进入 teardown),但 teardown 的destroy_distributed是全集群 world barrier——任一 rank 提前进入必然导致全体死锁 - 全程无数据量预校验、无 rank 间同步的终止信号、无有界超时 → 退化为永久挂起
- 附注:挂死时部分未断流 rank 的主线程长时间停在
transformers/masking_utils.py:788 find_packed_sequence_indices(Qwen3MoeAttention 的 mask 构建路径),除主因 barrier 死锁外疑似还有第二个性能隐患,建议一并排查
六、期望行为(满足任一即可)
- a. 启动预校验:数据集可打包 micro-batch 数 ≥
train_iters所需,不足直接报错 - b. 任一 rank 耗尽 → 广播终止信号或 raise,全 rank 同步退出并明确报"数据集耗尽"
- c. 至少
destroy_distributed的 barrier 加有界等待 + 清晰诊断
七、证据
- 单机日志:MFU/进度停在 step 76,随后
[INFO|text_trainer.py:311] epoch:0 Dataloader finished with drop_last True,进程常驻不退出 - 多机日志:4 节点各自
Dataloader finished出现次数为 2/3/3/4——各节点断流时刻不同步,直观证明 rank 分歧 - py-spy 栈(挂死时):
- 已断流 rank:
barrier (torch/distributed/distributed_c10d.py:4888) ← destroy_distributed (hyper_parallel/auto_models/trainer/base.py:776) - 未断流 rank:
find_packed_sequence_indices (transformers/masking_utils.py:788) ← create_causal_mask ← Qwen3MoeAttention.forward
- 已断流 rank:
- 挂死时状态:全部进程存活、NPU AICore 利用率 0%、超过 30 分钟无任何输出


Checklist
🐛 Describe the bug
使用小数据集进行训练,发现在某个step后静默退出。
Expected behavior
退出前有提示数据不足
Additional context
Environment info
Python 3.11.10
torch 2.6.0
transformers 5.13.0
Thanks for contributing 🎉!