服务器使用指南
一本简单的服务器使用指南,在建中。
服务器管理员请阅读管理员指南。
服务器用户请阅读用户指南。
自查清单
本页面给出了在使用服务器过程中建议遵循的最佳实践。这些最佳实践可以帮助你更好地使用服务器,也可以保证服务器的安全和可持续运行。
规则分为三个等级:
- 必须:必须遵守的规则,违反规则可能导致服务器故障、数据丢失等严重后果;
- 建议:建议遵守的规则,违反规则可能导致管理混乱等后果;
- 可以:可选遵守的规则,违反规则可能会影响使用体验。
必须使用个人 Linux 账户。
必须使用个人 Linux 账户,不得共用账户或使用 root(包括管理员账户,如 dell、admin 等)账户。
原因
不同用户的权限不同,共用账户会导致权限混乱,不利于追踪和管理。
由于每个账户仅有一个 HOME 目录,不同用户的文件和配置(例如 Shell、编辑器、tmux 终端等)会相互影响,造成使用体验不佳,也不方便管理员进行维护(例如备份、迁移用户数据等)。
root 账户拥有最高权限,使用 root 账户可导致误操作(例如不小心删除全盘),造成严重后果。
对策
向管理员请求创建个人账户。
如果要自己创建,可参考如何从公共账户迁移到个人账户。
必须使用 SSH 密钥登录。
原因
SSH 密钥登录比密码登录更安全(理论上无法被暴力破解),且更方便(每次登录无需输入密码)。
对策
必须使用安全的密码。
原因
即便使用了 SSH 密钥登录,使用弱密码(例如 123456、password、abc123 等)仍可能会导致账户的 sudoer 权限被他人获取,造成严重后果。
对策
使用强密码,密码长度不少于 8 位,且包含大小写字母、数字和特殊字符。此外,还需要妥善保管密码,不得将密码泄露给他人。
必须禁止直接编辑无权限编辑的文件。
禁止使用 sudo 命令编辑无权限编辑的文件。
例如(下面是错误示例):
$ sudo vim /etc/sudoers
$ sudo nano /etc/hosts
原因
一些文件(例如 /etc/ 目录下的文件)仅 root 用户有权限编辑,或者需要特殊的权限配置,直接编辑可能会破坏文件的权限设置,造成无法预料的后果。
对策
在终端配置文件(如 .bashrc、.zshrc)中设置默认编辑器,如:
export EDITOR=nano
然后依据以下原则修改:
- 通常建议不碰无权限的文件,除非你非常清楚自己在做什么!有问题建议优先联系管理员;
- 对于
/etc/sudoers文件,使用sudo visudo命令编辑;
- 对于其他文件,使用
sudoedit命令编辑:$ sudoedit /etc/hosts
建议不使用 root 权限执行命令。
原因
root 权限拥有最高权限,使用 root 权限可导致误操作,造成严重后果。
另外,需要 root 权限才能修改的文件通常是全局生效的(例如 /etc/ 目录下的文件),贸然修改可能会影响其他用户的使用。
这类问题在配置镜像源时尤其常见,也是弄乱很多服务器的主要原因之一。
对策
尽可能不使用和 root 权限相关的命令,例如 sudo。如果一定要使用,务必确认自己知道自己在做什么。
严禁从网上复制粘贴不了解的、需要 sudo 才能执行的命令,这些命令可能会造成严重后果!
严禁私自用以下命令之一改变服务器的电源状态:
shutdownrebootpoweroffhaltinitsystemctl poweroffsystemctl rebootsystemctl haltsystemctl suspendsystemctl hibernatesystemctl hybrid-sleep
有的教程习惯使用 $ sudo kill,容易给人造成「kill 必须使用 sudo」的错觉,实际上 kill 自己的进程,不需要 sudo。仅当你确定进程无法被普通用户权限杀死时,才需要使用 $ sudo kill。
有关配置镜像源等问题,可以让管理员协助。
建议使用 conda 管理 Python 环境。
原因
conda 是一个开源的软件包管理系统和环境管理系统,可以用于安装 Python 环境和第三方库。
系统的 Python 解释器已经被锁定在固定的旧版本,仅作为一些系统工具和服务的正常运行所必需的运行库而存在,不适合实验和开发。使用 conda 可以方便地安装自己需要的 Python 版本。
对策
在进行实验时,使用 conda 创建虚拟环境。使用方法可参考 Conda Cheat Sheet。
建议在 conda 中仅使用一种包安装机制。
原因
在 conda 环境中,用 conda install 和 pip install 安装包的底层机制完全不同,混用可能导致依赖关系错误,安装的包不能正常使用。
对策
在 conda 环境中,仅使用 conda install 或 pip install 安装包。由于 pip 支持的 Python 包通常是 conda 的超集,建议优先使用 pip 安装包。
推荐用一个 requirements.txt 文件记录所有需要安装的包,然后用 pip install -r requirements.txt 一次性安装所有包。
建议在实验结束后检查残留进程。
原因
一些进程在实验结束后可能仍在运行,例如 Jupyter Notebook 等。这些进程会占用系统资源(例如 GPU、内存、端口、硬盘文件等),造成大量资源浪费。
对策
每次运行以下程序后,留意是否有残留进程,如果有,手动杀死。
- VSCode 中打开 ipynb 文件时,会自动启动 ipykernel 进程,关闭 VSCode 后,ipykernel 进程有时仍会继续运行。
- VSCode 非正常断开(例如在笔记本上写代码后直接合盖休眠)后,vscode-server 进程有时仍会继续运行。
- Jetbrains Gateway 连接到服务器后,即使关闭了客户端,后端进程(例如 pycharm)有时仍会继续运行。
- 后台运行程序(即使用
&)时,脚本运行结束后,该后台进程可能仍会继续运行。
如果不能注意,至少做到在每次实验结束后,完全退出编辑器再关机。
可以使用 tmux 管理长时间运行实验的会话。
「会话」是 Linux 中的一个概念,可以理解为一个终端窗口。例如你在服务器上打开了两个可以输入命令的终端窗口,那么这两个窗口就是两个会话。
原因
tmux 是一个终端复用工具,可以在同一个终端窗口中创建多个会话,这些会话在用户断开后仍能继续运行,并且可以在多个终端窗口中连接到同一个会话。
对策
参考 Tmux Cheat Sheet & Quick Reference 学习 tmux 的基本用法,也可以阅读下面的极简指南。
类似的工具还包括 GNU Screen。
可以配置 tmux 为支持鼠标操作,这样就可以通过鼠标点击来切换窗口、滚动屏幕等:
echo "set -g mouse on" >> ~/.tmux.conf
配置后需要重新连接会话才能生效。
开启会话:
$ tmux new -s my-session-name
断开连接:
Ctrl + b,然后按 d(意为 detach)。
重新连接:
$ tmux a -t my-session-name
新建窗口:
Ctrl + b,然后按 c(意为 create)。
切换窗口:
直接用鼠标点击下面的 Tab 栏即可。
关闭整个会话和所有窗口:
Ctrl + b,然后按 &,再输入 y 回车确认。
如何使用 SSH 密钥登录
1. 检查是否已有 SSH 密钥
在你的(注意,不是在服务器上找,而是在自己的电脑上)HOME 目录下寻找 .ssh 目录,如果其中包含除了 known_hosts、config 和 authorized_keys 之外的一对名为 <名称> 和 <名称>.pub 的文件,例如 id_rsa 和 id_rsa.pub,则说明已有 SSH 密钥,可跳过下一步。
2. 生成 SSH 密钥对
在本地终端中执行以下命令:
$ ssh-keygen
程序会交互式地要求输入文件名、密码等信息,直接按回车键即可使用默认值。可能的输出如下:
[root@host ~]$ ssh-keygen <== 建立密钥对
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): <== 按 Enter
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase): <== 输入密钥锁码,或直接按 Enter 留空
Enter same passphrase again: <== 再输入一遍密钥锁码
Your identification has been saved in /root/.ssh/id_rsa. <== 私钥
Your public key has been saved in /root/.ssh/id_rsa.pub. <== 公钥
The key fingerprint is:
0f:d3:e7:1a:1c:bd:5c:03:f1:19:f1:22:df:9b:cc:08 root@host
3. 将公钥上传到服务器
直接打开公钥文件(即以 .pub 结尾的文件)并复制其中的内容,内容一般形为:
ssh-rsa AAAAB3NzaC1yc2EA... myname@mypc
然后在服务器上执行以下命令:
$ echo "<刚刚复制的公钥内容>" >> ~/.ssh/authorized_keys
为了确保连接成功,请在服务器上保证以下文件权限正确:
$ chmod 600 ~/.ssh/authorized_keys
$ chmod 700 ~/.ssh
4. 结束
完成,你现在可以尝试在本地使用 SSH 密钥登录服务器了:
$ ssh <用户名>@<服务器地址>
代理配置指南
由于众所周知的原因,许多服务(例如 Hugging Face Hub)在国内无法直接访问。为了解决这个问题,我们提供了一个简明的代理配置指南。
1. 启动代理服务
推荐使用 Clash 管理代理。Clash 是一个开源的代理软件,支持多种代理协议,例如 SOCKS5、HTTP、Shadowsocks、VMess 等。
目前,Clash 的作者已经停止维护 Clash,仓库亦已经删除,上面的链接无法访问。如果需要继续使用,可使用社区分支的 Mihomo。
许多代理提供者也提供了 clash 配置文件(或 URL),便于直接导入使用。下面假定你已经有了一个可用的 clash 配置文件(通常是一个 .yaml 文件)。
获取配置文件
首先需要将文件导入到服务器上,下面分三种情况:
a. 从 URL 导入
如果你的配置文件是一个 URL,可以在服务器上直接使用 wget 下载:
$ wget https://example.com/config.yaml
b. 从本地导入,使用 scp
如果你的配置文件在本地,可以在本地使用 scp 命令将其上传到服务器上:
$ scp /path/to/config.yaml <用户名>@<主机>:/path/to/config.yaml
其中 <用户名> 和 <主机> 分别是你的服务器的用户名和主机名,例如 root@123.456.789.0。
c. 从本地导入,使用 Visual Studio Code
如果你使用的是 Visual Studio Code 连接到服务器,也可以在 VSCode 的资源管理器中直接将文件从本地复制粘贴到服务器上。
检阅配置文件
在导入配置文件之后,可以使用 cat 命令检阅一下配置文件的内容:
$ cat /path/to/config.yaml
需要注意的是前几行的内容,尤其是端口号,通常形如:
mixed-port: 7890
external-controller: 0.0.0.0:9090
通常的默认值如上,但如果你在后续启动服务时发现报错「端口冲突」,应该同时将两者的端口号都改为其他值,直到不再报错为止。
启动服务
由于服务需要持续运行,建议用一个 tmux 会话来运行以下命令。
tmux 的使用方法见这里。
启动服务非常简单,只需要在服务器上执行:
$ clash -f /path/to/config.yaml
其中 /path/to/config.yaml 是你的 clash 配置文件的实际路径,推荐将其放置在 HOME 目录下,例如 ~/.config/clash/config.yaml。
如果报错,请回到上一步检查配置文件的内容。
2. 配置代理
启动 clash 代理后,需要配置应用程序或 Python 代码使用代理。
目前大部分 Linux 网络请求库、框架和应用程序都遵循同一惯例,即:读取 http_proxy 和 https_proxy 环境变量的值来作为代理,Python 也不例外。因此,只需要在启动应用程序之前设置这两个环境变量即可。
有很多种方法配置环境变量,这里我们只介绍三种:
a. 临时设置
在启动应用程序之前,可以在命令行中设置环境变量:
$ export http_proxy=http://127.0.0.1:7890
$ export https_proxy=http://127.0.0.1:7890
# 接下来的命令都会使用代理
$ python my_lab.py
这样设置仅会在当前 Shell 会话中接下来的命令生效,新开窗口或者重启后都会失效。
或者可以在启动应用程序的命令前加上环境变量:
$ https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 python my_lab.py
这样设置仅对本条命令生效。
b. 永久设置
如果你希望永久设置这两个环境变量,可以将其写入你的 Shell 配置文件中。
在文件尾部添加:
# >>> Proxy settings >>>
export http_proxy=http://127.0.0.1:7890
export https_proxy=$http_proxy
# <<< Proxy settings <<<
然后重启 Shell。
一些远古和错误的教程会建议将这些环境变量写入下面的位置之一,对于普通用户来说,这些全部是不正确的配置方法:
~/.bash_profile~/.profile/etc/environment/etc/profile/etc/profile.d/xxx.sh
c. 在 Python 方面设置
也可以仅在特定的 Python 代码中设置环境变量,这样不会影响其他程序。
# 在文件/笔记本的最开头执行:
import os
os.environ['http_proxy'] = 'http://127.0.0.1:7890'
os.environ['https_proxy'] = os.environ['http_proxy']
# ...余下的其他 import...
如何安装不同版本的 CUDA
CUDA 是 NVIDIA 公司推出的并行计算平台和编程模型,用于利用 NVIDIA GPU 的并行计算能力。
NVIDIA 公司混乱的术语表达,使人常常对 CUDA 的概念辨析和安装产生困惑。本文将介绍如何在 Linux 上正确安装不同版本的 CUDA。
1. CUDA 的相关术语辨析
本节仅仅做术语和概念的介绍,不感兴趣可以直接跳到下一节。
太长不看:
- CUDA 和驱动是不同的,根本不存在「CUDA 驱动」这个糅合的概念。
- CUDA 就是普通的库,更换版本不需要重新安装驱动。
- 但不同版本的驱动支持的 CUDA 版本范围不同。
nvidia-smi命令里显示的是「驱动最高支持的 CUDA 版本」,不是「当前的 CUDA 版本」。因为同一个电脑上可以安装零到多个版本的 CUDA,所以「当前的 CUDA 版本」这样的概念根本不存在。
我们常说的「CUDA」,有三个主要组成部分:
- NVIDIA Graphics Driver: NVIDIA 显卡驱动程序,用于内核和 GPU 之间的通信,是下面所有模块的基础。它由两部分组成:一些内核模块(主要是
nvidia.ko)和一些用户空间库(主要是libnvidia-xxx.so)。版本号目前是560.63。 - CUDA Runtime: CUDA 运行时库,用于将 CUDA 函数调用转换为 GPU 指令。它是一些用户空间库(主要是
libcudart.so)。版本号目前是12.6。 - CUDA Toolkit: CUDA 工具包,用于开发、编译和测试 CUDA 程序。它包含了 CUDA Runtime 和一些开发工具,如
nvcc编译器。版本号目前是12.6。
可以看出,在一台使用 NVIDIA 显卡的机器上,NVIDIA Graphics Driver 是必须的,而 CUDA Runtime 和 CUDA Toolkit 是可选的。实际上,用 pip 或 conda 安装 PyTorch 时,都会自动安装 CUDA Runtime。因此,只要安装了 NVIDIA Graphics Driver,就可以在机器上直接安装 PyTorch 等深度学习框架。
另一方面可以看出,「CUDA 驱动」的说法是非常不严谨的。上面三个部分中,只有 NVIDIA Graphics Driver 才是真正的「驱动」,后两个都是普通的用户空间库文件。
然而,NVIDIA 在发布驱动时,会笼统地称之为「CUDA 驱动」,把三个部分全部捆绑在一个 .run 安装包中。这就导致了很多人产生了错误的观念,以为想要改变 CUDA 的版本,就必须重新安装驱动。实际上,CUDA 版本和驱动版本是相互独立的,根本不是同一个东西。
一定要说两者间有关系的话,那就是不同版本的驱动支持的 CUDA 版本范围不同。例如,460 驱动支持最高 11.2 版本的 CUDA,470 驱动支持最高 11.4 版本的 CUDA(数字是胡诌的,请勿当真)。这个版本号对应关系可以在这里查到,也可以通过 nvidia-smi 命令查看。
2. 安装另一个版本的 CUDA
由于 CUDA Toolkit 包含了 CUDA Runtime,所以我们只需要安装 CUDA Toolkit 即可。下面以安装 CUDA 12.1 旧版本为例。
-
目前,只有 conda 提供了方便的安装方式,可以在自己的环境中通过下面的命令安装:
(my_env) $ conda install conda-forge::cuda-toolkit=12.1
anaconda 中目前有三个主要的源提供了 CUDA Toolkit:main、nvidia 和 conda-forge。三者之间的区别是:
- main:Anaconda 公司维护的源,其 CUDA Toolkit 版本更新非常不及时(实际上,main 源里绝大部分包都是非常老的版本,因此我们根本不推荐使用 main 源)。
- nvidia:NVIDIA 公司维护的源,其 CUDA Toolkit 版本更新较为及时,可以使用。
- conda-forge:社区维护的源,其 CUDA Toolkit 版本更新最及时,常常比 nvidia 源还要快。
因此推荐使用 conda-forge 源。
-
安装完成后,可以通过下面的命令查看安装的 CUDA 版本:
(my_env) $ nvcc --version如果看到输出中包含
release 12.1,则说明安装成功。否则,可能是因为 conda 的环境变量没有设置正确,可以尝试重启 Shell 或者手动检查环境变量:(my_env) $ echo $CONDA_PREFIX # 检查 conda 的根目录 (my_env) $ echo $PATH # 检查 PATH 是否包含了 conda 的 bin 目录如果 PATH 没有包含 conda 的 bin 目录,或者在 bin 目录前面还有别的有关 cuda 的路径,这意味着 PATH 被覆盖了。通常是因为在 Shell 配置文件中写了类似下面的代码:
export PATH=/usr/local/cuda/bin:$PATH这通常不是你想要的,不管你是从什么地方复制的这行糟糕的配置,都应该删除它。
-
如果你只需要运行该版本的
nvcc(也就是只需要编译器的功能),到这里已经全部结束了。但是,如果你还希望程序能够优先使用这个版本的 CUDA Runtime,可以通过下面的命令设置环境变量:(my_env) $ export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH这样,当你运行的程序需要 CUDA Runtime 时,会优先使用这个 12.1。
conda 目前的默认行为是会设置 PATH,但不会设置 LD_LIBRARY_PATH(可以用 $ conda info --system 查看):set LD_LIBRARY_PATH when activate。
因此,如果你不设置 LD_LIBRARY_PATH,那么你的程序在运行时就可能找不到正确的 CUDA Runtime 库文件。所以这里推荐手动设置 LD_LIBRARY_PATH。
当然,export 永远是临时设置环境变量的方法,如果你希望永久设置,推荐的方法有两个:
$ conda env config vars set LD_LIBRARY_PATH=$CONDA_PREFIX/lib:这样会将LD_LIBRARY_PATH写入到当前环境的配置文件中。每次激活这个环境时,都会自动设置这个环境变量。优点是不会总是设置这个环境变量,缺点是设置时会覆盖原有的LD_LIBRARY_PATH。- 在 Shell 配置文件中写入
export LD_LIBRARY_PATH=<你 $CONDA_PREFIX 的值>/lib:$LD_LIBRARY_PATH:这样会将LD_LIBRARY_PATH写入到 Shell 配置文件中。每次打开 Shell 时,都会自动设置这个环境变量。优缺点和上面的方法相反。
管理 Python 环境的最佳实践
1. 为什么 Python 项目环境需要被管理?
我们在平时的工作中,经常会遇到这样的问题:
- 项目 A 需要 Python 3.6,项目 B 需要 Python 3.7,项目 C 需要 Python 3.8。多个项目之间的 Python 版本不一致,导致我们需要频繁地切换 Python 版本;
- 项目 A 需要使用 PyTorch 1.6,项目 B 需要使用 PyTorch 2.5。同一个包不能同时安装多个版本;
- 需要将项目 A 的环境迁移到另一台机器上或者分享到 GitHub 仓库中,以便他人能够快速地复现项目环境和实验结果。
为了实现这些目标,我们需要管理 Python 项目环境。项目环境管理的本质就是记录如何重现整个环境的配置信息,包括 Python 版本、依赖包、系统环境等。
传统上很多人喜欢直接用 requirements.txt 来记录依赖包,pip 来安装依赖包。但是这种方式有很多问题,相当过时。现在有很多更好的工具可以帮助我们管理 Python 项目环境,下面是笔者摸索出的一套最佳实践,与读者们分享。
2. 更好的非 Python 包环境管理:Miniforge 和 environment.yml
一提到 Python 本身的环境管理,大部分人的第一反应都是 Anaconda,然而却没有明确过 Anaconda 为什么好。
Anaconda 之所以好,是因为它提供了一个非常好的环境管理工具 conda,可以方便地管理 Python 环境和非 Python 包环境。然而 Anaconda 并不等于 conda,conda 是一个独立的环境管理工具,可以单独安装。
Miniforge 是一个轻量级的 conda 发行版,使用 conda 和 mamba 作为包管理器。它的环境和 conda(以及 Anaconda)完全兼容,但是更轻量、更快。
- ⚡ 运行更快:mamba 用 C++ 编写,而 conda 用 Python 编写。启动速度上 mamba 几乎没有延迟,而 conda 总有 1-2 秒的延迟。
- ⚡ 解析依赖更快:mamba 自行编写的解析器非常快,不会像 conda 一样卡在
Solving environment半小时。甚至 conda 都要向 mamba 取经,在 Anaconda 22.11 版本中引入了 mamba 的解析器。 - ⚡ 下载包更快:mamba 支持多线程下载。尽管 conda 也支持多线程下载,但是得益于 C++ 良好的多线程,mamba 下载速度实际上更快。
- 🤝 完全兼容 conda:mamba 的命令、参数和 conda 基本一致,只需要将
conda install xxx替换为mamba install xxx即可开箱即用。
- ⚡ 使用 mamba:Miniforge 同时使用 mamba 和 conda 作为包管理器。在享受速度更快的 mamba 的同时,即使有的命令 mamba 不支持,也可以随时求助于 conda。
- 🪶 更轻量:Miniforge 只包含
conda和mamba,不包含其他无用的包。Anaconda 包含了几 GB 的包,而 Miniforge 只有几十 MB。 - 🌐 统一社区源:相比于 Anaconda 将数个维护质量良莠不齐、速度忽快忽慢、甚至可能相互冲突的企业源放在一起,Miniforge 从安装开始就只使用 conda-forge 社区源,彻底杜绝 “同一个包出现在多个源中、conda 随机选择一个源中的版本” 的问题。
Miniforge 由 Conda Forge 社区维护,直接从 Miniforge 官网 安装即可。过程和安装 Anaconda 类似,不再赘述。
至于有人喜欢 Miniconda,我只能说:你都知道 Miniconda 了,为什么不用 Miniforge?
接下来就是环境的导出了。你可以使用 conda 自己的 environment.yml 文件来分享环境:
# 导出环境
conda env export > environment.yml
# 导入环境
conda env create -f environment.yml
非常简单轻松,不再赘述。
最后需要注意的是,本文一直在强调非 Python 包环境,因为 conda 们并不擅长管理 Python 包,笔者也非常不推荐使用 conda 来安装、管理任何 Python 包。如果你看到某个包提供了 conda install xxx 的安装方式,请不要使用。
一般来说,你的 conda 环境里只需要 python、cuda-toolkit、gcc 等非 Python 包,Python 包的管理交给下面的工具。
3. 闪电般的 Python 包环境管理:uv 和 pyproject.toml
Python 的包管理一直是一个老大难问题。老在于 Python 自出现以来,包括 pip、pipenv、poetry 等工具可以说是层出不穷,前赴后继地去解决这个问题;难在于 Python 将自己的包依赖做得过于灵活,以至于每个工具都有一些考虑不到的 Caveat。
关于环境的描述文件也是逐代更迭,目前的版本是 pyproject.toml,出自 PEP 518。而 requirements.txt 作为 pip 自己定义的 “非官方” 格式,也仍在广泛应用中,甚至在很多 Python 初学者和项目维护者中仍然是首选。
支持 pyproject.toml 的工具有 poetry、pdm、uv 等。其中 poetry 和 pdm 都是比较完善的工具,但是笔者认为它们都有一些问题,不够优雅。而 uv 作为一个新兴包管理器,解决了这些问题,是目前使用的首选。
- ⚡ 运行更快:uv 用 Rust 编写,其启动速度只能用 “瞬间” 来形容。而 poetry 和 pdm 用 Python 编写,运行命令时总要卡数秒钟才会开始执行。
- ⚡ 解析依赖更快、下载更可靠:uv 的解析机制非常快,并发进行、有缓存且对弱网络友好。以我自己的项目为例,uv 解析依赖只需要不到半分钟,而 pdm 则花了将近 2 小时在反复下载同一个 URL 包(flash-attn)上,反复报错和失败;至于 poetry,笔者为了写这篇文章而进行的测试中,已经过去了 7 小时多,它仍卡在没有任何输出的
Locking dependencies阶段。 - ❤️ 进度友好:pdm 和 poetry(以及 conda)出于某些完全反人体工学、反人类的考虑,几乎隐藏了所有耗时长的过程的进度信息,让人无法知道当前进度和状态,只能干等着急。而 uv 会在终端输出所有下载和解析过程的详细进度信息,让人一目了然。
- 🤝 兼容性强:uv 本身支持直接作为 pip 的平替,只需将
pip install xxx换成uv pip install xxx即可。另一方面,它也支持项目模式,即直接接管整个 conda 环境内的包管理。poetry 和 pdm 都只支持接管整个环境。
- ⚡ 运行更更快:pip 也是用 Python 编写的,所以……你懂的。
- 💪 解析依赖更可靠:pip 只会象征性地考虑现在已安装包的版本依赖关系,在出现包依赖破损时简单提示一些 warnings,然而实际上这些 warnings 代表整个项目环境的依赖不完备。这样的消息不仅容易被新手忽略(直到他们发出 “我的 Python 环境又被我搞烂了” 的哀叹),而且对修复依赖关系没有任何帮助。相对地,uv 会严格地校验整个环境中所有包的依赖关系,保证不多装、不少装、不错装。
- 🗑️ 支持 “完整” 卸载包:pip 秉持 “只管装、不管卸” 的哲学,其卸载功能基本是半残废。例如安装了 A 包和它的 100 个依赖包,那么
pip uninstall A实际上只会卸载 A,而完全不管这 100 个包,最终导致环境被各种无用的包填满,并出现各种依赖冲突问题。uv 则会严格检查卸载后哪些包不再需要,并及时卸载它们; - 🆙 支持升级包:由于 pip 功能孱弱的包依赖解析器,其至今不支持安全地升级环境内的包。唯一保证升级成功的办法就是删掉整个环境,用
requirements.txt或pip install手动重新安装所有包。uv 中则可以简单地使用uv lock --upgrade来获取所有包的更新,同时不 “搞烂” 任何依赖关系。
安装 uv 的推荐做法是在基础环境(Miniforge 的 base 环境或系统环境)中安装 pipx,然后使用 pipx 安装 uv:
# 安装 pipx。注意:不要在项目环境中执行这个命令!务必 mamba deactivate 退出当前环境再执行
pip install pipx
# 配置 pipx 到 PATH
pipx ensurepath
# 安装 uv
pipx install uv
对于还没有 pyproject.toml 的项目,可以使用 uv init 来初始化一个项目。之后就可以用以下命令来安装、卸载、升级包:
# 安装包
uv add xxx
# 卸载包
uv remove xxx
# 升级包
uv lock --upgrade
# 将当前环境和 pyproject.toml 中的包同步
uv sync
此外还有一些别的命令,以及关于 pip 中常见的附加 index(如 pip install xxx -i https://xxx.com)参数等功能,请在 uv 的文档中查看对应的做法。
4. 如何分享 Python 项目环境?
当你需要和他人分享你的 Python 项目环境时,只需要将 environment.yml 和 pyproject.toml(有必要的话,还有 uv.lock)一起分享。他人只需要安装 Miniforge 和 uv,然后执行以下命令:
# 导入环境
conda env create -f environment.yml
# 安装项目 Python 依赖
uv sync
这样就可以快速地复现你的项目环境了。
如何从公共账户迁移到个人账户
如果你已经在使用公共账户了,你可能希望在创建新账户后能继续原来的工作流,这里是一份简明(但不完全的)教程。
开始之前,可确定当前账户拥有以 root 身份执行命令的权限:
$ sudo echo hello
如果能够正常执行,应该会提示输入密码(如果刚刚输入过,则不会提示)。若输出 hello,则说明当前账户拥有以 root 身份执行命令的权限。
本文中,# 开头的命令必须以 root 身份执行(即:用 sudo 运行),而 $ 开头的命令则必须不以 root 身份执行。
<> 中的内容需要被替换为实际内容,而不是按照尖括号原样输入,例如 ls <你的 HOME 目录> 可能替换成 ls /home/myname。
1. 创建新账户
用以下命令创建一个新账户:
# useradd --home-dir <新账户的 HOME 目录位置,如 /data/myname 或 /home/myname> \
--comment "<任意备注文字,例如你的真实姓名全称>" \
--create-home \
--shell <你偏好的默认 Shell,例如 /bin/bash 或 /bin/zsh> \
<账户名>
请不要在 --home-dir 处填入已有的目录,例如你原来的实验代码目录。这样做可能会导致权限错误。
如果一定要用已有的路径,可先将原来的路径重命名(假设原来的代码目录为 /data/myname):
# mv /data/myname /data/myname.bak
2. 设置密码
用以下命令设置新账户的密码。创建密码时请遵循基本安全原则(例如不要使用 123456、abcdefg 等简单密码)。
# passwd <账户名>
3. 设置用户组
如果你不需要在新账户中使用 root 权限,可跳过本步骤。
如果你需要在新账户中使用 root 权限,可用以下命令将新账户加入具有 sudo 权限的相关用户组:
# usermod --append --groups <用户组名> <账户名>
尽管以下这些名称可能在网上的不靠谱教程中很常见,但盲目地应用它们都是错误的,因为在不同发行版和服务器上用户组的配置很有可能完全不同:
sudowheeladminadmroot- ...
正确的做法是,先查看 /etc/sudoers 文件中的内容:
# cat /etc/sudoers
其中可能包含这样的行:
%abc ALL=(ALL) ALL
或者:
%abc ALL=(ALL:ALL) ALL
它们的意思是:组 abc 的成员,可以在任何位置(ALL=)以任何用户身份((ALL))执行任何命令(ALL)。
那么组 abc 就是你要找的一个「具有 sudo 权限的用户组」。
如果这样的组有多个,优先考虑听起来名称更加合理的组,例如 wheel、sudo 等。
4. 停止原程序
如果你此前在公共账户中运行了程序,请自行检查并停止这些程序。
容易忽略的点包括:
tmux或screen中运行的程序nohup或&后台运行的程序vscode-server进程
5. 切换到新账户
用以下命令切换到新账户:
如果你想进入新账户,但停在当前目录中:
$ su <账户名>
如果你想进入新账户,并切换到新账户的 HOME 目录:
$ su - <账户名>
6. 迁移数据
Conda 支持
如果你此前使用 conda 管理环境,可用以下命令在新账户中初始化 conda 支持:
$ conda init
如果报错找不到 conda,说明此前的 conda 安装不在新账户的 PATH 中,需要寻找原先的 conda 程序的位置。
输入 exit 回到原先的账户下,执行:
$ which conda
输出的路径就是 conda 程序的位置。再次到新账户中,用以下命令替代上面的 $ conda init:
$ <conda 程序的位置> init
用 $ exit 退出新账户,然后重新进入新账户,输入 $ conda info 确认 conda 是否正常工作。
Conda 环境迁移
如果你此前已经有环境(如果没有,可跳过本步骤),可能在使用时发现出现了一大堆权限问题:
(my_env) myname@server:~$ pip install test
Defaulting to user installation because normal site-packages is not writeable
...
这是因为原先的安装在公共账户下,而新账户没有权限修改。解决方法是将原先的环境迁移到新账户下。
遗憾的是,conda 并没有一个适用于所有情况的迁移环境方法,因此你不得不从下面几个迁移方法中做出选择:
更多方法可参考 Moving Conda Environments。
a. requirements.txt 文件
如果你有在原环境中维护一个 requirements.txt 文件的良好习惯,可用以下命令创建新环境:
$ conda create -n <新环境名> python=<原环境中的 Python 版本>
$ conda activate <新环境名>
$ pip install -r <原环境中的 requirements.txt 位置>
b. environment.yml 文件
切换到原来的 conda 环境,用以下命令导出环境:
$ conda env export > environment.yml
然后用以下命令创建新环境:
$ conda create -n <新环境名> -f environment.yml
c. --clone 参数
用以下命令创建新环境:
$ conda create -n <新环境名> --clone <原环境名>
迁移完成后,用 $ exit 回到原公共账户,用以下命令删除原环境:
$ conda env remove -n <原环境名>
代码和数据迁移
如果你的代码和数据都在公共账户的 HOME 目录下,可用以下命令在 原公共账户 下将它们迁移到新账户的 HOME 目录下:
$ cp -r <原来的代码位置> <新账户的 HOME 目录位置>
$ rm -rf <原来的代码位置>
如果你的代码和数据不在公共账户的 HOME 目录下(例如,在 /data 目录下),更简单的方法是更换原来的代码和数据的所有者:
# chown -R <新账户名>:<新账户名> <原来的代码位置>
有时你的数据集可能缓存在 $HOME/.cache 目录下(例如,用 Huggingface Hub 下载的代码)。这部分的迁移比较复杂,友情建议是重新下载数据集(也就是什么都不管,直接在新账户下跑原来的代码。由于 $HOME 已经发生了变化,所以程序理应会找不到原来的缓存,继而重新下载数据集)。
6. 配置 SSH 登录
你应该在新账户中使用 SSH 登录。参考如何使用 SSH 密钥登录。
恭喜完成!你现在可以用新账户登录服务器了。
Slurm 极简手册
本文是面向普通用户的 Slurm 速查手册,主要介绍 Slurm 的常用命令。
我正在开发一个 Slurm 的竞品调度系统,TurboSched,目前仍在原型阶段。欢迎关注!
什么是 Slurm
Slurm 是一个开源的作业调度系统,用于管理集群中的计算资源,实现作业的提交、调度、执行和管理。它可以解决以下场景中的问题:
- GPU「僧多粥少」,如何保证公平分配?
- 计算资源已占满时,新的任务如何在有空闲资源时立即执行?
- 如何了解其他用户的排队作业情况,避免资源冲突?
- 如何统计用户的作业信息,方便后续的资源分配和管理?
提交任务
- Slurm 不会真正追踪 GPU 的使用状态:它认为的「GPU 空闲」是指它之前没有把这张卡分配给其他任务。例如,你直接在 Slurm 外运行了一个使用 GPU 0 的任务,哪怕 GPU 使用率 100%,Slurm 仍然会认为 GPU 0 是空闲的,可以分配给其他任务。只有你使用 Slurm 调度任务,然后 Slurm 把这张卡分配给了你的任务,它才会认为这张卡是「被占用」的;
- Slurm 不会强制让你的程序只能使用指定的 GPU:Slurm「分配」给你的方法是在运行开始时设置环境变量
CUDA_VISIBLE_DEVICES,除此之外,它什么都没有做:你的程序可以看到所有 GPU,也可以通过重写环境变量的方式使用其他 GPU。
因此,如果你想遵守 Slurm 的游戏规则,让你的程序只使用 Slurm 分配给你的 GPU,需要做到:
- 你的程序中不可以显式地修改
CUDA_VISIBLE_DEVICES环境变量; - 设置了
CUDA_VISIBLE_DEVICES时,GPU 枚举顺序会被重新编号(例如CUDA_VISIBLE_DEVICES=3,7时,cuda:1指的是 7 号 GPU),所以你的程序中只可以使用cuda、cuda:0、cuda:1等这样的从 0 开始的编号。
a. 我要运行一行 python 命令
直接在原命令前加上 srun 即可,例如:
$ srun --gres=gpu:1 python train.py
--gres=gpu:1 是指申请 1 个 GPU 资源,如果需要多个 GPU,可以写成 --gres=gpu:<GPU 数量>。
b. 我运行的命令里需要输入交互,例如 bash, pdb 等
$ srun --gres=gpu:1 --pty bash
c. 我要运行一个脚本文件
使用 sbatch 命令,例如:
$ sbatch --gres=gpu:1 train.sh
sbatch 运行的脚本可以包含配置参数,详细参数可参考上海交大超算平台用户手册。
d. 我要运行一个 IPython 笔记本(例如 Jupyter Notebook)
这里介绍的办法在 Visual Studio Code 中也可以使用,但是可能有问题。
请先阅读下面「d.1. 在 Visual Studio Code 中运行」中「体验下降注意」的内容。
用以下命令启动一个 Jupyter Notebook 服务:
# 执行前确保你已经切换到自己的 Python 环境(例如 conda activate xxxx)
$ srun --gres=gpu:1 jupyter notebook --no-browser --ip localhost --port `python -c "import socket; s = socket.socket(); s.bind(('', 0));print(s.getsockname()[1]);s.close()"` --ServerApp.token='123'
这在服务器本地任意一个空闲端口上启动了一个 Jupyter Notebook 服务,密码为 123。
启动之后会显示端口号,输出类似于:
[I 10:00:00.000 ServerApp] http://localhost:12345/?token=...
记住这个端口号。
你也可以固定端口,便于在 Visual Studio Code 中多次使用。例如,固定端口为 8888,修改密码为 456:
# 执行前确保你已经切换到自己的 Python 环境(例如 conda activate xxxx)
$ srun --gres=gpu:1 jupyter notebook --no-browser --ip localhost --port 8888 --ServerApp.token='456'
接下来,根据运行环境的不同,有不同的对应步骤:
d.1. 在 Visual Studio Code 中运行
打开任意一个笔记本文件,在右上角切换内核,选择「选择其他内核」,然后选择「已有的 Jupyter 服务器」,输入 http://localhost:12345(端口号替换为刚才启动的),回车,输入连接密码连接即可。
截至 2023 年 11 月,Visual Studio Code 的 Jupyter Notebook 插件对连接到「已有的 Jupyter 服务器」的支持不太好,代码补全、已安装包解析等功能都不会正常工作。
如果你很介意,这里还有一种完全不同、让你可以完全按原来方式工作的方法,但不推荐使用:
- 安装包
simple_slurm:
$ pip install simple_slurm
- 在笔记本文件中加入以下两个代码块:
# 申请 GPU
from simple_slurm import Slurm
import os
slurm = Slurm(gres=['gpu:1'])
job_id = slurm.sbatch('env > slurm.env; yes > /dev/null')
with open("slurm.env", "r") as f:
for line in f.readlines():
if "CUDA_VISIBLE_DEVICES" in line:
print(line.strip())
os.environ["CUDA_VISIBLE_DEVICES"] = line.strip().split("=")[-1]
os.remove("slurm.env")
# 释放 GPU
import os
os.system(f"scancel {job_id}")
- 每次运行笔记本时,先运行第一个代码块,然后就可以像平常一样继续运行你的代码。关闭笔记本前,务必运行第二个代码块。如果你忘记了,请务必用以下命令手动释放 GPU:
$ scancel <第一个代码块输出的 job_id>
d.2. 在浏览器中使用 Jupyter Notebook
首先将服务器端口映射到本地,例如在本地执行(端口号 12345 替换为刚才启动的,<username> 和 <hostname> 替换为你实际连接服务器时用的用户名和服务器地址):
$ ssh -L 12345:localhost:12345 <username>@<hostname>
然后在浏览器中打开 http://localhost:12345 即可。
查看队列
a. 当前正在运行和排队等待的任务
使用 squeue 命令,例如:
# 简单查看,不显示每个任务申请的具体资源数量
$ squeue
# 完整查看
$ squeue -o "%.18i %.9P %.40j %.8u %.8T %.6D %.4C %.5D %.6m %.13b %.10M"
这个命令使用会非常频繁,可以在 Shell 配置文件中添加一个别名,例如 sq:
alias sq='squeue -o "%.18i %.9P %.40j %.8u %.8T %.6D %.4C %.5D %.6m %.13b %.10M"'
b. 只查看某个用户的任务
$ squeue -u <Linux 用户名>
c. 查看历史任务
$ sacct
取消正在执行或排队的任务
先查看队列,得到任务号,然后
$ scancel <任务号>
新服务器配置指南
在建中
参考
https://serverfault.com/questions/850283/new-ubuntu-server-best-practice-for-setup
Slurm(单机)部署
本文假定服务器操作系统的发行版为 Ubuntu 20.04 LTS,其他版本的 Ubuntu 或者其他 Linux 发行版可能需要做一些调整。
本文进行的是最小安装,依照安全最佳实践,你可能还需要对涉及的各个部件配置认证、防火墙等。
1. 安装 Slurm
需要安装的软件包:
slurmctld:Slurm 控制器守护进程slurmdbd:Slurm 数据库守护进程。数据库主要用于存储作业历史数据,显示统计信息等slurmd:Slurm 计算节点守护进程munge:Munge 认证服务,用于 Slurm 组件之间的权限认证mariadb:MariaDB 数据库,也可以使用 MySQL
2. 配置数据库
Slurm 的账户信息需要存储在数据库中,需要先创建数据库和用户。
注意,在配置过程中涉及到三种「账户」:
- 数据库账户:slurmdbd 用于连接数据库的账户,需要在数据库中创建
- Linux 账户:用于组件间最小权限管理(通常安装时会自动创建,分别名为
slurm、munge) - Slurm 账户:用于分组限制使用配额和记账统计数据的用户,需要在 Slurm Accounting Manager 中创建
在 mysql 终端(例如 # mysql)中执行以下命令:
-- 创建数据库
create database slurm_acct_db;
-- 创建用户,别忘了修改密码
create user 'slurm'@'localhost';
set password for 'slurm'@'localhost' = password('i_am_the_password_plz_change_me');
-- 授权
grant all on slurm_acct_db.* to 'slurm'@'localhost';
flush privileges;
exit
3. 配置 slurmdbd.conf
需要告诉 Slurm 的数据库守护进程 slurmdbd 数据库的位置和用户信息。
在 /etc/slurm-llnl/slurmdbd.conf 中添加以下内容:
# 数据库连接信息,和 MySQL/MariaDB 的配置一致
StorageType=accounting_storage/mysql
StorageHost=localhost
StoragePort=3306
StorageLoc=slurm_acct_db
StorageUser=slurm
StoragePass=i_am_the_password_plz_change_me
# 配置其他组件连接到 SlurmDBD 的信息
AuthType=auth/munge
DbdHost=server # SlurmDBD 所在主机的主机名
DbdPort=6819
SlurmUser=slurm # 作为数据库管理员的用户名,务必和下面的 slurm.conf 保持一致,否则 slurmctld 无法修改数据库
# 日志位置
LogFile=/var/log/slurm-llnl/slurmdbd.log
4. 配置 slurm.conf
slurm.conf 是 Slurm 所有组件的配置文件,需要放在 /etc/slurm-llnl/ 目录下。
默认情况下附带了一个 configurator.html,可以在安装位置找到,推荐使用它来生成初始配置文件。
可以将该网页文件复制到本地,然后在浏览器中打开,按照提示进行配置。
配置项较多,请耐心一个一个检查。下面列出一些需要注意的(不同版本的 Slurm 可能命令有所不同):
SlurmctldHost:Slurm 控制器的主机名,也就是你的主机名($ cat /etc/hostname)NodeName:计算节点名,可任意设置(例如node、server等)ProctrackType、TaskPlugin:有关进程跟踪的配置,建议一律配置为 CgroupSelectTypeParameters:需要配置哪些为可消费资源,建议配置为CR_Core_Memory(CPU 核心数和内存)AccountingStorageType:设为 SlurmDBD,即使用数据库存储账户信息AccountingStorageLoc:数据库名,例如slurm_acct_dbAccountingStorage[Host/Port/User/Pass]:连接到 SlurmDBD 的信息,和上面的slurmdbd.conf保持一致ClusterName:集群名,可任意设置
注意,AccountingStorage[Host/Port/User/Pass] 是连接到 SlurmDBD 的信息,而不是连接到数据库的信息。
也就是说,在上面的配置文件下,应该分别是 server、6819、slurm、留空(AccountingStoragePass 的含义不是字面意思,请参考文档。我们没有配置过,不要填写)。
配置完成后,将生成的 slurm.conf 保存,再添加(或更新)如下配置项:
# 追踪资源类型
AccountingStorageTRES=gres/gpu
# 在 Slurm 日志中显示 CPU 绑定和 GPU 使用情况,便于调试
DebugFlags=CPU_Bind,gres
# 使用 Cgroup 进行作业统计
JobAcctGatherType=jobacct_gather/cgroup
# ...
# 以下配置添加在节点附近
# 注册 GRes 类型
GresTypes=gpu
# 给节点声明 GRes 配置,gpu:2 表示每个节点有 2 个 GPU。按照你的实际情况修改
NodeName=node Gres=gpu:2
完成后,将 slurm.conf 写入到 /etc/slurm-llnl/ 目录下。
5. 配置 cgroup.conf
Slurm 的 Cgroup 配置文件是单独的,需要放在 /etc/slurm-llnl/cgroup.conf 位置。
示例内容如下:
CgroupAutomount=yes
6. 配置 gres.conf
Slurm 的 GRes 配置文件也是单独的,用于指定参与调度的 GRes 设备(在我们这里就是 NVIDIA GPU)的信息,需要放在 /etc/slurm-llnl/gres.conf 位置。
示例内容如下:
# 有几个 GPU 就写几个,请按照实际情况修改
Name=gpu File=/dev/nvidia0
Name=gpu File=/dev/nvidia1
7. 启用服务
用以下命令启用并启动服务:
$ systemctl enable --now slurmdbd
$ systemctl enable --now slurmctld
$ systemctl enable --now slurmd
第一次启动时还要将当前节点的状态设为 IDLE(默认值是 UNKNOWN):
# scontrol update nodename=node state=IDLE
8. 测试
在节点上运行以下命令:
$ srun --gres=gpu:<要请求的显卡数> env | grep CUDA
显示 CUDA_VISIBLE_DEVICES=<显卡位置> 说明配置成功。
接下来,你可以使用 sbatch 提交作业,使用 squeue 查看作业状态,使用 sacct 查看作业统计信息。
具体教程可以直接参考 Slurm 作业调度系统使用指南。
参考文档
- Slurm 资源管理与作业调度系统安装配置:https://hmli.ustc.edu.cn/doc/linux/slurm-install/