Skip to content
共绩算力文档中心

怎么用云主机配置 生图模型 环境并使用自动弹性扩缩容

在共绩算力云主机上搭建可弹性伸缩的 SD 生图服务环境,实现:

  • 私有化部署生图模型 API 服务
  • 根据流量自动扩缩容
  • 支持高并发访问

模型链接:https://www.modelscope.cn/models/AI-ModelScope/stable-diffusion-xl-base-1.0/summary

Stable Diffusion XL 是在 SD 的基础上的一个二阶段的级联扩散模型(Latent Diffusion Model),包括 Base 模型和 Refiner 模型。其中 Base 模型的主要工作和 Stable Diffusion 1.x-2.x 一致,具备文生图(txt2img)、图生图(img2img)、图像 inpainting 等能力。在 Base 模型之后,级联了 Refiner 模型,对 Base 模型生成的图像 Latent 特征进行精细化提升,其本质上是在做图生图的工作。

查看模型环境要求以及必要依赖项

登录云主机,通过共绩算力平台提供的 Web 终端或 Jupyter 终端登录,直接执行以下命令

登录共绩算力云主机,选择合适的云主机设备(大部分情况选择 4090 即可,如果需要先下载模型的情况。可以选择 CPU 启动 节省成本)

通过刚才的模型环境建议 选择适合的云主机框架环境

我们推荐在云主机中配置环境,存储模块中(对象存储加速中或者共享存储卷)放模型以达到镜像和模型的分离。

目前云主机磁盘限额 80GB,云主机磁盘内存过大会导致 关机/启动 时间过长

详细配置过程可以查看本文档底部附录一 附录二

  • 共享存储卷:主要用于高性能、高并发的读写场景。它像传统的硬盘或网络文件系统(如 NFS),可以被多个计算节点同时挂载,支持文件的频繁读写和修改,适合训练数据、模型中间结果、日志等需要频繁读写的场景。
  • 对象存储加速挂载:主要用于高效读取大规模数据,通常是只读场景。它将对象存储(如 S3)的数据通过挂载的方式直接呈现为本地文件系统,方便访问和读取,适合加载大数据集、预训练模型等只读需求。

通过共绩算力平台提供的 Web 终端或 Jupyter 终端登录,直接执行接下来的操作

如果需要使用 SSH 远程连接可以参考这篇文档:

https://www.gongjiyun.com/docs/server/jojuw4rfdiepfxkchonctw0jnxe/aanqw1zk3iedntkvqc6c6aapnkk/

清理旧环境(彻底消除冲突残留)

Terminal window
pip3 uninstall -y torch torchvision torchaudio vllm outlines xformers huggingface-hub openai
rm -rf /opt/miniconda3/lib/python3.12/site-packages/{torch*,vllm*,outlines*,xformers*,huggingface_hub*,openai*}
pip3 cache purge

安装核心依赖(经测试无任何冲突)

requirement.txt:

Terminal window
python>=3.8
[transformers](https://zhida.zhihu.com/search?content_id=240307647&content_type=Article&match_order=1&q=transformers&zhida_source=entity)>=4.37.0
[accelerate](https://zhida.zhihu.com/search?content_id=240307647&content_type=Article&match_order=1&q=accelerate&zhida_source=entity)>=0.27.0
modelscope>=1.9.5
numpy>=1.22.3
[torch](https://zhida.zhihu.com/search?content_id=240307647&content_type=Article&match_order=1&q=torch&zhida_source=entity)>=1.11.0
[gradio](https://zhida.zhihu.com/search?content_id=240307647&content_type=Article&match_order=1&q=gradio&zhida_source=entity)>=4.8.0
diffusers>=0.26.3
opencv-python>=4.9.0.80
safetensors>=0.4.2

验证环境(确保无兼容问题)

执行以下命令,输出均符合预期则环境正常:

Terminal window
torch.cuda.is_available()

这些依赖都搞定以后,咱们就可以通过下面这部分的代码自验,并且开始下载对应的 stable-diffusion-xl-base-1.0 模型了(耗时较久)

Terminal window
我们推荐使用命令行或者 ModelScope SDK 来进行模型的下载。[操作指引](https://www.modelscope.cn/docs/%E6%A8%A1%E5%9E%8B%E7%9A%84%E4%B8%8B%E8%BD%BD)
在下载前,请先通过如下命令安装ModelScope
pip install modelscope
命令行下载
下载完整模型库
modelscope download --model AI-ModelScope/stable-diffusion-xl-base-1.0
下载单个文件到指定本地文件夹(下载载到共享存储卷的路径中(可选))
modelscope download --model AI-ModelScope/stable-diffusion-xl-base-1.0 README.md --local_dir ./dir
更多更丰富的命令行下载选项,可参见[具体文档](https://www.modelscope.cn/docs/models/download#%E4%BD%BF%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7%E4%B8%8B%E8%BD%BD%E6%A8%A1%E5%9E%8B)
SDK下载
#模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('AI-ModelScope/stable-diffusion-xl-base-1.0')
Git下载
请确保 lfs 已经被正确安装
git lfs install
git clone https://www.modelscope.cn/AI-ModelScope/stable-diffusion-xl-base-1.0.git
如果您希望跳过 lfs 大文件下载,可以使用如下命令
GIT_LFS_SKIP_SMUDGE=1 git clone https://www.modelscope.cn/AI-Model
import cv2 # pip install opencv-python
import torch
import gradio as gr
import numpy as np
from modelscope.utils.constant import Tasks
from modelscope.pipelines import pipeline
prompt_dict = {
"None": "{prompt}",
"Enhance": "breathtaking {prompt} . award-winning, professional, highly detailed",
"Anime": "anime artwork {prompt} . anime style, key visual, vibrant, studio anime, highly detailed",
"Photographic": "cinematic photo {prompt} . 35mm photograph, film, bokeh, professional, 4k, highly detailed",
"Digital Art": "concept art {prompt} . digital artwork, illustrative, painterly, matte painting, highly detailed",
"Comic Book": "comic {prompt} . graphic illustration, comic art, graphic novel art, vibrant, highly detailed",
"Fantasy Art": "ethereal fantasy concept art of {prompt} . magnificent, celestial, ethereal, painterly, epic, majestic, magical, fantasy art, cover art, dreamy",
"Analog Film": "analog film photo {prompt} . faded film, desaturated, 35mm photo, grainy, vignette, vintage, Kodachrome, Lomography, stained, highly detailed, found footage",
"Neon Punk": "neonpunk style {prompt} . cyberpunk, vaporwave, neon, vibes, vibrant, stunningly beautiful, crisp, detailed, sleek, ultramodern, magenta highlights, dark purple shadows, high contrast, cinematic, ultra detailed, intricate, professional",
"Isometric": "isometric style {prompt} . vibrant, beautiful, crisp, detailed, ultra detailed, intricate",
"Low Poly": "low-poly style {prompt} . low-poly game art, polygon mesh, jagged, blocky, wireframe edges, centered composition",
"Origami": "origami style {prompt} . paper art, pleated paper, folded, origami art, pleats, cut and fold, centered composition",
"Line Art": "line art drawing {prompt} . professional, sleek, modern, minimalist, graphic, line art, vector graphics",
"Craft Clay": "play-doh style {prompt} . sculpture, clay art, centered composition, Claymation",
"Cinematic": "cinematic film still {prompt} . shallow depth of field, vignette, highly detailed, high budget Hollywood movie, bokeh, cinemascope, moody, epic, gorgeous, film grain, grainy",
"3D Model": "professional 3d model {prompt} . octane render, highly detailed, volumetric, dramatic lighting",
"Pixel Art": "pixel-art {prompt} . low-res, blocky, pixel art style, 8-bit graphics",
"Texture": "texture {prompt} top down close-up"
}
negative_prompt_dict = {
"None": "{negative_prompt}",
"Enhance": "{negative_prompt} ugly, deformed, noisy, blurry, distorted, grainy",
"Anime": "{negative_prompt} photo, deformed, black and white, realism, disfigured, low contrast",
"Photographic": "{negative_prompt} drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly",
"Digital Art": "{negative_prompt} photo, photorealistic, realism, ugly",
"Comic Book": "{negative_prompt} photograph, deformed, glitch, noisy, realistic, stock photo",
"Fantasy Art": "{negative_prompt} photographic, realistic, realism, 35mm film, dslr, cropped, frame, text, deformed, glitch, noise, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, sloppy, duplicate, mutated, black and white",
"Analog Film": "{negative_prompt} painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured",
"Neon Punk": "{negative_prompt} painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured",
"Isometric": "{negative_prompt} deformed, mutated, ugly, disfigured, blur, blurry, noise, noisy, realistic, photographic",
"Low Poly": "{negative_prompt} noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo",
"Origami": "{negative_prompt} noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo",
"Line Art": "{negative_prompt} anime, photorealistic, 35mm film, deformed, glitch, blurry, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, mutated, realism, realistic, impressionism, expressionism, oil, acrylic",
"Craft Clay": "{negative_prompt} sloppy, messy, grainy, highly detailed, ultra textured, photo",
"Cinematic": "{negative_prompt} anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured",
"3D Model": "{negative_prompt} ugly, deformed, noisy, low poly, blurry, painting",
"Pixel Art": "{negative_prompt} sloppy, messy, blurry, noisy, highly detailed, ultra textured, photo, realistic",
"Texture": "{negative_prompt} ugly, deformed, noisy, blurry"
}
torch.cuda.empty_cache()
def clear_fn(value):
return "", "", "None", 768, 768, 10, 50, None
def concatenate_images(images):
# 得到每张图片的高度,并存储在列表中
heights = [img.shape[0] for img in images]
# 计算所有图片宽度的总和
max_width = sum([img.shape[1] for img in images])
# 创建一个新的空白图像,大小为最大高度和总宽度
concatenated_image = np.zeros((max(heights), max_width, 3), dtype=np.uint8)
x_offset = 0 # 初始化偏移量为 0
for img in images: # 遍历所有图片
# 将图片复制到新图像的相应位置上
concatenated_image[0:img.shape[0], x_offset:x_offset + img.shape[1], :] = img
x_offset += img.shape[1] # 更新偏移量为下一张图片的起始位置
return concatenated_image # 返回拼接后的图片
pipe = pipeline(task=Tasks.text_to_image_synthesis,
model='AI-ModelScope/stable-diffusion-xl-base-1.0',
use_safetensors=True,
model_revision='v1.0.0')
def display_pipeline(prompt: str,
negative_prompt: str,
style: str = 'None',
height: int = 768,
width: int = 768,
scale: float = 10,
steps: int = 50,
seed: int = 0):
# 如果提示为空,则抛出异常
if not prompt:
raise gr.Error('The validation prompt is missing.')
# 打印预设风格字典中的样式
print(prompt_dict[style])
# 使用预设风格格式化正面提示语
prompt = prompt_dict[style].format(prompt=prompt)
# 使用预设风格格式化负面提示语
negative_prompt = negative_prompt_dict[style].format(negative_prompt=negative_prompt)
# 创建一个随机数生成器,并设定种子以方便复现结果
generator = torch.Generator(device='cuda').manual_seed(seed)
# 调用模型管道生成图片
output = pipe({'text': prompt,
'negative_prompt': negative_prompt,
'num_inference_steps': steps,
'guidance_scale': scale,
'height': height,
'width': width,
'generator': generator
})
# 获取输出结果中的图片
result = output['output_imgs'][0]
# 定义存储图片的路径
image_path = './lora_result.png'
# 将图片写入文件
cv2.imwrite(image_path, result)
# 读取图片文件,并将其从 BGR 格式转换为 RGB 格式
image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)
# 返回处理后的图片
return image

如果你的 GPU 内存不够出现 OOM 的报错,可以尝试以下解决方案:

  1. 缓存清理:
gc.collect()
torch.cuda.empty_cache()
  1. 换个小点的模型:
pipe = pipeline(task=Tasks.text_to_image_synthesis,
model='AI-ModelScope/stable-diffusion-v1-5',
use_safetensors=True,
model_revision='v1.0.0')

同样还是以 gradio 框架为例,通过以下代码快速构建一个用户和模型的交互界面:

with gr.Blocks() as demo:
# 创建一个水平排列的容器,即一行
with gr.Row():
# 在行中创建一个列容器,该容器的大小是默认的两倍
with gr.Column(scale=2):
# 创建一个多行文本框,用于输入提示词
prompt = gr.Textbox(label='提示词', lines=3)
# 创建一个多行文本框,用于输入负向提示词
negative_prompt = gr.Textbox(label='负向提示词', lines=3)
# 创建一个下拉菜单,用于选择风格,列出各种风格选项
style = gr.Dropdown(
['None', 'Enhance', 'Anime', 'Photographic', 'Digital Art', 'Comic Book', 'Fantasy Art', 'Analog Film',
'Cinematic', '3D Model', 'Neon Punk', 'Pixel Art', 'Isometric', 'Low Poly', 'Origami', 'Line Art',
'Craft Clay', 'Texture'], value='None', label='风格')
with gr.Row():
# 创建一个滑块,用于选择图片高度
height = gr.Slider(512, 1024, 768, step=128, label='高度')
# 创建一个滑块,用于选择图片宽度
width = gr.Slider(512, 1024, 768, step=128, label='宽度')
with gr.Row():
# 创建一个滑块,用于选择引导系数
scale = gr.Slider(1, 15, 10, step=.25, label='引导系数')
# 创建一个滑块,用于选择迭代步数
steps = gr.Slider(25, maximum=100, value=50, step=5, label='迭代步数')
# 创建一个滑块,用于选择随机数种子,并且有一个随机化按钮
seed = gr.Slider(minimum=1, step=1, maximum=999999999999999999, randomize=True, label='随机数种子')
# 创建一个水平排列的容器,即一行
with gr.Row():
# 创建一个按钮,用于清除输入
clear = gr.Button("清除")
# 创建一个按钮,用于提交输入并生成图片
submit = gr.Button("提交")
# 在行中创建另一个列容器,该容器的大小是默认的三倍
with gr.Column(scale=3):
# 创建一个用于显示输出图片的组件
output_image = gr.Image()
# 当提交按钮被点击时,调用 display_pipeline 函数并将输入参数传递给它,将结果输出到 output_image 组件
submit.click(fn=display_pipeline, inputs=[prompt, negative_prompt, style, height, width, scale, steps, seed],
outputs=output_image)
# 当清除按钮被点击时,调用 clear_fn 函数,将 clear 作为输入,输出到指定的组件上,并将它们重置为初始状态
clear.click(fn=clear_fn, inputs=clear,
outputs=[prompt, negative_prompt, style, height, width, scale, steps, output_image])
demo.queue(status_update_rate=1).launch(share=False)

至此,在本地机器上运行的话(把上述所有代码都粘贴到一个 app.py 脚本,运行即可)

咱们在平台上访问 7860 端口的链接就可以看到效果页面了!

有些小伙伴的 GPU 环境在远处服务器上,还需做一道端口转发,例如:

可以参考这篇文档

https://www.gongjiyun.com/docs/server/jojuw4rfdiepfxkchonctw0jnxe/taaewpcy3ierj6km0okcmr40nuc/

(本地执行)ssh -L 9000:127.0.0.1:7860 用户 ID@远程机器 IP

然后本地访问 http://127.0.0.1:9000/ 即可

好啦,如此一来,一个本地的文生图应用就构建完毕

用户在完成一个阶段性配置之后可以通过云主机列表页 保存镜像选项 自定义 tag 保存,方便之后再次使用

5.非平台基础镜像如何配置 SSH 连接

Section titled “5.非平台基础镜像如何配置 SSH 连接”

可以参考这篇文档:https://www.gongjiyun.com/docs/server/jojuw4rfdiepfxkchonctw0jnxe/vkqcwyjz8i8yksknot6cia2fn0e/

6.如何转换为弹性部署服务(自动弹性扩缩容)

Section titled “6.如何转换为弹性部署服务(自动弹性扩缩容)”

若您的业务流量存在明显波动或需要按需使用资源以控制成本,强烈建议启用自动扩缩容功能。按需分配资源,避免资源浪费,确保任务稳定运行,提升效率并降低长时间占用成本。

  1. 回到云主机页面,找到“部署服务”
  1. 关机并保存镜像,输入镜像标签
  1. 选择最新版本
  1. 选择 GPU 配置(如 4090),节点默认选择 1 个
  1. 挂载存储卷

注意:挂载存储的时候,不要直接挂载到/、/root、 /root/目录,否则会出现一些奇怪的问题。建议挂载在类似/root/data、/root/code 之类的二级目录。

其他配置根据自己镜像的需要来进行配置

  1. 点击最下方“确认部署”即可,若无问题则会进入节点拉取界面,耐心等待即可

具体的自动弹性扩缩容设置可以参考这篇使用文档:https://www.gongjiyun.com/docs/y/nnnkwiwlkid3m1ksgxdcrvsknrj/jurlwpstdiyokzkzwqocfpclnbf/

参数设置建议

最小节点数:建议设置为 1,避免冷启动影响。如果业务对响应时间要求极高,可以适当增加。

最大节点数:根据预算和业务峰值合理设置。建议先从小值开始,观察实际使用情况后逐步调整。

队列延迟阈值:建议设置为 4-10 秒,平衡成本和体验。对于实时性要求高的任务,可以设置较小值。

空闲超时时间:建议设置为 300-600 秒,减少冷启动影响的同时控制成本。

附录 1 对象存储加速中放置模型

Section titled “附录 1 对象存储加速中放置模型”

点击”新增对象存储配置”按钮,选择云服务商,填写以下信息:

  • 配置名称
  • 对象存储:服务商、地域、Endpoint(不能带 Bucket)、AccessKey、SecretKey、Bucket 名称
  • 加速目录:Bucket 中需进行加速处理的目录(不建议挂载根目录,因这会致使根目录下所有文件被缓存,占用大量空间,且不利于业务的合理分割)

当执行保存配置操作时,系统会自动对配置的可用性进行检测。只有在校验通过之后,配置才可成功保存。在列表中查找到相应配置后,点击“开始加速”选项,接着选择加速区域,此时系统将自动对 JuiceFS 文件系统进行初始化(此过程约需 1 - 2 分钟,期间状态会从蓝色转变为绿色,状态为绿色时已可以挂载此存储桶)。同时,系统还会执行提前预热操作(此操作需从云端将文件下载至本地,因此需等待一定时长。例如,若文件大小为 6.6 G,下载完成大约需要 30 分钟)。

任务发布时挂载

先选择 GPU、GPU 区域(需要与对象存储选择的加速区域一致)。我的对象存储加速了“浙江一区”,所以我选择“浙江一区”的 4090 GPU。

然后在任务发布页面的”存储配置”区域,选择已配置并加速的 S3 存储桶(当前状态为绿色时,可以直接挂载。集群内第一次使用需要等待从云端拉取文件到集群。此操作需从云端将文件下载至本地,因此需等待一定时长。例如,若文件大小为 6.6 G,下载完成大约需要 30 分钟。集群第二次挂载则会直接从本地拉取模型文件)。

为每个对象存储加速目录填写容器内的挂载路径(如 /mnt/my_model_data),路径需以 / 开头。需要注意两个目录的对应关系。

我这里将对象存储 Bucket 中的 /qwen1-5/hub/挂载到了容器中的 /root/.cache/huggingface 目录中

提交任务后,容器启动时会自动挂载所选 S3 存储。待容器启动完成后,可进入容器并验证挂载。查看挂载目录下的文件是否存在

详细配置细节可以参考对象存储加速文档

创建新的存储桶:

重要提醒:

• 存储桶共享 200.0 GBGB 总容量,可创建多个存储桶

• 单区域桶无流量传输费用,多区域桶会产生区域间同步费用

• 某地区未使用节点 15 天后,缓存组将被自动释放

• 多实例同时读写可能存在短暂延迟,但保证最终一致性

• 建议定期使用存储桶以避免节点释放影响

配置完成后输入挂载路径

在 jupyterlab 中将模型相关配置下载在挂载路径中(例如 /root/data)以实现多机数据共享 达到镜像和模型的分离