尧图精选

AutoDL 云 GPU 训练防断线:screen 长任务托管与故障排查

🕒 发布时间:2026/10/1 8:58:09 📁 来源:尧图网络
我在云上跑训练的头半年最大的浪费不是算力是断线。笔记本合盖、宿舍断网、地铁进隧道、VS Code 窗口手滑关掉——任意一个动作都能让跑了六个小时的训练进程在两秒内烟消云散。后来我把所有长任务都塞进screen会话里这个问题才算彻底消停。这篇就聊透一件事在 AutoDL 这类算力云上怎么用 screen 把网络训练任务稳稳地托管住从实例开机、会话创建、日志落盘到断线重连、故障排查全流程掰开揉碎讲一遍。适合刚接触云 GPU 的同学也适合已经跑过几轮实验、但还在被连接已断开折磨的算法工程师。1. 断线就白跑云上训练为什么必须有个会话保险1.1 一次合盖引发的血案SIGHUP 是怎么杀掉你的训练的先把原理讲清楚不然你永远会低估这个问题的杀伤力。我们用 SSH 登录远程服务器时建立的是一条交互式登录会话服务器端会给你分配一个伪终端pty。你的每个终端对应一个会话session会话里跑着一个前台进程组。当你断开 SSH——不管是网络抖动、客户端崩溃还是你主动退出——这个控制终端就消失了内核会向该会话的所有进程发送SIGHUP信号也就是挂起信号。默认情况下进程收到 SIGHUP 的处理方式就是直接终止。这就是为什么你在本地终端里敲python train.py跑得好好的SSH 一断screencap都不给你留。注意进程不是被网络杀死的是被内核的会话管理机制杀死的。理解了这一点你就能明白所有解决方案的本质都一样让训练进程脱离当前这个会随连接消失而消失的会话挂到一个独立于终端的载体上。nohup是最朴素的解法它让进程忽略 SIGHUP。但它有两个明显短板第一进程虽然活着你却失去了交互能力想中途改个参数、看个变量、手动触发一次保存只能干瞪眼第二nohup只是忽略了信号进程仍然挂在这个已经死掉的会话下标准输入输出重定向处理不当还会写出奇怪的日志。所以它适合一锤子买卖的脚本不适合需要你时不时瞄一眼、偶尔干预一下的训练任务。1.2 四种托管方案横向对比我为什么最后选了 screen云上跑长任务能选的托管方式其实不少我把它们拉平了对比一次你就知道该怎么选了。方案断线存活中途可交互多任务管理上手成本典型适用场景直接前台跑否是否极低三分钟内能跑完的调试nohup ... 是否弱靠 ps 找低一次性批处理脚本screen是是强会话列表中长时训练、需随时干预tmux是是最强窗格分屏中高需要同时盯日志、跑命令JupyterLab 内置终端视平台回收策略而定是弱极低快速验证不适合长跑tmux功能确实更强分屏用起来很爽但很多云平台的镜像里默认没装而且它的前缀键CtrlB在部分终端环境里会被截获。screen的优势在于几乎无处不在——绝大多数 Linux 发行版可以一条apt命令装好快捷键逻辑简单会话概念清晰资源占用极低。对我这种要的是稳定托管不是花哨分屏的场景来说screen 是性价比最高的那个。注意screen 解决的是网络断开问题不解决实例关机问题。云平台一旦关机或释放实例所有进程都会被回收screen 会话自然一并消失。别把它当成免死金牌。1.3 哪些人最该把这条流程固化下来只要你符合下面任意一条这篇内容就值得收藏实验周期超过 20 分钟、依赖网络连接稳定、需要在训练中途手动干预、需要随时查看 loss 曲线和日志输出、同时跑多个对比实验。特别是用 VS Code Remote SSH 连到云实例的同学——VS Code 的集成终端本质上还是一个 SSH 会话你关掉窗口的那一刻SIGHUP 一样会发出。我在 VS Code 里丢掉过两次实验结果之后才彻底养成先开 screen 再跑命令的条件反射。2. screen 到底怎么工作会话、窗口与分离重连机制2.1 会话与窗口两层结构别搞混很多人用了几十次 screen 还是搞不清会话和窗口的关系我用一个生活化类比说明screen会话session相当于一套公寓窗口window相当于公寓里的一个个房间。你租下这套公寓创建会话可以在里面开很多个房间窗口每个房间跑一件不同的事。你出门detach分离公寓还在房间里的灯还亮着里面的进程继续跑你回来时attach重连推开门直接进入你上次待的那个房间。这个两层结构带来的直接好处是一个 screen 会话可以同时容纳训练主进程日志监控GPU 状态轮询TensorBoard 服务四五个窗口你只需要记住一个会话名字就能在一套环境里来回切换比开四五个 SSH 连接干净得多。而会话本身是脱离终端的它的存在不依赖于你当前的 SSH 连接所以断线对它毫无影响。从技术实现上看screen 启动时会创建一个守护进程在screen -ls里能看到SCREEN进程这个进程持有一个 pty 主设备你的训练进程运行在它派生出的 pty 从设备上。你 attach 时只是把你的 SSH 终端接到这个主设备上做输入输出转发。断开连接时只是拆掉了转发链路pty 本身和里面的进程完好无损。这就是它能扛住断线的根本原因。2.2 前缀键与核心快捷键把这十几个动作背下来screen 的所有操作都靠一个前缀键触发默认是CtrlA。按下前缀键后松手再按功能键就完成一个动作。这套设计的好处是不和你程序里的按键冲突。常用的动作我整理成了表格建议直接照着练几遍。快捷键作用记忆点CtrlA然后d分离会话回到原始终端detach 首字母CtrlA然后c新建一个窗口createCtrlA然后n/p切到下一个 / 上一个窗口next / previousCtrlA然后0-9直接跳到第 N 号窗口编号直达CtrlA然后列出所有窗口让你选双引号像列表CtrlA然后A给当前窗口改名便于识别CtrlA然后k杀掉当前窗口危险需确认CtrlA然后[进入回滚模式翻看历史输出看之前的日志CtrlA然后?显示帮助忘了就查这里有两个特别值得说的点。第一CtrlA然后[进入的是复制模式回滚模式进去之后可以用方向键或PageUp/PageDown上下翻屏按Esc退出。这在你想回看前面几百行的报错时非常有用比翻终端缓冲靠谱得多。第二CtrlA然后k是直接杀窗口会弹一个Really kill this window [y/n]确认手滑按了y就没了训练进程也会一起被杀。我在这个键上栽过后来干脆把它从肌肉记忆里删掉了只保留干净的退出方式。如果CtrlA在你的某个终端环境里被占用比如某些编辑器的行首跳转可以在~/.screenrc里改前缀键# ~/.screenrc escape ^Jj # 上面的意思是把前缀键改成 CtrlJ后一个字符是备用触发键2.3 一份够用的 .screenrc 配置默认的 screen 有两个体验痛点一是底部没有状态栏你根本不知道自己在哪个窗口二是启动时会显示一段版权欢迎页每次都要按键跳过。这两点一条配置就能解决我自己的~/.screenrc常年就这么几行# 关掉启动时的欢迎信息 startup_message off # 开启底部状态栏显示窗口列表 hardstatus alwayslastline hardstatus string %{ kG}[ %{G}%H %{g}][% %{ kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%Lw%?%?% %{g}][%{B}%Y-%m-%d %{W}%c %{g}] # 加大回滚缓冲默认太小看日志不够用 defscrollback 10000 # 每次新窗口从家目录开始 chdir状态栏那一行看着像天书其实不用背直接复制粘贴就行效果是底部常驻一条灰色状态栏左边显示主机名中间是窗口列表当前窗口带星号右边是日期时间。有了它你一眼就能看到自己开到了第几个窗口、当前在哪一个。defscrollback 10000是为看日志准备的默认的 100 行缓冲基本没用翻两屏就到底了。提示.screenrc的修改只对新创建的会话生效已经在跑的会话需要重启才能应用。3. AutoDL 上从零跑通实例准备到训练启动的完整流程3.1 实例开机与环境确认数据该放哪儿先解决最容易踩坑的地方——存储路径。云实例通常有两块盘系统盘容量小、数据盘容量大。很多平台的约定是数据盘挂在/root/autodl-tmp这类路径下系统盘只有几十 GB。训练数据集动辄几十上百 GB你要是一股脑塞进系统盘跑到一半磁盘写满进程直接崩日志还会写得不明不白。我自己固化的规则是代码放系统盘的家目录便于版本管理数据集、权重、日志、checkpoint 一律放数据盘。日志尤其重要因为训练日志是持续追加写的几天下来的量很可观。启动前先确认一下磁盘余量# 看各挂载点的容量和使用率 df -h # 看当前目录到底是什么别把数据写到临时目录里 pwd ls -lh /root/autodl-tmp 2/dev/null | head开机时还有一个省钱技巧如果你只是要配环境、装依赖、下载数据完全可以先选无卡模式启动等所有准备工作做完再关机切成带卡模式。环境改动只要在系统盘上重启后一般还在但为保险起见配好环境后建议保存一次自定义镜像后续开新实例直接基于这个镜像起省掉重复配置的时间。3.2 安装 screen 并验证三步搞定大多数镜像已经预装了 screen先查一下which screen || screen --version如果返回为空装一下就行。Debian/Ubuntu 系用 aptCentOS 系用 yum# Ubuntu / Debian apt-get update apt-get install -y screen # CentOS / RHEL yum install -y screen装完再验证一次screen --version能看到版本号就说明可用。这里有个很多人忽略的细节在容器化实例里/dev/pts必须是可用的。如果你执行screen时报Cannot open your terminal /dev/pts/0说明你当前的终端设备权限不对处理办法是script /dev/null先切一个伪终端出来然后再执行 screen。这个报错在你用某些自动化脚本或特定客户端登录时很容易撞上。3.3 创建会话并把训练任务塞进去现在进入正题。先建立一个命名规范的会话名字里带上任务标识方便你同时开好几个实验时区分# -S 指定会话名-d -m 表示后台创建不立即进入 screen -dmS exp_resnet50 # 进入这个会话 screen -r exp_resnet50进去之后先给窗口改个有意义的名字CtrlA然后A输入train然后启动训练。关键一步是把输出重定向到日志文件这样即使你不 attach也能在别的终端里tail -f追踪进度# -u 让 Python 输出不做缓冲日志实时可见 python -u train.py \ --data_dir /root/autodl-tmp/dataset \ --save_dir /root/autodl-tmp/ckpt \ --epochs 100 --batch_size 32 --lr 1e-4 \ 21 | tee -a /root/autodl-tmp/logs/train_$(date %m%d_%H%M).log关于这个命令有三处细节必须解释清楚不然你一定会遇到日志半天不更新的困惑。第一-u参数。Python 默认对 stdout 做行缓冲或块缓冲当输出被重定向到文件或管道时会切换成 4KB 左右的块缓冲结果就是日志文件里半天不出现新内容你以为进程卡死了其实只是没刷盘。加-u强制无缓冲问题解决。如果你不想改命令行也可以设环境变量export PYTHONUNBUFFERED1第二21的位置。它的作用是把标准错误也合并到标准输出这样异常堆栈才会一起进日志。必须写在管道符|之前写在后面就变成把 stderr 输出到终端了等于白写。第三tee -a的-a是追加模式。如果你用了同名日志文件又不加-a每次重启训练都会把之前的日志清空。我用日期时间戳命名天然避免覆盖同时-a兜底。启动完之后按CtrlA然后d分离回到你原来的终端。此时screen -ls应该能看到类似这样的输出There is a screen on: 12345.exp_resnet50 (Detached) 1 Socket in /run/screen/S-root.前面的数字是进程 PID后面的名字是你起的会话名(Detached)表示已分离进程还在跑。到这一步你就可以放心合上笔记本了。3.4 新开一个窗口盯训练状态训练和监控应该分在两个窗口里互不干扰。在会话里按CtrlA然后c新建窗口改名叫monitor然后跑watch -n 5 nvidia-smi --query-gpuutilization.gpu,memory.used,memory.total --formatcsv这条命令每 5 秒刷新一次 GPU 利用率和显存占用你能直观看到训练有没有真的在吃卡。如果利用率长期在个位数徘徊、显存占用也很低八成是数据加载成了瓶颈num_workers设太小或者数据放在慢速存储上。想同时盯日志再开一个窗口tail -f那个日志文件就行。三个窗口——train、monitor、log——基本覆盖了日常训练的全部观察需求。4. 训练过程的细节把控日志、显存、断点与多卡4.1 日志策略别让日志把盘写爆日志是训练的眼睛但它也是磁盘杀手。一个中等规模的训练任务如果每个 step 都打一行 loss一天下来几百 MB 到几个 GB 都不奇怪。我的做法是分三层控制训练脚本里用logging模块控制级别正常训练只打 INFO调试时切 DEBUG每 N 个 step 才输出一次指标而不是每个 step日志按任务分文件避免单个文件无限增长。关于 screen 自身的日志还有一个容易忽略的功能screen -L会把窗口的所有输出记录到当前目录的screenlog.0。这个功能在你想完整留档时很好用但要注意它会和你tee出来的日志内容重复白白占两倍空间。我的建议是二选一——要么用tee精确控制日志内容要么用screen -L图省事别两个一起开。另外提醒一个实操细节如果你在会话里用CtrlA然后[翻历史翻到很久之前的内容时屏幕可能显示不全这是因为回滚缓冲的默认值太小。前面.screenrc里那个defscrollback 10000就是为这个准备的10000 行足够你翻回大半个训练周期的输出。4.2 显存估算与 batch size 的取舍跑训练最常见的报错是CUDA out of memory而它通常发生在训练已经跑了几百个 step 之后——比一开始就崩更让人抓狂。要避免这个启动前先做个粗略估算。以混合精度训练为例显存占用大致由四块构成组成粗略估算方式说明模型权重参数量 × 2 字节fp16混合精度下主体用半精度优化器状态参数量 × 8 字节Adamfp32 的一阶、二阶动量梯度参数量 × 2~4 字节取决于是否用梯度累积激活值与 batch size 和序列长度强相关变动最大的一块举个例子一个 1 亿参数的小模型权重约 200MB优化器状态约 800MB梯度约 400MB加起来静态部分就 1.4GB 左右剩下全给激活值和中间张量。所以判断能不能跑的关键不在模型大小而在 batch size。如果你不确定用一个小 batch 先跑通再逐级翻倍试探直到显存占用到 85% 左右停下。这个逐级试探的过程本身就适合放在 screen 里做因为你可能要跑很多轮。提示显存碎片也是 OOM 的常见诱因。如果反复出现明明够用却 OOM可以试试设置PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True来缓解碎片问题。4.3 断点续训把手滑的成本降到最低screen 能扛住断线但扛不住实例被关机、系统盘被重置、以及你自己手滑。真正让训练任务变得抗揍的是断点续训机制。这一条比 screen 本身更重要我说得直白一点没有 checkpoint 的训练任务本质上就是在赌运气。我的习惯是三层保存策略并行。第一层是周期性保存每 N 个 epoch 或每 M 个 step 存一次完整状态包括模型权重、优化器状态、当前 epoch 和 step、学习率调度器的状态、以及随机数种子。少了后面这几项续训出来的曲线会有明显跳变实验结论就不干净了。第二层是滚动保留只保留最近若干个 checkpoint老的自动删掉避免磁盘被塞满——这个逻辑写在训练脚本里别指望手动清理。第三层是最佳保存只在验证指标创新高时另存一份训练结束直接拿这份去评估。# 保存完整状态的示例结构缺一项都可能导致续训不稳 ckpt { model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), epoch: epoch, global_step: global_step, best_metric: best_metric, rng_state: torch.get_rng_state(), } torch.save(ckpt, f/root/autodl-tmp/ckpt/last_{epoch}.pt)续训的时候加载权重之后一定要确认optimizer和scheduler也恢复到位否则学习率会从初始值重新开始前期训练基本白费。这个坑我在早期踩过不止一次。4.4 多卡训练放在 screen 里要注意什么多卡场景比如torchrun启动分布式训练放进 screen 里跑有几个额外的注意点。第一分布式训练的进程组对信号很敏感CtrlC一次可能只杀掉主进程留下几个孤儿进程还在占着显存。所以杀任务之前先用nvidia-smi看清楚有哪些进程确认后再动手清理。第二多卡任务的启动命令要写成单行或者用脚本包起来别在 screen 里手敲多行命令——screen 的输入转发偶尔会因为终端差异出现字符丢失长命令容易被截断。我的习惯是把启动命令写进run.sh验证过一遍再在 screen 里执行bash run.sh。第三多卡训练启动慢init_process_group到真正开始吃卡之间可能有一两分钟的静默期这段时间日志什么都不输出。新手容易误判为卡死然后杀掉白白浪费一次启动。判断方法是看nvidia-smi里有没有对应的进程号出现有进程就说明还在初始化耐心等。5. 常见故障排查与避坑速查5.1 会话相关问题的速查表screen 本身的报错种类不多但每一种都挺让人懵我把遇到过的整理成一张表遇到时直接查。现象原因处理方式screen -ls显示(Attached)但连不上会话被别的终端占用或上次异常退出没释放用screen -d -r 会话名强制踢掉原连接再进入screen -ls显示(Dead ???)会话内进程异常退出会话残留screen -wipe清理死会话Cannot open your terminal /dev/pts/0当前终端设备权限异常先执行script /dev/null再启动 screen修改了.screenrc但状态栏没出现旧会话不会重载配置分离旧会话新建一个会话验证CtrlA后按键没反应前缀键被上层终端吞掉改.screenrc的escape或换用 tmux日志里中文显示为乱码服务端 locale 未设置在会话里export LANGC.UTF-8后重启任务screen -d -r这个组合值得单独记一下。场景很常见你在办公室电脑上 attach 了会话忘了分离回家用笔记本再screen -r就会提示会话已被占用。这时候-d -r会先强制分离远程连接再把会话接到你当前终端上一步到位。5.2 训练中断后的排查思路训练突然没了先别急着重跑按顺序查三件事。第一步screen -ls看会话还在不在。如果会话还在但没输出说明进程活着但卡住了多半是在等数据、等锁、或者被 IO 阻塞。第二步nvidia-smi看有没有你熟悉的进程。如果进程消失、显存已释放那就是进程死了去翻日志文件末尾的堆栈。第三步df -h看磁盘。磁盘写满导致的进程猝死是最隐蔽的一类问题日志可能只写到一半就断了甚至什么都不写因为它连写日志的空间都没有了。如果日志末尾是Killed这类字样大概率是被系统的 OOM Killer 干掉的原因通常是内存不是显存占用过高。这时候要检查的是数据加载部分比如num_workers开太大、把整个数据集一次性读进内存、或者缓存没做限制。内存和显存是两回事别看到 OOM 就只盯着显存。还有一种情况是实例层面的问题平台侧因为资源调度回收了实例或者网络存储挂载掉了。这类问题在日志里往往表现为無任何异常输出直接断在一行中间。你唯一能做的是确认最后一版 checkpoint 是否完整——如果 checkpoint 是在写入过程中被打断的那个文件可能是损坏的加载时会报unpickling error。所以保存 checkpoint 时最好先写临时文件再原子重命名避免留下半截文件。# 原子保存的思路先写中间文件再 rename torch.save(ckpt, /root/autodl-tmp/ckpt/tmp.pt) os.replace(/root/autodl-tmp/ckpt/tmp.pt, /root/autodl-tmp/ckpt/last.pt)5.3 几条我在实际使用中沉淀下来的习惯第一条会话命名必须带信息量。别用test、a、1这种名字等你同时开五个会话时就彻底分不清了。我的命名格式是任务_模型_日期比如seg_unet_0412一眼就知道是哪个实验。第二条启动命令一律写进脚本。不管是单卡还是多卡都在run.sh里写好参数验证过一遍再进 screen 执行。这样做的额外好处是命令有版本记录如果配合 git 管理换机器复现时不用回忆当初敲了什么。第三条分离之前先确认状态。按CtrlA然后d之前我会习惯性看一眼窗口里有没有正在运行的进程、日志有没有正常滚动。有几次我分离之后才发现命令敲错了一个参数结果跑了半小时全是废的。养成分离前看一眼的习惯成本是两秒钟收益是半小时算力。第四条也是最重要的一条screen 是保险checkpoint 才是救生艇。我把 screen 和 checkpoint 的优先级排得很清楚——先确保训练脚本能在任意时刻保存和恢复再考虑用 screen 托管。因为实例终究会关机会话终究会消失只有落在数据盘上的 checkpoint 和日志是真实存在的。刚上云的同学最容易把顺序搞反把 screen 当成了解决一切问题的银弹结果在一次实例回收里丢掉了三天的实验进度。第五条日志和 checkpoint 都放在数据盘并且养成每周把数据盘的重要产物同步一份到别处的习惯。数据盘通常比系统盘可靠但它也不是绝对保险——实例一旦被释放盘上的东西就一起没了。这个教训我吃过一次之后所有实验的最终权重都会额外备份一份不再赌任何单点存储。最后再补一个我刚上手时完全忽略的细节进 screen 之前先把工作目录切对。很多人习惯cd到项目目录之后直接screen -S xxx看起来没问题但如果你是在某个符号链接目录或者挂载点下面创建的会话不同窗口继承的工作目录可能和你预期不一致脚本里的相对路径就会找不到文件。我的做法是会话创建后第一件事就是显式cd到绝对路径然后pwd确认一次再开始跑任务。这个动作也就三秒钟但省下的排查时间可能是半小时。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →