问题背景#

在远程服务器上部署模型或运行长任务时,最让人焦虑的就是 SSH 断连导致命令被杀

  • 部署 vLLM 服务器,模型加载需要几分钟,突然网络抖动,进程直接中断
  • 跑 benchmark 测试,笔记本合上盖子,测试直接失败
  • 在咖啡厅或高铁上运维,网络不稳定,随时可能中断关键操作

这个问题的根源是:SSH 断连会发送 SIGHUP 信号,终端下的所有子进程都会被终止。

一个常见做法:用 tmux 在服务器上创建持久化会话。 即使你断开了 SSH,tmux 会话里的命令仍然在后台继续运行。

什么是 tmux?#

tmux(Terminal Multiplexer)是一个终端复用器,允许你在单个终端窗口中创建多个会话、窗口和窗格。它的核心价值在于:

  • 会话持久化:会话运行在服务器端,不依赖你的 SSH 连接
  • 网络韧性:WiFi 断线、VPN 断开、笔记本睡眠,都不会影响服务器上的进程
  • 多窗口/多窗格:可以在一个终端里同时运行命令、查看日志、监控资源

这与 Screen 类似,但 tmux 在今天的资料和使用案例里更常见一些。

一些外部实践参考#

tmux 在 ML/DevOps 场景里已经很常见。下面是一些我阅读过、也觉得有参考价值的材料:

Weights & Biases(W&B)官方教程#

W&B 在其 ML 从业者 tmux 教程(2022)里提到了一些做法:

  • 为每个实验创建命名会话tmux new -s <experiment_name>
  • 使用 Ctrl+B, d 安全分离,尽量避免直接关闭终端
  • 配置扩展的回滚缓冲区(history-limit 50000)保留训练日志
  • 在状态栏显示 GPU/服务器指标

tmux-trainsh:GPU 工作流自动化工具#

tmux-trainsh(2026)是一个专门为 GPU 和远程服务器设计的工作流运行器。它的核心理念是 “terminal-first”

  • 用 Python 编排远程任务,在 tmux 会话中执行
  • 支持在 Vast.ai、RunPod 等云 GPU 服务上远程部署
  • 自动管理 tmux 会话生命周期

DataMade 团队约定#

DataMade 工程团队使用以下约定:

  • 始终以特定用户运行 tmux(如 ubuntu
  • 会话命名格式:[用户名]-[进程描述]
  • 进程完成后立即清理会话,避免混乱

DevOps 侧的一些经验#

Mark Callen 在 Why Every DevOps Engineer Should Be Using tmux(2025)中总结:

  • 会话持久化是 DevOps 的"安全网"
  • 自动在 SSH 登录时启动/附加 tmux
  • 使用窗格分割实现命令+监控并排运行

基础用法#

1. 创建命名会话#

ssh admin@100.97.87.120
tmux new -s vllm-deploy

2. 在会话中运行命令#

# 在 tmux 会话内执行
docker run -d --name vllm-server --gpus all nvcr.io/nvidia/vllm:26.01-py3

3. 安全分离(不杀进程)#

Ctrl+B,然后按 d

这会分离会话,但命令仍在服务器后台运行。

4. 重新附加#

ssh admin@100.97.87.120
tmux attach -t vllm-deploy

5. 列出所有会话#

tmux list-sessions

6. 清理完成的会话#

tmux kill-session -t vllm-deploy

进阶:窗格分割用于实时监控#

tmux 允许你在同一个窗口中分割出多个窗格,非常适合一边跑命令一边监控:

# 在 tmux 会话内
Ctrl+B, %    # 垂直分割
Ctrl+B, "    # 水平分割

# 在不同窗格中运行不同命令
# 窗格 1: docker logs -f vllm-server
# 窗格 2: nvidia-smi -l 2
# 窗格 3: free -h -s 5

在本项目中的集成#

nv-dgx-spark 项目中,我已经将 tmux 集成到 Makefile 中,让远程部署更加可靠。

可用的 tmux 命令#

# 在 tmux 会话中部署 vLLM(不怕 SSH 断连)
make tmux-vllm-deploy

# 在 tmux 中运行任意命令
make tmux-cmd COMMAND="docker pull nvcr.io/nvidia/vllm:26.01-py3" SESSION="docker-pull"

# 查看服务器上的 tmux 会话列表
make tmux-list HOST=100.97.87.120

# 重新附加到某个会话
make tmux-attach HOST=100.97.87.120 SESSION=vllm-deploy

# 在 tmux 中跑 benchmark(长时间运行也安全)
make tmux-benchmark

# 清理完成的会话
make tmux-kill HOST=100.97.87.120 SESSION=vllm-benchmark

tmux 辅助脚本#

项目还提供了一个 tmux 管理器脚本:

# 创建会话
./scripts/tmux-manager.sh 100.97.87.120 new my-task "docker logs -f vllm-server"

# 附加到会话
./scripts/tmux-manager.sh 100.97.87.120 attach my-task

# 列出会话
./scripts/tmux-manager.sh 100.97.87.120 list

# 杀死会话
./scripts/tmux-manager.sh 100.97.87.120 kill my-task

tmux vs nohup vs systemd#

方案 持久化 交互性 日志管理 适用场景
tmux ✓(可重新附加) 手动(可用 tee 交互式部署、调试、benchmark
nohup 输出到文件 简单的一次性任务
systemd journalctl 生产服务长期运行

一种常见的分工

  • 部署/调试阶段:用 tmux,可视化、可交互、随时查看进度
  • 生产运行阶段:用 systemd service(项目已提供 config/vllm.service),自动重启、日志管理

实践小结#

结合上面的资料和我自己的使用场景,我现在更常用下面这套 tmux 工作流程:

1. 始终使用命名会话#

# ✓ 好:有意义的名称
tmux new -s vllm-deploy-qwen7b

# ✗ 避免:无意义的自动生成 ID
tmux new

2. 正确分离#

Ctrl+B, d    # ✓ 正确:安全分离
Ctrl+D       # ✗ 错误:关闭 shell,杀死会话

3. 及时清理完成的会话#

# 确认任务完成后
tmux kill-session -t vllm-deploy-qwen7b

4. SSH 登录时自动附加 tmux(可选)#

在远程服务器的 ~/.bashrc 中添加:

# Auto-attach to tmux on SSH login
if [[ -z "$TMUX" ]] && [[ -n "$SSH_CONNECTION" ]]; then
    tmux attach -t main || tmux new -s main
fi

这样每次 SSH 登录都会自动进入或创建 tmux 会话。

5. 配置扩展回滚缓冲区#

在远程服务器的 ~/.tmux.conf 中添加:

set -g history-limit 50000   # 保留 50000 行回滚
set -g mouse on              # 启用鼠标支持

真实场景示例#

场景 1:在高铁上部署 vLLM#

# 在 tmux 中启动部署
make tmux-vllm-deploy

# 安全分离
# (Ctrl+B, d 或在 Makefile 中已自动分离)

# 网络断了?没关系,部署仍在进行

# 网络恢复后,重新查看进度
make tmux-attach HOST=100.97.87.120 SESSION=vllm-deploy

场景 2:跑长时间 benchmark#

# 在 tmux 中启动(不会因为合上笔记本而中断)
make tmux-benchmark

# 查看结果
ssh admin@100.97.87.120 "tail -f /tmp/vllm-benchmark.log"

场景 3:多节点调试#

# 在节点 A 上监控日志
./scripts/tmux-manager.sh 100.97.87.120 new nodeA-watch "docker logs -f vllm-server"

# 在节点 B 上跑测试
./scripts/tmux-manager.sh 100.67.164.92 new nodeB-test "curl http://localhost:8000/health"

# 随时切换查看
./scripts/tmux-manager.sh 100.97.87.120 attach nodeA-watch
./scripts/tmux-manager.sh 100.67.164.92 attach nodeB-test

参考链接#