第三章:持久会话
SSH 陷阱
你 SSH 到服务器,启动了训练,合上笔记本回家。
训练死了。
你没有 kill 它,没有按 Ctrl+C,也没有显存不足。SSH 连接断开了——你在那个 SSH 会话里启动的所有进程都收到了 SIGHUP 信号,然后终止了。你那个 14 小时的训练,跑了 47 分钟就死了,原因是你合上了笔记本、WiFi 断了、或者 VPN 抽风了。
这就是 SSH 陷阱,每个研究者至少会踩一次。它是人们在实验室熬夜的原因——坐在终端前面,训练在跑,连笔记本都不敢碰。它是人们在第二块屏幕上保持一个 SSH 窗口死活不关的原因。它是人们在周五晚上离开前启动训练,然后在手机上强迫症般地检查,直到手机上的 SSH 会话也断了的原因。
问题是根本性的:SSH 把你的进程绑定在你的连接上。连接断了,进程就死了。这是设计如此——Unix 会话就是这样工作的。你的训练脚本是 SSH shell 的子进程。杀掉父进程,子进程也跟着死。
你需要一种方法,把进程和 SSH 连接解耦。你需要训练在服务器上独立运行,不管你有没有连着,不管笔记本开没开,不管手机有没有信号,不管你醒没醒。
这个工具叫 tmux。
Tmux 的工作原理
Tmux(终端复用器)用一个简单的架构技巧解决了 SSH 陷阱:它把服务端和客户端分开了。
当你在一台机器上启动 tmux 时,它会在后台创建一个服务端进程。在这个服务端里,你创建会话——每个会话就像一个虚拟终端。你实际的终端(通过 SSH 连上来的那个)只是一个客户端,用来显示会话里正在发生什么。
看起来是这样的:
你的 SSH 连接 服务器
┌──────────────┐ ┌──────────────────────────┐
│ │ SSH 隧道 │ │
│ 终端 │◄──────────────►│ tmux 服务端(始终运行) │
│ (客户端) │ │ │ │
│ │ │ ├─ 会话: train │
│ │ │ │ └─ python train.py│
│ │ │ │ │
│ │ │ └─ 会话: eval │
│ │ │ └─ python eval.py │
└──────────────┘ └──────────────────────────┘
当 SSH 断开时:
┌──────────────┐ ┌──────────────────────────┐
│ │ │ │
│ 终端 │ ✗ 断了 ✗ │ tmux 服务端(还在跑!) │
│ (死了) │ │ │ │
│ │ │ ├─ 会话: train │
└──────────────┘ │ │ └─ python train.py│
│ │ (继续运行!) │
│ └─ 会话: eval │
│ └─ python eval.py │
│ (继续运行!) │
└──────────────────────────┘当你的 SSH 连接断开时,客户端消失了——但 tmux 服务端继续运行。每个 tmux 会话里的每个进程都像什么都没发生一样继续执行。你的训练脚本不知道也不在乎你断开了。它跟 tmux 服务端通信,不跟你的 SSH 客户端通信。
当你重新 SSH 进来时,你只需要把一个新的客户端连接到同一个 tmux 服务端。你从离开的地方完全接上。终端输出还在那里。进程还在跑。什么都没丢。
这就是为什么 tmux 改变了一切。你的进程不再活在 SSH 连接里,它们活在 tmux 里。SSH 只是你用来查看它们的窗口。
核心命令
你只需要六个命令,不多不少。
创建命名会话
tmux new -s train这会创建一个叫 train 的新 tmux 会话,并自动连入。你会注意到终端底部多了一条绿色状态栏——这是 tmux 的状态栏,确认你已经在会话里面了。
永远给会话命名。不带 -s 的 tmux new 会创建名为 0、1、2 的会话。当你同时跑三个训练时,你得知道哪个是哪个。给它们起名:train、eval、download、experiment01——什么有意义就用什么。
脱离会话
Ctrl+b 然后 d这是一个两步按键序列。先按 Ctrl+b(按住 Ctrl,按 b,松开两个键),然后按 d。会话从你屏幕上消失了——但它还在服务器上运行。你回到了普通的 SSH 命令行。
脱离不是关闭。tmux 会话和里面的一切继续运行。这就是重点。
列出会话
tmux ls显示这台机器上所有运行中的 tmux 会话:
train: 1 windows (created Mon Mar 23 14:30:12 2026)
eval: 1 windows (created Mon Mar 23 15:45:03 2026)两个会话,都在跑。你可以看到每个的创建时间。
重新连入会话
tmux attach -t train你回到了 train 会话里。一切如你离开时一样。你不在时产生的终端输出都在——往上翻就行。
终止会话
tmux kill-session -t train这会真正停止会话并杀死里面的所有进程。当你用完一个会话时用这个——实验跑完了、下载完成了。别让僵尸会话堆在服务器上。
就这些
六个命令:
| 命令 | 功能 |
|---|---|
tmux new -s 名称 | 创建命名会话 |
Ctrl+b d | 脱离(会话继续运行) |
tmux ls | 列出所有会话 |
tmux attach -t 名称 | 重新连入会话 |
tmux kill-session -t 名称 | 终止会话及其进程 |
tmux 还能做的其他事情——分屏、面板、窗口、脚本——有用但不是现在必需的。之后可以慢慢学。这六个命令足以应对本指南的所有场景。
实测验证
证明它能用。这不是选做——你需要亲眼看到。
第 1 步:SSH 到服务器
ssh lab-server第 2 步:创建 tmux 会话
tmux new -s test你现在在一个叫 test 的 tmux 会话里。底部应该能看到绿色状态栏。
第 3 步:启动一个长时间运行的进程
python3 -c "import time; [print(i) or time.sleep(1) for i in range(999)]"你会看到数字一秒一个往上数:0、1、2、3……
至少看到数到 5。这就是你的"训练任务"。如果没有 tmux,你现在关掉 SSH 连接,这个进程立刻就死了。
第 4 步:脱离
按 Ctrl+b,然后按 d。
计数消失了。你回到了普通 SSH 命令行。会话还在跑——你只是看不到了。
第 5 步:断开连接
关闭 SSH 连接:
exit然后彻底关闭你的终端。如果你在手机上,关掉 Termius。如果你在笔记本上,关掉终端应用。SSH 连接没了,彻底断了。
第 6 步:等一等
等 30 秒。去倒杯水。看看手机。干点别的。重点是:你跟服务器之间已经没有任何连接了。你的"训练"(计数器)独自在那里。
第 7 步:重新连接
重新打开终端,SSH 回去:
ssh lab-server第 8 步:重新连入
tmux attach -t test第 9 步:见证奇迹
计数器还在跑。它没停过。如果你脱离时数到了 5,等了 30 秒,现在它已经到了 35。你断开的整个过程中它一直在数。
这就是 tmux 的核心承诺:你的进程不会因为断开连接而终止。计数器不知道你离开了,也不知道你回来了。它在 tmux 服务端里一直跑着,完全不在乎你的 SSH 连接状态。
用 Ctrl+C 停掉计数器,然后终止测试会话:
tmux kill-session -t test手机友好的 Tmux
你会通过 Termius 在手机上使用 tmux。小屏幕、没有物理键盘、触屏输入。做几个调整会好用很多。
开启鼠标模式
默认情况下,tmux 对鼠标事件的拦截方式可能会让手机终端感到困惑。开启鼠标模式可以让滑动滚屏和选择文本变得自然。在服务器上,创建或编辑 ~/.tmux.conf:
echo 'set -g mouse on' >> ~/.tmux.conf如果你已经有在运行的 tmux 会话,重新加载配置:
tmux source-file ~/.tmux.conf开启鼠标模式后,你可以在手机上通过滑动来滚屏浏览终端输出——比键盘操作方便多了。
无鼠标滚屏
如果鼠标滚屏在你的终端里不好使(有些手机客户端有兼容性问题),用 tmux 的复制模式:
- 按
Ctrl+b,然后按[—— 进入复制模式。 - 用方向键(或滑动,取决于你的终端)向上翻看输出。
- 按
q退出复制模式,回到正常状态。
复制模式是你在手机上查看报错信息和训练日志的方式。现在就练一练,别等到凌晨两点出事的时候手忙脚乱。
会话名称要短
在手机屏幕上列出会话时,长名字会被截断。保持简短且有意义:
| 好 | 不好 |
|---|---|
train | my-training-experiment-v2 |
exp01 | bert-finetune-lr3e5-bs32 |
dl | downloading-imagenet-dataset |
连入会话后你能看到完整上下文。名字只需要够你从 tmux ls 的列表里选对就行。
Tmux + 训练:每天的固定模式
这是你每天都会用到的模式。简单,好用。
1. SSH 到服务器
ssh lab-server2. 为实验创建命名会话
tmux new -s experiment013. 启动训练
cd /path/to/your/project
conda activate your-env
python train.py --config config.yaml训练在跑了。你能看到 loss 在降,epoch 在增。一切正常。
4. 脱离
按 Ctrl+b,然后按 d。
训练继续跑。你回到了 SSH 命令行。
5. 去过你的生活
关掉 SSH 连接。合上笔记本。回家。吃饭。睡觉。出去玩一个周末。都无所谓。你的训练在服务器的 tmux 里跑着,它不需要你。
6. 用手机随时查看
任何时候——在公交上、在床上、在餐厅——打开 Termius,SSH 进去,查看:
tmux attach -t experiment01你看到最新的训练输出。Loss 还在降。第 47 个 epoch,总共 100 个。一切正常。再脱离:
按 Ctrl+b,然后按 d。
手机操作总用时:15 秒。
7. 跑完了再回来
第二天早上,或者你回到工位时:
ssh lab-server
tmux attach -t experiment01训练跑完了。结果保存好了。你检查结果,清理一下,终止会话:
tmux kill-session -t experiment01整个流程就是这样。创建会话、启动训练、脱离、去过生活、偶尔看看、跑完回来。不用盯着,不用焦虑,不用在实验室熬夜。
这解锁了什么
有 tmux 之前,问题是:"训练在跑,我能离开实验室吗?"
有 tmux 之后,这个问题不存在了。你启动训练然后离开。方便的时候用手机看一眼。还在跑,好。跑完了,好。崩了——好吧,目前你得用手机 SSH 进去,用大拇指修,很痛苦。但那是后面几章要解决的问题。Claude Code 会处理崩溃。Tmux 只负责保证你的进程不会因为你不在就死掉。
从这里开始的每一章都默认 tmux 已经是你工作流的一部分。Claude Code 会在 tmux 里运行。训练会在 tmux 里运行。下载、评估、监控——全部都在 tmux 里。它是让一切成为可能的隐形基础。
检查点
在 tmux 里启动一个长时间运行的进程:
ssh lab-server
tmux new -s test
python3 -c "import time; [print(i) or time.sleep(1) for i in range(999)]"用 Ctrl+b d 脱离。彻底关掉 Termius。等整整一分钟。重新打开 Termius,SSH 回去,用 tmux attach -t test 重新连入。
如果计数器在你不在的时候一直在数——你已经解锁了持久会话。从今以后,你的训练再也不会因为断开连接而终止了。