ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

训练异常排查:保存配置、日志和随机种子

训练异常排查:保存配置、日志和随机种子 训练异常排查保存配置、日志和随机种子报错后的下一步应当可选择不是每个异常都要立刻重启训练。显存溢出可以先检查批大小和样本形状数据读取失败则先核对分片和权限。日志若能给出异常类别和当前阶段处理者就能选择合适动作避免用一次全量重跑掩盖真正的问题。这份记录也让后来的人知道哪些现象已经验证过哪些仍只是待查的猜测。排查结束后把这次异常对应的提交、修复配置和复跑结果连在同一条记录里。下次类似告警出现时先比较这些稳定字段效率比凭印象回忆训练过程高得多。一次分布式训练挂起主进程只留下 NCCL timeout本文围绕“PyTorch 训练流程优化与分布式训练实践排障时怎样留下有效证据”整理一个可复查的技术检查点。文中的容量、时延和故障情形只用于说明验证方法实际判断应以锁定的代码版本、脱敏样本、运行环境与评测脚本复测为准。在一个合成的挂起场景中进程仍出现在nvidia-smi列表中但计算利用率归零。若只保留主进程的超时信息就无法区分 OOM、数据加载阻塞与通信异常也难以复查触发链路。在多卡集群训练中死锁和静默挂起是定位成本最高的故障类型。如果排障体系没有在崩溃瞬时捕捉并 Dump 出 GPU 物理内存快照和 Python 调用栈排查过程就会沦为无休止的猜测与盲目重启。----------------------------------------------------------------------- | 常规低效的排障体验 (No Trace Evidence) | | - 只有主 Rank 抛出掩盖真相的 NCCL Timeout | | - GPU 显存全满但无从得知哪个 Tensor 引发了最后的 OOM | | - 无法定位是硬件 Drop Packet 还是 Worker CPU 卡死在 Data Loading | ----------------------------------------------------------------------- | 注入崩溃拦截与黑匣子 Dump v ----------------------------------------------------------------------- | 黑匣子故障证据捕捉 (Fault Memory Snapshot) | | - 信号拦截器 (SIGUSR1 / SIGSEGV) 自动导出 Python 堆栈 (traceback) | | - PyTorch CUDA Memory Dump Snapshot: 保存物理 Memory Segment 视图 | | - 广播崩溃事件 (Rank Breakdown Alert) 阻断静默挂起 | -----------------------------------------------------------------------排障必须保留的三大现场GPU 显存快照、分布式 Rank 通信拓扑、系统 Syscall Trace为了缩短定位范围训练 Harness 应在运行时持久化留存三类关键现场证据第一GPU 物理显存分配快照CUDA Memory Dump Snapshot。通过 PyTorch 内置的torch.cuda.memory._record_memory_history()可以记录显存中每一个 Block 的分配、释放与 Python 源代码行号的映射。在抛出 OOM 时将其 Dump 成二进制文件能通过 Visualizer 直观看到是哪一个巨大的 Attention 临时矩阵吃光了最后几百兆显存。第二分布式 Rank 心跳与通信状态拓扑。当出现 NCCL 阻塞时通过向所有进程发送SIGUSR1信号触发各卡独立的 Thread Dump迅速识别出究竟是 Rank 12 在等待数据加载还是 Rank 0 停在了磁盘 Checkpoint 保存上。第三CPU Data Feeding 异步队列监控。监控 PyTorchDataLoader的 Prefetch 队列深度区分是 GPU 计算崩溃还是 CPU 读盘卡死。PyTorch 分布式故障现场捕捉与黑匣子记录架构黑匣子记录架构展示了当训练发生 OOM 或卡死信号时分布式拦截器如何抢救现场数据。PyTorch 自动故障现场抓取拦截器与 Dump 引擎下面是一段生产级可用的 PyTorch 崩溃拦截与 GPU 显存快照 Dump 引擎代码。它支持信号触发式 Stack Dump 和 OOM 瞬时的显存历史导出。import os import sys import time import signal import traceback import logging import torch # 配置日志 logging.basicConfig(levellogging.INFO, format[%(asctime)s] [%(levelname)s] %(message)s) class DistributedFaultInterceptor: def __init__(self, dump_dir: str ./dump_evidence): self.dump_dir dump_dir self.rank int(os.environ.get(RANK, 0)) self.local_rank int(os.environ.get(LOCAL_RANK, 0)) os.makedirs(self.dump_dir, exist_okTrue) # 1. 初始化显存分配历史记录 (需 PyTorch 2.1 支持) if torch.cuda.is_available(): try: # 记录最多 100,000 次显存分配事件 torch.cuda.memory._record_memory_history( enabledall, contextalloc, stackspython ) logging.info(f[Rank {self.rank}] 成功开启 CUDA 物理显存分配历史追踪器) except Exception as e: logging.warning(f无法开启显存追踪器: {e}) # 2. 注册系统信号拦截器 (例如通过 kill -3 pid 触发 SIGUSR1 排查死锁) signal.signal(signal.SIGUSR1, self._signal_stack_dump_handler) def _signal_stack_dump_handler(self, signum, frame): 当收到 SIGUSR1 信号时强制导出当前进程的调用堆栈 dump_file os.path.join(self.dump_dir, fstack_rank_{self.rank}_pid_{os.getpid()}.txt) logging.warning(f收到信号 {signum}正在导出调用堆栈至 {dump_file}...) with open(dump_file, w) as f: f.write(f Rank {self.rank} (Local Rank {self.local_rank}) Stack Trace \n) traceback.print_stack(frame, filef) logging.warning(f堆栈已保存: {dump_file}) def dump_cuda_memory_snapshot(self, tag: str oom): 当发生 OOM 时导出显存快照二进制文件可导入 https://pytorch.org/memory_viz 查看可视化图表 if not torch.cuda.is_available(): return snapshot_file os.path.join(self.dump_dir, fcuda_memory_rank_{self.rank}_{tag}.pickle) try: logging.error(f[Rank {self.rank}] 正在导出 CUDA 显存 Snapshot 至 {snapshot_file}...) torch.cuda.memory._dump_snapshot(snapshot_file) logging.error(f✅ 显存 Snapshot 导出成功可使用 PyTorch Memory Visualizer 校验剖析。) except Exception as e: logging.error(f导出显存 Snapshot 失败: {e}) def handle_exception_and_dump(self, exc_type, exc_val, exc_tb): 全局未捕获异常句柄 logging.critical(f [Rank {self.rank}] 捕获到全局致命异常: {exc_val}) # 保存异常堆栈文件 error_file os.path.join(self.dump_dir, ferror_rank_{self.rank}.log) with open(error_file, w) as f: f.write(fTime: {time.strftime(%Y-%m-%d %H:%M:%S)}\n) f.write(fRank: {self.rank} | Local Rank: {self.local_rank}\n) f.write(fException Type: {exc_type.__name__}\n) f.write(fException Value: {str(exc_val)}\n\n) traceback.print_exception(exc_type, exc_val, exc_tb, filef) # 如果是 CUDA OOM自动 Dump 显存快照 if issubclass(exc_type, RuntimeError) and out of memory in str(exc_val).lower(): self.dump_cuda_memory_snapshot(tagoom_critical) # 调用默认句柄打印到 stderr sys.__excepthook__(exc_type, exc_val, exc_tb) # 使用示例 if __name__ __main__: interceptor DistributedFaultInterceptor(dump_dir./dump_evidence) # 将拦截器绑定到全局未捕获异常 sys.excepthook interceptor.handle_exception_and_dump print(f当前进程 PID: {os.getpid()} | 可通过以下命令测试堆栈 Dump: kill -3 {os.getpid()}) # 模拟触发一次 CUDA OOM 异常以验证拦截引擎 if torch.cuda.is_available(): print(正在模拟分配超大 Tensor 以触发 OOM 测试...) try: # 申请一个超出普通显存极限的超大 Tensor huge_tensor torch.empty((100000, 100000, 100), dtypetorch.float32, devicecuda) except Exception as e: # 手动触发全局异常句柄演示 interceptor.handle_exception_and_dump(type(e), e, e.__traceback__) # 清理生成的临时 dump 目录 if os.path.exists(./dump_evidence): import shutil shutil.rmtree(./dump_evidence)这段拦截引擎能够保证在发生 OOM 的瞬间自动将 PyTorch 物理 Memory Snapshot 保存为 Pickle 文件并且支持使用kill -3信号主动导出进程的当前 Python 堆栈。排障日志公约如何让日志打印从“废话连篇”变成“定位利器”除了崩溃 Snapshot 的硬核证据日常训练日志的格式也需要遵守排障公约。很多项目的日志文件动辄几个 G打开全是Epoch 1, Step 100, Loss: 2.34的重复刷新关键的节点 IP、GPU 温度、主机名却一概没有。高效的训练日志公约应当包含以下固定元数据头结语排障记录应让后来的人能重建现场而不是只看到最终修复。日志要说明当时的上下文训练停止的那一刻最有价值的是配置、数据版本、最近的指标和异常栈之间的对应关系。只截取最后一行报错通常不够。记录这些信息不等于保存原始训练数据敏感字段仍应在落盘前去掉。
返回列表