2025-12-19 06:11:54 +08:00
# Image-To-Image
2025-12-17 17:40:45 +08:00
2025-12-19 06:11:54 +08:00
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/image_to_image>.
2025-12-17 17:40:45 +08:00
This example demonstrates how to deploy Qwen-Image-Edit model for online image editing service using vLLM-Omni.
2025-12-22 21:29:40 +08:00
For **multi-image** input editing, use **Qwen-Image-Edit-2509** (QwenImageEditPlusPipeline) and send multiple images in the user message content.
2025-12-17 17:40:45 +08:00
## Start Server
### Basic Start
```bash
vllm serve Qwen/Qwen-Image-Edit --omni --port 8092
```
2026-02-11 06:00:43 +08:00
!!! note
If you encounter Out-of-Memory (OOM) issues or have limited GPU memory, you can enable VAE slicing and tiling to reduce memory usage, --vae-use-slicing --vae-use-tiling
2025-12-22 21:29:40 +08:00
### Multi-Image Edit (Qwen-Image-Edit-2509)
```bash
vllm serve Qwen/Qwen-Image-Edit-2509 --omni --port 8092
```
2025-12-17 17:40:45 +08:00
### Start with Parameters
2025-12-19 06:11:54 +08:00
2025-12-17 17:40:45 +08:00
Or use the startup script:
```bash
bash run_server.sh
```
2025-12-22 21:29:40 +08:00
To serve Qwen-Image-Edit-2509 with the script:
```bash
MODEL = Qwen/Qwen-Image-Edit-2509 bash run_server.sh
```
2025-12-17 17:40:45 +08:00
## API Calls
### Method 1: Using curl (Image Editing)
```bash
# Image editing
bash run_curl_image_edit.sh input.png "Convert this image to watercolor style"
# Or execute directly
IMG_B64 = $( base64 -w0 input.png)
2026-01-04 20:45:28 +08:00
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
2026-03-26 23:12:16 +08:00
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
2025-12-17 17:40:45 +08:00
```
2026-03-26 23:12:16 +08:00
### Method 2: Using OpenAI Python SDK
```python
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 ))
```
### Method 3: Using Python Client Script
2025-12-17 17:40:45 +08:00
```bash
python openai_chat_client.py --input input.png --prompt "Convert to oil painting style" --output output.png
2025-12-22 21:29:40 +08:00
# Multi-image editing (Qwen-Image-Edit-2509 server required)
python openai_chat_client.py --input input1.png input2.png --prompt "Combine these images into a single scene" --output output.png
2025-12-17 17:40:45 +08:00
```
2026-03-26 23:12:16 +08:00
### Method 4: Using Gradio Demo
2025-12-17 17:40:45 +08:00
2026-03-29 00:33:55 +08:00
!!! note "Gradio is an optional dependency"
The Gradio demo requires the `[demo]` extras. Install them first:
```bash
pip install 'vllm-omni[demo]'
` ``
Or, if installing from source: ` pip install -e '.[demo]'`
2025-12-17 17:40:45 +08:00
` ``bash
python gradio_demo.py
# Visit http://localhost:7861
` ``
## Request Format
### Image Editing (Using image_url Format)
` ``json
{
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Convert this image to watercolor style"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
]
}
]
}
` ``
### Image Editing (Using Simplified image Format)
` ``json
{
"messages": [
{
"role": "user",
"content": [
{"text": "Convert this image to watercolor style"},
{"image": "BASE64_IMAGE_DATA"}
]
}
]
}
` ``
### Image Editing with Parameters
2026-03-26 23:12:16 +08:00
Wrap generation parameters inside ` extra_body` in the request JSON:
2025-12-17 17:40:45 +08:00
` ``json
{
"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
}
}
` ``
2026-03-26 23:12:16 +08:00
!!! tip "Using the OpenAI SDK"
When using the OpenAI Python SDK, pass these parameters via the ` extra_body`
keyword argument. The SDK merges them into the top-level request body automatically:
` ``python
client.chat.completions.create(
model="Qwen/Qwen-Image-Edit",
messages=[...],
extra_body={"num_inference_steps": 50, "guidance_scale": 7.5, "seed": 42},
)
` ``
For details on how generation parameters are handled across different clients, see the
2026-05-19 11:30:33 +08:00
[Chat Completions API guide](../../../../serving/chat_completions_api.md).
2026-03-26 23:12:16 +08:00
### Layered Image Generation (Qwen-Image-Layered)
Qwen-Image-Layered generates multiple decomposed layers from a reference image and a text prompt.
Start the server with:
` ``bash
vllm serve Qwen/Qwen-Image-Layered --omni --port 8093
` ``
=== "curl"
` ``bash
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"
done
` ``
=== "OpenAI SDK"
` ``python
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))
` ``
=== "Python requests"
` ``python
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))
` ``
The response contains multiple images in ` choices[0].message.content` — one per generated layer.
#### Qwen-Image-Layered Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ` layers` | int | 4 | Number of layers to decompose |
| ` resolution` | int | 640 | Resolution for dimension calculation (640 or 1024) |
| ` cfg_scale` | float | 4.0 | Classifier-free guidance scale (alias for ` true_cfg_scale`) |
| ` num_inference_steps` | int | 50 | Number of denoising steps |
| ` seed` | int | None | Random seed for reproducibility |
2025-12-22 21:29:40 +08:00
### Multi-Image Editing (Qwen-Image-Edit-2509)
Provide multiple images in ` content` (order matters):
` ``json
{
"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,..."} }
]
}
]
}
` ``
2026-03-26 23:12:16 +08:00
## Generation Parameters
When using ` /v1/chat/completions`, pass these inside ` extra_body` in the curl
JSON, or via the ` extra_body` keyword argument in the OpenAI Python SDK (see the
2026-05-19 11:30:33 +08:00
[Chat Completions API guide](../../../../serving/chat_completions_api.md)).
2026-03-26 23:12:16 +08:00
When using the dedicated [` /v1/images/edits`](../../../../serving/image_edit_api.md)
endpoint, pass the supported generation controls as top-level form fields
directly. For image dimensions and count, use ` size` and ` n` rather than
` height`, ` width`, or ` num_outputs_per_prompt`.
2025-12-17 17:40:45 +08:00
2025-12-19 06:11:54 +08:00
| Parameter | Type | Default | Description |
| ------------------------ | ----- | ------- | ------------------------------------- |
| ` height` | int | None | Output image height in pixels |
| ` width` | int | None | Output image width in pixels |
| ` size` | str | None | Output image size (e.g., "1024x1024") |
| ` num_inference_steps` | int | 50 | Number of denoising steps |
2026-03-26 23:12:16 +08:00
| ` guidance_scale` | float | 1.0 | CFG guidance scale |
2025-12-19 06:11:54 +08:00
| ` seed` | int | None | Random seed (reproducible) |
| ` negative_prompt` | str | None | Negative prompt |
| ` num_outputs_per_prompt` | int | 1 | Number of images to generate |
2026-03-26 23:12:16 +08:00
| ` layers` | int | 4 | Number of layers (Qwen-Image-Layered) |
| ` resolution` | int | 640 | Resolution, 640 or 1024 (Qwen-Image-Layered) |
2025-12-17 17:40:45 +08:00
## Response Format
` ``json
{
"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": {...}
}
` ``
## Common Editing Instructions Examples
2025-12-19 06:11:54 +08:00
| Instruction | Description |
| ---------------------------------------- | ---------------- |
| ` Convert this image to watercolor style` | Style transfer |
| ` Convert the image to black and white` | Desaturation |
| ` Enhance the color saturation` | Color adjustment |
| ` Convert to cartoon style` | Cartoonization |
| ` Add vintage filter effect` | Filter effect |
| ` Convert daytime scene to nighttime` | Scene conversion |
2025-12-17 17:40:45 +08:00
## File Description
2025-12-19 06:11:54 +08:00
| File | Description |
| ------------------------ | ---------------------------- |
| ` run_server.sh` | Server startup script |
| ` run_curl_image_edit.sh` | curl image editing example |
| ` openai_chat_client.py` | Python client |
| ` gradio_demo.py` | Gradio interactive interface |
2025-12-17 17:40:45 +08:00
## Example materials
??? abstract "gradio_demo.py"
` `````py
2025-12-19 06:11:54 +08:00
--8<-- "examples/online_serving/image_to_image/gradio_demo.py"
2025-12-17 17:40:45 +08:00
` `````
??? abstract "openai_chat_client.py"
` `````py
2025-12-19 06:11:54 +08:00
--8<-- "examples/online_serving/image_to_image/openai_chat_client.py"
2025-12-17 17:40:45 +08:00
` `````
2026-01-06 22:32:46 +08:00
??? abstract "run_curl_image_edit.sh"
` `````sh
--8<-- "examples/online_serving/image_to_image/run_curl_image_edit.sh"
` `````
2025-12-17 17:40:45 +08:00
??? abstract "run_server.sh"
` `````sh
2025-12-19 06:11:54 +08:00
--8<-- "examples/online_serving/image_to_image/run_server.sh"
2025-12-17 17:40:45 +08:00
` `````