小明有一台运行在 IDC 内网的 NVIDIA GeForce RTX 4090 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 机器需要使用跳板机登陆,那么你需要用到 ProxyJump1
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 文件:before edit编辑后:after edit
解决了 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 的 cuda 版本跟 webui 默认安装的 pytorch 版本不一致导致的,笔者就遇到过这个问题。
那么如何安装正确版本的 pytorch 呢?首先我们得先检查当前系统安装的 cuda 版本:
nvcc --version
cuda 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
经过一系列的折腾之后,我们离终点越来越近了。当前我们已经成功搭建起了 stable diffusion web ui 服务,然而,怎么样才能像在本机一样使用这个服务呢?
要解决这个问题,首先我们得对于我们当前所处的网络环境有一定了解。network通常来说,我们本地的网络环境是无法访问 IDC 机器的,所以需要借助跳板机作为桥梁。要实现本地可以直接访问 IDC 机器中 webui 服务的端口,我们就需要先把本地端口的流量转发到跳板机的某个端口,然后再把跳板机这个端口的流量转发到 IDC 机器中 webui 服务监听的接口(比如默认的 7860)。这样我们就可以实现本地访问端口时,获取来自远端 IDC 机器的服务的响应。如下图:network这里需要正确理解 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 机器的话,那么可以不需要通过跳板机,直接在本地运行在上面需要在跳板机运行的指令即可。
如果你偏好于使用 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 端口映射可以让我们轻松地从本地访问到远端服务器运行的服务。然而每次都需要输入一长串命令来映射各个端口,并且还需要在跳板机和本地分别建立端口映射,这个过程实在是繁琐又复杂。试想一下,如果我们需要建立 N 个服务呢?那么我们就需要打开 N * 2 个终端,运行 2 * N 次端口映射指令,输入 N * 2 次密码,心智负担实在是太重了。
那么有没有更轻松的建立端口映射的方法呢?还真有,ssh 配置文件和映射多个端口可以完美解决我们的问题。
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:4090 的出图速度在 2s 之内还是挺快的orz)show
关于 ProxyJump 的简单说明和使用方法参考 👉 https://www.redhat.com/sysadmin/ssh-proxy-bastion-proxyjump ↩