前阵子在技术群里看到好几个朋友问同一个问题:自己在本地跑 Qwen-Image-2.1,不是显卡显存不够,就是推理慢得没法看,最后都跑来问我云端部署的方案。老实说,这件事我熟,因为我自己就是从本地显卡一路折腾到云服务器,踩过不少坑。这篇教程会把整个云端部署过程按我实操后的顺序写出来:从选云主机、装环境、下模型、跑通推理,到把生图能力封装成 HTTP 接口,最后拿到公网地址给业务或朋友使用。不管你是第一次用云服务器,还是对 Qwen-Image-2.1 不熟,照着做基本都能跑起来。
1. 云服务器选型:为什么 GPU 实例是唯一省心选项
1.1 先盘清楚需求:本地显卡的痛,在云端怎么解
Qwen-Image-2.1 这类开源图像生成模型,权重文件动辄几十 GB,加载进显存之后对显存容量要求很高。我自己之前用一张 8GB 显存的消费级显卡试过,模型加载阶段直接报 “CUDA out of memory”,根本轮不到推理那一步。有朋友用 16GB 显存硬跑,出图速度慢到喝两杯水才出来一张,而且中途风扇声音大得像飞机起飞。
云端 GPU 实例解决的是两件事:一是显存可以按需选,24GB、40GB、80GB 都行,不够就升配,不用换整机;二是按量付费,试错成本低。你今天开一台机器跑通流程,第二天关掉不产生费用,这个灵活度是本地自购硬件比不了的。
显存需求简单对比如下:
| 使用方式 | 建议显存 | 说明 |
|---|---|---|
| 全参模型推理 | 24GB 起步 | 体验流畅,适合长期当服务跑 |
| 全参模型 + CPU offload | 16GB 左右 | 会慢一些,但能跑 |
| 量化版本 | 8GB-16GB | 需要额外处理量化文件,适合显存紧张的情况 |
我建议第一次上云直接选 24GB 以上显存的实例,省得后面陷入调参地狱。
1.2 实例规格、地区与镜像:选对能少走一半弯路
创建云服务器时,几个选项要提前想清楚,别等机器起来再后悔。
GPU 类型:常见的选择有 T4、V100、A10 这几类。T4 便宜但性能一般,适合验证流程;A10 显存和速度都比较均衡,是我个人最常用的;A100 或更新的卡当然好,但价格也高。如果你的场景是“每天稳定出图几十张”,A10 级别足够;如果是跑批量任务,再考虑更高规格。
地区:选离你近的节点就行,主要影响的是你 SSH 连接的延迟和模型下载的速度。如果你还有阿里云 OSS 这类配套资源,优先选择同一个地域,内网访问会省很多事。
镜像:无脑选 Ubuntu 22.04 或 20.04 的纯净版。不要选 CentOS,很多新版的 Python 包和 GPU 驱动在 CentOS 上会出现莫名其妙的兼容性问题。如果控制台里有“预装 GPU 驱动”的镜像选项,直接选,能帮你跳过后面最疼的驱动安装环节。
数据盘:除了系统盘,务必加一块 100GB 以上的数据盘。模型权重、虚拟环境、生成的图片都放在数据盘,系统盘留出来跑系统和日志。
1.3 安全组先想好,别等服务起来了才到处开端口
很多人习惯先把机器开起来,等 API 访问不通了再回控制台看安全组。这个习惯在云上要改掉。创建实例的时候,安全组规则里只放行你明确需要的端口:
- 22 端口:SSH 登录,但建议限定来源 IP,不要对全网开放
- 80/443 端口:后面用 Nginx 代理访问
- 8080 端口:如果你的 API 直接用 uvicorn 监听,也需要放行
其他端口一律默认关闭。端口开放越小,被扫描和被薅羊毛的概率越低。这个习惯养成之后,你后面所有云上服务都会受益。
2. 初始化与运行环境:从 SSH 到 torch 可用
2.1 登进服务器后的第一件事就是升级系统
拿到公网 IP 和密钥之后,SSH 登录进服务器,先做一次系统更新。这一步很多人跳过,但 Ubuntu 的源如果不更新,后面 apt 装包时可能出现依赖冲突。
sudo apt update && sudo apt upgrade -y sudo apt install -y build-essential git git-lfs curl wget ca-certificatesgit-lfs 必须装,模型权重很多是通过 LFS 管理的,不装会导致拉下来的文件是几十字节的指针文件而不是真实权重。
2.2 检查 GPU 驱动:nvidia-smi 能跑通再继续
登录后立刻验证驱动是否可用:
nvidia-smi如果能正常输出 GPU 型号和显存信息,说明驱动已经就绪,直接进入下一步。如果提示command not found,说明这台机器没有预装驱动。有两条路:
一是换镜像。如果实例刚创建,直接在控制台把机器学习镜像或预装驱动的镜像换掉,重新启动。二是手动装驱动,我给的命令是:
sudo apt install -y nvidia-driver-535 sudo reboot重启之后再跑nvidia-smi,直到看到类似下面的输出:
+-----------------------------------------------------------------------------+ | NVIDIA-SMI 535.xx Driver Version: 535.xx CUDA Version: 12.x | +-----------------------------------------------------------------------------+注意看右上角的 CUDA Version,它代表驱动支持的最高 CUDA 版本,后面装 PyTorch 时要参考它。驱动没问题后,再装基础工具:
sudo apt install -y python3.11-venv python3.11-dev2.3 用 venv 隔离环境,别把 Python 搞得一团糟
云端服务器很可能同时有多个项目跑,Python 全局环境很容易互相污染。我统一用venv来隔离:
sudo mkdir -p /opt/qwen-env sudo chown $USER:$USER /opt/qwen-env python3.11 -m venv /opt/qwen-env source /opt/qwen-env/bin/activate激活后,命令行提示符前面会出现(qwen-env),后面所有安装都在这个环境里进行。
安装 PyTorch 是关键一步。用 CPU 版还是 GPU 版,直接决定了后面能不能用显卡推理。这里以 CUDA 12.4 为例:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124安装完成后验证一下:
python -c "import torch; print(torch.cuda.is_available())"输出True,说明 GPU 版本 PyTorch 安装成功。如果这里输出False,先不要往下走,重点检查驱动版本和 PyTorch 的 CUDA 版本是否匹配。
接着安装模型推理需要的其余依赖:
pip install diffusers transformers accelerate modelscope sentencepiece pip install --upgrade pillow这里解释一下为什么用这些库:diffusers是加载和运行扩散模型的核心框架;transformers负责文本编码器部分;modelscope是模型下载工具;accelerate是显存优化和分布式执行的后端。这几件套是跑 Qwen-Image 系列的标准组合,缺一不可。
3. 模型权重下载:直接走 ModelScope 通道
3.1 用 modelscope 命令下载,速度稳还不容易断
模型权重是最让人头疼的一环。几十 GB 的文件,如果从访问不稳定的源下载,下到一半断了、校验失败,心态直接崩。我的建议是走 ModelScope 通道,稳定性和速度都有保障。
先确认modelscope已经通过 pip 安装好了,然后执行:
mkdir -p /opt/models cd /opt/models modelscope download --model Qwen/Qwen-Image-2.1 --local_dir /opt/models/Qwen-Image-2.1下载过程中不要中断 SSH 会话,否则进程会终止。如果你使用的是需要长期运行的机器,建议用tmux或screen挂起来。简单做法是:
tmux new -s download modelscope download --model Qwen/Qwen-Image-2.1 --local_dir /opt/models/Qwen-Image-2.1 # 按 Ctrl+b,再按 d 可以脱离会话之后随时用tmux attach -t download回去看进度。
3.2 用 git clone 也能拉权重,适合想要断点续传的情况
除了modelscope命令,ModelScope 也支持直接用 git 克隆:
git lfs install git clone https://www.modelscope.cn/Qwen/Qwen-Image-2.1.git /opt/models/Qwen-Image-2.1这种方式的好处是你可以随时git pull增量更新,网络断了也能用git lfs pull续传。坏处是首次克隆时如果 LFS 文件多,过程会更慢。两种方式我都在实际部署中用过,结论是:机器网络稳定用modelscope download最省事,网络波动较大用 git 克隆更稳。
3.3 下载完成后,先核查目录再进入下一步
很多报错都是因为权重目录不全导致的。下载完成后,检查一下目录结构:
ls -lh /opt/models/Qwen-Image-2.1 du -sh /opt/models/Qwen-Image-2.1正常情况下应该能看到config.json、若干个大体积的模型分片文件(后缀是.safetensors或类似格式)、文本编码器相关文件等,总大小通常在几十 GB。如果你发现目录下都是几 KB 的文件,说明 LFS 指针没有正确拉取,需要用git lfs pull重新拉一次。
4. 第一次推理:写个最小脚本把图跑出来
4.1 最小推理脚本,跑通了再往上加东西
环境装好、权重就位,接下来就是见证奇迹的时刻。创建infer.py,写入以下内容:
import torch from diffusers import QwenImagePipeline model_path = "/opt/models/Qwen-Image-2.1" pipe = QwenImagePipeline.from_pretrained( model_path, torch_dtype=torch.bfloat16, ) pipe.to("cuda") image = pipe( prompt="一个穿着白色厨师服的橘猫在厨房案板上揉面团,柔和自然光,高清写实", num_inference_steps=32, guidance_scale=4.5, ).images[0] image.save("first_image.png") print("saved first_image.png")执行:
source /opt/qwen-env/bin/activate python infer.py看到saved first_image.png后,检查同目录下生成的图片。能跑通这一步,说明模型加载、文本编码、UNet 推理、图像解码全链路都没问题。
4.2 首次加载慢是正常的,显存不够也有救
第一次运行脚本时,你会感觉特别慢。这个正常,因为模型要从磁盘加载几十 GB 的权重到内存,再转移到显存。另外,diffusers 首次运行时做算子选择和 CUDA 图优化也要额外时间。跑完一次后再启动,速度会明显加快。
如果你在 16GB 甚至更小显存的机器上跑,直接pipe.to("cuda")大概率会爆显存。把这一行替换成:
pipe.enable_model_cpu_offload()enable_model_cpu_offload()会自动把模型的不同组件按需搬到显存,用完再挪回 CPU 内存。代价是推理速度变慢,但至少不会一上来就 OOM。
这里我还要强调一个精度选择:bfloat16在 A10、A100 这类新架构卡上表现很稳;如果是 T4、V100 这类老卡,部分运算对bfloat16支持不完整,建议改成torch.float16。加载时把torch_dtype换一下就行,其他代码不用动。
4.3 高频报错速查:看到错误不要慌
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
CUDA out of memory | 显存不足 | 启用enable_model_cpu_offload(),或换更大显存实例 |
flash_attn相关报错 | 环境缺少 flash attention 库 | 可安装pip install flash-attn,或忽略该警告,模型会自动走普通注意力路径 |
KeyError: "xxx" | diffusers 版本过旧,不认识模型结构 | 升级 diffusers:pip install -U diffusers |
加载配置文件时报No such file or directory | 权重目录不完整或路径错误 | 回到上一步,重新检查目录文件大小和路径 |
| 推理时进程被杀 | 可能触发 OOM Killer | 用free -h检查内存,确认 swap 或加内存 |
5. 把生图能力变成 HTTP 服务:FastAPI + systemd + Nginx
5.1 写一个可用的 API 服务
本地脚本能出图只是第一步。在实际使用中,你和你的业务方不可能每次跑到服务器上执行 Python 脚本,所以需要把推理封装成 HTTP 服务。FastAPI 是目前最稳妥的选择,代码量少、性能好、文档自动生成。
创建/opt/qwen-api/app.py:
import base64 import io from contextlib import asynccontextmanager import torch from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from pydantic import BaseModel from diffusers import QwenImagePipeline MODEL_PATH = "/opt/models/Qwen-Image-2.1" API_TOKEN = "your-secret-token" pipe = None @asynccontextmanager async def lifespan(app: FastAPI): global pipe pipe = QwenImagePipeline.from_pretrained( MODEL_PATH, torch_dtype=torch.bfloat16, ) pipe.to("cuda") yield app = FastAPI(lifespan=lifespan) class GenerateRequest(BaseModel): prompt: str num_inference_steps: int = 32 guidance_scale: float = 4.5 @app.middleware("http") async def token_auth(request: Request, call_next): if request.url.path in ("/docs", "/openapi.json", "/healthz"): return await call_next(request) token = request.headers.get("x-api-token", "") if token != API_TOKEN: return JSONResponse(status_code=401, content={"code": 401, "message": "unauthorized"}) return await call_next(request) @app.post("/generate") def generate(req: GenerateRequest): image = pipe( prompt=req.prompt, num_inference_steps=req.num_inference_steps, guidance_scale=req.guidance_scale, ).images[0] buf = io.BytesIO() image.save(buf, format="PNG") return { "code": 0, "image": base64.b64encode(buf.getvalue()).decode(), "format": "png", }这个服务里我做了一个鉴权中间件,目的是防止接口裸奔。凡是访问/generate的请求,必须携带请求头x-api-token: your-secret-token,否则直接返回 401。这个习惯非常重要,后文我会继续强调。
启动验证一下:
cd /opt/qwen-api source /opt/qwen-env/bin/activate pip install fastapi uvicorn uvicorn app:app --host 0.0.0.0 --port 8080看到Application startup complete,说明 API 已经起来了。用 curl 测一次:
curl -X POST http://127.0.0.1:8080/generate \ -H "Content-Type: application/json" \ -H "x-api-token: your-secret-token" \ -d '{"prompt":"一只柴犬在公园玩飞盘,电影感灯光","num_inference_steps":32}'如果返回一串很长的 base64 字符串,说明 API 服务完全正常。
5.2 用 systemd 管理进程:开机自启、崩了自动拉起
用uvicorn直接启动的服务,SSH 断开就没了,而且进程崩了没人管。正确的做法是把服务交给 systemd。
创建服务文件/etc/systemd/system/qwen-api.service:
[Unit] Description=Qwen Image 2.1 API Service After=network.target [Service] User=root WorkingDirectory=/opt/qwen-api Environment="PATH=/opt/qwen-env/bin" ExecStart=/opt/qwen-env/bin/uvicorn app:app --host 0.0.0.0 --port 8080 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target然后执行:
sudo systemctl daemon-reload sudo systemctl enable qwen-api sudo systemctl start qwen-api sudo systemctl status qwen-api以后重启服务器,服务会自动恢复。查看日志用journalctl -u qwen-api -f,排错时特别有用。
5.3 Nginx 反向代理:统一入口、超时拉长
API 服务有了,但直接暴露 8080 端口不够规范。我习惯在前面加一层 Nginx,负责监听 80/443 端口,转发到本机 8080。
安装并启动 Nginx:
sudo apt install -y nginx编辑/etc/nginx/sites-available/default,写入:
server { listen 80 default_server; server_name _; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 300s; proxy_send_timeout 300s; } }保存后重载配置:
sudo nginx -t sudo systemctl reload nginxproxy_read_timeout 300s这一行建议保留。生图请求不是毫秒级返回的接口,默认 60 秒超时很容易在处理长 prompt 或多步推理时被 Nginx 掐断。我实际测试过,设置成 300 秒后稳定很多。
5.4 客户端调用:拿到 base64 之后怎么用
API 返回的图片是 base64 编码的 PNG。用 Python 客户端保存成图片文件:
import requests import base64 response = requests.post( "http://your_server_ip/generate", headers={"x-api-token": "your-secret-token"}, json={"prompt": "一只柯基在草地上晒太阳,油画风格", "num_inference_steps": 28}, timeout=300, ) data = response.json() if data["code"] == 0: with open("result.png", "wb") as f: f.write(base64.b64decode(data["image"])) print("saved result.png")在系统提示里已经输出的内容,不需要重复确认。
6. 上云后最容易踩的坑与规避方式
6.1 安全组端口忘了放行
这个坑排第一,因为太常见了。你在服务器上用curl http://127.0.0.1:8080明明能通,但换到本地电脑用公网 IP 访问就超时。90% 的原因是云控制台的安全组没有放行对应端口。
每放行一个端口,都要去安全组规则里确认一下。如果你配置了宝塔面板这类运维工具,还要记得面板系统防火墙和云安全组是两个独立通道,两边都要放行。不是一边开了就万事大吉。
6.2 买了 GPU 实例但 nvidia-smi 就是报错
如果你创建实例时选的不是预装驱动镜像,又错过了换镜像的机会,手动装驱动时最容易犯的错是版本对不上。驱动装完一定先nvidia-smi看 CUDA Version,再用配合该版本的 PyTorch 安装命令。
有的朋友装 PyTorch 时用了 CPU 版本,代码跑得慢还以为是模型问题。验证方式很简单:
python -c "import torch; print(torch.cuda.is_available())"输出True才代表 GPU 真正可用。
6.3 磁盘空间规划不足,模型下到一半卡住
模型权重几十 GB,加上 Python 环境和依赖,数据盘空间很容易吃紧。下载前先看:
df -h确认你的数据盘剩余空间大于 80GB 再开始下载。我在测试时遇到过No space left on device,结果模型文件下到一半被中断,看起来目录完整,实际全是残缺文件。处理的办法也很直接:删掉重新下载,或者加一块数据盘挂载到模型目录,目录结构不变。
6.4 pip 下载依赖慢到怀疑人生
如果你在服务器上执行pip install时速度只有几 KB,多半是网络原因。换用国内 pip 镜像能解决 90% 的问题:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplePyTorch 的官方源走不通时,可以加清华源临时安装:
pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple这个操作不会影响最终结果,只是让等待时间从半小时缩到几分钟。
6.5 systemd 服务起不来的排查思路
systemctl start qwen-api后,curl 127.0.0.1:8080没反应,大概率是服务根本没起来。第一时间查看状态和日志:
sudo systemctl status qwen-api sudo journalctl -u qwen-api -f常见原因有三个:一是ExecStart里写的是绝对路径,但虚拟环境里其他命令没有被加载到 PATH;二是模型路径写错,from_pretrained 加载时报错退出;三是端口被占用。
我的建议是,在[Service]里显式设置Environment="PATH=/opt/qwen-env/bin",并在启动前手动执行一次uvicorn app:app --host 0.0.0.0 --port 8080确认完整流程能跑通,再丢给 systemd 管。
最后分享一点我的实操体会
整个流程走完一遍后,我最大的感受是:云端部署 Qwen-Image-2.1 的真正难点不在模型本身,而在于工程细节。模型的加载和推理,官方文档和开源社区已经解决得很好了,但“怎么让服务稳定跑着、怎么控制成本、怎么保护接口不裸奔”这些问题,都需要你自己一步步扛过坑。
我个人现在跑这套服务,已经形成了固定套路:先按量付费开一台 A10 级别实例,跑通最小链路后顺手做一个系统盘快照,再配置 systemd 和 Nginx,最后把 API token 改成一个足够随机的值。平时如果只是临时出图,用完就关机;如果是长期给项目提供服务,再考虑包年包月。这个方法既省钱又稳定,推荐你也试试。
如果你在部署过程中卡在某个具体环节,把报错信息和nvidia-smi的输出贴出来,基本就能定位到问题。云部署这件事,第一次走通之后,后面就会越来越顺。