1. 为什么远程训练离不开一个能"挂住"的会话窗口
刚入行那会儿,我在自己那台游戏本上跑一个语义分割的小模型,风扇从晚饭一直吼到凌晨,眼看着验证集指标一点点往上爬,结果键盘不小心被猫踩了一下,终端窗口一抖,训练进程直接没了。那种感觉比丢文件还难受,因为前面几个小时的电费和时间全白费了。后来开始租用云端算力,用 screen 窗口在 Autodl 服务器上训练网络,我才真正意识到"会话持久化"这四个字对做深度学习的人有多重要。这不是什么高深技术,但它是把训练从"人守着机器"变成"机器自己跑"的分水岭。
这篇东西写给两类人:一类是刚接触算力平台、准备把自己的第一个训练脚本丢到远程机器上跑的新手;另一类是已经会用 Autodl,但每次断线就手忙脚乱、训练一挂就得从头再来的人。我会把 screen 的机制、Autodl 实例的特点、训练任务的日志与监控、以及我自己踩过的一堆坑都摊开讲。读完你至少能做到一件事:合上电脑去睡觉,第二天回来训练还在跑,日志完整,显存没被白占。
核心工具就一个:screen。核心平台就是 Autodl 这类按量计费的 GPU 算力云。核心场景就是把神经网络训练任务安全地托管在远程服务器上。听起来简单,但每个环节都有讲究,下面一点点拆。
1.1 本地训练和云上训练,到底差在哪
先说清楚为什么要上云。本地训练最大的问题不是算力,而是"间断"。你的笔记本要关机、要休眠、要断网、要挪到另一个房间,任何一个动作都可能打断训练进程。而深度学习训练天然是长任务,动辄几小时甚至几天,中间还经常需要跑几十个 epoch 才能看出收敛趋势。让一个长任务去迁就一台会移动、会休眠、会没电的设备,本身就违背常理。
云上 GPU 实例把算力从设备上剥离出来,你租的是一块 RTX 4090 或者 A100,通过网络连进去操作。设备在哪、你人在哪,理论上互不影响。但这里有个陷阱:连接是靠 SSH 长连接维持的。你本地网络一断、笔记本一合盖,SSH 通道就断了,默认情况下,SSH 通道上挂着的进程会收到挂断信号,直接被杀掉。也就是说,上云只解决了一半问题,剩下那一半,得靠 screen 这类会话管理工具来补。
我见过太多人第一次用 Autodl 是这样的:SSH 连上去,敲python train.py,看着日志刷刷往外冒,心里美滋滋,然后关掉终端去吃个饭。回来一开,进程早没了,模型 checkpoint 也没保存。这不是平台的错,是没搞懂"前台进程"和"会话"的关系。
1.2 算力平台实例的本质:一台随时会被回收的远程主机
把 Autodl 的实例想象成一台你按小时租的远程电脑,这个类比虽然粗糙但很准。你开机(创建实例)的时候,平台给你分配一块物理 GPU、一部分 CPU、内存和一块系统盘,再挂一块数据盘。你关机(停止实例)的时候,GPU 和计算资源被平台收回去给别人用,按量付费的实例通常只对数据盘收很低的存储费,GPU 计费停止。开机再开,环境理论上还在,但这中间如果有任务没跑完,那就是断了。
这里有个关键认知:实例的生命周期和你的训练任务生命周期是两码事。你以为任务在跑,其实任务只活在实例开机的那段时间里。一旦实例关机、或者平台因为欠费、维护把实例停掉,任务就没了。screen 能做的是在你和实例之间建立一个"命题持久"的会话,让你断开 SSH 后任务继续;但它救不了实例本身被关机的情况。搞清楚这个边界,后面很多坑就不会踩。
另外,Autodl 上实例的系统盘和数据盘容量有限,默认数据盘一般是 50GB 左右。训练用的数据集、中间 checkpoint、日志文件全堆在这上面,很快就会满。磁盘一满,训练进程写 checkpoint 失败,轻则报错退出,重则把已有文件写坏。这跟 screen 没关系,但和"让训练稳定跑下去"这件事直接相关,后面我会单独讲。
1.3 screen 到底解决了什么问题
一句话概括:screen 让你在断开 SSH 之后,进程依然附着在一个虚拟终端上继续运行,等你下次连回来,还能重新"贴"回那个界面,看到实时输出。它的原理是在服务器端启动一个独立于你当前登录会话的守护进程,你的训练任务挂在这个守护进程管理的虚拟终端上,而不是挂在你这次 SSH 登录的会话上。
这就好比你在公司会议室开了个视频会议,然后把会议"托管"出去,自己先离开工位。会议照常进行,你什么时候回来,屏幕上还显示着当前的画面。而不用 screen 直接跑,相当于会议全靠你的电脑直播,你一锁屏,会议就断了。
对训练任务来说,这个能力带来三个实际好处。第一,不怕本地断网,Wi-Fi 抖动、路由器重启都无所谓。第二,可以同时开多个会话跑多个实验,互相不干扰,方便做对照。第三,可以随时回来查看进度,不用一直盯着。这三点看似平淡,但真正做实验的人知道,能一边跑着 A 实验一边调 B 实验的代码,效率是翻倍的。
2. screen 的核心机制与命令体系拆解
很多人学 screen 是照着命令表背,结果转头就忘。问题在于没理解它的"会话—窗口—分离"这套模型。我先把机制讲透,再给命令,记忆就牢了。
2.1 会话分离:把进程从终端上"摘"下来
要理解分离,先理解 Linux 的终端模型。你在 SSH 里敲的每一条命令,都是当前登录会话的子进程。这个会话由登录 shell 管理,当 SSH 断开,shell 收到 SIGHUP(挂断信号),它会把信号传给所有子进程,进程默认行为就是退出。所以你不加任何保护直接跑训练,断线就死。
screen 的做法是:它先启动一个常驻后台的服务进程(server),然后你在里面创建会话。这个会话有自己的虚拟终端,从登录 shell 的角度看,你的训练任务是挂在 screen 的进程树下,而不是挂在 SSH 会话下。SSH 断开时,受到 SIGHUP 的是 screen 客户端,而不是 screen server,训练进程安然无恙。
Ctrl+a d这个组合键就是"主动分离",把当前客户端从 server 上摘下来,GUI 界面消失,但 server 和里面的进程继续在后台活。你下次screen -r就是"重新附着",把客户端再贴回去。理解了这个"客户端可插拔、server 常驻"的模型,所有命令都只是这对动作的变体。
2.2 screen、nohup、tmux 三者怎么选
新手经常纠结:既然要后台跑,nohup不就够了?确实,nohup python train.py > log.txt 2>&1 &也能让进程在断线后存活,而且更简单。但它有个硬伤:你没法再"回到"这个进程的交互界面。nohup 把进程丢到后台,输出重定向到文件,你能看日志,但没法在进程内部敲命令、没法看它当前占用的显存对应的实时曲线,更没法在里面临时开个 Python 交互调试环境。
tmux 是 screen 的现代替代品,功能更强,窗口切分更漂亮,配置更灵活,社区也更活跃。如果你是新项目、没有历史包袱,我其实更推荐 tmux。但为什么这篇文章还是讲 screen?因为 Autodl 的默认镜像里 screen 基本都预装了,screen命令开箱即用,不用额外装;而且大量老教程、老脚本里用的都是 screen,接手别人代码时你得看得懂。两者核心概念(会话、分离、重连)几乎一样,学会一个,另一个半小时就能上手。
简单给个选型结论:
| 工具 | 是否可重连交互 | 上手难度 | 预装情况 | 适用场景 |
|---|---|---|---|---|
| nohup | 否,只能看日志 | 极低 | 系统自带 | 一次性、确定不用回来操作的任务 |
| screen | 是 | 低 | Autodl 镜像基本自带 | 常规训练、需要回来查看/调试 |
| tmux | 是 | 中 | 部分镜像需安装 | 多窗口、复杂实验管理 |
我的实际做法是:日常训练一律 screen,需要开一堆并排窗口对比多个实验时换 tmux。两者不冲突,可以共存。
2.3 必须刻进肌肉记忆的命令清单
命令不用多,下面这些覆盖了 95% 的使用场景。我按"创建—操作—维护"的顺序列,并且标出容易记混的点。
# 创建并进入一个名为 train_exp1 的新会话 screen -S train_exp1 # 查看当前所有会话,尤其是它们的状态是 Attached 还是 Detached screen -ls # 恢复(附着)到指定会话 screen -r train_exp1 # 当会话显示为 Attached(说明别处还连着)时的强制接管 screen -d -r train_exp1 # 单独分离一个别处的会话,让它变成 Detached screen -d train_exp1 # 直接在外部杀掉一个会话,不用进去 screen -S train_exp1 -X quit会话内部的操作全靠Ctrl+a前缀,这个前缀叫命令键,默认是Ctrl+a:
Ctrl+a然后按d:分离,回到普通终端。Ctrl+a然后按k:杀当前窗口(会二次确认)。Ctrl+a然后按c:新建一个窗口。Ctrl+a然后按n/p:切换到下一个 / 上一个窗口。Ctrl+a然后按":列出所有窗口让你选。
提示:
Ctrl+a在别的地方(比如某些编辑器的行首跳转)也被用,进 screen 后偶尔会误触。习惯了就好,或者自己改成Ctrl+b,写在~/.screenrc里。
命名这件事值得单独强调。我强烈建议每个会话都用有意义的名字,screen -S exp_lr3e4_bs32这种,别用默认的12345.pts-0.xxx。等你同时跑五六个实验,满屏无名会话时,你会感谢当初那个起名字的自己。命名规范本身就是一种实验管理,后面第 5 章还会展开。
3. 从开实例到跑通训练的完整实操
理论讲完,进入实操。我按"全新开始"的顺序走一遍,每一步都说明为什么这么做,你照着抄基本不会出问题。
3.1 实例创建与镜像、数据盘的选择
创建实例这一步,几个选择直接影响后面好不好跑。第一是 GPU 型号,看你模型大小和预算,小模型 4090 性价比高,大模型上 A100/H800 这类。第二是镜像,Autodl 提供了预装好 PyTorch、CUDA、conda 的基础镜像,选一个和你代码依赖匹配的版本,能省掉大量配环境的时间。第三是数据盘,默认一般够用,但如果你的数据集几十上百 GB,记得在创建时就把数据盘调大,事后扩容麻烦。
实例起来之后,第一件事我会把 conda 环境确认一下,然后装缺失的依赖。Autodl 有个很实用的"学术网络加速",跑source /etc/network_turbo之后 pip 安装会快很多。这一步和 screen 无关,但装依赖动辄十几分钟,如果中途断线,装一半的包很容易出问题。所以我的习惯是:装依赖这种"短任务"也用 screen 跑,或者至少保证网络稳定。
数据盘路径通常挂在/root/autodl-tmp之类的位置,注意不要把大数据集塞系统盘,系统盘满了实例会出各种诡异问题。训练脚本里所有输出路径、日志路径,我都会显式指到数据盘目录下。
3.2 用 screen 拉起第一个训练任务
环境准备好,进入正题。假设脚本叫train.py,我用屏幕会话把它拉起来:
# 1. 创建会话 screen -S train_exp1 # 2. 在会话里激活环境并启动训练(-u 关闭 Python 输出缓冲,让日志实时落盘) python -u train.py --epochs 100 --batch-size 32 --lr 3e-4 2>&1 | tee /root/autodl-tmp/logs/exp1.log # 3. 看到日志正常刷起来后,按 Ctrl+a 再按 d 分离这里有几个细节必须解释清楚,不然容易翻车。python -u里的-u是关掉标准输出的缓冲。Python 默认会把打印内容攒在缓冲区里,等缓冲区满了或者程序结束才落盘,结果就是你cat日志文件半天没动静,以为程序卡死了。加了-u,每一行 print 都立刻写出去,方便实时监控。
2>&1 | tee这一串的意图是:把标准错误合并到标准输出(2>&1),然后用tee同时输出到屏幕和文件。这样既有实时画面,又有持久日志。如果只重定向到文件(> log.txt),你在 screen 里就看不到进度了,心里没底。双通道输出是我强烈推荐的做法。
日志路径我显式写到数据盘,避免系统盘被撑爆。另外建议提前mkdir -p建好日志目录,不然tee会因为目录不存在直接失败。
3.3 分离、回看、杀会话的完整动作
分离之后,你就可以放心关终端、关电脑了。下次回来,SSH 重新连上,先看有哪些会话在跑:
screen -ls输出大概是这个样子:
There is a screen on: 12345.train_exp1 (Detached) 1 Socket in /run/screen/S-root.看到Detached就说明它好好活着。然后恢复:
screen -r train_exp1如果这时提示There is a screen on ... (Attached),说明这个会话在别的地方还连着,可能是你上一个 SSH 会话没释放。用screen -d -r train_exp1强行接管,它会先分离原来的连接,再把你接上去。
训练跑完或者要主动结束,进去之后按Ctrl+a再按k,确认一下当前窗口是否是你的训练窗口(多窗口时容易杀错),然后再确认退出。或者干脆在会话里敲exit,会话里最后一个窗口退出后会话自动结束。要是不想进去,用前面给的screen -S train_exp1 -X quit外部杀。
注意:杀会话之前一定确认 checkpoint 已经存好。screen 被杀,里面所有进程都会被清掉,正在写的模型权重文件可能损坏。
3.4 日志与监控的配套做法
screen 解决"不断线",但训练稳不稳,还得看监控。我日常用的三板斧:
第一是看日志尾部,tail -f /root/autodl-tmp/logs/exp1.log,实时刷 loss 和指标。-f会持续跟踪文件新增内容,配合前面-u的实时输出,体感很接近在前台跑。
第二是看 GPU 状态,watch -n 1 nvidia-smi每秒刷新一次,观察显存占用和 GPU 利用率。如果显存占得很满但 GPU-Util 长期接近 0,说明数据加载是瓶颈,可能是 dataloader 的num_workers设得太小。
第三是看 CPU 和内存,htop直观,能看出是不是内存爆了导致进程被系统杀掉。这种情况往往表现为主进程突然消失、日志无报错终止,很容易被误判成平台问题。
这三样我都会在新会话或新终端里跑,主训练会话保持干净,不被监控输出污染。整套组合下来,一个人管三四个并行实验完全不慌。
4. 训练场景里那些真正会咬人的坑
命令会敲只是入门,真正拉开差距的是对"什么时候会出事"的预判。下面这些坑我基本都亲自踩过。
4.1 显存、数据盘与缓存目录
显存这块,最常见的翻车是没估准 batch size。你以为显存够,跑到某个 epoch 数据 shape 一变,OOM 直接把进程干掉。规避做法是先小 batch 跑几个 step 探路,或者用 PyTorch 的torch.cuda.empty_cache()加梯度累积来降低峰值。但更根本的是,第一个 epoch 一定要在 screen 里盯着,确认稳定了再分离。很多人急着分离去睡觉,结果它二十分钟后 OOM,白睡一觉。
数据盘满是我见过最隐蔽的故障。表现是训练跑到一半突然报No space left on device,或者更糟——checkpoint 写了一半,文件损坏,下次加载失败。我的习惯是定期df -h看数据盘余量,同时给 checkpoint 做轮转,只保留最近 N 个,旧的删掉。日志文件也会膨胀,尤其是开了 verbose 的训练,tee输出的 log 几天就能到几个 G,记得配 logrotate 或者手动清理。
缓存目录也容易被忽视。Hugging Face 的模型缓存默认在~/.cache,也就是系统盘。你要下载预训练模型,系统盘很容易被撑满。我的做法是提前设export HF_HOME=/root/autodl-tmp/hf_cache,把它指到数据盘。这类环境变量最好写进.bashrc,一劳永逸。
4.2 实例关机、断线、超时的应对
前面说过,screen 救不了实例本身。Autodl 的按量付费实例如果长时间空闲或者你主动关机,GPU 会被释放。所以关键动作是:训练没结束之前,别关实例。有些平台有闲置检测机制,所以哪怕训练在跑,也尽量别让实例进入奇怪状态。
SSH 连接超时也很常见。你开着 screen 的会话,但本地 SSH 客户端因为长时间无操作被服务端踢掉,这个不影响 screen 里的任务,下次重连继续。但如果你在里面正跑一个交互式命令(比如手动调参),那就被打断了。解决方法是配置~/.ssh/config,加ServerAliveInterval 60之类的保活参数,让连接定期发心跳。
还有种情况是实例被平台迁移或重启。Autodl 偶尔会因为物理机维护需要重启你的实例,如果没提前通知或者你没注意,训练就断了。应对策略是养成"随时可恢复"的习惯:定期保存 checkpoint,脚本支持--resume从断点继续,日志里记录清楚当前 epoch。这样即使断了,重开实例、重进 screen、加--resume就能接着跑,损失可控。
4.3 常见问题速查表
把高频问题整理成表,出事了直接对号入座:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 分离后进程消失 | 没在 screen 里跑,被 SIGHUP 杀掉 | 确认是在 screen 会话内启动,screen -ls看会话是否还在 |
screen -r报 Attached | 旧连接未释放 | 用screen -d -r 名字强制接管 |
| 日志文件不更新 | Python 输出缓冲 | 启动加-u,或设PYTHONUNBUFFERED=1 |
| 训练中途 OOM | batch size 过大 / 数据 shape 变化 | 降 batch、开梯度累积、盯首个 epoch |
| 报 No space left | 数据盘或系统盘满 | df -h定位,清理日志和旧 checkpoint |
| 进程莫名消失无报错 | 内存被系统 OOM killer 干掉 | 看dmesg,降 dataloader workers 或内存占用 |
| GPU 利用率长期很低 | 数据加载瓶颈 | 增大num_workers,检查磁盘 IO |
提示:排查问题时,先看日志尾部的报错栈,再
nvidia-smi看显存,最后df -h看磁盘。这三步能定位绝大多数"训练突然挂了"的问题。
5. 把训练任务管起来的一点工程化经验
跑通一个实验不难,难的是同时管好一批实验,还能在几周后复盘清楚。这部分是我做项目攒下来的一些习惯,谈不上标准,但确实省事。
5.1 多任务并行与命名规范
当你要对比超参数时,最少是五六个实验同时跑。我的命名约定是exp_<日期>_<关键参数>,比如exp_0512_lr3e4_bs64。会话名、日志文件名、checkpoint 目录名共用同一套后缀,做到"看名字就知道这组实验在测什么"。这样screen -ls一列出来,一眼能分清哪个是哪个,不用担心串台。
多任务并行时,最忌讳的是显存不够硬上。一块 24G 的卡,你同时跑三个各占 10G 的任务,第三个必然 OOM。所以开新任务前先nvidia-smi看余量,算清楚再开。理想状态是每个实验的显存占用留出 20% 余量,给数据加载和峰值波动留空间。
会话数量也别贪多。我一般同时最多三个 screen 会话在跑,超过这个数就容易顾此失彼,监控也看不过来。真正的效率不是同时跑得多,而是每个都能顺利收敛、有结论。
5.2 从 screen 到脚本化
用久了你会发现,每次开机、建会话、激活环境、跑命令,动作是重复的。这时候就该脚本化。我写了一个run_exp.sh,把环境激活、目录创建、日志重定向、随机种子设置都包进去,最后一句是启动训练。然后启动就变成了:
screen -S exp_0512_lr3e4_bs64 -dm bash run_exp.sh注意这里的-dm,它的意思是"创建会话并直接分离",不在前台打开。这样我一条命令就能拉起一个新实验,不用手动进去敲。脚本里我把所有路径参数化,换数据集、换模型只改几行配置,非常省事。
脚本化还有个隐性好处:可复现。半年后你回头看某个实验怎么跑的,脚本就是最忠实的记录,比脑子靠谱得多。日志、脚本、checkpoint 三件套放在同一个实验目录下,归档时整体打包,清清楚楚。
最后分享一个我自己一直在用的习惯:每天收工前,花两分钟做三件事——screen -ls确认所有会话状态,df -h看磁盘余量,nvidia-smi看有没有异常占用。这三分钟能挡掉八成"第二天回来发现训练挂了"的情况。踩过的坑够多了之后你会发现,真正让训练稳的,不是多高深的工具,而是这些细碎但坚持下来的动作。