vLLM-Omni 图生图(Image-to-Image)在线编辑服务部署实战:Qwen-Image-Edit 单图 / 多图 / 分层生成全指南
【免费下载链接】vllm-omniA framework for efficient model inference with omni-modality models项目地址: https://gitcode.com/GitHub_Trending/vl/vllm-omni
vLLM-Omni 基于 OpenAI 兼容的多模态 Chat Completions 协议,提供开箱即用的图生图(Image-to-Image)在线编辑服务。本指南以 Qwen-Image-Edit 为例,完整讲解服务端启动、四种客户端调用方式、请求 / 响应协议、生成参数与分层生成(Qwen-Image-Layered)、多图编辑(Qwen-Image-Edit-2509)等实战要点。阅读完成后,你将能够独立部署一个可对外提供图像编辑能力的在线服务,并熟练使用 curl、OpenAI Python SDK、命令行客户端与 Gradio 界面四种方式完成调用与结果解码。
一、功能概览与适用模型
该示例位于仓库 examples/online_serving/image_to_image,用于演示如何通过 vLLM-Omni 部署 Image-to-Image 模型并提供在线图像编辑服务。除 Qwen-Image-Edit 外,还支持 BAGEL 等其他 Image-to-Image 扩散流水线。
| 模型 | 能力 | 说明 |
|---|---|---|
Qwen/Qwen-Image-Edit | 单图文本引导编辑 | 基础图生图编辑模型,风格迁移、调色、场景转换等 |
Qwen/Qwen-Image-Edit-2509 | 多图输入编辑 | 使用QwenImageEditPlusPipeline,支持在同一消息中传入多张图片并组合编辑 |
Qwen/Qwen-Image-Layered | 分层图像生成 | 从参考图 + 文本提示分解出多层图像 |
ByteDance-Seed/BAGEL-7B-MoT | 图像编辑(BAGEL 流水线) | 通过extra_body透传cfg_text_scale、cfg_img_scale等模型专属参数 |
从源码结构看,vLLM-Omni 将每种扩散模型封装为独立的 pipeline,例如 pipeline_qwen_image_edit.py 对应单图编辑、pipeline_qwen_image_edit_plus.py 对应多图编辑(QwenImageEditPlusPipeline,其注释明确"Edit prompt template - different from generation template, supports multiple images")。不同模型由服务启动时指定的--omni模式自动注册与调度。
二、服务端部署
2.1 基础启动(单图编辑)
vllm serve Qwen/Qwen-Image-Edit --omni --port 8092--omni为 vLLM-Omni 的多模态统一入口开关;--port 8092指定 OpenAI 兼容 API 端口。
显存不足(OOM)提示:若遇到显存溢出或 GPU 显存有限,可开启 VAE slicing 与 tiling 降低显存占用:
vllm serve Qwen/Qwen-Image-Edit --omni --port 8092 --vae-use-slicing --vae-use-tiling2.2 多图编辑启动(Qwen-Image-Edit-2509)
vllm serve Qwen/Qwen-Image-Edit-2509 --omni --port 8092多图编辑的核心区别在于请求的 user message content 中可包含多张图片(顺序有意义),详见下文"多图编辑"一节。
2.3 使用启动脚本(带参数)
仓库提供了现成启动脚本 run_server.sh:
bash run_server.sh脚本内部支持通过环境变量覆盖模型与端口:
MODEL=Qwen/Qwen-Image-Edit-2509 bash run_server.sh # 换模型 PORT=8093 bash run_server.sh # 换端口脚本实现如下(默认MODEL=Qwen/Qwen-Image-Edit、PORT=8092):
#!/bin/bash # Qwen-Image-Edit online serving startup script MODEL="${MODEL:-Qwen/Qwen-Image-Edit}" PORT="${PORT:-8092}" echo "Starting Qwen-Image-Edit server..." echo "Model: $MODEL" echo "Port: $PORT" vllm serve "$MODEL" --omni \ --port "$PORT"服务就绪的标志是日志中出现INFO: Application startup complete.(其前为Started server process [PID]与Waiting for application startup.)。若需更大并发或更低单请求延迟,可参考 Qwen/Qwen-Image-Edit.md 中验证过的--tensor-parallel-size 2(TP=2)配置。
2.4 部署其他 Image-to-Image 模型(BAGEL)
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091BAGEL 与 Qwen 系列在调用方式上的差异主要体现在模型专属生成参数上(如cfg_text_scale、cfg_img_scale),完整参数列表见 BAGEL-7B-MoT.md。
三、客户端调用:四种方式
3.1 方法一:curl(推荐用于快速验证)
仓库封装了 curl 调用脚本 run_curl_image_edit.sh,一行完成"读图 → 编码 → 请求 → 解码 → 落盘":
bash run_curl_image_edit.sh input.png "Convert this image to watercolor style" # 可指定输出文件(默认为 image_edit_<时间戳>.png) bash run_curl_image_edit.sh input.png "Convert to cartoon style" output.png该脚本的实现要点(值得借鉴):通过管道把 base64 数据喂给jq -Rs构造 JSON,从而避免超大图片突破 shell 的 ARG_MAX 参数长度限制;请求体结构为messages[0].content中先放文本指令、再放image_url格式的 data URL,生成参数统一放入extra_body。
不借助脚本、直接使用 curl 的方式如下:
IMG_B64=$(base64 -w0 input.png) cat <<EOF > request.json { "messages": [{ "role": "user", "content": [ {"type": "text", "text": "Convert this image to watercolor style"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,$IMG_B64"}} ] }], "extra_body": { "height": 1024, "width": 1024, "num_inference_steps": 50, "guidance_scale": 1, "seed": 42 } } EOF curl -s http://localhost:8092/v1/chat/completions \ -H "Content-Type: application/json" \ -d @request.json \ | jq -r '.choices[0].message.content[0].image_url.url' \ | cut -d',' -f2 | base64 -d > output.png注意:直接使用 curl 时,生成参数必须写在 JSON 顶层的字面量"extra_body"键内(如上所示)。
3.2 方法二:OpenAI Python SDK
import base64 from openai import OpenAI client = OpenAI(base_url="http://localhost:8092/v1", api_key="none") with open("input.png", "rb") as f: img_b64 = base64.b64encode(f.read()).decode() response = client.chat.completions.create( model="Qwen/Qwen-Image-Edit", messages=[{ "role": "user", "content": [ {"type": "text", "text": "Convert to watercolor style"}, {"type": "image_url", "image_url": { "url": f"data:image/png;base64,{img_b64}" }}, ], }], extra_body={ "num_inference_steps": 50, "guidance_scale": 1, "seed": 42, }, ) img_url = response.choices[0].message.content[0].image_url.url _, b64_data = img_url.split(",", 1) with open("output.png", "wb") as f: f.write(base64.b64decode(b64_data))关键点:OpenAI SDK 的extra_body关键字参数会自动将生成参数合并进请求体的顶层,无需手工嵌套"extra_body"键(这与 curl 的用法相反,详见 chat_completions_api.md 中的参数处理说明)。
3.3 方法三:Python 客户端脚本
仓库提供了功能更完整的命令行客户端 openai_chat_client.py:
# 单图编辑 python openai_chat_client.py --input input.png --prompt "Convert to oil painting style" --output output.png # 多图编辑(需要 Qwen-Image-Edit-2509 服务端) python openai_chat_client.py --input input1.png input2.png --prompt "Combine these images into a single scene" --output output.png该脚本支持的全部参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--input, -i(必填,可多个) | — | 输入图片路径,多图时按顺序传入多个 |
--prompt, -p(必填) | — | 编辑指令 |
--output, -o | output.png | 输出文件 |
--server, -s | http://localhost:8092 | 服务端地址 |
--height | 1024 | 输出图像高度(像素) |
--width | 1024 | 输出图像宽度(像素) |
--steps | 50 | 去噪步数 |
--guidance | 7.5 | CFG 引导强度 |
--seed | 0 | 随机种子 |
--negative | 无 | 负向提示词 |
--extra-body | 无 | 模型专属参数 JSON,如'{"cfg_text_scale": 4.0}' |
模型专属参数可通过--extra-body透传,例如针对 BAGEL:
python openai_chat_client.py \ --input input.png \ --prompt "Make the scene look like a watercolor painting" \ --server http://localhost:8091 \ --extra-body '{"cfg_text_scale": 4.0, "cfg_img_scale": 1.5}'从源码看,edit_image()会依次把height、width、num_inference_steps、guidance_scale、seed、negative_prompt写入extra_body,并用extra_body.update()合并模型专属参数,最终以requests.post提交到/v1/chat/completions,再从响应的content[0].image_url.url中截取data:image...;base64,之后的部分解码落盘。
3.4 方法四:Gradio 交互界面
前置依赖:Gradio 属于可选依赖,需先安装
[demo]extras:pip install 'vllm-omni[demo]'源码安装则使用
pip install -e '.[demo]'。
python gradio_demo.py # 浏览器访问 http://localhost:7861gradio_demo.py 提供可视化编辑界面:支持上传主图、附加多张辅助图(对应多图编辑)、填写编辑指令与负向提示词,并提供"推理步数(10–100,默认 50)"、"CFG 强度(1.0–20.0,默认 7.5)"、"随机种子(-1 表示随机)"三个控件,内置水彩、黑白、饱和度增强、卡通、复古滤镜、昼夜转换、油画、梦幻模糊等示例指令一键填充。界面默认连接http://localhost:8092,可通过--server、--port、--share参数调整。
四、请求格式详解
4.1 标准 image_url 格式
{ "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Convert this image to watercolor style"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}} ] } ] }4.2 简化 image 格式
不显式指定type,直接用text与image两个键:
{ "messages": [ { "role": "user", "content": [ {"text": "Convert this image to watercolor style"}, {"image": "BASE64_IMAGE_DATA"} ] } ] }4.3 带生成参数的请求
生成参数统一包裹在请求 JSON 的extra_body中:
{ "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Convert to ink wash painting style"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}} ] } ], "extra_body": { "height": 1024, "width": 1024, "num_inference_steps": 50, "guidance_scale": 7.5, "seed": 42 } }OpenAI SDK 用法提示:SDK 下通过extra_body关键字参数传入,SDK 会自动合并到请求体顶层:
client.chat.completions.create( model="Qwen/Qwen-Image-Edit", messages=[...], extra_body={"num_inference_steps": 50, "guidance_scale": 7.5, "seed": 42}, )生成参数在不同客户端中的处理细节参见 Chat Completions API 指南。
五、分层图像生成(Qwen-Image-Layered)
Qwen-Image-Layered 从一张参考图 + 一段文本提示中,将图像分解为多层。启动方式:
vllm serve Qwen/Qwen-Image-Layered --omni --port 80935.1 curl 方式(逐层落盘)
IMG_B64=$(base64 -w0 input.png) curl -sS http://localhost:8093/v1/chat/completions \ -H "Content-Type: application/json" \ -d "$(jq -n --arg img "$IMG_B64" '{ messages: [{ role: "user", content: [ {type: "image_url", image_url: {url: ("data:image/png;base64," + $img)}}, {type: "text", text: "a rabbit"} ] }], extra_body: { num_inference_steps: 50, cfg_scale: 4.0, seed: 0, layers: 4, resolution: 640 } }')" \ | jq -r '.choices[0].message.content[] | .image_url.url | split(",")[1]' \ | while IFS= read -r b64; do ((i++)); echo "$b64" | base64 -d > "layer_${i}.png" done5.2 OpenAI SDK 方式
import base64 from openai import OpenAI client = OpenAI(base_url="http://localhost:8093/v1", api_key="none") with open("input.png", "rb") as f: img_b64 = base64.b64encode(f.read()).decode() response = client.chat.completions.create( model="Qwen/Qwen-Image-Layered", messages=[{ "role": "user", "content": [ {"type": "image_url", "image_url": { "url": f"data:image/png;base64,{img_b64}" }}, {"type": "text", "text": "a rabbit"}, ], }], extra_body={ "num_inference_steps": 50, "cfg_scale": 4.0, "seed": 0, "layers": 4, "resolution": 640, }, ) for i, item in enumerate(response.choices[0].message.content): _, b64_data = item.image_url.url.split(",", 1) with open(f"layer_{i}.png", "wb") as f: f.write(base64.b64decode(b64_data))5.3 Python requests 方式
import base64 import requests with open("input.png", "rb") as f: img_b64 = base64.b64encode(f.read()).decode() payload = { "messages": [{ "role": "user", "content": [ {"type": "image_url", "image_url": { "url": f"data:image/png;base64,{img_b64}" }}, {"type": "text", "text": "a rabbit"}, ], }], "extra_body": { "num_inference_steps": 50, "cfg_scale": 4.0, "seed": 0, "layers": 4, "resolution": 640, }, } resp = requests.post( "http://localhost:8093/v1/chat/completions", json=payload, timeout=600, ) data = resp.json() for i, item in enumerate(data["choices"][0]["message"]["content"]): _, b64_data = item["image_url"]["url"].split(",", 1) with open(f"layer_{i}.png", "wb") as f: f.write(base64.b64decode(b64_data))响应特点:分层生成的响应中,choices[0].message.content是一个数组,每个元素对应一层生成图像,逐个解码即可得到所有图层。
5.4 Qwen-Image-Layered 参数表
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
layers | int | 4 | 分解的层数 |
resolution | int | 640 | 维度计算分辨率(640 或 1024) |
cfg_scale | float | 4.0 | 无分类器引导强度(true_cfg_scale的别名) |
num_inference_steps | int | 50 | 去噪步数 |
seed | int | None | 随机种子,用于结果复现 |
六、多图编辑(Qwen-Image-Edit-2509)
多图编辑需要在content中按顺序提供多张图片(顺序有意义,模型按传入顺序理解各图角色):
{ "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Combine these images into a single scene"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."} }, {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."} } ] } ] }对应的多图命令行调用:
python openai_chat_client.py --input input1.png input2.png --prompt "Combine these images into a single scene" --output output.png从源码看,pipeline_qwen_image_edit_plus.py 中的_get_qwen_prompt_embeds专门实现了"Build image prompt template for multiple images",即多图编辑的提示词模板拼接逻辑,这也是 2509 版与基础版在底层实现上的关键差异。
七、生成参数总览
使用/v1/chat/completions时,这些参数放在extra_body中(curl 为 JSON 字面量键,SDK 为extra_body关键字参数);使用专用的/v1/images/edits端点时,支持的生成控制项作为顶层表单字段直接传入。对于图像尺寸与数量,应使用size和n,而不是height、width或num_outputs_per_prompt。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
height | int | None | 输出图像高度(像素) |
width | int | None | 输出图像宽度(像素) |
size | str | None | 输出图像尺寸(如"1024x1024") |
num_inference_steps | int | 50 | 去噪步数 |
guidance_scale | float | 1.0 | CFG 引导强度 |
seed | int | None | 随机种子(复现用) |
negative_prompt | str | None | 负向提示词 |
num_outputs_per_prompt | int | 1 | 生成的图像数量 |
layers | int | 4 | 层数(Qwen-Image-Layered) |
resolution | int | 640 | 分辨率,640 或 1024(Qwen-Image-Layered) |
参数透传机制:从 image_edit_api.md 的参数处理说明可知,vLLM-Omni 将参数直接透传给扩散流水线,不做模型级转换——未指定时使用模型自身默认值;API 层仅做基础类型与取值范围校验,模型不支持的参数可能被静默忽略,不兼容的值会由底层流水线报错。因此最佳实践是:先采用模型官方推荐参数,再按需微调。
模型专属参数示例:BAGEL 等模型可通过extra_body接收额外参数(如cfg_text_scale、cfg_img_scale、cfg_interval、cfg_renorm_type、cfg_renorm_min、timestep_shift等),完整列表参见 BAGEL-7B-MoT.md。
八、响应格式
编辑请求的响应遵循 OpenAI Chat Completions 结构,图像以 data URL 形式内嵌在choices[0].message.content[0].image_url.url:
{ "id": "chatcmpl-xxx", "created": 1234567890, "model": "Qwen/Qwen-Image-Edit", "choices": [{ "index": 0, "message": { "role": "assistant", "content": [{ "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }] }, "finish_reason": "stop" }], "usage": {...} }解码方式为:取url字符串,按,分割后对第二部分做base64 -d(curl 场景)或base64.b64decode(Python 场景),即可得到 PNG 字节流。
若希望以 OpenAI DALL-E 兼容的/v1/images/edits端点调用(multipart/form-data表单,支持image/url输入、size、n、output_format、output_compression、negative_prompt、num_inference_steps、guidance_scale、true_cfg_scale、seed、reference_image、mask_image等参数,以及 HunyuanImage3 多阶段流水线的stream=true流式返回),可参考 Image Edit API 文档。
九、常见编辑指令示例
| 指令 | 说明 |
|---|---|
Convert this image to watercolor style | 风格迁移(水彩) |
Convert the image to black and white | 去饱和(黑白) |
Enhance the color saturation | 颜色调整(饱和度增强) |
Convert to cartoon style | 卡通化 |
Add vintage filter effect | 滤镜效果(复古) |
Convert daytime scene to nighttime | 场景转换(昼夜) |
十、配套文件说明与源码索引
| 文件 | 说明 | 路径 |
|---|---|---|
run_server.sh | 服务启动脚本(支持MODEL/PORT环境变量) | run_server.sh |
run_curl_image_edit.sh | curl 图生图调用示例(支持指定输出文件与SERVER环境变量) | run_curl_image_edit.sh |
openai_chat_client.py | 命令行 Python 客户端(支持单图 / 多图 /--extra-body) | openai_chat_client.py |
gradio_demo.py | Gradio 交互界面(需[demo]extras) | gradio_demo.py |
延伸阅读:
- 完整请求 / 响应模式与
extra_body处理: chat_completions_api.md - DALL-E 兼容的专用编辑端点
/v1/images/edits: image_edit_api.md - Qwen-Image-Edit 在 H200 上的 TP=1 / TP=2 验证数据(冷/热延迟、峰值显存、分阶段耗时剖析): recipes/Qwen/Qwen-Image-Edit.md
- BAGEL 模型专属参数完整清单: recipes/Bagel/BAGEL-7B-MoT.md
- 底层扩散流水线实现: pipeline_qwen_image_edit.py、pipeline_qwen_image_edit_plus.py
【免费下载链接】vllm-omniA framework for efficient model inference with omni-modality models项目地址: https://gitcode.com/GitHub_Trending/vl/vllm-omni
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考