背景
小明有一台运行在 IDC 内网的 NVIDIA GeForce RTX 3080 Ti GPU 服务器,他想要在这台 Linux 服务器上安装运行秋叶 SD 整合包,让自己在本地也可以运行整合包,以高效训练模型和生成图片。然而这个简单的想法第一步就把他难住了:“要怎么样才能把整合包上传到 IDC 内网呢?”。他开始抓耳挠腮,半个小时后他终于想出了一个完美的解决方案,然而紧接着另外的问题又出现了,不服输的精神让他走上了折腾 Linux 的不归路…
几经周折之后,小明终于成功的搭建起了本地可访问的 SD 工作流,开启了炼丹之路。他决定把自己漫长的折腾之路整理出来,希望看到这篇文章的人能少走一点弯路。
如何把秋叶整合包上传到 IDC 机器?
这个问题其实有多种解决方案,目前流传的整合包大多数都存储在在云盘上,可以根据自己的需求使用不同的云盘命令行工具下载,比如百度云可以通过 bypy 这个 pip 包来下载。
由于笔者没有百度云盘会员🤦,因此使用的是最粗暴的方法:先下载到本地,然后从本地通过 scp
上传。
scp -P 22 ~/downloads/sd-webui-aki-v4.1-original.zip user@host:~/
以上例子中,笔者的包存储在本地路径 ~/downloads/sd-webui-aki-v4.1-original.zip
下,其中:
user@host
- 指的是你的服务器的用户名和地址,与 ssh 登陆时保持一致就行。-P 22
- 指定的 ssh 端口,默认是 22,可根据服务器具体开放的端口修改。
如果你的 IDC 机器需要使用跳板机登陆,那么你需要用到 ProxyJump
1
scp -o "ProxyJump=jumpuser@jumphost:jumpport" -P 22 ~/downloads/sd-webui-aki-v4.1-original.zip user@targethost:~/
-o "ProxyJump=jumpuser@jumphost:jumpport"
- 表示使用跳板机进行连接,其中jumpuser
、jumphost
、jumpport
分别表示跳板机的用户名、地址、端口,需要根据读者实际情况替换。
解决换行符问题
可能因为整合包里脚本在 windows 被编辑过,运行的脚本的时候会报错 command not found
或者其他,所以首先需要把启动脚本的换行符从 CRLF
换成 LF
。
要这样做有两种解决方案,读者可以根据个人情况选择其中一种:
- 使用 vim 或者其他文本编辑器。以 vim 为例子,
vim webui.sh
然后运行:set fileformat=unix
即可。webui-user.sh
同理。 - 使用
doc2unix
包。以 ubuntu 为例,在命令行输入sudo apt-get update
、sudo apt install dos2unix
安装doc2unix
包,然后执行doc2unix webui.sh
、doc2unix webui-user.sh
即可。
“科学” 访问外网
由于特殊原因,IDC 机房并不能访问类似于 Github、huggingface、pypi 这种网站,然而整合包第一次运行的时候需要安装这些依赖。
让我们来逐一解决这些问题,首先对于 pypi,我们可以把源更改为清华的镜像源,
python -m pip install --upgrade pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
对于 Github,也有国内的代理,具体的使用方法也非常简单,只需要在 Github 的仓库链接之前加上 https://ghproxy.com/
,比如 https://ghproxy.com/https://github.com/stilleshan/ServerStatus
。因为整合包里面的 Github 依赖是硬编码在 py 文件的变量里,所以需要编辑对应的文件里在 Github 地址的前面加上 https://ghproxy.com/
。编辑 sd-webui-aki-v4.1/launch.py
文件:
编辑后:
解决了 Github 和 pypi 的问题,运行 webui.sh
的时候依赖可以成功安装了,但是 webui 成功运行起来之后会发现终端一直在报 MaxRetryError("HTTPSConnectionPool(host='huggingface.co', port=443): Max retries exceeded with url: /openai/clip-vit-large-patch14/resolve/main/merges.txt
的错误。幸运的是整合包里 sd-webui-aki-v4.1/.cache
中就包含了 openai/clip-vit-large-patch14
这个依赖,我们只需要把 sd-webui-aki-v4.1/.cache/huggingface/hub/
目录复制并替换掉 ~/.cache/hugginface/hub
目录(即用户目录)即可。
# 首先删除掉现有的 hub 文件夹
rm -r ~/.cache/huggingface/hub/
# 然后复制整合包目录下的 hub 文件夹到用户 .cache 目录下
cd sd-webui-aki-v4.1
cp -r .cache/huggingface/hub/ ~/.cache/huggingface/
最后再次运行 ./webui.sh
,会发现成功跑起服务并且没有任何报错 🎉。
难道装个 pytorch 也能击倒我吗?
这个问题不一定所有人都会遇到,如果在运行服务的时候碰到 pytorch 相关的报错,有可能是 pytorch 的 cuda 版本跟 webui 默认安装的 pytorch 版本不一致导致的,笔者就遇到过这个问题。
那么如何安装正确版本的 pytorch 呢?首先我们得先检查当前系统安装的 cuda 版本:
nvcc --version
像这样,当前安装的 cuda 版本是 11.8,跟 webui 默认安装的版本吻合,因此无需重新安装正确的 pytorch 版本。
Tips: 如果你运行
nvcc --version
提示没有这个命令的话,可以尝试ls /usr/local/ | grep cuda
,会打印出安装的 cuda 版本
然而,如果你的机器安装的 cuda 版本是 12.2,那么相应的你就需要安装最新版本的 pytorch,安装方法也很简单:
# 首先,你需要进入整合包启动时的虚拟环境,具体就是 sd-webui-aki-v4.1/venv
cd sd-webui-aki-v4.1
source venv/bin/activate
# 然后,你需要卸载当前版本的 torch
pip uninstall torch torchvision torchaudio
# 安装最新版本的 torch,因为 cuda12.1 已经是 stable 版本,所以直接安装即可
pip install torch torchvision torchaudio
我要如何在本地访问 IDC 运行的 Web UI 服务呢?
经过一系列的折腾之后,我们离终点越来越近了。当前我们已经成功搭建起了 stable diffusion web ui 服务,然而,怎么样才能像在本机一样使用这个服务呢?
要解决这个问题,首先我们得对于我们当前所处的网络环境有一定了解。
通常来说,我们本地的网络环境是无法访问 IDC 机器的,所以需要借助跳板机作为桥梁。要实现本地可以直接访问 IDC 机器中 webui 服务的端口,我们就需要先把本地端口的流量转发到跳板机的某个端口,然后再把跳板机这个端口的流量转发到 IDC 机器中 webui 服务监听的接口(比如默认的 7860)。这样我们就可以实现本地访问端口时,获取来自远端 IDC 机器的服务的响应。如下图:
这里需要正确理解 ssh
命令起的作用,简单来说,就是在本地设置一个端口转发,把发送给本地某个端口的数据通过 ssh
隧道发送给远端机器的端口。如:
ssh -CNg -L 6006:127.0.0.1:6006 urob@imurob.cn -p 37800
其中各个选项的参数详细解析为:
-C
: 这个选项启用压缩。数据在传输之前会先进行压缩,有助于提高带宽使用效率。-N
: 这个选项告诉 SSH 不要执行任何远程命令,也就是说,只进行端口转发。-g
: 允许来自任何主机的连接使用本地转发。默认情况下,只有本地主机可以使用转发。-L 6006:127.0.0.1:6006
: 这是本地端口转发(Local port forwarding)的设置。6006
: 本地主机(运行 SSH 客户端的计算机)上的端口。127.0.0.1
: 远程服务器上,流量将被发送到的 IP 地址。6006
: 远程服务器上,流量将被发送到的端口。
urob@imurob.cn
: 你要登录的远程服务器的用户名和地址。urob
: 用户名。imurob.cn
: 服务器地址。
-p 37800
: 指定远程服务器上 SSH 服务监听的端口号。在这里,该端口是 37881。 对于我们从本地到跳板机到 IDC 机器的场景,首先在本地到跳板机建立隧道,在本地机器运行:
# 下面两个 8080 端口是可替换的,可以根据当前系统可用端口或者个人喜好选择,user@jump_host -p 43001 替换成你跳板机的用户名、地址以及开放端口
ssh -CNg -L 8080:127.0.0.1:8080 user@jump_host -p 43001
然后,在跳板机运行:
# 下面 8080 端口需要跟上面跳板机指定的端口保持一致,也就是 127.0.0.1:8080 中的 8080,7860 是 webui 服务的默认端口,可以在运行脚本时指定 --port 来更改,user@idc_host -p 23001 替换成你 IDC 机器的用户名、地址以及开放端口
ssh -CNg -L 8080:127.0.0.1:7860 user@idc_host -p 23001
温馨提示:如果你本地可以直接访问 IDC 机器的话,那么可以不需要通过跳板机,直接在本地运行在上面需要在跳板机运行的指令即可。
Bonus
如果你偏好于使用 conda
配置环境依赖,那么你可以使用 conda
初始化虚拟环境,然后手动根据当前 cuda 版本和包的依赖(requirements.txt)安装依赖。这种方法适用于所有包的安装(不仅仅是秋叶的整合包)。需要注意的是,对于整合包而言,webui.sh
上有进入 venv
的指令,因此你需要单独运行整合包的根目录下的 launch.py
。
下面我将以 lora 训练的脚本 sd-script 为例简单介绍一下如何创建所需要的 conda 环境。
# 拉取代码库
git clone https://github.com/kohya-ss/sd-scripts.git
cd sd-scripts
# 创建一个空的 conda 环境,指定版本为 3.10
conda create -n sd python=3.10
# 进入 conda 环境
conda activate sd
# 安装 conda,这里首先需要根据上面所介绍的一样,先查看当前系统 cuda 版本,再根据对应的 cuda 版本安装 pytorch
# 如果你不知道你的 cuda 版本应该安装哪个 pytorch 版本,那么你可以去 https://pytorch.org/#:~:text=Aid%20to%20Ukraine.-,INSTALL%20PYTORCH,-Select%20your%20preferences 查看
# 这里我对应的 cuda 版本是 12,因此只需要安装最新版本的
pip install torch torchvision torchaudio
# 最后按照 sd-scripts README 里面介绍的安装依赖
pip install --upgrade -r requirements.txt
配置 ssh config 简化端口映射创建流程
ssh 端口映射可以让我们轻松地从本地访问到远端服务器运行的服务。然而每次都需要输入一长串命令来映射各个端口,并且还需要在跳板机和本地分别建立端口映射,这个过程实在是繁琐又复杂。试想一下,如果我们需要建立 N 个服务呢?那么我们就需要打开 N * 2 个终端,运行 2 * N 次端口映射指令,输入 N * 2 次密码,心智负担实在是太重了。
那么有没有更轻松的建立端口映射的方法呢?还真有,ssh 配置文件和映射多个端口可以完美解决我们的问题。
配置 ssh config
ssh 配置文件的位置放在用户目录的 .ssh
文件夹上,在 windows 上就是 C:\Users\你的用户名或者Admin\.ssh\config
,在 linux 或者 macos 上就是 ~/.ssh/config
。这个 config 文件并不是默认存在的,因此如果不存在需要新建。
首先,通过 ssh 建立的隧道默认情况下会有一个心跳检测,如果服务端检测到客户端不在活跃之后,就会自动断开连接,要避免这种情况有很多种方式,为了避免侵入服务端配置我选择了修改本地 ssh 配置,配置每隔 30 秒发送一个保活数据包,以避免服务端关闭连接。打开或新建 .ssh/config
文件,添加:
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
Host *
: 表示对于所有的远程主机。ServerAliveInterval
: 每隔 30s 发送一个数据包。ServerAliveCountMax
: 如果在发送 3 个数据包后服务器还没有响应那么客户端主动断开连接。
然后,配置的重点在于需要为我们的跳板机和 idc 机器配置一个别名,以及给 idc 机器配置一个跳板机。比如,如果我的跳板机的 ip 地址是 192.168.1.1
,用户名是 urob
,连接端口是默认的 22
;idc 机器的 ip 地址是 imurob.cn
,用户名是 urob
, 连接端口是 35013
,那么 .ssh/config
就可以配置成下面这样:
Host jumpserver
HostName 192.168.1.1
User urob
Port 22
Host a100
HostName imurob.cn
User urob
Port 35013
ProxyJump jumpserver
Host jumpserver/a100
: 表示主机的别名,方便记忆。HostName
: ip 地址。Port
: ssh 端口。User
: 用户名。ProxyJump
: 表示指定一个跳板机(在这里是jumpserver
,也就是192.168.1.1
这个主机)建立一个代理连接。实际上等同于手动先登录到跳板机,再从跳板机登陆到目标服务器,可以理解成一种快捷方式。设置 ProxyJump 有一个重要的好处是我们无需再手动设置从跳板机到 idc 机器的端口映射了,因为它会自动处理所有的端口转发。
配置好之后,如果我们需要设置一个从本地 8000
到 a100 机器 8000
端口的映射,我们只需要在命令行执行:
ssh -L 8000:localhost:8000 a100
有没有世界突然清净的感觉!?
更重要的是,如果你需要设置多个端口转发,比如你还需要设置本地 7860
到 idc 7860
,本地 6006
到 idc 6006
的端口映射,那么你也可以直接设置多个端口转发:
ssh -L 8000:localhost:8000 -L 7860:localhost:7860 -L 6006:localhost:6006 a100
设置端口转发竟如此轻松!
最后
在浏览器直接访问 http://localhost:<你设置的本地转发端口号,例如 6006>
(ps:3080 TI 的出图速度在 2s 之内还是挺快的orz)
Footnotes
-
关于
ProxyJump
的简单说明和使用方法参考 👉 https://www.redhat.com/sysadmin/ssh-proxy-bastion-proxyjump ↩