已开启
[Bug]: 数据不够时训练静默退出 #366
pangxz创建于  16 天前
pangxz
pangxz成员
16 天前 创建

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 🎉!

likedislike
pangxzpangxz成员
16 天前 添加了label:bug
fandengdong
fandengdong
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: 4096micro_batch_size: 1GBS=8(单机)/ GBS=32(4 机)
    • dataloader: DynamicBatchDataLoaderTextTokenBatcher 按 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 步实例)

  1. step 1~76 完全正常:loss / 进度条正常,所有 rank 每步经 world barrier 同步
  2. 第 77 次迭代:部分 rank 迭代器先耗尽StopIteration 被捕获(text_trainer.py:308)→ 日志 epoch:0 Dataloader finished with drop_last Truetext_trainer.py:311
  3. 已断流的 rank 走完 epoch 收尾 → 进入 destroy_distributed() → 阻塞在 torch.distributed.barrier()base.py:776
  4. 未断流的 rank 仍在执行第 77 步 forward
  5. world barrier 永远等不齐 → 全体挂死HCCL_EXEC_TIMEOUT(1800s)也不生效,因为卡在 c10d barrier 而非 HCCL 集合通信

五、根因分析

  1. 数据按 token 预算打包(非按行/按条数),各 rank 独立分片迭代同一数据文件;本数据行长极度不均(中位数 10 vs 平均 68.6、35% 空行)→ 各 rank 分片可产出的 micro-batch 数不同(实测 32 路分片 min/max 差 ≥2,8 路差 1)→ StopIteration 在 rank 间天然不同时发生
  2. trainer 对 StopIteration单 rank 局部处理(捕获后 break 进入 teardown),但 teardown 的 destroy_distributed 是全集群 world barrier——任一 rank 提前进入必然导致全体死锁
  3. 全程无数据量预校验、无 rank 间同步的终止信号、无有界超时 → 退化为永久挂起
  4. 附注:挂死时部分未断流 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
  • 挂死时状态:全部进程存活、NPU AICore 利用率 0%、超过 30 分钟无任何输出
likedislike